krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v1.7.1: DomainDig/RedirectChainService.swift · raw
1import Foundation
2
3struct RedirectChainService {
4 static func trace(domain: String) async throws -> [RedirectHop] {
5 // Try HTTPS first (avoids ATS issues), fall back to HTTP if it fails entirely
6 do {
7 return try await followChain(startingURL: URL(string: "https://\(domain)")!)
8 } catch {
9 return try await followChain(startingURL: URL(string: "http://\(domain)")!)
10 }
11 }
12
13 private static func followChain(startingURL: URL) async throws -> [RedirectHop] {
14 let delegate = NoRedirectDelegate()
15 let session = URLSession(
16 configuration: .ephemeral,
17 delegate: delegate,
18 delegateQueue: nil
19 )
20 defer { session.invalidateAndCancel() }
21
22 var hops: [RedirectHop] = []
23 var currentURL = startingURL
24 let maxRedirects = 10
25
26 for step in 1...maxRedirects + 1 {
27 var request = URLRequest(url: currentURL, timeoutInterval: 10)
28 request.httpMethod = "GET"
29
30 let (_, response) = try await session.data(for: request)
31
32 guard let httpResponse = response as? HTTPURLResponse else {
33 throw URLError(.badServerResponse)
34 }
35
36 let statusCode = httpResponse.statusCode
37 let isRedirect = (300...399).contains(statusCode)
38
39 if isRedirect, let location = httpResponse.value(forHTTPHeaderField: "Location") {
40 hops.append(RedirectHop(
41 stepNumber: step,
42 statusCode: statusCode,
43 url: currentURL.absoluteString,
44 isFinal: false
45 ))
46
47 // Resolve relative redirects
48 if let nextURL = URL(string: location, relativeTo: currentURL)?.absoluteURL {
49 currentURL = nextURL
50 } else {
51 break
52 }
53
54 if step > maxRedirects { break }
55 } else {
56 // Non-redirect — this is the final destination
57 hops.append(RedirectHop(
58 stepNumber: step,
59 statusCode: statusCode,
60 url: currentURL.absoluteString,
61 isFinal: true
62 ))
63 break
64 }
65 }
66
67 return hops
68 }
69}
70
71private final class NoRedirectDelegate: NSObject, URLSessionTaskDelegate, @unchecked Sendable {
72 func urlSession(
73 _ _: URLSession,
74 task _: URLSessionTask,
75 willPerformHTTPRedirection _: HTTPURLResponse,
76 newRequest _: URLRequest,
77 completionHandler: @escaping (URLRequest?) -> Void
78 ) {
79 // Don't follow redirects automatically — return nil to stop
80 completionHandler(nil)
81 }
82}