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);
+}