Commit 854dd9d735

854dd9d73528dcea717a01775ad0d2be19b14079

parent: e519d100b6

Unsigned

cmc <hello@cleberg.net> · 2026-03-19 00:16 UTC

Guard authenticated text fetches to sr.ht hosts and add regression test

Layout: unified · split

Hutch.xcodeproj/project.pbxproj +130
@@ -13,12 +13,25 @@
13 8B2F89682F69DEB900FC0253 /* SECURITY.md in Resources */ = {isa = PBXBuildFile; fileRef = 8B2F89652F69DEB900FC0253 /* SECURITY.md */; }; 13 8B2F89682F69DEB900FC0253 /* SECURITY.md in Resources */ = {isa = PBXBuildFile; fileRef = 8B2F89652F69DEB900FC0253 /* SECURITY.md */; };
14/* End PBXBuildFile section */ 14/* End PBXBuildFile section */
15 15
16/* Begin PBXContainerItemProxy section */
17 8B8182BB2F6B73F3000AE049 /* PBXContainerItemProxy */ = {
18 isa = PBXContainerItemProxy;
19 containerPortal = 8B4B28C92F6704280045FA19 /* Project object */;
20 proxyType = 1;
21 remoteGlobalIDString = 8B4B28D02F6704280045FA19;
22 remoteInfo = Hutch;
23 };
24/* End PBXContainerItemProxy section */
25
16/* Begin PBXFileReference section */ 26/* Begin PBXFileReference section */
17 8B2F89612F69DEA900FC0253 /* TODO.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TODO.md; sourceTree = "<group>"; }; 27 8B2F89612F69DEA900FC0253 /* TODO.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TODO.md; sourceTree = "<group>"; };
18 8B2F89632F69DEB900FC0253 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; }; 28 8B2F89632F69DEB900FC0253 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
19 8B2F89642F69DEB900FC0253 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; 29 8B2F89642F69DEB900FC0253 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
20 8B2F89652F69DEB900FC0253 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; }; 30 8B2F89652F69DEB900FC0253 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
21 8B4B28D12F6704280045FA19 /* Hutch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Hutch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 8B4B28D12F6704280045FA19 /* Hutch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Hutch.app; sourceTree = BUILT_PRODUCTS_DIR; };
32 8B8182B72F6B73F3000AE049 /* HutchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HutchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
33 8B8182C22F6B742B000AE049 /* HutchTests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = HutchTests; sourceTree = "<group>"; };
34 8BDCA5272F6B76B20066AA29 /* HutchTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = HutchTests.xctestplan; sourceTree = "<group>"; };
22/* End PBXFileReference section */ 35/* End PBXFileReference section */
23 36
24/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 37/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -40,6 +53,11 @@
40 path = Hutch; 53 path = Hutch;
41 sourceTree = "<group>"; 54 sourceTree = "<group>";
42 }; 55 };
56 8B8182B82F6B73F3000AE049 /* HutchTests */ = {
57 isa = PBXFileSystemSynchronizedRootGroup;
58 path = HutchTests;
59 sourceTree = "<group>";
60 };
43/* End PBXFileSystemSynchronizedRootGroup section */ 61/* End PBXFileSystemSynchronizedRootGroup section */
44 62
45/* Begin PBXFrameworksBuildPhase section */ 63/* Begin PBXFrameworksBuildPhase section */
@@ -50,17 +68,27 @@
50 ); 68 );
51 runOnlyForDeploymentPostprocessing = 0; 69 runOnlyForDeploymentPostprocessing = 0;
52 }; 70 };
71 8B8182B42F6B73F3000AE049 /* Frameworks */ = {
72 isa = PBXFrameworksBuildPhase;
73 buildActionMask = 2147483647;
74 files = (
75 );
76 runOnlyForDeploymentPostprocessing = 0;
77 };
53/* End PBXFrameworksBuildPhase section */ 78/* End PBXFrameworksBuildPhase section */
54 79
55/* Begin PBXGroup section */ 80/* Begin PBXGroup section */
56 8B4B28C82F6704280045FA19 = { 81 8B4B28C82F6704280045FA19 = {
57 isa = PBXGroup; 82 isa = PBXGroup;
58 children = ( 83 children = (
84 8BDCA5272F6B76B20066AA29 /* HutchTests.xctestplan */,
85 8B8182C22F6B742B000AE049 /* HutchTests */,
59 8B2F89632F69DEB900FC0253 /* LICENSE */, 86 8B2F89632F69DEB900FC0253 /* LICENSE */,
60 8B2F89642F69DEB900FC0253 /* README.md */, 87 8B2F89642F69DEB900FC0253 /* README.md */,
61 8B2F89652F69DEB900FC0253 /* SECURITY.md */, 88 8B2F89652F69DEB900FC0253 /* SECURITY.md */,
62 8B2F89612F69DEA900FC0253 /* TODO.md */, 89 8B2F89612F69DEA900FC0253 /* TODO.md */,
63 8B4B28D32F6704280045FA19 /* Hutch */, 90 8B4B28D32F6704280045FA19 /* Hutch */,
91 8B8182B82F6B73F3000AE049 /* HutchTests */,
64 8B4B28D22F6704280045FA19 /* Products */, 92 8B4B28D22F6704280045FA19 /* Products */,
65 ); 93 );
66 sourceTree = "<group>"; 94 sourceTree = "<group>";
@@ -69,6 +97,7 @@
69 isa = PBXGroup; 97 isa = PBXGroup;
70 children = ( 98 children = (
71 8B4B28D12F6704280045FA19 /* Hutch.app */, 99 8B4B28D12F6704280045FA19 /* Hutch.app */,
100 8B8182B72F6B73F3000AE049 /* HutchTests.xctest */,
72 ); 101 );
73 name = Products; 102 name = Products;
74 sourceTree = "<group>"; 103 sourceTree = "<group>";
@@ -98,6 +127,29 @@
98 productReference = 8B4B28D12F6704280045FA19 /* Hutch.app */; 127 productReference = 8B4B28D12F6704280045FA19 /* Hutch.app */;
99 productType = "com.apple.product-type.application"; 128 productType = "com.apple.product-type.application";
100 }; 129 };
130 8B8182B62F6B73F3000AE049 /* HutchTests */ = {
131 isa = PBXNativeTarget;
132 buildConfigurationList = 8B8182BF2F6B73F3000AE049 /* Build configuration list for PBXNativeTarget "HutchTests" */;
133 buildPhases = (
134 8B8182B32F6B73F3000AE049 /* Sources */,
135 8B8182B42F6B73F3000AE049 /* Frameworks */,
136 8B8182B52F6B73F3000AE049 /* Resources */,
137 );
138 buildRules = (
139 );
140 dependencies = (
141 8B8182BC2F6B73F3000AE049 /* PBXTargetDependency */,
142 );
143 fileSystemSynchronizedGroups = (
144 8B8182B82F6B73F3000AE049 /* HutchTests */,
145 );
146 name = HutchTests;
147 packageProductDependencies = (
148 );
149 productName = HutchTests;
150 productReference = 8B8182B72F6B73F3000AE049 /* HutchTests.xctest */;
151 productType = "com.apple.product-type.bundle.unit-test";
152 };
101/* End PBXNativeTarget section */ 153/* End PBXNativeTarget section */
102 154
103/* Begin PBXProject section */ 155/* Begin PBXProject section */
@@ -111,6 +163,10 @@
111 8B4B28D02F6704280045FA19 = { 163 8B4B28D02F6704280045FA19 = {
112 CreatedOnToolsVersion = 26.3; 164 CreatedOnToolsVersion = 26.3;
113 }; 165 };
166 8B8182B62F6B73F3000AE049 = {
167 CreatedOnToolsVersion = 26.3;
168 TestTargetID = 8B4B28D02F6704280045FA19;
169 };
114 }; 170 };
115 }; 171 };
116 buildConfigurationList = 8B4B28CC2F6704280045FA19 /* Build configuration list for PBXProject "Hutch" */; 172 buildConfigurationList = 8B4B28CC2F6704280045FA19 /* Build configuration list for PBXProject "Hutch" */;
@@ -128,6 +184,7 @@
128 projectRoot = ""; 184 projectRoot = "";
129 targets = ( 185 targets = (
130 8B4B28D02F6704280045FA19 /* Hutch */, 186 8B4B28D02F6704280045FA19 /* Hutch */,
187 8B8182B62F6B73F3000AE049 /* HutchTests */,
131 ); 188 );
132 }; 189 };
133/* End PBXProject section */ 190/* End PBXProject section */
@@ -144,6 +201,13 @@
144 ); 201 );
145 runOnlyForDeploymentPostprocessing = 0; 202 runOnlyForDeploymentPostprocessing = 0;
146 }; 203 };
204 8B8182B52F6B73F3000AE049 /* Resources */ = {
205 isa = PBXResourcesBuildPhase;
206 buildActionMask = 2147483647;
207 files = (
208 );
209 runOnlyForDeploymentPostprocessing = 0;
210 };
147/* End PBXResourcesBuildPhase section */ 211/* End PBXResourcesBuildPhase section */
148 212
149/* Begin PBXSourcesBuildPhase section */ 213/* Begin PBXSourcesBuildPhase section */
@@ -154,8 +218,23 @@
154 ); 218 );
155 runOnlyForDeploymentPostprocessing = 0; 219 runOnlyForDeploymentPostprocessing = 0;
156 }; 220 };
221 8B8182B32F6B73F3000AE049 /* Sources */ = {
222 isa = PBXSourcesBuildPhase;
223 buildActionMask = 2147483647;
224 files = (
225 );
226 runOnlyForDeploymentPostprocessing = 0;
227 };
157/* End PBXSourcesBuildPhase section */ 228/* End PBXSourcesBuildPhase section */
158 229
230/* Begin PBXTargetDependency section */
231 8B8182BC2F6B73F3000AE049 /* PBXTargetDependency */ = {
232 isa = PBXTargetDependency;
233 target = 8B4B28D02F6704280045FA19 /* Hutch */;
234 targetProxy = 8B8182BB2F6B73F3000AE049 /* PBXContainerItemProxy */;
235 };
236/* End PBXTargetDependency section */
237
159/* Begin XCBuildConfiguration section */ 238/* Begin XCBuildConfiguration section */
160 8B4B28DA2F6704290045FA19 /* Debug */ = { 239 8B4B28DA2F6704290045FA19 /* Debug */ = {
161 isa = XCBuildConfiguration; 240 isa = XCBuildConfiguration;
@@ -350,6 +429,48 @@
350 }; 429 };
351 name = Release; 430 name = Release;
352 }; 431 };
432 8B8182BD2F6B73F3000AE049 /* Debug */ = {
433 isa = XCBuildConfiguration;
434 buildSettings = {
435 BUNDLE_LOADER = "$(TEST_HOST)";
436 CODE_SIGN_STYLE = Automatic;
437 CURRENT_PROJECT_VERSION = 1;
438 DEVELOPMENT_TEAM = ZCNAX3VL9D;
439 GENERATE_INFOPLIST_FILE = YES;
440 MARKETING_VERSION = 1.0;
441 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.HutchTests;
442 PRODUCT_NAME = "$(TARGET_NAME)";
443 STRING_CATALOG_GENERATE_SYMBOLS = NO;
444 SWIFT_APPROACHABLE_CONCURRENCY = YES;
445 SWIFT_EMIT_LOC_STRINGS = NO;
446 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
447 SWIFT_VERSION = 5.0;
448 TARGETED_DEVICE_FAMILY = "1,2";
449 TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Hutch.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Hutch";
450 };
451 name = Debug;
452 };
453 8B8182BE2F6B73F3000AE049 /* Release */ = {
454 isa = XCBuildConfiguration;
455 buildSettings = {
456 BUNDLE_LOADER = "$(TEST_HOST)";
457 CODE_SIGN_STYLE = Automatic;
458 CURRENT_PROJECT_VERSION = 1;
459 DEVELOPMENT_TEAM = ZCNAX3VL9D;
460 GENERATE_INFOPLIST_FILE = YES;
461 MARKETING_VERSION = 1.0;
462 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.HutchTests;
463 PRODUCT_NAME = "$(TARGET_NAME)";
464 STRING_CATALOG_GENERATE_SYMBOLS = NO;
465 SWIFT_APPROACHABLE_CONCURRENCY = YES;
466 SWIFT_EMIT_LOC_STRINGS = NO;
467 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
468 SWIFT_VERSION = 5.0;
469 TARGETED_DEVICE_FAMILY = "1,2";
470 TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Hutch.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Hutch";
471 };
472 name = Release;
473 };
353/* End XCBuildConfiguration section */ 474/* End XCBuildConfiguration section */
354 475
355/* Begin XCConfigurationList section */ 476/* Begin XCConfigurationList section */
@@ -371,6 +492,15 @@
371 defaultConfigurationIsVisible = 0; 492 defaultConfigurationIsVisible = 0;
372 defaultConfigurationName = Release; 493 defaultConfigurationName = Release;
373 }; 494 };
495 8B8182BF2F6B73F3000AE049 /* Build configuration list for PBXNativeTarget "HutchTests" */ = {
496 isa = XCConfigurationList;
497 buildConfigurations = (
498 8B8182BD2F6B73F3000AE049 /* Debug */,
499 8B8182BE2F6B73F3000AE049 /* Release */,
500 );
501 defaultConfigurationIsVisible = 0;
502 defaultConfigurationName = Release;
503 };
374/* End XCConfigurationList section */ 504/* End XCConfigurationList section */
375 }; 505 };
376 rootObject = 8B4B28C92F6704280045FA19 /* Project object */; 506 rootObject = 8B4B28C92F6704280045FA19 /* Project object */;
Hutch.xcodeproj/xcshareddata/xcschemes/Hutch.xcscheme added +96
@@ -0,0 +1,96 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<Scheme
3 LastUpgradeVersion = "2630"
4 version = "1.7">
5 <BuildAction
6 parallelizeBuildables = "YES"
7 buildImplicitDependencies = "YES"
8 buildArchitectures = "Automatic">
9 <BuildActionEntries>
10 <BuildActionEntry
11 buildForTesting = "YES"
12 buildForRunning = "YES"
13 buildForProfiling = "YES"
14 buildForArchiving = "YES"
15 buildForAnalyzing = "YES">
16 <BuildableReference
17 BuildableIdentifier = "primary"
18 BlueprintIdentifier = "8B4B28D02F6704280045FA19"
19 BuildableName = "Hutch.app"
20 BlueprintName = "Hutch"
21 ReferencedContainer = "container:Hutch.xcodeproj">
22 </BuildableReference>
23 </BuildActionEntry>
24 </BuildActionEntries>
25 </BuildAction>
26 <TestAction
27 buildConfiguration = "Debug"
28 selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
29 selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
30 shouldUseLaunchSchemeArgsEnv = "YES">
31 <TestPlans>
32 <TestPlanReference
33 reference = "container:HutchTests"
34 default = "YES">
35 </TestPlanReference>
36 </TestPlans>
37 <Testables>
38 <TestableReference
39 skipped = "NO"
40 parallelizable = "YES">
41 <BuildableReference
42 BuildableIdentifier = "primary"
43 BlueprintIdentifier = "8B8182B62F6B73F3000AE049"
44 BuildableName = "HutchTests.xctest"
45 BlueprintName = "HutchTests"
46 ReferencedContainer = "container:Hutch.xcodeproj">
47 </BuildableReference>
48 </TestableReference>
49 </Testables>
50 </TestAction>
51 <LaunchAction
52 buildConfiguration = "Debug"
53 selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
54 selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
55 launchStyle = "0"
56 useCustomWorkingDirectory = "NO"
57 ignoresPersistentStateOnLaunch = "NO"
58 debugDocumentVersioning = "YES"
59 debugServiceExtension = "internal"
60 allowLocationSimulation = "YES">
61 <BuildableProductRunnable
62 runnableDebuggingMode = "0">
63 <BuildableReference
64 BuildableIdentifier = "primary"
65 BlueprintIdentifier = "8B4B28D02F6704280045FA19"
66 BuildableName = "Hutch.app"
67 BlueprintName = "Hutch"
68 ReferencedContainer = "container:Hutch.xcodeproj">
69 </BuildableReference>
70 </BuildableProductRunnable>
71 </LaunchAction>
72 <ProfileAction
73 buildConfiguration = "Release"
74 shouldUseLaunchSchemeArgsEnv = "YES"
75 savedToolIdentifier = ""
76 useCustomWorkingDirectory = "NO"
77 debugDocumentVersioning = "YES">
78 <BuildableProductRunnable
79 runnableDebuggingMode = "0">
80 <BuildableReference
81 BuildableIdentifier = "primary"
82 BlueprintIdentifier = "8B4B28D02F6704280045FA19"
83 BuildableName = "Hutch.app"
84 BlueprintName = "Hutch"
85 ReferencedContainer = "container:Hutch.xcodeproj">
86 </BuildableReference>
87 </BuildableProductRunnable>
88 </ProfileAction>
89 <AnalyzeAction
90 buildConfiguration = "Debug">
91 </AnalyzeAction>
92 <ArchiveAction
93 buildConfiguration = "Release"
94 revealArchiveInOrganizer = "YES">
95 </ArchiveAction>
96</Scheme>
Hutch.xcodeproj/xcshareddata/xcschemes/HutchTests.xcscheme added +69
@@ -0,0 +1,69 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<Scheme
3 LastUpgradeVersion = "2630"
4 version = "1.7">
5 <BuildAction
6 parallelizeBuildables = "YES"
7 buildImplicitDependencies = "YES"
8 buildArchitectures = "Automatic">
9 </BuildAction>
10 <TestAction
11 buildConfiguration = "Debug"
12 selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
13 selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
14 shouldUseLaunchSchemeArgsEnv = "YES">
15 <TestPlans>
16 <TestPlanReference
17 reference = "container:HutchTests.xctestplan"
18 default = "YES">
19 </TestPlanReference>
20 </TestPlans>
21 <Testables>
22 <TestableReference
23 skipped = "NO"
24 parallelizable = "YES">
25 <BuildableReference
26 BuildableIdentifier = "primary"
27 BlueprintIdentifier = "8B8182B62F6B73F3000AE049"
28 BuildableName = "HutchTests.xctest"
29 BlueprintName = "HutchTests"
30 ReferencedContainer = "container:Hutch.xcodeproj">
31 </BuildableReference>
32 </TestableReference>
33 </Testables>
34 </TestAction>
35 <LaunchAction
36 buildConfiguration = "Debug"
37 selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
38 selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
39 launchStyle = "0"
40 useCustomWorkingDirectory = "NO"
41 ignoresPersistentStateOnLaunch = "NO"
42 debugDocumentVersioning = "YES"
43 debugServiceExtension = "internal"
44 allowLocationSimulation = "YES">
45 <MacroExpansion>
46 <BuildableReference
47 BuildableIdentifier = "primary"
48 BlueprintIdentifier = "8B4B28D02F6704280045FA19"
49 BuildableName = "Hutch.app"
50 BlueprintName = "Hutch"
51 ReferencedContainer = "container:Hutch.xcodeproj">
52 </BuildableReference>
53 </MacroExpansion>
54 </LaunchAction>
55 <ProfileAction
56 buildConfiguration = "Release"
57 shouldUseLaunchSchemeArgsEnv = "YES"
58 savedToolIdentifier = ""
59 useCustomWorkingDirectory = "NO"
60 debugDocumentVersioning = "YES">
61 </ProfileAction>
62 <AnalyzeAction
63 buildConfiguration = "Debug">
64 </AnalyzeAction>
65 <ArchiveAction
66 buildConfiguration = "Release"
67 revealArchiveInOrganizer = "YES">
68 </ArchiveAction>
69</Scheme>
Hutch/Networking/SRHTClient.swift +14
@@ -435,6 +435,9 @@ final class SRHTClient: Sendable {
435 guard let token = _token.withLock({ $0 }), !token.isEmpty else { 435 guard let token = _token.withLock({ $0 }), !token.isEmpty else {
436 throw SRHTError.unauthorized 436 throw SRHTError.unauthorized
437 } 437 }
438 guard Self.isTrustedAuthenticatedTextURL(url) else {
439 throw SRHTError.invalidAuthenticatedURL(url)
440 }
438 441
439 var request = URLRequest(url: url) 442 var request = URLRequest(url: url)
440 request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") 443 request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
@@ -512,6 +515,17 @@ final class SRHTClient: Sendable {
512 515
513// MARK: - Data Helper 516// MARK: - Data Helper
514 517
518private extension SRHTClient {
519 static func isTrustedAuthenticatedTextURL(_ url: URL) -> Bool {
520 guard url.scheme?.localizedCaseInsensitiveCompare("https") == .orderedSame,
521 let host = url.host?.lowercased() else {
522 return false
523 }
524
525 return host.hasSuffix(".sr.ht")
526 }
527}
528
515private extension Data { 529private extension Data {
516 mutating func append(_ string: String) { 530 mutating func append(_ string: String) {
517 if let data = string.data(using: .utf8) { 531 if let data = string.data(using: .utf8) {
Hutch/Networking/SRHTError.swift +4
@@ -6,6 +6,8 @@ enum SRHTError: LocalizedError, Sendable {
6 case graphQLErrors([GraphQLError]) 6 case graphQLErrors([GraphQLError])
7 /// The HTTP response had a non-2xx status code. 7 /// The HTTP response had a non-2xx status code.
8 case httpError(Int) 8 case httpError(Int)
9 /// The client refused to send credentials to an unexpected URL.
10 case invalidAuthenticatedURL(URL)
9 /// The response data could not be decoded. 11 /// The response data could not be decoded.
10 case decodingError(any Error) 12 case decodingError(any Error)
11 /// A networking error from URLSession (timeout, DNS, connectivity, etc.). 13 /// A networking error from URLSession (timeout, DNS, connectivity, etc.).
@@ -20,6 +22,8 @@ enum SRHTError: LocalizedError, Sendable {
20 return "GraphQL error: \(messages)" 22 return "GraphQL error: \(messages)"
21 case .httpError(let code): 23 case .httpError(let code):
22 return "Server returned HTTP \(code)." 24 return "Server returned HTTP \(code)."
25 case .invalidAuthenticatedURL(let url):
26 return "Refused to authenticate request to unexpected URL: \(url.absoluteString)"
23 case .decodingError(let error): 27 case .decodingError(let error):
24 return "Failed to decode response: \(error.localizedDescription)" 28 return "Failed to decode response: \(error.localizedDescription)"
25 case .networkError(let error): 29 case .networkError(let error):
HutchTests.xctestplan added +30
@@ -0,0 +1,30 @@
1{
2 "configurations" : [
3 {
4 "id" : "06A589A5-80C7-41FE-911F-03518252CBDD",
5 "name" : "Test Scheme Action",
6 "options" : {
7
8 }
9 }
10 ],
11 "defaultOptions" : {
12 "performanceAntipatternCheckerEnabled" : true,
13 "targetForVariableExpansion" : {
14 "containerPath" : "container:Hutch.xcodeproj",
15 "identifier" : "8B4B28D02F6704280045FA19",
16 "name" : "Hutch"
17 }
18 },
19 "testTargets" : [
20 {
21 "parallelizable" : true,
22 "target" : {
23 "containerPath" : "container:Hutch.xcodeproj",
24 "identifier" : "8B8182B62F6B73F3000AE049",
25 "name" : "HutchTests"
26 }
27 }
28 ],
29 "version" : 1
30}
HutchTests/HutchTests.swift added +16
@@ -0,0 +1,16 @@
1//
2// HutchTests.swift
3// HutchTests
4//
5// Created by cmc on 2026-03-18.
6//
7
8import Testing
9
10struct HutchTests {
11
12 @Test func example() async throws {
13 // Write your test here and use APIs like `#expect(...)` to check expected conditions.
14 }
15
16}
HutchTests/SRHTClientTests.swift added +27
@@ -0,0 +1,27 @@
1import Foundation
2import Testing
3@testable import Hutch
4
5struct SRHTClientTests {
6
7 @Test
8 @MainActor
9 func fetchTextRejectsUnexpectedAuthenticatedURL() async throws {
10 let client = SRHTClient(token: "test-token")
11 let url = try #require(URL(string: "https://example.com/build-log"))
12
13 do {
14 _ = try await client.fetchText(url: url)
15 Issue.record("Expected fetchText(url:) to reject non-sr.ht URLs.")
16 } catch let error as SRHTError {
17 guard case .invalidAuthenticatedURL(let rejectedURL) = error else {
18 Issue.record("Expected invalidAuthenticatedURL error, got \(error).")
19 return
20 }
21
22 #expect(rejectedURL == url)
23 } catch {
24 Issue.record("Expected SRHTError.invalidAuthenticatedURL, got \(error).")
25 }
26 }
27}