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