use super::funejson::Value; use super::encoding_base64_base64_url_decode::base64_url_decode; /// Containers nested deeper than this are refused, in every language, rather /// than left to each parser's own limit. const MAX_DEPTH: usize = 32; const MAX_SAFE_F64: f64 = 9007199254740991.0; fn sextet(b: u8) -> i64 { match b { b'A'..=b'Z' => (b - b'A') as i64, b'a'..=b'z' => (b - b'a' + 26) as i64, b'0'..=b'9' => (b - b'0' + 52) as i64, b'-' => 62, b'_' => 63, _ => -1, } } /// One canonical, unpadded base64url segment? Checked before decoding so a /// hostile token is an answer (`None`), never a panic. fn is_segment(text: &str, allow_empty: bool) -> bool { let b = text.as_bytes(); if b.is_empty() { return allow_empty; } if b.len() % 4 == 1 { return false; } let mut last = 0; for &c in b { last = sextet(c); if last < 0 { return false; } } match b.len() % 4 { 2 => last & 15 == 0, 3 => last & 3 == 0, _ => true, } } /// A strict RFC 8259 parser. The funejson runtime's parser is built for test /// vectors and is lenient (a leading +, any Unicode whitespace); a token from /// the network has to be read exactly as TypeScript's JSON.parse and /// Python's json.loads read it, with the same refusals: integers beyond /// 2^53, non-finite numbers, lone surrogates and nesting past 32. struct Parser<'a> { b: &'a [u8], pos: usize, } impl<'a> Parser<'a> { fn ws(&mut self) { while self.pos < self.b.len() && matches!(self.b[self.pos], b' ' | b'\t' | b'\n' | b'\r') { self.pos += 1; } } fn peek(&self) -> Option { self.b.get(self.pos).copied() } fn literal(&mut self, word: &[u8], value: Value) -> Option { if self.b.len() >= self.pos + word.len() && &self.b[self.pos..self.pos + word.len()] == word { self.pos += word.len(); Some(value) } else { None } } fn value(&mut self, depth: usize) -> Option { self.ws(); match self.peek()? { b'{' => { if depth > MAX_DEPTH { return None; } self.pos += 1; let mut pairs: Vec<(String, Value)> = Vec::new(); self.ws(); if self.peek() == Some(b'}') { self.pos += 1; return Some(Value::Obj(pairs)); } loop { self.ws(); if self.peek()? != b'"' { return None; } let key = self.string()?; self.ws(); if self.peek()? != b':' { return None; } self.pos += 1; let item = self.value(depth + 1)?; // A repeated key keeps its first position and its last // value, as JavaScript objects and Python dicts do. match pairs.iter_mut().find(|(k, _)| *k == key) { Some(slot) => slot.1 = item, None => pairs.push((key, item)), } self.ws(); match self.peek()? { b',' => self.pos += 1, b'}' => { self.pos += 1; return Some(Value::Obj(pairs)); } _ => return None, } } } b'[' => { if depth > MAX_DEPTH { return None; } self.pos += 1; let mut items = Vec::new(); self.ws(); if self.peek() == Some(b']') { self.pos += 1; return Some(Value::Arr(items)); } loop { items.push(self.value(depth + 1)?); self.ws(); match self.peek()? { b',' => self.pos += 1, b']' => { self.pos += 1; return Some(Value::Arr(items)); } _ => return None, } } } b'"' => self.string().map(Value::Str), b't' => self.literal(b"true", Value::Bool(true)), b'f' => self.literal(b"false", Value::Bool(false)), b'n' => self.literal(b"null", Value::Null), _ => self.number(), } } fn hex4(&mut self) -> Option { if self.pos + 4 > self.b.len() { return None; } let mut n = 0u32; for &c in &self.b[self.pos..self.pos + 4] { n = n * 16 + (c as char).to_digit(16)?; } self.pos += 4; Some(n) } fn string(&mut self) -> Option { self.pos += 1; // opening quote let mut out: Vec = Vec::new(); loop { let c = self.peek()?; self.pos += 1; match c { b'"' => return String::from_utf8(out).ok(), b'\\' => { let e = self.peek()?; self.pos += 1; let ch = match e { b'"' => '"', b'\\' => '\\', b'/' => '/', b'b' => '\u{8}', b'f' => '\u{c}', b'n' => '\n', b'r' => '\r', b't' => '\t', b'u' => { let hi = self.hex4()?; let code = if (0xD800..=0xDBFF).contains(&hi) { if self.b.get(self.pos) != Some(&b'\\') || self.b.get(self.pos + 1) != Some(&b'u') { return None; } self.pos += 2; let lo = self.hex4()?; if !(0xDC00..=0xDFFF).contains(&lo) { return None; } 0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00) } else { hi }; // A lone low surrogate has no char and is refused here. char::from_u32(code)? } _ => return None, }; let mut buf = [0u8; 4]; out.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes()); } 0x00..=0x1f => return None, other => out.push(other), } } } fn digits(&mut self) -> usize { let start = self.pos; while self.pos < self.b.len() && self.b[self.pos].is_ascii_digit() { self.pos += 1; } self.pos - start } fn number(&mut self) -> Option { let start = self.pos; if self.peek() == Some(b'-') { self.pos += 1; } match self.peek()? { b'0' => self.pos += 1, b'1'..=b'9' => { self.digits(); } _ => return None, } let mut is_float = false; if self.peek() == Some(b'.') { self.pos += 1; if self.digits() == 0 { return None; } is_float = true; } if matches!(self.peek(), Some(b'e') | Some(b'E')) { self.pos += 1; if matches!(self.peek(), Some(b'+') | Some(b'-')) { self.pos += 1; } if self.digits() == 0 { return None; } is_float = true; } let text = std::str::from_utf8(&self.b[start..self.pos]).ok()?; if !is_float { if let Ok(i) = text.parse::() { return if (i as f64).abs() <= MAX_SAFE_F64 { Some(Value::Int(i)) } else { None }; } } let f: f64 = text.parse().ok()?; if !f.is_finite() || (f.fract() == 0.0 && f.abs() > MAX_SAFE_F64) { return None; } Some(Value::Float(f)) } } fn parse_object(segment: &str) -> Option { let bytes: Vec = base64_url_decode(segment).into_iter().map(|b| b as u8).collect(); let text = String::from_utf8(bytes).ok()?; let mut parser = Parser { b: text.as_bytes(), pos: 0 }; parser.ws(); if parser.peek() != Some(b'{') { return None; } let value = parser.value(1)?; parser.ws(); if parser.pos != parser.b.len() { return None; } Some(value) } /// (header, claims, signing input, signature bytes) of a compact JWT, or /// `None` when it is not three canonical base64url segments whose first two /// are JSON objects. Nothing is verified here; `verify_jwt` builds on it. pub fn split_jwt(token: &str) -> Option<(Value, Value, String, Vec)> { let parts: Vec<&str> = token.split('.').collect(); if parts.len() != 3 { return None; } if !is_segment(parts[0], false) || !is_segment(parts[1], false) || !is_segment(parts[2], true) { return None; } let header = parse_object(parts[0])?; let claims = parse_object(parts[1])?; Some((header, claims, format!("{}.{}", parts[0], parts[1]), base64_url_decode(parts[2]))) } /// RFC 7518 section 3.2: an HS256 key MUST be at least 256 bits. /// /// # Panics /// Panics on a value outside 0-255 or fewer than 32 bytes. pub fn check_jwt_secret(secret: &[i64]) { if secret.iter().any(|b| !(0..=255).contains(b)) { panic!("secret must be a list of integers from 0 to 255"); } if secret.len() < 32 { panic!( "secret must be at least 32 bytes (RFC 7518 section 3.2), received {}", secret.len() ); } } /// The claims of a JWT WITHOUT checking its signature, or `None` when it is /// malformed. For reading exp or name from a token you hold; never for /// deciding whether to trust one. pub fn decode_jwt(token: &str) -> Option { split_jwt(token).map(|(_, claims, _, _)| claims) } pub fn fune_vector(args: &[Value]) -> Value { match &args[0] { Value::Str(token) => decode_jwt(token).unwrap_or(Value::Null), _ => Value::Null, } }