Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] - Add CSPs support in Chromium WebUI #26402

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions chromium_src/ios/components/webui/web_ui_url_constants.cc
Original file line number Diff line number Diff line change
@@ -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";
13 changes: 13 additions & 0 deletions chromium_src/ios/components/webui/web_ui_url_constants.h
Original file line number Diff line number Diff line change
@@ -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_
35 changes: 35 additions & 0 deletions chromium_src/ios/web/public/web_state.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2025 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_WEB_STATE_H_
#define BRAVE_CHROMIUM_SRC_IOS_WEB_PUBLIC_WEB_STATE_H_

#define callbacks_ \
callbacks_; \
\
public: \
template <typename Interface> \
void AddUntrustedInterface( \
const GURL& url, \
base::RepeatingCallback<void(mojo::PendingReceiver<Interface>)> \
callback) { \
CHECK(!url.is_empty()); \
untrusted_callbacks_[url].emplace( \
std::string(Interface::Name_), \
base::BindRepeating(&WrapCallback<Interface>, std::move(callback))); \
} \
bool HasUntrustedInterface(const GURL& url, \
const std::string& interface_name); \
void BindUntrustedInterface(const GURL& url, \
mojo::GenericPendingReceiver receiver); \
\
private: \
std::map<GURL, std::map<std::string, Callback>> untrusted_callbacks_

#include "src/ios/web/public/web_state.h" // IWYU pragma: export

#undef callbacks_

#endif // BRAVE_CHROMIUM_SRC_IOS_WEB_PUBLIC_WEB_STATE_H_
17 changes: 17 additions & 0 deletions chromium_src/ios/web/public/webui/url_data_source_ios.h
Original file line number Diff line number Diff line change
@@ -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_
19 changes: 19 additions & 0 deletions chromium_src/ios/web/web_state/ui/crw_wk_ui_handler.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) 2025 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/web_client.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullability-completeness"

#define IsAppSpecificURL(URL) \
IsAppSpecificURL(URL) && \
self.mojoFacade->IsWebUIMessageAllowedForFrame(frame, origin, &prompt)

#include "src/ios/web/web_state/ui/crw_wk_ui_handler.mm"

#undef IsAppSpecificURL

#pragma clang diagnostic pop
37 changes: 37 additions & 0 deletions chromium_src/ios/web/web_state/web_state.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2025 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/web/web_state/web_state.mm"

namespace web {
bool WebState::InterfaceBinder::HasUntrustedInterface(
const GURL& url,
const std::string& interface_name) {
DCHECK(!url.is_empty());
DCHECK(!interface_name.empty());
if (auto it = untrusted_callbacks_.find(url);
it != untrusted_callbacks_.end()) {
return it->second.find(interface_name) != it->second.end();
}
return false;
}

void WebState::InterfaceBinder::BindUntrustedInterface(
const GURL& url,
mojo::GenericPendingReceiver receiver) {
DCHECK(!url.is_empty());
DCHECK(receiver.is_valid());
if (auto it = untrusted_callbacks_.find(url);
it != untrusted_callbacks_.end()) {
if (auto jt = it->second.find(*receiver.interface_name());
jt != it->second.end()) {
jt->second.Run(&receiver);

GetWebClient()->BindInterfaceReceiverFromMainFrame(web_state_,
std::move(receiver));
}
}
}
} // namespace web
8 changes: 8 additions & 0 deletions chromium_src/ios/web/webui/BUILD.gn
Original file line number Diff line number Diff line change
@@ -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" ]
}
3 changes: 3 additions & 0 deletions chromium_src/ios/web/webui/DEPS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include_rules = [
"+ios/components/webui/web_ui_url_constants.h",
]
78 changes: 78 additions & 0 deletions chromium_src/ios/web/webui/crw_web_ui_scheme_handler.mm
Original file line number Diff line number Diff line change
@@ -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 <map>

#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<net::HttpResponseHeaders> 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<net::HttpResponseHeaders>&)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
23 changes: 23 additions & 0 deletions chromium_src/ios/web/webui/mojo_facade.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2025 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_MOJO_FACADE_H_
#define BRAVE_CHROMIUM_SRC_IOS_WEB_WEBUI_MOJO_FACADE_H_

#include <WebKit/WebKit.h>

#include "url/gurl.h"

#define HandleMojoMessage \
Dummy(); \
bool IsWebUIMessageAllowedForFrame(WKFrameInfo* frame, const GURL& origin, \
NSString** prompt); \
std::string HandleMojoMessage

#include "src/ios/web/webui/mojo_facade.h" // IWYU pragma: export

#undef HandleMojoMessage

#endif // BRAVE_CHROMIUM_SRC_IOS_WEB_WEBUI_MOJO_FACADE_H_
51 changes: 51 additions & 0 deletions chromium_src/ios/web/webui/mojo_facade.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2025 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/web/webui/mojo_facade.mm"

namespace web {
bool MojoFacade::IsWebUIMessageAllowedForFrame(WKFrameInfo* frame,
const GURL& origin,
NSString** prompt) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
CHECK(prompt && *prompt);

auto name_and_args =
GetMessageNameAndArguments(base::SysNSStringToUTF8(*prompt));

// If the scheme is untrusted
if (name_and_args.name == "Mojo.bindInterface" &&
origin.scheme() == "chrome-untrusted") {
const base::Value::Dict& args = name_and_args.args;
const std::string* interface_name = args.FindString("interfaceName");
CHECK(interface_name);

// Check if the requested interface is registered
bool can_bind_interface =
web_state_->GetInterfaceBinderForMainFrame()->HasUntrustedInterface(
origin, *interface_name);

if (can_bind_interface) {
std::optional<int> pipe_id = args.FindInt("requestHandle");
CHECK(pipe_id.has_value());

mojo::ScopedMessagePipeHandle pipe = TakePipeFromId(*pipe_id);
CHECK(pipe.is_valid());
web_state_->GetInterfaceBinderForMainFrame()->BindUntrustedInterface(
origin,
mojo::GenericPendingReceiver(*interface_name, std::move(pipe)));

// Set the prompt to invalid, so that HandleMojoMessage will do nothing.
*prompt = @"{\"name\":\"Mojo.invalidInterface\",\"args\":{}}";
}
}

return true;
}

std::string MojoFacade::Dummy() {
return "";
}
} // namespace web
15 changes: 15 additions & 0 deletions chromium_src/ios/web/webui/url_data_manager_ios_backend.mm
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions chromium_src/ios/web/webui/url_data_source_ios.mm
Original file line number Diff line number Diff line change
@@ -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"
27 changes: 27 additions & 0 deletions chromium_src/ios/web/webui/url_fetcher_block_adapter.h
Original file line number Diff line number Diff line change
@@ -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(); \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this for?

Copy link
Contributor Author

@Brandon-T Brandon-T Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chromium has: https://source.chromium.org/chromium/chromium/src/+/main:ios/web/webui/crw_web_ui_scheme_handler.mm;l=89?q=CRWWebUISchemeHandler&ss=chromium%2Fchromium%2Fsrc

they completely ignore the headers from the response (and they use * for the Access-Control-Allow-Origin header. They hardcode headers). So to add the actual response headers, I need to retrieve it, and add it here (in this PR):

const network::mojom::URLResponseHeadPtr responseHead =
fetcher->getResponse();
if (responseHead) {
const scoped_refptr<net::HttpResponseHeaders> headers =
responseHead->headers;
if (headers) {
// const std::string& raw_headers = headers->raw_headers();
NSMutableDictionary* responseHeaders =
[strongSelf parseHeaders:headers];
if (![responseHeaders objectForKey:@"Content-Type"]) {
[responseHeaders setObject:mimeType forKey:@"Content-Type"];
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please add comments explaining?

} \
\
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_
Loading
Loading