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.toker@collabora.co.uk>
5 * Copyright (C) 2007 Holger Hans Peter Freyther
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "ResourceHandleManager.h"
34 #include "MIMETypeRegistry.h"
35 #include "NotImplemented.h"
36 #include "ResourceHandle.h"
37 #include "ResourceHandleInternal.h"
38 #include "HTTPParsers.h"
41 #include <wtf/Vector.h>
45 const int selectTimeoutMS = 5;
46 const double pollTimeSeconds = 0.05;
47 const int maxRunningJobs = 5;
49 ResourceHandleManager::ResourceHandleManager()
50 : m_downloadTimer(this, &ResourceHandleManager::downloadTimerCallback)
51 , m_cookieJarFileName(0)
54 curl_global_init(CURL_GLOBAL_ALL);
55 m_curlMultiHandle = curl_multi_init();
56 m_curlShareHandle = curl_share_init();
57 curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
58 curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
61 ResourceHandleManager::~ResourceHandleManager()
63 curl_multi_cleanup(m_curlMultiHandle);
64 curl_share_cleanup(m_curlShareHandle);
65 if (m_cookieJarFileName)
66 free(m_cookieJarFileName);
69 void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName)
71 m_cookieJarFileName = strdup(cookieJarFileName);
74 ResourceHandleManager* ResourceHandleManager::sharedInstance()
76 static ResourceHandleManager* sharedInstance = 0;
78 sharedInstance = new ResourceHandleManager();
79 return sharedInstance;
82 // called with data after all headers have been processed via headerCallback
83 static size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* data)
85 ResourceHandle* job = static_cast<ResourceHandle*>(data);
86 ResourceHandleInternal* d = job->getInternal();
87 int totalSize = size * nmemb;
89 // this shouldn't be necessary but apparently is. CURL writes the data
90 // of html page even if it is a redirect that was handled internally
91 // can be observed e.g. on gmail.com
92 CURL* h = d->m_handle;
94 CURLcode err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
95 if (CURLE_OK == err && httpCode >= 300 && httpCode < 400)
98 // since the code in headerCallback will not have run for local files
99 // the code to set the URL and fire didReceiveResponse is never run,
100 // which means the ResourceLoader's response does not contain the URL.
101 // Run the code here for local files to resolve the issue.
102 // TODO: See if there is a better approach for handling this.
103 if (!d->m_response.responseFired()) {
105 err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr);
106 d->m_response.setUrl(KURL(hdr));
108 d->client()->didReceiveResponse(job, d->m_response);
109 d->m_response.setResponseFired(true);
113 d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0);
118 * This is being called for each HTTP header in the response. This includes '\r\n'
119 * for the last line of the header.
121 * We will add each HTTP Header to the ResourceResponse and on the termination
122 * of the header (\r\n) we will parse Content-Type and Content-Disposition and
123 * update the ResourceResponse and then send it away.
126 static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* data)
128 ResourceHandle* job = static_cast<ResourceHandle*>(data);
129 ResourceHandleInternal* d = job->getInternal();
131 unsigned int totalSize = size * nmemb;
132 ResourceHandleClient* client = d->client();
134 String header(static_cast<const char*>(ptr), totalSize);
137 * a) We can finish and send the ResourceResponse
138 * b) We will add the current header to the HTTPHeaderMap of the ResourceResponse
140 if (header == String("\r\n")) {
141 CURL* h = d->m_handle;
144 double contentLength = 0;
145 err = curl_easy_getinfo(h, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength);
146 d->m_response.setExpectedContentLength(static_cast<long long int>(contentLength));
149 err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr);
150 d->m_response.setUrl(KURL(hdr));
153 err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
154 d->m_response.setHTTPStatusCode(httpCode);
156 d->m_response.setMimeType(extractMIMETypeFromMediaType(d->m_response.httpHeaderField("Content-Type")));
157 d->m_response.setTextEncodingName(extractCharsetFromMediaType(d->m_response.httpHeaderField("Content-Type")));
158 d->m_response.setSuggestedFilename(filenameFromHTTPContentDisposition(d->m_response.httpHeaderField("Content-Disposition")));
161 if (httpCode >= 300 && httpCode < 400) {
162 String location = d->m_response.httpHeaderField("location");
163 if (!location.isEmpty()) {
164 KURL newURL = KURL(job->request().url(), location.deprecatedString());
166 ResourceRequest redirectedRequest = job->request();
167 redirectedRequest.setURL(newURL);
169 client->willSendRequest(job, redirectedRequest, d->m_response);
171 d->m_request.setURL(newURL);
178 client->didReceiveResponse(job, d->m_response);
179 d->m_response.setResponseFired(true);
182 int splitPos = header.find(":");
184 d->m_response.setHTTPHeaderField(header.left(splitPos), header.substring(splitPos+1).stripWhiteSpace());
190 void ResourceHandleManager::downloadTimerCallback(Timer<ResourceHandleManager>* timer)
192 startScheduledJobs();
201 curl_multi_fdset(m_curlMultiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
203 struct timeval timeout;
205 timeout.tv_usec = selectTimeoutMS * 1000; // select waits microseconds
207 // Temporarily disable timers since signals may interrupt select(), raising EINTR errors on some platforms
208 setDeferringTimers(true);
209 int rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
210 setDeferringTimers(false);
214 printf("bad: select() returned -1\n");
219 int runningHandles = 0;
220 while (curl_multi_perform(m_curlMultiHandle, &runningHandles) == CURLM_CALL_MULTI_PERFORM) { }
222 // check the curl messages indicating completed transfers
223 // and free their resources
226 CURLMsg* msg = curl_multi_info_read(m_curlMultiHandle, &messagesInQueue);
230 if (CURLMSG_DONE != msg->msg)
233 // find the node which has same d->m_handle as completed transfer
234 CURL* handle = msg->easy_handle;
236 ResourceHandle* job = 0;
237 CURLcode err = curl_easy_getinfo(handle, CURLINFO_PRIVATE, &job);
238 ASSERT(CURLE_OK == err);
242 ResourceHandleInternal* d = job->getInternal();
243 ASSERT(d->m_handle == handle);
244 if (CURLE_OK == msg->data.result) {
246 d->client()->didFinishLoading(job);
250 curl_easy_getinfo(d->m_handle, CURLINFO_EFFECTIVE_URL, &url);
251 printf("Curl ERROR for url='%s', error: '%s'\n", url, curl_easy_strerror(msg->data.result));
254 d->client()->didFail(job, ResourceError());
260 bool started = startScheduledJobs(); // new jobs might have been added in the meantime
262 if (!m_downloadTimer.isActive() && (started || (runningHandles > 0)))
263 m_downloadTimer.startOneShot(pollTimeSeconds);
266 void ResourceHandleManager::removeFromCurl(ResourceHandle* job)
268 ResourceHandleInternal* d = job->getInternal();
273 curl_multi_remove_handle(m_curlMultiHandle, d->m_handle);
274 curl_easy_cleanup(d->m_handle);
278 void ResourceHandleManager::setupPUT(ResourceHandle*)
283 void ResourceHandleManager::setupPOST(ResourceHandle* job)
285 ResourceHandleInternal* d = job->getInternal();
287 curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE);
289 job->request().httpBody()->flatten(d->m_postBytes);
290 if (d->m_postBytes.size() != 0) {
291 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, d->m_postBytes.size());
292 curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDS, d->m_postBytes.data());
295 Vector<FormDataElement> elements = job->request().httpBody()->elements();
296 size_t size = elements.size();
297 struct curl_httppost* lastItem = 0;
298 struct curl_httppost* post = 0;
299 for (size_t i = 0; i < size; i++) {
300 if (elements[i].m_type != FormDataElement::encodedFile)
302 CString cstring = elements[i].m_filename.utf8();
303 ASSERT(!d->m_fileName);
304 d->m_fileName = strdup(cstring.data());
306 // Fill in the file upload field
307 curl_formadd(&post, &lastItem, CURLFORM_COPYNAME, "sendfile", CURLFORM_FILE, d->m_fileName, CURLFORM_END);
309 // Fill in the filename field
310 curl_formadd(&post, &lastItem, CURLFORM_COPYNAME, "filename", CURLFORM_COPYCONTENTS, d->m_fileName, CURLFORM_END);
312 // FIXME: We should not add a "submit" field for each file uploaded. Review this code.
313 // Fill in the submit field too, even if this is rarely needed
314 curl_formadd(&post, &lastItem, CURLFORM_COPYNAME, "submit", CURLFORM_COPYCONTENTS, "send", CURLFORM_END);
316 // FIXME: should we support more than one file?
321 curl_easy_setopt(d->m_handle, CURLOPT_HTTPPOST, post);
324 void ResourceHandleManager::add(ResourceHandle* job)
326 // we can be called from within curl, so to avoid re-entrancy issues
327 // schedule this job to be added the next time we enter curl download loop
328 m_resourceHandleList.append(job);
329 if (!m_downloadTimer.isActive())
330 m_downloadTimer.startOneShot(pollTimeSeconds);
333 bool ResourceHandleManager::removeScheduledJob(ResourceHandle* job)
335 int size = m_resourceHandleList.size();
336 for (int i=0; i < size; i++) {
337 if (job == m_resourceHandleList[i]) {
338 m_resourceHandleList.remove(i);
345 bool ResourceHandleManager::startScheduledJobs()
347 // TODO: Create a separate stack of jobs for each domain.
349 bool started = false;
350 while (!m_resourceHandleList.isEmpty() && m_runningJobs < maxRunningJobs) {
351 ResourceHandle* job = m_resourceHandleList[0];
353 m_resourceHandleList.remove(0);
359 static void parseDataUrl(ResourceHandle* handle)
361 DeprecatedString data = handle->request().url().deprecatedString();
363 ASSERT(data.startsWith("data:", false));
365 DeprecatedString header;
368 int index = data.find(',');
370 header = data.mid(5, index - 5).lower();
371 data = data.mid(index + 1);
373 if (header.endsWith(";base64")) {
375 header = header.left(header.length() - 7);
378 data = DeprecatedString();
380 data = KURL::decode_string(data);
384 if (base64Decode(data.ascii(), data.length(), out))
385 data = DeprecatedString(out.data(), out.size());
387 data = DeprecatedString();
390 if (header.isEmpty())
391 header = "text/plain;charset=US-ASCII";
393 ResourceHandleClient* client = handle->getInternal()->client();
395 ResourceResponse response;
397 response.setMimeType(extractMIMETypeFromMediaType(header));
398 response.setTextEncodingName(extractCharsetFromMediaType(header));
399 response.setExpectedContentLength(data.length());
400 response.setHTTPStatusCode(200);
402 client->didReceiveResponse(handle, response);
405 client->didReceiveData(handle, data.ascii(), data.length(), 0);
407 client->didFinishLoading(handle);
410 void ResourceHandleManager::startJob(ResourceHandle* job)
412 KURL kurl = job->request().url();
413 String protocol = kurl.protocol();
415 if (equalIgnoringCase(protocol, "data")) {
420 ResourceHandleInternal* d = job->getInternal();
421 DeprecatedString url = kurl.deprecatedString();
423 if (kurl.isLocalFile()) {
424 DeprecatedString query = kurl.query();
425 // Remove any query part sent to a local file.
426 if (!query.isEmpty())
427 url = url.left(url.find(query));
428 // Determine the MIME type based on the path.
429 d->m_response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(String(url)));
432 d->m_handle = curl_easy_init();
434 if (getenv("DEBUG_CURL"))
435 curl_easy_setopt(d->m_handle, CURLOPT_VERBOSE, 1);
437 curl_easy_setopt(d->m_handle, CURLOPT_PRIVATE, job);
438 curl_easy_setopt(d->m_handle, CURLOPT_ERRORBUFFER, m_curlErrorBuffer);
439 curl_easy_setopt(d->m_handle, CURLOPT_WRITEFUNCTION, writeCallback);
440 curl_easy_setopt(d->m_handle, CURLOPT_WRITEDATA, job);
441 curl_easy_setopt(d->m_handle, CURLOPT_HEADERFUNCTION, headerCallback);
442 curl_easy_setopt(d->m_handle, CURLOPT_WRITEHEADER, job);
443 curl_easy_setopt(d->m_handle, CURLOPT_AUTOREFERER, 1);
444 curl_easy_setopt(d->m_handle, CURLOPT_FOLLOWLOCATION, 1);
445 curl_easy_setopt(d->m_handle, CURLOPT_MAXREDIRS, 10);
446 curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
447 curl_easy_setopt(d->m_handle, CURLOPT_SHARE, m_curlShareHandle);
448 curl_easy_setopt(d->m_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5); // 5 minutes
449 // enable gzip and deflate through Accept-Encoding:
450 curl_easy_setopt(d->m_handle, CURLOPT_ENCODING, "");
452 // url must remain valid through the request
454 d->m_url = strdup(url.ascii());
455 curl_easy_setopt(d->m_handle, CURLOPT_URL, d->m_url);
457 if (m_cookieJarFileName) {
458 curl_easy_setopt(d->m_handle, CURLOPT_COOKIEFILE, m_cookieJarFileName);
459 curl_easy_setopt(d->m_handle, CURLOPT_COOKIEJAR, m_cookieJarFileName);
462 if (job->request().httpHeaderFields().size() > 0) {
463 struct curl_slist* headers = 0;
464 HTTPHeaderMap customHeaders = job->request().httpHeaderFields();
465 HTTPHeaderMap::const_iterator end = customHeaders.end();
466 for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) {
467 String key = it->first;
468 String value = it->second;
469 String headerString(key);
470 headerString.append(": ");
471 headerString.append(value);
472 CString headerLatin1 = headerString.latin1();
473 headers = curl_slist_append(headers, headerLatin1.data());
475 curl_easy_setopt(d->m_handle, CURLOPT_HTTPHEADER, headers);
476 d->m_customHeaders = headers;
479 if ("GET" == job->request().httpMethod())
480 curl_easy_setopt(d->m_handle, CURLOPT_HTTPGET, TRUE);
481 else if ("POST" == job->request().httpMethod())
483 else if ("PUT" == job->request().httpMethod())
485 else if ("HEAD" == job->request().httpMethod())
486 curl_easy_setopt(d->m_handle, CURLOPT_NOBODY, TRUE);
489 CURLMcode ret = curl_multi_add_handle(m_curlMultiHandle, d->m_handle);
490 // don't call perform, because events must be async
491 // timeout will occur and do curl_multi_perform
492 if (ret && ret != CURLM_CALL_MULTI_PERFORM) {
494 printf("Error %d starting job %s\n", ret, job->request().url().deprecatedString().ascii());
501 void ResourceHandleManager::cancel(ResourceHandle* job)
503 if (removeScheduledJob(job))
508 } // namespace WebCore