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