electron/shell/browser/api/electron_api_protocol.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

375 lines
14 KiB
C++
Raw Normal View History

// 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/electron_api_protocol.h"
#include <string_view>
#include <vector>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "content/common/url_schemes.h"
#include "content/public/browser/child_process_security_policy.h"
2020-03-26 10:34:32 -07:00
#include "gin/object_template_builder.h"
#include "shell/browser/browser.h"
#include "shell/browser/javascript_environment.h"
2020-03-26 10:34:32 -07:00
#include "shell/browser/protocol_registry.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/net_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/handle.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/options_switches.h"
#include "url/url_util.h"
namespace {
// List of registered custom standard schemes.
std::vector<std::string> g_standard_schemes;
// List of registered custom streaming schemes.
std::vector<std::string> g_streaming_schemes;
// Schemes that support V8 code cache.
std::vector<std::string> g_code_cache_schemes;
struct SchemeOptions {
bool standard = false;
bool secure = false;
bool bypassCSP = false;
bool allowServiceWorkers = false;
bool supportFetchAPI = false;
bool corsEnabled = false;
bool stream = false;
bool codeCache = false;
};
struct CustomScheme {
std::string scheme;
SchemeOptions options;
};
} // namespace
namespace gin {
template <>
struct Converter<CustomScheme> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
CustomScheme* out) {
gin::Dictionary dict(isolate);
if (!ConvertFromV8(isolate, val, &dict))
return false;
if (!dict.Get("scheme", &(out->scheme)))
return false;
gin::Dictionary opt(isolate);
// options are optional. Default values specified in SchemeOptions are used
if (dict.Get("privileges", &opt)) {
opt.Get("standard", &(out->options.standard));
opt.Get("secure", &(out->options.secure));
opt.Get("bypassCSP", &(out->options.bypassCSP));
opt.Get("allowServiceWorkers", &(out->options.allowServiceWorkers));
opt.Get("supportFetchAPI", &(out->options.supportFetchAPI));
opt.Get("corsEnabled", &(out->options.corsEnabled));
opt.Get("stream", &(out->options.stream));
opt.Get("codeCache", &(out->options.codeCache));
}
return true;
}
};
} // namespace gin
2022-06-29 12:55:47 -07:00
namespace electron::api {
chore: bump chromium to 140.0.7281.0 (38-x-y) (#47559) * chore: bump chromium in DEPS to 139.0.7258.6 * chore: bump chromium in DEPS to 139.0.7258.5 * chore: bump chromium in DEPS to 140.0.7270.1 * chore: bump chromium in DEPS to 140.0.7271.1 * chore: bump chromium in DEPS to 140.0.7273.0 * chore: bump chromium in DEPS to 140.0.7273.1 * chore: bump chromium in DEPS to 140.0.7275.1 * chore: bump chromium in DEPS to 140.0.7275.4 * chore: bump chromium in DEPS to 140.0.7277.1 * chore: bump chromium in DEPS to 140.0.7279.1 * chore: bump chromium in DEPS to 140.0.7281.1 * chore: bump chromium in DEPS to 140.0.7283.1 * chore: bump chromium in DEPS to 140.0.7285.1 * chore: bump chromium in DEPS to 140.0.7287.1 * chore: bump chromium in DEPS to 140.0.7289.0 * chore: bump chromium in DEPS to 140.0.7289.1 * chore: bump chromium in DEPS to 140.0.7291.1 * chore: bump chromium in DEPS to 140.0.7293.1 * chore: bump chromium in DEPS to 140.0.7295.1 * chore: bump chromium in DEPS to 140.0.7296.0 * chore: bump chromium to 140.0.7281.0 (main) (#47616) cherry picked from 603cafad7e82aa33485fb31e179ae0f116e26c7b * chore: bump chromium in DEPS to 140.0.7269.2 * chore: bump chromium in DEPS to 140.0.7270.0 * chore: bump chromium in DEPS to 140.0.7271.0 * chore: bump chromium in DEPS to 140.0.7273.0 * 6516731: [ExclusiveAccessForAndroid] remove unneeded includes & deps | https://chromium-review.googlesource.com/c/chromium/src/+/6516731 * 6694809: dbus: Ensure systemd scope is started before using any portal services | https://chromium-review.googlesource.com/c/chromium/src/+/6694809 * chore: patch chromium * chore: export patches * chore: bump chromium in DEPS to 140.0.7275.0 * 6677511: [pepper] More pepper removal | https://chromium-review.googlesource.com/c/chromium/src/+/6677511 * 6513641: [gin] Rename gin::Wrappable to gin::DeprecatedWrappable | https://chromium-review.googlesource.com/c/chromium/src/+/6513641 * chore: export chromium patches * 6513641: [gin] Rename gin::Wrappable to gin::DeprecatedWrappable | https://chromium-review.googlesource.com/c/chromium/src/+/6513641 * chore: bump chromium in DEPS to 140.0.7277.0 * chore: bump chromium in DEPS to 140.0.7279.0 * chore: bump chromium in DEPS to 140.0.7281.0 * 6677314: Plumb enabled client hints in the network requestion to network layer https://chromium-review.googlesource.com/c/chromium/src/+/6677314 * 6351556: [source-phase-imports] Support Wasm Source Phase Imports https://chromium-review.googlesource.com/c/chromium/src/+/6351556 * 6700077: [renderer] Avoid calls to deprecated GetIsolate methods https://chromium-review.googlesource.com/c/chromium/src/+/6700077 * 6692873: Reland "Reland "FSA: Only normalize the hardcoded rules once during initialization"" https://chromium-review.googlesource.com/c/chromium/src/+/6692873 * 6686234: [gin] Cleanup NamedPropertyInterceptor for Wrappable https://chromium-review.googlesource.com/c/chromium/src/+/6686234 * chore: export patches * 6667723: Remove content_enable_legacy_ipc GN arg. https://chromium-review.googlesource.com/c/chromium/src/+/6667723 * 6646566: ui: Move NativeWindowTracker to its own directory https://chromium-review.googlesource.com/c/chromium/src/+/6646566 * fix: add missing includes * 6580522: [WAR, DNR] Fix unsafe redirect error to web accessible resource https://chromium-review.googlesource.com/c/chromium/src/+/6580522 * 6680477: Implement `completeCode` endpoint and expose to DevTools https://chromium-review.googlesource.com/c/chromium/src/+/6680477 * 6677511: [pepper] More pepper removal https://chromium-review.googlesource.com/c/chromium/src/+/6677511 * 6696689: Rename views::WidgetFocusManager -> NativeViewFocusManager https://chromium-review.googlesource.com/c/chromium/src/+/6696689 * 6702812: Move wtf/text/string_impl*.* to "blink" namespace https://chromium-review.googlesource.com/c/chromium/src/+/6702812 * chore: fix dialog patch * 6702431: [animation-trigger] Parse timeline-trigger-name https://chromium-review.googlesource.com/c/chromium/src/+/6702431 * chore: fixup patch indices * feat: replace webFrame.routingId with webFrame.frameToken * feat: WebFrameMain.prototype.frameToken * test: refactor to use replacement APIs * chore: fixup pip patch * test: adjust webFrame tests for frameToken changes * 6703757: Reland "Enable -fsanitize=array-bounds in non-UBSan builds" https://chromium-review.googlesource.com/c/chromium/src/+/6703757 * test: switch to frameTokens * test: routingId is fine to test in the main process * docs: add routingId to breaking changes * docs: update plugin-crashed event * chore: fixup linux dialog patch --------- Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: alice <alice@makenotion.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> Co-authored-by: Samuel Maddock <smaddock@slack-corp.com> (cherry picked from commit 603cafad7e82aa33485fb31e179ae0f116e26c7b) --------- Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org> Co-authored-by: alice <alice@makenotion.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> Co-authored-by: Samuel Maddock <smaddock@slack-corp.com>
2025-07-15 12:05:29 -04:00
gin::DeprecatedWrapperInfo Protocol::kWrapperInfo = {gin::kEmbedderNativeGin};
2020-03-26 10:34:32 -07:00
const std::vector<std::string>& GetStandardSchemes() {
return g_standard_schemes;
}
const std::vector<std::string>& GetCodeCacheSchemes() {
return g_code_cache_schemes;
}
void AddServiceWorkerScheme(const std::string& scheme) {
// There is no API to add service worker scheme, but there is an API to
// return const reference to the schemes vector.
// If in future the API is changed to return a copy instead of reference,
// the compilation will fail, and we should add a patch at that time.
auto& mutable_schemes =
const_cast<std::vector<std::string>&>(content::GetServiceWorkerSchemes());
mutable_schemes.push_back(scheme);
}
void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower,
v8::Local<v8::Value> val) {
std::vector<CustomScheme> custom_schemes;
if (!gin::ConvertFromV8(thrower.isolate(), val, &custom_schemes)) {
thrower.ThrowError("Argument must be an array of custom schemes.");
return;
}
for (const auto& custom_scheme : custom_schemes) {
if (custom_scheme.options.codeCache && !custom_scheme.options.standard) {
thrower.ThrowError(
"Code cache can only be enabled when the custom scheme is registered "
"as standard scheme.");
return;
}
}
std::vector<std::string> secure_schemes, cspbypassing_schemes, fetch_schemes,
service_worker_schemes, cors_schemes;
for (const auto& custom_scheme : custom_schemes) {
// Register scheme to privileged list (https, wss, data, chrome-extension)
if (custom_scheme.options.standard) {
auto* policy = content::ChildProcessSecurityPolicy::GetInstance();
url::AddStandardScheme(custom_scheme.scheme.c_str(),
url::SCHEME_WITH_HOST);
g_standard_schemes.push_back(custom_scheme.scheme);
policy->RegisterWebSafeScheme(custom_scheme.scheme);
}
if (custom_scheme.options.secure) {
secure_schemes.push_back(custom_scheme.scheme);
url::AddSecureScheme(custom_scheme.scheme.c_str());
}
if (custom_scheme.options.bypassCSP) {
cspbypassing_schemes.push_back(custom_scheme.scheme);
url::AddCSPBypassingScheme(custom_scheme.scheme.c_str());
}
if (custom_scheme.options.corsEnabled) {
cors_schemes.push_back(custom_scheme.scheme);
url::AddCorsEnabledScheme(custom_scheme.scheme.c_str());
}
if (custom_scheme.options.supportFetchAPI) {
fetch_schemes.push_back(custom_scheme.scheme);
}
if (custom_scheme.options.allowServiceWorkers) {
service_worker_schemes.push_back(custom_scheme.scheme);
AddServiceWorkerScheme(custom_scheme.scheme);
}
if (custom_scheme.options.stream) {
g_streaming_schemes.push_back(custom_scheme.scheme);
}
if (custom_scheme.options.codeCache) {
g_code_cache_schemes.push_back(custom_scheme.scheme);
url::AddCodeCacheScheme(custom_scheme.scheme.c_str());
}
}
refactor: more inline constexpr string view (#44498) * chore: remove unused electron::kHidProductIdKey * refactor: make electron::kHidDeviceNameKey inline constexpr std::string_view refactor: make electron::kHidGuidKey inline constexpr std::string_view * refactor: make serial_chooser_context keys inline constexpr std::string_view * refactor: make native_window keys inline constexpr std::string_view * refactor: make electron::options keys inline constexpr std::string_view pt 1 * fixup! refactor: make electron::options keys inline constexpr std::string_view pt 1 chore: make kElectronNativeWindowKey a base::cstring_view ui::Widget::GetNativeWindowProperty requires a const char* * refactor: make electron::options keys inline constexpr std::string_view pt 2 * refactor: make electron::options keys inline constexpr std::string_view pt 3 * refactor: make electron::options keys inline constexpr std::string_view pt 4 * refactor: make electron::options keys inline constexpr std::string_view pt 5 * refactor: make electron::options keys inline constexpr std::string_view pt 6 * refactor: make electron::options keys inline constexpr std::string_view pt 7 * refactor: make electron::options keys inline constexpr std::string_view pt 8 * chore: remove unused file shell/common/options_switches.cc * docs: add code comment explaining use of base::cstring_view * fixup! fixup! refactor: make electron::options keys inline constexpr std::string_view pt 1 * chore: use consistent capitalization of the word 'fullscreen'
2024-11-04 12:27:49 -06:00
const auto AppendSchemesToCmdLine = [](const std::string_view switch_name,
std::vector<std::string> schemes) {
if (schemes.empty())
return;
// Add the schemes to command line switches, so child processes can also
// register them.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switch_name, base::JoinString(schemes, ","));
};
AppendSchemesToCmdLine(electron::switches::kSecureSchemes, secure_schemes);
AppendSchemesToCmdLine(electron::switches::kBypassCSPSchemes,
cspbypassing_schemes);
AppendSchemesToCmdLine(electron::switches::kCORSSchemes, cors_schemes);
AppendSchemesToCmdLine(electron::switches::kFetchSchemes, fetch_schemes);
AppendSchemesToCmdLine(electron::switches::kServiceWorkerSchemes,
service_worker_schemes);
AppendSchemesToCmdLine(electron::switches::kStandardSchemes,
g_standard_schemes);
AppendSchemesToCmdLine(electron::switches::kStreamingSchemes,
g_streaming_schemes);
AppendSchemesToCmdLine(electron::switches::kCodeCacheSchemes,
g_code_cache_schemes);
}
namespace {
const char* const kBuiltinSchemes[] = {
"about", "file", "http", "https", "data", "filesystem",
};
} // namespace
Protocol::Protocol(ProtocolRegistry* protocol_registry)
: protocol_registry_{protocol_registry} {}
// Convert error code to string.
// static
std::string_view Protocol::ErrorCodeToString(Error error) {
switch (error) {
case Error::kRegistered:
return "The scheme has been registered";
case Error::kNotRegistered:
return "The scheme has not been registered";
case Error::kIntercepted:
return "The scheme has been intercepted";
case Error::kNotIntercepted:
return "The scheme has not been intercepted";
default:
return "Unexpected error";
}
}
Protocol::Error Protocol::RegisterProtocol(ProtocolType type,
const std::string& scheme,
const ProtocolHandler& handler) {
2020-03-26 10:34:32 -07:00
bool added = protocol_registry_->RegisterProtocol(type, scheme, handler);
return added ? Error::kOK : Error::kRegistered;
}
bool Protocol::UnregisterProtocol(const std::string& scheme,
gin::Arguments* args) {
2020-03-26 10:34:32 -07:00
bool removed = protocol_registry_->UnregisterProtocol(scheme);
HandleOptionalCallback(args, removed ? Error::kOK : Error::kNotRegistered);
return removed;
}
bool Protocol::IsProtocolRegistered(const std::string& scheme) {
return protocol_registry_->FindRegistered(scheme) != nullptr;
}
Protocol::Error Protocol::InterceptProtocol(ProtocolType type,
const std::string& scheme,
const ProtocolHandler& handler) {
2020-03-26 10:34:32 -07:00
bool added = protocol_registry_->InterceptProtocol(type, scheme, handler);
return added ? Error::kOK : Error::kIntercepted;
}
bool Protocol::UninterceptProtocol(const std::string& scheme,
gin::Arguments* args) {
2020-03-26 10:34:32 -07:00
bool removed = protocol_registry_->UninterceptProtocol(scheme);
HandleOptionalCallback(args, removed ? Error::kOK : Error::kNotIntercepted);
return removed;
}
bool Protocol::IsProtocolIntercepted(const std::string& scheme) {
return protocol_registry_->FindIntercepted(scheme) != nullptr;
}
v8::Local<v8::Promise> Protocol::IsProtocolHandled(const std::string& scheme,
gin::Arguments* args) {
util::EmitWarning(args->isolate(),
"The protocol.isProtocolHandled API is deprecated, "
"use protocol.isProtocolRegistered "
"or protocol.isProtocolIntercepted instead.",
"ProtocolDeprecateIsProtocolHandled");
return gin_helper::Promise<bool>::ResolvedPromise(
2020-03-26 10:34:32 -07:00
args->isolate(),
IsProtocolRegistered(scheme) || IsProtocolIntercepted(scheme) ||
// The |isProtocolHandled| should return true for builtin
// schemes, however with NetworkService it is impossible to
// know which schemes are registered until a real network
// request is sent.
// So we have to test against a hard-coded builtin schemes
// list make it work with old code. We should deprecate
// this API with the new |isProtocolRegistered| API.
base::Contains(kBuiltinSchemes, scheme));
}
void Protocol::HandleOptionalCallback(gin::Arguments* args, Error error) {
2020-03-26 10:34:32 -07:00
base::RepeatingCallback<void(v8::Local<v8::Value>)> callback;
if (args->GetNext(&callback)) {
util::EmitWarning(
args->isolate(),
"The callback argument of protocol module APIs is no longer needed.",
"ProtocolDeprecateCallback");
if (error == Error::kOK)
callback.Run(v8::Null(args->isolate()));
else
callback.Run(v8::Exception::Error(
2020-03-26 10:34:32 -07:00
gin::StringToV8(args->isolate(), ErrorCodeToString(error))));
}
}
// static
gin_helper::Handle<Protocol> Protocol::Create(
v8::Isolate* isolate,
ProtocolRegistry* protocol_registry) {
return gin_helper::CreateHandle(isolate, new Protocol{protocol_registry});
}
2023-03-27 10:00:55 -07:00
// static
gin_helper::Handle<Protocol> Protocol::New(gin_helper::ErrorThrower thrower) {
2023-03-27 10:00:55 -07:00
thrower.ThrowError("Protocol cannot be created from JS");
return {};
2023-03-27 10:00:55 -07:00
}
// static
v8::Local<v8::ObjectTemplate> Protocol::FillObjectTemplate(
v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> tmpl) {
return gin::ObjectTemplateBuilder(isolate, GetClassName(), tmpl)
.SetMethod("registerStringProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kString>)
.SetMethod("registerBufferProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kBuffer>)
.SetMethod("registerFileProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kFile>)
.SetMethod("registerHttpProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kHttp>)
.SetMethod("registerStreamProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kStream>)
.SetMethod("registerProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kFree>)
.SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol)
.SetMethod("isProtocolRegistered", &Protocol::IsProtocolRegistered)
.SetMethod("isProtocolHandled", &Protocol::IsProtocolHandled)
.SetMethod("interceptStringProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kString>)
.SetMethod("interceptBufferProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kBuffer>)
.SetMethod("interceptFileProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kFile>)
.SetMethod("interceptHttpProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kHttp>)
.SetMethod("interceptStreamProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kStream>)
.SetMethod("interceptProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kFree>)
.SetMethod("uninterceptProtocol", &Protocol::UninterceptProtocol)
2023-03-27 10:00:55 -07:00
.SetMethod("isProtocolIntercepted", &Protocol::IsProtocolIntercepted)
.Build();
}
2020-03-26 10:34:32 -07:00
const char* Protocol::GetTypeName() {
return GetClassName();
2020-03-26 10:34:32 -07:00
}
2022-06-29 12:55:47 -07:00
} // namespace electron::api
namespace {
void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower,
v8::Local<v8::Value> val) {
if (electron::Browser::Get()->is_ready()) {
thrower.ThrowError(
"protocol.registerSchemesAsPrivileged should be called before "
"app is ready");
return;
}
electron::api::RegisterSchemesAsPrivileged(thrower, val);
}
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* const isolate = electron::JavascriptEnvironment::GetIsolate();
gin_helper::Dictionary dict{isolate, exports};
dict.Set("Protocol",
electron::api::Protocol::GetConstructor(isolate, context));
dict.SetMethod("registerSchemesAsPrivileged", &RegisterSchemesAsPrivileged);
dict.SetMethod("getStandardSchemes", &electron::api::GetStandardSchemes);
}
} // namespace
NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_protocol, Initialize)