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