feat: open streaming URLs without changing resolved handoffs

This commit is contained in:
2026-10-19 19:32:26 +02:00
parent d068c52e04
commit 9f1dca3081
17 changed files with 553 additions and 103 deletions
+38 -21
View File
@@ -12,6 +12,15 @@ struct ResolvedSubtitle: Decodable, Sendable {
headerFields.sorted { $0.key < $1.key }.map { "\($0.key): \($0.value)\r\n" }.joined()
}
fileprivate var validatedHeaders: [String: String] { headerFields }
init(url: String, format: String, language: String, label: String, isDefault: Bool, headers: [String: String]) {
self.url = url
self.format = format
self.language = language
self.label = label
self.isDefault = isDefault
self.headerFields = headers
}
private enum CodingKeys: String, CodingKey {
case url, format, language, label
case isDefault = "default"
@@ -26,7 +35,7 @@ struct ResolvedSource: Sendable {
let title: String
let subtitles: [ResolvedSubtitle]
private struct Document: Decodable {
struct Document: Decodable {
struct Identity: Decodable {
let provider: String
let media_id: String
@@ -62,29 +71,37 @@ struct ResolvedSource: Sendable {
let data = try handle.read(upToCount: 1_048_577) ?? Data()
guard data.count <= 1_048_576 else { throw InvalidSource() }
let document = try JSONDecoder().decode(Document.self, from: data)
let id = document.identity
guard document.version == 1, id.provider == "miruro",
!id.media_id.isEmpty, id.media_id.utf8.count <= 512,
id.media_id.utf8.allSatisfy({ isIDByte($0) }),
id.episode > 0, id.episode <= Int(Int32.max),
validURL(document.url), validHeaders(document.headers),
validText(document.title ?? "", limit: 4096),
document.subtitles.count <= 32,
document.subtitles.filter({ $0.isDefault }).count <= 1 else { throw InvalidSource() }
for subtitle in document.subtitles {
guard validURL(subtitle.url), subtitle.format == "vtt",
validText(subtitle.language, limit: 128), !subtitle.language.isEmpty,
validText(subtitle.label, limit: 4096), !subtitle.label.isEmpty,
validHeaders(subtitle.validatedHeaders) else { throw InvalidSource() }
}
let headers = document.headers.sorted { $0.key < $1.key }
.map { "\($0.key): \($0.value)\r\n" }.joined()
return ResolvedSource(url: document.url, headers: headers,
identity: "miruro:\(id.media_id):\(id.episode)", title: document.title ?? "Miruro episode \(id.episode)",
subtitles: document.subtitles)
return try validated(document)
} catch { throw InvalidSource() }
}
static func validIdentity(mediaID: String, episode: Int) -> Bool {
!mediaID.isEmpty && mediaID.utf8.count <= 512 &&
mediaID.utf8.allSatisfy({ isIDByte($0) }) &&
episode > 0 && episode <= Int(Int32.max)
}
static func validated(_ document: Document) throws -> ResolvedSource {
let id = document.identity
guard document.version == 1, id.provider == "miruro",
validIdentity(mediaID: id.media_id, episode: id.episode),
validURL(document.url), validHeaders(document.headers),
validText(document.title ?? "", limit: 4096),
document.subtitles.count <= 32,
document.subtitles.filter({ $0.isDefault }).count <= 1 else { throw InvalidSource() }
for subtitle in document.subtitles {
guard validURL(subtitle.url), subtitle.format == "vtt",
validText(subtitle.language, limit: 128), !subtitle.language.isEmpty,
validText(subtitle.label, limit: 4096), !subtitle.label.isEmpty,
validHeaders(subtitle.validatedHeaders) else { throw InvalidSource() }
}
let headers = document.headers.sorted { $0.key < $1.key }
.map { "\($0.key): \($0.value)\r\n" }.joined()
return ResolvedSource(url: document.url, headers: headers,
identity: "miruro:\(id.media_id):\(id.episode)", title: document.title ?? "Miruro episode \(id.episode)",
subtitles: document.subtitles)
}
private static func isIDByte(_ byte: UInt8) -> Bool {
(65...90).contains(byte) || (97...122).contains(byte) ||
(48...57).contains(byte) || byte == 45 || byte == 95