1 import Base 2 import bend-kit-url@0.4.1.0/url.bend as Url 3 4 type Query is Data: 5 Query{key: String, value: String} 6 7 type Target is Data: 8 Target{segments: List<&2, String>, query: List<&2, Query>, authority: Maybe<&2, String>, star: Bool} 9 10 type Utf is Data: 11 Utf{need: U32, lo: U32, hi: U32, cp: U32, out: String} 12 13 def between(+c: U32, lo: U32, hi: U32) -> Bool: 14 Bool.and(U32.is_le(lo, c), U32.is_le(c, hi)) 15 16 def utf.lead(+c: U32, out: String, ascii: Bool) -> Maybe<&2, Utf>: 17 match ascii: 18 case True{}: 19 Some{Utf{0, 128, 191, 0, SCon{Chr{c}, out}}} 20 case False{}: 21 +valid = between(c, 194, 244) 22 +need = Bool.pick(U32, U32.is_lt(c, 224), 1, Bool.pick(U32, U32.is_lt(c, 240), 2, 3)) 23 +lo = Bool.pick(U32, U32.is_eq(c, 224), 160, Bool.pick(U32, U32.is_eq(c, 240), 144, 128)) 24 +hi = Bool.pick(U32, U32.is_eq(c, 237), 159, Bool.pick(U32, U32.is_eq(c, 244), 143, 191)) 25 mask = Bool.pick(U32, U32.is_eq(need, 1), 31, Bool.pick(U32, U32.is_eq(need, 2), 15, 7)) 26 Bool.pick(Maybe<&2, Utf>, valid, Some{Utf{need, lo, hi, (c .&. mask : U32), out}}, None{}) 27 28 def utf.more(ok: Bool, +need: U32, +cp: U32, +out: String) -> Maybe<&2, Utf>: 29 match ok: 30 case False{}: 31 None{} 32 case True{}: 33 Some{Utf{(need - 1 : U32), 128, 191, cp, Bool.pick(String, U32.is_eq(need, 1), SCon{Chr{cp}, out}, out)}} 34 35 def utf.step(idle: Bool, +c: U32, need: U32, lo: U32, hi: U32, cp: U32, out: String) -> Maybe<&2, Utf>: 36 match idle: 37 case True{}: 38 utf.lead(c, out, U32.is_lt(c, 128)) 39 case False{}: 40 utf.more(between(c, lo, hi), need, (cp * 64 + (c .&. 63 : U32) : U32), out) 41 42 def utf.finish(st: Maybe<&2, Utf>) -> Maybe<&2, String>: 43 match st: 44 case None{}: 45 None{} 46 case Some{Utf{need, lo, hi, cp, out}}: 47 Bool.pick(Maybe<&2, String>, U32.is_eq(need, 0), Some{String.reverse(out)}, None{}) 48 49 def utf.go(s: String, st: Maybe<&2, Utf>) -> Maybe<&2, String>: 50 match s: 51 case SNil{}: 52 utf.finish(st) 53 case SCon{Chr{+c}, t}: 54 match st: 55 case None{}: 56 None{} 57 case Some{Utf{+need, lo, hi, cp, out}}: 58 utf.go(t, utf.step(U32.is_eq(need, 0), c, need, lo, hi, cp, out)) 59 60 def text.valid(s: String, +slash: Bool) -> Bool: 61 match s: 62 case SNil{}: 63 True{} 64 case SCon{Chr{+c}, t}: 65 Bool.and(Bool.and(U32.is_ne(c, 0), Bool.not(Bool.and(slash, U32.is_eq(c, 47)))), text.valid(t, slash)) 66 67 def decoded.valid(+s: String, slash: Bool) -> Maybe<&2, String>: 68 Bool.pick(Maybe<&2, String>, text.valid(s, slash), Some{s}, None{}) 69 70 def decoded(st: Maybe<&2, String>, slash: Bool) -> Maybe<&2, String>: 71 match st: 72 case None{}: 73 None{} 74 case Some{s}: 75 decoded.valid(s, slash) 76 77 def percent.got(st: Maybe<&2, String>, slash: Bool) -> Maybe<&2, String>: 78 match st: 79 case None{}: 80 None{} 81 case Some{s}: 82 decoded(utf.go(s, Some{Utf{0, 128, 191, 0, ""}}), slash) 83 84 def percent(s: String, slash: Bool) -> Maybe<&2, String>: 85 percent.got(Url.pct.decode(s), slash) 86 87 def segments(xs: List<&2, String>) -> Maybe<&2, List<&2, String>>: 88 match xs: 89 case Nil{}: 90 Some{Nil{}} 91 case Con{h, t}: 92 do Maybe<&2, List<&2, String>>: 93 head : String <- percent(h, True{}) 94 tail : List<&2, String> <- segments(t) 95 return head <> tail 96 97 def split.path(+path: String) -> List<&2, String>: 98 Bool.pick(List<&2, String>, String.eq(path, "/"), Nil{}, String.split(String.drop(path, 1n), '/')) 99 100 def plus(s: String) -> String: 101 match s: 102 case SNil{}: 103 SNil{} 104 case SCon{Chr{+c}, t}: 105 SCon{Chr{Bool.pick(U32, U32.is_eq(c, 43), 32, c)}, plus(t)} 106 107 def query.pair(pair: String & String) -> Maybe<&2, Query>: 108 (key, value) = pair 109 do Maybe<&2, Query>: 110 k : String <- percent(plus(key), False{}) 111 v : String <- percent(plus(value), False{}) 112 return Query{k, v} 113 114 def query.field(field: String) -> Maybe<&2, Query>: 115 query.pair(Url.take_to(field, 61)) 116 117 def query(xs: List<&2, String>, acc: List<&2, Query>) -> Maybe<&2, List<&2, Query>>: 118 match xs: 119 case Nil{}: 120 Some{List.reverse(&2, Query, acc)} 121 case "" <> t: 122 query(t, acc) 123 case h <> t: 124 do Maybe<&2, List<&2, Query>>: 125 pair : Query <- query.field(h) 126 query(t, pair <> acc) 127 128 def raw.valid(s: String) -> Bool: 129 match s: 130 case SNil{}: 131 True{} 132 case SCon{Chr{+c}, t}: 133 Bool.and(Bool.and(between(c, 33, 126), U32.is_ne(c, 35)), raw.valid(t)) 134 135 def origin(path: String, q: String, authority: Maybe<&2, String>) -> Maybe<&2, Target>: 136 do Maybe<&2, Target>: 137 ss : List<&2, String> <- segments(split.path(path)) 138 pairs : List<&2, Query> <- query(String.split(q, '&'), Nil{}) 139 return Target{ss, pairs, authority, False{}} 140 141 def origin.split(pair: String & String, authority: Maybe<&2, String>) -> Maybe<&2, Target>: 142 (path, q) = pair 143 origin(path, q, authority) 144 145 def alpha(+c: U32) -> Bool: 146 Bool.or(between(c, 65, 90), between(c, 97, 122)) 147 148 def scheme.tail(s: String) -> Bool: 149 match s: 150 case SNil{}: 151 True{} 152 case SCon{Chr{+c}, t}: 153 Bool.and(Bool.or(alpha(c), Bool.or(between(c, 48, 57), Bool.or(U32.is_eq(c, 43), Bool.or(U32.is_eq(c, 45), U32.is_eq(c, 46))))), scheme.tail(t)) 154 155 def scheme(s: String) -> Bool: 156 match s: 157 case SNil{}: 158 False{} 159 case SCon{Chr{c}, t}: 160 Bool.and(alpha(c), scheme.tail(t)) 161 162 def authority.take(s: String, acc: String) -> String & String: 163 match s: 164 case SNil{}: 165 (String.reverse(acc), "/") 166 case SCon{Chr{47}, t}: 167 (String.reverse(acc), SCon{Chr{47}, t}) 168 case SCon{Chr{63}, t}: 169 (String.reverse(acc), SCon{Chr{47}, SCon{Chr{63}, t}}) 170 case SCon{h, t}: 171 authority.take(t, SCon{h, acc}) 172 173 def absolute.authority(ok: Bool, authority: String, path: String) -> Maybe<&2, Target>: 174 match ok: 175 case False{}: 176 None{} 177 case True{}: 178 origin.split(Url.take_to(path, 63), Some{authority}) 179 180 def absolute.taken(pair: String & String) -> Maybe<&2, Target>: 181 (+host, path) = pair 182 absolute.authority(Bool.not(String.is_empty(host)), host, path) 183 184 def absolute.rest(rest: String) -> Maybe<&2, Target>: 185 match rest: 186 case SCon{Chr{47}, SCon{Chr{47}, t}}: 187 absolute.taken(authority.take(t, "")) 188 case _: 189 None{} 190 191 def absolute.scheme(ok: Bool, rest: String) -> Maybe<&2, Target>: 192 match ok: 193 case False{}: 194 None{} 195 case True{}: 196 absolute.rest(rest) 197 198 def absolute.split(pair: String & String) -> Maybe<&2, Target>: 199 (+protocol, rest) = pair 200 absolute.scheme(scheme(protocol), rest) 201 202 def parse.form(target: String, method: String) -> Maybe<&2, Target>: 203 match target: 204 case "*": 205 Bool.pick(Maybe<&2, Target>, String.eq(method, "OPTIONS"), Some{Target{Nil{}, Nil{}, None{}, True{}}}, None{}) 206 case SCon{Chr{47}, t}: 207 origin.split(Url.take_to(SCon{Chr{47}, t}, 63), None{}) 208 case SNil{}: 209 None{} 210 case SCon{h, t}: 211 absolute.split(Url.take_to(SCon{h, t}, 58)) 212 213 def parse.valid(ok: Bool, target: String, method: String) -> Maybe<&2, Target>: 214 match ok: 215 case False{}: 216 None{} 217 case True{}: 218 parse.form(target, method) 219 220 def parse(+target: String, method: String) -> Maybe<&2, Target>: 221 parse.valid(raw.valid(target), target, method)