From ae735653fd4c69e265607117505ad14593578e4f Mon Sep 17 00:00:00 2001 From: Jonathan Cuendet <50955550+JOCuendet@users.noreply.github.com> Date: Thu, 22 Oct 2020 17:24:11 +0100 Subject: [PATCH] Make url scheme and host match and register case insensitive (#222) Currently the mapping and register of url routes are case sensitive The RFC-3986 states that theses routes should be case insensitive - Add case insensitive url routes mapping and register logic - Add UnitTests to test, said logic - Conform to RFC-3986 --- Sources/DeepLinking/Route+TrieRouter.swift | 10 +++++---- .../Route+TrieRouter_DescriptionTests.swift | 14 ++++++------ .../Route+TrieRouter_RouteTests.swift | 22 +++++++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Sources/DeepLinking/Route+TrieRouter.swift b/Sources/DeepLinking/Route+TrieRouter.swift index 8c1a1a81..9efa80a6 100644 --- a/Sources/DeepLinking/Route+TrieRouter.swift +++ b/Sources/DeepLinking/Route+TrieRouter.swift @@ -178,8 +178,9 @@ extension Route { private func parseAnnotatedRoute(_ route: URL) throws -> [Route.Component] { // use a wildcard for empty schemes/hosts, to match any scheme/host - let schemeComponent = route.scheme.constantOrWildcardComponent - let hostComponent = route.host.constantOrWildcardComponent + // URL scheme and host comparison should be case insensitive in conformance to RFC-3986 + let schemeComponent = (route.scheme?.lowercased()).constantOrWildcardComponent + let hostComponent = (route.host?.lowercased()).constantOrWildcardComponent do { let pathComponents = try route.pathComponents.filter { $0 != "/" }.map(Route.Component.init(component:)) @@ -204,8 +205,9 @@ extension Route { private func parseMatchRoute(_ route: URL) throws -> MatchRoute { // use an empty string for empty scheme/host, to match wildcard scheme/host - let schemeComponent = route.scheme ?? "" - let hostComponent = route.host ?? "" + // URL scheme and host comparison should be case insensitive in conformance to RFC-3986 + let schemeComponent = route.scheme?.lowercased() ?? "" + let hostComponent = route.host?.lowercased() ?? "" let pathComponents = route.pathComponents.filter { $0 != "/" } let routeComponents = [schemeComponent, hostComponent] + pathComponents diff --git a/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_DescriptionTests.swift b/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_DescriptionTests.swift index c16e9c17..b35cf081 100644 --- a/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_DescriptionTests.swift +++ b/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_DescriptionTests.swift @@ -57,22 +57,22 @@ class Route_TrieRouter_DescriptionTests: XCTestCase { XCTAssertEqual( router.description, """ - ├──┬ schemeB - │ ├──┬ hostB + ├──┬ schemeb + │ ├──┬ hosta + │ │ └──● AnyRouteHandler(P) + │ │ + │ ├──┬ hostb │ │ └──┬ path │ │ ├──┬ * │ │ │ └──● AnyRouteHandler(R) │ │ │ │ │ └──● AnyRouteHandler(Q) │ │ - │ ├──┬ hostA - │ │ └──● AnyRouteHandler(P) - │ │ │ └──┬ * │ └──● AnyRouteHandler(O) │ - ├──┬ schemeA - │ ├──┬ hostC + ├──┬ schemea + │ ├──┬ hostc │ │ └──┬ * │ │ └──┬ yet │ │ └──┬ another diff --git a/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_RouteTests.swift b/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_RouteTests.swift index 2162450e..fe970054 100644 --- a/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_RouteTests.swift +++ b/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_RouteTests.swift @@ -130,6 +130,18 @@ class Route_TrieRouter_RouteTests: XCTestCase { XCTAssertRouteSucceeds(initial: [("/", testHandler)], route: "://") } + func testRoute_WithCaseInsensitiveMatchingScheme_ShouldSucceed() { + XCTAssertRouteSucceeds(initial: [("HTTP://", testHandler)], route: "http://") + XCTAssertRouteSucceeds(initial: [("HTTP://host/", testHandler)], route: "http://host/") + XCTAssertRouteSucceeds(initial: [("HTTP://host/path", testHandler)], route: "http://host/path") + XCTAssertRouteSucceeds(initial: [("HtTp://host/path", testHandler)], route: "http://host/path") + + XCTAssertRouteSucceeds(initial: [("http://", testHandler)], route: "HTTP://") + XCTAssertRouteSucceeds(initial: [("http://host/", testHandler)], route: "HTTP://host/") + XCTAssertRouteSucceeds(initial: [("http://host/path", testHandler)], route: "HTTP://host/path") + XCTAssertRouteSucceeds(initial: [("http://host/path", testHandler)], route: "httP://host/path") + } + // MARK: host func testRoute_WithMatchingHost_ShouldSucceed() { @@ -163,6 +175,16 @@ class Route_TrieRouter_RouteTests: XCTestCase { XCTAssertRouteSucceeds(initial: [("/", testHandler)], route: ":///") } + func testRoute_WithCaseInsensitiveMatchingHost_ShouldSucceed() { + XCTAssertRouteSucceeds(initial: [("http://HOST/", testHandler)], route: "http://host/") + XCTAssertRouteSucceeds(initial: [("http://HOST/path", testHandler)], route: "http://host/path") + XCTAssertRouteSucceeds(initial: [("http://HosT/path", testHandler)], route: "http://host/path") + + XCTAssertRouteSucceeds(initial: [("http://host/", testHandler)], route: "http://HOST/") + XCTAssertRouteSucceeds(initial: [("http://host/path", testHandler)], route: "http://HOST/path") + XCTAssertRouteSucceeds(initial: [("http://host/path", testHandler)], route: "http://hOSt/path") + } + // MARK: single level path func testRoute_WithMatchingSingleLevelPath_ShouldSucceed() {