krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v4.9.0: DomainDig/DomainAvailabilityService.swift · raw
1import Foundation
2
3struct DomainAvailabilityService {
4 private static let suggestionTLDs = ["net", "io", "dev", "app", "co", "org"]
5
6 static func check(domain: String) async -> DomainAvailabilityResult {
7 let normalizedDomain = normalize(domain)
8 guard !normalizedDomain.isEmpty else {
9 return DomainAvailabilityResult(domain: domain, status: .unknown)
10 }
11
12 if await checkViaRDAP(domain: normalizedDomain) == .registered {
13 return DomainAvailabilityResult(domain: normalizedDomain, status: .registered)
14 }
15
16 let fallbackStatus = await checkViaDNSFallback(domain: normalizedDomain)
17 if fallbackStatus == .registered {
18 return DomainAvailabilityResult(domain: normalizedDomain, status: .unknown)
19 }
20
21 return DomainAvailabilityResult(domain: normalizedDomain, status: fallbackStatus)
22 }
23
24 static func suggestions(for domain: String, limit: Int = 6) async -> [DomainSuggestionResult] {
25 let normalizedDomain = normalize(domain)
26 let candidates = suggestionCandidates(for: normalizedDomain, limit: limit)
27 guard !candidates.isEmpty else { return [] }
28
29 var results: [DomainSuggestionResult] = []
30 for candidate in candidates {
31 if Task.isCancelled { break }
32 let result = await check(domain: candidate)
33 results.append(DomainSuggestionResult(domain: result.domain, status: result.status))
34 }
35 return results
36 }
37
38 private static func checkViaRDAP(domain: String) async -> DomainAvailabilityStatus? {
39 let status = await RDAPService.registrationStatus(for: domain)
40 return status
41 }
42
43 private static func checkViaDNSFallback(domain: String) async -> DomainAvailabilityStatus {
44 do {
45 let aRecords = try await DNSLookupService.lookup(domain: domain, recordType: .A)
46 if !aRecords.isEmpty {
47 return .registered
48 }
49 } catch {
50 // No A record (or the lookup failed); fall through to the NS probe.
51 }
52
53 do {
54 let nsRecords = try await DNSLookupService.lookup(domain: domain, recordType: .NS)
55 if !nsRecords.isEmpty {
56 return .registered
57 }
58 return .unknown
59 } catch {
60 return .unknown
61 }
62 }
63
64 private static func suggestionCandidates(for domain: String, limit: Int) -> [String] {
65 let parts = domain.split(separator: ".")
66 guard parts.count >= 2 else { return [] }
67
68 let base = parts.dropLast().joined(separator: ".")
69 let tld = String(parts.last ?? "")
70
71 var candidates: [String] = []
72 for suggestionTLD in suggestionTLDs where suggestionTLD != tld {
73 candidates.append("\(base).\(suggestionTLD)")
74 if candidates.count == limit {
75 return candidates
76 }
77 }
78
79 if !base.contains("-"), base.count >= 6, candidates.count < limit {
80 let midpoint = base.index(base.startIndex, offsetBy: base.count / 2)
81 let hyphenated = "\(base[..<midpoint])-\(base[midpoint...]).\(tld)"
82 if hyphenated != domain {
83 candidates.append(hyphenated)
84 }
85 }
86
87 return Array(candidates.prefix(limit))
88 }
89
90 private static func normalize(_ domain: String) -> String {
91 domain
92 .trimmingCharacters(in: .whitespacesAndNewlines)
93 .lowercased()
94 }
95}