Home | History | Annotate | Download | only in proxy
      1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.  Use of this
      2 // source code is governed by a BSD-style license that can be found in the
      3 // LICENSE file.
      4 
      5 #include "net/proxy/proxy_resolver_v8.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/string_util.h"
      9 #include "googleurl/src/gurl.h"
     10 #include "net/base/load_log.h"
     11 #include "net/base/net_errors.h"
     12 #include "net/proxy/proxy_info.h"
     13 #include "net/proxy/proxy_resolver_js_bindings.h"
     14 #include "net/proxy/proxy_resolver_script.h"
     15 #include "v8/include/v8.h"
     16 
     17 // Notes on the javascript environment:
     18 //
     19 // For the majority of the PAC utility functions, we use the same code
     20 // as Firefox. See the javascript library that proxy_resolver_scipt.h
     21 // pulls in.
     22 //
     23 // In addition, we implement a subset of Microsoft's extensions to PAC.
     24 // TODO(eroman): Implement the rest.
     25 //
     26 //   - myIpAddressEx()
     27 //   - dnsResolveEx()
     28 //   - isResolvableEx()
     29 //
     30 // It is worth noting that the original PAC specification does not describe
     31 // the return values on failure. Consequently, there are compatibility
     32 // differences between browsers on what to return on failure, which are
     33 // illustrated below:
     34 //
     35 // ----------------+-------------+-------------------+--------------
     36 //                 | Firefox3    | InternetExplorer8 |  --> Us <---
     37 // ----------------+-------------+-------------------+--------------
     38 // myIpAddress()   | "127.0.0.1" |  ???              |  "127.0.0.1"
     39 // dnsResolve()    | null        |  false            |  null
     40 // myIpAddressEx() | N/A         |  ""               |  ""
     41 // dnsResolveEx()  | N/A         |  ""               |  ""
     42 // ----------------+-------------+-------------------+--------------
     43 //
     44 // TODO(eroman): The cell above reading ??? means I didn't test it.
     45 //
     46 // Another difference is in how dnsResolve() and myIpAddress() are
     47 // implemented -- whether they should restrict to IPv4 results, or
     48 // include both IPv4 and IPv6. The following table illustrates the
     49 // differences:
     50 //
     51 // -----------------+-------------+-------------------+--------------
     52 //                  | Firefox3    | InternetExplorer8 |  --> Us <---
     53 // -----------------+-------------+-------------------+--------------
     54 // myIpAddress()    | IPv4/IPv6   |  IPv4             |  IPv4
     55 // dnsResolve()     | IPv4/IPv6   |  IPv4             |  IPv4
     56 // isResolvable()   | IPv4/IPv6   |  IPv4             |  IPv4
     57 // myIpAddressEx()  | N/A         |  IPv4/IPv6        |  IPv4/IPv6
     58 // dnsResolveEx()   | N/A         |  IPv4/IPv6        |  IPv4/IPv6
     59 // isResolvableEx() | N/A         |  IPv4/IPv6        |  IPv4/IPv6
     60 // -----------------+-------------+-------------------+--------------
     61 
     62 namespace net {
     63 
     64 namespace {
     65 
     66 // Pseudo-name for the PAC script.
     67 const char kPacResourceName[] = "proxy-pac-script.js";
     68 // Pseudo-name for the PAC utility script.
     69 const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
     70 
     71 // Convert a V8 String to a std::string.
     72 std::string V8StringToStdString(v8::Handle<v8::String> s) {
     73   int len = s->Utf8Length();
     74   std::string result;
     75   s->WriteUtf8(WriteInto(&result, len + 1), len);
     76   return result;
     77 }
     78 
     79 // Convert a std::string (UTF8) to a V8 string.
     80 v8::Local<v8::String> StdStringToV8String(const std::string& s) {
     81   return v8::String::New(s.data(), s.size());
     82 }
     83 
     84 // String-ize a V8 object by calling its toString() method. Returns true
     85 // on success. This may fail if the toString() throws an exception.
     86 bool V8ObjectToString(v8::Handle<v8::Value> object, std::string* result) {
     87   if (object.IsEmpty())
     88     return false;
     89 
     90   v8::HandleScope scope;
     91   v8::Local<v8::String> str_object = object->ToString();
     92   if (str_object.IsEmpty())
     93     return false;
     94   *result = V8StringToStdString(str_object);
     95   return true;
     96 }
     97 
     98 }  // namespace
     99 
    100 // ProxyResolverV8::Context ---------------------------------------------------
    101 
    102 class ProxyResolverV8::Context {
    103  public:
    104   explicit Context(ProxyResolverJSBindings* js_bindings)
    105       : js_bindings_(js_bindings), current_request_load_log_(NULL) {
    106     DCHECK(js_bindings != NULL);
    107   }
    108 
    109   ~Context() {
    110     v8::Locker locked;
    111 
    112     v8_this_.Dispose();
    113     v8_context_.Dispose();
    114   }
    115 
    116   int ResolveProxy(const GURL& query_url, ProxyInfo* results) {
    117     v8::Locker locked;
    118     v8::HandleScope scope;
    119 
    120     v8::Context::Scope function_scope(v8_context_);
    121 
    122     v8::Local<v8::Value> function;
    123     if (!GetFindProxyForURL(&function)) {
    124       js_bindings_->OnError(-1, "FindProxyForURL() is undefined.");
    125       return ERR_PAC_SCRIPT_FAILED;
    126     }
    127 
    128     v8::Handle<v8::Value> argv[] = {
    129       StdStringToV8String(query_url.spec()),
    130       StdStringToV8String(query_url.host()),
    131     };
    132 
    133     v8::TryCatch try_catch;
    134     v8::Local<v8::Value> ret = v8::Function::Cast(*function)->Call(
    135         v8_context_->Global(), arraysize(argv), argv);
    136 
    137     if (try_catch.HasCaught()) {
    138       HandleError(try_catch.Message());
    139       return ERR_PAC_SCRIPT_FAILED;
    140     }
    141 
    142     if (!ret->IsString()) {
    143       js_bindings_->OnError(-1, "FindProxyForURL() did not return a string.");
    144       return ERR_PAC_SCRIPT_FAILED;
    145     }
    146 
    147     std::string ret_str = V8StringToStdString(ret->ToString());
    148 
    149     results->UsePacString(ret_str);
    150 
    151     return OK;
    152   }
    153 
    154   int InitV8(const std::string& pac_data_utf8) {
    155     v8::Locker locked;
    156     v8::HandleScope scope;
    157 
    158     v8_this_ = v8::Persistent<v8::External>::New(v8::External::New(this));
    159     v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
    160 
    161     // Attach the javascript bindings.
    162     v8::Local<v8::FunctionTemplate> alert_template =
    163         v8::FunctionTemplate::New(&AlertCallback, v8_this_);
    164     global_template->Set(v8::String::New("alert"), alert_template);
    165 
    166     v8::Local<v8::FunctionTemplate> my_ip_address_template =
    167         v8::FunctionTemplate::New(&MyIpAddressCallback, v8_this_);
    168     global_template->Set(v8::String::New("myIpAddress"),
    169         my_ip_address_template);
    170 
    171     v8::Local<v8::FunctionTemplate> dns_resolve_template =
    172         v8::FunctionTemplate::New(&DnsResolveCallback, v8_this_);
    173     global_template->Set(v8::String::New("dnsResolve"),
    174         dns_resolve_template);
    175 
    176     // Microsoft's PAC extensions (incomplete):
    177 
    178     v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
    179         v8::FunctionTemplate::New(&DnsResolveExCallback, v8_this_);
    180     global_template->Set(v8::String::New("dnsResolveEx"),
    181                          dns_resolve_ex_template);
    182 
    183     v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
    184         v8::FunctionTemplate::New(&MyIpAddressExCallback, v8_this_);
    185     global_template->Set(v8::String::New("myIpAddressEx"),
    186                          my_ip_address_ex_template);
    187 
    188     v8_context_ = v8::Context::New(NULL, global_template);
    189 
    190     v8::Context::Scope ctx(v8_context_);
    191 
    192     // Add the PAC utility functions to the environment.
    193     // (This script should never fail, as it is a string literal!)
    194     // Note that the two string literals are concatenated.
    195     int rv = RunScript(PROXY_RESOLVER_SCRIPT
    196                        PROXY_RESOLVER_SCRIPT_EX,
    197                        kPacUtilityResourceName);
    198     if (rv != OK) {
    199       NOTREACHED();
    200       return rv;
    201     }
    202 
    203     // Add the user's PAC code to the environment.
    204     rv = RunScript(pac_data_utf8, kPacResourceName);
    205     if (rv != OK)
    206       return rv;
    207 
    208     // At a minimum, the FindProxyForURL() function must be defined for this
    209     // to be a legitimiate PAC script.
    210     v8::Local<v8::Value> function;
    211     if (!GetFindProxyForURL(&function))
    212       return ERR_PAC_SCRIPT_FAILED;
    213 
    214     return OK;
    215   }
    216 
    217   void SetCurrentRequestLoadLog(LoadLog* load_log) {
    218     current_request_load_log_ = load_log;
    219   }
    220 
    221   void PurgeMemory() {
    222     v8::Locker locked;
    223     // Repeatedly call the V8 idle notification until it returns true ("nothing
    224     // more to free").  Note that it makes more sense to do this than to
    225     // implement a new "delete everything" pass because object references make
    226     // it difficult to free everything possible in just one pass.
    227     while (!v8::V8::IdleNotification())
    228       ;
    229   }
    230 
    231  private:
    232   bool GetFindProxyForURL(v8::Local<v8::Value>* function) {
    233     *function = v8_context_->Global()->Get(v8::String::New("FindProxyForURL"));
    234     return (*function)->IsFunction();
    235   }
    236 
    237   // Handle an exception thrown by V8.
    238   void HandleError(v8::Handle<v8::Message> message) {
    239     if (message.IsEmpty())
    240       return;
    241 
    242     // Otherwise dispatch to the bindings.
    243     int line_number = message->GetLineNumber();
    244     std::string error_message;
    245     V8ObjectToString(message->Get(), &error_message);
    246     js_bindings_->OnError(line_number, error_message);
    247   }
    248 
    249   // Compiles and runs |script_utf8| in the current V8 context.
    250   // Returns OK on success, otherwise an error code.
    251   int RunScript(const std::string& script_utf8, const char* script_name) {
    252     v8::TryCatch try_catch;
    253 
    254     // Compile the script.
    255     v8::Local<v8::String> text = StdStringToV8String(script_utf8);
    256     v8::ScriptOrigin origin = v8::ScriptOrigin(v8::String::New(script_name));
    257     v8::Local<v8::Script> code = v8::Script::Compile(text, &origin);
    258 
    259     // Execute.
    260     if (!code.IsEmpty())
    261       code->Run();
    262 
    263     // Check for errors.
    264     if (try_catch.HasCaught()) {
    265       HandleError(try_catch.Message());
    266       return ERR_PAC_SCRIPT_FAILED;
    267     }
    268 
    269     return OK;
    270   }
    271 
    272   // V8 callback for when "alert()" is invoked by the PAC script.
    273   static v8::Handle<v8::Value> AlertCallback(const v8::Arguments& args) {
    274     Context* context =
    275         static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
    276 
    277     // Like firefox we assume "undefined" if no argument was specified, and
    278     // disregard any arguments beyond the first.
    279     std::string message;
    280     if (args.Length() == 0) {
    281       message = "undefined";
    282     } else {
    283       if (!V8ObjectToString(args[0], &message))
    284         return v8::Undefined();  // toString() threw an exception.
    285     }
    286 
    287     context->js_bindings_->Alert(message);
    288     return v8::Undefined();
    289   }
    290 
    291   // V8 callback for when "myIpAddress()" is invoked by the PAC script.
    292   static v8::Handle<v8::Value> MyIpAddressCallback(const v8::Arguments& args) {
    293     Context* context =
    294         static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
    295 
    296     LoadLog::BeginEvent(context->current_request_load_log_,
    297                         LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS);
    298 
    299     // We shouldn't be called with any arguments, but will not complain if
    300     // we are.
    301     std::string result = context->js_bindings_->MyIpAddress();
    302 
    303     LoadLog::EndEvent(context->current_request_load_log_,
    304                       LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS);
    305 
    306     if (result.empty())
    307       result = "127.0.0.1";
    308     return StdStringToV8String(result);
    309   }
    310 
    311   // V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
    312   static v8::Handle<v8::Value> MyIpAddressExCallback(
    313       const v8::Arguments& args) {
    314     Context* context =
    315         static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
    316 
    317     LoadLog::BeginEvent(context->current_request_load_log_,
    318                         LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS_EX);
    319 
    320     // We shouldn't be called with any arguments, but will not complain if
    321     // we are.
    322     std::string result = context->js_bindings_->MyIpAddressEx();
    323 
    324     LoadLog::EndEvent(context->current_request_load_log_,
    325                       LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS_EX);
    326 
    327     return StdStringToV8String(result);
    328   }
    329 
    330   // V8 callback for when "dnsResolve()" is invoked by the PAC script.
    331   static v8::Handle<v8::Value> DnsResolveCallback(const v8::Arguments& args) {
    332     Context* context =
    333         static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
    334 
    335     // We need at least one argument.
    336     std::string host;
    337     if (args.Length() == 0) {
    338       host = "undefined";
    339     } else {
    340       if (!V8ObjectToString(args[0], &host))
    341         return v8::Undefined();
    342     }
    343 
    344     LoadLog::BeginEvent(context->current_request_load_log_,
    345                         LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE);
    346 
    347     std::string result = context->js_bindings_->DnsResolve(host);
    348 
    349     LoadLog::EndEvent(context->current_request_load_log_,
    350                       LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE);
    351 
    352     // DnsResolve() returns empty string on failure.
    353     return result.empty() ? v8::Null() : StdStringToV8String(result);
    354   }
    355 
    356   // V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
    357   static v8::Handle<v8::Value> DnsResolveExCallback(const v8::Arguments& args) {
    358     Context* context =
    359         static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
    360 
    361     // We need at least one argument.
    362     std::string host;
    363     if (args.Length() == 0) {
    364       host = "undefined";
    365     } else {
    366       if (!V8ObjectToString(args[0], &host))
    367         return v8::Undefined();
    368     }
    369 
    370     LoadLog::BeginEvent(context->current_request_load_log_,
    371                         LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE_EX);
    372 
    373     std::string result = context->js_bindings_->DnsResolveEx(host);
    374 
    375     LoadLog::EndEvent(context->current_request_load_log_,
    376                       LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE_EX);
    377 
    378     return StdStringToV8String(result);
    379   }
    380 
    381   ProxyResolverJSBindings* js_bindings_;
    382   LoadLog* current_request_load_log_;
    383   v8::Persistent<v8::External> v8_this_;
    384   v8::Persistent<v8::Context> v8_context_;
    385 };
    386 
    387 // ProxyResolverV8 ------------------------------------------------------------
    388 
    389 ProxyResolverV8::ProxyResolverV8(
    390     ProxyResolverJSBindings* custom_js_bindings)
    391     : ProxyResolver(true /*expects_pac_bytes*/),
    392       js_bindings_(custom_js_bindings) {
    393 }
    394 
    395 ProxyResolverV8::~ProxyResolverV8() {}
    396 
    397 int ProxyResolverV8::GetProxyForURL(const GURL& query_url,
    398                                     ProxyInfo* results,
    399                                     CompletionCallback* /*callback*/,
    400                                     RequestHandle* /*request*/,
    401                                     LoadLog* load_log) {
    402   // If the V8 instance has not been initialized (either because
    403   // SetPacScript() wasn't called yet, or because it failed.
    404   if (!context_.get())
    405     return ERR_FAILED;
    406 
    407   // Otherwise call into V8.
    408   context_->SetCurrentRequestLoadLog(load_log);
    409   int rv = context_->ResolveProxy(query_url, results);
    410   context_->SetCurrentRequestLoadLog(NULL);
    411 
    412   return rv;
    413 }
    414 
    415 void ProxyResolverV8::CancelRequest(RequestHandle request) {
    416   // This is a synchronous ProxyResolver; no possibility for async requests.
    417   NOTREACHED();
    418 }
    419 
    420 void ProxyResolverV8::PurgeMemory() {
    421   context_->PurgeMemory();
    422 }
    423 
    424 int ProxyResolverV8::SetPacScript(const GURL& /*url*/,
    425                                   const std::string& bytes_utf8,
    426                                   CompletionCallback* /*callback*/) {
    427   context_.reset();
    428   if (bytes_utf8.empty())
    429     return ERR_PAC_SCRIPT_FAILED;
    430 
    431   // Try parsing the PAC script.
    432   scoped_ptr<Context> context(new Context(js_bindings_.get()));
    433   int rv = context->InitV8(bytes_utf8);
    434   if (rv == OK)
    435     context_.reset(context.release());
    436   return rv;
    437 }
    438 
    439 }  // namespace net
    440