sxyprn fixes-ish
This commit is contained in:
@@ -476,6 +476,15 @@ impl SxyprnProvider {
|
|||||||
let mut formats = vec![];
|
let mut formats = vec![];
|
||||||
|
|
||||||
// Add sxyprn format
|
// Add sxyprn format
|
||||||
|
let sxyprn_url = format!(
|
||||||
|
"{}/proxy/sxyprn/post/{}",
|
||||||
|
options.public_url_base.as_deref().unwrap_or(""),
|
||||||
|
id
|
||||||
|
);
|
||||||
|
formats.push(
|
||||||
|
VideoFormat::new(sxyprn_url.clone(), "auto".to_string(), "mp4".to_string())
|
||||||
|
.format_note(sxyprn_url.split("/").nth(4).unwrap_or("sxyprn").to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
let doodstream_urls: Vec<String> = title_links
|
let doodstream_urls: Vec<String> = title_links
|
||||||
.iter()
|
.iter()
|
||||||
@@ -491,32 +500,24 @@ impl SxyprnProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let lulustream_urls: Vec<String> = title_links
|
// let lulustream_urls: Vec<String> = title_links
|
||||||
.iter()
|
// .iter()
|
||||||
.filter(|url| proxy_name_for_url(url).as_deref() == Some("lulustream"))
|
// .filter(|url| proxy_name_for_url(url).as_deref() == Some("lulustream"))
|
||||||
.map(|url| rewrite_hoster_url(options, url))
|
// .map(|url| rewrite_hoster_url(options, url))
|
||||||
.collect();
|
// .collect();
|
||||||
|
|
||||||
|
// for lulustream_url in lulustream_urls {
|
||||||
|
// formats.push(
|
||||||
|
// VideoFormat::m3u8(
|
||||||
|
// lulustream_url.clone(),
|
||||||
|
// "auto".to_string(),
|
||||||
|
// "m3u8".to_string(),
|
||||||
|
// )
|
||||||
|
// .format_note("lulustream".to_string())
|
||||||
|
// .format_id("lulustream".to_string()),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
for lulustream_url in lulustream_urls {
|
|
||||||
formats.push(
|
|
||||||
VideoFormat::m3u8(
|
|
||||||
lulustream_url.clone(),
|
|
||||||
"auto".to_string(),
|
|
||||||
"m3u8".to_string(),
|
|
||||||
)
|
|
||||||
.format_note("lulustream".to_string())
|
|
||||||
.format_id("lulustream".to_string()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let sxyprn_url = format!(
|
|
||||||
"{}/proxy/sxyprn/post/{}",
|
|
||||||
options.public_url_base.as_deref().unwrap_or(""),
|
|
||||||
id
|
|
||||||
);
|
|
||||||
formats.push(
|
|
||||||
VideoFormat::new(sxyprn_url.clone(), "auto".to_string(), "mp4".to_string())
|
|
||||||
.format_note(sxyprn_url.split("/").nth(4).unwrap_or("sxyprn").to_string()),
|
|
||||||
);
|
|
||||||
// Also collect and transform vidara.so URLs to proxy format and add as formats
|
// Also collect and transform vidara.so URLs to proxy format and add as formats
|
||||||
let vidara_urls: Vec<String> = title_links
|
let vidara_urls: Vec<String> = title_links
|
||||||
.iter()
|
.iter()
|
||||||
@@ -534,7 +535,7 @@ impl SxyprnProvider {
|
|||||||
let mut video_item = VideoItem::new(
|
let mut video_item = VideoItem::new(
|
||||||
id.clone(),
|
id.clone(),
|
||||||
title,
|
title,
|
||||||
format!("{}/post/{}", self.url, id.clone()),
|
url.clone(),
|
||||||
"sxyprn".to_string(),
|
"sxyprn".to_string(),
|
||||||
thumb,
|
thumb,
|
||||||
duration,
|
duration,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use ntex::web;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::util::requester::Requester;
|
use crate::util::{dean_edwards, requester::Requester};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LulustreamProxy {}
|
pub struct LulustreamProxy {}
|
||||||
@@ -51,7 +51,7 @@ impl LulustreamProxy {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
(host == "lulustream.com" || host == "www.lulustream.com" || host == "luluvdo.com")
|
(host == "lulustream.com" || host == "www.lulustream.com" || host == "luluvdo.com")
|
||||||
&& (parsed.path().starts_with("/v/")||parsed.path().starts_with("/e/"))
|
&& !parsed.path().is_empty() && parsed.path() != "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_video_url(
|
pub async fn get_video_url(
|
||||||
@@ -64,15 +64,19 @@ impl LulustreamProxy {
|
|||||||
println!("LulustreamProxy: Invalid detail URL: {url}");
|
println!("LulustreamProxy: Invalid detail URL: {url}");
|
||||||
return String::new();
|
return String::new();
|
||||||
};
|
};
|
||||||
let text = requester.get(&detail_url, None).await.unwrap_or_default();
|
let mut text = requester.get(&detail_url, None).await.unwrap_or_default();
|
||||||
let video_url = text.split("sources: [{file:\"")
|
if !text.contains("[{file:\"") {
|
||||||
|
let packedtext = text.split("<script type='text/javascript'>").nth(1).and_then(|t| t.split("</script>").next()).unwrap_or_default();
|
||||||
|
println!("LulustreamProxy: Found packed text: {packedtext}");
|
||||||
|
text = dean_edwards::unpack(&packedtext).unwrap_or_default();
|
||||||
|
println!("LulustreamProxy: Unpacked text: {text}");
|
||||||
|
}
|
||||||
|
let video_url = text.split("[{file:\"")
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.and_then(|s| s.split('"').next())
|
.and_then(|s| s.split('"').next())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string();
|
.to_string();
|
||||||
if video_url.is_empty() {
|
println!("LulustreamProxy: Extracted video URL: {video_url}");
|
||||||
println!("LulustreamProxy: Failed to extract video URL for video ID: {video_id}");
|
|
||||||
}
|
|
||||||
video_url
|
video_url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ impl SxyprnProxy {
|
|||||||
Ok(None) => println!("No redirect found for {}", sxyprn_video_url),
|
Ok(None) => println!("No redirect found for {}", sxyprn_video_url),
|
||||||
Err(e) => eprintln!("Request failed: {}", e),
|
Err(e) => eprintln!("Request failed: {}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
return "".to_string();
|
return "".to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use crate::proxies::spankbang::SpankbangProxy;
|
|||||||
use crate::proxies::sxyprn::SxyprnProxy;
|
use crate::proxies::sxyprn::SxyprnProxy;
|
||||||
use crate::proxies::vjav::VjavProxy;
|
use crate::proxies::vjav::VjavProxy;
|
||||||
use crate::proxies::vidara::VidaraProxy;
|
use crate::proxies::vidara::VidaraProxy;
|
||||||
|
use crate::proxies::lulustream::LulustreamProxy;
|
||||||
use crate::proxies::*;
|
use crate::proxies::*;
|
||||||
use crate::util::requester::Requester;
|
use crate::util::requester::Requester;
|
||||||
|
|
||||||
@@ -27,6 +28,11 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||||||
.route(web::post().to(proxy2redirect))
|
.route(web::post().to(proxy2redirect))
|
||||||
.route(web::get().to(proxy2redirect)),
|
.route(web::get().to(proxy2redirect)),
|
||||||
)
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/lulustream/{endpoint}*")
|
||||||
|
.route(web::post().to(proxy2redirect))
|
||||||
|
.route(web::get().to(proxy2redirect)),
|
||||||
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/sxyprn/{endpoint}*")
|
web::resource("/sxyprn/{endpoint}*")
|
||||||
.route(web::post().to(proxy2redirect))
|
.route(web::post().to(proxy2redirect))
|
||||||
@@ -149,6 +155,7 @@ fn get_proxy(proxy: &str) -> Option<AnyProxy> {
|
|||||||
"pimpbunny" => Some(AnyProxy::Pimpbunny(PimpbunnyProxy::new())),
|
"pimpbunny" => Some(AnyProxy::Pimpbunny(PimpbunnyProxy::new())),
|
||||||
"porndish" => Some(AnyProxy::Porndish(PorndishProxy::new())),
|
"porndish" => Some(AnyProxy::Porndish(PorndishProxy::new())),
|
||||||
"spankbang" => Some(AnyProxy::Spankbang(SpankbangProxy::new())),
|
"spankbang" => Some(AnyProxy::Spankbang(SpankbangProxy::new())),
|
||||||
|
"lulustream" => Some(AnyProxy::Lulustream(LulustreamProxy::new())),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
334
src/util/dean_edwards.rs
Normal file
334
src/util/dean_edwards.rs
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
/// Dean Edwards p,a,c,k,e,d unpacker.
|
||||||
|
///
|
||||||
|
/// Mirrors the original JS decoder:
|
||||||
|
/// while(c--) if(k[c]) p = p.replace(/\b{c.toString(a)}\b/g, k[c]);
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// let source = r#"eval(function(p,a,c,k,e,d){...}('...',36,N,'w0|w1|...'.split('|'),0,{}))"#;
|
||||||
|
/// let plain = unpack(source)?;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
// ── Error type ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum UnpackError {
|
||||||
|
NotPacked,
|
||||||
|
MalformedArgs(String),
|
||||||
|
BadBase(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for UnpackError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NotPacked => write!(f, "input does not look like a packed script"),
|
||||||
|
Self::MalformedArgs(s) => write!(f, "could not parse packed arguments: {s}"),
|
||||||
|
Self::BadBase(b) => write!(f, "unsupported base {b} (supported: 2–62)"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for UnpackError {}
|
||||||
|
|
||||||
|
// ── Public entry point ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Detect, parse, and unpack a Dean Edwards packed script.
|
||||||
|
/// Returns the deobfuscated source on success.
|
||||||
|
pub fn unpack(input: &str) -> Result<String, UnpackError> {
|
||||||
|
let args = extract_args(input.trim())?;
|
||||||
|
decode(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Argument extraction ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
struct PackedArgs {
|
||||||
|
payload: String, // p
|
||||||
|
base: u32, // a
|
||||||
|
words: Vec<String>, // k (already split)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the argument list of the outer `function(p,a,c,k,e,d){...}(...)` call
|
||||||
|
/// and parse p, a, c, k from it.
|
||||||
|
fn extract_args(input: &str) -> Result<PackedArgs, UnpackError> {
|
||||||
|
// Locate the opening of the argument tuple: the '(' that directly follows
|
||||||
|
// the closing '}' of the function body.
|
||||||
|
let body_end = input
|
||||||
|
.find("}('") // most common: }('payload',…
|
||||||
|
.or_else(|| input.find("}(\""))
|
||||||
|
.ok_or(UnpackError::NotPacked)?;
|
||||||
|
|
||||||
|
// args_start points at '(' – skip it to reach the opening quote of the payload.
|
||||||
|
let args_start = body_end + 1;
|
||||||
|
let args_str = input[args_start..].trim_start_matches('(');
|
||||||
|
|
||||||
|
// Pull the payload string (first argument).
|
||||||
|
let (payload, after_payload) = parse_js_string(args_str)
|
||||||
|
.ok_or_else(|| UnpackError::MalformedArgs("could not read payload string".into()))?;
|
||||||
|
|
||||||
|
// Expect ',base,count,'
|
||||||
|
let rest = after_payload
|
||||||
|
.trim_start_matches(',');
|
||||||
|
|
||||||
|
let (base_str, rest) = rest
|
||||||
|
.split_once(',')
|
||||||
|
.ok_or_else(|| UnpackError::MalformedArgs("missing base".into()))?;
|
||||||
|
|
||||||
|
let base: u32 = base_str
|
||||||
|
.trim()
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| UnpackError::MalformedArgs(format!("bad base: {base_str}")))?;
|
||||||
|
|
||||||
|
let (count_str, rest) = rest
|
||||||
|
.split_once(',')
|
||||||
|
.ok_or_else(|| UnpackError::MalformedArgs("missing count".into()))?;
|
||||||
|
|
||||||
|
let count: usize = count_str
|
||||||
|
.trim()
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| UnpackError::MalformedArgs(format!("bad count: {count_str}")))?;
|
||||||
|
|
||||||
|
// Parse the dictionary k. Two common forms:
|
||||||
|
// 'w0|w1|w2'.split('|')
|
||||||
|
// ["w0","w1","w2"]
|
||||||
|
let words = parse_dictionary(rest.trim(), count)
|
||||||
|
.ok_or_else(|| UnpackError::MalformedArgs("could not parse dictionary".into()))?;
|
||||||
|
|
||||||
|
Ok(PackedArgs { payload, base, words })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Dictionary parser ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn parse_dictionary(s: &str, count: usize) -> Option<Vec<String>> {
|
||||||
|
if s.starts_with('\'') || s.starts_with('"') {
|
||||||
|
// 'w0|w1|…'.split('|') – separator can be any single char
|
||||||
|
let (joined, rest) = parse_js_string(s)?;
|
||||||
|
// find .split('<sep>')
|
||||||
|
let sep_start = rest.find(".split(")?;
|
||||||
|
let after_split = &rest[sep_start + 7..]; // skip `.split(`
|
||||||
|
let (sep, _) = parse_js_string(after_split.trim())?;
|
||||||
|
let sep_char = if sep.is_empty() { '|' } else { sep.chars().next().unwrap() };
|
||||||
|
let words: Vec<String> = joined.split(sep_char).map(str::to_owned).collect();
|
||||||
|
Some(pad_to(words, count))
|
||||||
|
} else if s.starts_with('[') {
|
||||||
|
// ["w0","w1",…]
|
||||||
|
let end = s.find(']')?;
|
||||||
|
let inner = &s[1..end];
|
||||||
|
let words = parse_array_literal(inner);
|
||||||
|
Some(pad_to(words, count))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a JS array literal (no nesting needed for the k array).
|
||||||
|
fn parse_array_literal(s: &str) -> Vec<String> {
|
||||||
|
let mut words = Vec::new();
|
||||||
|
let mut rest = s.trim();
|
||||||
|
loop {
|
||||||
|
rest = rest.trim_start_matches(',').trim();
|
||||||
|
if rest.is_empty() { break; }
|
||||||
|
if rest.starts_with('\'') || rest.starts_with('"') {
|
||||||
|
if let Some((w, after)) = parse_js_string(rest) {
|
||||||
|
words.push(w);
|
||||||
|
rest = after.trim();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// empty slot → push empty string
|
||||||
|
if let Some(pos) = rest.find(',') {
|
||||||
|
words.push(String::new());
|
||||||
|
rest = &rest[pos..];
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
words
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad_to(mut v: Vec<String>, n: usize) -> Vec<String> {
|
||||||
|
v.resize(n, String::new());
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── JS string parser ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Parse a single-quoted or double-quoted JS string literal at the start of `s`.
|
||||||
|
/// Returns (unescaped content, remainder after closing quote).
|
||||||
|
fn parse_js_string(s: &str) -> Option<(String, &str)> {
|
||||||
|
let mut chars = s.char_indices();
|
||||||
|
let (_, quote) = chars.next()?;
|
||||||
|
if quote != '\'' && quote != '"' { return None; }
|
||||||
|
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut escaped = false;
|
||||||
|
|
||||||
|
for (i, ch) in chars {
|
||||||
|
if escaped {
|
||||||
|
match ch {
|
||||||
|
'n' => result.push('\n'),
|
||||||
|
'r' => result.push('\r'),
|
||||||
|
't' => result.push('\t'),
|
||||||
|
'\\' => result.push('\\'),
|
||||||
|
'\'' => result.push('\''),
|
||||||
|
'"' => result.push('"'),
|
||||||
|
_ => { result.push('\\'); result.push(ch); }
|
||||||
|
}
|
||||||
|
escaped = false;
|
||||||
|
} else if ch == '\\' {
|
||||||
|
escaped = true;
|
||||||
|
} else if ch == quote {
|
||||||
|
return Some((result, &s[i + ch.len_utf8()..]));
|
||||||
|
} else {
|
||||||
|
result.push(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None // unclosed string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Number → string for arbitrary base ───────────────────────────────────────
|
||||||
|
|
||||||
|
/// JavaScript's `Number.prototype.toString(radix)` for bases 2–62.
|
||||||
|
/// Digits: 0-9, then a-z (10-35), then A-Z (36-61).
|
||||||
|
fn num_to_base_str(mut n: usize, base: u32) -> Result<String, UnpackError> {
|
||||||
|
if base < 2 || base > 62 {
|
||||||
|
return Err(UnpackError::BadBase(base));
|
||||||
|
}
|
||||||
|
if n == 0 { return Ok("0".to_owned()); }
|
||||||
|
|
||||||
|
const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
let base = base as usize;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
while n > 0 {
|
||||||
|
buf.push(DIGITS[n % base] as char);
|
||||||
|
n /= base;
|
||||||
|
}
|
||||||
|
buf.reverse();
|
||||||
|
Ok(buf.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Core decode ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// JS regex `\b` word-boundary replacement.
|
||||||
|
/// We use a hand-rolled matcher so we don't need an external crate.
|
||||||
|
fn replace_word_boundary(haystack: &str, needle: &str, replacement: &str) -> String {
|
||||||
|
if needle.is_empty() { return haystack.to_owned(); }
|
||||||
|
|
||||||
|
let h: Vec<char> = haystack.chars().collect();
|
||||||
|
let n: Vec<char> = needle.chars().collect();
|
||||||
|
let nlen = n.len();
|
||||||
|
let hlen = h.len();
|
||||||
|
|
||||||
|
let is_word = |c: char| c.is_ascii_alphanumeric() || c == '_';
|
||||||
|
|
||||||
|
let mut out = String::with_capacity(haystack.len());
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
while i <= hlen.saturating_sub(nlen) {
|
||||||
|
// Check if h[i..i+nlen] == needle
|
||||||
|
if h[i..i + nlen] == n[..] {
|
||||||
|
// Word-boundary checks
|
||||||
|
let left_ok = i == 0 || !is_word(h[i - 1]);
|
||||||
|
let right_ok = i + nlen == hlen || !is_word(h[i + nlen]);
|
||||||
|
|
||||||
|
if left_ok && right_ok {
|
||||||
|
out.push_str(replacement);
|
||||||
|
i += nlen;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push(h[i]);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
// Append whatever is left
|
||||||
|
for ch in &h[i..] { out.push(*ch); }
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(args: PackedArgs) -> Result<String, UnpackError> {
|
||||||
|
let PackedArgs { mut payload, base, words } = args;
|
||||||
|
|
||||||
|
// Validate base once up front.
|
||||||
|
if base < 2 || base > 62 {
|
||||||
|
return Err(UnpackError::BadBase(base));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirror the JS: for c from (words.len()-1) down to 0
|
||||||
|
let count = words.len();
|
||||||
|
for c in (0..count).rev() {
|
||||||
|
if words[c].is_empty() { continue; }
|
||||||
|
let key = num_to_base_str(c, base)?;
|
||||||
|
payload = replace_word_boundary(&payload, &key, &words[c]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Minimal self-contained packed snippet (base 36, 3 words).
|
||||||
|
/// Original source: `var hello = world + foo;`
|
||||||
|
/// Packed manually for the test:
|
||||||
|
/// payload = "var 1 = 2 + 0;"
|
||||||
|
/// base = 36
|
||||||
|
/// count = 3
|
||||||
|
/// words = ["foo", "hello", "world"] (indices 0,1,2)
|
||||||
|
#[test]
|
||||||
|
fn test_basic_unpack() {
|
||||||
|
// Build a fake packed string without the eval wrapper for direct testing.
|
||||||
|
let args = PackedArgs {
|
||||||
|
payload: "var 1 = 2 + 0;".to_owned(),
|
||||||
|
base: 36,
|
||||||
|
words: vec!["foo".to_owned(), "hello".to_owned(), "world".to_owned()],
|
||||||
|
};
|
||||||
|
let result = decode(args).unwrap();
|
||||||
|
assert_eq!(result, "var hello = world + foo;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_word_boundary() {
|
||||||
|
// "foo10bar" should NOT replace "10", but " 10 " should.
|
||||||
|
let result = replace_word_boundary("foo10bar baz 10 qux10", "10", "X");
|
||||||
|
assert_eq!(result, "foo10bar baz X qux10");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_num_to_base_str() {
|
||||||
|
assert_eq!(num_to_base_str(0, 36).unwrap(), "0");
|
||||||
|
assert_eq!(num_to_base_str(10, 36).unwrap(), "a");
|
||||||
|
assert_eq!(num_to_base_str(35, 36).unwrap(), "z");
|
||||||
|
assert_eq!(num_to_base_str(36, 36).unwrap(), "10");
|
||||||
|
assert_eq!(num_to_base_str(255, 16).unwrap(), "ff");
|
||||||
|
assert_eq!(num_to_base_str(7, 2).unwrap(), "111");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_split_dictionary() {
|
||||||
|
let input = r#"'foo|bar|baz'.split('|'),0,{})"#;
|
||||||
|
let words = parse_dictionary(input, 3).unwrap();
|
||||||
|
assert_eq!(words, vec!["foo", "bar", "baz"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_array_dictionary() {
|
||||||
|
let input = r#"["alpha","","gamma"],0,{})"#;
|
||||||
|
let words = parse_dictionary(input, 3).unwrap();
|
||||||
|
assert_eq!(words[0], "alpha");
|
||||||
|
assert_eq!(words[1], "");
|
||||||
|
assert_eq!(words[2], "gamma");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full round-trip with the eval wrapper (base 10, tiny example).
|
||||||
|
#[test]
|
||||||
|
fn test_full_eval_wrapper() {
|
||||||
|
// payload: "0 1 2" words: ["hello","world","rust"] base:10 count:3
|
||||||
|
let packed = r#"eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('0 1 2',10,3,'hello|world|rust'.split('|'),0,{}))"#;
|
||||||
|
let result = unpack(packed).unwrap();
|
||||||
|
assert_eq!(result, "hello world rust");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ pub mod hoster_proxy;
|
|||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
pub mod requester;
|
pub mod requester;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
pub mod dean_edwards;
|
||||||
|
|
||||||
pub fn parse_abbreviated_number(s: &str) -> Option<u32> {
|
pub fn parse_abbreviated_number(s: &str) -> Option<u32> {
|
||||||
let s = s.trim();
|
let s = s.trim();
|
||||||
|
|||||||
Reference in New Issue
Block a user