"Fossies" - the Fresh Open Source Software Archive

Member "google-gadgets-for-linux-0.11.2/extensions/curl_xml_http_request/curl_xml_http_request.cc" (28 Dec 2009, 38834 Bytes) of package /linux/misc/old/google-gadgets-for-linux-0.11.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 /*
    2   Copyright 2008 Google Inc.
    3 
    4   Licensed under the Apache License, Version 2.0 (the "License");
    5   you may not use this file except in compliance with the License.
    6   You may obtain a copy of the License at
    7 
    8        http://www.apache.org/licenses/LICENSE-2.0
    9 
   10   Unless required by applicable law or agreed to in writing, software
   11   distributed under the License is distributed on an "AS IS" BASIS,
   12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13   See the License for the specific language governing permissions and
   14   limitations under the License.
   15 */
   16 
   17 #include <algorithm>
   18 #include <cstring>
   19 #include <curl/curl.h>
   20 #include <pthread.h>
   21 #include <vector>
   22 
   23 #include <ggadget/gadget_consts.h>
   24 #include <ggadget/main_loop_interface.h>
   25 #include <ggadget/logger.h>
   26 #include <ggadget/scriptable_binary_data.h>
   27 #include <ggadget/script_context_interface.h>
   28 #include <ggadget/scriptable_helper.h>
   29 #include <ggadget/signals.h>
   30 #include <ggadget/string_utils.h>
   31 #include <ggadget/xml_http_request_interface.h>
   32 #include <ggadget/xml_http_request_utils.h>
   33 #include <ggadget/xml_dom_interface.h>
   34 #include <ggadget/xml_parser_interface.h>
   35 
   36 namespace ggadget {
   37 namespace curl {
   38 
   39 static const long kMaxRedirections = 10;
   40 static const long kConnectTimeoutSec = 20;
   41 
   42 static const Variant kOpenDefaultArgs[] = {
   43   Variant(), Variant(),
   44   Variant(true),
   45   Variant(static_cast<const char *>(NULL)),
   46   Variant(static_cast<const char *>(NULL))
   47 };
   48 
   49 static const Variant kSendDefaultArgs[] = { Variant("") };
   50 
   51 #if 0 // Don't support newlines in header values.
   52 // Process a user inputed header value, add a space after newline.
   53 static std::string ReformatHttpHeaderValue(const char *value) {
   54   std::string tmp;
   55   if (value == NULL) return tmp;
   56   int n_crlf = 0;
   57   while (*value) {
   58     if (*value == '\r' || *value == '\n') {
   59       ++n_crlf;
   60     } else {
   61       if (n_crlf > 0) {
   62         // We have meeted some newline char(s). Both '\r' and '\n' are
   63         // recognised as newline chars, and continued newline chars
   64         // are taken as one. According the rfc2616, the server MAY
   65         // replace / *\r\n +/ to " ", so this will work fine.
   66         tmp += "\r\n ";
   67         n_crlf = 0;
   68       }
   69       tmp.push_back(*value);
   70     }
   71     ++value;
   72   }
   73   return tmp;
   74 }
   75 #else
   76 static const char *ReformatHttpHeaderValue(const char *value) {
   77   return value;
   78 }
   79 #endif
   80 
   81 class XMLHttpRequest : public ScriptableHelper<XMLHttpRequestInterface> {
   82  public:
   83   DEFINE_CLASS_ID(0xda25f528f28a4319, XMLHttpRequestInterface);
   84 
   85   XMLHttpRequest(CURLSH *share, MainLoopInterface *main_loop,
   86                  XMLParserInterface *xml_parser,
   87                  const std::string &default_user_agent)
   88       : curl_(NULL),
   89         share_(share),
   90         main_loop_(main_loop),
   91         xml_parser_(xml_parser),
   92         response_dom_(NULL),
   93         default_user_agent_(default_user_agent),
   94         status_(0),
   95         state_(UNSENT),
   96         method_(HTTP_GET),
   97         async_(false),
   98         send_flag_(false),
   99         succeeded_(false) {
  100     VERIFY_M(EnsureXHRBackoffOptions(main_loop->GetCurrentTime()),
  101              ("Required options module have not been loaded"));
  102     pthread_attr_init(&thread_attr_);
  103     pthread_attr_setdetachstate(&thread_attr_, PTHREAD_CREATE_DETACHED);
  104   }
  105 
  106   virtual void DoClassRegister() {
  107     RegisterClassSignal("onreadystatechange",
  108                         &XMLHttpRequest::onreadystatechange_signal_);
  109     RegisterProperty("readyState",
  110                      NewSlot(&XMLHttpRequest::GetReadyState), NULL);
  111     RegisterMethod("open",
  112         NewSlotWithDefaultArgs(NewSlot(&XMLHttpRequest::ScriptOpen),
  113                                kOpenDefaultArgs));
  114     RegisterMethod("setRequestHeader",
  115                    NewSlot(&XMLHttpRequest::ScriptSetRequestHeader));
  116     RegisterMethod("send",
  117         NewSlotWithDefaultArgs(NewSlot(&XMLHttpRequest::ScriptSend),
  118                                kSendDefaultArgs));
  119     RegisterMethod("abort", NewSlot(&XMLHttpRequest::Abort));
  120     RegisterMethod("getAllResponseHeaders",
  121                    NewSlot(&XMLHttpRequest::ScriptGetAllResponseHeaders));
  122     RegisterMethod("getResponseHeader",
  123                    NewSlot(&XMLHttpRequest::ScriptGetResponseHeader));
  124     RegisterProperty("responseStream",
  125                      NewSlot(&XMLHttpRequest::ScriptGetResponseBody),
  126                      NULL);
  127     RegisterProperty("responseBody",
  128                      NewSlot(&XMLHttpRequest::ScriptGetResponseBody),
  129                      NULL);
  130     RegisterProperty("responseText",
  131                      NewSlot(&XMLHttpRequest::ScriptGetResponseText),
  132                      NULL);
  133     RegisterProperty("responseXML",
  134                      NewSlot(&XMLHttpRequest::ScriptGetResponseXML),
  135                      NULL);
  136     RegisterProperty("status", NewSlot(&XMLHttpRequest::ScriptGetStatus),
  137                      NULL);
  138     RegisterProperty("statusText",
  139                      NewSlot(&XMLHttpRequest::ScriptGetStatusText), NULL);
  140   }
  141 
  142   ~XMLHttpRequest() {
  143     Abort();
  144     pthread_attr_destroy(&thread_attr_);
  145   }
  146 
  147   virtual Connection *ConnectOnReadyStateChange(Slot0<void> *handler) {
  148     return onreadystatechange_signal_.Connect(handler);
  149   }
  150 
  151   virtual State GetReadyState() {
  152     return state_;
  153   }
  154 
  155   bool ChangeState(State new_state) {
  156     DLOG("XMLHttpRequest: ChangeState from %d to %d this=%p",
  157          state_, new_state, this);
  158     state_ = new_state;
  159     onreadystatechange_signal_();
  160     // ChangeState may re-entered during the signal, so the current state_
  161     // may be different from the input parameter.
  162     return state_ == new_state;
  163   }
  164 
  165   // The maximum data size of this class can process.
  166   static const size_t kMaxDataSize = 8 * 1024 * 1024;
  167 
  168   static bool CheckSize(size_t current, size_t num_blocks, size_t block_size) {
  169     return current < kMaxDataSize && block_size > 0 &&
  170            (kMaxDataSize - current) / block_size > num_blocks;
  171   }
  172 
  173   virtual ExceptionCode Open(const char *method, const char *url, bool async,
  174                              const char *user, const char *password) {
  175     Abort();
  176     if (!method || !url)
  177       return NULL_POINTER_ERR;
  178 
  179     bool is_https = false;
  180     if (0 != strncasecmp(url, kHttpUrlPrefix, arraysize(kHttpUrlPrefix) - 1)) {
  181       if (0 != strncasecmp(url, kHttpsUrlPrefix,
  182                            arraysize(kHttpsUrlPrefix) - 1)) {
  183         return SYNTAX_ERR;
  184       } else {
  185         is_https = true;
  186       }
  187     }
  188 
  189     if (!GetUsernamePasswordFromURL(url).empty()) {
  190       // GDWin Compatibility.
  191       DLOG("Username:password in URL is not allowed: %s", url);
  192       return SYNTAX_ERR;
  193     }
  194 
  195     url_ = url;
  196     host_ = GetHostFromURL(url);
  197     curl_ = curl_easy_init();
  198     if (!curl_) {
  199       DLOG("XMLHttpRequest: curl_easy_init failed");
  200       // TODO: Send errors.
  201       return OTHER_ERR;
  202     }
  203 
  204     if (is_https) {
  205       curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 1);
  206       curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYHOST, 2);
  207       // Older versions of libcurl's ca bundle file is also very old, so add
  208       // OpenSSL's cert directory. Only for Linux and libcurl-openssl config.
  209       curl_easy_setopt(curl_, CURLOPT_CAPATH, "/etc/ssl/certs");
  210     }
  211 
  212     if (!default_user_agent_.empty())
  213       curl_easy_setopt(curl_, CURLOPT_USERAGENT, default_user_agent_.c_str());
  214 
  215     // Disable curl using signals because we use curl in multiple threads.
  216     curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1);
  217     if (share_)
  218       curl_easy_setopt(curl_, CURLOPT_SHARE, share_);
  219     // Enable cookies, but don't write them into any file.
  220     curl_easy_setopt(curl_, CURLOPT_COOKIEFILE, "");
  221 
  222     if (strcasecmp(method, "HEAD") == 0) {
  223       curl_easy_setopt(curl_, CURLOPT_HTTPGET, 1);
  224       curl_easy_setopt(curl_, CURLOPT_NOBODY, 1);
  225       method_ = HTTP_HEAD;
  226     } else if (strcasecmp(method, "GET") == 0) {
  227       curl_easy_setopt(curl_, CURLOPT_HTTPGET, 1);
  228       method_ = HTTP_GET;
  229     } else if (strcasecmp(method, "POST") == 0) {
  230       curl_easy_setopt(curl_, CURLOPT_POST, 1);
  231       method_ = HTTP_POST;
  232     } else if (strcasecmp(method, "PUT") == 0) {
  233       curl_easy_setopt(curl_, CURLOPT_UPLOAD, 1);
  234       method_ = HTTP_PUT;
  235     } else {
  236       LOG("XMLHttpRequest: Unsupported method: %s", method);
  237       return SYNTAX_ERR;
  238     }
  239     curl_easy_setopt(curl_, CURLOPT_URL, url_.c_str());
  240 
  241     if (user || password) {
  242       std::string user_pwd;
  243       if (user)
  244         user_pwd = user;
  245       user_pwd += ':';
  246       if (password)
  247         user_pwd += password;
  248       curl_easy_setopt(curl_, CURLOPT_USERPWD, user_pwd.c_str());
  249     }
  250 
  251     // Disable the default "Expect: 100-continue" request header.
  252     request_headers_map_["Expect"] = "";
  253 
  254     async_ = async;
  255     ChangeState(OPENED);
  256     return NO_ERR;
  257   }
  258 
  259   virtual ExceptionCode SetRequestHeader(const char *header,
  260                                          const char *value) {
  261     if (state_ != OPENED || send_flag_) {
  262       LOG("XMLHttpRequest: SetRequestHeader: Invalid state: %d", state_);
  263       return INVALID_STATE_ERR;
  264     }
  265 
  266     if (!IsValidHTTPToken(header)) {
  267       LOG("XMLHttpRequest::SetRequestHeader: Invalid header %s", header);
  268       return SYNTAX_ERR;
  269     }
  270 
  271     if (!IsValidHTTPHeaderValue(value)) {
  272       LOG("XMLHttpRequest::SetRequestHeader: Invalid value: %s", value);
  273       return SYNTAX_ERR;
  274     }
  275 
  276     if (IsForbiddenHeader(header)) {
  277       DLOG("XMLHttpRequest::SetRequestHeader: Forbidden header %s", header);
  278       return NO_ERR;
  279     }
  280 
  281     // strcasecmp shall be used, but it'll break gmail gadget.
  282     // Microsoft XHR is also case sensitive.
  283     if (strcmp(header, "Cookie") == 0 &&
  284         value && strcasecmp(value, "none") == 0) {
  285       // Microsoft XHR hidden feature: setRequestHeader('Cookie', 'none')
  286       // clears all cookies. Some gadgets (e.g. reader) use this.
  287       curl_easy_setopt(curl_, CURLOPT_COOKIELIST, "ALL");
  288       return NO_ERR;
  289     }
  290 
  291     std::string header_str(header);
  292     CaseInsensitiveStringMap::iterator it =
  293         request_headers_map_.find(header_str);
  294     if (it != request_headers_map_.end()) {
  295       if (IsUniqueHeader(header)) {
  296         it->second = ReformatHttpHeaderValue(value);
  297       } else {
  298         if (it->second.length())
  299           it->second += ", ";
  300         it->second += ReformatHttpHeaderValue(value);
  301       }
  302     } else {
  303       request_headers_map_[header_str] = ReformatHttpHeaderValue(value);
  304     }
  305 
  306     return NO_ERR;
  307   }
  308 
  309   struct WorkerContext {
  310     WorkerContext(XMLHttpRequest *a_this_p, CURL *a_curl, bool a_async,
  311                   curl_slist *a_request_headers,
  312                   const std::string &a_request_data)
  313         : this_p(a_this_p), curl(a_curl),
  314           request_headers(a_request_headers),
  315           request_data(a_request_data),
  316           request_offset(0), async(a_async) {
  317     }
  318     XMLHttpRequest *this_p;
  319     CURL *curl;
  320     curl_slist *request_headers;
  321     std::string request_data;
  322     size_t request_offset;
  323     bool async;
  324   };
  325 
  326   curl_slist *AssembleRequestHeaders() {
  327     CaseInsensitiveStringMap::iterator it = request_headers_map_.begin();
  328     CaseInsensitiveStringMap::iterator end = request_headers_map_.end();
  329     curl_slist *curl_headers = NULL;
  330     for (; it != end; ++it) {
  331       std::string whole_header = it->first + ": " + it->second;
  332       curl_headers = curl_slist_append(curl_headers, whole_header.c_str());
  333     }
  334     return curl_headers;
  335   }
  336 
  337   virtual ExceptionCode Send(const std::string &data) {
  338     if (state_ != OPENED || send_flag_) {
  339       LOG("XMLHttpRequest: Send: Invalid state: %d", state_);
  340       return INVALID_STATE_ERR;
  341     }
  342 
  343     if (!CheckSize(data.size(), 0, 512)) {
  344       LOG("XMLHttpRequest: Send: Size too big: %zu", data.size());
  345       return SYNTAX_ERR;
  346     }
  347 
  348     // As described in the spec, here don't change the state, but send
  349     // an event for historical reasons.
  350     if (!ChangeState(OPENED))
  351       return INVALID_STATE_ERR;
  352 
  353     // Do backoff checking to avoid DDOS attack to the server.
  354     if (!IsXHRBackoffRequestOK(main_loop_->GetCurrentTime(),
  355                                host_.c_str())) {
  356       Abort();
  357       if (async_) {
  358         // Don't raise exception here because async callers might not expect
  359         // this kind of exception.
  360         ChangeState(DONE);
  361         return NO_ERR;
  362       }
  363       return ABORT_ERR;
  364     }
  365 
  366     curl_slist *request_headers = AssembleRequestHeaders();
  367     request_headers_map_.clear();
  368 
  369     WorkerContext *context = new WorkerContext(this, curl_, async_,
  370                                                request_headers, data);
  371     if (data.size()) {
  372       DLOG("Send: data length: %zu, method: %d", data.size(), method_);
  373       if (method_ == HTTP_POST) {
  374         // CURLOPT_POSTFIELDSIZE_LARGE doesn't work on 32bit curl.
  375         curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE,
  376                          static_cast<long>(data.size()));
  377         // CURLOPT_COPYPOSTFIELDS is better, but requires libcurl version 7.17.
  378         curl_easy_setopt(curl_, CURLOPT_POSTFIELDS,
  379                          context->request_data.c_str());
  380       } else if (method_ == HTTP_PUT) {
  381         curl_easy_setopt(curl_, CURLOPT_READFUNCTION, ReadCallback);
  382         curl_easy_setopt(curl_, CURLOPT_READDATA, context);
  383         // CURLOPT_INFILESIZE_LARGE doesn't work on 32bit curl.
  384         curl_easy_setopt(curl_, CURLOPT_INFILESIZE,
  385                          static_cast<long>(data.size()));
  386       }
  387     }
  388 
  389   #ifdef _DEBUG
  390     curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1);
  391   #endif
  392     curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, context->request_headers);
  393     curl_easy_setopt(curl_, CURLOPT_FRESH_CONNECT, 1);
  394     curl_easy_setopt(curl_, CURLOPT_FORBID_REUSE, 1);
  395     curl_easy_setopt(curl_, CURLOPT_AUTOREFERER, 1);
  396     curl_easy_setopt(curl_, CURLOPT_FOLLOWLOCATION, 1);
  397     curl_easy_setopt(curl_, CURLOPT_MAXREDIRS, kMaxRedirections);
  398     curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, kConnectTimeoutSec);
  399 
  400     curl_easy_setopt(curl_, CURLOPT_HEADERFUNCTION, WriteHeaderCallback);
  401     curl_easy_setopt(curl_, CURLOPT_HEADERDATA, context);
  402     curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, WriteBodyCallback);
  403     curl_easy_setopt(curl_, CURLOPT_WRITEDATA, context);
  404 
  405     if (async_) {
  406       // Add an internal reference when this request is working to prevent
  407       // this object from being GC'ed during the request.
  408       Ref();
  409       send_flag_ = true;
  410       pthread_t thread;
  411       if (pthread_create(&thread, &thread_attr_, Worker, context) != 0) {
  412         DLOG("Failed to create worker thread");
  413         Unref();
  414         send_flag_ = false;
  415         Abort();
  416         if (context->request_headers) {
  417           curl_slist_free_all(context->request_headers);
  418           context->request_headers = NULL;
  419         }
  420         delete context;
  421         return ABORT_ERR;
  422       }
  423     } else {
  424       send_flag_ = true;
  425       // Run the worker directly in this thread.
  426       // Returns NULL means failed.
  427       void *result = Worker(context);
  428       send_flag_ = false;
  429       if (!result)
  430         return NETWORK_ERR;
  431     }
  432     return NO_ERR;
  433   }
  434 
  435   virtual ExceptionCode Send(const DOMDocumentInterface *data) {
  436     if (request_headers_map_.find("Content-Type") ==
  437         request_headers_map_.end()) {
  438       // Set content type if it's not set yet.
  439       request_headers_map_["Content-Type"] = "application/xml;charset=UTF-8";
  440     }
  441     return Send(data ? data->GetXML() : std::string());
  442   }
  443 
  444   static void GetStatusAndEffectiveUrl(CURL *curl, unsigned short *status,
  445                                        std::string *effective_url) {
  446     long curl_status = 0;
  447     curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &curl_status);
  448     *status = static_cast<unsigned short>(curl_status);
  449     char *url_ptr = NULL;
  450     curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url_ptr);
  451     *effective_url = url_ptr ? url_ptr : "";
  452   }
  453 
  454   // If async, this method runs in a separate thread.
  455   static void *Worker(void *arg) {
  456     WorkerContext *context = static_cast<WorkerContext *>(arg);
  457 
  458     CURLcode code = curl_easy_perform(context->curl);
  459 
  460     unsigned short status = 0;
  461     std::string effective_url;
  462     GetStatusAndEffectiveUrl(context->curl, &status, &effective_url);
  463 
  464     if (context->request_headers) {
  465       curl_slist_free_all(context->request_headers);
  466       context->request_headers = NULL;
  467     }
  468 
  469     if (code != CURLE_OK) {
  470       DLOG("XMLHttpRequest: Send: curl_easy_perform failed: %s",
  471            curl_easy_strerror(code));
  472     }
  473 
  474     WorkerDone(status, effective_url, context, code == CURLE_OK);
  475     delete context;
  476 
  477     // Returns something that isn't NULL when success.
  478     return code == CURLE_OK ? arg : NULL;
  479   }
  480 
  481   static size_t ReadCallback(void *ptr, size_t size, size_t mem_block,
  482                              void *user_p) {
  483     size_t data_size = size * mem_block;
  484     WorkerContext *context = static_cast<WorkerContext *>(user_p);
  485     ASSERT(context->request_data.size() >= context->request_offset);
  486     size_t bytes_left = context->request_data.size() - context->request_offset;
  487     DLOG("XMLHttpRequest: ReadCallback: %zu*%zu this=%p left=%zu",
  488          size, mem_block, context->this_p, bytes_left);
  489     if (bytes_left == 0)
  490       return 0;
  491 
  492     if (context->async &&
  493         context->this_p->curl_ != context->curl) {
  494       // The current XMLHttpRequest has been aborted, so abort the
  495       // curl request.
  496       return CURL_READFUNC_ABORT;
  497     }
  498 
  499     data_size = std::min(data_size, context->request_data.size() -
  500                                     context->request_offset);
  501     memcpy(ptr, context->request_data.c_str() + context->request_offset,
  502            data_size);
  503     context->request_offset += data_size;
  504     return data_size;
  505   }
  506 
  507   // Passes the WriteHeader() request from worker thread to the main thread.
  508   class WriteHeaderTask : public WatchCallbackInterface {
  509    public:
  510     WriteHeaderTask(const void *ptr, size_t size,
  511                     const WorkerContext *worker_context)
  512         : data_(static_cast<const char *>(ptr), size),
  513           worker_context_(*worker_context) {
  514     }
  515     virtual bool Call(MainLoopInterface *main_loop, int watch_id) {
  516       GGL_UNUSED(main_loop);
  517       GGL_UNUSED(watch_id);
  518       if (worker_context_.this_p->curl_ == worker_context_.curl &&
  519           worker_context_.this_p->WriteHeader(data_) != data_.size()) {
  520         // WriteHeader() failed. Terminate the request.
  521         worker_context_.this_p->Done(false, false);
  522       }
  523       return false;
  524     }
  525     virtual void OnRemove(MainLoopInterface *main_loop, int watch_id) {
  526       GGL_UNUSED(main_loop);
  527       GGL_UNUSED(watch_id);
  528       delete this;
  529     }
  530 
  531     std::string data_;
  532     WorkerContext worker_context_;
  533   };
  534 
  535   // Passes the WriteBody() request from worker thread to the main thread.
  536   class WriteBodyTask : public WriteHeaderTask {
  537    public:
  538     WriteBodyTask(const void *ptr, size_t size,
  539                   unsigned short status, const std::string &effective_url,
  540                   const WorkerContext *worker_context)
  541         : WriteHeaderTask(ptr, size, worker_context),
  542           effective_url_(effective_url),
  543           status_(status) {
  544     }
  545     virtual bool Call(MainLoopInterface *main_loop, int watch_id) {
  546       GGL_UNUSED(main_loop);
  547       GGL_UNUSED(watch_id);
  548       if (worker_context_.this_p->curl_ == worker_context_.curl &&
  549         worker_context_.this_p->WriteBody(data_, status_, effective_url_) !=
  550             data_.size()) {
  551         // WriteBody() failed. Terminate the request.
  552         worker_context_.this_p->Done(false, false);
  553       }
  554       return false;
  555     }
  556 
  557     std::string effective_url_;
  558     unsigned short status_;
  559   };
  560 
  561   // Passes the Done() request from worker thread to the main thread.
  562   class DoneTask : public WriteBodyTask {
  563    public:
  564     DoneTask(unsigned short status, const std::string &effective_url,
  565              const WorkerContext *worker_context, bool succeeded)
  566           // Write blank data to ensure the header is parsed.
  567         : WriteBodyTask("", 0, status, effective_url, worker_context),
  568           succeeded_(succeeded) {
  569     }
  570     virtual bool Call(MainLoopInterface *main_loop, int watch_id) {
  571       curl_easy_cleanup(worker_context_.curl);
  572       // This cleanup of share handle will only succeed if this request is the
  573       // final request that was active when the belonging session has been
  574       // destroyed before this request finishes.
  575       if (curl_share_cleanup(worker_context_.this_p->share_) == CURLSHE_OK) {
  576         worker_context_.this_p->share_ = NULL;
  577         DLOG("Hangover share handle successfully cleaned up");
  578       }
  579 
  580       WriteBodyTask::Call(main_loop, watch_id);
  581       if (worker_context_.this_p->curl_ == worker_context_.curl)
  582         worker_context_.this_p->Done(false, succeeded_);
  583       // Remove the internal reference that was added when the request was
  584       // started.
  585       worker_context_.this_p->Unref();
  586       return false;
  587     }
  588 
  589     bool succeeded_;
  590   };
  591 
  592   static void WorkerDone(unsigned short status,
  593                          const std::string &effective_url,
  594                          WorkerContext *context, bool succeeded) {
  595     if (context->async) {
  596       // Do actual work in the main thread. AddTimeoutWatch() is threadsafe.
  597       context->this_p->main_loop_->AddTimeoutWatch(
  598           0, new DoneTask(status, effective_url, context, succeeded));
  599     } else {
  600       // Write blank data to ensure the header is parsed.
  601       context->this_p->WriteBody("", status, effective_url);
  602       context->this_p->Done(false, succeeded);
  603     }
  604   }
  605 
  606   static size_t WriteHeaderCallback(void *ptr, size_t size,
  607                                     size_t mem_block, void *user_p) {
  608     if (!CheckSize(0, size, mem_block))
  609       return 0;
  610 
  611     size_t data_size = size * mem_block;
  612     WorkerContext *context = static_cast<WorkerContext *>(user_p);
  613     // DLOG("XMLHttpRequest: WriteHeaderCallback: %zu*%zu this=%p",
  614     //      size, mem_block, context->this_p);
  615     if (context->async) {
  616       if (context->this_p->curl_ != context->curl) {
  617         // The current XMLHttpRequest has been aborted, so abort the
  618         // curl request.
  619         return 0;
  620       }
  621 
  622       // Do actual work in the main thread. AddTimeoutWatch() is threadsafe.
  623       context->this_p->main_loop_->AddTimeoutWatch(
  624           0, new WriteHeaderTask(ptr, data_size, context));
  625       return size * mem_block;
  626     } else {
  627       return context->this_p->WriteHeader(
  628           std::string(static_cast<char *>(ptr), data_size));
  629     }
  630   }
  631 
  632   size_t WriteHeader(const std::string &data) {
  633     ASSERT(state_ == OPENED && send_flag_);
  634     size_t size = data.length();
  635     if (CheckSize(response_headers_.length(), size, 1)) {
  636       if (strncmp(data.c_str(), "HTTP/", 5) == 0) {
  637         // This is a new header. We may receive multiple headers if the
  638         // request is redirected.
  639         response_headers_.clear();
  640       }
  641       response_headers_ += data;
  642       return size;
  643     }
  644 
  645     // Terminate the transfer because the header is too long.
  646     LOG("XMLHttpRequest: Header too long.");
  647     return 0;
  648   }
  649 
  650   static size_t WriteBodyCallback(void *ptr, size_t size, size_t mem_block,
  651                                   void *user_p) {
  652     if (!CheckSize(0, size, mem_block))
  653       return 0;
  654 
  655     size_t data_size = size * mem_block;
  656     WorkerContext *context = static_cast<WorkerContext *>(user_p);
  657 
  658     unsigned short status = 0;
  659     std::string effective_url;
  660     GetStatusAndEffectiveUrl(context->curl, &status, &effective_url);
  661 
  662     if (context->async) {
  663       if (context->this_p->curl_ != context->curl) {
  664         // The current XMLHttpRequest has been aborted, so abort the
  665         // curl request.
  666         return 0;
  667       }
  668 
  669       // Do actual work in the main thread. AddTimeoutWatch() is threadsafe.
  670       context->this_p->main_loop_->AddTimeoutWatch(
  671           0, new WriteBodyTask(ptr, data_size, status, effective_url, context));
  672       return data_size;
  673     } else {
  674       return context->this_p->WriteBody(
  675           std::string(static_cast<char *>(ptr), data_size),
  676           status, effective_url);
  677     }
  678   }
  679 
  680   size_t WriteBody(const std::string &data, unsigned short status,
  681                    const std::string &effective_url) {
  682     if (state_ == OPENED) {
  683       status_ = status;
  684       effective_url_ = effective_url;
  685       SplitStatusFromResponseHeaders(&response_headers_, &status_text_);
  686       ParseResponseHeaders(response_headers_,
  687                            &response_headers_map_,
  688                            &response_content_type_,
  689                            &response_encoding_);
  690       if (!ChangeState(HEADERS_RECEIVED) || !ChangeState(LOADING))
  691         return 0;
  692     }
  693 
  694     ASSERT(state_ == LOADING && send_flag_);
  695     size_t size = data.length();
  696     // DLOG("XMLHttpRequest: WriteBody: %zu + %zu this=%p",
  697     //      response_body_.length(), size, this);
  698 
  699     // Streamed mode.
  700     if (ondatareceived_signal_.HasActiveConnections())
  701       return ondatareceived_signal_(data.c_str(), size);
  702 
  703     // Normal mode.
  704     if (CheckSize(response_body_.length(), size, 1)) {
  705       response_body_ += data;
  706       return size;
  707     }
  708 
  709     // Terminate the transfer because the data is too long.
  710     LOG("XMLHttpRequest: Body too long.");
  711     return 0;
  712   }
  713 
  714   void Done(bool aborting, bool succeeded) {
  715     if (curl_) {
  716       if (!send_flag_) {
  717         // This cleanup only happens if an XMLHttpRequest is opened but
  718         // no send() is called. For an active request, the curl handle will
  719         // be cleaned up when the request finishes or is aborted by error
  720         // return value of WriteHeader() and WriteBody().
  721         curl_easy_cleanup(curl_);
  722       }
  723       curl_ = NULL;
  724     }
  725 
  726     request_headers_map_.clear();
  727     bool save_send_flag = send_flag_;
  728     // Set send_flag_ to false early, to prevent problems when Done() is
  729     // re-entered.
  730     send_flag_ = false;
  731     succeeded_ = succeeded;
  732     if (!succeeded) {
  733       response_body_.clear();
  734       response_headers_.clear();
  735       response_headers_map_.clear();
  736       response_text_.clear();
  737     }
  738 
  739     bool no_unexpected_state_change = true;
  740     if ((state_ == OPENED && save_send_flag) ||
  741         state_ == HEADERS_RECEIVED || state_ == LOADING) {
  742       uint64_t now = main_loop_->GetCurrentTime();
  743       if (!aborting &&
  744           XHRBackoffReportResult(now, host_.c_str(), status_)) {
  745         SaveXHRBackoffData(now);
  746       }
  747       // The caller may call Open() again in the OnReadyStateChange callback,
  748       // which may cause Done() re-entered.
  749       no_unexpected_state_change = ChangeState(DONE);
  750     }
  751 
  752     if (aborting && no_unexpected_state_change) {
  753       // Don't dispatch this state change event, according to the spec.
  754       state_ = UNSENT;
  755     }
  756   }
  757 
  758   virtual void Abort() {
  759     response_headers_.clear();
  760     response_headers_map_.clear();
  761     response_body_.clear();
  762     response_text_.clear();
  763     status_ = 0;
  764     status_text_.clear();
  765     if (response_dom_) {
  766       response_dom_->Unref();
  767       response_dom_ = NULL;
  768     }
  769 
  770     Done(true, false);
  771   }
  772 
  773   virtual ExceptionCode GetAllResponseHeaders(const std::string **result) {
  774     ASSERT(result);
  775     if (state_ == HEADERS_RECEIVED || state_ == LOADING || state_ == DONE) {
  776       *result = &response_headers_;
  777       return NO_ERR;
  778     }
  779 
  780     *result = NULL;
  781     LOG("XMLHttpRequest: GetAllResponseHeaders: Invalid state: %d", state_);
  782     return INVALID_STATE_ERR;
  783   }
  784 
  785   virtual ExceptionCode GetResponseHeader(const char *header,
  786                                           const std::string **result) {
  787     ASSERT(result);
  788     if (!header)
  789       return NULL_POINTER_ERR;
  790 
  791     *result = NULL;
  792     if (state_ == HEADERS_RECEIVED || state_ == LOADING || state_ == DONE) {
  793       CaseInsensitiveStringMap::const_iterator it = response_headers_map_.find(
  794           header);
  795       if (it != response_headers_map_.end())
  796         *result = &it->second;
  797       return NO_ERR;
  798     }
  799     LOG("XMLHttpRequest: GetRequestHeader: Invalid state: %d", state_);
  800     return INVALID_STATE_ERR;
  801   }
  802 
  803   void DecodeResponseText() {
  804     std::string encoding;
  805     xml_parser_->ConvertContentToUTF8(response_body_, url_.c_str(),
  806                                       response_content_type_.c_str(),
  807                                       response_encoding_.c_str(),
  808                                       kEncodingFallback,
  809                                       &encoding, &response_text_);
  810   }
  811 
  812   void ParseResponseToDOM() {
  813     std::string encoding;
  814     response_dom_ = xml_parser_->CreateDOMDocument();
  815     response_dom_->Ref();
  816     if (!xml_parser_->ParseContentIntoDOM(response_body_, NULL, url_.c_str(),
  817                                           response_content_type_.c_str(),
  818                                           response_encoding_.c_str(),
  819                                           kEncodingFallback,
  820                                           response_dom_,
  821                                           &encoding, &response_text_) ||
  822         !response_dom_->GetDocumentElement()) {
  823       response_dom_->Unref();
  824       response_dom_ = NULL;
  825     }
  826   }
  827 
  828   virtual ExceptionCode GetResponseText(std::string *result) {
  829     ASSERT(result);
  830 
  831     if (state_ == LOADING) {
  832       // Though the spec allows getting responseText while loading, we can't
  833       // afford this because we rely on XML/HTML parser to get the encoding.
  834       *result = "";
  835       return NO_ERR;
  836     } else if (state_ == DONE) {
  837       if (response_text_.empty() && !response_body_.empty())
  838         DecodeResponseText();
  839 
  840       *result = response_text_;
  841       return NO_ERR;
  842     }
  843 
  844     result->clear();
  845     LOG("XMLHttpRequest: GetResponseText: Invalid state: %d", state_);
  846     return INVALID_STATE_ERR;
  847   }
  848 
  849   virtual ExceptionCode GetResponseBody(std::string *result) {
  850     ASSERT(result);
  851 
  852     if (state_ == LOADING || state_ == DONE) {
  853       *result = response_body_;
  854       return NO_ERR;
  855     }
  856 
  857     result->clear();
  858     LOG("XMLHttpRequest: GetResponseBody: Invalid state: %d", state_);
  859     return INVALID_STATE_ERR;
  860   }
  861 
  862   virtual ExceptionCode GetResponseXML(DOMDocumentInterface **result) {
  863     ASSERT(result);
  864 
  865     if (state_ == DONE) {
  866       if (!response_dom_ && !response_body_.empty())
  867         ParseResponseToDOM();
  868 
  869       *result = response_dom_;
  870       return NO_ERR;
  871     }
  872 
  873     result = NULL;
  874     LOG("XMLHttpRequest: GetResponseXML: Invalid state: %d", state_);
  875     return INVALID_STATE_ERR;
  876   }
  877 
  878   virtual ExceptionCode GetStatus(unsigned short *result) {
  879     ASSERT(result);
  880 
  881     if (state_ == LOADING || state_ == DONE) {
  882       *result = status_;
  883       return NO_ERR;
  884     }
  885 
  886     *result = 0;
  887     LOG("XMLHttpRequest: GetStatus: Invalid state: %d", state_);
  888     return INVALID_STATE_ERR;
  889   }
  890 
  891   virtual ExceptionCode GetStatusText(const std::string **result) {
  892     ASSERT(result);
  893 
  894     if (state_ == LOADING || state_ == DONE) {
  895       *result = &status_text_;
  896       return NO_ERR;
  897     }
  898 
  899     *result = NULL;
  900     LOG("XMLHttpRequest: GetStatusText: Invalid state: %d", state_);
  901     return INVALID_STATE_ERR;
  902   }
  903 
  904   virtual bool IsSuccessful() {
  905     return succeeded_;
  906   }
  907 
  908   virtual std::string GetEffectiveUrl() {
  909     return effective_url_;
  910   }
  911 
  912   virtual std::string GetResponseContentType() {
  913     return response_content_type_;
  914   }
  915 
  916   virtual Connection *ConnectOnDataReceived(
  917       Slot2<size_t, const void *, size_t> *receiver) {
  918     return ondatareceived_signal_.Connect(receiver);
  919   }
  920 
  921   // Used in the methods for script to throw an script exception on errors.
  922   bool CheckException(ExceptionCode code) {
  923     if (code != NO_ERR) {
  924       DLOG("XMLHttpRequest: Set pending exception: %d this=%p", code, this);
  925       SetPendingException(new XMLHttpRequestException(code));
  926       return false;
  927     }
  928     return true;
  929   }
  930 
  931   void ScriptOpen(const char *method, const char *url, bool async,
  932                   const char *user, const char *password) {
  933     CheckException(Open(method, url, async, user, password));
  934   }
  935 
  936   void ScriptSetRequestHeader(const char *header, const char *value) {
  937     CheckException(SetRequestHeader(header, value));
  938   }
  939 
  940   void ScriptSend(const Variant &v_data) {
  941     std::string data;
  942     if (v_data.ConvertToString(&data)) {
  943       CheckException(Send(data));
  944     } else if (v_data.type() == Variant::TYPE_SCRIPTABLE) {
  945       ScriptableInterface *scriptable =
  946           VariantValue<ScriptableInterface *>()(v_data);
  947       if (!scriptable) {
  948         CheckException(Send(std::string()));
  949       } else if (scriptable->IsInstanceOf(DOMDocumentInterface::CLASS_ID)) {
  950         CheckException(Send(down_cast<DOMDocumentInterface *>(scriptable)));
  951       } else if (scriptable->IsInstanceOf(ScriptableBinaryData::CLASS_ID)) {
  952         CheckException(
  953             Send(down_cast<ScriptableBinaryData *>(scriptable)->data()));
  954       } else {
  955         CheckException(SYNTAX_ERR);
  956       }
  957     } else {
  958       CheckException(SYNTAX_ERR);
  959     }
  960   }
  961 
  962   Variant ScriptGetAllResponseHeaders() {
  963     const std::string *result = NULL;
  964     CheckException(GetAllResponseHeaders(&result));
  965     return result ? Variant(*result) : Variant(static_cast<const char *>(NULL));
  966   }
  967 
  968   Variant ScriptGetResponseHeader(const char *header) {
  969     const std::string *result = NULL;
  970     CheckException(GetResponseHeader(header, &result));
  971     return result ? Variant(*result) : Variant(static_cast<const char *>(NULL));
  972   }
  973 
  974   // We can't return std::string here, because the response body may be binary
  975   // and can't be converted from UTF-8 to UTF-16 by the script adapter.
  976   ScriptableBinaryData *ScriptGetResponseBody() {
  977     std::string result;
  978     if (CheckException(GetResponseBody(&result)) && !result.empty())
  979       return new ScriptableBinaryData(result);
  980     return NULL;
  981   }
  982 
  983   std::string ScriptGetResponseText() {
  984     std::string result;
  985     CheckException(GetResponseText(&result));
  986     return result;
  987   }
  988 
  989   DOMDocumentInterface *ScriptGetResponseXML() {
  990     DOMDocumentInterface *result = NULL;
  991     CheckException(GetResponseXML(&result));
  992     return result;
  993   }
  994 
  995   unsigned short ScriptGetStatus() {
  996     unsigned short result = 0;
  997     CheckException(GetStatus(&result));
  998     return result;
  999   }
 1000 
 1001   Variant ScriptGetStatusText() {
 1002     const std::string *result = NULL;
 1003     CheckException(GetStatusText(&result));
 1004     return result ? Variant(*result) : Variant(static_cast<const char *>(NULL));
 1005   }
 1006 
 1007   CURL *curl_;
 1008   CURLSH *share_;
 1009   MainLoopInterface *main_loop_;
 1010   XMLParserInterface *xml_parser_;
 1011   DOMDocumentInterface *response_dom_;
 1012   CaseInsensitiveStringMap request_headers_map_;
 1013   CaseInsensitiveStringMap response_headers_map_;
 1014   Signal0<void> onreadystatechange_signal_;
 1015   Signal2<size_t, const void *, size_t> ondatareceived_signal_;
 1016 
 1017   std::string url_;
 1018   std::string host_;
 1019   std::string response_headers_;
 1020   std::string response_content_type_;
 1021   std::string response_encoding_;
 1022   std::string effective_url_;
 1023   std::string status_text_;
 1024   std::string response_body_;
 1025   std::string response_text_;
 1026   std::string default_user_agent_;
 1027   pthread_attr_t thread_attr_;
 1028 
 1029   unsigned short status_;
 1030   State state_ : 3;
 1031 
 1032   enum HTTPMethod { HTTP_HEAD, HTTP_GET, HTTP_POST, HTTP_PUT };
 1033   HTTPMethod method_ : 2;
 1034 
 1035   bool async_     : 1;
 1036   // Required by the specification.
 1037   // It will be true after send() is called in async mode.
 1038   bool send_flag_ : 1;
 1039   bool succeeded_ : 1;
 1040 };
 1041 
 1042 class XMLHttpRequestFactory : public XMLHttpRequestFactoryInterface {
 1043  public:
 1044   XMLHttpRequestFactory() : next_session_id_(1) {
 1045   }
 1046 
 1047   virtual int CreateSession() {
 1048     CURLSH *share = curl_share_init();
 1049     if (share) {
 1050       curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
 1051       curl_share_setopt(share, CURLSHOPT_LOCKFUNC, Lock);
 1052       curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, Unlock);
 1053       int result = next_session_id_++;
 1054       Session *session = &sessions_[result];
 1055       session->share = share;
 1056       session->share_ref = curl_easy_init();
 1057       // Add a reference from "share_ref" to "share" to prevent "share" be
 1058       // cleaned up by XMLHttpRequest instances.
 1059       curl_easy_setopt(session->share_ref, CURLOPT_SHARE, share);
 1060       return result;
 1061     }
 1062     return -1;
 1063   }
 1064 
 1065   virtual void DestroySession(int session_id) {
 1066     Sessions::iterator it = sessions_.find(session_id);
 1067     if (it != sessions_.end()) {
 1068       Session *session = &it->second;
 1069       curl_easy_setopt(session->share_ref, CURLOPT_SHARE, NULL);
 1070       // Cleanup the share_ref to prevent memory leak.
 1071       curl_easy_cleanup(session->share_ref);
 1072       // This cleanup will fail if there is still active requests. It'll be
 1073       // actually cleaned up when the requests finish.
 1074       CURLSHcode code = curl_share_cleanup(session->share);
 1075       if (code != CURLSHE_OK) {
 1076         DLOG("XMLHttpRequestFactory: Failed to DestroySession(): %s",
 1077              curl_share_strerror(code));
 1078       }
 1079       sessions_.erase(it);
 1080     } else {
 1081       DLOG("XMLHttpRequestFactory::DestroySession Invalid session: %d",
 1082            session_id);
 1083     }
 1084   }
 1085 
 1086   virtual XMLHttpRequestInterface *CreateXMLHttpRequest(
 1087       int session_id, XMLParserInterface *parser) {
 1088     if (session_id == 0) {
 1089       return new XMLHttpRequest(NULL, GetGlobalMainLoop(), parser,
 1090                                 default_user_agent_);
 1091     }
 1092 
 1093     Sessions::iterator it = sessions_.find(session_id);
 1094     if (it != sessions_.end()) {
 1095       return new XMLHttpRequest(it->second.share, GetGlobalMainLoop(), parser,
 1096                                 default_user_agent_);
 1097     }
 1098 
 1099     DLOG("XMLHttpRequestFactory::CreateXMLHttpRequest: "
 1100          "Invalid session: %d", session_id);
 1101     return NULL;
 1102   }
 1103 
 1104   virtual void SetDefaultUserAgent(const char *user_agent) {
 1105     if (user_agent)
 1106       default_user_agent_ = user_agent;
 1107   }
 1108 
 1109   static void Lock(CURL *handle, curl_lock_data data,
 1110                    curl_lock_access access, void *userptr) {
 1111     GGL_UNUSED(handle);
 1112     GGL_UNUSED(data);
 1113     GGL_UNUSED(access);
 1114     GGL_UNUSED(userptr);
 1115     // This synchronization scope is bigger than optimal, but is much simpler.
 1116     pthread_mutex_lock(&mutex_);
 1117   }
 1118 
 1119   static void Unlock(CURL *handle, curl_lock_data data, void *userptr) {
 1120     GGL_UNUSED(handle);
 1121     GGL_UNUSED(data);
 1122     GGL_UNUSED(userptr);
 1123     pthread_mutex_unlock(&mutex_);
 1124   }
 1125 
 1126  private:
 1127   struct Session {
 1128     CURLSH *share;
 1129     CURL *share_ref;
 1130   };
 1131 
 1132   typedef LightMap<int, Session> Sessions;
 1133   Sessions sessions_;
 1134   int next_session_id_;
 1135   std::string default_user_agent_;
 1136   static pthread_mutex_t mutex_;
 1137 };
 1138 
 1139 pthread_mutex_t XMLHttpRequestFactory::mutex_ = PTHREAD_MUTEX_INITIALIZER;
 1140 
 1141 } // namespace curl
 1142 } // namespace ggadget
 1143 
 1144 #define Initialize curl_xml_http_request_LTX_Initialize
 1145 #define Finalize curl_xml_http_request_LTX_Finalize
 1146 
 1147 static ggadget::curl::XMLHttpRequestFactory gFactory;
 1148 
 1149 extern "C" {
 1150   bool Initialize() {
 1151     LOGI("Initialize curl_xml_http_request extension.");
 1152     return ggadget::SetXMLHttpRequestFactory(&gFactory);
 1153   }
 1154 
 1155   void Finalize() {
 1156     LOGI("Finalize curl_xml_http_request extension.");
 1157   }
 1158 }