153 lines
8.9 KiB
Swift
153 lines
8.9 KiB
Swift
import Foundation
|
|
|
|
@main
|
|
struct StreamResolverChecks {
|
|
struct Failure: Error { let message: String }
|
|
static let mediaID = "OERe_a0YS4h-DlsBDOeXwX7HOO6316tw"
|
|
static let page = "https://barelystarted.miruro.tv/watch/\(mediaID)/the-apothecary-diaries-season-3?ep=1"
|
|
// Sanitized shape from Tubular's observed source response and projection fixture.
|
|
static let fixture = #"""
|
|
{
|
|
"episodeNumber": 1,
|
|
"config": {"sun": {"player": "native", "proxy": false,
|
|
"hls": {"segments": {"enabled": false}, "query": {"enabled": false}}}},
|
|
"providerOrder": ["sun", "bun"],
|
|
"tracks": [{"track": "ssub", "providers": [{
|
|
"provider": "sun",
|
|
"servers": [
|
|
{"server": "iframe", "headers": null, "streams": [], "embed": {"url": "https://embed.example/"}},
|
|
{"server": "icarus-2-1", "headers": {"Origin": "https://ultracloud.cc", "Referer": "https://ultracloud.cc/"},
|
|
"streams": [{"url": "https://vault.example/master.m3u8?token=secret", "format": "hls", "embed": null}]}
|
|
],
|
|
"subtitles": [
|
|
{"file": "https://vault.example/english.jpeg", "format": "vtt", "language": "en", "label": "English", "default": true},
|
|
{"file": "https://vault.example/spanish.jpeg", "format": "vtt", "language": "sp", "label": "Spanish (Latin America)", "default": false},
|
|
{"file": "https://vault.example/unsupported.srt", "format": "srt", "language": "en", "label": "English SRT", "default": false}
|
|
]
|
|
}]}]
|
|
}
|
|
"""#
|
|
|
|
static func require(_ condition: @autoclosure () -> Bool, _ message: String) throws {
|
|
if !condition() { throw Failure(message: message) }
|
|
}
|
|
static func reject(_ message: String, _ operation: () throws -> Void) throws {
|
|
do { try operation() }
|
|
catch {
|
|
try require(!error.localizedDescription.contains("secret"), "errors must not expose source secrets")
|
|
return
|
|
}
|
|
throw Failure(message: "Accepted \(message)")
|
|
}
|
|
static func project(_ json: String) throws -> ResolvedSource {
|
|
try MiruroResolver.decode(Data(json.utf8), mediaID: mediaID, episode: 1)
|
|
}
|
|
|
|
static func checks() async throws {
|
|
guard case .miruro(let id, let episode) = try StreamURL.parse(" \n" + page + "\n") else {
|
|
throw Failure(message: "Miruro URL classification")
|
|
}
|
|
try require(id == mediaID && episode == 1, "opaque identity and route episode retained")
|
|
guard case .youtube(let youtube) = try StreamURL.parse("https://youtu.be/jNQXAC9IVRw") else {
|
|
throw Failure(message: "YouTube URL classification")
|
|
}
|
|
try require(youtube.absoluteString == "https://www.youtube.com/watch?v=jNQXAC9IVRw", "existing YouTube normalization")
|
|
let other = "https://vimeo.com/76979871?quality=original"
|
|
guard case .youtube(let generic) = try StreamURL.parse(other) else {
|
|
throw Failure(message: "other HTTP(S) sites route through yt-dlp")
|
|
}
|
|
try require(generic.absoluteString == other, "other sites retain their URL and query")
|
|
for invalid in [
|
|
page.replacingOccurrences(of: "barelystarted.miruro.tv", with: "barelystarted.miruro.tv@evil.example"),
|
|
page.replacingOccurrences(of: "https://", with: "http://"),
|
|
page.replacingOccurrences(of: "https://", with: "https://user:secret@"),
|
|
page.replacingOccurrences(of: "/watch/", with: "/info/"),
|
|
page.replacingOccurrences(of: mediaID, with: "invalid%2Fid"),
|
|
page.replacingOccurrences(of: mediaID, with: "%2e%2e"),
|
|
page.replacingOccurrences(of: "?ep=1", with: ""),
|
|
page.replacingOccurrences(of: "?ep=1", with: "?ep=0"),
|
|
page.replacingOccurrences(of: "?ep=1", with: "?ep=01"),
|
|
page.replacingOccurrences(of: "?ep=1", with: "?ep=-1"),
|
|
page.replacingOccurrences(of: "?ep=1", with: "?ep=2147483648"),
|
|
page.replacingOccurrences(of: "?ep=1", with: "?ep=%31"),
|
|
page + "&ep=2", page + "&provider=bee", page + "#secret",
|
|
page.replacingOccurrences(of: ".tv/", with: ".tv:444/"),
|
|
page.replacingOccurrences(of: "?ep", with: "/?ep"),
|
|
"file:///secret"
|
|
] {
|
|
try reject("unsupported or unsafe URL") { _ = try StreamURL.parse(invalid) }
|
|
}
|
|
let source = try project(fixture)
|
|
try require(source.identity == "miruro:\(mediaID):1", "stable catalog identity")
|
|
try require(source.url == "https://vault.example/master.m3u8?token=secret", "first direct HLS stream retained")
|
|
try require(source.headers == "Origin: https://ultracloud.cc\r\nReferer: https://ultracloud.cc/\r\n", "all server headers retained")
|
|
try require(source.subtitles.count == 2 && source.subtitles[0].isDefault, "only external VTT retained with default")
|
|
try require(source.subtitles[0].url.hasSuffix(".jpeg") && source.subtitles[1].label == "Spanish (Latin America)", "subtitle format and labels independent of file extension")
|
|
try require(source.subtitles.allSatisfy { $0.headers == source.headers }, "verified Sun subtitle headers retained")
|
|
let renewed = try project(fixture.replacingOccurrences(of: "token=secret", with: "token=renewed"))
|
|
try require(renewed.identity == source.identity && renewed.url != source.url, "token rotation does not change stable identity")
|
|
for (old, replacement) in [
|
|
("\"episodeNumber\": 1", "\"episodeNumber\": 49"),
|
|
("\"proxy\": false", "\"proxy\": true"),
|
|
("\"enabled\": false", "\"enabled\": true"),
|
|
("\"player\": \"native\"", "\"player\": \"iframe\""),
|
|
("\"track\": \"ssub\"", "\"track\": \"dub\""),
|
|
("\"provider\": \"sun\"", "\"provider\": \"bee\""),
|
|
("\"format\": \"hls\"", "\"format\": \"mp4\""),
|
|
("\"embed\": null", "\"embed\": {\"url\": \"https://embed.example/secret\"}"),
|
|
("https://vault.example/master.m3u8?token=secret", "file:///secret"),
|
|
("https://vault.example/english.jpeg", "https://user:secret@vault.example/english.jpeg"),
|
|
("https://ultracloud.cc/\"", "https://ultracloud.cc/\\r\\nX-Injected: secret\""),
|
|
("\"Origin\":", "\"origin\": \"secret\", \"Origin\":"),
|
|
("\"default\": false", "\"default\": true")
|
|
] {
|
|
try reject("unsafe/unsupported source projection") {
|
|
_ = try project(fixture.replacingOccurrences(of: old, with: replacement))
|
|
}
|
|
}
|
|
try reject("oversized metadata") {
|
|
_ = try MiruroResolver.decode(Data(repeating: 32, count: MiruroResolver.bodyLimit + 1), mediaID: mediaID, episode: 1)
|
|
}
|
|
let responseURL = URL(string: "https://barelystarted.miruro.tv/api/sources")!
|
|
for (status, type, length) in [(403, "text/html", "12"), (302, "application/json", "12"), (200, "text/html", "12"), (200, "application/json", "2097153")] {
|
|
let response = HTTPURLResponse(url: responseURL, statusCode: status, httpVersion: nil,
|
|
headerFields: ["Content-Type": type, "Content-Length": length])!
|
|
try reject("protection/redirect/oversized response") { try MiruroResolver.validateResponse(response) }
|
|
}
|
|
let cancelled = Task {
|
|
withUnsafeCurrentTask { $0?.cancel() }
|
|
return try await MiruroResolver.resolve(mediaID: mediaID, episode: 1)
|
|
}
|
|
do {
|
|
_ = try await cancelled.value
|
|
throw Failure(message: "Cancelled resolution was accepted")
|
|
} catch is CancellationError {}
|
|
print("PASS: streaming URL security, Sun projection, shared validation, bounded responses and cancellation")
|
|
}
|
|
|
|
static func main() async {
|
|
do {
|
|
let args = Array(CommandLine.arguments.dropFirst())
|
|
if args.count == 2, args[0] == "--live" {
|
|
guard case .miruro(let id, let episode) = try StreamURL.parse(args[1]) else {
|
|
throw Failure(message: "--live requires a Miruro episode URL")
|
|
}
|
|
let source = try await MiruroResolver.resolve(mediaID: id, episode: episode)
|
|
print("PASS: live \(source.identity); \(source.subtitles.count) VTT tracks")
|
|
} else if args.count == 2, args[0] == "--fixture" {
|
|
let data = try Data(contentsOf: URL(fileURLWithPath: args[1]))
|
|
let source = try MiruroResolver.decode(data, mediaID: mediaID, episode: 1)
|
|
print("PASS: fixture \(source.identity); \(source.subtitles.count) VTT tracks")
|
|
} else if args.isEmpty {
|
|
try await checks()
|
|
} else {
|
|
throw Failure(message: "Usage: stream-resolver-check [--live EPISODE_URL | --fixture SOURCES_JSON]")
|
|
}
|
|
} catch {
|
|
let message = (error as? Failure)?.message ?? error.localizedDescription
|
|
FileHandle.standardError.write(Data("FAIL: \(message)\n".utf8))
|
|
exit(1)
|
|
}
|
|
}
|
|
}
|