• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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