Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
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<u8> {
self.b.get(self.pos).copied()
}
fn literal(&mut self, word: &[u8], value: Value) -> Option<Value> {
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<Value> {
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<u32> {
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<String> {
self.pos += 1; // opening quote
let mut out: Vec<u8> = 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<Value> {
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::<i64>() {
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<Value> {
let bytes: Vec<u8> = 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<i64>)> {
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<Value> {
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,
}
}