URLs → git.sulkta.com. Audit-ticket prefixes (SPEC §N, audit Track X, vc=N audit-fix, FIX (audit ...), PORT DEVIATION) stripped from comments — technical reasoning retained. Build-host references softened. README sheds marketing scaffolding + stale status tables.
101 lines
3.8 KiB
Rust
101 lines
3.8 KiB
Rust
// Strawcore error type — anything that can fail in our Rust core surfaces
|
|
// as one of these variants. UniFFI maps it to a Kotlin sealed class
|
|
// `StrawcoreException` so Kotlin can pattern-match on the kind.
|
|
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Error, uniffi::Error)]
|
|
pub enum StrawcoreError {
|
|
#[error("network: {msg}")]
|
|
Network { msg: String },
|
|
|
|
#[error("extractor: {msg}")]
|
|
Extractor { msg: String },
|
|
|
|
#[error("not found: {what}")]
|
|
NotFound { what: String },
|
|
|
|
#[error("unsupported: {detail}")]
|
|
Unsupported { detail: String },
|
|
|
|
#[error("age restricted")]
|
|
AgeRestricted,
|
|
|
|
#[error("geo restricted")]
|
|
GeoRestricted,
|
|
|
|
#[error("private content")]
|
|
Private,
|
|
|
|
#[error("requires login: {detail}")]
|
|
RequiresLogin { detail: String },
|
|
}
|
|
|
|
/// Drop the `continue=<signed-url>` param from a google.com/sorry/...
|
|
/// URL while leaving every other param intact. Used only for surfacing
|
|
/// recaptcha challenge URLs to the UI; keeps the URL tappable for the
|
|
/// user to solve the challenge while scrubbing the embedded
|
|
/// googlevideo signature.
|
|
fn strip_continue_param(url: &str) -> String {
|
|
let (base, query) = match url.split_once('?') {
|
|
Some(pair) => pair,
|
|
None => return url.to_owned(),
|
|
};
|
|
let filtered: Vec<&str> = query
|
|
.split('&')
|
|
.filter(|kv| {
|
|
let key = kv.split_once('=').map(|(k, _)| k).unwrap_or(*kv);
|
|
!key.eq_ignore_ascii_case("continue")
|
|
})
|
|
.collect();
|
|
if filtered.is_empty() {
|
|
base.to_owned()
|
|
} else {
|
|
format!("{}?{}", base, filtered.join("&"))
|
|
}
|
|
}
|
|
|
|
impl From<strawcore_core::exceptions::ExtractionError> for StrawcoreError {
|
|
fn from(e: strawcore_core::exceptions::ExtractionError) -> Self {
|
|
use strawcore_core::exceptions::{ContentUnavailable, ExtractionError, NetworkError};
|
|
match e {
|
|
ExtractionError::Network(NetworkError::Recaptcha { url }) => {
|
|
// Strip the `continue=` query param before propagating.
|
|
// google.com/sorry/index carries the full signed
|
|
// googlevideo URL in `continue=` so the user can be
|
|
// sent back to the stream after solving — but
|
|
// surfacing that to the UI is a credential leak via
|
|
// screenshot, and Kotlin's LogDump scrubber only
|
|
// catches googlevideo.com hosts. The challenge URL
|
|
// itself still solves without `continue=`, so the
|
|
// user can tap to unblock without leaking the
|
|
// signature/expire/pot token.
|
|
StrawcoreError::RequiresLogin {
|
|
detail: format!("reCAPTCHA challenge: {}", strip_continue_param(&url)),
|
|
}
|
|
}
|
|
ExtractionError::Network(NetworkError::Transport(msg)) => {
|
|
StrawcoreError::Network { msg }
|
|
}
|
|
ExtractionError::Parsing(p) => StrawcoreError::Extractor {
|
|
msg: p.to_string(),
|
|
},
|
|
ExtractionError::ContentUnavailable(ContentUnavailable::AgeRestricted) => {
|
|
StrawcoreError::AgeRestricted
|
|
}
|
|
ExtractionError::ContentUnavailable(ContentUnavailable::GeoRestricted) => {
|
|
StrawcoreError::GeoRestricted
|
|
}
|
|
ExtractionError::ContentUnavailable(ContentUnavailable::Private) => {
|
|
StrawcoreError::Private
|
|
}
|
|
ExtractionError::ContentUnavailable(other) => StrawcoreError::Extractor {
|
|
msg: other.to_string(),
|
|
},
|
|
ExtractionError::DownloaderMissing => StrawcoreError::Extractor {
|
|
msg: "downloader not initialized".into(),
|
|
},
|
|
ExtractionError::Other(msg) => StrawcoreError::Extractor { msg },
|
|
}
|
|
}
|
|
}
|