rsynko_reqwest/
signature.rs1use thiserror::Error;
9
10#[derive(Clone, Debug, Error, PartialEq, Eq)]
12pub enum SignatureProgramError {
13 #[error("the player program states no signature decipher function")]
15 MissingFunction,
16 #[error("the decipher function calls a helper object the player program does not state")]
18 MissingHelper,
19 #[error("the decipher function applies an unrecognized transformation: {0}")]
21 UnknownTransformation(String),
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum SignatureStep {
27 Reverse,
29 Drop(usize),
31 Swap(usize),
33}
34
35#[derive(Clone, Debug, Default, PartialEq, Eq)]
37pub struct SignatureProgram {
38 steps: Vec<SignatureStep>,
39}
40
41impl SignatureProgram {
42 pub fn recover(player: &str) -> Result<Self, SignatureProgramError> {
48 let body = decipher_body(player).ok_or(SignatureProgramError::MissingFunction)?;
49 let helper = helper_name(body).ok_or(SignatureProgramError::MissingHelper)?;
50 let object = helper_object(player, &helper).ok_or(SignatureProgramError::MissingHelper)?;
51 body.split(';')
52 .filter_map(|call| helper_call(call, &helper))
53 .map(|(name, argument)| step(&object, name, argument))
54 .collect::<Result<Vec<_>, _>>()
55 .map(|steps| Self { steps })
56 }
57
58 #[must_use]
60 pub fn steps(&self) -> &[SignatureStep] {
61 &self.steps
62 }
63
64 #[must_use]
66 pub fn decipher(&self, signature: &str) -> String {
67 let mut characters: Vec<char> = signature.chars().collect();
68 for step in &self.steps {
69 if characters.is_empty() {
70 break;
71 }
72 match *step {
73 SignatureStep::Reverse => characters.reverse(),
74 SignatureStep::Drop(count) => {
75 characters.drain(..count.min(characters.len()));
76 }
77 SignatureStep::Swap(index) => {
78 let position = index % characters.len();
79 characters.swap(0, position);
80 }
81 }
82 }
83 characters.into_iter().collect()
84 }
85}
86
87fn decipher_body(player: &str) -> Option<&str> {
89 for marker in ["=a.split(\"\")", "=a.split('')"] {
92 let mut search = player;
93 let mut offset = 0;
94 while let Some(index) = search.find(marker) {
95 let start = offset + index + marker.len();
96 let tail = &player[start..];
97 if let Some(end) = tail.find("return a.join(") {
98 let body = tail.get(..end)?;
99 if !body.contains("function") {
100 return Some(body.trim_start_matches(';'));
101 }
102 }
103 offset = start;
104 search = &player[offset..];
105 }
106 }
107 None
108}
109
110fn helper_name(body: &str) -> Option<String> {
112 let call = body
113 .split(';')
114 .map(str::trim)
115 .find(|call| call.split_once('.').is_some_and(|(head, tail)| !head.is_empty() && tail.contains('(')))?;
116 call.split('.').next().map(str::to_owned)
117}
118
119fn helper_object(player: &str, name: &str) -> Option<String> {
121 let start = [format!("var {name}={{"), format!("var {name} = {{"), format!("{name}={{")]
123 .into_iter()
124 .find_map(|marker| Some(player.find(&marker)? + marker.len()))?;
125 let tail = &player[start..];
126 let mut depth = 1_u32;
127 for (offset, character) in tail.char_indices() {
128 match character {
129 '{' => depth += 1,
130 '}' => {
131 depth -= 1;
132 if depth == 0 {
133 return tail.get(..offset).map(str::to_owned);
134 }
135 }
136 _ => {}
137 }
138 }
139 None
140}
141
142fn helper_call<'a>(call: &'a str, helper: &str) -> Option<(&'a str, usize)> {
144 let call = call.trim();
145 let tail = call.strip_prefix(helper)?.strip_prefix('.')?;
146 let (name, arguments) = tail.split_once('(')?;
147 let argument = arguments.trim_end_matches(')').split(',').nth(1)?;
148 Some((name, argument.trim().parse().unwrap_or(0)))
149}
150
151fn step(object: &str, name: &str, argument: usize) -> Result<SignatureStep, SignatureProgramError> {
153 let marker = format!("{name}:function");
154 let start = object.find(&marker).ok_or_else(|| SignatureProgramError::UnknownTransformation(name.to_owned()))?;
155 let body = &object[start..];
156 let body = body.split_once('}').map_or(body, |(head, _)| head);
157 if body.contains("reverse") {
158 Ok(SignatureStep::Reverse)
159 } else if body.contains("splice") {
160 Ok(SignatureStep::Drop(argument))
161 } else if body.contains('%') {
162 Ok(SignatureStep::Swap(argument))
163 } else {
164 Err(SignatureProgramError::UnknownTransformation(name.to_owned()))
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 const PLAYER: &str = r#"
174var _yt_player={};
175(function(g){var window=this;var Kx={
176xW:function(a){a.reverse()},
177Jd:function(a,b){a.splice(0,b)},
178Ll:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}};
179g.tj=function(a){a=a.split("");Kx.Ll(a,32);Kx.xW(a,58);Kx.Jd(a,2);Kx.Ll(a,7);
180return a.join("")};
181})(_yt_player);
182"#;
183
184 #[test]
185 fn the_recovered_program_is_the_sequence_the_player_states() {
186 let program = SignatureProgram::recover(PLAYER).expect("decipher program");
187
188 assert_eq!(
189 program.steps(),
190 [SignatureStep::Swap(32), SignatureStep::Reverse, SignatureStep::Drop(2), SignatureStep::Swap(7),]
191 );
192 }
193
194 #[test]
195 fn deciphering_applies_every_transformation_in_order() {
196 let program = SignatureProgram::recover(PLAYER).expect("decipher program");
197
198 let mut expected: Vec<char> = "abcdefghijklmnopqrstuvwxyz0123456789".chars().collect();
200 let position = 32 % expected.len();
201 expected.swap(0, position);
202 expected.reverse();
203 expected.drain(..2);
204 let position = 7 % expected.len();
205 expected.swap(0, position);
206
207 assert_eq!(program.decipher("abcdefghijklmnopqrstuvwxyz0123456789"), expected.into_iter().collect::<String>());
208 }
209
210 #[test]
211 fn deciphering_preserves_the_character_count_the_steps_denote() {
212 let program = SignatureProgram::recover(PLAYER).expect("decipher program");
213 let signature = "abcdefghijklmnopqrstuvwxyz0123456789";
214
215 assert_eq!(program.decipher(signature).chars().count(), signature.len() - 2);
216 }
217
218 #[test]
219 fn a_script_stating_no_decipher_function_states_no_program() {
220 assert_eq!(SignatureProgram::recover("var a = 1;"), Err(SignatureProgramError::MissingFunction));
221 }
222
223 #[test]
224 fn a_decipher_function_without_its_helper_object_states_no_program() {
225 let orphan = r#"g.tj=function(a){a=a.split("");Kx.Ll(a,32);return a.join("")};"#;
226
227 assert_eq!(SignatureProgram::recover(orphan), Err(SignatureProgramError::MissingHelper));
228 }
229
230 #[test]
231 fn the_empty_program_leaves_a_signature_unchanged() {
232 assert_eq!(SignatureProgram::default().decipher("posed"), "posed");
233 }
234}