2010-10-13 Sergio Villar Senin <svillar@igalia.com>
[WebKit-https.git] / WebCore / platform / network / soup / cache / soup-request-file.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-request-file.c: file: URI request object
4  *
5  * Copyright (C) 2009 Red Hat, Inc.
6  * Copyright (C) 2010 Igalia, S.L.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include "soup-request-file.h"
29 #include "soup-directory-input-stream.h"
30 #include "soup-requester.h"
31 #include <glib/gi18n.h>
32
33 G_DEFINE_TYPE (WebKitSoupRequestFile, webkit_soup_request_file, WEBKIT_TYPE_SOUP_REQUEST)
34
35 struct _WebKitSoupRequestFilePrivate {
36         GFile *gfile;
37
38         char *mime_type;
39         goffset size;
40 };
41
42 GFile *
43 webkit_soup_request_file_get_file (WebKitSoupRequestFile *file)
44 {
45         return g_object_ref (file->priv->gfile);
46 }
47
48 static void
49 webkit_soup_request_file_init (WebKitSoupRequestFile *file)
50 {
51         file->priv = G_TYPE_INSTANCE_GET_PRIVATE (file, WEBKIT_TYPE_SOUP_REQUEST_FILE, WebKitSoupRequestFilePrivate);
52
53         file->priv->size = -1;
54 }
55
56 static void
57 webkit_soup_request_file_finalize (GObject *object)
58 {
59         WebKitSoupRequestFile *file = WEBKIT_SOUP_REQUEST_FILE (object);
60
61         if (file->priv->gfile)
62                 g_object_unref (file->priv->gfile);
63         g_free (file->priv->mime_type);
64
65         G_OBJECT_CLASS (webkit_soup_request_file_parent_class)->finalize (object);
66 }
67
68 static gboolean
69 webkit_soup_request_file_check_uri (WebKitSoupRequest  *request,
70                                     SoupURI      *uri,
71                                     GError      **error)
72 {
73         /* "file:/foo" is not valid */
74         if (!uri->host)
75                 return FALSE;
76
77         /* but it must be "file:///..." or "file://localhost/..." */
78         if (uri->scheme == SOUP_URI_SCHEME_FILE &&
79             *uri->host &&
80             g_ascii_strcasecmp (uri->host, "localhost") != 0)
81                 return FALSE;
82
83         return TRUE;
84 }
85
86 static void
87 webkit_soup_request_file_ftp_main_loop_quit (GObject      *object,
88                                              GAsyncResult *result,
89                                              gpointer loop)
90 {
91         g_main_loop_quit (loop);
92 }
93
94 /* This is a somewhat hacky way to get FTP to almost work. The proper way to
95  * get FTP to _really_ work involves hacking GIO to have APIs to handle
96  * canoncial URLs.
97  */
98 static GFile *
99 webkit_soup_request_file_ensure_file_ftp (SoupURI       *uri,
100                                           GCancellable  *cancellable,
101                                           GError       **error)
102 {
103         SoupURI *host;
104         char *s;
105         GFile *file, *result;
106         GMount *mount;
107
108         host = soup_uri_copy_host (uri);
109         s = soup_uri_to_string (host, FALSE);
110         file = g_file_new_for_uri (s);
111         soup_uri_free (host);
112         g_free (s);
113
114         mount = g_file_find_enclosing_mount (file, cancellable, error);
115         if (mount == NULL && g_file_supports_thread_contexts (file)) {
116                 GMainContext *context = g_main_context_new ();
117                 GMainLoop *loop = g_main_loop_new (context, FALSE);
118
119                 g_clear_error (error);
120                 g_main_context_push_thread_default (context);
121                 g_file_mount_enclosing_volume (file,
122                                                G_MOUNT_MOUNT_NONE,
123                                                NULL, /* FIXME! */
124                                                cancellable,
125                                                webkit_soup_request_file_ftp_main_loop_quit,
126                                                loop);
127                 g_main_loop_run (loop);
128                 g_main_context_pop_thread_default (context);
129                 g_main_loop_unref (loop);
130                 g_main_context_unref (context);
131                 mount = g_file_find_enclosing_mount (file, cancellable, error);
132         }
133         if (mount == NULL)
134                 return NULL;
135         g_object_unref (file);
136
137         file = g_mount_get_default_location (mount);
138         g_object_unref (mount);
139
140         s = g_strdup (uri->path);
141         if (strchr (s, ';'))
142                 *strchr (s, ';') = 0;
143
144         result = g_file_resolve_relative_path (file, s);
145         g_free (s);
146         g_object_unref (file);
147
148         return result;
149 }
150
151 static gboolean
152 webkit_soup_request_file_ensure_file (WebKitSoupRequestFile  *file,
153                                       GCancellable     *cancellable,
154                                       GError          **error)
155 {
156         SoupURI *uri;
157
158         if (file->priv->gfile)
159                 return TRUE;
160
161         uri = webkit_soup_request_get_uri (WEBKIT_SOUP_REQUEST (file));
162         if (uri->scheme == SOUP_URI_SCHEME_FILE) {
163                 /* We cannot use soup_uri_decode as it incorrectly
164                  * returns NULL for incorrectly encoded URIs (that
165                  * could be valid filenames). This will be hopefully
166                  * shipped in libsoup 2.32.1 but we want to land this
167                  * first. TODO: replace uri_decoded_copy by
168                  * soup_uri_decode when the required libsoup version
169                  * is bumped out to 2.32.1
170                  */
171                 gchar *uri_path = soup_uri_get_path (uri);
172                 gchar *decoded_uri = webkit_soup_request_uri_decoded_copy (uri_path, strlen (uri_path));
173
174                 if (decoded_uri) {
175                         /* Do not use new_for_uri() as the decoded URI
176                          *  could not be a valid URI
177                          */
178                         file->priv->gfile = g_file_new_for_path (decoded_uri);
179                         g_free (decoded_uri);
180                 }
181
182                 return TRUE;
183         } else if (uri->scheme == SOUP_URI_SCHEME_FTP) {
184                 file->priv->gfile = webkit_soup_request_file_ensure_file_ftp (uri,
185                                                                               cancellable,
186                                                                               error);
187                 return file->priv->gfile != NULL;
188         }
189
190         g_set_error (error, WEBKIT_SOUP_ERROR, WEBKIT_SOUP_ERROR_UNSUPPORTED_URI_SCHEME,
191                      _ ("Unsupported URI scheme '%s'"), uri->scheme);
192         return FALSE;
193 }
194
195 static GInputStream *
196 webkit_soup_request_file_send (WebKitSoupRequest          *request,
197                                GCancellable         *cancellable,
198                                GError              **error)
199 {
200         WebKitSoupRequestFile *file = WEBKIT_SOUP_REQUEST_FILE (request);
201         GInputStream *stream;
202         GError *my_error = NULL;
203
204         if (!webkit_soup_request_file_ensure_file (file, cancellable, error))
205                 return NULL;
206
207         stream = G_INPUT_STREAM (g_file_read (file->priv->gfile,
208                                               cancellable, &my_error));
209         if (stream == NULL) {
210                 if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) {
211                         GFileEnumerator *enumerator;
212                         g_clear_error (&my_error);
213                         enumerator = g_file_enumerate_children (file->priv->gfile,
214                                                                 "*",
215                                                                 G_FILE_QUERY_INFO_NONE,
216                                                                 cancellable,
217                                                                 error);
218                         if (enumerator) {
219                                 stream = webkit_soup_directory_input_stream_new (enumerator,
220                                                                                  webkit_soup_request_get_uri (request));
221                                 g_object_unref (enumerator);
222                                 file->priv->mime_type = g_strdup ("text/html");
223                         }
224                 } else {
225                         g_propagate_error (error, my_error);
226                 }
227         } else {
228                 GFileInfo *info = g_file_query_info (file->priv->gfile,
229                                                      G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
230                                                      G_FILE_ATTRIBUTE_STANDARD_SIZE,
231                                                      0, cancellable, NULL);
232                 if (info) {
233                         const char *content_type;
234                         file->priv->size = g_file_info_get_size (info);
235                         content_type = g_file_info_get_content_type (info);
236
237                         if (content_type)
238                                 file->priv->mime_type = g_content_type_get_mime_type (content_type);
239                         g_object_unref (info);
240                 }
241         }
242
243         return stream;
244 }
245
246 static void
247 webkit_soup_request_file_send_async_thread (GSimpleAsyncResult *res,
248                                             GObject            *object,
249                                             GCancellable       *cancellable)
250 {
251         GInputStream *stream;
252         WebKitSoupRequest *request;
253         GError *error = NULL;
254
255         request = WEBKIT_SOUP_REQUEST (object);
256
257         stream = webkit_soup_request_file_send (request, cancellable, &error);
258
259         if (stream == NULL) {
260                 g_simple_async_result_set_from_error (res, error);
261                 g_error_free (error);
262         } else {
263                 g_simple_async_result_set_op_res_gpointer (res, stream, g_object_unref);
264         }
265 }
266
267 static void
268 webkit_soup_request_file_send_async (WebKitSoupRequest          *request,
269                                      GCancellable         *cancellable,
270                                      GAsyncReadyCallback callback,
271                                      gpointer user_data)
272 {
273         GSimpleAsyncResult *res;
274
275         res = g_simple_async_result_new (G_OBJECT (request), callback, user_data, webkit_soup_request_file_send_async);
276
277         g_simple_async_result_run_in_thread (res, webkit_soup_request_file_send_async_thread, G_PRIORITY_DEFAULT, cancellable);
278         g_object_unref (res);
279 }
280
281 static GInputStream *
282 webkit_soup_request_file_send_finish (WebKitSoupRequest          *request,
283                                       GAsyncResult         *result,
284                                       GError              **error)
285 {
286         GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
287
288         g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == webkit_soup_request_file_send_async);
289
290         if (g_simple_async_result_propagate_error (simple, error))
291                 return NULL;
292
293         return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
294 }
295
296 static goffset
297 webkit_soup_request_file_get_content_length (WebKitSoupRequest *request)
298 {
299         WebKitSoupRequestFile *file = WEBKIT_SOUP_REQUEST_FILE (request);
300
301         return file->priv->size;
302 }
303
304 static const char *
305 webkit_soup_request_file_get_content_type (WebKitSoupRequest *request)
306 {
307         WebKitSoupRequestFile *file = WEBKIT_SOUP_REQUEST_FILE (request);
308
309         if (!file->priv->mime_type)
310                 return "application/octet-stream";
311
312         return file->priv->mime_type;
313 }
314
315 static void
316 webkit_soup_request_file_class_init (WebKitSoupRequestFileClass *request_file_class)
317 {
318         GObjectClass *object_class = G_OBJECT_CLASS (request_file_class);
319         WebKitSoupRequestClass *request_class =
320                 WEBKIT_SOUP_REQUEST_CLASS (request_file_class);
321
322         g_type_class_add_private (request_file_class, sizeof (WebKitSoupRequestFilePrivate));
323
324         object_class->finalize = webkit_soup_request_file_finalize;
325
326         request_class->check_uri = webkit_soup_request_file_check_uri;
327         request_class->send = webkit_soup_request_file_send;
328         request_class->send_async = webkit_soup_request_file_send_async;
329         request_class->send_finish = webkit_soup_request_file_send_finish;
330         request_class->get_content_length = webkit_soup_request_file_get_content_length;
331         request_class->get_content_type = webkit_soup_request_file_get_content_type;
332 }