1 import Base 2 3 # Encode one byte as a percent escape. The SQL library used this representation; 4 # keeping it preserves existing query and form-body behavior. 5 def hex_digit_kind(n: U32, decimal: Bool) -> U32: 6 match decimal: 7 case True{}: (48 + n : U32) 8 case False{}: (55 + n : U32) 9 10 def hex_digit(+n: U32) -> U32: 11 hex_digit_kind(n, U32.is_lt(n, 10)) 12 13 def percent_byte(+byte: U32) -> String: 14 SCon{Chr{37}, SCon{Chr{hex_digit(U32.div(byte, 16))}, SCon{Chr{hex_digit(U32.mod(byte, 16))}, SNil{}}}} 15 16 def percent_codepoint_width(+codepoint: U32, width: U32) -> String: 17 match width: 18 case 1: percent_byte(codepoint) 19 case 2: percent_byte((192 + U32.shrn(codepoint, 6n) : U32)) ++ 20 percent_byte((128 + U32.and(codepoint, 63) : U32)) 21 case 3: percent_byte((224 + U32.shrn(codepoint, 12n) : U32)) ++ 22 percent_byte((128 + U32.and(U32.shrn(codepoint, 6n), 63) : U32)) ++ 23 percent_byte((128 + U32.and(codepoint, 63) : U32)) 24 case _: percent_byte((240 + U32.shrn(codepoint, 18n) : U32)) ++ 25 percent_byte((128 + U32.and(U32.shrn(codepoint, 12n), 63) : U32)) ++ 26 percent_byte((128 + U32.and(U32.shrn(codepoint, 6n), 63) : U32)) ++ 27 percent_byte((128 + U32.and(codepoint, 63) : U32)) 28 29 def percent_codepoint(+codepoint: U32) -> String: 30 +width = Bool.pick(U32, U32.is_lt(codepoint, 128), 1, 31 Bool.pick(U32, U32.is_lt(codepoint, 2048), 2, 32 Bool.pick(U32, U32.is_lt(codepoint, 65536), 3, 4))) 33 percent_codepoint_width(codepoint, width) 34 35 def percent_characters(text: String) -> String: 36 match text: 37 case SNil{}: "" 38 case SCon{Chr{+codepoint}, tail}: 39 percent_codepoint(codepoint) ++ percent_characters(tail) 40 41 # Percent-encode a URL component as UTF-8 bytes. This deliberately encodes 42 # every byte, matching the former SQL.percent_component behavior. 43 def percent_component(text: String) -> String: 44 percent_characters(text)