refactor: make DeobfError private

This commit is contained in:
ThetaDev 2023-05-08 01:51:27 +02:00
parent d7caba81d0
commit 6ab7b2415a
3 changed files with 56 additions and 44 deletions

View file

@ -10,7 +10,7 @@ use url::Url;
use crate::{ use crate::{
deobfuscate::Deobfuscator, deobfuscate::Deobfuscator,
error::{DeobfError, Error, ExtractionError}, error::{internal::DeobfError, Error, ExtractionError},
model::{ model::{
traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, ChannelId, Subtitle, traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, ChannelId, Subtitle,
VideoCodec, VideoFormat, VideoPlayer, VideoPlayerDetails, VideoStream, VideoCodec, VideoFormat, VideoPlayer, VideoPlayerDetails, VideoStream,
@ -143,8 +143,7 @@ impl MapResponse<VideoPlayer> for response::Player {
_lang: Language, _lang: Language,
deobf: Option<&crate::deobfuscate::DeobfData>, deobf: Option<&crate::deobfuscate::DeobfData>,
) -> Result<super::MapResult<VideoPlayer>, ExtractionError> { ) -> Result<super::MapResult<VideoPlayer>, ExtractionError> {
let deobf = Deobfuscator::new(deobf.unwrap()) let deobf = Deobfuscator::new(deobf.unwrap())?;
.map_err(|e| ExtractionError::InvalidData(e.to_string().into()))?;
let mut warnings = vec![]; let mut warnings = vec![];
// Check playability status // Check playability status

View file

@ -4,9 +4,10 @@ use regex::Regex;
use reqwest::Client; use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{error::DeobfError, util}; use crate::{
error::{internal::DeobfError, Error},
type Result<T> = core::result::Result<T, DeobfError>; util,
};
pub struct Deobfuscator { pub struct Deobfuscator {
ctx: quick_js::Context, ctx: quick_js::Context,
@ -21,7 +22,7 @@ pub struct DeobfData {
} }
impl DeobfData { impl DeobfData {
pub async fn download(http: Client) -> Result<Self> { pub async fn download(http: Client) -> Result<Self, Error> {
let js_url = get_player_js_url(&http).await?; let js_url = get_player_js_url(&http).await?;
let player_js = get_response(&http, &js_url).await?; let player_js = get_response(&http, &js_url).await?;
@ -41,7 +42,7 @@ impl DeobfData {
} }
impl Deobfuscator { impl Deobfuscator {
pub fn new(data: &DeobfData) -> Result<Self> { pub fn new(data: &DeobfData) -> Result<Self, DeobfError> {
let ctx = let ctx =
quick_js::Context::new().or(Err(DeobfError::Other("could not create QuickJS rt")))?; quick_js::Context::new().or(Err(DeobfError::Other("could not create QuickJS rt")))?;
ctx.eval(&data.sig_fn)?; ctx.eval(&data.sig_fn)?;
@ -50,7 +51,7 @@ impl Deobfuscator {
Ok(Self { ctx }) Ok(Self { ctx })
} }
pub fn deobfuscate_sig(&self, sig: &str) -> Result<String> { pub fn deobfuscate_sig(&self, sig: &str) -> Result<String, DeobfError> {
let res = self.ctx.call_function(DEOBF_SIG_FUNC_NAME, vec![sig])?; let res = self.ctx.call_function(DEOBF_SIG_FUNC_NAME, vec![sig])?;
res.as_str().map_or( res.as_str().map_or(
@ -62,7 +63,7 @@ impl Deobfuscator {
) )
} }
pub fn deobfuscate_nsig(&self, nsig: &str) -> Result<String> { pub fn deobfuscate_nsig(&self, nsig: &str) -> Result<String, DeobfError> {
let res = self.ctx.call_function(DEOBF_NSIG_FUNC_NAME, vec![nsig])?; let res = self.ctx.call_function(DEOBF_NSIG_FUNC_NAME, vec![nsig])?;
res.as_str().map_or( res.as_str().map_or(
@ -78,7 +79,7 @@ impl Deobfuscator {
const DEOBF_SIG_FUNC_NAME: &str = "deobf_sig"; const DEOBF_SIG_FUNC_NAME: &str = "deobf_sig";
const DEOBF_NSIG_FUNC_NAME: &str = "deobf_nsig"; const DEOBF_NSIG_FUNC_NAME: &str = "deobf_nsig";
fn get_sig_fn_name(player_js: &str) -> Result<String> { fn get_sig_fn_name(player_js: &str) -> Result<String, DeobfError> {
static FUNCTION_REGEXES: Lazy<[FancyRegex; 6]> = Lazy::new(|| { static FUNCTION_REGEXES: Lazy<[FancyRegex; 6]> = Lazy::new(|| {
[ [
FancyRegex::new("(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)").unwrap(), FancyRegex::new("(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)").unwrap(),
@ -98,7 +99,7 @@ fn caller_function(mapped_name: &str, fn_name: &str) -> String {
format!("var {mapped_name}={fn_name};") format!("var {mapped_name}={fn_name};")
} }
fn get_sig_fn(player_js: &str) -> Result<String> { fn get_sig_fn(player_js: &str) -> Result<String, DeobfError> {
let dfunc_name = get_sig_fn_name(player_js)?; let dfunc_name = get_sig_fn_name(player_js)?;
let function_pattern_str = let function_pattern_str =
@ -141,7 +142,7 @@ fn get_sig_fn(player_js: &str) -> Result<String> {
+ &caller_function(DEOBF_SIG_FUNC_NAME, &dfunc_name)) + &caller_function(DEOBF_SIG_FUNC_NAME, &dfunc_name))
} }
fn get_nsig_fn_name(player_js: &str) -> Result<String> { fn get_nsig_fn_name(player_js: &str) -> Result<String, DeobfError> {
static FUNCTION_NAME_REGEX: Lazy<Regex> = Lazy::new(|| { static FUNCTION_NAME_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new("\\.get\\(\"n\"\\)\\)&&\\([a-zA-Z0-9$_]=([a-zA-Z0-9$_]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9$_]\\)") Regex::new("\\.get\\(\"n\"\\)\\)&&\\([a-zA-Z0-9$_]=([a-zA-Z0-9$_]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9$_]\\)")
.unwrap() .unwrap()
@ -183,7 +184,7 @@ fn get_nsig_fn_name(player_js: &str) -> Result<String> {
Ok(name.to_owned()) Ok(name.to_owned())
} }
fn extract_js_fn(js: &str, name: &str) -> Result<String> { fn extract_js_fn(js: &str, name: &str) -> Result<String, DeobfError> {
let scan = ress::Scanner::new(js); let scan = ress::Scanner::new(js);
let mut state = 0; let mut state = 0;
let mut level = 0; let mut level = 0;
@ -235,7 +236,7 @@ fn extract_js_fn(js: &str, name: &str) -> Result<String> {
Ok(js[start..end].to_owned()) Ok(js[start..end].to_owned())
} }
fn get_nsig_fn(player_js: &str) -> Result<String> { fn get_nsig_fn(player_js: &str) -> Result<String, DeobfError> {
let function_name = get_nsig_fn_name(player_js)?; let function_name = get_nsig_fn_name(player_js)?;
let function_base = function_name.to_owned() + "=function"; let function_base = function_name.to_owned() + "=function";
let offset = player_js.find(&function_base).unwrap_or_default(); let offset = player_js.find(&function_base).unwrap_or_default();
@ -244,7 +245,7 @@ fn get_nsig_fn(player_js: &str) -> Result<String> {
.map(|s| s + ";" + &caller_function(DEOBF_NSIG_FUNC_NAME, &function_name)) .map(|s| s + ";" + &caller_function(DEOBF_NSIG_FUNC_NAME, &function_name))
} }
async fn get_player_js_url(http: &Client) -> Result<String> { async fn get_player_js_url(http: &Client) -> Result<String, Error> {
let resp = http let resp = http
.get("https://www.youtube.com/iframe_api") .get("https://www.youtube.com/iframe_api")
.send() .send()
@ -267,12 +268,12 @@ async fn get_player_js_url(http: &Client) -> Result<String> {
)) ))
} }
async fn get_response(http: &Client, url: &str) -> Result<String> { async fn get_response(http: &Client, url: &str) -> Result<String, Error> {
let resp = http.get(url).send().await?.error_for_status()?; let resp = http.get(url).send().await?.error_for_status()?;
Ok(resp.text().await?) Ok(resp.text().await?)
} }
fn get_sts(player_js: &str) -> Result<String> { fn get_sts(player_js: &str) -> Result<String, DeobfError> {
static STS_PATTERN: Lazy<Regex> = static STS_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new("signatureTimestamp[=:](\\d+)").unwrap()); Lazy::new(|| Regex::new("signatureTimestamp[=:](\\d+)").unwrap());

View file

@ -2,16 +2,13 @@
use std::borrow::Cow; use std::borrow::Cow;
/// Custom error type for the RustyPipe library /// Error type for the RustyPipe library
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// Error extracting content from YouTube /// Error extracting content from YouTube
#[error("extraction error: {0}")] #[error("extraction error: {0}")]
Extraction(#[from] ExtractionError), Extraction(#[from] ExtractionError),
/// Error from the deobfuscater
#[error("deobfuscator error: {0}")]
Deobfuscation(#[from] DeobfError),
/// File IO error /// File IO error
#[error(transparent)] #[error(transparent)]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
@ -26,28 +23,6 @@ pub enum Error {
Other(Cow<'static, str>), Other(Cow<'static, str>),
} }
/// Error that occurred during the initialization
/// or use of the YouTube URL signature deobfuscator.
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum DeobfError {
/// Error from the HTTP client
#[error("http error: {0}")]
Http(#[from] reqwest::Error),
/// Error during JavaScript execution
#[error("js execution error: {0}")]
JavaScript(#[from] quick_js::ExecutionError),
/// Error during JavaScript parsing
#[error("js parsing: {0}")]
JsParser(#[from] ress::error::Error),
/// Could not extract certain data
#[error("could not extract {0}")]
Extraction(&'static str),
/// Unspecified error
#[error("error: {0}")]
Other(&'static str),
}
/// Error extracting content from YouTube /// Error extracting content from YouTube
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
#[non_exhaustive] #[non_exhaustive]
@ -80,6 +55,9 @@ pub enum ExtractionError {
/// Error deserializing YouTube's response JSON /// Error deserializing YouTube's response JSON
#[error("deserialization error: {0}")] #[error("deserialization error: {0}")]
Deserialization(#[from] serde_json::Error), Deserialization(#[from] serde_json::Error),
/// Error deobfuscating YouTube's URL signatures
#[error("deobfuscation error: {0}")]
Deobfuscation(Cow<'static, str>),
/// YouTube returned invalid data /// YouTube returned invalid data
#[error("got invalid data from YT: {0}")] #[error("got invalid data from YT: {0}")]
InvalidData(Cow<'static, str>), InvalidData(Cow<'static, str>),
@ -102,6 +80,40 @@ pub enum ExtractionError {
DeserializationWarnings, DeserializationWarnings,
} }
pub(crate) mod internal {
use super::*;
/// Error that occurred during the initialization
/// or use of the YouTube URL signature deobfuscator.
#[derive(thiserror::Error, Debug)]
pub enum DeobfError {
/// Error during JavaScript execution
#[error("js execution error: {0}")]
JavaScript(#[from] quick_js::ExecutionError),
/// Error during JavaScript parsing
#[error("js parsing: {0}")]
JsParser(#[from] ress::error::Error),
/// Could not extract certain data
#[error("could not extract {0}")]
Extraction(&'static str),
/// Unspecified error
#[error("error: {0}")]
Other(&'static str),
}
impl From<DeobfError> for Error {
fn from(value: DeobfError) -> Self {
Self::Extraction(value.into())
}
}
impl From<DeobfError> for ExtractionError {
fn from(value: DeobfError) -> Self {
Self::Deobfuscation(value.to_string().into())
}
}
}
impl ExtractionError { impl ExtractionError {
pub(crate) fn should_report(&self) -> bool { pub(crate) fn should_report(&self) -> bool {
matches!( matches!(