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