]> Pileus Git - ~andy/gtk/blob - modules/printbackends/cups/gtkcupsutils.c
d7c5b7cce0b8e2715b9ebd55446a7cb7c9357c94
[~andy/gtk] / modules / printbackends / cups / gtkcupsutils.c
1 /* GTK - The GIMP 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.
5  *
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.
10  *
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.
15  *
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.
20  */
21
22 #include "config.h"
23 #include <gtk/gtk.h>
24 #include "gtkcupsutils.h"
25
26 #include <errno.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <stdlib.h>
31 #include <time.h>
32 #include <fcntl.h>
33 #include <sys/socket.h>
34
35 typedef void (*GtkCupsRequestStateFunc) (GtkCupsRequest *request);
36
37 static void _connect            (GtkCupsRequest *request);
38 static void _post_send          (GtkCupsRequest *request);
39 static void _post_write_request (GtkCupsRequest *request);
40 static void _post_write_data    (GtkCupsRequest *request);
41 static void _post_check         (GtkCupsRequest *request);
42 static void _post_auth          (GtkCupsRequest *request);
43 static void _post_read_response (GtkCupsRequest *request);
44
45 static void _get_send           (GtkCupsRequest *request);
46 static void _get_check          (GtkCupsRequest *request);
47 static void _get_auth           (GtkCupsRequest *request);
48 static void _get_read_data      (GtkCupsRequest *request);
49
50 struct _GtkCupsResult
51 {
52   gchar *error_msg;
53   ipp_t *ipp_response;
54   GtkCupsErrorType error_type;
55
56   /* some error types like HTTP_ERROR have a status and a code */
57   int error_status;            
58   int error_code;
59
60   guint is_error : 1;
61   guint is_ipp_response : 1;
62 };
63
64
65 #define _GTK_CUPS_MAX_ATTEMPTS 10 
66 #define _GTK_CUPS_MAX_CHUNK_SIZE 8192
67
68 static GtkCupsRequestStateFunc post_states[] = {
69   _connect,
70   _post_send,
71   _post_write_request,
72   _post_write_data,
73   _post_check,
74   _post_auth,
75   _post_read_response
76 };
77
78 static GtkCupsRequestStateFunc get_states[] = {
79   _connect,
80   _get_send,
81   _get_check,
82   _get_auth,
83   _get_read_data
84 };
85
86 static void
87 gtk_cups_result_set_error (GtkCupsResult    *result,
88                            GtkCupsErrorType  error_type,
89                            int               error_status,
90                            int               error_code, 
91                            const char       *error_msg,
92                            ...)
93 {
94   va_list args;
95
96   result->is_ipp_response = FALSE;
97   result->is_error = TRUE;
98   result->error_type = error_type;
99   result->error_status = error_status;
100   result->error_code = error_code;
101
102   va_start (args, error_msg);
103   result->error_msg = g_strdup_vprintf (error_msg, args);
104   va_end (args);
105 }
106
107 GtkCupsRequest *
108 gtk_cups_request_new_with_username (http_t             *connection,
109                                     GtkCupsRequestType  req_type, 
110                                     gint                operation_id,
111                                     GIOChannel         *data_io,
112                                     const char         *server,
113                                     const char         *resource,
114                                     const char         *username)
115 {
116   GtkCupsRequest *request;
117   cups_lang_t *language;
118   
119   request = g_new0 (GtkCupsRequest, 1);
120   request->result = g_new0 (GtkCupsResult, 1);
121
122   request->result->error_msg = NULL;
123   request->result->ipp_response = NULL;
124
125   request->result->is_error = FALSE;
126   request->result->is_ipp_response = FALSE;
127
128   request->type = req_type;
129   request->state = GTK_CUPS_REQUEST_START;
130
131   request->password_state = GTK_CUPS_PASSWORD_NONE;
132
133    if (server)
134     request->server = g_strdup (server);
135   else
136     request->server = g_strdup (cupsServer ());
137
138
139   if (resource)
140     request->resource = g_strdup (resource);
141   else
142     request->resource = g_strdup ("/");
143  
144   if (connection != NULL)
145     {
146       request->http = connection;
147       request->own_http = FALSE;
148     }
149   else
150     {
151       request->http = NULL;
152       request->http = httpConnectEncrypt (request->server, 
153                                           ippPort (), 
154                                           cupsEncryption ());
155
156       if (request->http)
157         httpBlocking (request->http, 0);
158         
159       request->own_http = TRUE;
160     }
161
162   request->last_status = HTTP_CONTINUE;
163
164   request->attempts = 0;
165   request->data_io = data_io;
166
167   request->ipp_request = ippNew ();
168   request->ipp_request->request.op.operation_id = operation_id;
169   request->ipp_request->request.op.request_id = 1;
170
171   language = cupsLangDefault ();
172
173   gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
174                                    "attributes-charset", 
175                                    NULL, "utf-8");
176         
177   gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
178                                    "attributes-natural-language", 
179                                    NULL, language->language);
180
181   if (username != NULL)
182     gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
183                                      "requesting-user-name",
184                                      NULL, username);
185   else
186     gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
187                                      "requesting-user-name",
188                                      NULL, cupsUser ());
189
190   request->auth_info_required = NULL;
191   request->auth_info = NULL;
192   request->need_auth_info = FALSE;
193
194   cupsLangFree (language);
195
196   return request;
197 }
198
199 GtkCupsRequest *
200 gtk_cups_request_new (http_t             *connection,
201                       GtkCupsRequestType  req_type, 
202                       gint                operation_id,
203                       GIOChannel         *data_io,
204                       const char         *server,
205                       const char         *resource)
206 {
207   return gtk_cups_request_new_with_username (connection,
208                                              req_type,
209                                              operation_id,
210                                              data_io,
211                                              server,
212                                              resource,
213                                              NULL);
214 }
215
216 static void
217 gtk_cups_result_free (GtkCupsResult *result)
218 {
219   g_free (result->error_msg);
220
221   if (result->ipp_response)
222     ippDelete (result->ipp_response);
223
224   g_free (result);
225 }
226
227 void
228 gtk_cups_request_free (GtkCupsRequest *request)
229 {
230   if (request->own_http)
231     {
232       if (request->http)
233         httpClose (request->http);
234     }
235   
236   if (request->ipp_request)
237     ippDelete (request->ipp_request);
238
239   g_free (request->server);
240   g_free (request->resource);
241   if (request->password != NULL)
242     {
243       memset (request->password, 0, strlen (request->password));
244       g_free (request->password);
245     }
246
247   g_free (request->username);
248   g_strfreev (request->auth_info_required);
249
250   gtk_cups_result_free (request->result);
251
252   g_free (request);
253 }
254
255 gboolean 
256 gtk_cups_request_read_write (GtkCupsRequest *request)
257 {
258   if (request->type == GTK_CUPS_POST)
259     post_states[request->state] (request);
260   else if (request->type == GTK_CUPS_GET)
261     get_states[request->state] (request);
262
263   if (request->attempts > _GTK_CUPS_MAX_ATTEMPTS && 
264       request->state != GTK_CUPS_REQUEST_DONE)
265     {
266       /* TODO: should add a status or error code for too many failed attempts */
267       gtk_cups_result_set_error (request->result, 
268                                  GTK_CUPS_ERROR_GENERAL,
269                                  0,
270                                  0, 
271                                  "Too many failed attempts");
272
273       request->state = GTK_CUPS_REQUEST_DONE;
274       request->poll_state = GTK_CUPS_HTTP_IDLE;
275     }
276     
277   if (request->state == GTK_CUPS_REQUEST_DONE)
278     {
279       request->poll_state = GTK_CUPS_HTTP_IDLE;
280       return TRUE;
281     }
282   else
283     return FALSE;
284 }
285
286 GtkCupsPollState 
287 gtk_cups_request_get_poll_state (GtkCupsRequest *request)
288 {
289   return request->poll_state;
290 }
291
292
293
294 GtkCupsResult *
295 gtk_cups_request_get_result (GtkCupsRequest *request)
296 {
297   return request->result;
298 }
299
300 void            
301 gtk_cups_request_ipp_add_string (GtkCupsRequest *request,
302                                  ipp_tag_t       group,
303                                  ipp_tag_t       tag,
304                                  const char     *name,
305                                  const char     *charset,
306                                  const char     *value)
307 {
308   ippAddString (request->ipp_request,
309                 group,
310                 tag,
311                 name,
312                 charset,
313                 value);
314 }
315
316 void            
317 gtk_cups_request_ipp_add_strings (GtkCupsRequest    *request,
318                                   ipp_tag_t          group,
319                                   ipp_tag_t          tag,
320                                   const char        *name,
321                                   int                num_values,
322                                   const char        *charset,
323                                   const char *const *values)
324 {
325   ippAddStrings (request->ipp_request,
326                  group,
327                  tag,
328                  name,
329                  num_values,
330                  charset,
331                  values);
332 }
333
334 const char *
335 gtk_cups_request_ipp_get_string (GtkCupsRequest *request,
336                                  ipp_tag_t       tag,
337                                  const char     *name)
338 {
339   ipp_attribute_t *attribute = NULL;
340
341   if (request != NULL && request->ipp_request != NULL)
342     attribute = ippFindAttribute (request->ipp_request,
343                                   name,
344                                   tag);
345
346   if (attribute != NULL && attribute->values != NULL)
347     return attribute->values[0].string.text;
348   else
349     return NULL;
350 }
351
352
353 typedef struct
354 {
355   const char    *name;
356   ipp_tag_t     value_tag;
357 } ipp_option_t;
358
359 static const ipp_option_t ipp_options[] = {
360   { "blackplot",                IPP_TAG_BOOLEAN },
361   { "brightness",               IPP_TAG_INTEGER },
362   { "columns",                  IPP_TAG_INTEGER },
363   { "copies",                   IPP_TAG_INTEGER },
364   { "finishings",               IPP_TAG_ENUM },
365   { "fitplot",                  IPP_TAG_BOOLEAN },
366   { "gamma",                    IPP_TAG_INTEGER },
367   { "hue",                      IPP_TAG_INTEGER },
368   { "job-k-limit",              IPP_TAG_INTEGER },
369   { "job-page-limit",           IPP_TAG_INTEGER },
370   { "job-priority",             IPP_TAG_INTEGER },
371   { "job-quota-period",         IPP_TAG_INTEGER },
372   { "landscape",                IPP_TAG_BOOLEAN },
373   { "media",                    IPP_TAG_KEYWORD },
374   { "mirror",                   IPP_TAG_BOOLEAN },
375   { "natural-scaling",          IPP_TAG_INTEGER },
376   { "number-up",                IPP_TAG_INTEGER },
377   { "orientation-requested",    IPP_TAG_ENUM },
378   { "page-bottom",              IPP_TAG_INTEGER },
379   { "page-left",                IPP_TAG_INTEGER },
380   { "page-ranges",              IPP_TAG_RANGE },
381   { "page-right",               IPP_TAG_INTEGER },
382   { "page-top",                 IPP_TAG_INTEGER },
383   { "penwidth",                 IPP_TAG_INTEGER },
384   { "ppi",                      IPP_TAG_INTEGER },
385   { "prettyprint",              IPP_TAG_BOOLEAN },
386   { "printer-resolution",       IPP_TAG_RESOLUTION },
387   { "print-quality",            IPP_TAG_ENUM },
388   { "saturation",               IPP_TAG_INTEGER },
389   { "scaling",                  IPP_TAG_INTEGER },
390   { "sides",                    IPP_TAG_KEYWORD },
391   { "wrap",                     IPP_TAG_BOOLEAN },
392   { "number-up-layout",         IPP_TAG_INTEGER }
393 };
394
395
396 static ipp_tag_t
397 _find_option_tag (const gchar *option)
398 {
399   int lower_bound, upper_bound, num_options;
400   int current_option;
401   ipp_tag_t result;
402
403   result = IPP_TAG_ZERO;
404
405   lower_bound = 0;
406   upper_bound = num_options = (int) G_N_ELEMENTS (ipp_options) - 1;
407   
408   while (1)
409     {
410       int match;
411       current_option = (int) (((upper_bound - lower_bound) / 2) + lower_bound);
412
413       match = strcasecmp (option, ipp_options[current_option].name);
414       if (match == 0)
415         {
416           result = ipp_options[current_option].value_tag;
417           return result;
418         }
419       else if (match < 0)
420         {
421           upper_bound = current_option - 1;
422         }
423       else
424         {
425           lower_bound = current_option + 1;
426         }
427
428       if (upper_bound == lower_bound && upper_bound == current_option)
429         return result;
430
431       if (upper_bound < 0)
432         return result;
433
434       if (lower_bound > num_options)
435         return result;
436
437       if (upper_bound < lower_bound)
438         return result;
439     }
440 }
441
442 /*
443  * Note that this function uses IPP_TAG_JOB, so it is
444  * only suitable for IPP Group 2 attributes.
445  * See RFC 2911.
446  */
447 void
448 gtk_cups_request_encode_option (GtkCupsRequest *request,
449                                 const gchar    *option,
450                                 const gchar    *value)
451 {
452   ipp_tag_t option_tag;
453
454   g_return_if_fail (option != NULL);
455   g_return_if_fail (value != NULL);
456
457   option_tag = _find_option_tag (option);
458
459   if (option_tag == IPP_TAG_ZERO)
460     {
461       option_tag = IPP_TAG_NAME;
462       if (strcasecmp (value, "true") == 0 ||
463           strcasecmp (value, "false") == 0)
464         {
465           option_tag = IPP_TAG_BOOLEAN;
466         }
467     }
468         
469   switch (option_tag)
470     {
471       case IPP_TAG_INTEGER:
472       case IPP_TAG_ENUM:
473         ippAddInteger (request->ipp_request,
474                        IPP_TAG_JOB,
475                        option_tag,
476                        option,
477                        strtol (value, NULL, 0));
478         break;
479
480       case IPP_TAG_BOOLEAN:
481         {
482           char b;
483           
484           if (strcasecmp (value, "true") == 0 ||
485               strcasecmp (value, "on") == 0 ||
486               strcasecmp (value, "yes") == 0) 
487             b = 1;
488           else
489             b = 0;
490
491           ippAddBoolean (request->ipp_request,
492                          IPP_TAG_JOB,
493                          option,
494                          b);
495         
496           break;
497         }
498         
499       case IPP_TAG_RANGE:
500         {
501           char  *s;
502           int lower;
503           int upper;
504           
505           if (*value == '-')
506             {
507               lower = 1;
508               s = (char *)value;
509             }
510           else
511             lower = strtol (value, &s, 0);
512
513           if (*s == '-')
514             {
515               if (s[1])
516                 upper = strtol (s + 1, NULL, 0);
517               else
518                 upper = 2147483647;
519             }
520           else
521             upper = lower;
522          
523           ippAddRange (request->ipp_request,
524                        IPP_TAG_JOB,
525                        option,
526                        lower,
527                        upper);
528
529           break;
530         }
531
532       case IPP_TAG_RESOLUTION:
533         {
534           char *s;
535           int xres;
536           int yres;
537           ipp_res_t units;
538           
539           xres = strtol (value, &s, 0);
540
541           if (*s == 'x')
542             yres = strtol (s + 1, &s, 0);
543           else
544             yres = xres;
545
546           if (strcasecmp (s, "dpc") == 0)
547             units = IPP_RES_PER_CM;
548           else
549             units = IPP_RES_PER_INCH;
550           
551           ippAddResolution (request->ipp_request,
552                             IPP_TAG_JOB,
553                             option,
554                             units,
555                             xres,
556                             yres);
557
558           break;
559         }
560
561       default:
562         {
563           char *values;
564           char *s;
565           int in_quotes;
566           char *next;
567           GPtrArray *strings;
568           
569           values = g_strdup (value);
570           strings = NULL;
571           in_quotes = 0;
572
573           for (s = values, next = s; *s != '\0'; s++)
574             {
575               if (in_quotes != 2 && *s == '\'')
576                 {
577                   /* skip quoted value */
578                   if (in_quotes == 0)
579                     in_quotes = 1;
580                   else
581                     in_quotes = 0;
582                 }
583               else if (in_quotes != 1 && *s == '\"')
584                 {
585                   /* skip quoted value */
586                   if (in_quotes == 0)
587                     in_quotes = 2;
588                   else
589                     in_quotes = 0;
590                 }
591               else if (in_quotes == 0 && *s == ',')
592                 {
593                   /* found delimiter, add to value array */
594                   *s = '\0';
595                   if (strings == NULL)
596                     strings = g_ptr_array_new ();
597                   g_ptr_array_add (strings, next);
598                   next = s + 1;
599                 }
600               else if (in_quotes == 0 && *s == '\\' && s[1] != '\0')
601                 {
602                   /* skip escaped character */
603                   s++;
604                 }
605             }
606           
607           if (strings == NULL)
608             {
609               /* single value */
610               ippAddString (request->ipp_request,
611                             IPP_TAG_JOB,
612                             option_tag,
613                             option,
614                             NULL,
615                             value);
616             }
617           else
618             {
619               /* multiple values */
620               
621               /* add last value */
622               g_ptr_array_add (strings, next);
623               
624               ippAddStrings (request->ipp_request,
625                              IPP_TAG_JOB,
626                              option_tag,
627                              option,
628                              strings->len,
629                              NULL,
630                              (const char **) strings->pdata);
631               g_ptr_array_free (strings, TRUE);
632             }
633
634           g_free (values);
635         }
636
637         break;
638     }
639 }
640                                 
641
642 static void
643 _connect (GtkCupsRequest *request)
644 {
645   request->poll_state = GTK_CUPS_HTTP_IDLE;
646
647   if (request->http == NULL)
648     {
649       request->http = httpConnectEncrypt (request->server, 
650                                           ippPort (), 
651                                           cupsEncryption ());
652
653       if (request->http == NULL)
654         request->attempts++;
655
656       if (request->http)
657         httpBlocking (request->http, 0);
658         
659       request->own_http = TRUE;
660     }
661   else
662     {
663       request->attempts = 0;
664       request->state++;
665
666       /* we always write to the socket after we get
667          the connection */
668       request->poll_state = GTK_CUPS_HTTP_WRITE;
669     }
670 }
671
672 static void 
673 _post_send (GtkCupsRequest *request)
674 {
675   gchar length[255];
676   struct stat data_info;
677
678   GTK_NOTE (PRINTING,
679             g_print ("CUPS Backend: %s\n", G_STRFUNC));
680
681   request->poll_state = GTK_CUPS_HTTP_WRITE;
682
683   if (request->data_io != NULL)
684     {
685       fstat (g_io_channel_unix_get_fd (request->data_io), &data_info);
686       sprintf (length, "%lu", (unsigned long) (ippLength (request->ipp_request) + data_info.st_size));
687     }
688   else
689     sprintf (length, "%lu", (unsigned long) ippLength (request->ipp_request));
690         
691   httpClearFields (request->http);
692   httpSetField (request->http, HTTP_FIELD_CONTENT_LENGTH, length);
693   httpSetField (request->http, HTTP_FIELD_CONTENT_TYPE, "application/ipp");
694 #ifdef HAVE_HTTPGETAUTHSTRING
695   httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString (request->http));
696 #else
697 #ifdef HAVE_HTTP_AUTHSTRING
698   httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring);
699 #endif
700 #endif
701
702   if (httpPost (request->http, request->resource))
703     {
704       if (httpReconnect (request->http))
705         {
706           request->state = GTK_CUPS_POST_DONE;
707           request->poll_state = GTK_CUPS_HTTP_IDLE;
708
709           /* TODO: should add a status or error code for failed post */
710           gtk_cups_result_set_error (request->result,
711                                      GTK_CUPS_ERROR_GENERAL,
712                                      0,
713                                      0,
714                                      "Failed Post");
715         }
716
717       request->attempts++;
718       return;    
719     }
720         
721     request->attempts = 0;
722
723     request->state = GTK_CUPS_POST_WRITE_REQUEST;
724     request->ipp_request->state = IPP_IDLE;
725 }
726
727 static void 
728 _post_write_request (GtkCupsRequest *request)
729 {
730   ipp_state_t ipp_status;
731
732   GTK_NOTE (PRINTING,
733             g_print ("CUPS Backend: %s\n", G_STRFUNC));
734
735   request->poll_state = GTK_CUPS_HTTP_WRITE;
736   
737   ipp_status = ippWrite (request->http, request->ipp_request);
738
739   if (ipp_status == IPP_ERROR)
740     {
741       int cups_error = cupsLastError ();
742       request->state = GTK_CUPS_POST_DONE;
743       request->poll_state = GTK_CUPS_HTTP_IDLE;
744  
745       gtk_cups_result_set_error (request->result, 
746                                  GTK_CUPS_ERROR_IPP,
747                                  ipp_status,
748                                  cups_error,
749                                  "%s", 
750                                  ippErrorString (cups_error));
751       return;
752     }
753
754   if (ipp_status == IPP_DATA)
755     {
756       if (request->data_io != NULL)
757         request->state = GTK_CUPS_POST_WRITE_DATA;
758       else
759         {
760           request->state = GTK_CUPS_POST_CHECK;
761           request->poll_state = GTK_CUPS_HTTP_READ;
762         }
763     }
764 }
765
766 static void 
767 _post_write_data (GtkCupsRequest *request)
768 {
769   gsize bytes;
770   char buffer[_GTK_CUPS_MAX_CHUNK_SIZE];
771   http_status_t http_status;
772
773   GTK_NOTE (PRINTING,
774             g_print ("CUPS Backend: %s\n", G_STRFUNC));
775
776   request->poll_state = GTK_CUPS_HTTP_WRITE;
777   
778   if (httpCheck (request->http))
779     http_status = httpUpdate (request->http);
780   else
781     http_status = request->last_status;
782
783   request->last_status = http_status;
784
785
786   if (http_status == HTTP_CONTINUE || http_status == HTTP_OK)
787     {
788       GIOStatus io_status;
789       GError *error;
790
791       error = NULL;
792
793       /* send data */
794       io_status =
795         g_io_channel_read_chars (request->data_io, 
796                                  buffer, 
797                                  _GTK_CUPS_MAX_CHUNK_SIZE,
798                                  &bytes,
799                                  &error);
800
801       if (io_status == G_IO_STATUS_ERROR)
802         {
803           request->state = GTK_CUPS_POST_DONE;
804           request->poll_state = GTK_CUPS_HTTP_IDLE;
805      
806           gtk_cups_result_set_error (request->result,
807                                      GTK_CUPS_ERROR_IO,
808                                      io_status,
809                                      error->code, 
810                                      "Error reading from cache file: %s",
811                                      error->message);
812
813           g_error_free (error);
814           return;
815         }
816       else if (bytes == 0 && io_status == G_IO_STATUS_EOF)
817         {
818           request->state = GTK_CUPS_POST_CHECK;
819           request->poll_state = GTK_CUPS_HTTP_READ;
820
821           request->attempts = 0;
822           return;
823         }
824
825
826 #if HAVE_CUPS_API_1_2
827       if (httpWrite2 (request->http, buffer, bytes) < bytes)
828 #else
829       if (httpWrite (request->http, buffer, (int) bytes) < bytes)
830 #endif /* HAVE_CUPS_API_1_2 */
831         {
832           int http_errno;
833
834           http_errno = httpError (request->http);
835
836           request->state = GTK_CUPS_POST_DONE;
837           request->poll_state = GTK_CUPS_HTTP_IDLE;
838      
839           gtk_cups_result_set_error (request->result,
840                                      GTK_CUPS_ERROR_HTTP,
841                                      http_status,
842                                      http_errno, 
843                                      "Error writing to socket in Post %s", 
844                                      g_strerror (http_errno));
845           return;
846         }
847     }
848   else if (http_status == HTTP_UNAUTHORIZED)
849     {
850       request->state = GTK_CUPS_POST_CHECK;
851       request->poll_state = GTK_CUPS_HTTP_READ;
852
853       request->attempts = 0;
854       return;
855     }
856   else
857     {
858       request->attempts++;
859     }
860 }
861
862 static void
863 _post_auth (GtkCupsRequest *request)
864 {
865   if (request->password_state == GTK_CUPS_PASSWORD_HAS)
866     {
867       if (request->password == NULL)
868         {
869           request->state = GTK_CUPS_POST_DONE;
870           request->poll_state = GTK_CUPS_HTTP_IDLE;
871
872           gtk_cups_result_set_error (request->result, 
873                                      GTK_CUPS_ERROR_AUTH,
874                                      0,
875                                      1,
876                                      "Canceled by user");
877         }
878       else
879         request->state = GTK_CUPS_POST_CHECK;
880     }
881 }
882
883 static void
884 _get_auth (GtkCupsRequest *request)
885 {
886   if (request->password_state == GTK_CUPS_PASSWORD_HAS)
887     {
888       if (request->password == NULL)
889         {
890           request->state = GTK_CUPS_GET_DONE;
891           request->poll_state = GTK_CUPS_HTTP_IDLE;
892
893           gtk_cups_result_set_error (request->result, 
894                                      GTK_CUPS_ERROR_AUTH,
895                                      0,
896                                      1,
897                                      "Canceled by user");
898         }
899       else
900         request->state = GTK_CUPS_GET_CHECK;
901     }
902 }
903
904 /* Very ugly hack: cups has a stupid synchronous password callback 
905  * that doesn't even take the request or user data parameters, so 
906  * we have to use a static variable to pass the password to it.
907  * Not threadsafe !
908  * The callback sets cups_password to NULL to signal that the 
909  * password has been used.
910  */
911 static char *cups_password;
912 static char *cups_username;
913
914 static const char *
915 passwordCB (const char *prompt)
916 {
917   char *pwd = cups_password;
918   cups_password = NULL;
919
920   cupsSetUser (cups_username);
921
922   return pwd;
923 }
924
925 static void 
926 _post_check (GtkCupsRequest *request)
927 {
928   http_status_t http_status;
929
930   http_status = request->last_status;
931
932   GTK_NOTE (PRINTING,
933             g_print ("CUPS Backend: %s - status %i\n", G_STRFUNC, http_status));
934
935   request->poll_state = GTK_CUPS_HTTP_READ;
936
937   if (http_status == HTTP_CONTINUE)
938     {
939       goto again; 
940     }
941   else if (http_status == HTTP_UNAUTHORIZED)
942     {
943       int auth_result = -1;
944       httpFlush (request->http);
945
946       if (request->password_state == GTK_CUPS_PASSWORD_APPLIED)
947         {
948           request->password_state = GTK_CUPS_PASSWORD_NOT_VALID;
949           request->state = GTK_CUPS_POST_AUTH;
950           request->need_password = TRUE;
951
952           return;
953         }
954
955       /* Negotiate */
956       if (strncmp (httpGetField (request->http, HTTP_FIELD_WWW_AUTHENTICATE), "Negotiate", 9) == 0)
957         {
958           auth_result = cupsDoAuthentication (request->http, "POST", request->resource);
959         }
960       /* Basic, BasicDigest, Digest and PeerCred */
961       else
962         {
963           if (request->password_state == GTK_CUPS_PASSWORD_NONE)
964             {
965               cups_password = g_strdup ("");
966               cups_username = request->username;
967               cupsSetPasswordCB (passwordCB);
968
969               /* This call success for PeerCred authentication */
970               auth_result = cupsDoAuthentication (request->http, "POST", request->resource);
971
972               if (auth_result != 0)
973                 {
974                   /* move to AUTH state to let the backend 
975                    * ask for a password
976                    */ 
977                   request->state = GTK_CUPS_POST_AUTH;
978                   request->need_password = TRUE;
979
980                   return;
981                 }
982             }
983           else
984             {
985               cups_password = request->password;
986               cups_username = request->username;
987
988               auth_result = cupsDoAuthentication (request->http, "POST", request->resource);
989
990               if (cups_password != NULL)
991                 return;
992
993               if (request->password != NULL)
994                 {
995                   memset (request->password, 0, strlen (request->password));
996                   g_free (request->password);
997                   request->password = NULL;
998                 }
999
1000               request->password_state = GTK_CUPS_PASSWORD_APPLIED;
1001             }
1002         }
1003
1004       if (auth_result ||
1005           httpReconnect (request->http))
1006         {
1007           /* if the password has been used, reset password_state
1008            * so that we ask for a new one next time around
1009            */ 
1010           if (cups_password == NULL)
1011             request->password_state = GTK_CUPS_PASSWORD_NONE;
1012
1013           request->state = GTK_CUPS_POST_DONE;
1014           request->poll_state = GTK_CUPS_HTTP_IDLE;
1015           gtk_cups_result_set_error (request->result, 
1016                                      GTK_CUPS_ERROR_AUTH,
1017                                      0,
1018                                      0,
1019                                      "Not authorized");
1020           return;
1021         }
1022       
1023       if (request->data_io != NULL)
1024         g_io_channel_seek_position (request->data_io, 0, G_SEEK_SET, NULL);
1025
1026       request->state = GTK_CUPS_POST_CONNECT;
1027       request->poll_state = GTK_CUPS_HTTP_WRITE;
1028     }
1029   else if (http_status == HTTP_ERROR)
1030     {
1031       int error = httpError (request->http);
1032 #ifdef G_OS_WIN32
1033       if (error != WSAENETDOWN && error != WSAENETUNREACH)
1034 #else
1035       if (error != ENETDOWN && error != ENETUNREACH)      
1036 #endif /* G_OS_WIN32 */
1037         {
1038           request->attempts++;
1039           goto again;
1040         }
1041       else
1042         {
1043           request->state = GTK_CUPS_POST_DONE;
1044           request->poll_state = GTK_CUPS_HTTP_IDLE;
1045      
1046           gtk_cups_result_set_error (request->result,
1047                                      GTK_CUPS_ERROR_HTTP,
1048                                      http_status,
1049                                      error, 
1050                                      "Unknown HTTP error");
1051
1052           return;
1053         }
1054     }
1055   else if (http_status == HTTP_UPGRADE_REQUIRED)
1056     {
1057       /* Flush any error message... */
1058       httpFlush (request->http);
1059
1060       cupsSetEncryption (HTTP_ENCRYPT_REQUIRED);
1061       request->state = GTK_CUPS_POST_CONNECT;
1062
1063       /* Reconnect... */
1064       httpReconnect (request->http);
1065
1066       /* Upgrade with encryption... */
1067       httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED);
1068  
1069       request->attempts++;
1070       goto again;
1071     }
1072   else if (http_status != HTTP_OK)
1073     {
1074       int http_errno;
1075
1076       http_errno = httpError (request->http);
1077
1078       if (http_errno == EPIPE)
1079         request->state = GTK_CUPS_POST_CONNECT;
1080       else
1081         {
1082           request->state = GTK_CUPS_POST_DONE;
1083           gtk_cups_result_set_error (request->result,
1084                                      GTK_CUPS_ERROR_HTTP,
1085                                      http_status,
1086                                      http_errno, 
1087                                      "HTTP Error in POST %s", 
1088                                      g_strerror (http_errno));
1089           request->poll_state = GTK_CUPS_HTTP_IDLE;
1090  
1091           httpFlush (request->http); 
1092           return;
1093         }
1094
1095       request->poll_state = GTK_CUPS_HTTP_IDLE;
1096        
1097       httpFlush (request->http); 
1098       
1099       request->last_status = HTTP_CONTINUE;
1100       httpClose (request->http);
1101       request->http = NULL;
1102       return;  
1103     }
1104   else
1105     {
1106       request->state = GTK_CUPS_POST_READ_RESPONSE;
1107       return;
1108     }
1109
1110  again:
1111   http_status = HTTP_CONTINUE;
1112
1113   if (httpCheck (request->http))
1114     http_status = httpUpdate (request->http);
1115
1116   request->last_status = http_status;
1117 }
1118
1119 static void 
1120 _post_read_response (GtkCupsRequest *request)
1121 {
1122   ipp_state_t ipp_status;
1123
1124   GTK_NOTE (PRINTING,
1125             g_print ("CUPS Backend: %s\n", G_STRFUNC));
1126
1127   request->poll_state = GTK_CUPS_HTTP_READ;
1128
1129   if (request->result->ipp_response == NULL)
1130     request->result->ipp_response = ippNew();
1131
1132   ipp_status = ippRead (request->http, 
1133                         request->result->ipp_response);
1134
1135   if (ipp_status == IPP_ERROR)
1136     {
1137       int ipp_error = cupsLastError ();
1138       gtk_cups_result_set_error (request->result,  
1139                                  GTK_CUPS_ERROR_IPP,
1140                                  ipp_status,
1141                                  ipp_error,
1142                                  "%s",
1143                                  ippErrorString (ipp_error));
1144       
1145       ippDelete (request->result->ipp_response);
1146       request->result->ipp_response = NULL;
1147
1148       request->state = GTK_CUPS_POST_DONE;
1149       request->poll_state = GTK_CUPS_HTTP_IDLE;
1150     }
1151   else if (ipp_status == IPP_DATA)
1152     {
1153       request->state = GTK_CUPS_POST_DONE;
1154       request->poll_state = GTK_CUPS_HTTP_IDLE;
1155     }
1156 }
1157
1158 static void 
1159 _get_send (GtkCupsRequest *request)
1160 {
1161   GTK_NOTE (PRINTING,
1162             g_print ("CUPS Backend: %s\n", G_STRFUNC));
1163
1164   request->poll_state = GTK_CUPS_HTTP_WRITE;
1165
1166   if (request->data_io == NULL)
1167     {
1168       gtk_cups_result_set_error (request->result,
1169                                  GTK_CUPS_ERROR_IO,
1170                                  G_IO_STATUS_ERROR,
1171                                  G_IO_CHANNEL_ERROR_FAILED, 
1172                                  "Get requires an open io channel");
1173
1174       request->state = GTK_CUPS_GET_DONE;
1175       request->poll_state = GTK_CUPS_HTTP_IDLE;
1176
1177       return;
1178     }
1179
1180   httpClearFields (request->http);
1181 #ifdef HAVE_HTTPGETAUTHSTRING
1182   httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString (request->http));
1183 #else
1184 #ifdef HAVE_HTTP_AUTHSTRING
1185   httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring);
1186 #endif
1187 #endif
1188
1189   if (httpGet (request->http, request->resource))
1190     {
1191       if (httpReconnect (request->http))
1192         {
1193           request->state = GTK_CUPS_GET_DONE;
1194           request->poll_state = GTK_CUPS_HTTP_IDLE;
1195          
1196           /* TODO: should add a status or error code for failed GET */ 
1197           gtk_cups_result_set_error (request->result, 
1198                                      GTK_CUPS_ERROR_GENERAL,
1199                                      0,
1200                                      0,
1201                                      "Failed Get");
1202         }
1203
1204       request->attempts++;
1205       return;    
1206     }
1207
1208   if (httpCheck (request->http))
1209     request->last_status = httpUpdate (request->http);
1210         
1211   request->attempts = 0;
1212
1213   request->state = GTK_CUPS_GET_CHECK;
1214   request->poll_state = GTK_CUPS_HTTP_READ;
1215   
1216   request->ipp_request->state = IPP_IDLE;
1217 }
1218
1219 static void 
1220 _get_check (GtkCupsRequest *request)
1221 {
1222   http_status_t http_status;
1223
1224   GTK_NOTE (PRINTING,
1225             g_print ("CUPS Backend: %s\n", G_STRFUNC));
1226
1227   http_status = request->last_status;
1228
1229   request->poll_state = GTK_CUPS_HTTP_READ;
1230
1231   if (http_status == HTTP_CONTINUE)
1232     {
1233       goto again; 
1234     }
1235   else if (http_status == HTTP_UNAUTHORIZED)
1236     {
1237       int auth_result = -1;
1238       httpFlush (request->http);
1239
1240       if (request->password_state == GTK_CUPS_PASSWORD_APPLIED)
1241         {
1242           request->password_state = GTK_CUPS_PASSWORD_NOT_VALID;
1243           request->state = GTK_CUPS_GET_AUTH;
1244           request->need_password = TRUE;
1245
1246           return;
1247         }
1248
1249       /* Negotiate */
1250       if (strncmp (httpGetField (request->http, HTTP_FIELD_WWW_AUTHENTICATE), "Negotiate", 9) == 0)
1251         {
1252           auth_result = cupsDoAuthentication (request->http, "GET", request->resource);
1253         }
1254       /* Basic, BasicDigest, Digest and PeerCred */
1255       else
1256         {
1257           if (request->password_state == GTK_CUPS_PASSWORD_NONE)
1258             {
1259               cups_password = g_strdup ("");
1260               cups_username = request->username;
1261               cupsSetPasswordCB (passwordCB);
1262
1263               /* This call success for PeerCred authentication */
1264               auth_result = cupsDoAuthentication (request->http, "GET", request->resource);
1265
1266               if (auth_result != 0)
1267                 {
1268                   /* move to AUTH state to let the backend
1269                    * ask for a password
1270                    */
1271                   request->state = GTK_CUPS_GET_AUTH;
1272                   request->need_password = TRUE;
1273
1274                   return;
1275                 }
1276             }
1277           else
1278             {
1279               cups_password = request->password;
1280               cups_username = request->username;
1281
1282               auth_result = cupsDoAuthentication (request->http, "GET", request->resource);
1283
1284               if (cups_password != NULL)
1285                 return;
1286
1287               if (request->password != NULL)
1288                 {
1289                   memset (request->password, 0, strlen (request->password));
1290                   g_free (request->password);
1291                   request->password = NULL;
1292                 }
1293
1294               request->password_state = GTK_CUPS_PASSWORD_APPLIED;
1295             }
1296         }
1297
1298       if (auth_result ||
1299           httpReconnect (request->http))
1300         {
1301           /* if the password has been used, reset password_state
1302            * so that we ask for a new one next time around
1303            */
1304           if (cups_password == NULL)
1305             request->password_state = GTK_CUPS_PASSWORD_NONE;
1306
1307           request->state = GTK_CUPS_GET_DONE;
1308           request->poll_state = GTK_CUPS_HTTP_IDLE;
1309           gtk_cups_result_set_error (request->result, 
1310                                      GTK_CUPS_ERROR_AUTH,
1311                                      0,
1312                                      0,
1313                                      "Not authorized");
1314           return;
1315         }
1316
1317       request->state = GTK_CUPS_GET_SEND;
1318       request->last_status = HTTP_CONTINUE;
1319
1320      return;
1321     }
1322   else if (http_status == HTTP_UPGRADE_REQUIRED)
1323     {
1324       /* Flush any error message... */
1325       httpFlush (request->http);
1326
1327       cupsSetEncryption (HTTP_ENCRYPT_REQUIRED);
1328       request->state = GTK_CUPS_GET_CONNECT;
1329
1330       /* Reconnect... */
1331       httpReconnect (request->http);
1332
1333       /* Upgrade with encryption... */
1334       httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED);
1335  
1336       request->attempts++;
1337       goto again;
1338     }
1339   else if (http_status != HTTP_OK)
1340     {
1341       int http_errno;
1342
1343       http_errno = httpError (request->http);
1344
1345       if (http_errno == EPIPE)
1346         request->state = GTK_CUPS_GET_CONNECT;
1347       else
1348         {
1349           request->state = GTK_CUPS_GET_DONE;
1350           gtk_cups_result_set_error (request->result,
1351                                      GTK_CUPS_ERROR_HTTP,
1352                                      http_status,
1353                                      http_errno, 
1354                                      "HTTP Error in GET %s", 
1355                                      g_strerror (http_errno));
1356           request->poll_state = GTK_CUPS_HTTP_IDLE;
1357           httpFlush (request->http);
1358
1359           return;
1360         }
1361
1362       request->poll_state = GTK_CUPS_HTTP_IDLE;
1363       httpFlush (request->http);
1364       httpClose (request->http);
1365       request->last_status = HTTP_CONTINUE;
1366       request->http = NULL;
1367       return;
1368
1369     }
1370   else
1371     {
1372       request->state = GTK_CUPS_GET_READ_DATA;
1373       return;
1374     }
1375
1376  again:
1377   http_status = HTTP_CONTINUE;
1378
1379   if (httpCheck (request->http))
1380     http_status = httpUpdate (request->http);
1381
1382   request->last_status = http_status;
1383
1384 }
1385
1386 static void 
1387 _get_read_data (GtkCupsRequest *request)
1388 {
1389   char buffer[_GTK_CUPS_MAX_CHUNK_SIZE];
1390   gsize bytes;
1391   gsize bytes_written;
1392   GIOStatus io_status;
1393   GError *error;
1394
1395   GTK_NOTE (PRINTING,
1396             g_print ("CUPS Backend: %s\n", G_STRFUNC));
1397
1398   error = NULL;
1399
1400   request->poll_state = GTK_CUPS_HTTP_READ;
1401
1402 #if HAVE_CUPS_API_1_2
1403   bytes = httpRead2 (request->http, buffer, sizeof (buffer));
1404 #else
1405   bytes = httpRead (request->http, buffer, sizeof (buffer));
1406 #endif /* HAVE_CUPS_API_1_2 */
1407
1408   GTK_NOTE (PRINTING,
1409             g_print ("CUPS Backend: %"G_GSIZE_FORMAT" bytes read\n", bytes));
1410   
1411   if (bytes == 0)
1412     {
1413       request->state = GTK_CUPS_GET_DONE;
1414       request->poll_state = GTK_CUPS_HTTP_IDLE;
1415
1416       return;
1417     }
1418   
1419   io_status =
1420     g_io_channel_write_chars (request->data_io, 
1421                               buffer, 
1422                               bytes, 
1423                               &bytes_written,
1424                               &error);
1425
1426   if (io_status == G_IO_STATUS_ERROR)
1427     {
1428       request->state = GTK_CUPS_GET_DONE;
1429       request->poll_state = GTK_CUPS_HTTP_IDLE;
1430     
1431       gtk_cups_result_set_error (request->result,
1432                                  GTK_CUPS_ERROR_IO,
1433                                  io_status,
1434                                  error->code, 
1435                                  error->message);
1436       g_error_free (error);
1437     }
1438 }
1439
1440 gboolean
1441 gtk_cups_request_is_done (GtkCupsRequest *request)
1442 {
1443   return (request->state == GTK_CUPS_REQUEST_DONE);
1444 }
1445
1446 gboolean
1447 gtk_cups_result_is_error (GtkCupsResult *result)
1448 {
1449   return result->is_error;
1450 }
1451
1452 ipp_t *
1453 gtk_cups_result_get_response (GtkCupsResult *result)
1454 {
1455   return result->ipp_response;
1456 }
1457
1458 GtkCupsErrorType
1459 gtk_cups_result_get_error_type (GtkCupsResult *result)
1460 {
1461   return result->error_type;
1462 }
1463
1464 int
1465 gtk_cups_result_get_error_status (GtkCupsResult *result)
1466 {
1467   return result->error_status;
1468 }
1469
1470 int
1471 gtk_cups_result_get_error_code (GtkCupsResult *result)
1472 {
1473   return result->error_code;
1474 }
1475
1476 const char *
1477 gtk_cups_result_get_error_string (GtkCupsResult *result)
1478 {
1479   return result->error_msg; 
1480 }
1481
1482 /* This function allocates new instance of GtkCupsConnectionTest() and creates
1483  * a socket for communication with a CUPS server 'server'.
1484  */
1485 GtkCupsConnectionTest *
1486 gtk_cups_connection_test_new (const char *server)
1487 {
1488   GtkCupsConnectionTest *result = NULL;
1489 #ifdef HAVE_CUPS_API_1_2
1490   gchar                 *port_str = NULL;
1491
1492   result = g_new (GtkCupsConnectionTest, 1);
1493
1494   port_str = g_strdup_printf ("%d", ippPort ());
1495
1496   if (server != NULL)
1497     result->addrlist = httpAddrGetList (server, AF_UNSPEC, port_str);
1498   else
1499     result->addrlist = httpAddrGetList (cupsServer (), AF_UNSPEC, port_str);
1500
1501   g_free (port_str);
1502
1503   result->socket = -1;
1504   result->current_addr = NULL;
1505   result->last_wrong_addr = NULL;
1506   result->at_init = GTK_CUPS_CONNECTION_NOT_AVAILABLE;
1507
1508   result->at_init = gtk_cups_connection_test_get_state (result);
1509 #else
1510   result = g_new (GtkCupsConnectionTest, 1);
1511 #endif
1512
1513   return result;
1514 }
1515
1516
1517 /* A non-blocking test whether it is possible to connect to a CUPS server specified
1518  * inside of GtkCupsConnectionTest structure.
1519  *  - you need to check it more then once.
1520  * The connection is closed after a successful connection.
1521  */
1522 GtkCupsConnectionState 
1523 gtk_cups_connection_test_get_state (GtkCupsConnectionTest *test)
1524 {
1525 #ifdef HAVE_CUPS_API_1_2
1526   GtkCupsConnectionState result = GTK_CUPS_CONNECTION_NOT_AVAILABLE;
1527   http_addrlist_t       *iter;
1528   gint                   error_code;
1529   gint                   flags;
1530   gint                   code;
1531
1532   if (test == NULL)
1533     return GTK_CUPS_CONNECTION_NOT_AVAILABLE;
1534
1535   if (test->at_init == GTK_CUPS_CONNECTION_AVAILABLE)
1536     {
1537       test->at_init = GTK_CUPS_CONNECTION_NOT_AVAILABLE;
1538       return GTK_CUPS_CONNECTION_AVAILABLE;
1539     }
1540   else
1541     {
1542       if (test->socket == -1)
1543         {
1544           if (test->last_wrong_addr != NULL && test->last_wrong_addr->next != NULL)
1545             iter = test->last_wrong_addr->next;
1546           else
1547             {
1548               test->last_wrong_addr = NULL;
1549               iter = test->addrlist;
1550             }
1551
1552           while (iter)
1553             {
1554               test->socket = socket (iter->addr.addr.sa_family,
1555                                      SOCK_STREAM,
1556                                      0);
1557
1558               if (test->socket >= 0)
1559                 {
1560                   flags = fcntl (test->socket, F_GETFL);
1561
1562                   if (flags != -1)
1563                     flags |= O_NONBLOCK;
1564
1565                   fcntl (test->socket, F_SETFL, flags);
1566               
1567                   test->current_addr = iter;
1568               
1569                   break;
1570                 }
1571                iter = iter->next;
1572             }
1573         }
1574
1575       if (test->socket >= 0)
1576         {
1577           code = connect (test->socket,
1578                           &test->current_addr->addr.addr,
1579                           httpAddrLength (&test->current_addr->addr));
1580
1581           error_code = errno;
1582
1583           if (code == 0 || error_code == EISCONN)
1584             {
1585               close (test->socket);
1586               test->socket = -1;
1587               test->current_addr = NULL;
1588               result = GTK_CUPS_CONNECTION_AVAILABLE;
1589             }
1590           else
1591             {
1592               if (error_code == EALREADY || error_code == EINPROGRESS)
1593                 result = GTK_CUPS_CONNECTION_IN_PROGRESS;
1594               else
1595                 {
1596                   close (test->socket);
1597                   test->socket = -1;
1598                   test->last_wrong_addr = test->current_addr;
1599                   result = GTK_CUPS_CONNECTION_NOT_AVAILABLE;
1600                 }
1601             }
1602          }
1603
1604       return result;
1605     }
1606 #else
1607   return GTK_CUPS_CONNECTION_AVAILABLE;
1608 #endif
1609 }
1610
1611 /* This function frees memory used by the GtkCupsConnectionTest structure.
1612  */
1613 void 
1614 gtk_cups_connection_test_free (GtkCupsConnectionTest *test)
1615 {
1616   if (test == NULL)
1617     return;
1618
1619 #ifdef HAVE_CUPS_API_1_2
1620   test->current_addr = NULL;
1621   test->last_wrong_addr = NULL;
1622   httpAddrFreeList (test->addrlist);
1623   if (test->socket != -1)
1624     {
1625       close (test->socket);
1626       test->socket = -1;
1627     }
1628 #endif
1629   g_free (test);
1630 }