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>
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.
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.
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.
28 #include "ResourceHandle.h"
32 #include "ChromeClient.h"
33 #include "CookieJarSoup.h"
34 #include "CachedResourceLoader.h"
35 #include "FileSystem.h"
37 #include "GOwnPtrSoup.h"
38 #include "HTTPParsers.h"
40 #include "MIMETypeRegistry.h"
41 #include "NotImplemented.h"
43 #include "ResourceError.h"
44 #include "ResourceHandleClient.h"
45 #include "ResourceHandleInternal.h"
46 #include "ResourceResponse.h"
47 #include "SharedBuffer.h"
48 #include "soup-request-http.h"
49 #include "TextEncoding.h"
54 #include <libsoup/soup.h>
56 #include <sys/types.h>
61 #define READ_BUFFER_SIZE 8192
63 class WebCoreSynchronousLoader : public ResourceHandleClient {
64 WTF_MAKE_NONCOPYABLE(WebCoreSynchronousLoader);
66 WebCoreSynchronousLoader(ResourceError&, ResourceResponse &, Vector<char>&);
67 ~WebCoreSynchronousLoader();
69 virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
70 virtual void didReceiveData(ResourceHandle*, const char*, int, int lengthReceived);
71 virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
72 virtual void didFail(ResourceHandle*, const ResourceError&);
77 ResourceError& m_error;
78 ResourceResponse& m_response;
81 GMainLoop* m_mainLoop;
84 WebCoreSynchronousLoader::WebCoreSynchronousLoader(ResourceError& error, ResourceResponse& response, Vector<char>& data)
86 , m_response(response)
90 m_mainLoop = g_main_loop_new(0, false);
93 WebCoreSynchronousLoader::~WebCoreSynchronousLoader()
95 g_main_loop_unref(m_mainLoop);
98 void WebCoreSynchronousLoader::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
100 m_response = response;
103 void WebCoreSynchronousLoader::didReceiveData(ResourceHandle*, const char* data, int length, int)
105 m_data.append(data, length);
108 void WebCoreSynchronousLoader::didFinishLoading(ResourceHandle*, double)
110 g_main_loop_quit(m_mainLoop);
114 void WebCoreSynchronousLoader::didFail(ResourceHandle* handle, const ResourceError& error)
117 didFinishLoading(handle, 0);
120 void WebCoreSynchronousLoader::run()
123 g_main_loop_run(m_mainLoop);
126 static void cleanupSoupRequestOperation(ResourceHandle*, bool isDestroying);
127 static void sendRequestCallback(GObject*, GAsyncResult*, gpointer);
128 static void readCallback(GObject*, GAsyncResult*, gpointer);
129 static void closeCallback(GObject*, GAsyncResult*, gpointer);
130 static bool startGio(ResourceHandle*, KURL);
132 ResourceHandleInternal::~ResourceHandleInternal()
135 g_object_set_data(G_OBJECT(m_soupRequest.get()), "webkit-resource", 0);
138 g_source_remove(m_idleHandler);
143 ResourceHandle::~ResourceHandle()
145 cleanupSoupRequestOperation(this, true);
148 void ResourceHandle::prepareForURL(const KURL &url)
150 GOwnPtr<SoupURI> soupURI(soup_uri_new(url.prettyURL().utf8().data()));
153 soup_session_prepare_for_uri(ResourceHandle::defaultSession(), soupURI.get());
156 // All other kinds of redirections, except for the *304* status code
157 // (SOUP_STATUS_NOT_MODIFIED) which needs to be fed into WebCore, will be
158 // handled by soup directly.
159 static gboolean statusWillBeHandledBySoup(guint statusCode)
161 if (SOUP_STATUS_IS_TRANSPORT_ERROR(statusCode)
162 || (SOUP_STATUS_IS_REDIRECTION(statusCode) && (statusCode != SOUP_STATUS_NOT_MODIFIED))
163 || (statusCode == SOUP_STATUS_UNAUTHORIZED))
169 static void fillResponseFromMessage(SoupMessage* msg, ResourceResponse* response)
171 response->updateFromSoupMessage(msg);
174 // Called each time the message is going to be sent again except the first time.
175 // It's used mostly to let webkit know about redirects.
176 static void restartedCallback(SoupMessage* msg, gpointer data)
178 ResourceHandle* handle = static_cast<ResourceHandle*>(data);
181 ResourceHandleInternal* d = handle->getInternal();
185 GOwnPtr<char> uri(soup_uri_to_string(soup_message_get_uri(msg), false));
186 String location = String::fromUTF8(uri.get());
187 KURL newURL = KURL(handle->firstRequest().url(), location);
189 ResourceRequest request = handle->firstRequest();
190 ResourceResponse response;
191 request.setURL(newURL);
192 request.setHTTPMethod(msg->method);
193 fillResponseFromMessage(msg, &response);
195 // Should not set Referer after a redirect from a secure resource to non-secure one.
196 if (!request.url().protocolIs("https") && protocolIs(request.httpReferrer(), "https")) {
197 request.clearHTTPReferrer();
198 soup_message_headers_remove(msg->request_headers, "Referer");
202 d->client()->willSendRequest(handle, request, response);
207 // Update the first party in case the base URL changed with the redirect
208 String firstPartyString = request.firstPartyForCookies().string();
209 if (!firstPartyString.isEmpty()) {
210 GOwnPtr<SoupURI> firstParty(soup_uri_new(firstPartyString.utf8().data()));
211 soup_message_set_first_party(d->m_soupMessage.get(), firstParty.get());
215 static void contentSniffedCallback(SoupMessage*, const char*, GHashTable*, gpointer);
217 static void gotHeadersCallback(SoupMessage* msg, gpointer data)
219 // For 401, we will accumulate the resource body, and only use it
220 // in case authentication with the soup feature doesn't happen.
221 // For 302 we accumulate the body too because it could be used by
222 // some servers to redirect with a clunky http-equiv=REFRESH
223 if (statusWillBeHandledBySoup(msg->status_code)) {
224 soup_message_body_set_accumulate(msg->response_body, TRUE);
228 // For all the other responses, we handle each chunk ourselves,
229 // and we don't need msg->response_body to contain all of the data
230 // we got, when we finish downloading.
231 soup_message_body_set_accumulate(msg->response_body, FALSE);
233 RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
235 // The content-sniffed callback will handle the response if WebCore
236 // require us to sniff.
237 if (!handle || statusWillBeHandledBySoup(msg->status_code))
240 if (handle->shouldContentSniff()) {
241 // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
242 if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) {
243 soup_message_disable_feature(msg, SOUP_TYPE_CONTENT_SNIFFER);
244 g_signal_handlers_disconnect_by_func(msg, reinterpret_cast<gpointer>(contentSniffedCallback), handle.get());
249 ResourceHandleInternal* d = handle->getInternal();
252 ResourceHandleClient* client = handle->client();
256 ASSERT(d->m_response.isNull());
258 fillResponseFromMessage(msg, &d->m_response);
259 client->didReceiveResponse(handle.get(), d->m_response);
262 // This callback will not be called if the content sniffer is disabled in startHttp.
263 static void contentSniffedCallback(SoupMessage* msg, const char* sniffedType, GHashTable *params, gpointer data)
266 const char* officialType = soup_message_headers_get_one(msg->response_headers, "Content-Type");
268 if (!officialType || strcmp(officialType, sniffedType))
269 soup_message_headers_set_content_type(msg->response_headers, sniffedType, params);
272 if (statusWillBeHandledBySoup(msg->status_code))
275 RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
278 ResourceHandleInternal* d = handle->getInternal();
281 ResourceHandleClient* client = handle->client();
285 ASSERT(d->m_response.isNull());
287 fillResponseFromMessage(msg, &d->m_response);
288 client->didReceiveResponse(handle.get(), d->m_response);
291 static void gotChunkCallback(SoupMessage* msg, SoupBuffer* chunk, gpointer data)
293 if (statusWillBeHandledBySoup(msg->status_code))
296 RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(data);
299 ResourceHandleInternal* d = handle->getInternal();
302 ResourceHandleClient* client = handle->client();
306 ASSERT(!d->m_response.isNull());
308 client->didReceiveData(handle.get(), chunk->data, chunk->length, false);
311 static gboolean parseDataUrl(gpointer callbackData)
313 ResourceHandle* handle = static_cast<ResourceHandle*>(callbackData);
314 ResourceHandleClient* client = handle->client();
315 ResourceHandleInternal* d = handle->getInternal();
319 d->m_idleHandler = 0;
325 String url = handle->firstRequest().url().string();
326 ASSERT(url.startsWith("data:", false));
328 int index = url.find(',');
330 client->cannotShowURL(handle);
334 String mediaType = url.substring(5, index - 5);
336 bool isBase64 = mediaType.endsWith(";base64", false);
338 mediaType = mediaType.left(mediaType.length() - 7);
340 if (mediaType.isEmpty())
341 mediaType = "text/plain;charset=US-ASCII";
343 String mimeType = extractMIMETypeFromMediaType(mediaType);
344 String charset = extractCharsetFromMediaType(mediaType);
346 ASSERT(d->m_response.isNull());
348 d->m_response.setURL(handle->firstRequest().url());
349 d->m_response.setMimeType(mimeType);
351 // For non base64 encoded data we have to convert to UTF-16 early
352 // due to limitations in KURL
353 d->m_response.setTextEncodingName(isBase64 ? charset : "UTF-16");
354 client->didReceiveResponse(handle, d->m_response);
356 // The load may be cancelled, and the client may be destroyed
357 // by any of the client reporting calls, so we check, and bail
358 // out in either of those cases.
359 if (d->m_cancelled || !handle->client())
362 SoupSession* session = handle->defaultSession();
363 GOwnPtr<GError> error;
364 d->m_soupRequest = adoptGRef(webkit_soup_requester_request(d->m_requester.get(), handle->firstRequest().url().string().utf8().data(), session, &error.outPtr()));
366 d->m_soupRequest = 0;
367 client->didFinishLoading(handle, 0);
371 d->m_inputStream = adoptGRef(webkit_soup_request_send(d->m_soupRequest.get(), 0, &error.outPtr()));
373 d->m_inputStream = 0;
374 client->didFinishLoading(handle, 0);
378 d->m_buffer = static_cast<char*>(g_slice_alloc0(READ_BUFFER_SIZE));
381 g_object_set_data(G_OBJECT(d->m_inputStream.get()), "webkit-resource", handle);
382 // balanced by a deref() in cleanupSoupRequestOperation, which should always run
385 d->m_cancellable = adoptGRef(g_cancellable_new());
386 g_input_stream_read_async(d->m_inputStream.get(), d->m_buffer, READ_BUFFER_SIZE, G_PRIORITY_DEFAULT,
387 d->m_cancellable.get(), readCallback, GINT_TO_POINTER(!isBase64));
392 static bool startData(ResourceHandle* handle, String urlString)
396 ResourceHandleInternal* d = handle->getInternal();
398 // If parseDataUrl is called synchronously the job is not yet effectively started
399 // and webkit won't never know that the data has been parsed even didFinishLoading is called.
400 d->m_idleHandler = g_timeout_add(0, parseDataUrl, handle);
404 static SoupSession* createSoupSession()
406 return soup_session_async_new();
409 // Values taken from http://stevesouders.com/ua/index.php following
410 // the rule "Do What Every Other Modern Browser Is Doing". They seem
411 // to significantly improve page loading time compared to soup's
413 #define MAX_CONNECTIONS 60
414 #define MAX_CONNECTIONS_PER_HOST 6
416 static void ensureSessionIsInitialized(SoupSession* session)
418 if (g_object_get_data(G_OBJECT(session), "webkit-init"))
421 SoupCookieJar* jar = reinterpret_cast<SoupCookieJar*>(soup_session_get_feature(session, SOUP_TYPE_COOKIE_JAR));
423 soup_session_add_feature(session, SOUP_SESSION_FEATURE(defaultCookieJar()));
425 setDefaultCookieJar(jar);
427 if (!soup_session_get_feature(session, SOUP_TYPE_LOGGER) && LogNetwork.state == WTFLogChannelOn) {
428 SoupLogger* logger = soup_logger_new(static_cast<SoupLoggerLogLevel>(SOUP_LOGGER_LOG_BODY), -1);
429 soup_logger_attach(logger, session);
430 g_object_unref(logger);
433 g_object_set(session,
434 SOUP_SESSION_MAX_CONNS, MAX_CONNECTIONS,
435 SOUP_SESSION_MAX_CONNS_PER_HOST, MAX_CONNECTIONS_PER_HOST,
438 g_object_set_data(G_OBJECT(session), "webkit-init", reinterpret_cast<void*>(0xdeadbeef));
441 static void cleanupSoupRequestOperation(ResourceHandle* handle, bool isDestroying = false)
443 ResourceHandleInternal* d = handle->getInternal();
445 if (d->m_soupRequest) {
446 g_object_set_data(G_OBJECT(d->m_soupRequest.get()), "webkit-resource", 0);
447 d->m_soupRequest.clear();
450 if (d->m_inputStream) {
451 g_object_set_data(G_OBJECT(d->m_inputStream.get()), "webkit-resource", 0);
452 d->m_inputStream.clear();
455 d->m_cancellable.clear();
457 if (d->m_soupMessage) {
458 g_signal_handlers_disconnect_matched(d->m_soupMessage.get(), G_SIGNAL_MATCH_DATA,
460 d->m_soupMessage.clear();
464 g_slice_free1(READ_BUFFER_SIZE, d->m_buffer);
472 static void sendRequestCallback(GObject* source, GAsyncResult* res, gpointer userData)
474 RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(source, "webkit-resource"));
478 ResourceHandleInternal* d = handle->getInternal();
479 ResourceHandleClient* client = handle->client();
481 if (d->m_gotChunkHandler) {
482 // No need to call gotChunkHandler anymore. Received data will
483 // be reported by readCallback
484 if (g_signal_handler_is_connected(d->m_soupMessage.get(), d->m_gotChunkHandler))
485 g_signal_handler_disconnect(d->m_soupMessage.get(), d->m_gotChunkHandler);
488 if (d->m_cancelled || !client) {
489 cleanupSoupRequestOperation(handle.get());
493 GOwnPtr<GError> error;
494 GInputStream* in = webkit_soup_request_send_finish(d->m_soupRequest.get(), res, &error.outPtr());
497 SoupMessage* soupMsg = d->m_soupMessage.get();
498 gboolean isTransportError = d->m_soupMessage && SOUP_STATUS_IS_TRANSPORT_ERROR(soupMsg->status_code);
500 if (isTransportError || (error->domain == G_IO_ERROR)) {
501 SoupURI* uri = webkit_soup_request_get_uri(d->m_soupRequest.get());
502 GOwnPtr<char> uriStr(soup_uri_to_string(uri, false));
503 gint errorCode = isTransportError ? static_cast<gint>(soupMsg->status_code) : error->code;
504 const gchar* errorMsg = isTransportError ? soupMsg->reason_phrase : error->message;
505 const gchar* quarkStr = isTransportError ? g_quark_to_string(SOUP_HTTP_ERROR) : g_quark_to_string(G_IO_ERROR);
506 ResourceError resourceError(quarkStr, errorCode, uriStr.get(), String::fromUTF8(errorMsg));
508 cleanupSoupRequestOperation(handle.get());
509 client->didFail(handle.get(), resourceError);
513 if (d->m_soupMessage && statusWillBeHandledBySoup(d->m_soupMessage->status_code)) {
514 ASSERT(d->m_response.isNull());
516 fillResponseFromMessage(soupMsg, &d->m_response);
517 client->didReceiveResponse(handle.get(), d->m_response);
519 // WebCore might have cancelled the job in the while. We
520 // must check for response_body->length and not
521 // response_body->data as libsoup always creates the
522 // SoupBuffer for the body even if the length is 0
523 if (!d->m_cancelled && soupMsg->response_body->length)
524 client->didReceiveData(handle.get(), soupMsg->response_body->data, soupMsg->response_body->length, true);
527 // didReceiveData above might have cancelled it
528 if (d->m_cancelled || !client) {
529 cleanupSoupRequestOperation(handle.get());
533 client->didFinishLoading(handle.get(), 0);
537 if (d->m_cancelled) {
538 cleanupSoupRequestOperation(handle.get());
542 d->m_inputStream = adoptGRef(in);
543 d->m_buffer = static_cast<char*>(g_slice_alloc0(READ_BUFFER_SIZE));
546 // readCallback needs it
547 g_object_set_data(G_OBJECT(d->m_inputStream.get()), "webkit-resource", handle.get());
549 // Ensure a response is sent for any protocols that don't explicitly support responses
550 // through got-headers signal or content sniffing.
551 // (e.g. file and GIO based protocol).
552 if (!handle->shouldContentSniff() && d->m_response.isNull()) {
553 d->m_response.setURL(handle->firstRequest().url());
554 d->m_response.setMimeType(webkit_soup_request_get_content_type(d->m_soupRequest.get()));
555 d->m_response.setExpectedContentLength(webkit_soup_request_get_content_length(d->m_soupRequest.get()));
556 client->didReceiveResponse(handle.get(), d->m_response);
558 if (d->m_cancelled) {
559 cleanupSoupRequestOperation(handle.get());
564 if (d->m_defersLoading)
565 soup_session_pause_message(handle->defaultSession(), d->m_soupMessage.get());
567 g_input_stream_read_async(d->m_inputStream.get(), d->m_buffer, READ_BUFFER_SIZE,
568 G_PRIORITY_DEFAULT, d->m_cancellable.get(), readCallback, 0);
571 static bool startHttp(ResourceHandle* handle)
575 SoupSession* session = handle->defaultSession();
576 ensureSessionIsInitialized(session);
578 ResourceHandleInternal* d = handle->getInternal();
580 ResourceRequest request(handle->firstRequest());
581 KURL url(request.url());
582 url.removeFragmentIdentifier();
585 GOwnPtr<GError> error;
586 d->m_soupRequest = adoptGRef(webkit_soup_requester_request(d->m_requester.get(), url.string().utf8().data(), session, &error.outPtr()));
588 d->m_soupRequest = 0;
592 g_object_set_data(G_OBJECT(d->m_soupRequest.get()), "webkit-resource", handle);
594 d->m_soupMessage = adoptGRef(webkit_soup_request_http_get_message(WEBKIT_SOUP_REQUEST_HTTP(d->m_soupRequest.get())));
595 if (!d->m_soupMessage)
598 SoupMessage* soupMessage = d->m_soupMessage.get();
599 request.updateSoupMessage(soupMessage);
601 if (!handle->shouldContentSniff())
602 soup_message_disable_feature(soupMessage, SOUP_TYPE_CONTENT_SNIFFER);
604 g_signal_connect(soupMessage, "content-sniffed", G_CALLBACK(contentSniffedCallback), handle);
606 g_signal_connect(soupMessage, "restarted", G_CALLBACK(restartedCallback), handle);
607 g_signal_connect(soupMessage, "got-headers", G_CALLBACK(gotHeadersCallback), handle);
608 d->m_gotChunkHandler = g_signal_connect(soupMessage, "got-chunk", G_CALLBACK(gotChunkCallback), handle);
610 String firstPartyString = request.firstPartyForCookies().string();
611 if (!firstPartyString.isEmpty()) {
612 GOwnPtr<SoupURI> firstParty(soup_uri_new(firstPartyString.utf8().data()));
613 soup_message_set_first_party(soupMessage, firstParty.get());
616 FormData* httpBody = d->m_firstRequest.httpBody();
617 if (httpBody && !httpBody->isEmpty()) {
618 size_t numElements = httpBody->elements().size();
620 // handle the most common case (i.e. no file upload)
621 if (numElements < 2) {
623 httpBody->flatten(body);
624 soup_message_set_request(soupMessage, d->m_firstRequest.httpContentType().utf8().data(),
625 SOUP_MEMORY_COPY, body.data(), body.size());
628 * we have more than one element to upload, and some may
629 * be (big) files, which we will want to mmap instead of
630 * copying into memory; TODO: support upload of non-local
631 * (think sftp://) files by using GIO?
633 soup_message_body_set_accumulate(soupMessage->request_body, FALSE);
634 for (size_t i = 0; i < numElements; i++) {
635 const FormDataElement& element = httpBody->elements()[i];
637 if (element.m_type == FormDataElement::data)
638 soup_message_body_append(soupMessage->request_body, SOUP_MEMORY_TEMPORARY, element.m_data.data(), element.m_data.size());
641 * mapping for uploaded files code inspired by technique used in
642 * libsoup's simple-httpd test
644 GOwnPtr<GError> error;
645 CString fileName = fileSystemRepresentation(element.m_filename);
646 GMappedFile* fileMapping = g_mapped_file_new(fileName.data(), false, &error.outPtr());
649 g_signal_handlers_disconnect_matched(soupMessage, G_SIGNAL_MATCH_DATA,
651 d->m_soupMessage.clear();
656 SoupBuffer* soupBuffer = soup_buffer_new_with_owner(g_mapped_file_get_contents(fileMapping),
657 g_mapped_file_get_length(fileMapping),
659 reinterpret_cast<GDestroyNotify>(g_mapped_file_unref));
660 soup_message_body_append_buffer(soupMessage->request_body, soupBuffer);
661 soup_buffer_free(soupBuffer);
667 // balanced by a deref() in cleanupSoupRequestOperation, which should always run
670 // Make sure we have an Accept header for subresources; some sites
671 // want this to serve some of their subresources
672 if (!soup_message_headers_get_one(soupMessage->request_headers, "Accept"))
673 soup_message_headers_append(soupMessage->request_headers, "Accept", "*/*");
675 // Send the request only if it's not been explicitely deferred.
676 if (!d->m_defersLoading) {
677 d->m_cancellable = adoptGRef(g_cancellable_new());
678 webkit_soup_request_send_async(d->m_soupRequest.get(), d->m_cancellable.get(), sendRequestCallback, 0);
684 bool ResourceHandle::start(NetworkingContext* context)
686 ASSERT(!d->m_soupMessage);
688 // The frame could be null if the ResourceHandle is not associated to any
689 // Frame, e.g. if we are downloading a file.
690 // If the frame is not null but the page is null this must be an attempted
691 // load from an unload handler, so let's just block it.
692 // If both the frame and the page are not null the context is valid.
693 if (context && !context->isValid())
696 if (!(d->m_user.isEmpty() || d->m_pass.isEmpty())) {
697 // If credentials were specified for this request, add them to the url,
698 // so that they will be passed to NetworkRequest.
699 KURL urlWithCredentials(firstRequest().url());
700 urlWithCredentials.setUser(d->m_user);
701 urlWithCredentials.setPass(d->m_pass);
702 d->m_firstRequest.setURL(urlWithCredentials);
705 KURL url = firstRequest().url();
706 String urlString = url.string();
707 String protocol = url.protocol();
709 // Used to set the authentication dialog toplevel; may be NULL
710 d->m_context = context;
712 if (equalIgnoringCase(protocol, "data"))
713 return startData(this, urlString);
715 if (equalIgnoringCase(protocol, "http") || equalIgnoringCase(protocol, "https")) {
720 if (equalIgnoringCase(protocol, "file") || equalIgnoringCase(protocol, "ftp") || equalIgnoringCase(protocol, "ftps")) {
721 // FIXME: should we be doing any other protocols here?
722 if (startGio(this, url))
726 // Error must not be reported immediately
727 this->scheduleFailure(InvalidURLFailure);
732 void ResourceHandle::cancel()
734 d->m_cancelled = true;
735 if (d->m_soupMessage)
736 soup_session_cancel_message(defaultSession(), d->m_soupMessage.get(), SOUP_STATUS_CANCELLED);
737 else if (d->m_cancellable)
738 g_cancellable_cancel(d->m_cancellable.get());
741 PassRefPtr<SharedBuffer> ResourceHandle::bufferedData()
743 ASSERT_NOT_REACHED();
747 bool ResourceHandle::supportsBufferedData()
752 void ResourceHandle::platformSetDefersLoading(bool defersLoading)
754 // Initial implementation of this method was required for bug #44157.
759 if (!defersLoading && !d->m_cancellable && d->m_soupRequest.get()) {
760 d->m_cancellable = adoptGRef(g_cancellable_new());
761 webkit_soup_request_send_async(d->m_soupRequest.get(), d->m_cancellable.get(), sendRequestCallback, 0);
765 // Only supported for http(s) transfers. Something similar would
766 // probably be needed for data transfers done with GIO.
767 if (!d->m_soupMessage)
771 soup_session_pause_message(defaultSession(), d->m_soupMessage.get());
773 soup_session_unpause_message(defaultSession(), d->m_soupMessage.get());
776 bool ResourceHandle::loadsBlocked()
781 bool ResourceHandle::willLoadFromCache(ResourceRequest&, Frame*)
783 // Not having this function means that we'll ask the user about re-posting a form
784 // even when we go back to a page that's still in the cache.
789 void ResourceHandle::loadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentials /*storedCredentials*/, ResourceError& error, ResourceResponse& response, Vector<char>& data)
791 WebCoreSynchronousLoader syncLoader(error, response, data);
792 // FIXME: we should use the ResourceHandle::create method here,
793 // but it makes us timeout in a couple of tests. See
794 // https://bugs.webkit.org/show_bug.cgi?id=41823
795 RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(request, &syncLoader, false /*defersLoading*/, false /*shouldContentSniff*/));
796 handle->start(context);
798 // If the request has already failed, do not run the main loop, or else we'll block indefinitely.
799 if (handle->d->m_scheduledFailureType != NoFailure)
805 static void closeCallback(GObject* source, GAsyncResult* res, gpointer)
807 RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(source, "webkit-resource"));
811 ResourceHandleInternal* d = handle->getInternal();
812 g_input_stream_close_finish(d->m_inputStream.get(), res, 0);
813 cleanupSoupRequestOperation(handle.get());
816 static void readCallback(GObject* source, GAsyncResult* asyncResult, gpointer data)
818 RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(source, "webkit-resource"));
822 bool convertToUTF16 = static_cast<bool>(data);
823 ResourceHandleInternal* d = handle->getInternal();
824 ResourceHandleClient* client = handle->client();
826 if (d->m_cancelled || !client) {
827 cleanupSoupRequestOperation(handle.get());
831 GOwnPtr<GError> error;
833 gssize bytesRead = g_input_stream_read_finish(d->m_inputStream.get(), asyncResult, &error.outPtr());
835 SoupURI* uri = webkit_soup_request_get_uri(d->m_soupRequest.get());
836 GOwnPtr<char> uriStr(soup_uri_to_string(uri, false));
837 ResourceError resourceError(g_quark_to_string(G_IO_ERROR), error->code, uriStr.get(),
838 error ? String::fromUTF8(error->message) : String());
839 cleanupSoupRequestOperation(handle.get());
840 client->didFail(handle.get(), resourceError);
845 // Finish the load. We do not wait for the stream to
846 // close. Instead we better notify WebCore as soon as possible
847 client->didFinishLoading(handle.get(), 0);
849 g_input_stream_close_async(d->m_inputStream.get(), G_PRIORITY_DEFAULT,
850 0, closeCallback, 0);
854 // It's mandatory to have sent a response before sending data
855 ASSERT(!d->m_response.isNull());
857 d->m_total += bytesRead;
858 if (G_LIKELY(!convertToUTF16))
859 client->didReceiveData(handle.get(), d->m_buffer, bytesRead, d->m_total);
861 // We have to convert it to UTF-16 due to limitations in KURL
862 String data = String::fromUTF8(d->m_buffer, bytesRead);
863 client->didReceiveData(handle.get(), reinterpret_cast<const char*>(data.characters()), data.length() * sizeof(UChar), 0);
866 // didReceiveData may cancel the load, which may release the last reference.
867 if (d->m_cancelled || !client) {
868 cleanupSoupRequestOperation(handle.get());
872 g_input_stream_read_async(d->m_inputStream.get(), d->m_buffer, READ_BUFFER_SIZE, G_PRIORITY_DEFAULT,
873 d->m_cancellable.get(), readCallback, data);
876 static bool startGio(ResourceHandle* handle, KURL url)
880 if (handle->firstRequest().httpMethod() != "GET" && handle->firstRequest().httpMethod() != "POST")
883 SoupSession* session = handle->defaultSession();
884 ResourceHandleInternal* d = handle->getInternal();
886 // GIO doesn't know how to handle refs and queries, so remove them
887 // TODO: use KURL.fileSystemPath after KURLGtk and FileSystemGtk are
888 // using GIO internally, and providing URIs instead of file paths
889 url.removeFragmentIdentifier();
890 url.setQuery(String());
892 CString urlStr = url.string().utf8();
894 GOwnPtr<GError> error;
895 d->m_soupRequest = adoptGRef(webkit_soup_requester_request(d->m_requester.get(), urlStr.data(), session, &error.outPtr()));
897 d->m_soupRequest = 0;
901 g_object_set_data(G_OBJECT(d->m_soupRequest.get()), "webkit-resource", handle);
903 // balanced by a deref() in cleanupSoupRequestOperation, which should always run
906 d->m_cancellable = adoptGRef(g_cancellable_new());
907 webkit_soup_request_send_async(d->m_soupRequest.get(), d->m_cancellable.get(), sendRequestCallback, 0);
912 SoupSession* ResourceHandle::defaultSession()
914 static SoupSession* session = createSoupSession();