diff --git a/ios/browser/api/url/BUILD.gn b/ios/browser/api/url/BUILD.gn index 795b497f6881..92f9fa70ea83 100644 --- a/ios/browser/api/url/BUILD.gn +++ b/ios/browser/api/url/BUILD.gn @@ -7,12 +7,15 @@ source_set("url") { configs += [ "//build/config/compiler:enable_arc" ] sources = [ + "url_pattern_ios.h", + "url_pattern_ios.mm", "url_utils.h", "url_utils.mm", ] deps = [ "//base", + "//brave/extensions:common", "//net", "//url", ] diff --git a/ios/browser/api/url/DEPS b/ios/browser/api/url/DEPS new file mode 100644 index 000000000000..921acbbcffc5 --- /dev/null +++ b/ios/browser/api/url/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+extensions/common/url_pattern.h" +] diff --git a/ios/browser/api/url/headers.gni b/ios/browser/api/url/headers.gni index b7ed4cdb4a1d..f9dd0811e1c6 100644 --- a/ios/browser/api/url/headers.gni +++ b/ios/browser/api/url/headers.gni @@ -1 +1,4 @@ -browser_api_url_public_headers = [ "//brave/ios/browser/api/url/url_utils.h" ] +browser_api_url_public_headers = [ + "//brave/ios/browser/api/url/url_pattern_ios.h", + "//brave/ios/browser/api/url/url_utils.h", +] diff --git a/ios/browser/api/url/url_pattern_ios.h b/ios/browser/api/url/url_pattern_ios.h new file mode 100644 index 000000000000..81c71a5d340c --- /dev/null +++ b/ios/browser/api/url/url_pattern_ios.h @@ -0,0 +1,119 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_IOS_BROWSER_API_URL_URL_PATTERN_IOS_H_ +#define BRAVE_IOS_BROWSER_API_URL_URL_PATTERN_IOS_H_ + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_OPTIONS(NSInteger, URLPatternIOSScheme) { + URLPatternIOSSchemeNone = 0, + URLPatternIOSSchemeHttp = 1 << 0, + URLPatternIOSSchemeHttps = 1 << 1, + URLPatternIOSSchemeFile = 1 << 2, + URLPatternIOSSchemeFtp = 1 << 3, + URLPatternIOSSchemeChromeUI = 1 << 4, + URLPatternIOSSchemeExtension = 1 << 5, + URLPatternIOSSchemeFilesystem = 1 << 6, + URLPatternIOSSchemeWs = 1 << 7, + URLPatternIOSSchemeWss = 1 << 8, + URLPatternIOSSchemeData = 1 << 9, + URLPatternIOSSchemeUrn = 1 << 10, + URLPatternIOSSchemeUUIDInPackage = 1 << 11, + URLPatternIOSSchemeAll = -1 +} NS_SWIFT_NAME(URLPattern.Scheme); + +typedef NSInteger URLPatternIOSParseResult NS_TYPED_ENUM + NS_SWIFT_NAME(URLPattern.ParseResult); +OBJC_EXPORT URLPatternIOSParseResult const URLPatternIOSParseResultSuccess; +OBJC_EXPORT URLPatternIOSParseResult const + URLPatternIOSParseResultMissingSchemeSeparator; +OBJC_EXPORT URLPatternIOSParseResult const + URLPatternIOSParseResultInvalidScheme; +OBJC_EXPORT URLPatternIOSParseResult const + URLPatternIOSParseResultWrongSchemeSeparator; +OBJC_EXPORT URLPatternIOSParseResult const URLPatternIOSParseResultEmptyHost; +OBJC_EXPORT URLPatternIOSParseResult const + URLPatternIOSParseResultInvalidHostWildcard; +OBJC_EXPORT URLPatternIOSParseResult const URLPatternIOSParseResultEmptyPath; +OBJC_EXPORT URLPatternIOSParseResult const URLPatternIOSParseResultInvalidPort; +OBJC_EXPORT URLPatternIOSParseResult const URLPatternIOSParseResultInvalidHost; + +/// A wrapper around Chromium's `URLPattern` functionality +OBJC_EXPORT +NS_SWIFT_NAME(URLPattern) +@interface URLPatternIOS : NSObject + +/// A pattern that will match all urls. +/// +/// Can be passed into `parsePattern` or `initWithValidSchemes:patternLiteral:` +@property(class, readonly) NSString* allURLsPattern; + +@property(nonatomic) URLPatternIOSScheme validSchemes; +@property(nonatomic) NSString* scheme; +@property(nonatomic) NSString* host; +@property(nonatomic) NSString* port; +@property(nonatomic) NSString* path; +@property(nonatomic) bool isMatchingSubdomains; +@property(nonatomic) bool isMatchingAllURLs; + +/// Convenience to construct an empty URLPattern with no schemes setup +- (instancetype)init; + +- (instancetype)initWithValidSchemes:(URLPatternIOSScheme)schemes + NS_DESIGNATED_INITIALIZER; + +/// Convenience to construct a URLPattern from a string literal. If the string +/// is not known ahead of time, use `parsePattern:` instead +- (instancetype)initWithValidSchemes:(URLPatternIOSScheme)schemes + patternLiteral:(NSString*)patternLiteral; + +/// Sets the current pattern to match against +- (URLPatternIOSParseResult)parsePattern:(NSString*)pattern + NS_SWIFT_NAME(parse(pattern:)); + +/// Returns true if the specified scheme can be used in this URL pattern, and +/// false otherwise. +- (bool)isValidScheme:(NSString*)scheme; + +/// Returns true if this instance matches the specified URL. Always returns +/// false for invalid URLs. +- (bool)matchesURL:(NSURL*)url; + +/// Returns true if this instance matches the specified security origin. +- (bool)matchesSecurityOrigin:(NSURL*)origin; + +/// Returns true if `scheme` matches our scheme. +/// Note that if scheme is "filesystem", this may fail whereas `matchesURL` +/// may succeed. `matchesURL` is smart enough to look at the inner url instead +/// of the outer "filesystem:" part. +- (bool)matchesScheme:(NSString*)scheme; + +/// Returns true if `host` matches our host. +- (bool)matchesHost:(NSString*)host; + +/// Returns true if `path` matches our path. +- (bool)matchesPath:(NSString*)path; + +/// Returns true if the pattern only matches a single origin. The pattern may +/// include a path. +- (bool)matchesSingleOrigin; + +/// Returns true if this pattern matches all possible URLs that `pattern` can +/// match. For example, http://*.google.com encompasses http://www.google.com. +- (bool)containsOtherURLPattern:(URLPatternIOS*)pattern; + +/// Determines whether there is a URL that would match this instance and +/// another instance. +- (bool)overlapsWithOtherURLPattern:(URLPatternIOS*)pattern + NS_SWIFT_NAME(overlapsWithOtherURLPattern(_:)); + +@end + +NS_ASSUME_NONNULL_END + +#endif // BRAVE_IOS_BROWSER_API_URL_URL_PATTERN_IOS_H_ diff --git a/ios/browser/api/url/url_pattern_ios.mm b/ios/browser/api/url/url_pattern_ios.mm new file mode 100644 index 000000000000..e6b506fcce2e --- /dev/null +++ b/ios/browser/api/url/url_pattern_ios.mm @@ -0,0 +1,296 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#include "brave/ios/browser/api/url/url_pattern_ios.h" + +#include "base/check_op.h" +#include "base/logging.h" +#include "base/strings/sys_string_conversions.h" +#include "extensions/common/url_pattern.h" +#include "net/base/mac/url_conversions.h" +#include "url/gurl.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +URLPatternIOSScheme IOSSchemeForSchemeMask( + int /* URLPattern::SchemeMasks */ masks) { + if (masks == -1) { + return URLPatternIOSSchemeAll; + } + URLPatternIOSScheme scheme = URLPatternIOSSchemeNone; + if (masks & URLPattern::SchemeMasks::SCHEME_HTTP) { + scheme |= URLPatternIOSSchemeHttp; + } + if (masks & URLPattern::SchemeMasks::SCHEME_HTTPS) { + scheme |= URLPatternIOSSchemeHttps; + } + if (masks & URLPattern::SchemeMasks::SCHEME_FILE) { + scheme |= URLPatternIOSSchemeFile; + } + if (masks & URLPattern::SchemeMasks::SCHEME_FTP) { + scheme |= URLPatternIOSSchemeFtp; + } + if (masks & URLPattern::SchemeMasks::SCHEME_CHROMEUI) { + scheme |= URLPatternIOSSchemeChromeUI; + } + if (masks & URLPattern::SchemeMasks::SCHEME_EXTENSION) { + scheme |= URLPatternIOSSchemeExtension; + } + if (masks & URLPattern::SchemeMasks::SCHEME_FILESYSTEM) { + scheme |= URLPatternIOSSchemeFilesystem; + } + if (masks & URLPattern::SchemeMasks::SCHEME_WS) { + scheme |= URLPatternIOSSchemeWs; + } + if (masks & URLPattern::SchemeMasks::SCHEME_WSS) { + scheme |= URLPatternIOSSchemeWss; + } + if (masks & URLPattern::SchemeMasks::SCHEME_DATA) { + scheme |= URLPatternIOSSchemeData; + } + if (masks & URLPattern::SchemeMasks::SCHEME_URN) { + scheme |= URLPatternIOSSchemeUrn; + } + if (masks & URLPattern::SchemeMasks::SCHEME_UUID_IN_PACKAGE) { + scheme |= URLPatternIOSSchemeUUIDInPackage; + } + DCHECK_EQ(static_cast(scheme), masks) + << "Obj-C mask conversion mismatch. Got " << static_cast(scheme) + << " Expected: " << masks; + return scheme; +} + +int /* URLPattern::SchemeMasks */ SchemeMasksForIOSScheme( + URLPatternIOSScheme masks) { + if (masks == URLPatternIOSSchemeAll) { + return URLPattern::SchemeMasks::SCHEME_ALL; + } + int scheme = URLPattern::SchemeMasks::SCHEME_NONE; + if (masks & URLPatternIOSSchemeHttp) { + scheme |= URLPattern::SchemeMasks::SCHEME_HTTP; + } + if (masks & URLPatternIOSSchemeHttps) { + scheme |= URLPattern::SchemeMasks::SCHEME_HTTPS; + } + if (masks & URLPatternIOSSchemeFile) { + scheme |= URLPattern::SchemeMasks::SCHEME_FILE; + } + if (masks & URLPatternIOSSchemeFtp) { + scheme |= URLPattern::SchemeMasks::SCHEME_FTP; + } + if (masks & URLPatternIOSSchemeChromeUI) { + scheme |= URLPattern::SchemeMasks::SCHEME_CHROMEUI; + } + if (masks & URLPatternIOSSchemeExtension) { + scheme |= URLPattern::SchemeMasks::SCHEME_EXTENSION; + } + if (masks & URLPatternIOSSchemeFilesystem) { + scheme |= URLPattern::SchemeMasks::SCHEME_FILESYSTEM; + } + if (masks & URLPatternIOSSchemeWs) { + scheme |= URLPattern::SchemeMasks::SCHEME_WS; + } + if (masks & URLPatternIOSSchemeWss) { + scheme |= URLPattern::SchemeMasks::SCHEME_WSS; + } + if (masks & URLPatternIOSSchemeData) { + scheme |= URLPattern::SchemeMasks::SCHEME_DATA; + } + if (masks & URLPatternIOSSchemeUrn) { + scheme |= URLPattern::SchemeMasks::SCHEME_URN; + } + if (masks & URLPatternIOSSchemeUUIDInPackage) { + scheme |= URLPattern::SchemeMasks::SCHEME_UUID_IN_PACKAGE; + } + DCHECK_EQ(static_cast(masks), scheme) + << "Obj-C mask conversion mismatch. Got " << scheme + << " Expected: " << static_cast(masks); + return scheme; +} + +URLPatternIOSParseResult const URLPatternIOSParseResultSuccess = + static_cast(URLPattern::ParseResult::kSuccess); +URLPatternIOSParseResult const URLPatternIOSParseResultMissingSchemeSeparator = + static_cast(URLPattern::ParseResult::kMissingSchemeSeparator); +URLPatternIOSParseResult const URLPatternIOSParseResultInvalidScheme = + static_cast(URLPattern::ParseResult::kInvalidScheme); +URLPatternIOSParseResult const URLPatternIOSParseResultWrongSchemeSeparator = + static_cast(URLPattern::ParseResult::kWrongSchemeSeparator); +URLPatternIOSParseResult const URLPatternIOSParseResultEmptyHost = + static_cast(URLPattern::ParseResult::kEmptyHost); +URLPatternIOSParseResult const URLPatternIOSParseResultInvalidHostWildcard = + static_cast(URLPattern::ParseResult::kInvalidHostWildcard); +URLPatternIOSParseResult const URLPatternIOSParseResultEmptyPath = + static_cast(URLPattern::ParseResult::kEmptyPath); +URLPatternIOSParseResult const URLPatternIOSParseResultInvalidPort = + static_cast(URLPattern::ParseResult::kInvalidPort); +URLPatternIOSParseResult const URLPatternIOSParseResultInvalidHost = + static_cast(URLPattern::ParseResult::kInvalidHost); + +@implementation URLPatternIOS { + std::unique_ptr url_pattern; +} + ++ (NSString*)allURLsPattern { + return base::SysUTF8ToNSString(URLPattern::kAllUrlsPattern); +} + +- (instancetype)init { + return [self initWithValidSchemes:URLPatternIOSSchemeNone]; +} + +- (instancetype)initWithValidSchemes:(URLPatternIOSScheme)schemes { + if ((self = [super init])) { + url_pattern = + std::make_unique(SchemeMasksForIOSScheme(schemes)); + } + return self; +} + +- (instancetype)initWithValidSchemes:(URLPatternIOSScheme)schemes + patternLiteral:(NSString*)patternLiteral { + if ((self = [self initWithValidSchemes:schemes])) { + const auto parseResult = [self parsePattern:patternLiteral]; + if (parseResult != URLPatternIOSParseResultSuccess) { + VLOG(0) << "Failed to parse pattern literal: \"" + << base::SysNSStringToUTF8(patternLiteral) << "\". Reason: " + << URLPattern::GetParseResultString( + static_cast(parseResult)); + } + } + return self; +} + +- (bool)isEqual:(id)obj { + if ([obj isKindOfClass:URLPatternIOS.class]) { + return *url_pattern == *static_cast(obj)->url_pattern; + } + return [super isEqual:obj]; +} + +- (NSString*)description { + return [NSString + stringWithFormat:@"<%@: %p; pattern = %@>", NSStringFromClass(self.class), + self, + base::SysUTF8ToNSString(url_pattern->GetAsString())]; +} + +- (id)copyWithZone:(NSZone*)zone { + auto other = [[URLPatternIOS alloc] initWithValidSchemes:self.validSchemes]; + other.scheme = self.scheme; + other.host = self.host; + other.port = self.port; + other.path = self.path; + other.isMatchingSubdomains = self.isMatchingSubdomains; + other.isMatchingAllURLs = self.isMatchingAllURLs; + return other; +} + +- (URLPatternIOSParseResult)parsePattern:(NSString*)pattern { + return static_cast( + url_pattern->Parse(base::SysNSStringToUTF8(pattern))); +} + +- (URLPatternIOSScheme)validSchemes { + return IOSSchemeForSchemeMask(url_pattern->valid_schemes()); +} + +- (void)setValidSchemes:(URLPatternIOSScheme)schemes { + url_pattern->SetValidSchemes(SchemeMasksForIOSScheme(schemes)); +} + +- (NSString*)scheme { + return base::SysUTF8ToNSString(url_pattern->scheme()); +} + +- (void)setScheme:(NSString*)scheme { + url_pattern->SetScheme(base::SysNSStringToUTF8(scheme)); +} + +- (NSString*)host { + return base::SysUTF8ToNSString(url_pattern->host()); +} + +- (void)setHost:(NSString*)host { + url_pattern->SetHost(base::SysNSStringToUTF8(host)); +} + +- (NSString*)port { + return base::SysUTF8ToNSString(url_pattern->port()); +} + +- (void)setPort:(NSString*)port { + url_pattern->SetPort(base::SysNSStringToUTF8(port)); +} + +- (NSString*)path { + return base::SysUTF8ToNSString(url_pattern->path()); +} + +- (void)setPath:(NSString*)path { + url_pattern->SetPath(base::SysNSStringToUTF8(path)); +} + +- (bool)isMatchingSubdomains { + return url_pattern->match_subdomains(); +} + +- (void)setIsMatchingSubdomains:(bool)matchingSubdomains { + url_pattern->SetMatchSubdomains(matchingSubdomains); +} + +- (bool)isMatchingAllURLs { + return url_pattern->match_all_urls(); +} + +- (void)setIsMatchingAllURLs:(bool)matchingAllURLs { + url_pattern->SetMatchAllURLs(matchingAllURLs); +} + +- (bool)isValidScheme:(NSString*)scheme { + return url_pattern->IsValidScheme(base::SysNSStringToUTF8(scheme)); +} + +- (bool)matchesURL:(NSURL*)url { + return url_pattern->MatchesURL(net::GURLWithNSURL(url)); +} + +- (bool)matchesSecurityOrigin:(NSURL*)origin { + return url_pattern->MatchesSecurityOrigin(net::GURLWithNSURL(origin)); +} + +- (bool)matchesScheme:(NSString*)scheme { + return url_pattern->MatchesScheme(base::SysNSStringToUTF8(scheme)); +} + +- (bool)matchesHost:(NSString*)host { + return url_pattern->MatchesHost(base::SysNSStringToUTF8(host)); +} + +- (bool)matchesPath:(NSString*)path { + return url_pattern->MatchesPath(base::SysNSStringToUTF8(path)); +} + +- (bool)matchesSingleOrigin { + return url_pattern->MatchesSingleOrigin(); +} + +- (bool)containsOtherURLPattern:(URLPatternIOS*)pattern { + if (!pattern) { + return false; + } + return url_pattern->Contains(*pattern->url_pattern); +} + +- (bool)overlapsWithOtherURLPattern:(URLPatternIOS*)pattern { + if (!pattern) { + return false; + } + return url_pattern->OverlapsWith(*pattern->url_pattern); +} + +@end