diff --git a/chromium_src/ios/chrome/browser/shared/model/profile/profile_ios.mm b/chromium_src/ios/chrome/browser/shared/model/profile/profile_ios.mm new file mode 100644 index 000000000000..68969c67c3fb --- /dev/null +++ b/chromium_src/ios/chrome/browser/shared/model/profile/profile_ios.mm @@ -0,0 +1,31 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +// URLRequestJobFactory::CreateJob checks the protocol_handler_map_ +// to see what requests can be handled +// The FactoryForMain contains the ProtocolHandlerMap +// This is initialized via ProfileIOSIOData::Init(ProtocolHandlerMap* +// protocol_handlers) Which is called via ProfileIOS::GetRequestContext + +#include "ios/chrome/browser/shared/model/profile/profile_ios.h" + +#include "ios/components/webui/web_ui_url_constants.h" +#include "ios/web/webui/url_data_manager_ios_backend.h" + +// Add the chrome-untrusted scheme +auto CreateRequestContext_Brave(ProfileIOS* profile) { + return [profile](ProtocolHandlerMap* protocol_handlers) { + protocol_handlers->insert( + {kChromeUIUntrustedScheme, + web::URLDataManagerIOSBackend::CreateProtocolHandler(profile)}); + return profile->CreateRequestContext(protocol_handlers); + }; +} + +#define CreateRequestContext CreateRequestContext_Brave(this) + +#include "src/ios/chrome/browser/shared/model/profile/profile_ios.mm" + +#undef CreateRequestContext diff --git a/chromium_src/ios/components/webui/web_ui_url_constants.cc b/chromium_src/ios/components/webui/web_ui_url_constants.cc new file mode 100644 index 000000000000..66f4725efcbe --- /dev/null +++ b/chromium_src/ios/components/webui/web_ui_url_constants.cc @@ -0,0 +1,8 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "src/ios/components/webui/web_ui_url_constants.cc" + +const char kChromeUIUntrustedScheme[] = "chrome-untrusted"; diff --git a/chromium_src/ios/components/webui/web_ui_url_constants.h b/chromium_src/ios/components/webui/web_ui_url_constants.h new file mode 100644 index 000000000000..dd32a6b0c3af --- /dev/null +++ b/chromium_src/ios/components/webui/web_ui_url_constants.h @@ -0,0 +1,13 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_CHROMIUM_SRC_IOS_COMPONENTS_WEBUI_WEB_UI_URL_CONSTANTS_H_ +#define BRAVE_CHROMIUM_SRC_IOS_COMPONENTS_WEBUI_WEB_UI_URL_CONSTANTS_H_ + +extern const char kChromeUIUntrustedScheme[]; + +#include "src/ios/components/webui/web_ui_url_constants.h" // IWYU pragma: export + +#endif // BRAVE_CHROMIUM_SRC_IOS_COMPONENTS_WEBUI_WEB_UI_URL_CONSTANTS_H_ diff --git a/chromium_src/ios/web/public/webui/url_data_source_ios.h b/chromium_src/ios/web/public/webui/url_data_source_ios.h new file mode 100644 index 000000000000..e1651d5251ca --- /dev/null +++ b/chromium_src/ios/web/public/webui/url_data_source_ios.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_CHROMIUM_SRC_IOS_WEB_PUBLIC_WEBUI_URL_DATA_SOURCE_IOS_H_ +#define BRAVE_CHROMIUM_SRC_IOS_WEB_PUBLIC_WEBUI_URL_DATA_SOURCE_IOS_H_ + +#define GetContentSecurityPolicyObjectSrc \ + GetContentSecurityPolicyObjectSrc() const; \ + virtual std::string GetContentSecurityPolicyFrameSrc + +#import "src/ios/web/public/webui/url_data_source_ios.h" // IWYU pragma: export + +#undef GetContentSecurityPolicyObjectSrc + +#endif // BRAVE_CHROMIUM_SRC_IOS_WEB_PUBLIC_WEBUI_URL_DATA_SOURCE_IOS_H_ diff --git a/chromium_src/ios/web/webui/BUILD.gn b/chromium_src/ios/web/webui/BUILD.gn new file mode 100644 index 000000000000..598531e22f38 --- /dev/null +++ b/chromium_src/ios/web/webui/BUILD.gn @@ -0,0 +1,8 @@ +# Copyright (c) 2024 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +source_set("webui") { + deps = [ "//ios/components/webui:web_ui_url_constants" ] +} diff --git a/chromium_src/ios/web/webui/DEPS b/chromium_src/ios/web/webui/DEPS new file mode 100644 index 000000000000..180617c759a1 --- /dev/null +++ b/chromium_src/ios/web/webui/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+ios/components/webui/web_ui_url_constants.h", + "+ios/chrome/browser/shared/model/url/chrome_url_constants.h", + "+brave/ios/browser/ui/webui/brave_url_data_source_ios.h", +] diff --git a/chromium_src/ios/web/webui/crw_web_ui_scheme_handler.mm b/chromium_src/ios/web/webui/crw_web_ui_scheme_handler.mm new file mode 100644 index 000000000000..9b9105d30e71 --- /dev/null +++ b/chromium_src/ios/web/webui/crw_web_ui_scheme_handler.mm @@ -0,0 +1,78 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#import "ios/web/webui/crw_web_ui_scheme_handler.h" + +#include + +#import "base/files/file_path.h" +#import "base/ranges/algorithm.h" +#import "base/strings/sys_string_conversions.h" +#import "ios/web/webui/url_fetcher_block_adapter.h" +#import "ios/web/webui/web_ui_ios_controller_factory_registry.h" +#import "net/base/apple/url_conversions.h" +#import "url/gurl.h" + +@interface CRWWebUISchemeHandler (Override) +- (void)dummy:(NSHTTPURLResponse*)response; +- (NSHTTPURLResponse*)processResponse:(NSHTTPURLResponse*)response + fetcher:(web::URLFetcherBlockAdapter*)fetcher; +@end + +// Override + +#define didReceiveResponse \ + didReceiveResponse:[strongSelf processResponse:response fetcher:fetcher]]; \ + [strongSelf dummy + +#include "src/ios/web/webui/crw_web_ui_scheme_handler.mm" + +#undef didReceiveResponse + +@implementation CRWWebUISchemeHandler (Override) +- (void)dummy:(NSHTTPURLResponse*)response { +} + +- (NSHTTPURLResponse*)processResponse:(NSHTTPURLResponse*)response + fetcher:(web::URLFetcherBlockAdapter*)fetcher { + const network::mojom::URLResponseHeadPtr responseHead = + fetcher->getResponse(); + if (responseHead) { + const scoped_refptr headers = + responseHead->headers; + if (headers) { + NSMutableDictionary* responseHeaders = [self parseHeaders:headers]; + + if (![responseHeaders objectForKey:@"Content-Type"]) { + [responseHeaders setObject:[response MIMEType] forKey:@"Content-Type"]; + } + + if (![responseHeaders objectForKey:@"Access-Control-Allow-Origin"]) { + [responseHeaders setObject:@"*" forKey:@"Access-Control-Allow-Origin"]; + } + + return [[NSHTTPURLResponse alloc] initWithURL:[response URL] + statusCode:[response statusCode] + HTTPVersion:@"HTTP/1.1" + headerFields:responseHeaders]; + } + } + return response; +} + +- (NSMutableDictionary*)parseHeaders: + (const scoped_refptr&)headers { + NSMutableDictionary* result = [[NSMutableDictionary alloc] init]; + + std::size_t iterator = 0; + std::string name, value; + while (headers->EnumerateHeaderLines(&iterator, &name, &value)) { + [result setObject:base::SysUTF8ToNSString(value) + forKey:base::SysUTF8ToNSString(name)]; + } + + return result; +} +@end diff --git a/chromium_src/ios/web/webui/url_data_manager_ios_backend.mm b/chromium_src/ios/web/webui/url_data_manager_ios_backend.mm new file mode 100644 index 000000000000..997a7d782911 --- /dev/null +++ b/chromium_src/ios/web/webui/url_data_manager_ios_backend.mm @@ -0,0 +1,15 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "ios/web/webui/url_data_manager_ios_backend.h" + +#define ShouldDenyXFrameOptions ShouldDenyXFrameOptions()); \ + job->set_content_security_policy_frame_source( \ + source->source()->GetContentSecurityPolicyFrameSrc()); \ + void(void + +#include "src/ios/web/webui/url_data_manager_ios_backend.mm" + +#undef ShouldDenyXFrameOptions diff --git a/chromium_src/ios/web/webui/url_data_source_ios.mm b/chromium_src/ios/web/webui/url_data_source_ios.mm new file mode 100644 index 000000000000..2bdda3d3c90b --- /dev/null +++ b/chromium_src/ios/web/webui/url_data_source_ios.mm @@ -0,0 +1,18 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "ios/web/public/webui/url_data_source_ios.h" + +namespace web { + +std::string URLDataSourceIOS::GetContentSecurityPolicyFrameSrc() const { + // Default for iOS: + // https://source.chromium.org/chromium/chromium/src/+/main:ios/web/webui/url_data_manager_ios_backend.mm;l=511?q=set_content_security_policy_frame_source&ss=chromium%2Fchromium%2Fsrc + return "frame-src 'none';"; +} + +} // namespace web + +#include "src/ios/web/webui/url_data_source_ios.mm" diff --git a/chromium_src/ios/web/webui/url_fetcher_block_adapter.h b/chromium_src/ios/web/webui/url_fetcher_block_adapter.h new file mode 100644 index 000000000000..38ebdf2347ef --- /dev/null +++ b/chromium_src/ios/web/webui/url_fetcher_block_adapter.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_CHROMIUM_SRC_IOS_WEB_WEBUI_URL_FETCHER_BLOCK_ADAPTER_H_ +#define BRAVE_CHROMIUM_SRC_IOS_WEB_WEBUI_URL_FETCHER_BLOCK_ADAPTER_H_ + +#include "base/memory/raw_ptr.h" +#include "services/network/public/mojom/url_response_head.mojom.h" + +#define completion_handler_ \ + completion_handler_; \ + \ + public: \ + const network::mojom::URLResponseHeadPtr getResponse() { \ + return response_.Clone(); \ + } \ + \ + private: \ + network::mojom::URLResponseHeadPtr response_ + +#include "src/ios/web/webui/url_fetcher_block_adapter.h" // IWYU pragma: export + +#undef completion_handler_ + +#endif // BRAVE_CHROMIUM_SRC_IOS_WEB_WEBUI_URL_FETCHER_BLOCK_ADAPTER_H_ diff --git a/chromium_src/ios/web/webui/url_fetcher_block_adapter.mm b/chromium_src/ios/web/webui/url_fetcher_block_adapter.mm new file mode 100644 index 000000000000..beba3959dde5 --- /dev/null +++ b/chromium_src/ios/web/webui/url_fetcher_block_adapter.mm @@ -0,0 +1,21 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "ios/web/webui/url_fetcher_block_adapter.h" + +#include "services/network/public/cpp/simple_url_loader.h" + +#define GetFinalURL \ + GetFinalURL(); \ + } \ + \ + response_ = url_loader_->TakeResponseInfo(); \ + \ + if (!response_body) { \ + void + +#include "src/ios/web/webui/url_fetcher_block_adapter.mm" + +#undef GetFinalURL diff --git a/chromium_src/ios/web/webui/web_ui_ios_data_source_impl.h b/chromium_src/ios/web/webui/web_ui_ios_data_source_impl.h new file mode 100644 index 000000000000..4a6881e504aa --- /dev/null +++ b/chromium_src/ios/web/webui/web_ui_ios_data_source_impl.h @@ -0,0 +1,51 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_CHROMIUM_SRC_IOS_WEB_WEBUI_WEB_UI_IOS_DATA_SOURCE_IMPL_H_ +#define BRAVE_CHROMIUM_SRC_IOS_WEB_WEBUI_WEB_UI_IOS_DATA_SOURCE_IMPL_H_ + +#include +#include +#include + +#include "base/containers/flat_map.h" +#include "ios/web/public/webui/web_ui_ios_data_source.h" +#include "services/network/public/mojom/content_security_policy.mojom.h" + +class BraveWebUIIOSDataSource; + +#define should_replace_i18n_in_js_ \ + should_replace_i18n_in_js_; \ + friend BraveWebUIIOSDataSource +#include "src/ios/web/webui/web_ui_ios_data_source_impl.h" // IWYU pragma: export +#undef should_replace_i18n_in_js_ + +class BraveWebUIIOSDataSource : public web::WebUIIOSDataSourceImpl { + public: + static web::WebUIIOSDataSource* Create(const std::string& source_name); + + // Brave CSP's & Security implementation: + virtual void OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName directive, + const std::string& value); + + virtual void AddFrameAncestor(const GURL& frame_ancestor); + virtual void DisableTrustedTypesCSP(); + + protected: + ~BraveWebUIIOSDataSource() override; + + private: + class InternalDataSource; + friend class InternalDataSource; + friend class WebUIIOSDataSourceImpl; + explicit BraveWebUIIOSDataSource(const std::string& source_name); + + // Brave CSP's & Security variables: + base::flat_map csp_overrides_; + std::set frame_ancestors_; +}; + +#endif // BRAVE_CHROMIUM_SRC_IOS_WEB_WEBUI_WEB_UI_IOS_DATA_SOURCE_IMPL_H_ diff --git a/chromium_src/ios/web/webui/web_ui_ios_data_source_impl.mm b/chromium_src/ios/web/webui/web_ui_ios_data_source_impl.mm new file mode 100644 index 000000000000..a7113ac8e484 --- /dev/null +++ b/chromium_src/ios/web/webui/web_ui_ios_data_source_impl.mm @@ -0,0 +1,118 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "ios/web/webui/web_ui_ios_data_source_impl.h" + +#include "base/memory/raw_ptr.h" +#include "brave/ios/browser/ui/webui/brave_url_data_source_ios.h" +#include "ios/chrome/browser/shared/model/url/chrome_url_constants.h" +#include "ios/components/webui/web_ui_url_constants.h" + +// static +web::WebUIIOSDataSource* BraveWebUIIOSDataSource::Create( + const std::string& source_name) { + return new BraveWebUIIOSDataSource(source_name); +} + +class BraveWebUIIOSDataSource::InternalDataSource + : public BraveURLDataSourceIOS { + public: + InternalDataSource(web::WebUIIOSDataSourceImpl* parent) : parent_(parent) {} + ~InternalDataSource() override {} + + std::string GetSource() const override { return parent_->GetSource(); } + + void StartDataRequest( + const std::string& path, + web::URLDataSourceIOS::GotDataCallback callback) override { + return parent_->StartDataRequest(path, std::move(callback)); + } + + std::string GetMimeType(const std::string& path) const override { + return parent_->GetMimeType(path); + } + + bool ShouldReplaceExistingSource() const override { + return parent_->replace_existing_source_; + } + + bool ShouldReplaceI18nInJS() const override { + return parent_->ShouldReplaceI18nInJS(); + } + + bool AllowCaching() const override { return false; } + + bool ShouldDenyXFrameOptions() const override { + return parent_->deny_xframe_options_; + } + + std::string GetContentSecurityPolicy( + network::mojom::CSPDirectiveName directive) const override { + auto* parent = static_cast(parent_); + + // Check CSP overrides + if (parent->csp_overrides_.contains(directive)) { + return parent->csp_overrides_.at(directive); + } + + // Check Frame-Ancestors overrides + if (directive == network::mojom::CSPDirectiveName::FrameAncestors) { + std::string frame_ancestors; + if (parent->frame_ancestors_.empty()) { + frame_ancestors += " 'none'"; + } + + for (const GURL& frame_ancestor : parent->frame_ancestors_) { + frame_ancestors += " " + frame_ancestor.spec(); + } + return "frame-ancestors" + frame_ancestors + ";"; + } + + return BraveURLDataSourceIOS::GetContentSecurityPolicy(directive); + } + + private: + raw_ptr parent_; +}; + +// WebUIIOSDataSource implementation: + +BraveWebUIIOSDataSource::BraveWebUIIOSDataSource(const std::string& source_name) + : web::WebUIIOSDataSourceImpl(source_name) { + CHECK(!source_name.ends_with("://")); +} + +BraveWebUIIOSDataSource::~BraveWebUIIOSDataSource() = default; + +// Brave CSP's & Security implementation: + +void BraveWebUIIOSDataSource::OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName directive, + const std::string& value) { + csp_overrides_.insert_or_assign(directive, value); +} + +void BraveWebUIIOSDataSource::AddFrameAncestor(const GURL& frame_ancestor) { + // Do not allow a wildcard to be a frame ancestor or it will allow any website + // to embed the WebUI. + CHECK(frame_ancestor.SchemeIs(kChromeUIScheme)); + frame_ancestors_.insert(frame_ancestor); +} + +void BraveWebUIIOSDataSource::DisableTrustedTypesCSP() { + // TODO(crbug.com/40137141): Trusted Type remaining WebUI + // This removes require-trusted-types-for and trusted-types directives + // from the CSP header. + OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::RequireTrustedTypesFor, std::string()); + OverrideContentSecurityPolicy(network::mojom::CSPDirectiveName::TrustedTypes, + std::string()); +} + +#define URLDataSourceIOSImpl(SOURCE_NAME, DATA_SOURCE) \ + URLDataSourceIOSImpl(SOURCE_NAME, \ + new BraveWebUIIOSDataSource::InternalDataSource(this)) +#include "src/ios/web/webui/web_ui_ios_data_source_impl.mm" +#undef URLDataSourceIOSImpl diff --git a/ios/browser/api/web/ui/chrome_webview.h b/ios/browser/api/web/ui/chrome_webview.h index 527688218949..9a03600bffbb 100644 --- a/ios/browser/api/web/ui/chrome_webview.h +++ b/ios/browser/api/web/ui/chrome_webview.h @@ -12,15 +12,35 @@ NS_ASSUME_NONNULL_BEGIN +@protocol ChromeWebViewControllerUIDelegate; OBJC_EXPORT @interface ChromeWebViewController : UIViewController @property(nonatomic, readonly) WKWebView* webView; @property(nonatomic, readonly) bool isOffTheRecord; +@property(nonatomic, weak) id delegate; - (instancetype)initWithPrivateBrowsing:(bool)isPrivateBrowsing; - (void)loadURL:(NSString*)urlString; @end +OBJC_EXPORT +@protocol ChromeWebViewControllerUIDelegate + +/** + * Called when the web page wants to open a new window. + * + * @param URL The url of the new window. + * @param openerURL the URL of the page which requested a window to be open. + * @param initiatedByUser If action was caused by the user. + */ +@optional +- (void)chromeWebViewController: + (ChromeWebViewController*)chromeWebViewController + openNewWindowFor:(const NSURL* _Nullable)newWindowURL + openerURL:(const NSURL* _Nullable)openerURL + initiatedByUser:(BOOL)initiatedByUser; +@end + NS_ASSUME_NONNULL_END #endif // BRAVE_IOS_BROWSER_API_WEB_UI_CHROME_WEBVIEW_H_ diff --git a/ios/browser/api/web/ui/chrome_webview.mm b/ios/browser/api/web/ui/chrome_webview.mm index 7853fdd50ea3..5bf3cb2bc5cf 100644 --- a/ios/browser/api/web/ui/chrome_webview.mm +++ b/ios/browser/api/web/ui/chrome_webview.mm @@ -31,7 +31,7 @@ @class ChromeWebViewController; -@protocol ChromeWebControllerUIDelegate +@protocol WebViewJavaScriptDialogPresenterUIDelegate @optional - (void)webController:(ChromeWebViewController*)controller runJavaScriptAlertPanelWithMessage:(NSString*)message @@ -64,7 +64,8 @@ - (void)webController:(ChromeWebViewController*)controller ~WebViewJavaScriptDialogPresenter() override; - void SetUIDelegate(id ui_delegate); + void SetUIDelegate( + id ui_delegate); // web::JavaScriptDialogPresenter overrides: void RunJavaScriptAlertDialog(web::WebState* web_state, @@ -86,7 +87,7 @@ void RunJavaScriptPromptDialog( private: __weak ChromeWebViewController* controller_ = nil; - __weak id ui_delegate_ = nil; + __weak id ui_delegate_ = nil; }; WebViewJavaScriptDialogPresenter::WebViewJavaScriptDialogPresenter( @@ -156,14 +157,15 @@ void RunJavaScriptPromptDialog( } void WebViewJavaScriptDialogPresenter::SetUIDelegate( - id ui_delegate) { + id ui_delegate) { ui_delegate_ = ui_delegate; } } // namespace -@interface ChromeWebViewController () { +@interface ChromeWebViewController () < + CRWWebStateDelegate, + CRWWebStateObserver, + WebViewJavaScriptDialogPresenterUIDelegate> { raw_ptr profile_; std::unique_ptr web_state_; std::unique_ptr web_state_observer_; @@ -276,7 +278,7 @@ - (void)webStateDestroyed:(web::WebState*)webState { return dialog_presenter_.get(); } -// MARK: - ChromeWebControllerUIDelegate implementation +// MARK: - WebViewJavaScriptDialogPresenter implementation - (void)webController:(ChromeWebViewController*)controller runJavaScriptAlertPanelWithMessage:(NSString*)message @@ -350,4 +352,22 @@ - (void)webController:(ChromeWebViewController*)controller [controller presentViewController:alert animated:YES completion:nil]; } + +- (web::WebState*)webState:(web::WebState*)webState + createNewWebStateForURL:(const GURL&)URL + openerURL:(const GURL&)openerURL + initiatedByUser:(BOOL)initiatedByUser { + SEL delegate_method = @selector + (chromeWebViewController:openNewWindowFor:openerURL:initiatedByUser:); + if ([_delegate respondsToSelector:delegate_method]) { + NSURL* nsURL = net::NSURLWithGURL(URL); + NSURL* nsOpenerURL = net::NSURLWithGURL(openerURL); + [_delegate chromeWebViewController:self + openNewWindowFor:nsURL + openerURL:nsOpenerURL + initiatedByUser:initiatedByUser]; + } + // return nil to indicate `webState` not to open new window. + return nil; +} @end diff --git a/ios/browser/brave_web_client.h b/ios/browser/brave_web_client.h index f71eb62626af..faf2f22c5f1d 100644 --- a/ios/browser/brave_web_client.h +++ b/ios/browser/brave_web_client.h @@ -35,6 +35,9 @@ class BraveWebClient : public ChromeWebClient { void PostBrowserURLRewriterCreation( web::BrowserURLRewriter* rewriter) override; + void GetAdditionalWebUISchemes( + std::vector* additional_schemes) override; + private: std::string user_agent_; }; diff --git a/ios/browser/brave_web_client.mm b/ios/browser/brave_web_client.mm index fa0e605e9ca0..6f85ebc6b2ff 100644 --- a/ios/browser/brave_web_client.mm +++ b/ios/browser/brave_web_client.mm @@ -44,10 +44,14 @@ schemes->standard_schemes.push_back(kBraveUIScheme); schemes->secure_schemes.push_back(kBraveUIScheme); + + schemes->standard_schemes.push_back(kChromeUIUntrustedScheme); + schemes->secure_schemes.push_back(kChromeUIUntrustedScheme); } bool BraveWebClient::IsAppSpecificURL(const GURL& url) const { - return ChromeWebClient::IsAppSpecificURL(url) || url.SchemeIs(kBraveUIScheme); + return ChromeWebClient::IsAppSpecificURL(url) || + url.SchemeIs(kBraveUIScheme) || url.SchemeIs(kChromeUIUntrustedScheme); } bool WillHandleBraveURLRedirect(GURL* url, web::BrowserState* browser_state) { @@ -71,3 +75,8 @@ bool WillHandleBraveURLRedirect(GURL* url, web::BrowserState* browser_state) { rewriter->AddURLRewriter(&WillHandleBraveURLRedirect); ChromeWebClient::PostBrowserURLRewriterCreation(rewriter); } + +void BraveWebClient::GetAdditionalWebUISchemes( + std::vector* additional_schemes) { + ChromeWebClient::GetAdditionalWebUISchemes(additional_schemes); +} diff --git a/ios/browser/ui/webui/BUILD.gn b/ios/browser/ui/webui/BUILD.gn index 0a5e5a63b3cf..24317dec88da 100644 --- a/ios/browser/ui/webui/BUILD.gn +++ b/ios/browser/ui/webui/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2023 The Brave Authors. All rights reserved. +# Copyright (c) 2024 The Brave Authors. All rights reserved. # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at https://mozilla.org/MPL/2.0/. @@ -9,8 +9,12 @@ import("//mojo/public/tools/bindings/mojom.gni") source_set("webui") { sources = [ + "brave_url_data_source_ios.h", + "brave_url_data_source_ios.mm", "brave_web_ui_controller_factory.h", "brave_web_ui_controller_factory.mm", + "brave_webui_data_source.h", + "brave_webui_data_source.mm", "brave_webui_source.h", "brave_webui_source.mm", ] @@ -30,6 +34,7 @@ source_set("webui") { "//ios/chrome/browser/webui/ui_bundled:webui_internal", "//ios/components/webui:url_constants", "//ios/web/public/webui", + "//ios/web/webui:webui", "//services/network/public/cpp", "//services/service_manager/public/cpp", "//third_party/abseil-cpp:absl", diff --git a/ios/browser/ui/webui/brave_url_data_source_ios.h b/ios/browser/ui/webui/brave_url_data_source_ios.h new file mode 100644 index 000000000000..e2226d1df97d --- /dev/null +++ b/ios/browser/ui/webui/brave_url_data_source_ios.h @@ -0,0 +1,30 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_IOS_BROWSER_UI_WEBUI_BRAVE_URL_DATA_SOURCE_IOS_H_ +#define BRAVE_IOS_BROWSER_UI_WEBUI_BRAVE_URL_DATA_SOURCE_IOS_H_ + +#include + +#include "ios/web/public/webui/url_data_source_ios.h" +#include "services/network/public/mojom/content_security_policy.mojom.h" + +class BraveURLDataSourceIOS : public web::URLDataSourceIOS { + public: + BraveURLDataSourceIOS(); + ~BraveURLDataSourceIOS() override; + + virtual std::string GetContentSecurityPolicy( + network::mojom::CSPDirectiveName directive) const; + + private: + std::string GetContentSecurityPolicyObjectSrc() const override; + std::string GetContentSecurityPolicyFrameSrc() const override; + + // Brave CSP's & Security variables: + base::flat_map csp_overrides_; +}; + +#endif // BRAVE_IOS_BROWSER_UI_WEBUI_BRAVE_URL_DATA_SOURCE_IOS_H_ diff --git a/ios/browser/ui/webui/brave_url_data_source_ios.mm b/ios/browser/ui/webui/brave_url_data_source_ios.mm new file mode 100644 index 000000000000..b2d73eeca15b --- /dev/null +++ b/ios/browser/ui/webui/brave_url_data_source_ios.mm @@ -0,0 +1,135 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/ios/browser/ui/webui/brave_url_data_source_ios.h" + +#include "base/no_destructor.h" +#include "base/strings/strcat.h" +#include "ios/components/webui/web_ui_url_constants.h" + +namespace { + +// A chrome-untrusted data source's name starts with chrome-untrusted://. +bool IsChromeUntrustedDataSource(const web::URLDataSourceIOS* source) { + static const base::NoDestructor kChromeUntrustedSourceNamePrefix( + base::StrCat({kChromeUIUntrustedScheme, url::kStandardSchemeSeparator})); + + return base::StartsWith(source->GetSource(), + *kChromeUntrustedSourceNamePrefix, + base::CompareCase::SENSITIVE); +} + +} // namespace + +BraveURLDataSourceIOS::BraveURLDataSourceIOS() {} +BraveURLDataSourceIOS::~BraveURLDataSourceIOS() = default; + +std::string BraveURLDataSourceIOS::GetContentSecurityPolicyObjectSrc() const { + std::string csp_header; + + // Same as on Desktop + // content/browser/webui/url_data_manager_backend.cc + const network::mojom::CSPDirectiveName kAllDirectives[] = { + network::mojom::CSPDirectiveName::BaseURI, + network::mojom::CSPDirectiveName::ChildSrc, + network::mojom::CSPDirectiveName::ConnectSrc, + network::mojom::CSPDirectiveName::DefaultSrc, + network::mojom::CSPDirectiveName::FencedFrameSrc, + network::mojom::CSPDirectiveName::FormAction, + network::mojom::CSPDirectiveName::FontSrc, + network::mojom::CSPDirectiveName::ImgSrc, + network::mojom::CSPDirectiveName::MediaSrc, + network::mojom::CSPDirectiveName::ObjectSrc, + network::mojom::CSPDirectiveName::RequireTrustedTypesFor, + network::mojom::CSPDirectiveName::ScriptSrc, + network::mojom::CSPDirectiveName::StyleSrc, + network::mojom::CSPDirectiveName::TrustedTypes, + network::mojom::CSPDirectiveName::WorkerSrc}; + + for (auto& directive : kAllDirectives) { + csp_header.append(GetContentSecurityPolicy(directive)); + } + + // TODO(crbug.com/40118579): Both CSP frame ancestors and XFO headers may be + // added to the response but frame ancestors would take precedence. In the + // future, XFO will be removed so when that happens remove the check and + // always add frame ancestors. + if (ShouldDenyXFrameOptions()) { + csp_header.append(GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::FrameAncestors)); + } + + // Add the iOS default frame-src + csp_header.append(GetContentSecurityPolicyFrameSrc()); + + return csp_header; +} + +std::string BraveURLDataSourceIOS::GetContentSecurityPolicyFrameSrc() const { + std::string frame_src = + GetContentSecurityPolicy(network::mojom::CSPDirectiveName::FrameSrc); + if (!frame_src.empty()) { + return frame_src; + } + + // The default for iOS in url_data_manager_ios_backend.mm + return "frame-src 'none';"; +} + +std::string BraveURLDataSourceIOS::GetContentSecurityPolicy( + network::mojom::CSPDirectiveName directive) const { + // Default policies matching Chromium Desktop + // content/public/browser/url_data_source.cc + switch (directive) { + case network::mojom::CSPDirectiveName::ChildSrc: + return "child-src 'none';"; + case network::mojom::CSPDirectiveName::DefaultSrc: + return IsChromeUntrustedDataSource(this) ? "default-src 'self';" + : std::string(); + case network::mojom::CSPDirectiveName::ObjectSrc: + return "object-src 'none';"; + case network::mojom::CSPDirectiveName::ScriptSrc: + // Note: Do not add 'unsafe-eval' here. Instead override CSP for the + // specific pages that need it, see context http://crbug.com/525224. + return IsChromeUntrustedDataSource(this) + ? base::StrCat({"script-src", kChromeUIUntrustedScheme, + url::kStandardSchemeSeparator, + "resources 'self';"}) + : "script-src chrome://resources 'self';"; + case network::mojom::CSPDirectiveName::FrameAncestors: + return "frame-ancestors 'none';"; + case network::mojom::CSPDirectiveName::RequireTrustedTypesFor: + return "require-trusted-types-for 'script';"; + case network::mojom::CSPDirectiveName::TrustedTypes: + return "trusted-types;"; + case network::mojom::CSPDirectiveName::BaseURI: + return IsChromeUntrustedDataSource(this) ? "base-uri 'none';" + : std::string(); + case network::mojom::CSPDirectiveName::FormAction: + return IsChromeUntrustedDataSource(this) ? "form-action 'none';" + : std::string(); + case network::mojom::CSPDirectiveName::BlockAllMixedContent: + case network::mojom::CSPDirectiveName::ConnectSrc: + case network::mojom::CSPDirectiveName::FencedFrameSrc: + case network::mojom::CSPDirectiveName::FrameSrc: + case network::mojom::CSPDirectiveName::FontSrc: + case network::mojom::CSPDirectiveName::ImgSrc: + case network::mojom::CSPDirectiveName::ManifestSrc: + case network::mojom::CSPDirectiveName::MediaSrc: + case network::mojom::CSPDirectiveName::ReportURI: + case network::mojom::CSPDirectiveName::Sandbox: + case network::mojom::CSPDirectiveName::ScriptSrcAttr: + case network::mojom::CSPDirectiveName::ScriptSrcElem: + case network::mojom::CSPDirectiveName::StyleSrc: + case network::mojom::CSPDirectiveName::StyleSrcAttr: + case network::mojom::CSPDirectiveName::StyleSrcElem: + case network::mojom::CSPDirectiveName::UpgradeInsecureRequests: + case network::mojom::CSPDirectiveName::TreatAsPublicAddress: + case network::mojom::CSPDirectiveName::WorkerSrc: + case network::mojom::CSPDirectiveName::ReportTo: + case network::mojom::CSPDirectiveName::Unknown: + return std::string(); + } +} diff --git a/ios/browser/ui/webui/brave_web_ui_controller_factory.mm b/ios/browser/ui/webui/brave_web_ui_controller_factory.mm index 58b04d5dc246..cdaaa6d8d95d 100644 --- a/ios/browser/ui/webui/brave_web_ui_controller_factory.mm +++ b/ios/browser/ui/webui/brave_web_ui_controller_factory.mm @@ -41,9 +41,12 @@ // a tab, based on its URL. Returns nullptr if the URL doesn't have WebUIIOS // associated with it. WebUIIOSFactoryFunction GetWebUIIOSFactoryFunction(const GURL& url) { + const char kChromeUIUntrustedScheme[] = "chrome-untrusted"; + // This will get called a lot to check all URLs, so do a quick check of other // schemes to filter out most URLs. - if (!url.SchemeIs(kBraveUIScheme) && !url.SchemeIs(kChromeUIScheme)) { + if (!url.SchemeIs(kBraveUIScheme) && !url.SchemeIs(kChromeUIScheme) && + !url.SchemeIs(kChromeUIUntrustedScheme)) { return nullptr; } diff --git a/ios/browser/ui/webui/brave_webui_data_source.h b/ios/browser/ui/webui/brave_webui_data_source.h new file mode 100644 index 000000000000..eec623483cb7 --- /dev/null +++ b/ios/browser/ui/webui/brave_webui_data_source.h @@ -0,0 +1,93 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_IOS_BROWSER_UI_WEBUI_BRAVE_WEBUI_DATA_SOURCE_H_ +#define BRAVE_IOS_BROWSER_UI_WEBUI_BRAVE_WEBUI_DATA_SOURCE_H_ + +#include +#include +#include +#include + +#include "base/functional/callback.h" +#include "base/values.h" +#include "ios/web/public/webui/url_data_source_ios.h" +#include "ios/web/webui/url_data_manager_ios.h" +#include "ui/base/template_expressions.h" + +namespace network::mojom { +enum class CSPDirectiveName : std::int32_t; +} // namespace network::mojom + +namespace webui { +struct LocalizedString; +struct ResourcePath; +} // namespace webui + +class BraveWebUIDataSource : public web::URLDataSourceIOS { + public: + BraveWebUIDataSource(); + + ~BraveWebUIDataSource() override; + + BraveWebUIDataSource(const BraveWebUIDataSource&) = delete; + BraveWebUIDataSource& operator=(const BraveWebUIDataSource&) = delete; + + void AddString(const std::string& name, const std::u16string& value); + void AddString(const std::string& name, const std::string& value); + void AddLocalizedString(const std::string& name, int ids); + void AddLocalizedStrings(const base::Value::Dict& localized_strings); + void AddLocalizedStrings(base::span strings); + void AddBoolean(const std::string& name, bool value); + void UseStringsJs(); + void EnableReplaceI18nInJS(); + void AddResourcePath(const std::string& path, int resource_id); + void AddResourcePaths(base::span paths); + void SetDefaultResource(int resource_id); + void DisableDenyXFrameOptions(); + const ui::TemplateReplacements* GetReplacements() const; + + void OverrideContentSecurityPolicy(network::mojom::CSPDirectiveName directive, + const std::string& value); + void AddFrameAncestor(const GURL& frame_ancestor); + void DisableTrustedTypesCSP(); + + private: + void EnsureLoadTimeDataDefaultsAdded(); + void SendLocalizedStringsAsJSON(GotDataCallback callback, + bool from_js_module); + int PathToIdrOrDefault(const std::string& path) const; + + // web::URLDataSourceIOS overrides: + std::string GetSource() const override; + void StartDataRequest(const std::string& path, + GotDataCallback callback) override; + std::string GetMimeType(const std::string& path) const override; + bool ShouldReplaceExistingSource() const override; + bool ShouldReplaceI18nInJS() const override; + bool AllowCaching() const override; + bool ShouldDenyXFrameOptions() const override; + + bool ShouldServiceRequest(const GURL& url) const override; + std::string GetContentSecurityPolicy( + network::mojom::CSPDirectiveName directive) const override; + std::string GetContentSecurityPolicyObjectSrc() const override; + std::string GetContentSecurityPolicyFrameSrc() const override; + + int default_resource_; + bool use_strings_js_; + std::map path_to_idr_map_; + base::Value::Dict localized_strings_; + ui::TemplateReplacements replacements_; + bool deny_xframe_options_; + bool load_time_data_defaults_added_; + bool replace_existing_source_; + bool should_replace_i18n_in_js_; + + base::flat_map csp_overrides_; + std::set frame_ancestors_; +}; + +#endif // BRAVE_IOS_BROWSER_UI_WEBUI_BRAVE_WEBUI_DATA_SOURCE_H_ diff --git a/ios/browser/ui/webui/brave_webui_data_source.mm b/ios/browser/ui/webui/brave_webui_data_source.mm new file mode 100644 index 000000000000..374327d7daab --- /dev/null +++ b/ios/browser/ui/webui/brave_webui_data_source.mm @@ -0,0 +1,302 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/ios/browser/ui/webui/brave_webui_data_source.h" + +#include "base/functional/bind.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/ref_counted_memory.h" +#include "base/strings/strcat.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "ios/chrome/browser/shared/model/url/chrome_url_constants.h" +#include "ios/web/public/web_client.h" +#include "services/network/public/mojom/content_security_policy.mojom.h" +#include "ui/base/webui/jstemplate_builder.h" +#include "ui/base/webui/resource_path.h" +#include "ui/base/webui/web_ui_util.h" + +BraveWebUIDataSource::BraveWebUIDataSource() + : default_resource_(-1), + use_strings_js_(false), + deny_xframe_options_(true), + load_time_data_defaults_added_(false), + replace_existing_source_(true), + should_replace_i18n_in_js_(false) {} + +BraveWebUIDataSource::~BraveWebUIDataSource() = default; + +void BraveWebUIDataSource::AddString(const std::string& name, + const std::u16string& value) { + localized_strings_.Set(name, value); + replacements_[name] = base::UTF16ToUTF8(value); +} + +void BraveWebUIDataSource::AddString(const std::string& name, + const std::string& value) { + localized_strings_.Set(name, value); + replacements_[name] = value; +} + +void BraveWebUIDataSource::AddLocalizedString(const std::string& name, + int ids) { + localized_strings_.Set(name, web::GetWebClient()->GetLocalizedString(ids)); + replacements_[name] = + base::UTF16ToUTF8(web::GetWebClient()->GetLocalizedString(ids)); +} + +void BraveWebUIDataSource::AddLocalizedStrings( + const base::Value::Dict& localized_strings) { + localized_strings_.Merge(localized_strings.Clone()); + ui::TemplateReplacementsFromDictionaryValue(localized_strings, + &replacements_); +} + +void BraveWebUIDataSource::AddLocalizedStrings( + base::span strings) { + for (const auto& str : strings) { + AddLocalizedString(str.name, str.id); + } +} + +void BraveWebUIDataSource::AddBoolean(const std::string& name, bool value) { + localized_strings_.Set(name, value); +} + +void BraveWebUIDataSource::UseStringsJs() { + use_strings_js_ = true; +} + +void BraveWebUIDataSource::EnableReplaceI18nInJS() { + should_replace_i18n_in_js_ = true; +} + +bool BraveWebUIDataSource::ShouldReplaceI18nInJS() const { + return should_replace_i18n_in_js_; +} + +void BraveWebUIDataSource::AddResourcePath(const std::string& path, + int resource_id) { + path_to_idr_map_[path] = resource_id; +} + +void BraveWebUIDataSource::AddResourcePaths( + base::span paths) { + for (const auto& path : paths) { + AddResourcePath(path.path, path.id); + } +} + +void BraveWebUIDataSource::SetDefaultResource(int resource_id) { + default_resource_ = resource_id; +} + +void BraveWebUIDataSource::DisableDenyXFrameOptions() { + deny_xframe_options_ = false; +} + +const ui::TemplateReplacements* BraveWebUIDataSource::GetReplacements() const { + return &replacements_; +} + +void BraveWebUIDataSource::OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName directive, + const std::string& value) { + csp_overrides_.insert_or_assign(directive, value); +} + +void BraveWebUIDataSource::AddFrameAncestor(const GURL& frame_ancestor) { + // Do not allow a wildcard to be a frame ancestor or it will allow any website + // to embed the WebUI. + CHECK(frame_ancestor.SchemeIs(kChromeUIScheme) || + frame_ancestor.SchemeIs(kChromeUIUntrustedScheme)); + frame_ancestors_.insert(frame_ancestor); +} + +void BraveWebUIDataSource::DisableTrustedTypesCSP() { + // TODO(crbug.com/40137141): Trusted Type remaining WebUI + // This removes require-trusted-types-for and trusted-types directives + // from the CSP header. + OverrideContentSecurityPolicy( + network::mojom::CSPDirectiveName::RequireTrustedTypesFor, std::string()); + OverrideContentSecurityPolicy(network::mojom::CSPDirectiveName::TrustedTypes, + std::string()); +} + +// URLDataSourceIOS + +std::string BraveWebUIDataSource::GetSource() const { + return base::StrCat( + {kChromeUIUntrustedScheme, url::kStandardSchemeSeparator}); +} + +void BraveWebUIDataSource::StartDataRequest(const std::string& path, + GotDataCallback callback) { + EnsureLoadTimeDataDefaultsAdded(); + + if (use_strings_js_) { + bool from_js_module = path == "strings.m.js"; + if (from_js_module || path == "strings.js") { + SendLocalizedStringsAsJSON(std::move(callback), from_js_module); + return; + } + } + + int resource_id = PathToIdrOrDefault(path); + DCHECK_NE(resource_id, -1); + scoped_refptr response( + web::GetWebClient()->GetDataResourceBytes(resource_id)); + std::move(callback).Run(response); +} + +std::string BraveWebUIDataSource::GetMimeType(const std::string& path) const { + if (base::EndsWith(path, ".png", base::CompareCase::INSENSITIVE_ASCII)) { + return "image/png"; + } + + if (base::EndsWith(path, ".gif", base::CompareCase::INSENSITIVE_ASCII)) { + return "image/gif"; + } + + if (base::EndsWith(path, ".jpg", base::CompareCase::INSENSITIVE_ASCII)) { + return "image/jpg"; + } + + if (base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII)) { + return "application/javascript"; + } + + if (base::EndsWith(path, ".json", base::CompareCase::INSENSITIVE_ASCII)) { + return "application/json"; + } + + if (base::EndsWith(path, ".pdf", base::CompareCase::INSENSITIVE_ASCII)) { + return "application/pdf"; + } + + if (base::EndsWith(path, ".css", base::CompareCase::INSENSITIVE_ASCII)) { + return "text/css"; + } + + if (base::EndsWith(path, ".svg", base::CompareCase::INSENSITIVE_ASCII)) { + return "image/svg+xml"; + } + + return "text/html"; +} + +bool BraveWebUIDataSource::ShouldReplaceExistingSource() const { + return replace_existing_source_; +} + +bool BraveWebUIDataSource::AllowCaching() const { + return false; +} + +bool BraveWebUIDataSource::ShouldDenyXFrameOptions() const { + return deny_xframe_options_; +} + +bool BraveWebUIDataSource::ShouldServiceRequest(const GURL& url) const { + return web::URLDataSourceIOS::ShouldServiceRequest(url); +} + +std::string BraveWebUIDataSource::GetContentSecurityPolicy( + network::mojom::CSPDirectiveName directive) const { + if (csp_overrides_.contains(directive)) { + return csp_overrides_.at(directive); + } else if (directive == network::mojom::CSPDirectiveName::FrameAncestors) { + std::string frame_ancestors; + if (frame_ancestors_.size() == 0) { + frame_ancestors += " 'none'"; + } + for (const GURL& frame_ancestor : frame_ancestors_) { + frame_ancestors += " " + frame_ancestor.spec(); + } + return "frame-ancestors" + frame_ancestors + ";"; + } + return web::URLDataSourceIOS::GetContentSecurityPolicy(directive); +} + +std::string BraveWebUIDataSource::GetContentSecurityPolicyObjectSrc() const { + if (ShouldAddContentSecurityPolicy()) { + std::string csp_header; + + const network::mojom::CSPDirectiveName kAllDirectives[] = { + network::mojom::CSPDirectiveName::BaseURI, + network::mojom::CSPDirectiveName::ChildSrc, + network::mojom::CSPDirectiveName::ConnectSrc, + network::mojom::CSPDirectiveName::DefaultSrc, + network::mojom::CSPDirectiveName::FencedFrameSrc, + network::mojom::CSPDirectiveName::FormAction, + network::mojom::CSPDirectiveName::FontSrc, + network::mojom::CSPDirectiveName::ImgSrc, + network::mojom::CSPDirectiveName::MediaSrc, + network::mojom::CSPDirectiveName::ObjectSrc, + network::mojom::CSPDirectiveName::RequireTrustedTypesFor, + network::mojom::CSPDirectiveName::ScriptSrc, + network::mojom::CSPDirectiveName::StyleSrc, + network::mojom::CSPDirectiveName::TrustedTypes, + network::mojom::CSPDirectiveName::WorkerSrc}; + + for (auto& directive : kAllDirectives) { + csp_header.append(GetContentSecurityPolicy(directive)); + } + + // TODO(crbug.com/40118579): Both CSP frame ancestors and XFO headers may be + // added to the response but frame ancestors would take precedence. In the + // future, XFO will be removed so when that happens remove the check and + // always add frame ancestors. + if (ShouldDenyXFrameOptions()) { + csp_header.append(GetContentSecurityPolicy( + network::mojom::CSPDirectiveName::FrameAncestors)); + } + + return csp_header; + } + + return web::URLDataSourceIOS::GetContentSecurityPolicyObjectSrc(); +} + +std::string BraveWebUIDataSource::GetContentSecurityPolicyFrameSrc() const { + if (csp_overrides_.contains(network::mojom::CSPDirectiveName::FrameSrc)) { + return csp_overrides_.at(network::mojom::CSPDirectiveName::FrameSrc); + } + + std::string frame_src = + GetContentSecurityPolicy(network::mojom::CSPDirectiveName::FrameSrc); + if (!frame_src.empty()) { + return frame_src; + } + + // See url_data_manager_ios_backend.mm chromium_src override for more details + return web::URLDataSourceIOS::GetContentSecurityPolicyFrameSrc(); +} + +void BraveWebUIDataSource::EnsureLoadTimeDataDefaultsAdded() { + if (load_time_data_defaults_added_) { + return; + } + + load_time_data_defaults_added_ = true; + base::Value::Dict defaults; + webui::SetLoadTimeDataDefaults(web::GetWebClient()->GetApplicationLocale(), + &defaults); + AddLocalizedStrings(defaults); +} + +void BraveWebUIDataSource::SendLocalizedStringsAsJSON( + URLDataSourceIOS::GotDataCallback callback, + bool from_js_module) { + std::string template_data; + webui::AppendJsonJS(localized_strings_, &template_data, from_js_module); + std::move(callback).Run( + base::MakeRefCounted(std::move(template_data))); +} + +int BraveWebUIDataSource::PathToIdrOrDefault(const std::string& path) const { + auto it = path_to_idr_map_.find(path); + return it == path_to_idr_map_.end() ? default_resource_ : it->second; +} diff --git a/ios/browser/ui/webui/brave_webui_source.mm b/ios/browser/ui/webui/brave_webui_source.mm index baaa688e9806..7dc52a37c15e 100644 --- a/ios/browser/ui/webui/brave_webui_source.mm +++ b/ios/browser/ui/webui/brave_webui_source.mm @@ -16,6 +16,7 @@ #include "components/grit/components_resources.h" #include "ios/chrome/browser/shared/model/profile/profile_ios.h" #include "ios/web/public/webui/web_ui_ios_data_source.h" +#include "ios/web/webui/web_ui_ios_data_source_impl.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/webui/resource_path.h" #include "ui/base/webui/web_ui_util.h" @@ -36,7 +37,7 @@ void CustomizeWebUIHTMLSource(web::WebUIIOS* web_ui, size_t resource_map_size, int html_resource_id, bool disable_trusted_types_csp) { - web::WebUIIOSDataSource* source = web::WebUIIOSDataSource::Create(name); + web::WebUIIOSDataSource* source = BraveWebUIIOSDataSource::Create(name); web::WebUIIOSDataSource::Add(ProfileIOS::FromWebUIIOS(web_ui), source); source->UseStringsJs();