1 #include "gskurl.h"
2 #include "../http/gskhttpclient.h"
3 #include "../gskstreamclient.h"
4 #include "../gsknameresolver.h"
5 #include "../gskmacros.h"
6 #include "../gskdebug.h"
7 #include "../debug.h"
8 
9 /* XXX: thread-safety */
10 
11 typedef struct _DownloadInfo DownloadInfo;
12 struct _DownloadInfo
13 {
14   GskUrlScheme scheme;
15   GskUrlDownloadMethod download;
16   gpointer download_data;
17   DownloadInfo *next;
18 };
19 
20 struct _GskUrlDownload
21 {
22   GskUrl          *url;
23   GskUrlSuccess    success_func;
24   GskUrlFailure    failure_func;
25   gpointer         user_data;
26 };
27 
28 /* global list of download methods */
29 static DownloadInfo *first_dl_info = NULL;
30 
31 /* whether we've initialized the download-method system */
32 static gboolean has_initialized = FALSE;
33 static void initialize_url_download_system (void);
34 #define CHECK_HAS_INITIALIZED()				\
35   G_STMT_START{						\
36     if (!has_initialized)				\
37       initialize_url_download_system ();		\
38   }G_STMT_END
39 
40 static DownloadInfo *
find_download_info(GskUrlScheme scheme)41 find_download_info (GskUrlScheme scheme)
42 {
43   DownloadInfo *at;
44   for (at = first_dl_info; at != NULL; at = at->next)
45     if (at->scheme == scheme)
46       return at;
47   return NULL;
48 }
49 
50 
51 /**
52  * gsk_url_scheme_add_dl_method:
53  * @scheme: the URL scheme which this download method can handle.
54  * @download_method: function to call to initiate the URL download.
55  * @download_data: data to be passed to download_method.
56  *
57  * Register a new method for downloading a URL of a particular scheme.
58  *
59  * The callback @download_method will be run with each new requested URL.
60  * Each call to @download_method must cause it to
61  * eventually call gsk_url_download_success()
62  * or gsk_url_download_fail(); furthermore, it may
63  * call those functions before returning.
64  */
65 void
gsk_url_scheme_add_dl_method(GskUrlScheme scheme,GskUrlDownloadMethod download_method,gpointer download_data)66 gsk_url_scheme_add_dl_method (GskUrlScheme     scheme,
67 			      GskUrlDownloadMethod  download_method,
68 			      gpointer         download_data)
69 {
70   DownloadInfo *info;
71   g_return_if_fail (find_download_info (scheme) == NULL);
72   CHECK_HAS_INITIALIZED ();
73   info = g_new (DownloadInfo, 1);
74 
75   info->scheme = scheme;
76   info->download = download_method;
77   info->download_data = download_data;
78   info->next = first_dl_info;
79 
80   first_dl_info = info;
81 }
82 
83 /**
84  * gsk_url_download:
85  * @url: the URL to attempt to retrieve.
86  * @success_func: a function to call with a stream corresponding to the
87  * content of the page.
88  * @failure_func: a function to call with an error message if the
89  * url cannot be retrieved.
90  * @user_data: data to pass to @success_func or @failure_func.
91  *
92  * Initiate a URL download.
93  *
94  * A caller-supplied function will be invoked when
95  * the first content is available or a different
96  * function will be called if there is a problem.
97  * Only exactly one of these functions will be called.
98  *
99  * Either callback may be invoked before this function returns:
100  * the caller must deal with it.
101  */
102 void
gsk_url_download(GskUrl * url,GskUrlSuccess success_func,GskUrlFailure failure_func,gpointer user_data)103 gsk_url_download            (GskUrl          *url,
104 			     GskUrlSuccess    success_func,
105 			     GskUrlFailure    failure_func,
106 			     gpointer         user_data)
107 {
108   DownloadInfo *info;
109   CHECK_HAS_INITIALIZED ();
110   info = find_download_info (url->scheme);
111   if (info == NULL)
112     {
113       GError *error = g_error_new (GSK_G_ERROR_DOMAIN,
114 				   GSK_ERROR_PROTOCOL_NOT_AVAILABLE,
115 				   _("couldn't find url-download method for %s"),
116 				   url->scheme_name);
117       if (failure_func)
118 	failure_func (error, user_data);
119       g_error_free (error);
120     }
121   else
122     {
123       GskUrlDownload *download = g_new (GskUrlDownload, 1);
124       download->success_func = success_func;
125       download->url = g_object_ref (url);
126       download->failure_func = failure_func;
127       download->user_data = user_data;
128       (*info->download) (download, info->download_data);
129     }
130 }
131 
132 /* --- protected methods --- */
133 static inline void
gsk_url_download_destroy(GskUrlDownload * download)134 gsk_url_download_destroy (GskUrlDownload *download)
135 {
136   g_object_unref (download->url);
137   g_free (download);
138 }
139 
140 /**
141  * gsk_url_download_success:
142  * @download: the download object.
143  * @stream: the content stream to return to the caller
144  * who requested the download.
145  *
146  * Give a stream to the user which requested it
147  * via gsk_url_download().
148  *
149  * This function should only be used for implementing
150  * handlers for new URL schemes.
151  */
152 void
gsk_url_download_success(GskUrlDownload * download,GskStream * stream)153 gsk_url_download_success    (GskUrlDownload  *download,
154 			     GskStream       *stream)
155 {
156   if (download->success_func)
157     download->success_func (stream, download->user_data);
158   gsk_url_download_destroy (download);
159 }
160 
161 /**
162  * gsk_url_download_fail:
163  * @download: the download object.
164  * @error: an error to return.
165  *
166  * Give a failure notice to the user which requested a url download
167  * via gsk_url_download().
168  *
169  * This function should only be used for implementing
170  * handlers for new URL schemes.
171  */
172 void
gsk_url_download_fail(GskUrlDownload * download,GError * error)173 gsk_url_download_fail       (GskUrlDownload  *download,
174 			     GError          *error)
175 {
176   if (download->failure_func)
177     download->failure_func (error, download->user_data);
178   gsk_url_download_destroy (download);
179 }
180 
181 /**
182  * gsk_url_download_peek_url:
183  * @download: the download object to query.
184  *
185  * Get the URL that you are supposed to download.
186  *
187  * returns: the URL for downloading.
188  */
189 GskUrl *
gsk_url_download_peek_url(GskUrlDownload * download)190 gsk_url_download_peek_url (GskUrlDownload *download)
191 {
192   return download->url;
193 }
194 
195 /**
196  * gsk_url_download_redirect:
197  * @download: the download whose URL has been redirected.
198  * @new_url: the new URL to download.
199  *
200  * Indicate that retrieving one URL lead to a message
201  * saying that the content is at a different URL.
202  * The new URL should be retrieved, just like if it has
203  * been the original requestor.
204  *
205  * This function should only be used for implementing
206  * handlers for new URL schemes.
207  */
208 void
gsk_url_download_redirect(GskUrlDownload * download,GskUrl * new_url)209 gsk_url_download_redirect   (GskUrlDownload  *download,
210 			     GskUrl          *new_url)
211 {
212   DownloadInfo *info = find_download_info (new_url->scheme);
213   if (info == NULL)
214     {
215       GError *error = g_error_new (GSK_G_ERROR_DOMAIN,
216 				   GSK_ERROR_PROTOCOL_NOT_AVAILABLE,
217 				   _("couldn't find url-download method for %s"),
218 				   new_url->scheme_name);
219       gsk_url_download_fail (download, error);
220       g_error_free (error);
221     }
222   else
223     {
224       GskUrl *old_url = download->url;
225       download->url = g_object_ref (new_url);
226       g_object_unref (old_url);
227       (*info->download) (download, info->download_data);
228     }
229 }
230 
231 /* --- http implementation --- */
232 typedef struct _HttpDownloadInfo HttpDownloadInfo;
233 struct _HttpDownloadInfo
234 {
235   GskUrlDownload *download;
236   gboolean has_notified;
237 };
238 
239 static void
http_handle_response(GskHttpRequest * request,GskHttpResponse * response,GskStream * input,gpointer hook_data)240 http_handle_response  (GskHttpRequest  *request,
241 		       GskHttpResponse *response,
242 		       GskStream       *input,
243 		       gpointer         hook_data)
244 {
245   HttpDownloadInfo *info = hook_data;
246   GskUrlDownload *download = info->download;
247   switch (response->status_code)
248     {
249       /* errors i don't expect should ever happen, given the request
250        * we've issued */
251     /*case GSK_HTTP_STATUS_CONTINUE:*/
252     /*case GSK_HTTP_STATUS_SWITCHING_PROTOCOLS:*/
253     /*case GSK_HTTP_STATUS_NOT_MODIFIED:*/
254     /*case GSK_HTTP_STATUS_BAD_REQUEST:*/
255     /*case GSK_HTTP_STATUS_CREATED:*/
256     /*case GSK_HTTP_STATUS_ACCEPTED:*/
257     /*case GSK_HTTP_STATUS_NONAUTHORITATIVE_INFO:*/
258     /*case GSK_HTTP_STATUS_RESET_CONTENT:*/
259     /*case GSK_HTTP_STATUS_PARTIAL_CONTENT:*/
260     /*case GSK_HTTP_STATUS_CONFLICT:*/
261     /*case GSK_HTTP_STATUS_LENGTH_REQUIRED:*/
262     /*case GSK_HTTP_STATUS_BAD_RANGE:*/
263     /*case GSK_HTTP_STATUS_BAD_GATEWAY:*/
264     /*case GSK_HTTP_STATUS_GATEWAY_TIMEOUT:*/
265 
266       /* XXX: errors that i'm not sure about */
267     /*case GSK_HTTP_STATUS_NO_CONTENT:*/
268     /*case GSK_HTTP_STATUS_MULTIPLE_CHOICES:*/
269     /*case GSK_HTTP_STATUS_USE_PROXY:*/
270     /*case GSK_HTTP_STATUS_PROXY_AUTH_REQUIRED:*/
271 
272       /* XXX: if we get this, we should try again at HTTP/1.0... whatever */
273     /*case GSK_HTTP_STATUS_UNSUPPORTED_VERSION:*/
274 
275       /* errors that really mean the download failed,
276        * in a protologically valid way.
277        * (some of these indicate a seriously broken server)
278        */
279     /*case GSK_HTTP_STATUS_UNAUTHORIZED:*/
280     /*case GSK_HTTP_STATUS_PAYMENT_REQUIRED:*/
281     /*case GSK_HTTP_STATUS_FORBIDDEN:*/
282     /*case GSK_HTTP_STATUS_NOT_FOUND:*/
283     /*case GSK_HTTP_STATUS_METHOD_NOT_ALLOWED:*/
284     /*case GSK_HTTP_STATUS_NOT_ACCEPTABLE:*/
285     /*case GSK_HTTP_STATUS_REQUEST_TIMEOUT:*/
286     /*case GSK_HTTP_STATUS_GONE:*/
287     /*case GSK_HTTP_STATUS_PRECONDITION_FAILED:*/
288     /*case GSK_HTTP_STATUS_ENTITY_TOO_LARGE:*/
289     /*case GSK_HTTP_STATUS_URI_TOO_LARGE:*/
290     /*case GSK_HTTP_STATUS_EXPECTATION_FAILED:*/
291     /*case GSK_HTTP_STATUS_UNSUPPORTED_MEDIA:*/
292     /*case GSK_HTTP_STATUS_INTERNAL_SERVER_ERROR:*/
293     /*case GSK_HTTP_STATUS_NOT_IMPLEMENTED:*/
294     /*case GSK_HTTP_STATUS_SERVICE_UNAVAILABLE:*/
295 
296     case GSK_HTTP_STATUS_OK:
297       info->has_notified = TRUE;
298       gsk_url_download_success (download, input);
299       break;
300 
301       /* redirections */
302     case GSK_HTTP_STATUS_MOVED_PERMANENTLY:
303     case GSK_HTTP_STATUS_FOUND:
304     case GSK_HTTP_STATUS_SEE_OTHER:
305     case GSK_HTTP_STATUS_TEMPORARY_REDIRECT:
306       {
307 	GskUrl *new_url = NULL;
308 	if (response->location != NULL)
309 	  {
310 	    GskUrl *old_url = gsk_url_download_peek_url (download);
311 	    GError *error = NULL;
312 	    new_url = gsk_url_new_relative (old_url, response->location, &error);
313 	    if (new_url == NULL)
314 	      {
315 		gsk_url_download_fail (download, error);
316 		info->has_notified = TRUE;
317 		g_error_free (error);
318 		return;
319 	      }
320 	  }
321 	if (new_url != NULL)
322 	  {
323 	    gsk_url_download_redirect (download, new_url);
324 	    info->has_notified = TRUE;
325 	    g_object_unref (new_url);
326 	    return;
327 	  }
328       }
329 
330       /* Fall through to the error case:  all of the above error conditions
331        *     should be accompanied by a Location: header.
332        */
333 
334       /* default case indicates an error occurred */
335     default:
336       {
337 	GEnumClass *eclass = g_type_class_ref (GSK_TYPE_HTTP_STATUS);
338 	GEnumValue *evalue = g_enum_get_value (eclass, response->status_code);
339 	const char *error_code_name = evalue ? evalue->value_nick : "**unknown status**";
340 	GError *error = g_error_new (GSK_G_ERROR_DOMAIN,
341 				     GSK_ERROR_HTTP_NOT_FOUND,
342 				     _("error downloading via http: error %d [%s]"),
343 				     response->status_code, error_code_name);
344 	gsk_url_download_fail (download, error);
345 	info->has_notified = TRUE;
346 	g_error_free (error);
347 	g_type_class_unref (eclass);
348 	break;
349       }
350     }
351 }
352 
353 static void
destroy_http_download(gpointer hook_data)354 destroy_http_download  (gpointer         hook_data)
355 {
356   HttpDownloadInfo *info = hook_data;
357   if (!info->has_notified)
358     {
359       GError *error = g_error_new (GSK_G_ERROR_DOMAIN,
360 				   GSK_ERROR_HTTP_NOT_FOUND,
361 				   _("problem getting response from HTTP server"));
362       gsk_url_download_fail (info->download, error);
363       g_error_free (error);
364       info->has_notified = TRUE;
365     }
366   g_free (info);
367 }
368 
369 static void
http_name_lookup_success(GskSocketAddress * address,gpointer func_data)370 http_name_lookup_success (GskSocketAddress *address,
371 			  gpointer          func_data)
372 {
373   GskUrlDownload *download = func_data;
374   GskUrl *url = gsk_url_download_peek_url (download);
375   GError *error = NULL;
376   GskStream *istream;
377 
378   /* XXX: most likely this should be moved into the generic resolver code */
379   if (GSK_IS_SOCKET_ADDRESS_IPV4 (address))
380     {
381       GskSocketAddressIpv4 *ipv4 = GSK_SOCKET_ADDRESS_IPV4 (address);
382 
383       /* HACK: we need to get the port from the GskUrl
384        */
385       ipv4->ip_port = url->port;
386 
387       /* HACK: what if someone else is using 'address', an ostensibly
388        *       immutable class.
389        */
390 
391       if (ipv4->ip_port == 0)
392 	ipv4->ip_port = 80;
393     }
394 
395   istream = gsk_stream_new_connecting (address, &error);
396   if (istream == NULL)
397     gsk_url_download_fail (download, error);
398   else
399     {
400       GskHttpClient *client = gsk_http_client_new ();
401       GskStream *cstream;
402       GskHttpRequest *request;
403       HttpDownloadInfo *download_info = g_new (HttpDownloadInfo, 1);
404       char *full_path;
405 
406       if (GSK_IS_DEBUGGING (FD))
407         {
408           g_message ("debug-fd: open(%d): url->scheme,host=%s,%s",
409                      GSK_STREAM_FD (istream)->fd,
410                      url->scheme_name,
411                      url->host);
412         }
413 
414       download_info->has_notified = FALSE;
415       download_info->download = download;
416 
417       full_path = gsk_url_get_relative_path (url);
418       request = gsk_http_request_new (GSK_HTTP_VERB_GET, full_path);
419       g_free (full_path);
420 
421       g_object_set (request, "host", url->host, NULL);
422       gsk_http_client_request (client,
423 			       request,
424 			       NULL,
425 			       http_handle_response,
426 			       download_info,
427 			       destroy_http_download);
428       cstream = GSK_STREAM (client);
429       gsk_stream_attach (istream, cstream, NULL);
430       gsk_stream_attach (cstream, istream, NULL);
431       g_object_unref (istream);
432       gsk_http_client_shutdown_when_done (client);
433       g_object_unref (cstream);
434     }
435 }
436 
437 static void
http_name_lookup_failure(GError * error,gpointer func_data)438 http_name_lookup_failure (GError           *error,
439 			  gpointer          func_data)
440 {
441   GskUrlDownload *download = func_data;
442   gsk_url_download_fail (download, error);
443 }
444 
445 static void
download_http(GskUrlDownload * download,gpointer data)446 download_http (GskUrlDownload  *download,
447 	       gpointer         data)
448 {
449   GskUrl *url = gsk_url_download_peek_url (download);
450   GskNameResolverTask *task = gsk_name_resolve (GSK_NAME_RESOLVER_FAMILY_IPV4,
451 		                                url->host,
452 		                                http_name_lookup_success,
453 		                                http_name_lookup_failure,
454 		                                download,
455 		                                NULL);
456   gsk_name_resolver_task_unref (task);
457 }
458 
459 /* --- initialization --- */
460 static void
initialize_url_download_system(void)461 initialize_url_download_system (void)
462 {
463   has_initialized = TRUE;
464   gsk_url_scheme_add_dl_method (GSK_URL_SCHEME_HTTP, download_http, NULL);
465 }
466