//// Percent-encoding helpers shared by SigV4 canonical-URI/query encoding and //// by HTTP-form encoding in the STS Web Identity provider. Kept in its own //// module so SigV4 and the STS provider can both depend on it without //// creating an import cycle through `aws/credentials`. import gleam/bit_array /// Percent-encode a single URI path segment. RFC 3986 unreserved characters /// (`A-Za-z0-9-_.~`) pass through; everything else becomes `%HH`. pub fn encode_segment(s: String) -> String { encode_bytes(bit_array.from_string(s), "") } /// Percent-encode a URI component, first decoding any pre-existing /// percent-escapes so callers can pass values that may or may not already /// be encoded. pub fn encode_component(s: String) -> String { encode_bytes(percent_decode_bytes(bit_array.from_string(s)), "") } fn encode_bytes(bits: BitArray, acc: String) -> String { case bits { <<>> -> acc <> -> case is_unreserved(b) { True -> encode_bytes(rest, acc <> byte_to_string(b)) False -> encode_bytes(rest, acc <> "%" <> hex_byte(b)) } _ -> acc } } fn is_unreserved(b: Int) -> Bool { { b >= 0x41 && b <= 0x5A } || { b >= 0x61 && b <= 0x7A } || { b >= 0x30 && b <= 0x39 } || b == 0x2D || b == 0x5F || b == 0x2E || b == 0x7E } fn byte_to_string(b: Int) -> String { let bits = <> case bit_array.to_string(bits) { Ok(s) -> s Error(_) -> "" } } fn hex_byte(b: Int) -> String { let high = b / 16 let low = b % 16 hex_digit(high) <> hex_digit(low) } fn hex_digit(n: Int) -> String { case n { 0 -> "0" 1 -> "1" 2 -> "2" 3 -> "3" 4 -> "4" 5 -> "5" 6 -> "6" 7 -> "7" 8 -> "8" 9 -> "9" 10 -> "A" 11 -> "B" 12 -> "C" 13 -> "D" 14 -> "E" _ -> "F" } } fn percent_decode_bytes(bits: BitArray) -> BitArray { do_percent_decode(bits, <<>>) } fn do_percent_decode(bits: BitArray, acc: BitArray) -> BitArray { case bits { <<>> -> acc <<0x25, h, l, rest:bits>> -> case hex_value(h), hex_value(l) { Ok(hv), Ok(lv) -> { let byte = hv * 16 + lv do_percent_decode(rest, bit_array.append(acc, <>)) } _, _ -> do_percent_decode(rest, bit_array.append(acc, <<0x25, h, l>>)) } <> -> do_percent_decode(rest, bit_array.append(acc, <>)) _ -> acc } } fn hex_value(b: Int) -> Result(Int, Nil) { case b { _ if b >= 0x30 && b <= 0x39 -> Ok(b - 0x30) _ if b >= 0x41 && b <= 0x46 -> Ok(b - 0x41 + 10) _ if b >= 0x61 && b <= 0x66 -> Ok(b - 0x61 + 10) _ -> Error(Nil) } }