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