// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. #include "net/proxy/proxy_resolver_v8.h" #include "base/logging.h" #include "base/string_util.h" #include "googleurl/src/gurl.h" #include "net/base/address_list.h" #include "net/base/host_resolver.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/proxy/proxy_info.h" #include "net/proxy/proxy_resolver_script.h" #include "v8/include/v8.h" namespace net { namespace { // Pseudo-name for the PAC script. const char kPacResourceName[] = "proxy-pac-script.js"; // Convert a V8 String to a std::string. std::string V8StringToStdString(v8::Handle s) { int len = s->Utf8Length(); std::string result; s->WriteUtf8(WriteInto(&result, len + 1), len); return result; } // Convert a std::string to a V8 string. v8::Local StdStringToV8String(const std::string& s) { return v8::String::New(s.data(), s.size()); } // String-ize a V8 object by calling its toString() method. Returns true // on success. This may fail if the toString() throws an exception. bool V8ObjectToString(v8::Handle object, std::string* result) { if (object.IsEmpty()) return false; v8::HandleScope scope; v8::Local str_object = object->ToString(); if (str_object.IsEmpty()) return false; *result = V8StringToStdString(str_object); return true; } // JSBIndings implementation. class DefaultJSBindings : public ProxyResolverV8::JSBindings { public: // Handler for "alert(message)". virtual void Alert(const std::string& message) { LOG(INFO) << "PAC-alert: " << message; } // Handler for "myIpAddress()". Returns empty string on failure. virtual std::string MyIpAddress() { // DnsResolve("") returns "", so no need to check for failure. return DnsResolve(GetHostName()); } // Handler for "dnsResolve(host)". Returns empty string on failure. virtual std::string DnsResolve(const std::string& host) { // TODO(eroman): Should this return our IP address, or fail, or // simply be unspecified (works differently on windows and mac os x). if (host.empty()) return std::string(); // Try to resolve synchronously. net::AddressList address_list; const int kPort = 80; // Doesn't matter what this is. int result = host_resolver_.Resolve(host, kPort, &address_list, NULL); if (result != OK) return std::string(); // Failed. if (!address_list.head()) return std::string(); // There may be multiple results; we will just use the first one. // This returns empty string on failure. return net::NetAddressToString(address_list.head()); } // Handler for when an error is encountered. |line_number| may be -1. virtual void OnError(int line_number, const std::string& message) { if (line_number == -1) LOG(INFO) << "PAC-error: " << message; else LOG(INFO) << "PAC-error: " << "line: " << line_number << ": " << message; } private: HostResolver host_resolver_; }; } // namespace // ProxyResolverV8::Context --------------------------------------------------- class ProxyResolverV8::Context { public: Context(JSBindings* js_bindings, const std::string& pac_data) : js_bindings_(js_bindings) { DCHECK(js_bindings != NULL); InitV8(pac_data); } ~Context() { v8::Locker locked; v8_this_.Dispose(); v8_context_.Dispose(); } int ResolveProxy(const GURL& query_url, ProxyInfo* results) { v8::Locker locked; v8::HandleScope scope; v8::Context::Scope function_scope(v8_context_); v8::Local function = v8_context_->Global()->Get(v8::String::New("FindProxyForURL")); if (!function->IsFunction()) { js_bindings_->OnError(-1, "FindProxyForURL() is undefined."); return ERR_PAC_SCRIPT_FAILED; } v8::Handle argv[] = { StdStringToV8String(query_url.spec()), StdStringToV8String(query_url.host()), }; v8::TryCatch try_catch; v8::Local ret = v8::Function::Cast(*function)->Call( v8_context_->Global(), arraysize(argv), argv); if (try_catch.HasCaught()) { HandleError(try_catch.Message()); return ERR_PAC_SCRIPT_FAILED; } if (!ret->IsString()) { js_bindings_->OnError(-1, "FindProxyForURL() did not return a string."); return ERR_PAC_SCRIPT_FAILED; } std::string ret_str = V8StringToStdString(ret->ToString()); results->UsePacString(ret_str); return OK; } private: void InitV8(const std::string& pac_data) { v8::Locker locked; v8::HandleScope scope; v8_this_ = v8::Persistent::New(v8::External::New(this)); v8::Local global_template = v8::ObjectTemplate::New(); // Attach the javascript bindings. v8::Local alert_template = v8::FunctionTemplate::New(&AlertCallback, v8_this_); global_template->Set(v8::String::New("alert"), alert_template); v8::Local my_ip_address_template = v8::FunctionTemplate::New(&MyIpAddressCallback, v8_this_); global_template->Set(v8::String::New("myIpAddress"), my_ip_address_template); v8::Local dns_resolve_template = v8::FunctionTemplate::New(&DnsResolveCallback, v8_this_); global_template->Set(v8::String::New("dnsResolve"), dns_resolve_template); v8_context_ = v8::Context::New(NULL, global_template); v8::Context::Scope ctx(v8_context_); v8::TryCatch try_catch; // Compile the script, including the PAC library functions. std::string text_raw = pac_data + PROXY_RESOLVER_SCRIPT; v8::Local text = StdStringToV8String(text_raw); v8::ScriptOrigin origin = v8::ScriptOrigin( v8::String::New(kPacResourceName)); v8::Local code = v8::Script::Compile(text, &origin); // Execute. if (!code.IsEmpty()) code->Run(); if (try_catch.HasCaught()) HandleError(try_catch.Message()); } // Handle an exception thrown by V8. void HandleError(v8::Handle message) { if (message.IsEmpty()) return; // Otherwise dispatch to the bindings. int line_number = message->GetLineNumber(); std::string error_message; V8ObjectToString(message->Get(), &error_message); js_bindings_->OnError(line_number, error_message); } // V8 callback for when "alert()" is invoked by the PAC script. static v8::Handle AlertCallback(const v8::Arguments& args) { Context* context = static_cast(v8::External::Cast(*args.Data())->Value()); // Like firefox we assume "undefined" if no argument was specified, and // disregard any arguments beyond the first. std::string message; if (args.Length() == 0) { message = "undefined"; } else { if (!V8ObjectToString(args[0], &message)) return v8::Undefined(); // toString() threw an exception. } context->js_bindings_->Alert(message); return v8::Undefined(); } // V8 callback for when "myIpAddress()" is invoked by the PAC script. static v8::Handle MyIpAddressCallback(const v8::Arguments& args) { Context* context = static_cast(v8::External::Cast(*args.Data())->Value()); // We shouldn't be called with any arguments, but will not complain if // we are. std::string result = context->js_bindings_->MyIpAddress(); if (result.empty()) result = "127.0.0.1"; return StdStringToV8String(result); } // V8 callback for when "dnsResolve()" is invoked by the PAC script. static v8::Handle DnsResolveCallback(const v8::Arguments& args) { Context* context = static_cast(v8::External::Cast(*args.Data())->Value()); // We need at least one argument. std::string host; if (args.Length() == 0) { host = "undefined"; } else { if (!V8ObjectToString(args[0], &host)) return v8::Undefined(); } std::string result = context->js_bindings_->DnsResolve(host); // DoDnsResolve() returns empty string on failure. return result.empty() ? v8::Null() : StdStringToV8String(result); } JSBindings* js_bindings_; HostResolver host_resolver_; v8::Persistent v8_this_; v8::Persistent v8_context_; }; // ProxyResolverV8 ------------------------------------------------------------ // the |false| argument to ProxyResolver means the ProxyService will handle // downloading of the PAC script, and notify changes through SetPacScript(). ProxyResolverV8::ProxyResolverV8() : ProxyResolver(false /*does_fetch*/), js_bindings_(new DefaultJSBindings()) { } ProxyResolverV8::ProxyResolverV8( ProxyResolverV8::JSBindings* custom_js_bindings) : ProxyResolver(false), js_bindings_(custom_js_bindings) { } ProxyResolverV8::~ProxyResolverV8() {} int ProxyResolverV8::GetProxyForURL(const GURL& query_url, const GURL& /*pac_url*/, ProxyInfo* results) { // If the V8 instance has not been initialized (either because SetPacScript() // wasn't called yet, or because it was called with empty string). if (!context_.get()) return ERR_FAILED; // Otherwise call into V8. return context_->ResolveProxy(query_url, results); } void ProxyResolverV8::SetPacScript(const std::string& data) { context_.reset(); if (!data.empty()) context_.reset(new Context(js_bindings_.get(), data)); } } // namespace net