0d84388e0a2eeb5e7e24d4b7a4e147e51a28172f
[WebKit-https.git] / WebCore / platform / network / soup / ResourceHandleSoup.cpp
1 /*
2  * Copyright (C) 2008 Alp Toker <alp@atoker.com>
3  * Copyright (C) 2008 Xan Lopez <xan@gnome.org>
4  * Copyright (C) 2008, 2010 Collabora Ltd.
5  * Copyright (C) 2009 Holger Hans Peter Freyther
6  * Copyright (C) 2009 Gustavo Noronha Silva <gns@gnome.org>
7  * Copyright (C) 2009 Christian Dywan <christian@imendio.com>
8  * Copyright (C) 2009 Igalia S.L.
9  * Copyright (C) 2009 John Kjellberg <john.kjellberg@power.alstom.com>
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Library General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Library General Public License for more details.
20  *
21  * You should have received a copy of the GNU Library General Public License
22  * along with this library; see the file COPYING.LIB.  If not, write to
23  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  * Boston, MA 02110-1301, USA.
25  */
26
27 #include "config.h"
28 #include "ResourceHandle.h"
29
30 #include "Base64.h"
31 #include "CString.h"
32 #include "ChromeClient.h"
33 #include "CookieJarSoup.h"
34 #include "CachedResourceLoader.h"
35 #include "FileSystem.h"
36 #include "Frame.h"
37 #include "GOwnPtrSoup.h"
38 #include "HTTPParsers.h"
39 #include "Logging.h"
40 #include "MIMETypeRegistry.h"
41 #include "NotImplemented.h"
42 #include "Page.h"
43 #include "ResourceError.h"
44 #include "ResourceHandleClient.h"
45 #include "ResourceHandleInternal.h"
46 #include "ResourceResponse.h"
47 #include "SharedBuffer.h"
48 #include "TextEncoding.h"
49
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <gio/gio.h>
53 #include <glib.h>
54 #include <libsoup/soup.h>
55 #include <sys/stat.h>
56 #include <sys/types.h>
57 #include <unistd.h>
58
59 namespace WebCore {
60
61 class WebCoreSynchronousLoader : public ResourceHandleClient, public Noncopyable {
62 public:
63     WebCoreSynchronousLoader(ResourceError&, ResourceResponse &, Vector<char>&);
64     ~WebCoreSynchronousLoader();
65
66     virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
67     virtual void didReceiveData(ResourceHandle*, const char*, int, int lengthReceived);
68     virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
69     virtual void didFail(ResourceHandle*, const ResourceError&);
70
71     void run();
72
73 private:
74     ResourceError& m_error;
75     ResourceResponse& m_response;
76     Vector<char>& m_data;
77     bool m_finished;
78     GMainLoop* m_mainLoop;
79 };
80
81 WebCoreSynchronousLoader::WebCoreSynchronousLoader(ResourceError& error, ResourceResponse& response, Vector<char>& data)
82     : m_error(error)
83     , m_response(response)
84     , m_data(data)
85     , m_finished(false)
86 {
87     m_mainLoop = g_main_loop_new(0, false);
88 }
89
90 WebCoreSynchronousLoader::~WebCoreSynchronousLoader()
91 {
92     g_main_loop_unref(m_mainLoop);
93 }
94
95 void WebCoreSynchronousLoader::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
96 {
97     m_response = response;
98 }
99
100 void WebCoreSynchronousLoader::didReceiveData(ResourceHandle*, const char* data, int length, int)
101 {
102     m_data.append(data, length);
103 }
104
105 void WebCoreSynchronousLoader::didFinishLoading(ResourceHandle*, double)
106 {
107     g_main_loop_quit(m_mainLoop);
108     m_finished = true;
109 }
110
111 void WebCoreSynchronousLoader::didFail(ResourceHandle* handle, const ResourceError& error)
112 {
113     m_error = error;
114     didFinishLoading(handle, 0);
115 }
116
117 void WebCoreSynchronousLoader::run()
118 {
119     if (!m_finished)
120         g_main_loop_run(m_mainLoop);
121 }
122
123 static void cleanupGioOperation(ResourceHandle* handle, bool isDestroying);
124 static bool startData(ResourceHandle* handle, String urlString);
125 static bool startGio(ResourceHandle* handle, KURL url);
126
127 ResourceHandleInternal::~ResourceHandleInternal()
128 {
129     if (m_msg) {
130         g_object_unref(m_msg);
131         m_msg = 0;
132     }
133
134     if (m_idleHandler) {
135         g_source_remove(m_idleHandler);
136         m_idleHandler = 0;
137     }
138 }
139
140 ResourceHandle::~ResourceHandle()
141 {
142     if (d->m_msg)
143         g_signal_handlers_disconnect_matched(d->m_msg, G_SIGNAL_MATCH_DATA,
144                                              0, 0, 0, 0, this);
145
146     cleanupGioOperation(this, true);
147 }
148
149 void ResourceHandle::prepareForURL(const KURL &url)
150 {
151 #ifdef HAVE_LIBSOUP_2_29_90
152     GOwnPtr<SoupURI> soupURI(soup_uri_new(url.prettyURL().utf8().data()));
153     if (!soupURI)
154         return;
155     soup_session_prepare_for_uri(ResourceHandle::defaultSession(), soupURI.get());
156 #endif
157 }
158
159 // All other kinds of redirections, except for the *304* status code
160 // (SOUP_STATUS_NOT_MODIFIED) which needs to be fed into WebCore, will be
161 // handled by soup directly.
162 static gboolean statusWillBeHandledBySoup(guint statusCode)
163 {
164     if (SOUP_STATUS_IS_TRANSPORT_ERROR(statusCode)
165         || (SOUP_STATUS_IS_REDIRECTION(statusCode) && (statusCode != SOUP_STATUS_NOT_MODIFIED))
166         || (statusCode == SOUP_STATUS_UNAUTHORIZED))
167         return true;
168
169     return false;
170 }
171
172 static void fillResponseFromMessage(SoupMessage* msg, ResourceResponse* response)
173 {
174     response->updateFromSoupMessage(msg);
175 }
176
177 // Called each time the message is going to be sent again except the first time.
178 // It's used mostly to let webkit know about redirects.
179 static void restartedCallback(SoupMessage* msg, gpointer data)
180 {
181     ResourceHandle* handle = static_cast<ResourceHandle*>(data);
182     if (!handle)
183         return;
184     ResourceHandleInternal* d = handle->getInternal();
185     if (d->m_cancelled)
186         return;
187
188     char* uri = soup_uri_to_string(soup_message_get_uri(msg), false);
189     String location = String(uri);
190     g_free(uri);
191     KURL newURL = KURL(handle->firstRequest().url(), location);
192
193     ResourceRequest request = handle->firstRequest();
194     ResourceResponse response;
195     request.setURL(newURL);
196     request.setHTTPMethod(msg->method);
197     fillResponseFromMessage(msg, &response);
198
199     // Should not set Referer after a redirect from a secure resource to non-secure one.
200     if (!request.url().protocolIs("https") && protocolIs(request.httpReferrer(), "https")) {
201         request.clearHTTPReferrer();
202         soup_message_headers_remove(msg->request_headers, "Referer");
203     }
204
205     if (d->client())
206         d->client()->willSendRequest(handle, request, response);
207
208     if (d->m_cancelled)
209         return;
210
211 #ifdef HAVE_LIBSOUP_2_29_90
212     // Update the first party in case the base URL changed with the redirect
213     String firstPartyString = request.firstPartyForCookies().string();
214     if (!firstPartyString.isEmpty()) {
215         GOwnPtr<SoupURI> firstParty(soup_uri_new(firstPartyString.utf8().data()));
216         soup_message_set_first_party(d->m_msg, firstParty.get());
217     }
218 #endif
219 }
220
221 static void gotHeadersCallback(SoupMessage* msg, gpointer data)
222 {
223     // For 401, we will accumulate the resource body, and only use it
224     // in case authentication with the soup feature doesn't happen
225     if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
226         soup_message_body_set_accumulate(msg->response_body, TRUE);
227         return;
228     }
229
230     // For all the other responses, we handle each chunk ourselves,
231     // and we don't need msg->response_body to contain all of the data
232     // we got, when we finish downloading.
233     soup_message_body_set_accumulate(msg->response_body, FALSE);
234
235     RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
236
237     // The content-sniffed callback will handle the response if WebCore
238     // require us to sniff.
239     if (!handle || statusWillBeHandledBySoup(msg->status_code) || handle->shouldContentSniff())
240         return;
241
242     ResourceHandleInternal* d = handle->getInternal();
243     if (d->m_cancelled)
244         return;
245     ResourceHandleClient* client = handle->client();
246     if (!client)
247         return;
248
249     fillResponseFromMessage(msg, &d->m_response);
250     client->didReceiveResponse(handle.get(), d->m_response);
251 }
252
253 // This callback will not be called if the content sniffer is disabled in startHttp.
254 static void contentSniffedCallback(SoupMessage* msg, const char* sniffedType, GHashTable *params, gpointer data)
255 {
256     if (sniffedType) {
257         const char* officialType = soup_message_headers_get_one(msg->response_headers, "Content-Type");
258
259         if (!officialType || strcmp(officialType, sniffedType))
260             soup_message_headers_set_content_type(msg->response_headers, sniffedType, params);
261     }
262
263     if (statusWillBeHandledBySoup(msg->status_code))
264         return;
265
266     RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
267     if (!handle)
268         return;
269     ResourceHandleInternal* d = handle->getInternal();
270     if (d->m_cancelled)
271         return;
272     ResourceHandleClient* client = handle->client();
273     if (!client)
274         return;
275
276     fillResponseFromMessage(msg, &d->m_response);
277     client->didReceiveResponse(handle.get(), d->m_response);
278 }
279
280 static void gotChunkCallback(SoupMessage* msg, SoupBuffer* chunk, gpointer data)
281 {
282     if (statusWillBeHandledBySoup(msg->status_code))
283         return;
284
285     RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
286     if (!handle)
287         return;
288     ResourceHandleInternal* d = handle->getInternal();
289     if (d->m_cancelled)
290         return;
291     ResourceHandleClient* client = handle->client();
292     if (!client)
293         return;
294
295     client->didReceiveData(handle.get(), chunk->data, chunk->length, false);
296 }
297
298 // Called at the end of the message, with all the necessary about the last informations.
299 // Doesn't get called for redirects.
300 static void finishedCallback(SoupSession *session, SoupMessage* msg, gpointer data)
301 {
302     RefPtr<ResourceHandle> handle = adoptRef(static_cast<ResourceHandle*>(data));
303     // TODO: maybe we should run this code even if there's no client?
304     if (!handle)
305         return;
306
307     ResourceHandleInternal* d = handle->getInternal();
308
309     ResourceHandleClient* client = handle->client();
310     if (!client)
311         return;
312
313     if (d->m_cancelled)
314         return;
315
316     if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code)) {
317         char* uri = soup_uri_to_string(soup_message_get_uri(msg), false);
318         ResourceError error(g_quark_to_string(SOUP_HTTP_ERROR),
319                             msg->status_code,
320                             uri,
321                             String::fromUTF8(msg->reason_phrase));
322         g_free(uri);
323         client->didFail(handle.get(), error);
324         return;
325     }
326
327     if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
328         fillResponseFromMessage(msg, &d->m_response);
329         client->didReceiveResponse(handle.get(), d->m_response);
330
331         // WebCore might have cancelled the job in the while
332         if (d->m_cancelled)
333             return;
334
335         if (msg->response_body->data)
336             client->didReceiveData(handle.get(), msg->response_body->data, msg->response_body->length, true);
337     }
338
339     client->didFinishLoading(handle.get(), 0);
340 }
341
342 // parseDataUrl() is taken from the CURL http backend.
343 static gboolean parseDataUrl(gpointer callbackData)
344 {
345     ResourceHandle* handle = static_cast<ResourceHandle*>(callbackData);
346     ResourceHandleClient* client = handle->client();
347     ResourceHandleInternal* d = handle->getInternal();
348     if (d->m_cancelled)
349         return false;
350
351     d->m_idleHandler = 0;
352
353     ASSERT(client);
354     if (!client)
355         return false;
356
357     String url = handle->firstRequest().url().string();
358     ASSERT(url.startsWith("data:", false));
359
360     int index = url.find(',');
361     if (index == -1) {
362         client->cannotShowURL(handle);
363         return false;
364     }
365
366     String mediaType = url.substring(5, index - 5);
367     String data = url.substring(index + 1);
368
369     bool isBase64 = mediaType.endsWith(";base64", false);
370     if (isBase64)
371         mediaType = mediaType.left(mediaType.length() - 7);
372
373     if (mediaType.isEmpty())
374         mediaType = "text/plain;charset=US-ASCII";
375
376     String mimeType = extractMIMETypeFromMediaType(mediaType);
377     String charset = extractCharsetFromMediaType(mediaType);
378
379     ResourceResponse response;
380     response.setURL(handle->firstRequest().url());
381     response.setMimeType(mimeType);
382
383     if (isBase64) {
384         data = decodeURLEscapeSequences(data);
385         response.setTextEncodingName(charset);
386         client->didReceiveResponse(handle, response);
387
388         // The load may be cancelled, and the client may be destroyed
389         // by any of the client reporting calls, so we check, and bail
390         // out in either of those cases.
391         if (d->m_cancelled || !handle->client())
392             return false;
393
394         // Use the GLib Base64, since WebCore's decoder isn't
395         // general-purpose and fails on Acid3 test 97 (whitespace).
396         size_t outLength = 0;
397         char* outData = 0;
398         outData = reinterpret_cast<char*>(g_base64_decode(data.utf8().data(), &outLength));
399         if (outData && outLength > 0)
400             client->didReceiveData(handle, outData, outLength, 0);
401         g_free(outData);
402     } else {
403         // We have to convert to UTF-16 early due to limitations in KURL
404         data = decodeURLEscapeSequences(data, TextEncoding(charset));
405         response.setTextEncodingName("UTF-16");
406         client->didReceiveResponse(handle, response);
407
408         if (d->m_cancelled || !handle->client())
409             return false;
410
411         if (data.length() > 0)
412             client->didReceiveData(handle, reinterpret_cast<const char*>(data.characters()), data.length() * sizeof(UChar), 0);
413     }
414
415     if (d->m_cancelled || !handle->client())
416         return false;
417
418     client->didFinishLoading(handle, 0);
419
420     return false;
421 }
422
423 static bool startData(ResourceHandle* handle, String urlString)
424 {
425     ASSERT(handle);
426
427     ResourceHandleInternal* d = handle->getInternal();
428
429     // If parseDataUrl is called synchronously the job is not yet effectively started
430     // and webkit won't never know that the data has been parsed even didFinishLoading is called.
431     d->m_idleHandler = g_timeout_add(0, parseDataUrl, handle);
432     return true;
433 }
434
435 static SoupSession* createSoupSession()
436 {
437     return soup_session_async_new();
438 }
439
440 // Values taken from http://stevesouders.com/ua/index.php following
441 // the rule "Do What Every Other Modern Browser Is Doing". They seem
442 // to significantly improve page loading time compared to soup's
443 // default values.
444 #define MAX_CONNECTIONS          60
445 #define MAX_CONNECTIONS_PER_HOST 6
446
447 static void ensureSessionIsInitialized(SoupSession* session)
448 {
449     if (g_object_get_data(G_OBJECT(session), "webkit-init"))
450         return;
451
452     SoupCookieJar* jar = reinterpret_cast<SoupCookieJar*>(soup_session_get_feature(session, SOUP_TYPE_COOKIE_JAR));
453     if (!jar)
454         soup_session_add_feature(session, SOUP_SESSION_FEATURE(defaultCookieJar()));
455     else
456         setDefaultCookieJar(jar);
457
458     if (!soup_session_get_feature(session, SOUP_TYPE_LOGGER) && LogNetwork.state == WTFLogChannelOn) {
459         SoupLogger* logger = soup_logger_new(static_cast<SoupLoggerLogLevel>(SOUP_LOGGER_LOG_BODY), -1);
460         soup_logger_attach(logger, session);
461         g_object_unref(logger);
462     }
463
464     g_object_set(session,
465                  SOUP_SESSION_MAX_CONNS, MAX_CONNECTIONS,
466                  SOUP_SESSION_MAX_CONNS_PER_HOST, MAX_CONNECTIONS_PER_HOST,
467                  NULL);
468
469     g_object_set_data(G_OBJECT(session), "webkit-init", reinterpret_cast<void*>(0xdeadbeef));
470 }
471
472 static bool startHttp(ResourceHandle* handle)
473 {
474     ASSERT(handle);
475
476     SoupSession* session = handle->defaultSession();
477     ensureSessionIsInitialized(session);
478
479     ResourceHandleInternal* d = handle->getInternal();
480
481     ResourceRequest request(handle->firstRequest());
482     KURL url(request.url());
483     url.removeFragmentIdentifier();
484     request.setURL(url);
485
486     d->m_msg = request.toSoupMessage();
487     if (!d->m_msg)
488         return false;
489
490     if (!handle->shouldContentSniff())
491         soup_message_disable_feature(d->m_msg, SOUP_TYPE_CONTENT_SNIFFER);
492
493     g_signal_connect(d->m_msg, "restarted", G_CALLBACK(restartedCallback), handle);
494     g_signal_connect(d->m_msg, "got-headers", G_CALLBACK(gotHeadersCallback), handle);
495     g_signal_connect(d->m_msg, "content-sniffed", G_CALLBACK(contentSniffedCallback), handle);
496     g_signal_connect(d->m_msg, "got-chunk", G_CALLBACK(gotChunkCallback), handle);
497
498 #ifdef HAVE_LIBSOUP_2_29_90
499     String firstPartyString = request.firstPartyForCookies().string();
500     if (!firstPartyString.isEmpty()) {
501         GOwnPtr<SoupURI> firstParty(soup_uri_new(firstPartyString.utf8().data()));
502         soup_message_set_first_party(d->m_msg, firstParty.get());
503     }
504 #endif
505     g_object_set_data(G_OBJECT(d->m_msg), "resourceHandle", reinterpret_cast<void*>(handle));
506
507     FormData* httpBody = d->m_firstRequest.httpBody();
508     if (httpBody && !httpBody->isEmpty()) {
509         size_t numElements = httpBody->elements().size();
510
511         // handle the most common case (i.e. no file upload)
512         if (numElements < 2) {
513             Vector<char> body;
514             httpBody->flatten(body);
515             soup_message_set_request(d->m_msg, d->m_firstRequest.httpContentType().utf8().data(),
516                                      SOUP_MEMORY_COPY, body.data(), body.size());
517         } else {
518             /*
519              * we have more than one element to upload, and some may
520              * be (big) files, which we will want to mmap instead of
521              * copying into memory; TODO: support upload of non-local
522              * (think sftp://) files by using GIO?
523              */
524             soup_message_body_set_accumulate(d->m_msg->request_body, FALSE);
525             for (size_t i = 0; i < numElements; i++) {
526                 const FormDataElement& element = httpBody->elements()[i];
527
528                 if (element.m_type == FormDataElement::data)
529                     soup_message_body_append(d->m_msg->request_body, SOUP_MEMORY_TEMPORARY, element.m_data.data(), element.m_data.size());
530                 else {
531                     /*
532                      * mapping for uploaded files code inspired by technique used in
533                      * libsoup's simple-httpd test
534                      */
535                     GError* error = 0;
536                     CString fileName = fileSystemRepresentation(element.m_filename);
537                     GMappedFile* fileMapping = g_mapped_file_new(fileName.data(), false, &error);
538
539                     if (error) {
540                         g_error_free(error);
541                         g_signal_handlers_disconnect_matched(d->m_msg, G_SIGNAL_MATCH_DATA,
542                                                              0, 0, 0, 0, handle);
543                         g_object_unref(d->m_msg);
544                         d->m_msg = 0;
545
546                         return false;
547                     }
548
549                     SoupBuffer* soupBuffer = soup_buffer_new_with_owner(g_mapped_file_get_contents(fileMapping),
550                                                                         g_mapped_file_get_length(fileMapping),
551                                                                         fileMapping,
552 #if GLIB_CHECK_VERSION(2, 21, 3)
553                                                                         reinterpret_cast<GDestroyNotify>(g_mapped_file_unref));
554 #else
555                                                                         reinterpret_cast<GDestroyNotify>(g_mapped_file_free));
556 #endif
557                     soup_message_body_append_buffer(d->m_msg->request_body, soupBuffer);
558                     soup_buffer_free(soupBuffer);
559                 }
560             }
561         }
562     }
563
564     // balanced by a deref() in finishedCallback, which should always run
565     handle->ref();
566
567     // Make sure we have an Accept header for subresources; some sites
568     // want this to serve some of their subresources
569     if (!soup_message_headers_get_one(d->m_msg->request_headers, "Accept"))
570         soup_message_headers_append(d->m_msg->request_headers, "Accept", "*/*");
571
572     // Balanced in ResourceHandleInternal's destructor; we need to
573     // keep our own ref, because after queueing the message, the
574     // session owns the initial reference.
575     g_object_ref(d->m_msg);
576     soup_session_queue_message(session, d->m_msg, finishedCallback, handle);
577
578     return true;
579 }
580
581 bool ResourceHandle::start(NetworkingContext* context)
582 {
583     ASSERT(!d->m_msg);
584
585     // The frame could be null if the ResourceHandle is not associated to any
586     // Frame, e.g. if we are downloading a file.
587     // If the frame is not null but the page is null this must be an attempted
588     // load from an unload handler, so let's just block it.
589     // If both the frame and the page are not null the context is valid.
590     if (context && !context->isValid())
591         return false;
592
593     KURL url = firstRequest().url();
594     String urlString = url.string();
595     String protocol = url.protocol();
596
597     // Used to set the authentication dialog toplevel; may be NULL
598     d->m_context = context;
599
600     if (equalIgnoringCase(protocol, "data"))
601         return startData(this, urlString);
602
603     if (equalIgnoringCase(protocol, "http") || equalIgnoringCase(protocol, "https")) {
604         if (startHttp(this))
605             return true;
606     }
607
608     if (equalIgnoringCase(protocol, "file") || equalIgnoringCase(protocol, "ftp") || equalIgnoringCase(protocol, "ftps")) {
609         // FIXME: should we be doing any other protocols here?
610         if (startGio(this, url))
611             return true;
612     }
613
614     // Error must not be reported immediately
615     this->scheduleFailure(InvalidURLFailure);
616
617     return true;
618 }
619
620 void ResourceHandle::cancel()
621 {
622     d->m_cancelled = true;
623     if (d->m_msg)
624         soup_session_cancel_message(defaultSession(), d->m_msg, SOUP_STATUS_CANCELLED);
625     else if (d->m_cancellable)
626         g_cancellable_cancel(d->m_cancellable);
627 }
628
629 PassRefPtr<SharedBuffer> ResourceHandle::bufferedData()
630 {
631     ASSERT_NOT_REACHED();
632     return 0;
633 }
634
635 bool ResourceHandle::supportsBufferedData()
636 {
637     return false;
638 }
639
640 void ResourceHandle::platformSetDefersLoading(bool)
641 {
642     notImplemented();
643 }
644
645 bool ResourceHandle::loadsBlocked()
646 {
647     return false;
648 }
649
650 bool ResourceHandle::willLoadFromCache(ResourceRequest&, Frame*)
651 {
652     // Not having this function means that we'll ask the user about re-posting a form
653     // even when we go back to a page that's still in the cache.
654     notImplemented();
655     return false;
656 }
657
658 void ResourceHandle::loadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentials /*storedCredentials*/, ResourceError& error, ResourceResponse& response, Vector<char>& data)
659 {
660     WebCoreSynchronousLoader syncLoader(error, response, data);
661     // FIXME: we should use the ResourceHandle::create method here,
662     // but it makes us timeout in a couple of tests. See
663     // https://bugs.webkit.org/show_bug.cgi?id=41823
664     RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(request, &syncLoader, true, false));
665     handle->start(context);
666
667     syncLoader.run();
668 }
669
670 // GIO-based loader
671
672 static void cleanupGioOperation(ResourceHandle* handle, bool isDestroying = false)
673 {
674     ResourceHandleInternal* d = handle->getInternal();
675
676     if (d->m_gfile) {
677         g_object_set_data(G_OBJECT(d->m_gfile), "webkit-resource", 0);
678         g_object_unref(d->m_gfile);
679         d->m_gfile = 0;
680     }
681
682     if (d->m_cancellable) {
683         g_object_unref(d->m_cancellable);
684         d->m_cancellable = 0;
685     }
686
687     if (d->m_inputStream) {
688         g_object_set_data(G_OBJECT(d->m_inputStream), "webkit-resource", 0);
689         g_object_unref(d->m_inputStream);
690         d->m_inputStream = 0;
691     }
692
693     if (d->m_buffer) {
694         g_free(d->m_buffer);
695         d->m_buffer = 0;
696     }
697
698     if (!isDestroying)
699         handle->deref();
700 }
701
702 static void closeCallback(GObject* source, GAsyncResult* res, gpointer)
703 {
704     RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(source, "webkit-resource"));
705     if (!handle)
706         return;
707
708     ResourceHandleInternal* d = handle->getInternal();
709     ResourceHandleClient* client = handle->client();
710
711     g_input_stream_close_finish(d->m_inputStream, res, 0);
712     cleanupGioOperation(handle.get());
713
714     // The load may have been cancelled, the client may have been
715     // destroyed already. In such cases calling didFinishLoading is a
716     // bad idea.
717     if (d->m_cancelled || !client)
718         return;
719
720     client->didFinishLoading(handle.get(), 0);
721 }
722
723 static void readCallback(GObject* source, GAsyncResult* res, gpointer)
724 {
725     RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(source, "webkit-resource"));
726     if (!handle)
727         return;
728
729     ResourceHandleInternal* d = handle->getInternal();
730     ResourceHandleClient* client = handle->client();
731
732     if (d->m_cancelled || !client) {
733         cleanupGioOperation(handle.get());
734         return;
735     }
736
737     GError* error = 0;
738
739     gssize bytesRead = g_input_stream_read_finish(d->m_inputStream, res, &error);
740     if (error) {
741         char* uri = g_file_get_uri(d->m_gfile);
742         ResourceError resourceError(g_quark_to_string(G_IO_ERROR),
743                                     error->code,
744                                     uri,
745                                     error ? String::fromUTF8(error->message) : String());
746         g_free(uri);
747         g_error_free(error);
748         cleanupGioOperation(handle.get());
749         client->didFail(handle.get(), resourceError);
750         return;
751     }
752
753     if (!bytesRead) {
754         g_input_stream_close_async(d->m_inputStream, G_PRIORITY_DEFAULT,
755                                    0, closeCallback, 0);
756         return;
757     }
758
759     d->m_total += bytesRead;
760     client->didReceiveData(handle.get(), d->m_buffer, bytesRead, d->m_total);
761
762     // didReceiveData may cancel the load, which may release the last reference.
763     if (d->m_cancelled) {
764         cleanupGioOperation(handle.get());
765         return;
766     }
767
768     g_input_stream_read_async(d->m_inputStream, d->m_buffer, d->m_bufferSize,
769                               G_PRIORITY_DEFAULT, d->m_cancellable,
770                               readCallback, 0);
771 }
772
773 static void openCallback(GObject* source, GAsyncResult* res, gpointer)
774 {
775     RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(source, "webkit-resource"));
776     if (!handle)
777         return;
778
779     ResourceHandleInternal* d = handle->getInternal();
780     ResourceHandleClient* client = handle->client();
781
782     if (d->m_cancelled || !client) {
783         cleanupGioOperation(handle.get());
784         return;
785     }
786
787     GError* error = 0;
788     GFileInputStream* in = g_file_read_finish(G_FILE(source), res, &error);
789     if (error) {
790         char* uri = g_file_get_uri(d->m_gfile);
791         ResourceError resourceError(g_quark_to_string(G_IO_ERROR),
792                                     error->code,
793                                     uri,
794                                     error ? String::fromUTF8(error->message) : String());
795         g_free(uri);
796         g_error_free(error);
797         cleanupGioOperation(handle.get());
798         client->didFail(handle.get(), resourceError);
799         return;
800     }
801
802     d->m_inputStream = G_INPUT_STREAM(in);
803     d->m_bufferSize = 8192;
804     d->m_buffer = static_cast<char*>(g_malloc(d->m_bufferSize));
805     d->m_total = 0;
806
807     g_object_set_data(G_OBJECT(d->m_inputStream), "webkit-resource", handle.get());
808     g_input_stream_read_async(d->m_inputStream, d->m_buffer, d->m_bufferSize,
809                               G_PRIORITY_DEFAULT, d->m_cancellable,
810                               readCallback, 0);
811 }
812
813 static void queryInfoCallback(GObject* source, GAsyncResult* res, gpointer)
814 {
815     RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(source, "webkit-resource"));
816     if (!handle)
817         return;
818
819     ResourceHandleInternal* d = handle->getInternal();
820     ResourceHandleClient* client = handle->client();
821
822     if (d->m_cancelled) {
823         cleanupGioOperation(handle.get());
824         return;
825     }
826
827     ResourceResponse response;
828
829     char* uri = g_file_get_uri(d->m_gfile);
830     response.setURL(KURL(KURL(), uri));
831     g_free(uri);
832
833     GError* error = 0;
834     GFileInfo* info = g_file_query_info_finish(d->m_gfile, res, &error);
835
836     if (error) {
837         // FIXME: to be able to handle ftp URIs properly, we must
838         // check if the error is G_IO_ERROR_NOT_MOUNTED, and if so,
839         // call g_file_mount_enclosing_volume() to mount the ftp
840         // server (and then keep track of the fact that we mounted it,
841         // and set a timeout to unmount it later after it's been idle
842         // for a while).
843         char* uri = g_file_get_uri(d->m_gfile);
844         ResourceError resourceError(g_quark_to_string(G_IO_ERROR),
845                                     error->code,
846                                     uri,
847                                     error ? String::fromUTF8(error->message) : String());
848         g_free(uri);
849         g_error_free(error);
850         cleanupGioOperation(handle.get());
851         client->didFail(handle.get(), resourceError);
852         return;
853     }
854
855     if (g_file_info_get_file_type(info) != G_FILE_TYPE_REGULAR) {
856         // FIXME: what if the URI points to a directory? Should we
857         // generate a listing? How? What do other backends do here?
858         char* uri = g_file_get_uri(d->m_gfile);
859         ResourceError resourceError(g_quark_to_string(G_IO_ERROR),
860                                     G_IO_ERROR_FAILED,
861                                     uri,
862                                     String());
863         g_free(uri);
864         cleanupGioOperation(handle.get());
865         client->didFail(handle.get(), resourceError);
866         return;
867     }
868
869     // According to http://library.gnome.org/devel/gio/stable/gio-GContentType.html
870     // GContentType on Unix is the mime type, but not on Win32.
871     GOwnPtr<gchar> mimeType(g_content_type_get_mime_type(g_file_info_get_content_type(info)));
872     response.setMimeType(mimeType.get());
873     response.setExpectedContentLength(g_file_info_get_size(info));
874
875     GTimeVal tv;
876     g_file_info_get_modification_time(info, &tv);
877     response.setLastModifiedDate(tv.tv_sec);
878
879     client->didReceiveResponse(handle.get(), response);
880
881     if (d->m_cancelled) {
882         cleanupGioOperation(handle.get());
883         return;
884     }
885
886     g_file_read_async(d->m_gfile, G_PRIORITY_DEFAULT, d->m_cancellable,
887                       openCallback, 0);
888 }
889 static bool startGio(ResourceHandle* handle, KURL url)
890 {
891     ASSERT(handle);
892
893     ResourceHandleInternal* d = handle->getInternal();
894
895     if (handle->firstRequest().httpMethod() != "GET" && handle->firstRequest().httpMethod() != "POST")
896         return false;
897
898     // GIO doesn't know how to handle refs and queries, so remove them
899     // TODO: use KURL.fileSystemPath after KURLGtk and FileSystemGtk are
900     // using GIO internally, and providing URIs instead of file paths
901     url.removeFragmentIdentifier();
902     url.setQuery(String());
903     url.removePort();
904
905 #if !OS(WINDOWS)
906     // we avoid the escaping for local files, because
907     // g_filename_from_uri (used internally by GFile) has problems
908     // decoding strings with arbitrary percent signs
909     if (url.isLocalFile())
910         d->m_gfile = g_file_new_for_path(url.prettyURL().utf8().data() + sizeof("file://") - 1);
911     else
912 #endif
913         d->m_gfile = g_file_new_for_uri(url.string().utf8().data());
914     g_object_set_data(G_OBJECT(d->m_gfile), "webkit-resource", handle);
915
916     // balanced by a deref() in cleanupGioOperation, which should always run
917     handle->ref();
918
919     d->m_cancellable = g_cancellable_new();
920     g_file_query_info_async(d->m_gfile,
921                             G_FILE_ATTRIBUTE_STANDARD_TYPE ","
922                             G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
923                             G_FILE_ATTRIBUTE_STANDARD_SIZE,
924                             G_FILE_QUERY_INFO_NONE,
925                             G_PRIORITY_DEFAULT, d->m_cancellable,
926                             queryInfoCallback, 0);
927     return true;
928 }
929
930 SoupSession* ResourceHandle::defaultSession()
931 {
932     static SoupSession* session = createSoupSession();
933
934     return session;
935 }
936
937 }