X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=modules%2Fprintbackends%2Fcups%2Fgtkcupsutils.c;h=f0bb951b5d4c62a00d178e53464bf8b8916de97a;hb=eed9e72e831e54f4a405474316835685f3a61e79;hp=a19639783d377a4e16c11409a1f4fb523463c1bb;hpb=7e480bb4b8d0004cc23a9d3e28deb3297a37c1cf;p=~andy%2Fgtk diff --git a/modules/printbackends/cups/gtkcupsutils.c b/modules/printbackends/cups/gtkcupsutils.c index a19639783..f0bb951b5 100644 --- a/modules/printbackends/cups/gtkcupsutils.c +++ b/modules/printbackends/cups/gtkcupsutils.c @@ -1,7 +1,7 @@ /* GTK - The GIMP Toolkit * gtkcupsutils.h: Statemachine implementation of POST and GET - * cup calls which can be used to create a non-blocking cups API - * Copyright (C) 2003, Red Hat, Inc. + * cups calls which can be used to create a non-blocking cups API + * Copyright (C) 2006, 2007 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -14,14 +14,12 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ -#include "gtkcupsutils.h" #include "config.h" -#include "gtkdebug.h" +#include +#include "gtkcupsutils.h" #include #include @@ -29,10 +27,8 @@ #include #include #include - -#if CUPS_VERSION_MAJOR > 1 || (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR > 1) || (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR == 1 && CUPS_VERSION_PATCH >= 20) -#define HAVE_HTTP_AUTHSTRING 1 -#endif +#include +#include typedef void (*GtkCupsRequestStateFunc) (GtkCupsRequest *request); @@ -41,16 +37,23 @@ static void _post_send (GtkCupsRequest *request); static void _post_write_request (GtkCupsRequest *request); static void _post_write_data (GtkCupsRequest *request); static void _post_check (GtkCupsRequest *request); +static void _post_auth (GtkCupsRequest *request); static void _post_read_response (GtkCupsRequest *request); static void _get_send (GtkCupsRequest *request); static void _get_check (GtkCupsRequest *request); +static void _get_auth (GtkCupsRequest *request); static void _get_read_data (GtkCupsRequest *request); struct _GtkCupsResult { gchar *error_msg; ipp_t *ipp_response; + GtkCupsErrorType error_type; + + /* some error types like HTTP_ERROR have a status and a code */ + int error_status; + int error_code; guint is_error : 1; guint is_ipp_response : 1; @@ -60,28 +63,61 @@ struct _GtkCupsResult #define _GTK_CUPS_MAX_ATTEMPTS 10 #define _GTK_CUPS_MAX_CHUNK_SIZE 8192 -GtkCupsRequestStateFunc post_states[] = {_connect, - _post_send, - _post_write_request, - _post_write_data, - _post_check, - _post_read_response}; +static GtkCupsRequestStateFunc post_states[] = { + _connect, + _post_send, + _post_write_request, + _post_write_data, + _post_check, + _post_auth, + _post_read_response +}; + +static GtkCupsRequestStateFunc get_states[] = { + _connect, + _get_send, + _get_check, + _get_auth, + _get_read_data +}; -GtkCupsRequestStateFunc get_states[] = {_connect, - _get_send, - _get_check, - _get_read_data}; +#ifndef HAVE_CUPS_API_1_6 +#define ippSetOperation(ipp_request, ipp_op_id) ipp_request->request.op.operation_id = ipp_op_id +#define ippSetRequestId(ipp_request, ipp_rq_id) ipp_request->request.op.request_id = ipp_rq_id +#define ippSetState(ipp_request, ipp_state) ipp_request->state = ipp_state +#define ippGetString(attr, index, foo) attr->values[index].string.text +#define ippGetCount(attr) attr->num_values + +int +ippSetVersion (ipp_t *ipp, + int major, + int minor) +{ + if (!ipp || major < 0 || minor < 0) + return 0; + + ipp->request.any.version[0] = major; + ipp->request.any.version[1] = minor; + + return 1; +} +#endif static void -gtk_cups_result_set_error (GtkCupsResult *result, - const char *error_msg, +gtk_cups_result_set_error (GtkCupsResult *result, + GtkCupsErrorType error_type, + int error_status, + int error_code, + const char *error_msg, ...) { va_list args; result->is_ipp_response = FALSE; - result->is_error = TRUE; + result->error_type = error_type; + result->error_status = error_status; + result->error_code = error_code; va_start (args, error_msg); result->error_msg = g_strdup_vprintf (error_msg, args); @@ -89,12 +125,13 @@ gtk_cups_result_set_error (GtkCupsResult *result, } GtkCupsRequest * -gtk_cups_request_new (http_t *connection, - GtkCupsRequestType req_type, - gint operation_id, - GIOChannel *data_io, - const char *server, - const char *resource) +gtk_cups_request_new_with_username (http_t *connection, + GtkCupsRequestType req_type, + gint operation_id, + GIOChannel *data_io, + const char *server, + const char *resource, + const char *username) { GtkCupsRequest *request; cups_lang_t *language; @@ -111,10 +148,12 @@ gtk_cups_request_new (http_t *connection, request->type = req_type; request->state = GTK_CUPS_REQUEST_START; + request->password_state = GTK_CUPS_PASSWORD_NONE; + if (server) request->server = g_strdup (server); else - request->server = g_strdup (cupsServer()); + request->server = g_strdup (cupsServer ()); if (resource) @@ -130,7 +169,9 @@ gtk_cups_request_new (http_t *connection, else { request->http = NULL; - request->http = httpConnectEncrypt (request->server, ippPort(), cupsEncryption()); + request->http = httpConnectEncrypt (request->server, + ippPort (), + cupsEncryption ()); if (request->http) httpBlocking (request->http, 0); @@ -143,9 +184,9 @@ gtk_cups_request_new (http_t *connection, request->attempts = 0; request->data_io = data_io; - request->ipp_request = ippNew(); - request->ipp_request->request.op.operation_id = operation_id; - request->ipp_request->request.op.request_id = 1; + request->ipp_request = ippNew (); + ippSetOperation (request->ipp_request, operation_id); + ippSetRequestId (request->ipp_request, 1); language = cupsLangDefault (); @@ -157,15 +198,41 @@ gtk_cups_request_new (http_t *connection, "attributes-natural-language", NULL, language->language); - gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME, - "requesting-user-name", - NULL, cupsUser()); - + if (username != NULL) + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", + NULL, username); + else + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", + NULL, cupsUser ()); + + request->auth_info_required = NULL; + request->auth_info = NULL; + request->need_auth_info = FALSE; + cupsLangFree (language); return request; } +GtkCupsRequest * +gtk_cups_request_new (http_t *connection, + GtkCupsRequestType req_type, + gint operation_id, + GIOChannel *data_io, + const char *server, + const char *resource) +{ + return gtk_cups_request_new_with_username (connection, + req_type, + operation_id, + data_io, + server, + resource, + NULL); +} + static void gtk_cups_result_free (GtkCupsResult *result) { @@ -181,14 +248,24 @@ void gtk_cups_request_free (GtkCupsRequest *request) { if (request->own_http) - if (request->http) - httpClose (request->http); + { + if (request->http) + httpClose (request->http); + } if (request->ipp_request) ippDelete (request->ipp_request); g_free (request->server); g_free (request->resource); + if (request->password != NULL) + { + memset (request->password, 0, strlen (request->password)); + g_free (request->password); + } + + g_free (request->username); + g_strfreev (request->auth_info_required); gtk_cups_result_free (request->result); @@ -196,30 +273,46 @@ gtk_cups_request_free (GtkCupsRequest *request) } gboolean -gtk_cups_request_read_write (GtkCupsRequest *request) +gtk_cups_request_read_write (GtkCupsRequest *request, gboolean connect_only) { - if (request->type == GTK_CUPS_POST) - post_states[request->state](request); - else if (request->type == GTK_CUPS_GET) - get_states[request->state](request); + if (connect_only && request->state != GTK_CUPS_REQUEST_START) + return FALSE; - if (request->attempts > _GTK_CUPS_MAX_ATTEMPTS && - request->state != GTK_CUPS_REQUEST_DONE) - { - gtk_cups_result_set_error (request->result, "Too many failed attempts"); - request->state = GTK_CUPS_REQUEST_DONE; - request->poll_state = GTK_CUPS_HTTP_IDLE; - } - - if (request->state == GTK_CUPS_REQUEST_DONE) - { - request->poll_state = GTK_CUPS_HTTP_IDLE; - return TRUE; - } - else + do { - return FALSE; + if (request->type == GTK_CUPS_POST) + post_states[request->state] (request); + else if (request->type == GTK_CUPS_GET) + get_states[request->state] (request); + + if (gtk_cups_result_is_error (request->result)) + request->state = GTK_CUPS_REQUEST_DONE; + + if (request->attempts > _GTK_CUPS_MAX_ATTEMPTS && + request->state != GTK_CUPS_REQUEST_DONE) + { + /* TODO: should add a status or error code for too many failed attempts */ + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_GENERAL, + 0, + 0, + "Too many failed attempts"); + + request->state = GTK_CUPS_REQUEST_DONE; + } + + if (request->state == GTK_CUPS_REQUEST_DONE) + { + request->poll_state = GTK_CUPS_HTTP_IDLE; + return TRUE; + } } + /* We need to recheck using httpCheck if the poll_state is read, because + * Cups has an internal read buffer. And if this buffer is filled, we may + * never get a poll event again. */ + while (request->poll_state == GTK_CUPS_HTTP_READ && request->http && httpCheck(request->http)); + + return FALSE; } GtkCupsPollState @@ -238,11 +331,11 @@ gtk_cups_request_get_result (GtkCupsRequest *request) void gtk_cups_request_ipp_add_string (GtkCupsRequest *request, - ipp_tag_t group, - ipp_tag_t tag, - const char *name, - const char *charset, - const char *value) + ipp_tag_t group, + ipp_tag_t tag, + const char *name, + const char *charset, + const char *value) { ippAddString (request->ipp_request, group, @@ -253,13 +346,13 @@ gtk_cups_request_ipp_add_string (GtkCupsRequest *request, } void -gtk_cups_request_ipp_add_strings (GtkCupsRequest *request, - ipp_tag_t group, - ipp_tag_t tag, - const char *name, - int num_values, - const char *charset, - const char * const *values) +gtk_cups_request_ipp_add_strings (GtkCupsRequest *request, + ipp_tag_t group, + ipp_tag_t tag, + const char *name, + int num_values, + const char *charset, + const char *const *values) { ippAddStrings (request->ipp_request, group, @@ -270,6 +363,23 @@ gtk_cups_request_ipp_add_strings (GtkCupsRequest *request, values); } +const char * +gtk_cups_request_ipp_get_string (GtkCupsRequest *request, + ipp_tag_t tag, + const char *name) +{ + ipp_attribute_t *attribute = NULL; + + if (request != NULL && request->ipp_request != NULL) + attribute = ippFindAttribute (request->ipp_request, + name, + tag); + + if (attribute != NULL && ippGetCount (attribute) > 0) + return ippGetString (attribute, 0, NULL); + else + return NULL; +} typedef struct @@ -278,41 +388,41 @@ typedef struct ipp_tag_t value_tag; } ipp_option_t; -static const ipp_option_t ipp_options[] = - { - { "blackplot", IPP_TAG_BOOLEAN }, - { "brightness", IPP_TAG_INTEGER }, - { "columns", IPP_TAG_INTEGER }, - { "copies", IPP_TAG_INTEGER }, - { "finishings", IPP_TAG_ENUM }, - { "fitplot", IPP_TAG_BOOLEAN }, - { "gamma", IPP_TAG_INTEGER }, - { "hue", IPP_TAG_INTEGER }, - { "job-k-limit", IPP_TAG_INTEGER }, - { "job-page-limit", IPP_TAG_INTEGER }, - { "job-priority", IPP_TAG_INTEGER }, - { "job-quota-period", IPP_TAG_INTEGER }, - { "landscape", IPP_TAG_BOOLEAN }, - { "media", IPP_TAG_KEYWORD }, - { "mirror", IPP_TAG_BOOLEAN }, - { "natural-scaling", IPP_TAG_INTEGER }, - { "number-up", IPP_TAG_INTEGER }, - { "orientation-requested", IPP_TAG_ENUM }, - { "page-bottom", IPP_TAG_INTEGER }, - { "page-left", IPP_TAG_INTEGER }, - { "page-ranges", IPP_TAG_RANGE }, - { "page-right", IPP_TAG_INTEGER }, - { "page-top", IPP_TAG_INTEGER }, - { "penwidth", IPP_TAG_INTEGER }, - { "ppi", IPP_TAG_INTEGER }, - { "prettyprint", IPP_TAG_BOOLEAN }, - { "printer-resolution", IPP_TAG_RESOLUTION }, - { "print-quality", IPP_TAG_ENUM }, - { "saturation", IPP_TAG_INTEGER }, - { "scaling", IPP_TAG_INTEGER }, - { "sides", IPP_TAG_KEYWORD }, - { "wrap", IPP_TAG_BOOLEAN } - }; +static const ipp_option_t ipp_options[] = { + { "blackplot", IPP_TAG_BOOLEAN }, + { "brightness", IPP_TAG_INTEGER }, + { "columns", IPP_TAG_INTEGER }, + { "copies", IPP_TAG_INTEGER }, + { "finishings", IPP_TAG_ENUM }, + { "fitplot", IPP_TAG_BOOLEAN }, + { "gamma", IPP_TAG_INTEGER }, + { "hue", IPP_TAG_INTEGER }, + { "job-k-limit", IPP_TAG_INTEGER }, + { "job-page-limit", IPP_TAG_INTEGER }, + { "job-priority", IPP_TAG_INTEGER }, + { "job-quota-period", IPP_TAG_INTEGER }, + { "landscape", IPP_TAG_BOOLEAN }, + { "media", IPP_TAG_KEYWORD }, + { "mirror", IPP_TAG_BOOLEAN }, + { "natural-scaling", IPP_TAG_INTEGER }, + { "number-up", IPP_TAG_INTEGER }, + { "orientation-requested", IPP_TAG_ENUM }, + { "page-bottom", IPP_TAG_INTEGER }, + { "page-left", IPP_TAG_INTEGER }, + { "page-ranges", IPP_TAG_RANGE }, + { "page-right", IPP_TAG_INTEGER }, + { "page-top", IPP_TAG_INTEGER }, + { "penwidth", IPP_TAG_INTEGER }, + { "ppi", IPP_TAG_INTEGER }, + { "prettyprint", IPP_TAG_BOOLEAN }, + { "printer-resolution", IPP_TAG_RESOLUTION }, + { "print-quality", IPP_TAG_ENUM }, + { "saturation", IPP_TAG_INTEGER }, + { "scaling", IPP_TAG_INTEGER }, + { "sides", IPP_TAG_KEYWORD }, + { "wrap", IPP_TAG_BOOLEAN }, + { "number-up-layout", IPP_TAG_INTEGER } +}; static ipp_tag_t @@ -325,14 +435,14 @@ _find_option_tag (const gchar *option) result = IPP_TAG_ZERO; lower_bound = 0; - upper_bound = num_options = (int)(sizeof(ipp_options) / sizeof(ipp_options[0])) - 1; + upper_bound = num_options = (int) G_N_ELEMENTS (ipp_options) - 1; while (1) { int match; current_option = (int) (((upper_bound - lower_bound) / 2) + lower_bound); - match = strcasecmp(option, ipp_options[current_option].name); + match = strcasecmp (option, ipp_options[current_option].name); if (match == 0) { result = ipp_options[current_option].value_tag; @@ -361,15 +471,20 @@ _find_option_tag (const gchar *option) } } +/* + * Note that this function uses IPP_TAG_JOB, so it is + * only suitable for IPP Group 2 attributes. + * See RFC 2911. + */ void gtk_cups_request_encode_option (GtkCupsRequest *request, - const gchar *option, - const gchar *value) + const gchar *option, + const gchar *value) { ipp_tag_t option_tag; - g_assert (option != NULL); - g_assert (value != NULL); + g_return_if_fail (option != NULL); + g_return_if_fail (value != NULL); option_tag = _find_option_tag (option); @@ -388,7 +503,7 @@ gtk_cups_request_encode_option (GtkCupsRequest *request, case IPP_TAG_INTEGER: case IPP_TAG_ENUM: ippAddInteger (request->ipp_request, - IPP_TAG_OPERATION, + IPP_TAG_JOB, option_tag, option, strtol (value, NULL, 0)); @@ -397,16 +512,18 @@ gtk_cups_request_encode_option (GtkCupsRequest *request, case IPP_TAG_BOOLEAN: { char b; - b = 0; - if (!strcasecmp(value, "true") || - !strcasecmp(value, "on") || - !strcasecmp(value, "yes")) + + if (strcasecmp (value, "true") == 0 || + strcasecmp (value, "on") == 0 || + strcasecmp (value, "yes") == 0) b = 1; - - ippAddBoolean(request->ipp_request, - IPP_TAG_OPERATION, - option, - b); + else + b = 0; + + ippAddBoolean (request->ipp_request, + IPP_TAG_JOB, + option, + b); break; } @@ -423,12 +540,12 @@ gtk_cups_request_encode_option (GtkCupsRequest *request, s = (char *)value; } else - lower = strtol(value, &s, 0); + lower = strtol (value, &s, 0); if (*s == '-') { if (s[1]) - upper = strtol(s + 1, NULL, 0); + upper = strtol (s + 1, NULL, 0); else upper = 2147483647; } @@ -436,7 +553,7 @@ gtk_cups_request_encode_option (GtkCupsRequest *request, upper = lower; ippAddRange (request->ipp_request, - IPP_TAG_OPERATION, + IPP_TAG_JOB, option, lower, upper); @@ -451,20 +568,20 @@ gtk_cups_request_encode_option (GtkCupsRequest *request, int yres; ipp_res_t units; - xres = strtol(value, &s, 0); + xres = strtol (value, &s, 0); if (*s == 'x') - yres = strtol(s + 1, &s, 0); + yres = strtol (s + 1, &s, 0); else yres = xres; - if (strcasecmp(s, "dpc") == 0) + if (strcasecmp (s, "dpc") == 0) units = IPP_RES_PER_CM; else units = IPP_RES_PER_INCH; ippAddResolution (request->ipp_request, - IPP_TAG_OPERATION, + IPP_TAG_JOB, option, units, xres, @@ -474,26 +591,104 @@ gtk_cups_request_encode_option (GtkCupsRequest *request, } default: - ippAddString (request->ipp_request, - IPP_TAG_OPERATION, - option_tag, - option, - NULL, - value); + { + char *values; + char *s; + int in_quotes; + char *next; + GPtrArray *strings; + + values = g_strdup (value); + strings = NULL; + in_quotes = 0; + + for (s = values, next = s; *s != '\0'; s++) + { + if (in_quotes != 2 && *s == '\'') + { + /* skip quoted value */ + if (in_quotes == 0) + in_quotes = 1; + else + in_quotes = 0; + } + else if (in_quotes != 1 && *s == '\"') + { + /* skip quoted value */ + if (in_quotes == 0) + in_quotes = 2; + else + in_quotes = 0; + } + else if (in_quotes == 0 && *s == ',') + { + /* found delimiter, add to value array */ + *s = '\0'; + if (strings == NULL) + strings = g_ptr_array_new (); + g_ptr_array_add (strings, next); + next = s + 1; + } + else if (in_quotes == 0 && *s == '\\' && s[1] != '\0') + { + /* skip escaped character */ + s++; + } + } + + if (strings == NULL) + { + /* single value */ + ippAddString (request->ipp_request, + IPP_TAG_JOB, + option_tag, + option, + NULL, + value); + } + else + { + /* multiple values */ + + /* add last value */ + g_ptr_array_add (strings, next); + + ippAddStrings (request->ipp_request, + IPP_TAG_JOB, + option_tag, + option, + strings->len, + NULL, + (const char **) strings->pdata); + g_ptr_array_free (strings, TRUE); + } + + g_free (values); + } break; } } +void +gtk_cups_request_set_ipp_version (GtkCupsRequest *request, + gint major, + gint minor) +{ + ippSetVersion (request->ipp_request, major, minor); +} static void _connect (GtkCupsRequest *request) { request->poll_state = GTK_CUPS_HTTP_IDLE; + request->bytes_received = 0; if (request->http == NULL) { - request->http = httpConnectEncrypt (request->server, ippPort(), cupsEncryption()); + request->http = httpConnectEncrypt (request->server, + ippPort (), + cupsEncryption ()); if (request->http == NULL) request->attempts++; @@ -528,28 +723,35 @@ _post_send (GtkCupsRequest *request) if (request->data_io != NULL) { fstat (g_io_channel_unix_get_fd (request->data_io), &data_info); - sprintf (length, "%lu", (unsigned long)ippLength(request->ipp_request) + data_info.st_size); + sprintf (length, "%lu", (unsigned long) (ippLength (request->ipp_request) + data_info.st_size)); } else - { - sprintf (length, "%lu", (unsigned long)ippLength(request->ipp_request)); - } + sprintf (length, "%lu", (unsigned long) ippLength (request->ipp_request)); - httpClearFields(request->http); - httpSetField(request->http, HTTP_FIELD_CONTENT_LENGTH, length); - httpSetField(request->http, HTTP_FIELD_CONTENT_TYPE, "application/ipp"); + httpClearFields (request->http); + httpSetField (request->http, HTTP_FIELD_CONTENT_LENGTH, length); + httpSetField (request->http, HTTP_FIELD_CONTENT_TYPE, "application/ipp"); +#ifdef HAVE_HTTPGETAUTHSTRING + httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString (request->http)); +#else #ifdef HAVE_HTTP_AUTHSTRING - httpSetField(request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring); + httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring); +#endif #endif - if (httpPost(request->http, request->resource)) + if (httpPost (request->http, request->resource)) { - if (httpReconnect(request->http)) + if (httpReconnect (request->http)) { request->state = GTK_CUPS_POST_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; - gtk_cups_result_set_error (request->result, "Failed Post"); + /* TODO: should add a status or error code for failed post */ + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_GENERAL, + 0, + 0, + "Failed Post"); } request->attempts++; @@ -559,7 +761,7 @@ _post_send (GtkCupsRequest *request) request->attempts = 0; request->state = GTK_CUPS_POST_WRITE_REQUEST; - request->ipp_request->state = IPP_IDLE; + ippSetState (request->ipp_request, IPP_IDLE); } static void @@ -572,14 +774,20 @@ _post_write_request (GtkCupsRequest *request) request->poll_state = GTK_CUPS_HTTP_WRITE; - ipp_status = ippWrite(request->http, request->ipp_request); + ipp_status = ippWrite (request->http, request->ipp_request); if (ipp_status == IPP_ERROR) { + int cups_error = cupsLastError (); request->state = GTK_CUPS_POST_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; - gtk_cups_result_set_error (request->result, "%s",ippErrorString (cupsLastError ())); + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_IPP, + ipp_status, + cups_error, + "%s", + ippErrorString (cups_error)); return; } @@ -608,7 +816,7 @@ _post_write_data (GtkCupsRequest *request) request->poll_state = GTK_CUPS_HTTP_WRITE; if (httpCheck (request->http)) - http_status = httpUpdate(request->http); + http_status = httpUpdate (request->http); else http_status = request->last_status; @@ -635,7 +843,12 @@ _post_write_data (GtkCupsRequest *request) request->state = GTK_CUPS_POST_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; - gtk_cups_result_set_error (request->result, "Error reading from cache file: %s", error->message); + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_IO, + io_status, + error->code, + "Error reading from cache file: %s", + error->message); g_error_free (error); return; @@ -650,25 +863,101 @@ _post_write_data (GtkCupsRequest *request) } -#if HAVE_CUPS_API_1_2 - if (httpWrite2(request->http, buffer, bytes) < bytes) -#else - if (httpWrite(request->http, buffer, (int) bytes) < bytes) -#endif /* HAVE_CUPS_API_1_2 */ + if (httpWrite2 (request->http, buffer, bytes) < bytes) { + int http_errno; + + http_errno = httpError (request->http); + request->state = GTK_CUPS_POST_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; - gtk_cups_result_set_error (request->result, "Error writting to socket in Post %s", strerror (httpError (request->http))); + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_HTTP, + http_status, + http_errno, + "Error writing to socket in Post %s", + g_strerror (http_errno)); return; } } - else + else if (http_status == HTTP_UNAUTHORIZED) + { + request->state = GTK_CUPS_POST_CHECK; + request->poll_state = GTK_CUPS_HTTP_READ; + + request->attempts = 0; + return; + } + else { request->attempts++; } } +static void +_post_auth (GtkCupsRequest *request) +{ + if (request->password_state == GTK_CUPS_PASSWORD_HAS) + { + if (request->password == NULL) + { + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_AUTH, + 0, + 1, + "Canceled by user"); + } + else + request->state = GTK_CUPS_POST_CHECK; + } +} + +static void +_get_auth (GtkCupsRequest *request) +{ + if (request->password_state == GTK_CUPS_PASSWORD_HAS) + { + if (request->password == NULL) + { + request->state = GTK_CUPS_GET_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_AUTH, + 0, + 1, + "Canceled by user"); + } + else + request->state = GTK_CUPS_GET_CHECK; + } +} + +/* Very ugly hack: cups has a stupid synchronous password callback + * that doesn't even take the request or user data parameters, so + * we have to use a static variable to pass the password to it. + * Not threadsafe ! + * The callback sets cups_password to NULL to signal that the + * password has been used. + */ +static char *cups_password = NULL; +static char *cups_username = NULL; + +static const char * +passwordCB (const char *prompt) +{ + char *pwd = cups_password; + cups_password = NULL; + + cupsSetUser (cups_username); + + return pwd; +} + static void _post_check (GtkCupsRequest *request) { @@ -687,22 +976,100 @@ _post_check (GtkCupsRequest *request) } else if (http_status == HTTP_UNAUTHORIZED) { - /* TODO: callout for auth */ - g_warning ("NOT IMPLEMENTED: We need to prompt for authorization"); - request->state = GTK_CUPS_POST_DONE; - request->poll_state = GTK_CUPS_HTTP_IDLE; + int auth_result = -1; + httpFlush (request->http); + + if (request->password_state == GTK_CUPS_PASSWORD_APPLIED) + { + request->poll_state = GTK_CUPS_HTTP_IDLE; + request->password_state = GTK_CUPS_PASSWORD_NOT_VALID; + request->state = GTK_CUPS_POST_AUTH; + request->need_password = TRUE; + + return; + } + + /* Negotiate */ + if (strncmp (httpGetField (request->http, HTTP_FIELD_WWW_AUTHENTICATE), "Negotiate", 9) == 0) + { + auth_result = cupsDoAuthentication (request->http, "POST", request->resource); + } + /* Basic, BasicDigest, Digest and PeerCred */ + else + { + if (request->password_state == GTK_CUPS_PASSWORD_NONE) + { + cups_username = request->username; + cupsSetPasswordCB (passwordCB); + + /* This call success for PeerCred authentication */ + auth_result = cupsDoAuthentication (request->http, "POST", request->resource); + + if (auth_result != 0) + { + /* move to AUTH state to let the backend + * ask for a password + */ + request->poll_state = GTK_CUPS_HTTP_IDLE; + request->state = GTK_CUPS_POST_AUTH; + request->need_password = TRUE; + + return; + } + } + else + { + cups_password = request->password; + cups_username = request->username; + + auth_result = cupsDoAuthentication (request->http, "POST", request->resource); + + if (cups_password != NULL) + return; + + if (request->password != NULL) + { + memset (request->password, 0, strlen (request->password)); + g_free (request->password); + request->password = NULL; + } + + request->password_state = GTK_CUPS_PASSWORD_APPLIED; + } + } + + if (auth_result || + httpReconnect (request->http)) + { + /* if the password has been used, reset password_state + * so that we ask for a new one next time around + */ + if (cups_password == NULL) + request->password_state = GTK_CUPS_PASSWORD_NONE; + + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_AUTH, + 0, + 0, + "Not authorized"); + return; + } - gtk_cups_result_set_error (request->result, "Can't prompt for authorization"); - return; + if (request->data_io != NULL) + g_io_channel_seek_position (request->data_io, 0, G_SEEK_SET, NULL); + + request->state = GTK_CUPS_POST_CONNECT; + request->poll_state = GTK_CUPS_HTTP_WRITE; } else if (http_status == HTTP_ERROR) { + int error = httpError (request->http); #ifdef G_OS_WIN32 - if (request->http->error != WSAENETDOWN && - request->http->error != WSAENETUNREACH) + if (error != WSAENETDOWN && error != WSAENETUNREACH) #else - if (request->http->error != ENETDOWN && - request->http->error != ENETUNREACH) + if (error != ENETDOWN && error != ENETUNREACH) #endif /* G_OS_WIN32 */ { request->attempts++; @@ -713,27 +1080,32 @@ _post_check (GtkCupsRequest *request) request->state = GTK_CUPS_POST_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; - gtk_cups_result_set_error (request->result, "Unknown HTTP error"); + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_HTTP, + http_status, + error, + "Unknown HTTP error"); + return; } } -/* TODO: detect ssl in configure.ac */ -#if HAVE_SSL else if (http_status == HTTP_UPGRADE_REQUIRED) { /* Flush any error message... */ httpFlush (request->http); + cupsSetEncryption (HTTP_ENCRYPT_REQUIRED); + request->state = GTK_CUPS_POST_CONNECT; + /* Reconnect... */ httpReconnect (request->http); /* Upgrade with encryption... */ - httpEncryption(request->http, HTTP_ENCRYPT_REQUIRED); + httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED); request->attempts++; goto again; } -#endif else if (http_status != HTTP_OK) { int http_errno; @@ -745,16 +1117,21 @@ _post_check (GtkCupsRequest *request) else { request->state = GTK_CUPS_POST_DONE; - gtk_cups_result_set_error (request->result, "HTTP Error in POST %s", strerror (http_errno)); - request->poll_state = GTK_CUPS_HTTP_IDLE; + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_HTTP, + http_status, + http_errno, + "HTTP Error in POST %s", + g_strerror (http_errno)); + request->poll_state = GTK_CUPS_HTTP_IDLE; - httpFlush(request->http); + httpFlush (request->http); return; } request->poll_state = GTK_CUPS_HTTP_IDLE; - httpFlush(request->http); + httpFlush (request->http); request->last_status = HTTP_CONTINUE; httpClose (request->http); @@ -794,7 +1171,13 @@ _post_read_response (GtkCupsRequest *request) if (ipp_status == IPP_ERROR) { - gtk_cups_result_set_error (request->result, "%s", ippErrorString (cupsLastError())); + int ipp_error = cupsLastError (); + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_IPP, + ipp_status, + ipp_error, + "%s", + ippErrorString (ipp_error)); ippDelete (request->result->ipp_response); request->result->ipp_response = NULL; @@ -819,38 +1202,55 @@ _get_send (GtkCupsRequest *request) if (request->data_io == NULL) { - gtk_cups_result_set_error (request->result, "Get requires an open io channel"); + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_IO, + G_IO_STATUS_ERROR, + G_IO_CHANNEL_ERROR_FAILED, + "Get requires an open io channel"); + request->state = GTK_CUPS_GET_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; return; } - httpClearFields(request->http); + httpClearFields (request->http); +#ifdef HAVE_HTTPGETAUTHSTRING + httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString (request->http)); +#else #ifdef HAVE_HTTP_AUTHSTRING - httpSetField(request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring); + httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring); +#endif #endif - if (httpGet(request->http, request->resource)) + if (httpGet (request->http, request->resource)) { - if (httpReconnect(request->http)) + if (httpReconnect (request->http)) { request->state = GTK_CUPS_GET_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; - - gtk_cups_result_set_error (request->result, "Failed Get"); + + /* TODO: should add a status or error code for failed GET */ + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_GENERAL, + 0, + 0, + "Failed Get"); } request->attempts++; return; } + + if (httpCheck (request->http)) + request->last_status = httpUpdate (request->http); request->attempts = 0; request->state = GTK_CUPS_GET_CHECK; request->poll_state = GTK_CUPS_HTTP_READ; - request->ipp_request->state = IPP_IDLE; + ippSetState (request->ipp_request, IPP_IDLE); } static void @@ -871,31 +1271,109 @@ _get_check (GtkCupsRequest *request) } else if (http_status == HTTP_UNAUTHORIZED) { - /* TODO: callout for auth */ - g_warning ("NOT IMPLEMENTED: We need to prompt for authorization in a non blocking manner"); - request->state = GTK_CUPS_GET_DONE; - request->poll_state = GTK_CUPS_HTTP_IDLE; - - gtk_cups_result_set_error (request->result, "Can't prompt for authorization"); - return; + int auth_result = -1; + httpFlush (request->http); + + if (request->password_state == GTK_CUPS_PASSWORD_APPLIED) + { + request->poll_state = GTK_CUPS_HTTP_IDLE; + request->password_state = GTK_CUPS_PASSWORD_NOT_VALID; + request->state = GTK_CUPS_GET_AUTH; + request->need_password = TRUE; + + return; + } + + /* Negotiate */ + if (strncmp (httpGetField (request->http, HTTP_FIELD_WWW_AUTHENTICATE), "Negotiate", 9) == 0) + { + auth_result = cupsDoAuthentication (request->http, "GET", request->resource); + } + /* Basic, BasicDigest, Digest and PeerCred */ + else + { + if (request->password_state == GTK_CUPS_PASSWORD_NONE) + { + cups_username = request->username; + cupsSetPasswordCB (passwordCB); + + /* This call success for PeerCred authentication */ + auth_result = cupsDoAuthentication (request->http, "GET", request->resource); + + if (auth_result != 0) + { + /* move to AUTH state to let the backend + * ask for a password + */ + request->poll_state = GTK_CUPS_HTTP_IDLE; + request->state = GTK_CUPS_GET_AUTH; + request->need_password = TRUE; + + return; + } + } + else + { + cups_password = request->password; + cups_username = request->username; + + auth_result = cupsDoAuthentication (request->http, "GET", request->resource); + + if (cups_password != NULL) + return; + + if (request->password != NULL) + { + memset (request->password, 0, strlen (request->password)); + g_free (request->password); + request->password = NULL; + } + + request->password_state = GTK_CUPS_PASSWORD_APPLIED; + } + } + + if (auth_result || + httpReconnect (request->http)) + { + /* if the password has been used, reset password_state + * so that we ask for a new one next time around + */ + if (cups_password == NULL) + request->password_state = GTK_CUPS_PASSWORD_NONE; + + request->state = GTK_CUPS_GET_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_AUTH, + 0, + 0, + "Not authorized"); + return; + } + + request->state = GTK_CUPS_GET_CONNECT; + request->last_status = HTTP_CONTINUE; + + return; } -/* TODO: detect ssl in configure.ac */ -#if HAVE_SSL else if (http_status == HTTP_UPGRADE_REQUIRED) { /* Flush any error message... */ httpFlush (request->http); + cupsSetEncryption (HTTP_ENCRYPT_REQUIRED); + request->state = GTK_CUPS_GET_CONNECT; + /* Reconnect... */ httpReconnect (request->http); /* Upgrade with encryption... */ - httpEncryption(request->http, HTTP_ENCRYPT_REQUIRED); + httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED); request->attempts++; goto again; } -#endif else if (http_status != HTTP_OK) { int http_errno; @@ -907,9 +1385,14 @@ _get_check (GtkCupsRequest *request) else { request->state = GTK_CUPS_GET_DONE; - gtk_cups_result_set_error (request->result, "HTTP Error in GET %s", strerror (http_errno)); + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_HTTP, + http_status, + http_errno, + "HTTP Error in GET %s", + g_strerror (http_errno)); request->poll_state = GTK_CUPS_HTTP_IDLE; - httpFlush(request->http); + httpFlush (request->http); return; } @@ -954,23 +1437,12 @@ _get_read_data (GtkCupsRequest *request) request->poll_state = GTK_CUPS_HTTP_READ; -#if HAVE_CUPS_API_1_2 - bytes = httpRead2(request->http, buffer, sizeof(buffer)); -#else - bytes = httpRead(request->http, buffer, sizeof(buffer)); -#endif /* HAVE_CUPS_API_1_2 */ + bytes = httpRead2 (request->http, buffer, sizeof (buffer)); + request->bytes_received += bytes; GTK_NOTE (PRINTING, - g_print ("CUPS Backend: %i bytes read\n", bytes)); - - if (bytes == 0) - { - request->state = GTK_CUPS_GET_DONE; - request->poll_state = GTK_CUPS_HTTP_IDLE; + g_print ("CUPS Backend: %"G_GSIZE_FORMAT" bytes read\n", bytes)); - return; - } - io_status = g_io_channel_write_chars (request->data_io, buffer, @@ -980,12 +1452,25 @@ _get_read_data (GtkCupsRequest *request) if (io_status == G_IO_STATUS_ERROR) { - request->state = GTK_CUPS_POST_DONE; + request->state = GTK_CUPS_GET_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; - gtk_cups_result_set_error (request->result, error->message); + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_IO, + io_status, + error->code, + error->message); g_error_free (error); } + + /* Stop if we do not expect any more data or EOF was received. */ + if (httpGetLength2 (request->http) <= request->bytes_received || bytes == 0) + { + request->state = GTK_CUPS_GET_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + return; + } } gboolean @@ -1006,9 +1491,166 @@ gtk_cups_result_get_response (GtkCupsResult *result) return result->ipp_response; } +GtkCupsErrorType +gtk_cups_result_get_error_type (GtkCupsResult *result) +{ + return result->error_type; +} + +int +gtk_cups_result_get_error_status (GtkCupsResult *result) +{ + return result->error_status; +} + +int +gtk_cups_result_get_error_code (GtkCupsResult *result) +{ + return result->error_code; +} + const char * gtk_cups_result_get_error_string (GtkCupsResult *result) { return result->error_msg; } +/* This function allocates new instance of GtkCupsConnectionTest() and creates + * a socket for communication with a CUPS server 'server'. + */ +GtkCupsConnectionTest * +gtk_cups_connection_test_new (const char *server) +{ + GtkCupsConnectionTest *result = NULL; + gchar *port_str = NULL; + + result = g_new (GtkCupsConnectionTest, 1); + + port_str = g_strdup_printf ("%d", ippPort ()); + + if (server != NULL) + result->addrlist = httpAddrGetList (server, AF_UNSPEC, port_str); + else + result->addrlist = httpAddrGetList (cupsServer (), AF_UNSPEC, port_str); + + g_free (port_str); + + result->socket = -1; + result->current_addr = NULL; + result->last_wrong_addr = NULL; + result->at_init = GTK_CUPS_CONNECTION_NOT_AVAILABLE; + + result->at_init = gtk_cups_connection_test_get_state (result); + + return result; +} + + +/* A non-blocking test whether it is possible to connect to a CUPS server specified + * inside of GtkCupsConnectionTest structure. + * - you need to check it more then once. + * The connection is closed after a successful connection. + */ +GtkCupsConnectionState +gtk_cups_connection_test_get_state (GtkCupsConnectionTest *test) +{ + GtkCupsConnectionState result = GTK_CUPS_CONNECTION_NOT_AVAILABLE; + http_addrlist_t *iter; + gint error_code; + gint flags; + gint code; + + if (test == NULL) + return GTK_CUPS_CONNECTION_NOT_AVAILABLE; + + if (test->at_init == GTK_CUPS_CONNECTION_AVAILABLE) + { + test->at_init = GTK_CUPS_CONNECTION_NOT_AVAILABLE; + return GTK_CUPS_CONNECTION_AVAILABLE; + } + else + { + if (test->socket == -1) + { + if (test->last_wrong_addr != NULL && test->last_wrong_addr->next != NULL) + iter = test->last_wrong_addr->next; + else + { + test->last_wrong_addr = NULL; + iter = test->addrlist; + } + + while (iter) + { + test->socket = socket (iter->addr.addr.sa_family, + SOCK_STREAM, + 0); + + if (test->socket >= 0) + { + flags = fcntl (test->socket, F_GETFL); + + if (flags != -1) + flags |= O_NONBLOCK; + + fcntl (test->socket, F_SETFL, flags); + + test->current_addr = iter; + + break; + } + iter = iter->next; + } + } + + if (test->socket >= 0) + { + code = connect (test->socket, + &test->current_addr->addr.addr, + httpAddrLength (&test->current_addr->addr)); + + error_code = errno; + + if (code == 0 || error_code == EISCONN) + { + close (test->socket); + test->socket = -1; + test->current_addr = NULL; + result = GTK_CUPS_CONNECTION_AVAILABLE; + } + else + { + if (error_code == EALREADY || error_code == EINPROGRESS) + result = GTK_CUPS_CONNECTION_IN_PROGRESS; + else + { + close (test->socket); + test->socket = -1; + test->last_wrong_addr = test->current_addr; + result = GTK_CUPS_CONNECTION_NOT_AVAILABLE; + } + } + } + + return result; + } +} + +/* This function frees memory used by the GtkCupsConnectionTest structure. + */ +void +gtk_cups_connection_test_free (GtkCupsConnectionTest *test) +{ + if (test == NULL) + return; + + test->current_addr = NULL; + test->last_wrong_addr = NULL; + httpAddrFreeList (test->addrlist); + if (test->socket != -1) + { + close (test->socket); + test->socket = -1; + } + g_free (test); +}