ResourceHandleManager.cpp [plain text]
#include "config.h"
#include "ResourceHandleManager.h"
#include "CString.h"
#include "NotImplemented.h"
#include "ResourceHandle.h"
#include "ResourceHandleInternal.h"
#include "HTTPParsers.h"
#include <wtf/Vector.h>
namespace WebCore {
const int selectTimeoutMS = 5;
const double pollTimeSeconds = 0.05;
ResourceHandleManager::ResourceHandleManager()
: m_downloadTimer(this, &ResourceHandleManager::downloadTimerCallback)
, m_cookieJarFileName(0)
, m_resourceHandleListHead(0)
{
curl_global_init(CURL_GLOBAL_ALL);
m_curlMultiHandle = curl_multi_init();
m_curlShareHandle = curl_share_init();
curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
curl_share_setopt(m_curlShareHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
}
ResourceHandleManager::~ResourceHandleManager()
{
curl_multi_cleanup(m_curlMultiHandle);
curl_share_cleanup(m_curlShareHandle);
if (m_cookieJarFileName)
free(m_cookieJarFileName);
}
void ResourceHandleManager::setCookieJarFileName(const char* cookieJarFileName)
{
m_cookieJarFileName = strdup(cookieJarFileName);
}
ResourceHandleManager* ResourceHandleManager::sharedInstance()
{
static ResourceHandleManager* sharedInstance = 0;
if (!sharedInstance)
sharedInstance = new ResourceHandleManager();
return sharedInstance;
}
static size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* data)
{
ResourceHandle* job = static_cast<ResourceHandle*>(data);
ResourceHandleInternal* d = job->getInternal();
int totalSize = size * nmemb;
CURL* h = d->m_handle;
long httpCode = 0;
CURLcode err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
if (CURLE_OK == err && httpCode >= 300 && httpCode < 400)
return totalSize;
if (d->client())
d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0);
return totalSize;
}
static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* data)
{
ResourceHandle* job = static_cast<ResourceHandle*>(data);
ResourceHandleInternal* d = job->getInternal();
unsigned int totalSize = size * nmemb;
ResourceHandleClient* client = d->client();
if (!client) {
return totalSize;
}
String header(static_cast<const char*>(ptr), totalSize);
if (header == String("\r\n")) {
CURL* h = d->m_handle;
CURLcode err;
double contentLength = 0;
err = curl_easy_getinfo(h, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength);
d->m_response.setExpectedContentLength(static_cast<long long int>(contentLength));
const char* hdr;
err = curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &hdr);
d->m_response.setUrl(KURL(hdr));
long httpCode = 0;
err = curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &httpCode);
d->m_response.setHTTPStatusCode(httpCode);
d->m_response.setMimeType(extractMIMETypeFromMediaType(d->m_response.httpHeaderField("Content-Type")));
d->m_response.setTextEncodingName(extractCharsetFromMediaType(d->m_response.httpHeaderField("Content-Type")));
d->m_response.setSuggestedFilename(filenameFromHTTPContentDisposition(d->m_response.httpHeaderField("Content-Disposition")));
client->didReceiveResponse(job, d->m_response);
} else {
int splitPos = header.find(":");
if (splitPos != -1)
d->m_response.setHTTPHeaderField(header.left(splitPos), header.substring(splitPos+1).stripWhiteSpace());
}
return totalSize;
}
void ResourceHandleManager::downloadTimerCallback(Timer<ResourceHandleManager>* timer)
{
startScheduledJobs();
fd_set fdread;
FD_ZERO(&fdread);
fd_set fdwrite;
FD_ZERO(&fdwrite);
fd_set fdexcep;
FD_ZERO(&fdexcep);
int maxfd = 0;
curl_multi_fdset(m_curlMultiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = selectTimeoutMS * 1000;
int rc = ::select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
if (-1 == rc) {
#ifndef NDEBUG
printf("bad: select() returned -1\n");
#endif
return;
}
int runningHandles = 0;
CURLMcode curlCode = CURLM_CALL_MULTI_PERFORM;
while (CURLM_CALL_MULTI_PERFORM == curlCode) {
curlCode = curl_multi_perform(m_curlMultiHandle, &runningHandles);
}
while (true) {
int messagesInQueue;
CURLMsg* msg = curl_multi_info_read(m_curlMultiHandle, &messagesInQueue);
if (!msg)
break;
if (CURLMSG_DONE != msg->msg)
continue;
CURL* handle = msg->easy_handle;
ASSERT(handle);
ResourceHandle* job = 0;
CURLcode err = curl_easy_getinfo(handle, CURLINFO_PRIVATE, &job);
ASSERT(CURLE_OK == err);
ASSERT(job);
if (!job)
continue;
ResourceHandleInternal* d = job->getInternal();
ASSERT(d->m_handle == handle);
if (CURLE_OK == msg->data.result) {
if (d->client())
d->client()->didFinishLoading(job);
} else {
#ifndef NDEBUG
char* url = 0;
curl_easy_getinfo(d->m_handle, CURLINFO_EFFECTIVE_URL, &url);
printf("Curl ERROR for url='%s', error: '%s'\n", url, curl_easy_strerror(msg->data.result));
#endif
if (d->client())
d->client()->didFail(job, ResourceError());
}
removeFromCurl(job);
}
bool started = startScheduledJobs();
if (!m_downloadTimer.isActive() && (started || (runningHandles > 0)))
m_downloadTimer.startOneShot(pollTimeSeconds);
}
void ResourceHandleManager::removeFromCurl(ResourceHandle* job)
{
ResourceHandleInternal* d = job->getInternal();
ASSERT(d->m_handle);
if (!d->m_handle)
return;
curl_multi_remove_handle(m_curlMultiHandle, d->m_handle);
curl_easy_cleanup(d->m_handle);
d->m_handle = 0;
}
void ResourceHandleManager::setupPUT(ResourceHandle*)
{
notImplemented();
}
void ResourceHandleManager::setupPOST(ResourceHandle* job)
{
ResourceHandleInternal* d = job->getInternal();
curl_easy_setopt(d->m_handle, CURLOPT_POST, TRUE);
job->request().httpBody()->flatten(d->m_postBytes);
if (d->m_postBytes.size() != 0) {
curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDSIZE, d->m_postBytes.size());
curl_easy_setopt(d->m_handle, CURLOPT_POSTFIELDS, d->m_postBytes.data());
}
Vector<FormDataElement> elements = job->request().httpBody()->elements();
size_t size = elements.size();
struct curl_httppost* lastItem = 0;
struct curl_httppost* post = 0;
for (size_t i = 0; i < size; i++) {
if (elements[i].m_type != FormDataElement::encodedFile)
continue;
CString cstring = elements[i].m_filename.utf8();
ASSERT(!d->m_fileName);
d->m_fileName = strdup(cstring.data());
curl_formadd(&post, &lastItem, CURLFORM_COPYNAME, "sendfile", CURLFORM_FILE, d->m_fileName, CURLFORM_END);
curl_formadd(&post, &lastItem, CURLFORM_COPYNAME, "filename", CURLFORM_COPYCONTENTS, d->m_fileName, CURLFORM_END);
curl_formadd(&post, &lastItem, CURLFORM_COPYNAME, "submit", CURLFORM_COPYCONTENTS, "send", CURLFORM_END);
break;
}
if (post)
curl_easy_setopt(d->m_handle, CURLOPT_HTTPPOST, post);
}
void ResourceHandleManager::add(ResourceHandle* job)
{
m_resourceHandleListHead = new ResourceHandleList(job, m_resourceHandleListHead);
if (!m_downloadTimer.isActive())
m_downloadTimer.startOneShot(pollTimeSeconds);
}
bool ResourceHandleManager::removeScheduledJob(ResourceHandle* job)
{
ResourceHandleList* node = m_resourceHandleListHead;
while (node) {
ResourceHandleList* next = node->next();
if (job == node->job()) {
node->setRemoved(true);
return true;
}
node = next;
}
return false;
}
bool ResourceHandleManager::startScheduledJobs()
{
bool started = false;
ResourceHandleList* node = m_resourceHandleListHead;
while (node) {
ResourceHandleList* next = node->next();
if (!node->removed()) {
startJob(node->job());
started = true;
}
delete node;
node = next;
}
m_resourceHandleListHead = 0;
return started;
}
void ResourceHandleManager::startJob(ResourceHandle* job)
{
ResourceHandleInternal* d = job->getInternal();
DeprecatedString url = job->request().url().url();
d->m_handle = curl_easy_init();
#ifndef NDEBUG
if (getenv("DEBUG_CURL"))
curl_easy_setopt(d->m_handle, CURLOPT_VERBOSE, 1);
#endif
curl_easy_setopt(d->m_handle, CURLOPT_PRIVATE, job);
curl_easy_setopt(d->m_handle, CURLOPT_ERRORBUFFER, m_curlErrorBuffer);
curl_easy_setopt(d->m_handle, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(d->m_handle, CURLOPT_WRITEDATA, job);
curl_easy_setopt(d->m_handle, CURLOPT_HEADERFUNCTION, headerCallback);
curl_easy_setopt(d->m_handle, CURLOPT_WRITEHEADER, job);
curl_easy_setopt(d->m_handle, CURLOPT_AUTOREFERER, 1);
curl_easy_setopt(d->m_handle, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(d->m_handle, CURLOPT_MAXREDIRS, 10);
curl_easy_setopt(d->m_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_easy_setopt(d->m_handle, CURLOPT_SHARE, m_curlShareHandle);
curl_easy_setopt(d->m_handle, CURLOPT_DNS_CACHE_TIMEOUT, 60 * 5); curl_easy_setopt(d->m_handle, CURLOPT_ENCODING, "");
ASSERT(!d->m_url);
d->m_url = strdup(url.ascii());
curl_easy_setopt(d->m_handle, CURLOPT_URL, d->m_url);
if (m_cookieJarFileName) {
curl_easy_setopt(d->m_handle, CURLOPT_COOKIEFILE, m_cookieJarFileName);
curl_easy_setopt(d->m_handle, CURLOPT_COOKIEJAR, m_cookieJarFileName);
}
if (job->request().httpHeaderFields().size() > 0) {
struct curl_slist* headers = 0;
HTTPHeaderMap customHeaders = job->request().httpHeaderFields();
HTTPHeaderMap::const_iterator end = customHeaders.end();
for (HTTPHeaderMap::const_iterator it = customHeaders.begin(); it != end; ++it) {
String key = it->first;
String value = it->second;
String headerString(key);
headerString.append(": ");
headerString.append(value);
CString headerLatin1 = headerString.latin1();
headers = curl_slist_append(headers, headerLatin1.data());
}
curl_easy_setopt(d->m_handle, CURLOPT_HTTPHEADER, headers);
d->m_customHeaders = headers;
}
if ("GET" == job->request().httpMethod())
curl_easy_setopt(d->m_handle, CURLOPT_HTTPGET, TRUE);
else if ("POST" == job->request().httpMethod())
setupPOST(job);
else if ("PUT" == job->request().httpMethod())
setupPUT(job);
else if ("HEAD" == job->request().httpMethod())
curl_easy_setopt(d->m_handle, CURLOPT_NOBODY, TRUE);
CURLMcode ret = curl_multi_add_handle(m_curlMultiHandle, d->m_handle);
if (ret && ret != CURLM_CALL_MULTI_PERFORM) {
#ifndef NDEBUG
printf("Error %d starting job %s\n", ret, job->request().url().url().ascii());
#endif
job->cancel();
return;
}
}
void ResourceHandleManager::cancel(ResourceHandle* job)
{
if (removeScheduledJob(job))
return;
removeFromCurl(job);
}
}