From cb6393bdb121593b57c1e959767a2f28c95aa990 Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Fri, 23 Aug 2024 23:31:11 +0800 Subject: [PATCH 1/4] fix: avoid path trailing slash stripped in older os versions Co-authored-by: esterTion --- xcode/Shared/UrlPolyfill.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xcode/Shared/UrlPolyfill.swift b/xcode/Shared/UrlPolyfill.swift index dc76e2b3..915ae8a7 100644 --- a/xcode/Shared/UrlPolyfill.swift +++ b/xcode/Shared/UrlPolyfill.swift @@ -79,6 +79,9 @@ func jsLikeURL(_ urlString: String, baseString: String? = nil) -> [String: Strin let host = (port == "") ? hostname : "\(hostname):\(port)" let query = url.query ?? "" let fragment = url.fragment ?? "" + /// If the path has a trailing slash, it is stripped. https://developer.apple.com/documentation/foundation/nsurl/1408809-path + let strippedPath = url.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? url.path + let pathname = (strippedPath != "/" && url.hasDirectoryPath) ? "\(strippedPath)/" : strippedPath return [ "hash": fragment == "" ? "" : "#\(fragment)", "host": host, @@ -86,7 +89,7 @@ func jsLikeURL(_ urlString: String, baseString: String? = nil) -> [String: Strin // "href": url.absoluteString, "origin": "\(scheme)://\(host)", "password": url.password ?? "", - "pathname": url.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? url.path, + "pathname": pathname, "port": port, "protocol": "\(scheme):", "search": query == "" ? "" : "?\(query)", From d216e3bdc49f2fcda650b6364e8672e4470d37f7 Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Fri, 23 Aug 2024 23:43:55 +0800 Subject: [PATCH 2/4] refactor: improve trailing slash detection --- xcode/Shared/UrlPolyfill.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcode/Shared/UrlPolyfill.swift b/xcode/Shared/UrlPolyfill.swift index 915ae8a7..5ebac865 100644 --- a/xcode/Shared/UrlPolyfill.swift +++ b/xcode/Shared/UrlPolyfill.swift @@ -81,7 +81,7 @@ func jsLikeURL(_ urlString: String, baseString: String? = nil) -> [String: Strin let fragment = url.fragment ?? "" /// If the path has a trailing slash, it is stripped. https://developer.apple.com/documentation/foundation/nsurl/1408809-path let strippedPath = url.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? url.path - let pathname = (strippedPath != "/" && url.hasDirectoryPath) ? "\(strippedPath)/" : strippedPath + let pathname = (!strippedPath.hasSuffix("/") && url.hasDirectoryPath) ? "\(strippedPath)/" : strippedPath return [ "hash": fragment == "" ? "" : "#\(fragment)", "host": host, From a79549f5741e1063e3cf5c541bc708ca7b727ece Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Sat, 24 Aug 2024 08:08:38 +0800 Subject: [PATCH 3/4] test: add some match test cases --- xcode/Ext-Safari/Functions.swift | 2 +- xcode/Tests-Mac/UserscriptsTests.swift | 131 ++++++++++++++++++++++--- 2 files changed, 117 insertions(+), 16 deletions(-) diff --git a/xcode/Ext-Safari/Functions.swift b/xcode/Ext-Safari/Functions.swift index 18717b9f..9d8de01f 100644 --- a/xcode/Ext-Safari/Functions.swift +++ b/xcode/Ext-Safari/Functions.swift @@ -1047,7 +1047,7 @@ func match(_ url: String, _ matchPattern: String) -> Bool { let partsPatternReg = try! NSRegularExpression(pattern: partsPattern, options: .caseInsensitive) let range = NSMakeRange(0, matchPattern.utf16.count) guard let parts = partsPatternReg.firstMatch(in: matchPattern, options: [], range: range) else { - logger?.error("\(#function, privacy: .public) - malformed regex match pattern") + logger?.error("\(#function, privacy: .public) - malformed regex match pattern - \(matchPattern, privacy: .public)") return false } // ensure url protocol matches pattern protocol diff --git a/xcode/Tests-Mac/UserscriptsTests.swift b/xcode/Tests-Mac/UserscriptsTests.swift index 451f9e99..b099c0b8 100644 --- a/xcode/Tests-Mac/UserscriptsTests.swift +++ b/xcode/Tests-Mac/UserscriptsTests.swift @@ -124,59 +124,160 @@ class UserscriptsTests: XCTestCase { } func testMatching() throws { + /// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns#examples let patternDict = [ + "": [ + "http://example.org/", + "https://a.org/some/path/" + ], "*://*/*": [ - "https://www.bing.com/", - "https://example.org/foo/bar.html", + "http://example.org/", "https://a.org/some/path/", - "https://☁️.com/" + "https://www.example.com/", + "https://example.org/foo/bar.html", + "https://☁️.com/", + "https://🙂.☁️/", ], "*://*.mozilla.org/*": [ "http://mozilla.org/", "https://mozilla.org/", - "https://b.mozilla.org/path/" + "http://a.mozilla.org/", + "http://a.b.mozilla.org/", + "https://b.mozilla.org/path/", + ], + "*://mozilla.org/": [ + "http://mozilla.org/", + "https://mozilla.org/", + ], + "https://*/path": [ + "https://mozilla.org/path", + "https://a.mozilla.org/path", + "https://something.com/path", + ], + "https://*/path/": [ + "https://mozilla.org/path/", + "https://a.mozilla.org/path/", + "https://something.com/path/", + ], + "https://mozilla.org/*": [ + "https://mozilla.org/", + "https://mozilla.org/path", + "https://mozilla.org/another", + "https://mozilla.org/path/to/doc", + "https://mozilla.org/path/to/doc?foo=1", + ], + "https://mozilla.org/a/b/c/": [ + "https://mozilla.org/a/b/c/", + "https://mozilla.org/a/b/c/#section1", + ], + "https://mozilla.org/*/b/*/": [ + "https://mozilla.org/a/b/c/", + "https://mozilla.org/d/b/f/", + "https://mozilla.org/a/b/c/d/", + "https://mozilla.org/a/b/c/d/#section1", + "https://mozilla.org/a/b/c/d/?foo=/", + "https://mozilla.org/a?foo=21314&bar=/b/&extra=c/", ], "*://www.google.com/*": [ "https://www.google.com/://aa", "https://www.google.com/preferences?prev=https://www.google.com/", "https://www.google.com/preferences?prev=", - "https://www.google.com/" + "https://www.google.com/", ], "*://localhost/*": [ "http://localhost:8000/", - "https://localhost:3000/foo.html" + "https://localhost:3000/foo.html", ], "http://127.0.0.1/*": [ "http://127.0.0.1/", - "http://127.0.0.1/foo/bar.html" + "http://127.0.0.1/foo/bar.html", + "http://127.0.0.1/?bar=1", + "http://127.0.0.1/foo?bar=1", ], "*://*.example.com/*?a=1*": [ "http://example.com/?a=1", - "https://www.example.com/index?a=1&b=2" - ] + "https://www.example.com/index?a=1&b=2", + ], + "*://*.example.com/path/": [ + "http://example.com/path/", + "http://a.example.com/path/", + "https://example.com/path/", + "https://a.b.example.com/path/", + "https://☁️.example.com/path/", + ], + "*://*.example.com/path/*": [ + "https://example.com/path/", + "https://example.com/path/foo", + "https://example.com/path/foo?bar", + "https://example.com/path/?foo=bar", + "https://example.com/path/☁️?☁️=🙂", + ], ] let patternDictFails = [ + "*://*/*": [ + "ftp://ftp.example.org/", // unmatched scheme) + "file:///a/", // unmatched scheme + ], + "*://*.mozilla.org/*": [ + "ftp://mozilla.org/", // unmatched scheme + "http://mozilla.com/", // unmatched host + "http://firefox.org/", // unmatched host + ], + "*://mozilla.org/": [ + "ftp://mozilla.org/", // unmatched scheme + "http://a.mozilla.org/", // unmatched host + "http://mozilla.org/a", // unmatched path + ], + "https://*/path": [ + "http://mozilla.org/path", // unmatched scheme + "https://mozilla.org/path/", // unmatched path + "https://mozilla.org/a", // unmatched path + "https://mozilla.org/", // unmatched path + "https://mozilla.org/path?foo=1", // unmatched path due to URL query string + ], + "https://*/path/": [ + "http://mozilla.org/path/", // unmatched scheme + "https://mozilla.org/path", // unmatched path + "https://mozilla.org/a", // unmatched path + "https://mozilla.org/", // unmatched path + "https://mozilla.org/path/?foo=1", // unmatched path due to URL query string + ], + "https://mozilla.org/*": [ + "http://mozilla.org/path", // unmatched scheme + "https://mozilla.com/path", // unmatched host + ], + "https://mozilla.org/*/b/*/": [ + "https://mozilla.org/b/*/", // unmatched path + "https://mozilla.org/a/b/", // unmatched path + "https://mozilla.org/a/b/c/d/?foo=bar", // unmatched path due to URL query string + ], "https://www.example.com/*": [ "file://www.example.com/", "ftp://www.example.com/", "ws://www.example.com/", - "http://www.example.com/" + "http://www.example.com/", ], "http://www.example.com/index.html": [ "http://www.example.com/", - "https://www.example.com/index.html" + "https://www.example.com/index.html", ], "*://localhost/*": [ "https://localhost.com/", - "ftp://localhost:8080/" + "ftp://localhost:8080/", ], "https://www.example*/*": [ - "https://www.example.com/" + "https://www.example.com/", ], "*://*.example.com/*?a=1*": [ "http://example.com/", - "https://www.example.com/?a=2" - ] + "https://www.example.com/?a=2", + ], + "*://*.example.com/foo/": [ + "https://example.com/foo", + "https://example.com/foo?bar", + "https://example.com/foo/bar", + "https://example.com/foo/?bar", + ], ] for (pattern, urls) in patternDict { for url in urls { From a27ae56cbd1c5ce7f220a25c6910ff78f32d6059 Mon Sep 17 00:00:00 2001 From: ACTCD <101378590+ACTCD@users.noreply.github.com> Date: Sat, 24 Aug 2024 09:13:29 +0800 Subject: [PATCH 4/4] refactor: revert d216e3b and add a new test case --- xcode/Shared/UrlPolyfill.swift | 2 +- xcode/Tests-Mac/UrlCodecTests.swift | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/xcode/Shared/UrlPolyfill.swift b/xcode/Shared/UrlPolyfill.swift index 5ebac865..915ae8a7 100644 --- a/xcode/Shared/UrlPolyfill.swift +++ b/xcode/Shared/UrlPolyfill.swift @@ -81,7 +81,7 @@ func jsLikeURL(_ urlString: String, baseString: String? = nil) -> [String: Strin let fragment = url.fragment ?? "" /// If the path has a trailing slash, it is stripped. https://developer.apple.com/documentation/foundation/nsurl/1408809-path let strippedPath = url.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? url.path - let pathname = (!strippedPath.hasSuffix("/") && url.hasDirectoryPath) ? "\(strippedPath)/" : strippedPath + let pathname = (strippedPath != "/" && url.hasDirectoryPath) ? "\(strippedPath)/" : strippedPath return [ "hash": fragment == "" ? "" : "#\(fragment)", "host": host, diff --git a/xcode/Tests-Mac/UrlCodecTests.swift b/xcode/Tests-Mac/UrlCodecTests.swift index 0188cd00..ca54bcfd 100644 --- a/xcode/Tests-Mac/UrlCodecTests.swift +++ b/xcode/Tests-Mac/UrlCodecTests.swift @@ -149,6 +149,20 @@ final class UrlCodecTests: XCTestCase { "search": "", "username": "" ])) + + XCTAssert(check("http://host.test/path//", [ + "hash": "", + "host": "host.test", + "hostname": "host.test", + // "href": "http://host.test/path//", + "origin": "http://host.test", + "password": "", + "pathname": "/path//", + "port": "", + "protocol": "http:", + "search": "", + "username": "" + ])) XCTAssert(check("http://host.test/", [ "hash": "",