| 1 | /* |
|---|
| 2 | * Copyright (C) 2004, 2006 Apple Computer, Inc. All rights reserved. |
|---|
| 3 | * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com |
|---|
| 4 | * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
|---|
| 5 | * Copyright (C) 2007 Holger Hans Peter Freyther |
|---|
| 6 | * Copyright (C) 2008 Collabora Ltd. |
|---|
| 7 | * Copyright (C) 2008 Nuanti Ltd. |
|---|
| 8 | * All rights reserved. |
|---|
| 9 | * |
|---|
| 10 | * Redistribution and use in source and binary forms, with or without |
|---|
| 11 | * modification, are permitted provided that the following conditions |
|---|
| 12 | * are met: |
|---|
| 13 | * 1. Redistributions of source code must retain the above copyright |
|---|
| 14 | * notice, this list of conditions and the following disclaimer. |
|---|
| 15 | * 2. Redistributions in binary form must reproduce the above copyright |
|---|
| 16 | * notice, this list of conditions and the following disclaimer in the |
|---|
| 17 | * documentation and/or other materials provided with the distribution. |
|---|
| 18 | * |
|---|
| 19 | * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
|---|
| 20 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|---|
| 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|---|
| 22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
|---|
| 23 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
|---|
| 24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
|---|
| 25 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|---|
| 26 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
|---|
| 27 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|---|
| 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|---|
| 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 30 | */ |
|---|
| 31 | |
|---|
| 32 | #include "config.h" |
|---|
| 33 | #include "ResourceHandleManager.h" |
|---|
| 34 | |
|---|
| 35 | #include "Base64.h" |
|---|
| 36 | #include "CString.h" |
|---|
| 37 | #include "HTTPParsers.h" |
|---|
| 38 | #include "MIMETypeRegistry.h" |
|---|
| 39 | #include "NotImplemented.h" |
|---|
| 40 | #include "ResourceError.h" |
|---|
| 41 | #include "ResourceHandle.h" |
|---|
| 42 | #include "ResourceHandleInternal.h" |
|---|
| 43 | #include "TextEncoding.h" |
|---|
| 44 | |
|---|
| 45 | #include <errno.h> |
|---|
| 46 | #include <stdio.h> |
|---|
| 47 | #include <wtf/Vector.h> |
|---|
| 48 | |
|---|
| 49 | #if PLATFORM(GTK) |
|---|
| 50 | #if GLIB_CHECK_VERSION(2,12,0) |
|---|
| 51 | #define USE_GLIB_BASE64 |
|---|
| 52 | #endif |
|---|
| 53 | #endif |
|---|
| 54 | |
|---|
| 55 | namespace WebCore { |
|---|
| 56 | |
|---|
| 57 | const int selectTimeoutMS = 5; |
|---|
| 58 | const double pollTimeSeconds = 0.05; |
|---|
| 59 | const int maxRunningJobs = 5; |
|---|
| 60 | |
|---|
| 61 | static const bool ignoreSSLErrors = getenv("WEBKIT_IGNORE_SSL_ERRORS"); |
|---|
| 62 | |
|---|
| 63 | ResourceHandleManager::ResourceHandleManager() |
|---|
| 64 | : m_downloadTimer(this, &ResourceHandleManager::downloadTimerCallback) |
|---|
| 65 | , m_cookieJarFileName(0) |
|---|
| 66 | , m_runningJobs(0) |
|---|
| 67 | { |
|---|
| 68 | curl_global_init(CURL_GLOBAL_ALL); |
|---|
| 69 | m_curlMultiHandle = curl_multi_init(); |
|---|
| 70 | m_curlShareHandle = curl_share_init(); |
|---|
| 71 | curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); |
|---|
| 72 | curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); |
|---|
| 73 | } |
|---|
| 74 | |
|---|
| 75 | ResourceHandleManager::~ResourceHandleManager() |
|---|
| 76 | { |
|---|
| 77 | curl_multi_cleanup(m_curlMultiHandle); |
|---|
| 78 | curl_share_cleanup(m_curlShareHandle); |
|---|
| 79 | if (m_cookieJarFileName) |
|---|
| 80 | free(m_cookieJarFileName); |
|---|
| 81 | curl_global_cleanup(); |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName) |
|---|
| 85 | { |
|---|
| 86 | m_cookieJarFileName = strdup(cookieJarFileName); |
|---|
| 87 | } |
|---|
| 88 | |
|---|
| 89 | ResourceHandleManager* ResourceHandleManager::sharedInstance() |
|---|
| 90 | { |
|---|
| 91 | static ResourceHandleManager* sharedInstance = 0; |
|---|
| 92 | if (!sharedInstance) |
|---|
| 93 | sharedInstance = new ResourceHandleManager(); |
|---|
| 94 | return sharedInstance; |
|---|
| 95 | } |
|---|
| 96 | |
|---|
| 97 | // called with data after all headers have been processed via headerCallback |
|---|
| 98 | static size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* data) |
|---|
| 99 | { |
|---|
| 100 | ResourceHandle* job = static_cast<ResourceHandle*>(data); |
|---|
| 101 | ResourceHandleInternal* d = job->getInternal(); |
|---|
| 102 | if (d->m_cancelled) |
|---|
| 103 | return 0; |
|---|
| 104 | |
|---|
| 105 | #if LIBCURL_VERSION_NUM > 0x071200 |
|---|
| 106 | // We should never be called when deferred loading is activated. |
|---|
| 107 | ASSERT(!d->m_defersLoading); |
|---|
| 108 | #endif |
|---|
| 109 | |
|---|
| 110 | size_t totalSize = size * nmemb; |
|---|
| 111 | |
|---|
| 112 | // this shouldn't be necessary but apparently is. CURL writes the data |
|---|
| 113 | // of html page even if it is a redirect that was handled internally |
|---|
| 114 | // can be observed e.g. on gmail.com |
|---|
| 115 | CURL* h = d->m_handle; |
|---|
| 116 | long httpCode = 0; |
|---|
| 117 | CURLcode err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode); |
|---|
| 118 | if (CURLE_OK == err && httpCode >= 300 && httpCode < 400) |
|---|
| 119 | return totalSize; |
|---|
| 120 | |
|---|
| 121 | // since the code in headerCallback will not have run for local files |
|---|
| 122 | // the code to set the URL and fire didReceiveResponse is never run, |
|---|
| 123 | // which means the ResourceLoader's response does not contain the URL. |
|---|
| 124 | // Run the code here for local files to resolve the issue. |
|---|
| 125 | // TODO: See if there is a better approach for handling this. |
|---|
| 126 | if (!d->m_response.responseFired()) { |
|---|
| 127 | const char* hdr; |
|---|
| 128 | err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr); |
|---|
| 129 | d->m_response.setUrl(KURL(hdr)); |
|---|
| 130 | if (d->client()) |
|---|
| 131 | d->client()->didReceiveResponse(job, d->m_response); |
|---|
| 132 | d->m_response.setResponseFired(true); |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | if (d->client()) |
|---|
| 136 | d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0); |
|---|
| 137 | return totalSize; |
|---|
| 138 | } |
|---|
| 139 | |
|---|
| 140 | /* |
|---|
| 141 | * This is being called for each HTTP header in the response. This includes '\r\n' |
|---|
| 142 | * for the last line of the header. |
|---|
| 143 | * |
|---|
| 144 | * We will add each HTTP Header to the ResourceResponse and on the termination |
|---|
| 145 | * of the header (\r\n) we will parse Content-Type and Content-Disposition and |
|---|
| 146 | * update the ResourceResponse and then send it away. |
|---|
| 147 | * |
|---|
| 148 | */ |
|---|
| 149 | static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* data) |
|---|
| 150 | { |
|---|
| 151 | ResourceHandle* job = static_cast<ResourceHandle*>(data); |
|---|
| 152 | ResourceHandleInternal* d = job->getInternal(); |
|---|
| 153 | if (d->m_cancelled) |
|---|
| 154 | return 0; |
|---|
| 155 | |
|---|
| 156 | #if LIBCURL_VERSION_NUM > 0x071200 |
|---|
| 157 | // We should never be called when deferred loading is activated. |
|---|
| 158 | ASSERT(!d->m_defersLoading); |
|---|
| 159 | #endif |
|---|
| 160 | |
|---|
| 161 | size_t totalSize = size * nmemb; |
|---|
| 162 | ResourceHandleClient* client = d->client(); |
|---|
| 163 | |
|---|
| 164 | String header(static_cast<const char*>(ptr), totalSize); |
|---|
| 165 | |
|---|
| 166 | /* |
|---|
| 167 | * a) We can finish and send the ResourceResponse |
|---|
| 168 | * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse |
|---|
| 169 | * |
|---|
| 170 | * The HTTP standard requires to use \r\n but for compatibility it recommends to |
|---|
| 171 | * accept also \n. |
|---|
| 172 | */ |
|---|
| 173 | if (header == String("\r\n") || header == String("\n")) { |
|---|
| 174 | CURL* h = d->m_handle; |
|---|
| 175 | CURLcode err; |
|---|
| 176 | |
|---|
| 177 | double contentLength = 0; |
|---|
| 178 | err = curl_easy_getinfo(h, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength); |
|---|
| 179 | d->m_response.setExpectedContentLength(static_cast<long long int>(contentLength)); |
|---|
| 180 | |
|---|
| 181 | const char* hdr; |
|---|
| 182 | err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr); |
|---|
| 183 | d->m_response.setUrl(KURL(hdr)); |
|---|
| 184 | |
|---|
| 185 | long httpCode = 0; |
|---|
| 186 | err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode); |
|---|
| 187 | d->m_response.setHTTPStatusCode(httpCode); |
|---|
| 188 | |
|---|
| 189 | d->m_response.setMimeType(extractMIMETypeFromMediaType(d->m_response.httpHeaderField("Content-Type"))); |
|---|
| 190 | d->m_response.setTextEncodingName(extractCharsetFromMediaType(d->m_response.httpHeaderField("Content-Type"))); |
|---|
| 191 | d->m_response.setSuggestedFilename(filenameFromHTTPContentDisposition(d->m_response.httpHeaderField("Content-Disposition"))); |
|---|
| 192 | |
|---|
| 193 | // HTTP redirection |
|---|
| 194 | if (httpCode >= 300 && httpCode < 400) { |
|---|
| 195 | String location = d->m_response.httpHeaderField("location"); |
|---|
| 196 | if (!location.isEmpty()) { |
|---|
| 197 | KURL newURL = KURL(job->request().url(), location); |
|---|
| 198 | |
|---|
| 199 | ResourceRequest redirectedRequest = job->request(); |
|---|
| 200 | redirectedRequest.setURL(newURL); |
|---|
| 201 | if (client) |
|---|
| 202 | client->willSendRequest(job, redirectedRequest, d->m_response); |
|---|
| 203 | |
|---|
| 204 | d->m_request.setURL(newURL); |
|---|
| 205 | |
|---|
| 206 | return totalSize; |
|---|
| 207 | } |
|---|
| 208 | } |
|---|
| 209 | |
|---|
| 210 | if (client) |
|---|
| 211 | client->didReceiveResponse(job, d->m_response); |
|---|
| 212 | d->m_response.setResponseFired(true); |
|---|
| 213 | |
|---|
| 214 | } else { |
|---|
| 215 | int splitPos = header.find(":"); |
|---|
| 216 | if (splitPos != -1) |
|---|
| 217 | d->m_response.setHTTPHeaderField(header.left(splitPos), header.substring(splitPos+1).stripWhiteSpace()); |
|---|
| 218 | } |
|---|
| 219 | |
|---|
| 220 | return totalSize; |
|---|
| 221 | } |
|---|
| 222 | |
|---|
| 223 | /* This is called to obtain HTTP POST or PUT data. |
|---|
| 224 | Iterate through FormData elements and upload files. |
|---|
| 225 | Carefully respect the given buffer size and fill the rest of the data at the next calls. |
|---|
| 226 | */ |
|---|
| 227 | size_t readCallback(void* ptr, size_t size, size_t nmemb, void* data) |
|---|
| 228 | { |
|---|
| 229 | ResourceHandle* job = static_cast<ResourceHandle*>(data); |
|---|
| 230 | ResourceHandleInternal* d = job->getInternal(); |
|---|
| 231 | if (d->m_cancelled) |
|---|
| 232 | return 0; |
|---|
| 233 | |
|---|
| 234 | #if LIBCURL_VERSION_NUM > 0x071200 |
|---|
| 235 | // We should never be called when deferred loading is activated. |
|---|
| 236 | ASSERT(!d->m_defersLoading); |
|---|
| 237 | #endif |
|---|
| 238 | |
|---|
| 239 | if (!size || !nmemb) |
|---|
| 240 | return 0; |
|---|
| 241 | |
|---|
| 242 | if (!d->m_formDataStream.hasMoreElements()) |
|---|
| 243 | return 0; |
|---|
| 244 | |
|---|
| 245 | size_t sent = d->m_formDataStream.read(ptr, size, nmemb); |
|---|
| 246 | |
|---|
| 247 | // Something went wrong so cancel the job. |
|---|
| 248 | if (!sent) |
|---|
| 249 | job->cancel(); |
|---|
| 250 | |
|---|
| 251 | return sent; |
|---|
| 252 | } |
|---|
| 253 | |
|---|
| 254 | void ResourceHandleManager::downloadTimerCallback(Timer<ResourceHandleManager>* timer) |
|---|
| 255 | { |
|---|
| 256 | startScheduledJobs(); |
|---|
| 257 | |
|---|
| 258 | fd_set fdread; |
|---|
| 259 | fd_set fdwrite; |
|---|
| 260 | fd_set fdexcep; |
|---|
| 261 | int maxfd = 0; |
|---|
| 262 | |
|---|
| 263 | struct timeval timeout; |
|---|
| 264 | timeout.tv_sec = 0; |
|---|
| 265 | timeout.tv_usec = selectTimeoutMS * 1000; // select waits microseconds |
|---|
| 266 | |
|---|
| 267 | // Temporarily disable timers since signals may interrupt select(), raising EINTR errors on some platforms |
|---|
| 268 | setDeferringTimers(true); |
|---|
| 269 | int rc = 0; |
|---|
| 270 | do { |
|---|
| 271 | FD_ZERO(&fdread); |
|---|
| 272 | FD_ZERO(&fdwrite); |
|---|
| 273 | FD_ZERO(&fdexcep); |
|---|
| 274 | curl_multi_fdset(m_curlMultiHandle, &fdread, &fdwrite, &fdexcep, &maxfd); |
|---|
| 275 | // When the 3 file descriptors are empty, winsock will return -1 |
|---|
| 276 | // and bail out, stopping the file download. So make sure we |
|---|
| 277 | // have valid file descriptors before calling select. |
|---|
| 278 | if (maxfd >= 0) |
|---|
| 279 | rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); |
|---|
| 280 | } while (rc == -1 && errno == EINTR); |
|---|
| 281 | setDeferringTimers(false); |
|---|
| 282 | |
|---|
| 283 | if (-1 == rc) { |
|---|
| 284 | #ifndef NDEBUG |
|---|
| 285 | perror("bad: select() returned -1: "); |
|---|
| 286 | #endif |
|---|
| 287 | return; |
|---|
| 288 | } |
|---|
| 289 | |
|---|
| 290 | int runningHandles = 0; |
|---|
| 291 | while (curl_multi_perform(m_curlMultiHandle, &runningHandles) == CURLM_CALL_MULTI_PERFORM) { } |
|---|
| 292 | |
|---|
| 293 | // check the curl messages indicating completed transfers |
|---|
| 294 | // and free their resources |
|---|
| 295 | while (true) { |
|---|
| 296 | int messagesInQueue; |
|---|
| 297 | CURLMsg* msg = curl_multi_info_read(m_curlMultiHandle, &messagesInQueue); |
|---|
| 298 | if (!msg) |
|---|
| 299 | break; |
|---|
| 300 | |
|---|
| 301 | // find the node which has same d->m_handle as completed transfer |
|---|
| 302 | CURL* handle = msg->easy_handle; |
|---|
| 303 | ASSERT(handle); |
|---|
| 304 | ResourceHandle* job = 0; |
|---|
| 305 | CURLcode err = curl_easy_getinfo(handle, CURLINFO_PRIVATE, &job); |
|---|
| 306 | ASSERT(CURLE_OK == err); |
|---|
| 307 | ASSERT(job); |
|---|
| 308 | if (!job) |
|---|
| 309 | continue; |
|---|
| 310 | ResourceHandleInternal* d = job->getInternal(); |
|---|
| 311 | ASSERT(d->m_handle == handle); |
|---|
| 312 | |
|---|
| 313 | if (d->m_cancelled) { |
|---|
| 314 | removeFromCurl(job); |
|---|
| 315 | continue; |
|---|
| 316 | } |
|---|
| 317 | |
|---|
| 318 | if (CURLMSG_DONE != msg->msg) |
|---|
| 319 | continue; |
|---|
| 320 | |
|---|
| 321 | if (CURLE_OK == msg->data.result) { |
|---|
| 322 | if (d->client()) |
|---|
| 323 | d->client()->didFinishLoading(job); |
|---|
| 324 | } else { |
|---|
| 325 | #ifndef NDEBUG |
|---|
| 326 | char* url = 0; |
|---|
| 327 | curl_easy_getinfo(d->m_handle, CURLINFO_EFFECTIVE_URL, &url); |
|---|
| 328 | fprintf(stderr, "Curl ERROR for url='%s', error: '%s'\n", url, curl_easy_strerror(msg->data.result)); |
|---|
| 329 | #endif |
|---|
| 330 | if (d->client()) |
|---|
| 331 | d->client()->didFail(job, ResourceError()); |
|---|
| 332 | } |
|---|
| 333 | |
|---|
| 334 | removeFromCurl(job); |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | bool started = startScheduledJobs(); // new jobs might have been added in the meantime |
|---|
| 338 | |
|---|
| 339 | if (!m_downloadTimer.isActive() && (started || (runningHandles > 0))) |
|---|
| 340 | m_downloadTimer.startOneShot(pollTimeSeconds); |
|---|
| 341 | } |
|---|
| 342 | |
|---|
| 343 | void ResourceHandleManager::removeFromCurl(ResourceHandle* job) |
|---|
| 344 | { |
|---|
| 345 | ResourceHandleInternal* d = job->getInternal(); |
|---|
| 346 | ASSERT(d->m_handle); |
|---|
| 347 | if (!d->m_handle) |
|---|
| 348 | return; |
|---|
| 349 | m_runningJobs--; |
|---|
| 350 | curl_multi_remove_handle(m_curlMultiHandle, d->m_handle); |
|---|
| 351 | curl_easy_cleanup(d->m_handle); |
|---|
| 352 | d->m_handle = 0; |
|---|
| 353 | } |
|---|
| 354 | |
|---|
| 355 | void ResourceHandleManager::setupPUT(ResourceHandle*, struct curl_slist**) |
|---|
| 356 | { |
|---|
| 357 | notImplemented(); |
|---|
| 358 | } |
|---|
| 359 | |
|---|
| 360 | /* Calculate the length of the POST. |
|---|
| 361 | Force chunked data transfer if size of files can't be obtained. |
|---|
| 362 | */ |
|---|
| 363 | void ResourceHandleManager::setupPOST(ResourceHandle* job, struct curl_slist** headers) |
|---|
| 364 | { |
|---|
| 365 | ResourceHandleInternal* d = job->getInternal(); |
|---|
| 366 | Vector<FormDataElement> elements; |
|---|
| 367 | // Fix crash when httpBody is null (see bug #16906). |
|---|
| 368 | if (job->request().httpBody()) |
|---|
| 369 | elements = job->request().httpBody()->elements(); |
|---|
| 370 | size_t numElements = elements.size(); |
|---|
| 371 | |
|---|
| 372 | if (!numElements) |
|---|
| 373 | return; |
|---|
| 374 | |
|---|
| 375 | // Do not stream for simple POST data |
|---|
| 376 | if (numElements == 1) { |
|---|
| 377 | job->request().httpBody()->flatten(d->m_postBytes); |
|---|
| 378 | if (d->m_postBytes.size() != 0) { |
|---|
| 379 | curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE); |
|---|
| 380 | curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, d->m_postBytes.size()); |
|---|
| 381 | curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDS, d->m_postBytes.data()); |
|---|
| 382 | } |
|---|
| 383 | return; |
|---|
| 384 | } |
|---|
| 385 | |
|---|
| 386 | // Obtain the total size of the POST |
|---|
| 387 | // The size of a curl_off_t could be different in WebKit and in cURL depending on |
|---|
| 388 | // compilation flags of both. For CURLOPT_POSTFIELDSIZE_LARGE we have to pass the |
|---|
| 389 | // right size or random data will be used as the size. |
|---|
| 390 | static int expectedSizeOfCurlOffT = 0; |
|---|
| 391 | if (!expectedSizeOfCurlOffT) { |
|---|
| 392 | curl_version_info_data *infoData = curl_version_info(CURLVERSION_NOW); |
|---|
| 393 | if (infoData->features & CURL_VERSION_LARGEFILE) |
|---|
| 394 | expectedSizeOfCurlOffT = sizeof(long long); |
|---|
| 395 | else |
|---|
| 396 | expectedSizeOfCurlOffT = sizeof(int); |
|---|
| 397 | } |
|---|
| 398 | |
|---|
| 399 | #if COMPILER(MSVC) |
|---|
| 400 | // work around compiler error in Visual Studio 2005. It can't properly |
|---|
| 401 | // handle math with 64-bit constant declarations. |
|---|
| 402 | #pragma warning(disable: 4307) |
|---|
| 403 | #endif |
|---|
| 404 | static const long long maxCurlOffT = (1LL << (expectedSizeOfCurlOffT * 8 - 1)) - 1; |
|---|
| 405 | curl_off_t size = 0; |
|---|
| 406 | bool chunkedTransfer = false; |
|---|
| 407 | for (size_t i = 0; i < numElements; i++) { |
|---|
| 408 | FormDataElement element = elements[i]; |
|---|
| 409 | if (element.m_type == FormDataElement::encodedFile) { |
|---|
| 410 | long long fileSizeResult; |
|---|
| 411 | if (getFileSize(element.m_filename, fileSizeResult)) { |
|---|
| 412 | if (fileSizeResult > maxCurlOffT) { |
|---|
| 413 | // File size is too big for specifying it to cURL |
|---|
| 414 | chunkedTransfer = true; |
|---|
| 415 | break; |
|---|
| 416 | } |
|---|
| 417 | size += fileSizeResult; |
|---|
| 418 | } else { |
|---|
| 419 | chunkedTransfer = true; |
|---|
| 420 | break; |
|---|
| 421 | } |
|---|
| 422 | } else |
|---|
| 423 | size += elements[i].m_data.size(); |
|---|
| 424 | } |
|---|
| 425 | |
|---|
| 426 | curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE); |
|---|
| 427 | |
|---|
| 428 | // cURL guesses that we want chunked encoding as long as we specify the header |
|---|
| 429 | if (chunkedTransfer) |
|---|
| 430 | *headers = curl_slist_append(*headers, "Transfer-Encoding: chunked"); |
|---|
| 431 | else { |
|---|
| 432 | if (sizeof(long long) == expectedSizeOfCurlOffT) |
|---|
| 433 | curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (long long)size); |
|---|
| 434 | else |
|---|
| 435 | curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE_LARGE, (int)size); |
|---|
| 436 | } |
|---|
| 437 | |
|---|
| 438 | curl_easy_setopt(d->m_handle, CURLOPT_READFUNCTION, readCallback); |
|---|
| 439 | curl_easy_setopt(d->m_handle, CURLOPT_READDATA, job); |
|---|
| 440 | } |
|---|
| 441 | |
|---|
| 442 | void ResourceHandleManager::add(ResourceHandle* job) |
|---|
| 443 | { |
|---|
| 444 | // we can be called from within curl, so to avoid re-entrancy issues |
|---|
| 445 | // schedule this job to be added the next time we enter curl download loop |
|---|
| 446 | m_resourceHandleList.append(job); |
|---|
| 447 | if (!m_downloadTimer.isActive()) |
|---|
| 448 | m_downloadTimer.startOneShot(pollTimeSeconds); |
|---|
| 449 | } |
|---|
| 450 | |
|---|
| 451 | bool ResourceHandleManager::removeScheduledJob(ResourceHandle* job) |
|---|
| 452 | { |
|---|
| 453 | int size = m_resourceHandleList.size(); |
|---|
| 454 | for (int i = 0; i < size; i++) { |
|---|
| 455 | if (job == m_resourceHandleList[i]) { |
|---|
| 456 | m_resourceHandleList.remove(i); |
|---|
| 457 | return true; |
|---|
| 458 | } |
|---|
| 459 | } |
|---|
| 460 | return false; |
|---|
| 461 | } |
|---|
| 462 | |
|---|
| 463 | bool ResourceHandleManager::startScheduledJobs() |
|---|
| 464 | { |
|---|
| 465 | // TODO: Create a separate stack of jobs for each domain. |
|---|
| 466 | |
|---|
| 467 | bool started = false; |
|---|
| 468 | while (!m_resourceHandleList.isEmpty() && m_runningJobs < maxRunningJobs) { |
|---|
| 469 | ResourceHandle* job = m_resourceHandleList[0]; |
|---|
| 470 | m_resourceHandleList.remove(0); |
|---|
| 471 | startJob(job); |
|---|
| 472 | started = true; |
|---|
| 473 | } |
|---|
| 474 | return started; |
|---|
| 475 | } |
|---|
| 476 | |
|---|
| 477 | static void parseDataUrl(ResourceHandle* handle) |
|---|
| 478 | { |
|---|
| 479 | ResourceHandleClient* client = handle->client(); |
|---|
| 480 | |
|---|
| 481 | ASSERT(client); |
|---|
| 482 | if (!client) |
|---|
| 483 | return; |
|---|
| 484 | |
|---|
| 485 | String url = handle->request().url().string(); |
|---|
| 486 | ASSERT(url.startsWith("data:", false)); |
|---|
| 487 | |
|---|
| 488 | int index = url.find(','); |
|---|
| 489 | if (index == -1) { |
|---|
| 490 | client->cannotShowURL(handle); |
|---|
| 491 | return; |
|---|
| 492 | } |
|---|
| 493 | |
|---|
| 494 | String mediaType = url.substring(5, index - 5); |
|---|
| 495 | String data = url.substring(index + 1); |
|---|
| 496 | |
|---|
| 497 | bool base64 = mediaType.endsWith(";base64", false); |
|---|
| 498 | if (base64) |
|---|
| 499 | mediaType = mediaType.left(mediaType.length() - 7); |
|---|
| 500 | |
|---|
| 501 | if (mediaType.isEmpty()) |
|---|
| 502 | mediaType = "text/plain;charset=US-ASCII"; |
|---|
| 503 | |
|---|
| 504 | String mimeType = extractMIMETypeFromMediaType(mediaType); |
|---|
| 505 | String charset = extractCharsetFromMediaType(mediaType); |
|---|
| 506 | |
|---|
| 507 | ResourceResponse response; |
|---|
| 508 | response.setMimeType(mimeType); |
|---|
| 509 | |
|---|
| 510 | if (base64) { |
|---|
| 511 | data = decodeURLEscapeSequences(data); |
|---|
| 512 | response.setTextEncodingName(charset); |
|---|
| 513 | client->didReceiveResponse(handle, response); |
|---|
| 514 | |
|---|
| 515 | // Use the GLib Base64 if available, since WebCore's decoder isn't |
|---|
| 516 | // general-purpose and fails on Acid3 test 97 (whitespace). |
|---|
| 517 | #ifdef USE_GLIB_BASE64 |
|---|
| 518 | size_t outLength = 0; |
|---|
| 519 | char* outData = 0; |
|---|
| 520 | outData = reinterpret_cast<char*>(g_base64_decode(data.utf8().data(), &outLength)); |
|---|
| 521 | if (outData && outLength > 0) |
|---|
| 522 | client->didReceiveData(handle, outData, outLength, 0); |
|---|
| 523 | g_free(outData); |
|---|
| 524 | #else |
|---|
| 525 | Vector<char> out; |
|---|
| 526 | if (base64Decode(data.latin1().data(), data.latin1().length(), out) && out.size() > 0) |
|---|
| 527 | client->didReceiveData(handle, out.data(), out.size(), 0); |
|---|
| 528 | #endif |
|---|
| 529 | } else { |
|---|
| 530 | // We have to convert to UTF-16 early due to limitations in KURL |
|---|
| 531 | data = decodeURLEscapeSequences(data, TextEncoding(charset)); |
|---|
| 532 | response.setTextEncodingName("UTF-16"); |
|---|
| 533 | client->didReceiveResponse(handle, response); |
|---|
| 534 | if (data.length() > 0) |
|---|
| 535 | client->didReceiveData(handle, reinterpret_cast<const char*>(data.characters()), data.length() * sizeof(UChar), 0); |
|---|
| 536 | } |
|---|
| 537 | |
|---|
| 538 | client->didFinishLoading(handle); |
|---|
| 539 | } |
|---|
| 540 | |
|---|
| 541 | void ResourceHandleManager::dispatchSynchronousJob(ResourceHandle* job) |
|---|
| 542 | { |
|---|
| 543 | KURL kurl = job->request().url(); |
|---|
| 544 | |
|---|
| 545 | if (kurl.protocolIs("data")) { |
|---|
| 546 | parseDataUrl(job); |
|---|
| 547 | return; |
|---|
| 548 | } |
|---|
| 549 | |
|---|
| 550 | ResourceHandleInternal* handle = job->getInternal(); |
|---|
| 551 | |
|---|
| 552 | #if LIBCURL_VERSION_NUM > 0x071200 |
|---|
| 553 | // If defersLoading is true and we call curl_easy_perform |
|---|
| 554 | // on a paused handle, libcURL would do the transfert anyway |
|---|
| 555 | // and we would assert so force defersLoading to be false. |
|---|
| 556 | handle->m_defersLoading = false; |
|---|
| 557 | #endif |
|---|
| 558 | |
|---|
| 559 | initializeHandle(job); |
|---|
| 560 | |
|---|
| 561 | // curl_easy_perform blocks until the transfert is finished. |
|---|
| 562 | CURLcode ret = curl_easy_perform(handle->m_handle); |
|---|
| 563 | |
|---|
| 564 | if (ret != 0) { |
|---|
| 565 | ResourceError error(String(handle->m_url), ret, String(handle->m_url), String(curl_easy_strerror(ret))); |
|---|
| 566 | handle->client()->didFail(job, error); |
|---|
| 567 | } |
|---|
| 568 | |
|---|
| 569 | curl_easy_cleanup(handle->m_handle); |
|---|
| 570 | } |
|---|
| 571 | |
|---|
| 572 | void ResourceHandleManager::startJob(ResourceHandle* job) |
|---|
| 573 | { |
|---|
| 574 | KURL kurl = job->request().url(); |
|---|
| 575 | |
|---|
| 576 | if (kurl.protocolIs("data")) { |
|---|
| 577 | parseDataUrl(job); |
|---|
| 578 | return; |
|---|
| 579 | } |
|---|
| 580 | |
|---|
| 581 | initializeHandle(job); |
|---|
| 582 | |
|---|
| 583 | m_runningJobs++; |
|---|
| 584 | CURLMcode ret = curl_multi_add_handle(m_curlMultiHandle, job->getInternal()->m_handle); |
|---|
| 585 | // don't call perform, because events must be async |
|---|
| 586 | // timeout will occur and do curl_multi_perform |
|---|
| 587 | if (ret && ret != CURLM_CALL_MULTI_PERFORM) { |
|---|
| 588 | #ifndef NDEBUG |
|---|
| 589 | fprintf(stderr, "Error %d starting job %s\n", ret, encodeWithURLEscapeSequences(job->request().url().string()).latin1().data()); |
|---|
| 590 | #endif |
|---|
| 591 | job->cancel(); |
|---|
| 592 | return; |
|---|
| 593 | } |
|---|
| 594 | } |
|---|
| 595 | |
|---|
| 596 | void ResourceHandleManager::initializeHandle(ResourceHandle* job) |
|---|
| 597 | { |
|---|
| 598 | KURL kurl = job->request().url(); |
|---|
| 599 | |
|---|
| 600 | // Remove any fragment part, otherwise curl will send it as part of the request. |
|---|
| 601 | kurl.removeRef(); |
|---|
| 602 | |
|---|
| 603 | ResourceHandleInternal* d = job->getInternal(); |
|---|
| 604 | String url = kurl.string(); |
|---|
| 605 | |
|---|
| 606 | if (kurl.isLocalFile()) { |
|---|
| 607 | String query = kurl.query(); |
|---|
| 608 | // Remove any query part sent to a local file. |
|---|
| 609 | if (!query.isEmpty()) |
|---|
| 610 | url = url.left(url.find(query)); |
|---|
| 611 | // Determine the MIME type based on the path. |
|---|
| 612 | d->m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(url)); |
|---|
| 613 | } |
|---|
| 614 | |
|---|
| 615 | d->m_handle = curl_easy_init(); |
|---|
| 616 | |
|---|
| 617 | #if LIBCURL_VERSION_NUM > 0x071200 |
|---|
| 618 | if (d->m_defersLoading) { |
|---|
| 619 | CURLcode error = curl_easy_pause(d->m_handle, CURLPAUSE_ALL); |
|---|
| 620 | // If we did not pause the handle, we would ASSERT in the |
|---|
| 621 | // header callback. So just assert here. |
|---|
| 622 | ASSERT(error == CURLE_OK); |
|---|
| 623 | } |
|---|
| 624 | #endif |
|---|
| 625 | #ifndef NDEBUG |
|---|
| 626 | if (getenv("DEBUG_CURL")) |
|---|
| 627 | curl_easy_setopt(d->m_handle, CURLOPT_VERBOSE, 1); |
|---|
| 628 | #endif |
|---|
| 629 | curl_easy_setopt(d->m_handle, CURLOPT_PRIVATE, job); |
|---|
| 630 | curl_easy_setopt(d->m_handle, CURLOPT_ERRORBUFFER, m_curlErrorBuffer); |
|---|
| 631 | curl_easy_setopt(d->m_handle, CURLOPT_WRITEFUNCTION, writeCallback); |
|---|
| 632 | curl_easy_setopt(d->m_handle, CURLOPT_WRITEDATA, job); |
|---|
| 633 | curl_easy_setopt(d->m_handle, CURLOPT_HEADERFUNCTION, headerCallback); |
|---|
| 634 | curl_easy_setopt(d->m_handle, CURLOPT_WRITEHEADER, job); |
|---|
| 635 | curl_easy_setopt(d->m_handle, CURLOPT_AUTOREFERER, 1); |
|---|
| 636 | curl_easy_setopt(d->m_handle, CURLOPT_FOLLOWLOCATION, 1); |
|---|
| 637 | curl_easy_setopt(d->m_handle, CURLOPT_MAXREDIRS, 10); |
|---|
| 638 | curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); |
|---|
| 639 | curl_easy_setopt(d->m_handle, CURLOPT_SHARE, m_curlShareHandle); |
|---|
| 640 | curl_easy_setopt(d->m_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5); // 5 minutes |
|---|
| 641 | // FIXME: Enable SSL verification when we have a way of shipping certs |
|---|
| 642 | // and/or reporting SSL errors to the user. |
|---|
| 643 | if (ignoreSSLErrors) |
|---|
| 644 | curl_easy_setopt(d->m_handle, CURLOPT_SSL_VERIFYPEER, false); |
|---|
| 645 | // enable gzip and deflate through Accept-Encoding: |
|---|
| 646 | curl_easy_setopt(d->m_handle, CURLOPT_ENCODING, ""); |
|---|
| 647 | |
|---|
| 648 | // url must remain valid through the request |
|---|
| 649 | ASSERT(!d->m_url); |
|---|
| 650 | |
|---|
| 651 | // url is in ASCII so latin1() will only convert it to char* without character translation. |
|---|
| 652 | d->m_url = strdup(url.latin1().data()); |
|---|
| 653 | curl_easy_setopt(d->m_handle, CURLOPT_URL, d->m_url); |
|---|
| 654 | |
|---|
| 655 | if (m_cookieJarFileName) { |
|---|
| 656 | curl_easy_setopt(d->m_handle, CURLOPT_COOKIEFILE, m_cookieJarFileName); |
|---|
| 657 | curl_easy_setopt(d->m_handle, CURLOPT_COOKIEJAR, m_cookieJarFileName); |
|---|
| 658 | |
|---|