// Copyright (c) 2019 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "shell/browser/api/atom_api_web_request_ns.h" #include #include #include #include "base/stl_util.h" #include "gin/converter.h" #include "gin/dictionary.h" #include "gin/object_template_builder.h" #include "shell/browser/api/atom_api_session.h" #include "shell/browser/api/atom_api_web_contents.h" #include "shell/browser/atom_browser_context.h" #include "shell/common/gin_converters/callback_converter_gin_adapter.h" #include "shell/common/gin_converters/gurl_converter.h" #include "shell/common/gin_converters/net_converter.h" #include "shell/common/gin_converters/std_converter.h" #include "shell/common/gin_converters/value_converter_gin_adapter.h" namespace gin { template <> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, URLPattern* out) { std::string pattern; if (!ConvertFromV8(isolate, val, &pattern)) return false; *out = URLPattern(URLPattern::SCHEME_ALL); return out->Parse(pattern) == URLPattern::ParseResult::kSuccess; } }; template <> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, content::ResourceType type) { const char* result; switch (type) { case content::ResourceType::kMainFrame: result = "mainFrame"; break; case content::ResourceType::kSubFrame: result = "subFrame"; break; case content::ResourceType::kStylesheet: result = "stylesheet"; break; case content::ResourceType::kScript: result = "script"; break; case content::ResourceType::kImage: result = "image"; break; case content::ResourceType::kObject: result = "object"; break; case content::ResourceType::kXhr: result = "xhr"; break; default: result = "other"; } return StringToV8(isolate, result); } }; } // namespace gin namespace electron { namespace api { namespace { const char* kUserDataKey = "WebRequestNS"; // BrowserContext <=> WebRequestNS relationship. struct UserData : public base::SupportsUserData::Data { explicit UserData(WebRequestNS* data) : data(data) {} WebRequestNS* data; }; // Test whether the URL of |request| matches |patterns|. bool MatchesFilterCondition(extensions::WebRequestInfo* info, const std::set& patterns) { if (patterns.empty()) return true; for (const auto& pattern : patterns) { if (pattern.MatchesURL(info->url)) return true; } return false; } // Overloaded by multiple types to fill the |details| object. void ToDictionary(gin::Dictionary* details, extensions::WebRequestInfo* info) { details->Set("id", info->id); details->Set("url", info->url); details->Set("method", info->method); details->Set("timestamp", base::Time::Now().ToDoubleT() * 1000); if (!info->response_ip.empty()) details->Set("ip", info->response_ip); if (info->type.has_value()) details->Set("resourceType", info->type.value()); else details->Set("resourceType", base::StringPiece("other")); if (info->response_headers) { details->Set("fromCache", info->response_from_cache); details->Set("responseHeaders", info->response_headers.get()); details->Set("statusLine", info->response_headers->GetStatusLine()); details->Set("statusCode", info->response_headers->response_code()); } auto* web_contents = content::WebContents::FromRenderFrameHost( content::RenderFrameHost::FromID(info->render_process_id, info->frame_id)); int32_t id = api::WebContents::GetIDFromWrappedClass(web_contents); // id must be greater than zero. if (id > 0) details->Set("webContentsId", id); } void ToDictionary(gin::Dictionary* details, const network::ResourceRequest& request) { details->Set("referrer", request.referrer); } void ToDictionary(gin::Dictionary* details, const net::HttpRequestHeaders& headers) { details->Set("requestHeaders", headers); } void ToDictionary(gin::Dictionary* details, const GURL& location) { details->Set("redirectURL", location); } void ToDictionary(gin::Dictionary* details, int net_error) { details->Set("error", net::ErrorToString(net_error)); } // Helper function to fill |details| with arbitrary |args|. template void FillDetails(gin::Dictionary* details, Arg arg) { ToDictionary(details, arg); } template void FillDetails(gin::Dictionary* details, Arg arg, Args... args) { ToDictionary(details, arg); FillDetails(details, args...); } // Fill the native types with the result from the response object. void ReadFromResponse(v8::Isolate* isolate, gin::Dictionary* response, GURL* new_location) { response->Get("redirectURL", new_location); } void ReadFromResponse(v8::Isolate* isolate, gin::Dictionary* response, net::HttpRequestHeaders* headers) { headers->Clear(); gin::ConvertFromV8(isolate, gin::ConvertToV8(isolate, *response), headers); } void ReadFromResponse(v8::Isolate* isolate, gin::Dictionary* response, const std::pair*, const std::string>& headers) { std::string status_line; if (!response->Get("statusLine", &status_line)) status_line = headers.second; v8::Local value; if (response->Get("responseHeaders", &value)) { *headers.first = new net::HttpResponseHeaders(""); (*headers.first)->ReplaceStatusLine(status_line); gin::Converter::FromV8(isolate, value, (*headers.first).get()); } } } // namespace gin::WrapperInfo WebRequestNS::kWrapperInfo = {gin::kEmbedderNativeGin}; WebRequestNS::SimpleListenerInfo::SimpleListenerInfo( std::set patterns_, SimpleListener listener_) : url_patterns(std::move(patterns_)), listener(listener_) {} WebRequestNS::SimpleListenerInfo::SimpleListenerInfo() = default; WebRequestNS::SimpleListenerInfo::~SimpleListenerInfo() = default; WebRequestNS::ResponseListenerInfo::ResponseListenerInfo( std::set patterns_, ResponseListener listener_) : url_patterns(std::move(patterns_)), listener(listener_) {} WebRequestNS::ResponseListenerInfo::ResponseListenerInfo() = default; WebRequestNS::ResponseListenerInfo::~ResponseListenerInfo() = default; WebRequestNS::WebRequestNS(v8::Isolate* isolate, content::BrowserContext* browser_context) : browser_context_(browser_context) { browser_context_->SetUserData(kUserDataKey, std::make_unique(this)); } WebRequestNS::~WebRequestNS() { browser_context_->RemoveUserData(kUserDataKey); } gin::ObjectTemplateBuilder WebRequestNS::GetObjectTemplateBuilder( v8::Isolate* isolate) { return gin::Wrappable::GetObjectTemplateBuilder(isolate) .SetMethod("onBeforeRequest", &WebRequestNS::SetResponseListener) .SetMethod("onBeforeSendHeaders", &WebRequestNS::SetResponseListener) .SetMethod("onHeadersReceived", &WebRequestNS::SetResponseListener) .SetMethod("onSendHeaders", &WebRequestNS::SetSimpleListener) .SetMethod("onBeforeRedirect", &WebRequestNS::SetSimpleListener) .SetMethod("onResponseStarted", &WebRequestNS::SetSimpleListener) .SetMethod("onErrorOccurred", &WebRequestNS::SetSimpleListener) .SetMethod("onCompleted", &WebRequestNS::SetSimpleListener); } const char* WebRequestNS::GetTypeName() { return "WebRequest"; } int WebRequestNS::OnBeforeRequest(extensions::WebRequestInfo* info, const network::ResourceRequest& request, net::CompletionOnceCallback callback, GURL* new_url) { return HandleResponseEvent(kOnBeforeRequest, info, std::move(callback), new_url, request); } int WebRequestNS::OnBeforeSendHeaders(extensions::WebRequestInfo* info, const network::ResourceRequest& request, BeforeSendHeadersCallback callback, net::HttpRequestHeaders* headers) { return HandleResponseEvent( kOnBeforeSendHeaders, info, base::BindOnce(std::move(callback), std::set(), std::set()), headers, request); } int WebRequestNS::OnHeadersReceived( extensions::WebRequestInfo* info, const network::ResourceRequest& request, net::CompletionOnceCallback callback, const net::HttpResponseHeaders* original_response_headers, scoped_refptr* override_response_headers, GURL* allowed_unsafe_redirect_url) { return HandleResponseEvent( kOnHeadersReceived, info, std::move(callback), std::make_pair(override_response_headers, original_response_headers->GetStatusLine()), request); } void WebRequestNS::OnSendHeaders(extensions::WebRequestInfo* info, const network::ResourceRequest& request, const net::HttpRequestHeaders& headers) { HandleSimpleEvent(kOnSendHeaders, info, request, headers); } void WebRequestNS::OnBeforeRedirect(extensions::WebRequestInfo* info, const network::ResourceRequest& request, const GURL& new_location) { HandleSimpleEvent(kOnBeforeRedirect, info, request, new_location); } void WebRequestNS::OnResponseStarted(extensions::WebRequestInfo* info, const network::ResourceRequest& request) { HandleSimpleEvent(kOnResponseStarted, info, request); } void WebRequestNS::OnErrorOccurred(extensions::WebRequestInfo* info, const network::ResourceRequest& request, int net_error) { callbacks_.erase(info->id); HandleSimpleEvent(kOnErrorOccurred, info, request, net_error); } void WebRequestNS::OnCompleted(extensions::WebRequestInfo* info, const network::ResourceRequest& request, int net_error) { callbacks_.erase(info->id); HandleSimpleEvent(kOnCompleted, info, request, net_error); } template void WebRequestNS::SetSimpleListener(gin::Arguments* args) { SetListener(event, &simple_listeners_, args); } template void WebRequestNS::SetResponseListener(gin::Arguments* args) { SetListener(event, &response_listeners_, args); } template void WebRequestNS::SetListener(Event event, Listeners* listeners, gin::Arguments* args) { // { urls }. std::set patterns; gin::Dictionary dict(args->isolate()); args->GetNext(&dict) && dict.Get("urls", &patterns); // Function or null. v8::Local value; Listener listener; if (!args->GetNext(&listener) && !(args->GetNext(&value) && value->IsNull())) { args->ThrowTypeError("Must pass null or a Function"); return; } if (listener.is_null()) listeners->erase(event); else (*listeners)[event] = {std::move(patterns), std::move(listener)}; } template void WebRequestNS::HandleSimpleEvent(SimpleEvent event, extensions::WebRequestInfo* request_info, Args... args) { const auto& info = simple_listeners_[event]; if (!MatchesFilterCondition(request_info, info.url_patterns)) return; v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::HandleScope handle_scope(isolate); gin::Dictionary details(isolate, v8::Object::New(isolate)); FillDetails(&details, request_info, args...); info.listener.Run(gin::ConvertToV8(isolate, details)); } template int WebRequestNS::HandleResponseEvent(ResponseEvent event, extensions::WebRequestInfo* request_info, net::CompletionOnceCallback callback, Out out, Args... args) { const auto& info = response_listeners_[event]; if (!MatchesFilterCondition(request_info, info.url_patterns)) return net::OK; callbacks_[request_info->id] = std::move(callback); v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::HandleScope handle_scope(isolate); gin::Dictionary details(isolate, v8::Object::New(isolate)); FillDetails(&details, request_info, args...); ResponseCallback response = base::BindOnce(&WebRequestNS::OnListenerResult, base::Unretained(this), request_info->id, out); info.listener.Run(gin::ConvertToV8(isolate, details), std::move(response)); return net::ERR_IO_PENDING; } template void WebRequestNS::OnListenerResult(uint64_t id, T out, v8::Local response) { if (!base::Contains(callbacks_, id)) return; int result = net::OK; if (response->IsObject()) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); gin::Dictionary dict(isolate, response.As()); bool cancel = false; dict.Get("cancel", &cancel); if (cancel) result = net::ERR_BLOCKED_BY_CLIENT; else ReadFromResponse(isolate, &dict, out); } std::move(callbacks_[id]).Run(result); } // static gin::Handle WebRequestNS::FromOrCreate( v8::Isolate* isolate, content::BrowserContext* browser_context) { gin::Handle handle = From(isolate, browser_context); if (handle.IsEmpty()) { // Make sure the |Session| object has the |webRequest| property created. v8::Local web_request = Session::CreateFrom(isolate, static_cast(browser_context)) ->WebRequest(isolate); gin::ConvertFromV8(isolate, web_request, &handle); } DCHECK(!handle.IsEmpty()); return handle; } // static gin::Handle WebRequestNS::Create( v8::Isolate* isolate, content::BrowserContext* browser_context) { DCHECK(From(isolate, browser_context).IsEmpty()) << "WebRequestNS already created"; return gin::CreateHandle(isolate, new WebRequestNS(isolate, browser_context)); } // static gin::Handle WebRequestNS::From( v8::Isolate* isolate, content::BrowserContext* browser_context) { if (!browser_context) return gin::Handle(); auto* user_data = static_cast(browser_context->GetUserData(kUserDataKey)); if (!user_data) return gin::Handle(); return gin::CreateHandle(isolate, user_data->data); } } // namespace api } // namespace electron