1 /* TODO: HTTP proxy support */
2 
3 #include "gskurltransferhttp.h"
4 #include "../gsknameresolver.h"
5 #include "../gskstreamclient.h"
6 #include "../ssl/gskstreamssl.h"
7 #include "../http/gskhttpclient.h"
8 #include <string.h>
9 
10 G_DEFINE_TYPE(GskUrlTransferHttp, gsk_url_transfer_http, GSK_TYPE_URL_TRANSFER);
11 
12 /* used to restart a transfer on a new URL (a redirect) */
13 static void start_name_resolution (GskUrlTransferHttp *http);
14 
15 struct _GskUrlTransferHttpModifierNode
16 {
17   GskUrlTransferHttpRequestModifierFunc modifier;
18   gpointer data;
19   GDestroyNotify destroy;
20   GskUrlTransferHttpModifierNode *next;
21 };
22 
23 static void
handle_http_response(GskHttpRequest * request,GskHttpResponse * response,GskStream * input,gpointer hook_data)24 handle_http_response (GskHttpRequest  *request,
25                       GskHttpResponse *response,
26                       GskStream       *input,
27                       gpointer         hook_data)
28 {
29   GskUrlTransfer *transfer = GSK_URL_TRANSFER (hook_data);
30   GskUrlTransferHttp *http = GSK_URL_TRANSFER_HTTP (hook_data);
31 
32   ++(http->response_count);
33   if (gsk_url_transfer_is_done (transfer))
34     return;
35 
36   /* For exposition we retain copy
37      the entire list of GskHttpStatusCodes.
38 
39      However, the error case is the most common,
40      which we handle in the 'default' case.
41 
42      Therefore, most of the error status-codes
43      are commented out:  they will use the default case. */
44   switch (response->status_code)
45     {
46       /* errors i don't expect should ever happen, given the request
47        * we've issued (handled with the default case) */
48     /*case GSK_HTTP_STATUS_CONTINUE:*/
49     /*case GSK_HTTP_STATUS_SWITCHING_PROTOCOLS:*/
50     /*case GSK_HTTP_STATUS_NOT_MODIFIED:*/
51     /*case GSK_HTTP_STATUS_BAD_REQUEST:*/
52     /*case GSK_HTTP_STATUS_CREATED:*/
53     /*case GSK_HTTP_STATUS_ACCEPTED:*/
54     /*case GSK_HTTP_STATUS_NONAUTHORITATIVE_INFO:*/
55     /*case GSK_HTTP_STATUS_RESET_CONTENT:*/
56     /*case GSK_HTTP_STATUS_PARTIAL_CONTENT:*/
57     /*case GSK_HTTP_STATUS_CONFLICT:*/
58     /*case GSK_HTTP_STATUS_BAD_RANGE:*/
59     /*case GSK_HTTP_STATUS_BAD_GATEWAY:*/
60     /*case GSK_HTTP_STATUS_GATEWAY_TIMEOUT:*/
61 
62       /* XXX: errors that i'm not sure about
63               (currently handled with the default case) */
64     /*case GSK_HTTP_STATUS_MULTIPLE_CHOICES:*/
65     /*case GSK_HTTP_STATUS_USE_PROXY:*/
66     /*case GSK_HTTP_STATUS_PROXY_AUTH_REQUIRED:*/
67 
68     case GSK_HTTP_STATUS_NO_CONTENT:
69     case GSK_HTTP_STATUS_OK:
70       gsk_url_transfer_set_response (transfer, G_OBJECT (response));
71       if (input)
72         gsk_url_transfer_set_download (transfer, input);
73       gsk_url_transfer_notify_done (transfer, GSK_URL_TRANSFER_SUCCESS);
74       break;
75 
76       /* errors that really mean the download failed,
77        * in a protologically valid way.
78        * (some of these indicate a seriously broken server)
79        * (handled with the default case)
80        */
81     /*case GSK_HTTP_STATUS_UNAUTHORIZED:*/
82     /*case GSK_HTTP_STATUS_PAYMENT_REQUIRED:*/
83     /*case GSK_HTTP_STATUS_FORBIDDEN:*/
84     /*case GSK_HTTP_STATUS_NOT_FOUND:*/
85     /*case GSK_HTTP_STATUS_METHOD_NOT_ALLOWED:*/
86     /*case GSK_HTTP_STATUS_NOT_ACCEPTABLE:*/
87     /*case GSK_HTTP_STATUS_REQUEST_TIMEOUT:*/
88     /*case GSK_HTTP_STATUS_GONE:*/
89     /*case GSK_HTTP_STATUS_LENGTH_REQUIRED:*/
90     /*case GSK_HTTP_STATUS_PRECONDITION_FAILED:*/
91     /*case GSK_HTTP_STATUS_ENTITY_TOO_LARGE:*/
92     /*case GSK_HTTP_STATUS_URI_TOO_LARGE:*/
93     /*case GSK_HTTP_STATUS_EXPECTATION_FAILED:*/
94     /*case GSK_HTTP_STATUS_UNSUPPORTED_MEDIA:*/
95     /*case GSK_HTTP_STATUS_INTERNAL_SERVER_ERROR:*/
96     /*case GSK_HTTP_STATUS_NOT_IMPLEMENTED:*/
97     /*case GSK_HTTP_STATUS_SERVICE_UNAVAILABLE:*/
98       /* XXX: if we get this, we should try again at HTTP/1.0... whatever */
99     /*case GSK_HTTP_STATUS_UNSUPPORTED_VERSION:*/
100 
101       /* redirections */
102     case GSK_HTTP_STATUS_MOVED_PERMANENTLY:
103     case GSK_HTTP_STATUS_FOUND:
104     case GSK_HTTP_STATUS_SEE_OTHER:
105     case GSK_HTTP_STATUS_TEMPORARY_REDIRECT:
106       {
107 	GskUrl *new_url = NULL;
108         gboolean is_permanent = (response->status_code == GSK_HTTP_STATUS_MOVED_PERMANENTLY);
109 	if (response->location != NULL)
110 	  {
111 	    GskUrl *old_url = transfer->url;
112 	    GError *error = NULL;
113 	    new_url = gsk_url_new_relative (old_url, response->location, &error);
114 	    if (new_url == NULL)
115 	      {
116                 GskUrlTransferResult result = GSK_URL_TRANSFER_ERROR_UNSUPPORTED;       /* XXX: not especially correct */
117                 g_warning ("redirect to invalid Location: %s: %s",
118                            response->location,
119                            error ? error->message : "unknown error");
120                 gsk_url_transfer_take_error (transfer, error);
121 		gsk_url_transfer_notify_done (transfer, result);
122 		if (input)
123                   gsk_io_read_shutdown (input, NULL);
124 		return;
125 	      }
126             if (new_url != NULL)
127               {
128                 if (!gsk_url_transfer_add_redirect (transfer,
129                                                     NULL,
130                                                     G_OBJECT (response),
131                                                     is_permanent,
132                                                     new_url))
133                   {
134                     if (input)
135                       gsk_io_read_shutdown (input, NULL);
136                     g_object_unref (new_url);
137                     return;
138                   }
139 
140                 g_object_unref (new_url);
141 
142                 if (transfer->follow_redirects)
143                   /* restart at name-lookup */
144                   start_name_resolution (http);
145                 else
146                   gsk_url_transfer_notify_done (transfer, GSK_URL_TRANSFER_REDIRECT);
147 		if (input)
148                   gsk_io_read_shutdown (input, NULL);
149 
150                 return;
151               }
152 	  }
153       }
154 
155       /* default case indicates an error occurred */
156     default:
157       {
158 	GEnumClass *eclass = g_type_class_ref (GSK_TYPE_HTTP_STATUS);
159 	GEnumValue *evalue = g_enum_get_value (eclass, response->status_code);
160 	const char *error_code_name = evalue ? evalue->value_nick : "**unknown status**";
161         GskUrlTransferResult result;
162 	gsk_url_transfer_take_error (transfer,
163                                      g_error_new (GSK_G_ERROR_DOMAIN,
164 				                  GSK_ERROR_HTTP_NOT_FOUND,
165 				                  "error downloading via http: error %d [%s]",
166 				                  response->status_code, error_code_name));
167 	g_type_class_unref (eclass);
168 
169         if (response->status_code / 100 == 4)   /* 400s are not found type errors */
170           result = GSK_URL_TRANSFER_ERROR_NOT_FOUND;
171         else if (response->status_code / 100 == 5)   /* 400s are server errors */
172           result = GSK_URL_TRANSFER_ERROR_SERVER_ERROR;
173         else
174           result = GSK_URL_TRANSFER_ERROR_UNSUPPORTED; /* who knows? */
175         gsk_url_transfer_notify_done (transfer, result);
176         if (input)
177           gsk_io_read_shutdown (input, NULL);
178 	break;
179       }
180     }
181 }
182 
183 static void
http_client_request_destroyed(gpointer data)184 http_client_request_destroyed (gpointer data)
185 {
186   GskUrlTransfer *transfer = GSK_URL_TRANSFER (data);
187   GskUrlTransferHttp *http = GSK_URL_TRANSFER_HTTP (data);
188 
189   /* http invariant */
190   g_assert (http->response_count <= http->request_count);
191 
192   g_assert (http->undestroyed_requests > 0);
193   --(http->undestroyed_requests);
194 
195   if (!transfer->timed_out
196    && !gsk_url_transfer_is_done (transfer)
197    && http->undestroyed_requests == 0
198    && http->response_count < http->request_count)
199     {
200       /* transfer has been aborted for mysterious reasons */
201       gsk_url_transfer_take_error (transfer,
202                                    g_error_new (GSK_G_ERROR_DOMAIN,
203                                                 GSK_ERROR_BAD_FORMAT,
204                                                 "unable to get HTTP response from server"));
205       gsk_url_transfer_notify_done (transfer, GSK_URL_TRANSFER_ERROR_SERVER_ERROR);
206     }
207   g_object_unref (transfer);
208 }
209 
210 static void
handle_name_resolution_succeeded(GskSocketAddress * address,gpointer data)211 handle_name_resolution_succeeded (GskSocketAddress *address,
212                                   gpointer          data)
213 {
214   GskUrlTransfer *transfer = GSK_URL_TRANSFER (data);
215   GskUrlTransferHttp *http = GSK_URL_TRANSFER_HTTP (data);
216   GError *error = NULL;
217   GskStream *transport;
218   GskHttpClient *http_client;
219   GskHttpRequest *http_request;
220   GskStream *upload_stream;
221   GskUrlTransferHttpModifierNode *modifier;
222   GskUrl *url = transfer->redirect_url ? transfer->redirect_url : transfer->url;
223 
224   if (gsk_url_transfer_is_done (transfer))
225     return;
226 
227   /* Create actual address (with correct port) */
228   {
229     GskSocketAddressIpv4 *found = GSK_SOCKET_ADDRESS_IPV4 (address);
230     GskSocketAddress *addr;
231     guint url_port = gsk_url_get_port (url);
232     if (http->is_proxy || found->ip_port == url_port)
233       addr = g_object_ref (address);
234     else
235       addr = gsk_socket_address_ipv4_new (found->ip_address, url_port);
236     gsk_url_transfer_set_address (transfer, addr);
237 
238     /* Create a TCP connection to that address. */
239     if (http->raw_transport != NULL)
240       {
241         /* from a redirect */
242         g_object_unref (http->raw_transport);
243       }
244     http->raw_transport = gsk_stream_new_connecting (addr, &error);
245     if (http->raw_transport == NULL)
246       {
247         gsk_url_transfer_take_error (transfer, error);
248         gsk_url_transfer_notify_done (transfer, GSK_URL_TRANSFER_ERROR_NO_SERVER);
249         return;
250       }
251     g_object_unref (addr);
252     addr = NULL;
253   }
254 
255   /* For SSL streams, create the ssl-transport */
256   if (url->scheme == GSK_URL_SCHEME_HTTPS)
257     {
258       transport = gsk_stream_ssl_new_client (http->ssl_cert,
259                                              http->ssl_key,
260                                              http->ssl_password,
261                                              http->raw_transport,
262                                              &error);
263       if (transport == NULL)
264         {
265           gsk_url_transfer_take_error (transfer, error);
266           gsk_url_transfer_notify_done (transfer, GSK_URL_TRANSFER_ERROR_BAD_REQUEST);
267           return;
268         }
269     }
270   else
271     {
272       /* otherwise, use the raw_transport directly. */
273       transport = g_object_ref (http->raw_transport);
274     }
275 
276   /* setup path */
277   {
278     char *to_free = NULL;
279     const char *path;
280     GskHttpVerb verb;
281     if (http->is_proxy)
282       path = to_free = gsk_url_to_string (url);
283     else if (url->query)
284       path = to_free = g_strdup_printf ("%s?%s", url->path, url->query);
285     else
286       path = url->path;
287     verb = gsk_url_transfer_has_upload (transfer) ? GSK_HTTP_VERB_POST : GSK_HTTP_VERB_GET;
288 
289     /* make basic request object */
290     http_request = gsk_http_request_new (verb, path);
291     g_free (to_free);
292     if (http->is_proxy)
293       {
294         /* TODO:
295            gsk_http_request_set_proxy_host?
296          */
297       }
298     else if (url->port == 0
299      || url->port == 80)
300       gsk_http_request_set_host (http_request, url->host);
301     else
302       {
303         guint hostlen = strlen (url->host);
304         guint hostport_len = hostlen + 20;
305         char *hostport = g_alloca (hostport_len);
306         g_snprintf (hostport, hostport_len,
307                     "%s:%u",
308                     url->host, url->port);
309         gsk_http_request_set_host (http_request, hostport);
310       }
311   }
312 
313   /* run modifiers */
314   for (modifier = http->first_modifier; modifier != NULL; modifier = modifier->next)
315     modifier->modifier (http_request, modifier->data);
316 
317   gsk_url_transfer_set_request (transfer, G_OBJECT (http_request));
318 
319   if (gsk_url_transfer_has_upload (transfer))
320     {
321       gssize size;
322       upload_stream = gsk_url_transfer_create_upload (transfer, &size, &error);
323       if (upload_stream == NULL)
324         {
325           g_object_unref (transport);
326           g_object_unref (http_request);
327           gsk_url_transfer_take_error (transfer, error);
328           gsk_url_transfer_notify_done (transfer, GSK_URL_TRANSFER_ERROR_BAD_REQUEST);
329           return;
330         }
331       if (size >= 0)
332         gsk_http_header_set_content_length (GSK_HTTP_HEADER (http_request), size);
333     }
334   else
335     {
336       upload_stream = NULL;
337     }
338 
339   http_client = gsk_http_client_new ();
340   ++(http->request_count);
341   ++(http->undestroyed_requests);
342   gsk_http_client_propagate_content_read_shutdown (http_client);
343   gsk_http_client_request (http_client, http_request, upload_stream,
344                            handle_http_response,
345                            g_object_ref (transfer),
346                            http_client_request_destroyed);
347   gsk_http_client_shutdown_when_done (http_client);
348   if (!gsk_stream_attach_pair (transport, GSK_STREAM (http_client), &error))
349     {
350       g_warning ("gsk_stream_attach_pair: transport/http-client: %s", error->message);
351       g_clear_error (&error);
352     }
353   if (upload_stream)
354     g_object_unref (upload_stream);
355   g_object_unref (transport);
356   g_object_unref (http_request);
357   g_object_unref (http_client);
358 }
359 
360 static void
handle_name_resolution_failed(GError * error,gpointer data)361 handle_name_resolution_failed (GError       *error,
362                                gpointer      data)
363 {
364   GskUrlTransfer *transfer = GSK_URL_TRANSFER (data);
365   gsk_url_transfer_set_error (transfer, error);
366   if (!gsk_url_transfer_is_done (transfer))
367     gsk_url_transfer_notify_done (transfer, GSK_URL_TRANSFER_ERROR_BAD_NAME);
368 }
369 
370 static void
set_name_lookup_NULL_and_unref(gpointer data)371 set_name_lookup_NULL_and_unref (gpointer data)
372 {
373   GskUrlTransferHttp *http = GSK_URL_TRANSFER_HTTP (data);
374   http->name_lookup = NULL;
375   g_object_unref (http);
376 }
377 
378 static void
start_name_resolution(GskUrlTransferHttp * http)379 start_name_resolution (GskUrlTransferHttp *http)
380 {
381   GskUrlTransfer *transfer = GSK_URL_TRANSFER (http);
382   GskUrl *url = transfer->redirect_url ? transfer->redirect_url : transfer->url;
383   g_return_if_fail (GSK_IS_URL (url));
384   g_return_if_fail (url->host != NULL);
385   gsk_name_resolver_lookup (GSK_NAME_RESOLVER_FAMILY_IPV4,
386                             url->host,
387                             handle_name_resolution_succeeded,
388                             handle_name_resolution_failed,
389                             g_object_ref (transfer),
390                             set_name_lookup_NULL_and_unref);
391 }
392 
393 static gboolean
gsk_url_transfer_http_start(GskUrlTransfer * transfer,GError ** error)394 gsk_url_transfer_http_start (GskUrlTransfer *transfer,
395                              GError        **error)
396 {
397   GskUrlTransferHttp *http = GSK_URL_TRANSFER_HTTP (transfer);
398   GskUrl *url = transfer->url;
399   if (url->host == NULL)
400     {
401       g_set_error (error,
402                    GSK_G_ERROR_DOMAIN,
403                    GSK_ERROR_BAD_FORMAT,
404                    "HTTP urls must have hosts");
405       return FALSE;
406     }
407 
408   if (transfer->address_hint)
409     handle_name_resolution_succeeded (transfer->address_hint, transfer);
410   else
411     start_name_resolution (http);
412   return TRUE;
413 }
414 
415 static void
cancel_internal(GskUrlTransferHttp * http)416 cancel_internal (GskUrlTransferHttp *http)
417 {
418   if (http->name_lookup)
419     {
420       /* we were doing name resolution */
421       gsk_name_resolver_task_cancel (http->name_lookup);
422     }
423   else if (http->raw_transport)
424     {
425       /* who knows where we are... just cancel now. */
426       GError *error = NULL;
427       gsk_io_shutdown (GSK_IO (http->raw_transport), &error);
428       if (error)
429         {
430           g_message ("GskUrlTransferHttp: error shutting down for cancellation: %s", error->message);
431           g_error_free (error);
432         }
433     }
434 }
435 
436 static void
gsk_url_transfer_http_cancel(GskUrlTransfer * transfer)437 gsk_url_transfer_http_cancel (GskUrlTransfer *transfer)
438 {
439   g_object_ref (transfer);
440   cancel_internal (GSK_URL_TRANSFER_HTTP (transfer));
441   if (!gsk_url_transfer_is_done (transfer))
442     gsk_url_transfer_notify_done (transfer, GSK_URL_TRANSFER_CANCELLED);
443   g_object_unref (transfer);
444 }
445 
446 static void
gsk_url_transfer_http_timed_out(GskUrlTransfer * transfer)447 gsk_url_transfer_http_timed_out (GskUrlTransfer *transfer)
448 {
449   cancel_internal (GSK_URL_TRANSFER_HTTP (transfer));
450   GSK_URL_TRANSFER_CLASS (gsk_url_transfer_http_parent_class)->timed_out (transfer);
451 }
452 
453 static char *
gsk_url_transfer_http_get_running_state(GskUrlTransfer * transfer)454 gsk_url_transfer_http_get_running_state (GskUrlTransfer *transfer)
455 {
456   GString *str = g_string_new ("RUNNING: ");
457   GskUrlTransferHttp *http = GSK_URL_TRANSFER_HTTP (transfer);
458   if (transfer->url)
459     {
460       char *url_str = gsk_url_to_string (transfer->url);
461       g_string_append (str, url_str);
462       g_free (url_str);
463     }
464   else
465     g_string_append (str, "(no url!?!)");
466 
467   if (http->name_lookup)
468     g_string_append (str, ": doing name lookup");
469   else if (http->raw_transport == NULL)
470     g_string_append (str, ": no raw transport");
471   else if (gsk_io_get_is_connecting (http->raw_transport))
472     g_string_append (str, ": connecting");
473   return g_string_free (str, FALSE);
474 }
475 
476 static void
gsk_url_transfer_http_finalize(GObject * object)477 gsk_url_transfer_http_finalize (GObject *object)
478 {
479   GskUrlTransferHttp *http = GSK_URL_TRANSFER_HTTP (object);
480   GskUrlTransferHttpModifierNode *mod;
481 
482   g_free (http->ssl_cert);
483   g_free (http->ssl_key);
484   g_free (http->ssl_password);
485 
486   g_assert (http->name_lookup == NULL);
487   if (http->raw_transport)
488     g_object_unref (http->raw_transport);
489 
490   for (mod = http->first_modifier; mod; )
491     {
492       GskUrlTransferHttpModifierNode *next = mod->next;
493       if (mod->destroy)
494         mod->destroy (mod->data);
495       g_free (mod);
496       mod = next;
497     }
498 
499   G_OBJECT_CLASS (gsk_url_transfer_http_parent_class)->finalize (object);
500 }
501 
502 static void
gsk_url_transfer_http_init(GskUrlTransferHttp * url_transfer_http)503 gsk_url_transfer_http_init (GskUrlTransferHttp *url_transfer_http)
504 {
505 }
506 
507 static void
gsk_url_transfer_http_class_init(GskUrlTransferHttpClass * class)508 gsk_url_transfer_http_class_init (GskUrlTransferHttpClass *class)
509 {
510   GObjectClass *object_class = G_OBJECT_CLASS (class);
511   GskUrlTransferClass *transfer_class = GSK_URL_TRANSFER_CLASS (class);
512   object_class->finalize = gsk_url_transfer_http_finalize;
513   transfer_class->start = gsk_url_transfer_http_start;
514   transfer_class->cancel = gsk_url_transfer_http_cancel;
515   transfer_class->get_running_state = gsk_url_transfer_http_get_running_state;
516   transfer_class->timed_out = gsk_url_transfer_http_timed_out;
517 }
518 
519 /**
520  * gsk_url_transfer_http_set_ssl_cert:
521  * @http: the transfer to affect.
522  * @cert_fname: the certificate filename.
523  *
524  * Set the SSL certificate file for this connection.
525  */
526 void
gsk_url_transfer_http_set_ssl_cert(GskUrlTransferHttp * http,const char * cert_fname)527 gsk_url_transfer_http_set_ssl_cert    (GskUrlTransferHttp *http,
528                                        const char         *cert_fname)
529 {
530   char *str = g_strdup (cert_fname);
531   g_free (http->ssl_cert);
532   http->ssl_cert = str;
533 }
534 
535 void
gsk_url_transfer_http_set_ssl_key(GskUrlTransferHttp * http,const char * key_fname)536 gsk_url_transfer_http_set_ssl_key     (GskUrlTransferHttp *http,
537                                        const char         *key_fname)
538 {
539   char *str = g_strdup (key_fname);
540   g_free (http->ssl_key);
541   http->ssl_key = str;
542 }
543 
544 void
gsk_url_transfer_http_set_ssl_password(GskUrlTransferHttp * http,const char * password)545 gsk_url_transfer_http_set_ssl_password(GskUrlTransferHttp *http,
546                                        const char         *password)
547 {
548   char *str = g_strdup (password);
549   g_free (http->ssl_password);
550   http->ssl_password = str;
551 }
552 
553 static void
transfer_modifier_set_user_agent(GskHttpRequest * request,gpointer mod_data)554 transfer_modifier_set_user_agent (GskHttpRequest *request,
555                                   gpointer        mod_data)
556 {
557   gsk_http_request_set_user_agent (request, (const char*)mod_data);
558 }
559 
560 /**
561  * gsk_url_transfer_http_set_user_agent:
562  * @http: the transfer to affect.
563  * @user_agent: the User-Agent: header's value for this transfer.
564  *
565  * Set the User-Agent to use for this HTTP transaction.
566  */
567 void
gsk_url_transfer_http_set_user_agent(GskUrlTransferHttp * http,const char * user_agent)568 gsk_url_transfer_http_set_user_agent  (GskUrlTransferHttp *http,
569                                        const char         *user_agent)
570 {
571   gsk_url_transfer_http_add_modifier (http,
572                                       transfer_modifier_set_user_agent,
573                                       g_strdup (user_agent),
574                                       g_free);
575 }
576 
577 /**
578  * gsk_url_transfer_http_set_proxy_address:
579  * @http: the transfer to affect.
580  * @proxy_address: the socket-address to
581  * really connect to.
582  *
583  * Set an HTTP proxy for this transfer.
584  */
585 void
gsk_url_transfer_http_set_proxy_address(GskUrlTransferHttp * http,GskSocketAddress * proxy_address)586 gsk_url_transfer_http_set_proxy_address  (GskUrlTransferHttp *http,
587                                           GskSocketAddress   *proxy_address)
588 {
589   gsk_url_transfer_set_address_hint (GSK_URL_TRANSFER (http), proxy_address);
590   http->is_proxy = TRUE;
591 }
592 
593 static void
transfer_modifier_set_misc_header(GskHttpRequest * request,gpointer mod_data)594 transfer_modifier_set_misc_header  (GskHttpRequest *request,
595                                     gpointer        mod_data)
596 {
597   char *key = mod_data;
598   char *value = strchr (key, 0) + 1;
599   gsk_http_header_add_misc (GSK_HTTP_HEADER (request), key, value);
600 }
601 
602 /**
603  * gsk_url_transfer_http_add_extra_header:
604  * @http: the transfer to affect.
605  * @key: a HTTP header name
606  * @value: the value of that HTTP header.
607  *
608  * Add an arbitrary header to the HTTP request.
609  */
610 void
gsk_url_transfer_http_add_extra_header(GskUrlTransferHttp * http,const char * key,const char * value)611 gsk_url_transfer_http_add_extra_header(GskUrlTransferHttp *http,
612                                        const char         *key,
613                                        const char         *value)
614 {
615   guint key_len, value_len;
616   char *kv;
617   g_return_if_fail (key != NULL && value != NULL);
618   key_len = strlen (key);
619   value_len = strlen (value);
620   kv = g_malloc (key_len + 1 + value_len + 1);
621   strcpy (kv, key);
622   strcpy (kv + key_len + 1, value);
623   gsk_url_transfer_http_add_modifier (http,
624                                       transfer_modifier_set_misc_header,
625                                       kv,
626                                       g_free);
627 }
628 
629 /**
630  * gsk_url_transfer_http_add_modifier:
631  * @http: the transfer to affect.
632  * @modifier: function to call to modify the HTTP request header.
633  * @data: data to pass to modifier.
634  * @destroy: called with data when the modifier is destroyed.
635  *
636  * Add a generic transformation to do to the HTTP request header.
637  */
638 void
gsk_url_transfer_http_add_modifier(GskUrlTransferHttp * http,GskUrlTransferHttpRequestModifierFunc modifier,gpointer data,GDestroyNotify destroy)639 gsk_url_transfer_http_add_modifier (GskUrlTransferHttp *http,
640                                     GskUrlTransferHttpRequestModifierFunc modifier,
641                                     gpointer data,
642                                     GDestroyNotify destroy)
643 {
644   GskUrlTransferHttpModifierNode *node = g_new (GskUrlTransferHttpModifierNode, 1);
645   node->modifier = modifier;
646   node->data = data;
647   node->destroy = destroy;
648   node->next = NULL;
649   if (http->first_modifier == NULL)
650     http->first_modifier = node;
651   else
652     http->last_modifier->next = node;
653   http->last_modifier = node;
654 }
655