1 /* GTK - The GTK+ Toolkit
2 * gtkcupsutils.h: Statemachine implementation of POST and GET
3 * cups calls which can be used to create a non-blocking cups API
4 * Copyright (C) 2006, 2007 Red Hat, Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
24 #include "gtkcupsutils.h"
28 #include <sys/types.h>
33 typedef void (*GtkCupsRequestStateFunc) (GtkCupsRequest *request);
35 static void _connect (GtkCupsRequest *request);
36 static void _post_send (GtkCupsRequest *request);
37 static void _post_write_request (GtkCupsRequest *request);
38 static void _post_write_data (GtkCupsRequest *request);
39 static void _post_check (GtkCupsRequest *request);
40 static void _post_read_response (GtkCupsRequest *request);
42 static void _get_send (GtkCupsRequest *request);
43 static void _get_check (GtkCupsRequest *request);
44 static void _get_read_data (GtkCupsRequest *request);
50 GtkCupsErrorType error_type;
52 /* some error types like HTTP_ERROR have a status and a code */
57 guint is_ipp_response : 1;
61 #define _GTK_CUPS_MAX_ATTEMPTS 10
62 #define _GTK_CUPS_MAX_CHUNK_SIZE 8192
64 static GtkCupsRequestStateFunc post_states[] = {
73 static GtkCupsRequestStateFunc get_states[] = {
81 gtk_cups_result_set_error (GtkCupsResult *result,
82 GtkCupsErrorType error_type,
85 const char *error_msg,
90 result->is_ipp_response = FALSE;
91 result->is_error = TRUE;
92 result->error_type = error_type;
93 result->error_status = error_status;
94 result->error_code = error_code;
96 va_start (args, error_msg);
97 result->error_msg = g_strdup_vprintf (error_msg, args);
102 gtk_cups_request_new (http_t *connection,
103 GtkCupsRequestType req_type,
107 const char *resource)
109 GtkCupsRequest *request;
110 cups_lang_t *language;
112 request = g_new0 (GtkCupsRequest, 1);
113 request->result = g_new0 (GtkCupsResult, 1);
115 request->result->error_msg = NULL;
116 request->result->ipp_response = NULL;
118 request->result->is_error = FALSE;
119 request->result->is_ipp_response = FALSE;
121 request->type = req_type;
122 request->state = GTK_CUPS_REQUEST_START;
125 request->server = g_strdup (server);
127 request->server = g_strdup (cupsServer ());
131 request->resource = g_strdup (resource);
133 request->resource = g_strdup ("/");
135 if (connection != NULL)
137 request->http = connection;
138 request->own_http = FALSE;
142 request->http = NULL;
143 request->http = httpConnectEncrypt (request->server,
148 httpBlocking (request->http, 0);
150 request->own_http = TRUE;
153 request->last_status = HTTP_CONTINUE;
155 request->attempts = 0;
156 request->data_io = data_io;
158 request->ipp_request = ippNew ();
159 request->ipp_request->request.op.operation_id = operation_id;
160 request->ipp_request->request.op.request_id = 1;
162 language = cupsLangDefault ();
164 gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
165 "attributes-charset",
168 gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
169 "attributes-natural-language",
170 NULL, language->language);
172 gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
173 "requesting-user-name",
176 cupsLangFree (language);
182 gtk_cups_result_free (GtkCupsResult *result)
184 g_free (result->error_msg);
186 if (result->ipp_response)
187 ippDelete (result->ipp_response);
193 gtk_cups_request_free (GtkCupsRequest *request)
195 if (request->own_http)
198 httpClose (request->http);
201 if (request->ipp_request)
202 ippDelete (request->ipp_request);
204 g_free (request->server);
205 g_free (request->resource);
207 gtk_cups_result_free (request->result);
213 gtk_cups_request_read_write (GtkCupsRequest *request)
215 if (request->type == GTK_CUPS_POST)
216 post_states[request->state] (request);
217 else if (request->type == GTK_CUPS_GET)
218 get_states[request->state] (request);
220 if (request->attempts > _GTK_CUPS_MAX_ATTEMPTS &&
221 request->state != GTK_CUPS_REQUEST_DONE)
223 /* TODO: should add a status or error code for too many failed attempts */
224 gtk_cups_result_set_error (request->result,
225 GTK_CUPS_ERROR_GENERAL,
228 "Too many failed attempts");
230 request->state = GTK_CUPS_REQUEST_DONE;
231 request->poll_state = GTK_CUPS_HTTP_IDLE;
234 if (request->state == GTK_CUPS_REQUEST_DONE)
236 request->poll_state = GTK_CUPS_HTTP_IDLE;
244 gtk_cups_request_get_poll_state (GtkCupsRequest *request)
246 return request->poll_state;
252 gtk_cups_request_get_result (GtkCupsRequest *request)
254 return request->result;
258 gtk_cups_request_ipp_add_string (GtkCupsRequest *request,
265 ippAddString (request->ipp_request,
274 gtk_cups_request_ipp_add_strings (GtkCupsRequest *request,
280 const char *const *values)
282 ippAddStrings (request->ipp_request,
299 static const ipp_option_t ipp_options[] = {
300 { "blackplot", IPP_TAG_BOOLEAN },
301 { "brightness", IPP_TAG_INTEGER },
302 { "columns", IPP_TAG_INTEGER },
303 { "copies", IPP_TAG_INTEGER },
304 { "finishings", IPP_TAG_ENUM },
305 { "fitplot", IPP_TAG_BOOLEAN },
306 { "gamma", IPP_TAG_INTEGER },
307 { "hue", IPP_TAG_INTEGER },
308 { "job-k-limit", IPP_TAG_INTEGER },
309 { "job-page-limit", IPP_TAG_INTEGER },
310 { "job-priority", IPP_TAG_INTEGER },
311 { "job-quota-period", IPP_TAG_INTEGER },
312 { "landscape", IPP_TAG_BOOLEAN },
313 { "media", IPP_TAG_KEYWORD },
314 { "mirror", IPP_TAG_BOOLEAN },
315 { "natural-scaling", IPP_TAG_INTEGER },
316 { "number-up", IPP_TAG_INTEGER },
317 { "orientation-requested", IPP_TAG_ENUM },
318 { "page-bottom", IPP_TAG_INTEGER },
319 { "page-left", IPP_TAG_INTEGER },
320 { "page-ranges", IPP_TAG_RANGE },
321 { "page-right", IPP_TAG_INTEGER },
322 { "page-top", IPP_TAG_INTEGER },
323 { "penwidth", IPP_TAG_INTEGER },
324 { "ppi", IPP_TAG_INTEGER },
325 { "prettyprint", IPP_TAG_BOOLEAN },
326 { "printer-resolution", IPP_TAG_RESOLUTION },
327 { "print-quality", IPP_TAG_ENUM },
328 { "saturation", IPP_TAG_INTEGER },
329 { "scaling", IPP_TAG_INTEGER },
330 { "sides", IPP_TAG_KEYWORD },
331 { "wrap", IPP_TAG_BOOLEAN }
336 _find_option_tag (const gchar *option)
338 int lower_bound, upper_bound, num_options;
342 result = IPP_TAG_ZERO;
345 upper_bound = num_options = (int) G_N_ELEMENTS (ipp_options) - 1;
350 current_option = (int) (((upper_bound - lower_bound) / 2) + lower_bound);
352 match = strcasecmp (option, ipp_options[current_option].name);
355 result = ipp_options[current_option].value_tag;
360 upper_bound = current_option - 1;
364 lower_bound = current_option + 1;
367 if (upper_bound == lower_bound && upper_bound == current_option)
373 if (lower_bound > num_options)
376 if (upper_bound < lower_bound)
382 * Note that this function uses IPP_TAG_JOB, so it is
383 * only suitable for IPP Group 2 attributes.
387 gtk_cups_request_encode_option (GtkCupsRequest *request,
391 ipp_tag_t option_tag;
393 g_return_if_fail (option != NULL);
394 g_return_if_fail (value != NULL);
396 option_tag = _find_option_tag (option);
398 if (option_tag == IPP_TAG_ZERO)
400 option_tag = IPP_TAG_NAME;
401 if (strcasecmp (value, "true") == 0 ||
402 strcasecmp (value, "false") == 0)
404 option_tag = IPP_TAG_BOOLEAN;
410 case IPP_TAG_INTEGER:
412 ippAddInteger (request->ipp_request,
416 strtol (value, NULL, 0));
419 case IPP_TAG_BOOLEAN:
423 if (strcasecmp (value, "true") == 0 ||
424 strcasecmp (value, "on") == 0 ||
425 strcasecmp (value, "yes") == 0)
430 ippAddBoolean (request->ipp_request,
450 lower = strtol (value, &s, 0);
455 upper = strtol (s + 1, NULL, 0);
462 ippAddRange (request->ipp_request,
471 case IPP_TAG_RESOLUTION:
478 xres = strtol (value, &s, 0);
481 yres = strtol (s + 1, &s, 0);
485 if (strcasecmp (s, "dpc") == 0)
486 units = IPP_RES_PER_CM;
488 units = IPP_RES_PER_INCH;
490 ippAddResolution (request->ipp_request,
508 values = g_strdup (value);
512 for (s = values, next = s; *s != '\0'; s++)
514 if (in_quotes != 2 && *s == '\'')
516 /* skip quoted value */
522 else if (in_quotes != 1 && *s == '\"')
524 /* skip quoted value */
530 else if (in_quotes == 0 && *s == ',')
532 /* found delimiter, add to value array */
535 strings = g_ptr_array_new ();
536 g_ptr_array_add (strings, next);
539 else if (in_quotes == 0 && *s == '\\' && s[1] != '\0')
541 /* skip escaped character */
549 ippAddString (request->ipp_request,
558 /* multiple values */
561 g_ptr_array_add (strings, next);
563 ippAddStrings (request->ipp_request,
569 (const char **) strings->pdata);
570 g_ptr_array_free (strings, TRUE);
582 _connect (GtkCupsRequest *request)
584 request->poll_state = GTK_CUPS_HTTP_IDLE;
586 if (request->http == NULL)
588 request->http = httpConnectEncrypt (request->server,
592 if (request->http == NULL)
596 httpBlocking (request->http, 0);
598 request->own_http = TRUE;
602 request->attempts = 0;
605 /* we always write to the socket after we get
607 request->poll_state = GTK_CUPS_HTTP_WRITE;
612 _post_send (GtkCupsRequest *request)
615 struct stat data_info;
618 g_print ("CUPS Backend: %s\n", G_STRFUNC));
620 request->poll_state = GTK_CUPS_HTTP_WRITE;
622 if (request->data_io != NULL)
624 fstat (g_io_channel_unix_get_fd (request->data_io), &data_info);
625 sprintf (length, "%lu", (unsigned long) (ippLength (request->ipp_request) + data_info.st_size));
628 sprintf (length, "%lu", (unsigned long) ippLength (request->ipp_request));
630 httpClearFields (request->http);
631 httpSetField (request->http, HTTP_FIELD_CONTENT_LENGTH, length);
632 httpSetField (request->http, HTTP_FIELD_CONTENT_TYPE, "application/ipp");
633 #ifdef HAVE_HTTPGETAUTHSTRING
634 httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString (request->http));
636 #ifdef HAVE_HTTP_AUTHSTRING
637 httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring);
641 if (httpPost (request->http, request->resource))
643 if (httpReconnect (request->http))
645 request->state = GTK_CUPS_POST_DONE;
646 request->poll_state = GTK_CUPS_HTTP_IDLE;
648 /* TODO: should add a status or error code for failed post */
649 gtk_cups_result_set_error (request->result,
650 GTK_CUPS_ERROR_GENERAL,
660 request->attempts = 0;
662 request->state = GTK_CUPS_POST_WRITE_REQUEST;
663 request->ipp_request->state = IPP_IDLE;
667 _post_write_request (GtkCupsRequest *request)
669 ipp_state_t ipp_status;
672 g_print ("CUPS Backend: %s\n", G_STRFUNC));
674 request->poll_state = GTK_CUPS_HTTP_WRITE;
676 ipp_status = ippWrite (request->http, request->ipp_request);
678 if (ipp_status == IPP_ERROR)
680 int cups_error = cupsLastError ();
681 request->state = GTK_CUPS_POST_DONE;
682 request->poll_state = GTK_CUPS_HTTP_IDLE;
684 gtk_cups_result_set_error (request->result,
689 ippErrorString (cups_error));
693 if (ipp_status == IPP_DATA)
695 if (request->data_io != NULL)
696 request->state = GTK_CUPS_POST_WRITE_DATA;
699 request->state = GTK_CUPS_POST_CHECK;
700 request->poll_state = GTK_CUPS_HTTP_READ;
706 _post_write_data (GtkCupsRequest *request)
709 char buffer[_GTK_CUPS_MAX_CHUNK_SIZE];
710 http_status_t http_status;
713 g_print ("CUPS Backend: %s\n", G_STRFUNC));
715 request->poll_state = GTK_CUPS_HTTP_WRITE;
717 if (httpCheck (request->http))
718 http_status = httpUpdate (request->http);
720 http_status = request->last_status;
722 request->last_status = http_status;
725 if (http_status == HTTP_CONTINUE || http_status == HTTP_OK)
734 g_io_channel_read_chars (request->data_io,
736 _GTK_CUPS_MAX_CHUNK_SIZE,
740 if (io_status == G_IO_STATUS_ERROR)
742 request->state = GTK_CUPS_POST_DONE;
743 request->poll_state = GTK_CUPS_HTTP_IDLE;
745 gtk_cups_result_set_error (request->result,
749 "Error reading from cache file: %s",
752 g_error_free (error);
755 else if (bytes == 0 && io_status == G_IO_STATUS_EOF)
757 request->state = GTK_CUPS_POST_CHECK;
758 request->poll_state = GTK_CUPS_HTTP_READ;
760 request->attempts = 0;
765 #if HAVE_CUPS_API_1_2
766 if (httpWrite2 (request->http, buffer, bytes) < bytes)
768 if (httpWrite (request->http, buffer, (int) bytes) < bytes)
769 #endif /* HAVE_CUPS_API_1_2 */
773 http_errno = httpError (request->http);
775 request->state = GTK_CUPS_POST_DONE;
776 request->poll_state = GTK_CUPS_HTTP_IDLE;
778 gtk_cups_result_set_error (request->result,
782 "Error writing to socket in Post %s",
783 g_strerror (http_errno));
794 _post_check (GtkCupsRequest *request)
796 http_status_t http_status;
798 http_status = request->last_status;
801 g_print ("CUPS Backend: %s - status %i\n", G_STRFUNC, http_status));
803 request->poll_state = GTK_CUPS_HTTP_READ;
805 if (http_status == HTTP_CONTINUE)
809 else if (http_status == HTTP_UNAUTHORIZED)
811 /* TODO: callout for auth */
812 g_warning ("NOT IMPLEMENTED: We need to prompt for authorization");
813 request->state = GTK_CUPS_POST_DONE;
814 request->poll_state = GTK_CUPS_HTTP_IDLE;
816 /* TODO: create a not implemented error code */
817 gtk_cups_result_set_error (request->result,
818 GTK_CUPS_ERROR_GENERAL,
821 "Can't prompt for authorization");
824 else if (http_status == HTTP_ERROR)
826 int error = httpError (request->http);
828 if (error != WSAENETDOWN && error != WSAENETUNREACH)
830 if (error != ENETDOWN && error != ENETUNREACH)
831 #endif /* G_OS_WIN32 */
838 request->state = GTK_CUPS_POST_DONE;
839 request->poll_state = GTK_CUPS_HTTP_IDLE;
841 gtk_cups_result_set_error (request->result,
845 "Unknown HTTP error");
850 else if (http_status == HTTP_UPGRADE_REQUIRED)
852 /* Flush any error message... */
853 httpFlush (request->http);
855 cupsSetEncryption (HTTP_ENCRYPT_REQUIRED);
856 request->state = GTK_CUPS_POST_CONNECT;
859 httpReconnect (request->http);
861 /* Upgrade with encryption... */
862 httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED);
867 else if (http_status != HTTP_OK)
871 http_errno = httpError (request->http);
873 if (http_errno == EPIPE)
874 request->state = GTK_CUPS_POST_CONNECT;
877 request->state = GTK_CUPS_POST_DONE;
878 gtk_cups_result_set_error (request->result,
882 "HTTP Error in POST %s",
883 g_strerror (http_errno));
884 request->poll_state = GTK_CUPS_HTTP_IDLE;
886 httpFlush (request->http);
890 request->poll_state = GTK_CUPS_HTTP_IDLE;
892 httpFlush (request->http);
894 request->last_status = HTTP_CONTINUE;
895 httpClose (request->http);
896 request->http = NULL;
901 request->state = GTK_CUPS_POST_READ_RESPONSE;
906 http_status = HTTP_CONTINUE;
908 if (httpCheck (request->http))
909 http_status = httpUpdate (request->http);
911 request->last_status = http_status;
915 _post_read_response (GtkCupsRequest *request)
917 ipp_state_t ipp_status;
920 g_print ("CUPS Backend: %s\n", G_STRFUNC));
922 request->poll_state = GTK_CUPS_HTTP_READ;
924 if (request->result->ipp_response == NULL)
925 request->result->ipp_response = ippNew();
927 ipp_status = ippRead (request->http,
928 request->result->ipp_response);
930 if (ipp_status == IPP_ERROR)
932 int ipp_error = cupsLastError ();
933 gtk_cups_result_set_error (request->result,
938 ippErrorString (ipp_error));
940 ippDelete (request->result->ipp_response);
941 request->result->ipp_response = NULL;
943 request->state = GTK_CUPS_POST_DONE;
944 request->poll_state = GTK_CUPS_HTTP_IDLE;
946 else if (ipp_status == IPP_DATA)
948 request->state = GTK_CUPS_POST_DONE;
949 request->poll_state = GTK_CUPS_HTTP_IDLE;
954 _get_send (GtkCupsRequest *request)
957 g_print ("CUPS Backend: %s\n", G_STRFUNC));
959 request->poll_state = GTK_CUPS_HTTP_WRITE;
961 if (request->data_io == NULL)
963 gtk_cups_result_set_error (request->result,
966 G_IO_CHANNEL_ERROR_FAILED,
967 "Get requires an open io channel");
969 request->state = GTK_CUPS_GET_DONE;
970 request->poll_state = GTK_CUPS_HTTP_IDLE;
975 httpClearFields (request->http);
976 #ifdef HAVE_HTTP_AUTHSTRING
977 httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring);
980 if (httpGet (request->http, request->resource))
982 if (httpReconnect (request->http))
984 request->state = GTK_CUPS_GET_DONE;
985 request->poll_state = GTK_CUPS_HTTP_IDLE;
987 /* TODO: should add a status or error code for failed GET */
988 gtk_cups_result_set_error (request->result,
989 GTK_CUPS_ERROR_GENERAL,
999 request->attempts = 0;
1001 request->state = GTK_CUPS_GET_CHECK;
1002 request->poll_state = GTK_CUPS_HTTP_READ;
1004 request->ipp_request->state = IPP_IDLE;
1008 _get_check (GtkCupsRequest *request)
1010 http_status_t http_status;
1013 g_print ("CUPS Backend: %s\n", G_STRFUNC));
1015 http_status = request->last_status;
1017 request->poll_state = GTK_CUPS_HTTP_READ;
1019 if (http_status == HTTP_CONTINUE)
1023 else if (http_status == HTTP_UNAUTHORIZED)
1025 /* TODO: callout for auth */
1026 g_warning ("NOT IMPLEMENTED: We need to prompt for authorization in a non blocking manner");
1027 request->state = GTK_CUPS_GET_DONE;
1028 request->poll_state = GTK_CUPS_HTTP_IDLE;
1030 /* TODO: should add a status or error code for not implemented */
1031 gtk_cups_result_set_error (request->result,
1032 GTK_CUPS_ERROR_GENERAL,
1035 "Can't prompt for authorization");
1038 else if (http_status == HTTP_UPGRADE_REQUIRED)
1040 /* Flush any error message... */
1041 httpFlush (request->http);
1043 cupsSetEncryption (HTTP_ENCRYPT_REQUIRED);
1044 request->state = GTK_CUPS_POST_CONNECT;
1047 httpReconnect (request->http);
1049 /* Upgrade with encryption... */
1050 httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED);
1052 request->attempts++;
1055 else if (http_status != HTTP_OK)
1059 http_errno = httpError (request->http);
1061 if (http_errno == EPIPE)
1062 request->state = GTK_CUPS_GET_CONNECT;
1065 request->state = GTK_CUPS_GET_DONE;
1066 gtk_cups_result_set_error (request->result,
1067 GTK_CUPS_ERROR_HTTP,
1070 "HTTP Error in GET %s",
1071 g_strerror (http_errno));
1072 request->poll_state = GTK_CUPS_HTTP_IDLE;
1073 httpFlush (request->http);
1078 request->poll_state = GTK_CUPS_HTTP_IDLE;
1079 httpFlush (request->http);
1080 httpClose (request->http);
1081 request->last_status = HTTP_CONTINUE;
1082 request->http = NULL;
1088 request->state = GTK_CUPS_GET_READ_DATA;
1093 http_status = HTTP_CONTINUE;
1095 if (httpCheck (request->http))
1096 http_status = httpUpdate (request->http);
1098 request->last_status = http_status;
1103 _get_read_data (GtkCupsRequest *request)
1105 char buffer[_GTK_CUPS_MAX_CHUNK_SIZE];
1107 gsize bytes_written;
1108 GIOStatus io_status;
1112 g_print ("CUPS Backend: %s\n", G_STRFUNC));
1116 request->poll_state = GTK_CUPS_HTTP_READ;
1118 #if HAVE_CUPS_API_1_2
1119 bytes = httpRead2 (request->http, buffer, sizeof (buffer));
1121 bytes = httpRead (request->http, buffer, sizeof (buffer));
1122 #endif /* HAVE_CUPS_API_1_2 */
1125 g_print ("CUPS Backend: %i bytes read\n", bytes));
1129 request->state = GTK_CUPS_GET_DONE;
1130 request->poll_state = GTK_CUPS_HTTP_IDLE;
1136 g_io_channel_write_chars (request->data_io,
1142 if (io_status == G_IO_STATUS_ERROR)
1144 request->state = GTK_CUPS_POST_DONE;
1145 request->poll_state = GTK_CUPS_HTTP_IDLE;
1147 gtk_cups_result_set_error (request->result,
1152 g_error_free (error);
1157 gtk_cups_request_is_done (GtkCupsRequest *request)
1159 return (request->state == GTK_CUPS_REQUEST_DONE);
1163 gtk_cups_result_is_error (GtkCupsResult *result)
1165 return result->is_error;
1169 gtk_cups_result_get_response (GtkCupsResult *result)
1171 return result->ipp_response;
1175 gtk_cups_result_get_error_type (GtkCupsResult *result)
1177 return result->error_type;
1181 gtk_cups_result_get_error_status (GtkCupsResult *result)
1183 return result->error_status;
1187 gtk_cups_result_get_error_code (GtkCupsResult *result)
1189 return result->error_code;
1193 gtk_cups_result_get_error_string (GtkCupsResult *result)
1195 return result->error_msg;