fmt.ha (2977B)
1 use ascii; 2 use encoding::utf8; 3 use fmt; 4 use io; 5 use net::ip; 6 use strconv; 7 use strings; 8 use strio; 9 10 11 // Extract from RFC3986 ABNF 12 // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] 13 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 14 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" 15 // reg-name = *( unreserved / pct-encoded / sub-delims ) 16 // host = IP-literal / IPv4address / reg-name 17 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 18 // query = *( pchar / "/" / "?" ) 19 // fragment = *( pchar / "/" / "?" ) 20 21 def unres_host: str = "-._~!$&'()*+,;="; 22 def unres_query_frag: str = "-._~!$&'()*+,;=:@/?"; 23 def unres_path: str = "-._~!$&'()*+,;=:@/"; 24 25 // Writes a formatted [[uri]] to an [[io::handle]]. Returns the number of bytes 26 // written. 27 export fn fmt(out: io::handle, u: *const uri) (size | io::error) = { 28 let n = 0z; 29 let slashes_w = false; 30 if (u.scheme != "") { 31 n += fmt::fprintf(out, "{}:", u.scheme)?; 32 }; 33 if (len(u.userinfo) > 0) { 34 assert(!(u.host is str) || len(u.host as str) > 0); 35 n += fmt::fprintf(out, "//{}@", u.userinfo)?; 36 slashes_w = true; 37 }; 38 match (u.host) { 39 case let host: str => 40 // file scheme is allowed an empty host 41 if (len(host) > 0 || u.scheme == "file") { 42 if (!slashes_w) { 43 n += fmt::fprint(out, "//")?; 44 }; 45 let unres = if(u.scheme == "file") { 46 yield unres_path; 47 } else { 48 yield unres_host; 49 }; 50 n += percent_encode(out, host, unres)?; 51 }; 52 case let addr: ip::addr => 53 if (!slashes_w) { 54 n += fmt::fprint(out, "//")?; 55 }; 56 n += fmtaddr(out, addr)?; 57 }; 58 if (u.port != 0) { 59 n += fmt::fprintf(out, ":{}", u.port)?; 60 }; 61 n += percent_encode(out, u.path, unres_path)?; 62 if (len(u.query) > 0) { 63 // Always percent-encoded, see parse and encodequery/decodequery 64 n += fmt::fprintf(out, "?{}", u.query)?; 65 }; 66 if (len(u.fragment) > 0) { 67 n += fmt::fprint(out, "#")?; 68 n += percent_encode(out, u.fragment, unres_query_frag)?; 69 }; 70 71 return n; 72 }; 73 74 fn fmtaddr(out: io::handle, addr: ip::addr) (size | io::error) = { 75 let n = 0z; 76 match (addr) { 77 case let addr: ip::addr4 => 78 n += ip::fmt(out, addr)?; 79 case let addr: ip::addr6 => 80 n += fmt::fprintf(out, "[")?; 81 n += ip::fmt(out, addr)?; 82 n += fmt::fprintf(out, "]")?; 83 }; 84 return n; 85 }; 86 87 fn percent_encode(out: io::handle, src: str, allowed: str) (size | io::error) = { 88 let iter = strings::iter(src); 89 let n = 0z; 90 for (true) { 91 const r = match (strings::next(&iter)) { 92 case let r: rune => 93 yield r; 94 case => 95 break; 96 }; 97 if (ascii::isalnum(r) || strings::contains(allowed, r)) { 98 n += fmt::fprint(out, r)?; 99 } else { 100 const en = utf8::encoderune(r); 101 for (let i = 0z; i < len(en); i += 1) { 102 n += fmt::fprintf(out, "%{:X}", en[i])?; 103 }; 104 }; 105 }; 106 return n; 107 }; 108 109 // Formats a [[uri]] into a string. The result must be freed by the caller. 110 export fn string(u: *const uri) str = { 111 const st = strio::dynamic(); 112 fmt(&st, u)!; 113 return strio::string(&st); 114 };