import Foundation import Darwin @main struct ExternalSubtitleChecks { struct Failure: Error, CustomStringConvertible { let description: String } static func require(_ condition: @autoclosure () throws -> Bool, _ message: String) throws { guard try condition() else { throw Failure(description: "FAIL: \(message)") } } static func reject(_ operation: () throws -> Void) throws { do { try operation() } catch is Failure { throw Failure(description: "FAIL: rejection assertion") } catch { return } throw Failure(description: "FAIL: malformed input accepted") } static let fixture = """ WEBVTT example NOTE ignored comment not a cue STYLE ::cue { color: white; } REGION id:bottom width:80% cue-one 00:01.000 --> 00:04.000 line:90% position:50%,center align:center size:80% region:bottom Hello & goodbye second line <3 ! cue-two 00:02.000 --> 00:03.000 vertical:rl Overlap <00:02.500>text 00:04.000 --> 00:05.000 Next """ static func main() async throws { let document = try WebVTTDocument.parse("\u{FEFF}" + fixture.replacingOccurrences(of: "\n", with: "\r\n")) let first = "Hello & goodbye\nsecond line <3 !" try require(document.cues.count == 3, "BOM, CRLF, identifiers and metadata blocks") try require(document.text(at: 0.999).isEmpty, "before first cue") try require(document.text(at: 1) == first, "inclusive start and plain-text markup/entities") try require(document.text(at: 2.5) == first + "\nOverlap text", "simultaneous overlapping cues") try require(document.text(at: 3) == first, "exclusive overlap end") try require(document.text(at: 4) == "Next", "adjacent exclusive end/inclusive start") try require(document.text(at: 10).isEmpty, "after final cue") try require(document.text(at: 2) == first + "\nOverlap text", "backward seek after final cue") try require(document.text(at: .nan).isEmpty && document.text(at: -1).isEmpty, "invalid acknowledged positions") let nested = try WebVTTDocument.parse("WEBVTT\n\n00:00.000 --> 01:00.000\nlong\n\n00:02.000 --> 00:03.000\nshort\n") try require(nested.text(at: 4) == "long", "earlier long cue survives later cue ending") try require(try WebVTTDocument.parse("WEBVTT\n\n01:02:03.004 --> 01:02:04.000\nhour\n").text(at: 3723.004) == "hour", "hour timestamps") let zeroDuration = try WebVTTDocument.parse("WEBVTT\n\n00:01.000 --> 00:01.000\nnever visible\n\n00:02.000 --> 00:03.000\nvalid later cue\n") try require(zeroDuration.text(at: 0.999).isEmpty && zeroDuration.text(at: 1).isEmpty && zeroDuration.text(at: 1.001).isEmpty, "zero-duration cue is never active") try require(zeroDuration.text(at: 2) == "valid later cue", "zero-duration cue does not invalidate later subtitles") try require(zeroDuration.text(at: 4).isEmpty && zeroDuration.text(at: 1).isEmpty, "zero-duration cue stays inactive after backward seek") for malformed in [ "not VTT", "WEBVTT\n00:01.000 --> 00:02.000\nmissing separator", "WEBVTT\n\n00:61.000 --> 00:62.000\nbad time", "WEBVTT\n\n00:02.000 --> 00:01.000\nreversed interval", "WEBVTT\n\n00:01.000 --> 00:02.000 size:101%\nbad setting", "WEBVTT\n\n00:01.000 --> 00:02.000\n 00:02.000\ncue\n\nSTYLE\nlate style" ] { try reject { _ = try WebVTTDocument.parse(malformed) } } try reject { _ = try WebVTTDocument.parse(String(repeating: "x", count: WebVTTDocument.byteLimit + 1)) } let headers = "Authorization: Bearer fixture-secret\r\nReferer: https://provider.example/\r\nX-Track: english\r\n" let request = try WebVTTDocument.request(url: "https://provider.example/track.jpeg", headers: headers) try require(request.value(forHTTPHeaderField: "Authorization") == "Bearer fixture-secret", "resource authorization header") try require(request.value(forHTTPHeaderField: "X-Track") == "english", "resource-specific custom header") let whitespace = try WebVTTDocument.request(url: "https://provider.example/track", headers: "X-Exact: padded \t \r\n") try require(whitespace.value(forHTTPHeaderField: "X-Exact") == " padded \t ", "preserve header value whitespace after generated separator") for malformed in ["X: a\nInjected: value\r\n", "X: a\rInjected: value\r\n", "X: a\r\nX: b\r\n", "X: a", ": a\r\n"] { try reject { _ = try WebVTTDocument.request(url: "https://provider.example/track", headers: malformed) } } for url in ["file:///tmp/track", "https://user:secret@provider.example/track", "https://provider.example/track#fragment"] { try reject { _ = try WebVTTDocument.request(url: url, headers: "") } } let source = URL(string: "https://provider.example/track.jpeg")! try require(WebVTTDocument.permitsRedirect(from: source, to: URL(string: "https://PROVIDER.example:443/other")!), "same origin includes default port") for target in ["https://cdn.example/track", "http://provider.example/track", "https://provider.example:8443/track", "https://user:secret@provider.example/track"] { try require(!WebVTTDocument.permitsRedirect(from: source, to: URL(string: target)!), "cross-origin or credentialed redirect rejection") } let server = try SubtitleFixtureServer(body: fixture) defer { server.stop() } let loaded = try await WebVTTDocument.load(url: server.url("/same.jpeg"), headers: headers) try require(loaded.cues == document.cues, "load JPEG-named, image/jpeg VTT via same-origin redirect") let requests = server.requests try require(requests.count == 2 && requests.allSatisfy { $0.contains("Bearer fixture-secret") && $0.contains("english") }, "resource headers preserved only on same origin") do { _ = try await WebVTTDocument.load(url: server.url("/cross.jpeg"), headers: headers) throw Failure(description: "FAIL: cross-origin redirect accepted") } catch is Failure { throw Failure(description: "FAIL: cross-origin redirect accepted") } catch { let message = error.localizedDescription try require(!message.contains("fixture-secret") && !message.contains("127.0.0.1"), "sanitized visible transport error") } try require(server.requests.count == 3, "cross-origin redirect never sends credential-bearing request") do { _ = try await WebVTTDocument.load(url: server.url("/oversized.jpeg"), headers: "") throw Failure(description: "FAIL: oversized response accepted") } catch is Failure { throw Failure(description: "FAIL: oversized response accepted") } catch { } print("External subtitle checks passed") } } /// A loopback-only HTTP fixture; no external services, dates, credentials or frameworks. private final class SubtitleFixtureServer: @unchecked Sendable { private let descriptor: Int32 private let port: UInt16 private let body: String private let lock = NSLock() private var recorded: [String] = [] private let ended = DispatchSemaphore(value: 0) var requests: [String] { lock.lock() defer { lock.unlock() } return recorded } init(body: String) throws { self.body = body let descriptor = socket(AF_INET, SOCK_STREAM, 0) self.descriptor = descriptor guard descriptor >= 0 else { throw ExternalSubtitleChecks.Failure(description: "FAIL: fixture socket") } var address = sockaddr_in() address.sin_len = UInt8(MemoryLayout.size) address.sin_family = sa_family_t(AF_INET) address.sin_addr.s_addr = inet_addr("127.0.0.1") let bound = withUnsafePointer(to: &address) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { bind(descriptor, $0, socklen_t(MemoryLayout.size)) } } var size = socklen_t(MemoryLayout.size) let named = withUnsafeMutablePointer(to: &address) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { getsockname(descriptor, $0, &size) } } guard bound == 0, named == 0, listen(descriptor, 4) == 0 else { close(descriptor) throw ExternalSubtitleChecks.Failure(description: "FAIL: fixture listener") } port = UInt16(bigEndian: address.sin_port) DispatchQueue.global().async { self.serve() } } func url(_ path: String) -> String { "http://127.0.0.1:\(port)\(path)" } func stop() { shutdown(descriptor, SHUT_RDWR) close(descriptor) ended.wait() } private func serve() { defer { ended.signal() } while true { let client = accept(descriptor, nil, nil) guard client >= 0 else { return } var noSignal: Int32 = 1 _ = setsockopt(client, SOL_SOCKET, SO_NOSIGPIPE, &noSignal, socklen_t(MemoryLayout.size)) var buffer = [UInt8](repeating: 0, count: 4096) var data = Data() while data.count < 65536 { let count = recv(client, &buffer, buffer.count, 0) if count <= 0 { break } data.append(contentsOf: buffer.prefix(count)) if String(decoding: data, as: UTF8.self).contains("\r\n\r\n") { break } } let request = String(decoding: data, as: UTF8.self) lock.lock() recorded.append(request) lock.unlock() let path = request.split(separator: " ").dropFirst().first ?? "" let response: String switch path { case "/same.jpeg": response = "HTTP/1.1 302 Found\r\nLocation: /track.jpeg\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" case "/cross.jpeg": response = "HTTP/1.1 302 Found\r\nLocation: http://localhost:\(port)/track.jpeg\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" case "/oversized.jpeg": response = "HTTP/1.1 200 OK\r\nContent-Length: \(WebVTTDocument.byteLimit + 1)\r\nConnection: close\r\n\r\n" default: response = "HTTP/1.1 200 OK\r\nContent-Type: image/jpeg\r\nContent-Length: \(body.utf8.count)\r\nConnection: close\r\n\r\n\(body)" } let bytes = Array(response.utf8) bytes.withUnsafeBytes { raw in var offset = 0 while offset < raw.count { let count = send(client, raw.baseAddress!.advanced(by: offset), raw.count - offset, 0) if count <= 0 { break } offset += count } } close(client) } } }