10e96539fSCharles.Forsythimplement URIs; 20e96539fSCharles.Forsyth 30e96539fSCharles.Forsyth# 40e96539fSCharles.Forsyth# RFC3986, URI Generic Syntax 50e96539fSCharles.Forsyth# 60e96539fSCharles.Forsyth 70e96539fSCharles.Forsythinclude "sys.m"; 80e96539fSCharles.Forsyth sys: Sys; 90e96539fSCharles.Forsyth 100e96539fSCharles.Forsythinclude "string.m"; 110e96539fSCharles.Forsyth S: String; 120e96539fSCharles.Forsyth 130e96539fSCharles.Forsythinclude "uris.m"; 140e96539fSCharles.Forsyth 150e96539fSCharles.ForsythAlpha: con "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 160e96539fSCharles.ForsythDigit: con "0123456789"; 170e96539fSCharles.Forsyth 180e96539fSCharles.ForsythGenDelims: con ":/?#[]@"; 190e96539fSCharles.ForsythSubDelims: con "!$&'()*+,;="; 200e96539fSCharles.ForsythReserved: con GenDelims + SubDelims; 210e96539fSCharles.ForsythHexDigit: con Digit+"abcdefABCDEF"; 220e96539fSCharles.Forsyth 230e96539fSCharles.ForsythEscape: con GenDelims+"%"; # "%" must be encoded as %25 240e96539fSCharles.Forsyth 250e96539fSCharles.ForsythUnreserved: con Alpha+Digit+"-._~"; 260e96539fSCharles.Forsyth 270e96539fSCharles.ForsythF_Esc, F_Scheme: con byte(1<<iota); 280e96539fSCharles.Forsyth 290e96539fSCharles.Forsythctype: array of byte; 300e96539fSCharles.Forsyth 310e96539fSCharles.Forsythclassify(s: string, f: byte) 320e96539fSCharles.Forsyth{ 330e96539fSCharles.Forsyth for(i := 0; i < len s; i++) 340e96539fSCharles.Forsyth ctype[s[i]] |= f; 350e96539fSCharles.Forsyth} 360e96539fSCharles.Forsyth 370e96539fSCharles.Forsythinit() 380e96539fSCharles.Forsyth{ 390e96539fSCharles.Forsyth sys = load Sys Sys->PATH; 400e96539fSCharles.Forsyth S = load String String->PATH; 410e96539fSCharles.Forsyth if(S == nil) 420e96539fSCharles.Forsyth raise sys->sprint("can't load %s: %r", String->PATH); 430e96539fSCharles.Forsyth 440e96539fSCharles.Forsyth ctype = array [256] of { * => byte 0 }; 450e96539fSCharles.Forsyth classify(Escape, F_Esc); 460e96539fSCharles.Forsyth for(i := 0; i <= ' '; i++) 470e96539fSCharles.Forsyth ctype[i] |= F_Esc; 480e96539fSCharles.Forsyth for(i = 16r80; i <= 16rFF; i++) 490e96539fSCharles.Forsyth ctype[i] |= F_Esc; 500e96539fSCharles.Forsyth classify(Alpha+Digit+"+-.", F_Scheme); 510e96539fSCharles.Forsyth} 520e96539fSCharles.Forsyth 530e96539fSCharles.Forsyth# scheme://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment> 540e96539fSCharles.Forsyth# 550e96539fSCharles.Forsyth# ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? 560e96539fSCharles.Forsyth# 570e96539fSCharles.Forsyth# delimiters: :/?# /?# ?# # 580e96539fSCharles.Forsyth# 590e96539fSCharles.ForsythURI.parse(url: string): ref URI 600e96539fSCharles.Forsyth{ 610e96539fSCharles.Forsyth scheme, userinfo, host, port, path, query, frag: string; 620e96539fSCharles.Forsyth for(i := 0; i < len url; i++){ 630e96539fSCharles.Forsyth c := url[i]; 640e96539fSCharles.Forsyth if(c == ':'){ 650e96539fSCharles.Forsyth scheme = S->tolower(url[0:i]); 660e96539fSCharles.Forsyth url = url[i+1:]; 670e96539fSCharles.Forsyth break; 680e96539fSCharles.Forsyth } 690e96539fSCharles.Forsyth if(c < 0 || c >= len ctype || (ctype[c] & F_Scheme) == byte 0) 700e96539fSCharles.Forsyth break; 710e96539fSCharles.Forsyth } 720e96539fSCharles.Forsyth 730e96539fSCharles.Forsyth if(S->prefix("//", url)){ 740e96539fSCharles.Forsyth authority: string; 750e96539fSCharles.Forsyth (authority, path) = S->splitstrl(url[2:], "/"); 760e96539fSCharles.Forsyth (up, hp) := splitl(authority, "@"); 770e96539fSCharles.Forsyth if(hp == "") 780e96539fSCharles.Forsyth hp = authority; 790e96539fSCharles.Forsyth else 800e96539fSCharles.Forsyth userinfo = up; 810e96539fSCharles.Forsyth if(hp != nil && hp[0] == '['){ # another rfc hack, for IPv6 addresses, which contain : 820e96539fSCharles.Forsyth (host, hp) = S->splitstrr(hp, "]"); 830e96539fSCharles.Forsyth if(hp != nil && hp[0] == ':') 840e96539fSCharles.Forsyth port = hp[1:]; 850e96539fSCharles.Forsyth else 860e96539fSCharles.Forsyth host += hp; # put it back 870e96539fSCharles.Forsyth }else 880e96539fSCharles.Forsyth (host, port) = splitl(hp, ":"); 890e96539fSCharles.Forsyth if(path == nil) 900e96539fSCharles.Forsyth path = "/"; 910e96539fSCharles.Forsyth }else 920e96539fSCharles.Forsyth path = url; 930e96539fSCharles.Forsyth (path, frag) = S->splitstrl(path, "#"); # includes # in frag 940e96539fSCharles.Forsyth (path, query) = S->splitstrl(path, "?"); # includes ? in query 950e96539fSCharles.Forsyth return ref URI(scheme, dec(userinfo), dec(host), port, dec(path), query, dec(frag)); 960e96539fSCharles.Forsyth} 970e96539fSCharles.Forsyth 980e96539fSCharles.ForsythURI.userpw(u: self ref URI): (string, string) 990e96539fSCharles.Forsyth{ 1000e96539fSCharles.Forsyth return splitl(u.userinfo, ":"); 1010e96539fSCharles.Forsyth} 1020e96539fSCharles.Forsyth 1030e96539fSCharles.ForsythURI.text(u: self ref URI): string 1040e96539fSCharles.Forsyth{ 1050e96539fSCharles.Forsyth s := ""; 1060e96539fSCharles.Forsyth if(u.scheme != nil) 1070e96539fSCharles.Forsyth s += u.scheme + ":"; 1080e96539fSCharles.Forsyth if(u.hasauthority()) 1090e96539fSCharles.Forsyth s += "//" + u.authority(); 1100e96539fSCharles.Forsyth return s + enc(u.path, "/@:") + u.query + enc1(u.fragment, "@:/?"); 1110e96539fSCharles.Forsyth} 1120e96539fSCharles.Forsyth 1130e96539fSCharles.ForsythURI.copy(u: self ref URI): ref URI 1140e96539fSCharles.Forsyth{ 1150e96539fSCharles.Forsyth return ref *u; 1160e96539fSCharles.Forsyth} 1170e96539fSCharles.Forsyth 1180e96539fSCharles.ForsythURI.pathonly(u: self ref URI): ref URI 1190e96539fSCharles.Forsyth{ 1200e96539fSCharles.Forsyth v := ref *u; 1210e96539fSCharles.Forsyth v.userinfo = nil; 1220e96539fSCharles.Forsyth v.query = nil; 1230e96539fSCharles.Forsyth v.fragment = nil; 1240e96539fSCharles.Forsyth return v; 1250e96539fSCharles.Forsyth} 1260e96539fSCharles.Forsyth 1270e96539fSCharles.ForsythURI.addbase(u: self ref URI, b: ref URI): ref URI 1280e96539fSCharles.Forsyth{ 1290e96539fSCharles.Forsyth # RFC3986 5.2.2, rearranged 1300e96539fSCharles.Forsyth r := ref *u; 1310e96539fSCharles.Forsyth if(r.scheme == nil && b != nil){ 1320e96539fSCharles.Forsyth r.scheme = b.scheme; 1330e96539fSCharles.Forsyth if(!r.hasauthority()){ 1340e96539fSCharles.Forsyth r.userinfo = b.userinfo; 1350e96539fSCharles.Forsyth r.host = b.host; 1360e96539fSCharles.Forsyth r.port = b.port; 1370e96539fSCharles.Forsyth if(r.path == nil){ 1380e96539fSCharles.Forsyth r.path = b.path; 1390e96539fSCharles.Forsyth if(r.query == nil) 1400e96539fSCharles.Forsyth r.query = b.query; 1410e96539fSCharles.Forsyth }else if(r.path[0] != '/'){ 1420e96539fSCharles.Forsyth # 5.2.3: merge paths 1430e96539fSCharles.Forsyth if(b.path == "" && b.hasauthority()) 1440e96539fSCharles.Forsyth p1 := "/"; 1450e96539fSCharles.Forsyth else 1460e96539fSCharles.Forsyth (p1, nil) = S->splitstrr(b.path, "/"); 1470e96539fSCharles.Forsyth r.path = p1 + r.path; 1480e96539fSCharles.Forsyth } 1490e96539fSCharles.Forsyth } 1500e96539fSCharles.Forsyth } 1510e96539fSCharles.Forsyth r.path = removedots(r.path); 1520e96539fSCharles.Forsyth return r; 1530e96539fSCharles.Forsyth} 1540e96539fSCharles.Forsyth 1550e96539fSCharles.ForsythURI.nodots(u: self ref URI): ref URI 1560e96539fSCharles.Forsyth{ 1570e96539fSCharles.Forsyth return u.addbase(nil); 1580e96539fSCharles.Forsyth} 1590e96539fSCharles.Forsyth 1600e96539fSCharles.ForsythURI.hasauthority(u: self ref URI): int 1610e96539fSCharles.Forsyth{ 1620e96539fSCharles.Forsyth return u.host != nil || u.userinfo != nil || u.port != nil; 1630e96539fSCharles.Forsyth} 1640e96539fSCharles.Forsyth 1650e96539fSCharles.ForsythURI.isabsolute(u: self ref URI): int 1660e96539fSCharles.Forsyth{ 1670e96539fSCharles.Forsyth return u.scheme != nil; 1680e96539fSCharles.Forsyth} 1690e96539fSCharles.Forsyth 1700e96539fSCharles.ForsythURI.authority(u: self ref URI): string 1710e96539fSCharles.Forsyth{ 1720e96539fSCharles.Forsyth s := enc(u.userinfo, ":"); 1730e96539fSCharles.Forsyth if(s != nil) 1740e96539fSCharles.Forsyth s += "@"; 1750e96539fSCharles.Forsyth if(u.host != nil){ 1760e96539fSCharles.Forsyth s += enc(u.host, "[]:"); # assumes : appears inside []; could enforce it 1770e96539fSCharles.Forsyth if(u.port != nil) 1780e96539fSCharles.Forsyth s += ":" + enc(u.port,nil); 1790e96539fSCharles.Forsyth } 1800e96539fSCharles.Forsyth return s; 1810e96539fSCharles.Forsyth} 1820e96539fSCharles.Forsyth 1830e96539fSCharles.Forsyth# 1840e96539fSCharles.Forsyth# simplified version of procedure in RFC3986 5.2.4: 1850e96539fSCharles.Forsyth# it extracts a complete segment from the input first, then analyses it 1860e96539fSCharles.Forsyth# 1870e96539fSCharles.Forsythremovedots(s: string): string 1880e96539fSCharles.Forsyth{ 1890e96539fSCharles.Forsyth if(s == nil) 1900e96539fSCharles.Forsyth return ""; 1910e96539fSCharles.Forsyth out := ""; 1920e96539fSCharles.Forsyth for(p := 0; p < len s;){ 1930e96539fSCharles.Forsyth # extract the first segment and any preceding / 1940e96539fSCharles.Forsyth q := p; 1950e96539fSCharles.Forsyth if(++p < len s){ 1960e96539fSCharles.Forsyth while(++p < len s && s[p] != '/') 1970e96539fSCharles.Forsyth {} 1980e96539fSCharles.Forsyth } 1990e96539fSCharles.Forsyth seg := s[q: p]; 2000e96539fSCharles.Forsyth if((e := p) < len s) 2010e96539fSCharles.Forsyth e++; 2020e96539fSCharles.Forsyth case s[q: e] { # includes any following / 2030e96539fSCharles.Forsyth "../" or "./" => ; 2040e96539fSCharles.Forsyth "/./" or "/." => 2050e96539fSCharles.Forsyth if(p >= len s) 2060e96539fSCharles.Forsyth s += "/"; 2070e96539fSCharles.Forsyth "/../" or "/.." => 2080e96539fSCharles.Forsyth if(p >= len s) 2090e96539fSCharles.Forsyth s += "/"; 2100e96539fSCharles.Forsyth if(out != nil){ 2110e96539fSCharles.Forsyth for(q = len out; --q > 0 && out[q] != '/';) 2120e96539fSCharles.Forsyth {} # skip 2130e96539fSCharles.Forsyth out = out[0: q]; 2140e96539fSCharles.Forsyth } 2150e96539fSCharles.Forsyth "." or ".." => ; # null effect 2160e96539fSCharles.Forsyth * => # including "/" 2170e96539fSCharles.Forsyth out += seg; 2180e96539fSCharles.Forsyth } 2190e96539fSCharles.Forsyth } 2200e96539fSCharles.Forsyth return out; 2210e96539fSCharles.Forsyth} 2220e96539fSCharles.Forsyth 2230e96539fSCharles.Forsyth# 2240e96539fSCharles.Forsyth# similar to splitstrl but trims the matched character from the result 2250e96539fSCharles.Forsyth# 2260e96539fSCharles.Forsythsplitl(s, c: string): (string, string) 2270e96539fSCharles.Forsyth{ 2280e96539fSCharles.Forsyth (a, b) := S->splitstrl(s, c); 2290e96539fSCharles.Forsyth if(b != "") 2300e96539fSCharles.Forsyth b = b[1:]; 2310e96539fSCharles.Forsyth return (a, b); 2320e96539fSCharles.Forsyth} 2330e96539fSCharles.Forsyth 2340e96539fSCharles.Forsythhex2(s: string): int 2350e96539fSCharles.Forsyth{ 2360e96539fSCharles.Forsyth n := 0; 2370e96539fSCharles.Forsyth for(i := 0; i < 2; i++){ 2380e96539fSCharles.Forsyth if(i >= len s) 2390e96539fSCharles.Forsyth return -1; 2400e96539fSCharles.Forsyth n <<= 4; 2410e96539fSCharles.Forsyth case c := s[i] { 2420e96539fSCharles.Forsyth '0' to '9' => 2430e96539fSCharles.Forsyth n += c-'0'; 2440e96539fSCharles.Forsyth 'a' to 'f' => 2450e96539fSCharles.Forsyth n += 10+(c-'a'); 2460e96539fSCharles.Forsyth 'A' to 'F' => 2470e96539fSCharles.Forsyth n += 10+(c-'A'); 2480e96539fSCharles.Forsyth * => 2490e96539fSCharles.Forsyth return -1; 2500e96539fSCharles.Forsyth } 2510e96539fSCharles.Forsyth } 2520e96539fSCharles.Forsyth return n; 2530e96539fSCharles.Forsyth} 2540e96539fSCharles.Forsyth 2550e96539fSCharles.Forsythdec(s: string): string 2560e96539fSCharles.Forsyth{ 2570e96539fSCharles.Forsyth for(i := 0;; i++){ 2580e96539fSCharles.Forsyth if(i >= len s) 2590e96539fSCharles.Forsyth return s; 2600e96539fSCharles.Forsyth if(s[i] == '%' || s[i] == 0) 2610e96539fSCharles.Forsyth break; 2620e96539fSCharles.Forsyth } 263*35f503c6Sforsyth t := s[0:i]; 264*35f503c6Sforsyth a := array[Sys->UTFmax*len s] of byte; # upper bound 265*35f503c6Sforsyth o := 0; 2660e96539fSCharles.Forsyth while(i < len s){ 267*35f503c6Sforsyth c := s[i++]; 268*35f503c6Sforsyth if(c < 16r80){ 269*35f503c6Sforsyth case c { 2700e96539fSCharles.Forsyth '%' => 2710e96539fSCharles.Forsyth if((v := hex2(s[i:])) > 0){ 2720e96539fSCharles.Forsyth c = v; 2730e96539fSCharles.Forsyth i += 2; 2740e96539fSCharles.Forsyth } 2750e96539fSCharles.Forsyth 0 => 2760e96539fSCharles.Forsyth c = ' '; # shouldn't happen 2770e96539fSCharles.Forsyth } 278*35f503c6Sforsyth a[o++] = byte c; 279*35f503c6Sforsyth }else 280*35f503c6Sforsyth o += sys->char2byte(c, a, o); # string contained Unicode 2810e96539fSCharles.Forsyth } 282*35f503c6Sforsyth return t + string a[0:o]; 2830e96539fSCharles.Forsyth} 2840e96539fSCharles.Forsyth 2850e96539fSCharles.Forsythenc1(s: string, safe: string): string 2860e96539fSCharles.Forsyth{ 2870e96539fSCharles.Forsyth if(len s > 1) 2880e96539fSCharles.Forsyth return s[0:1] + enc(s[1:], safe); 2890e96539fSCharles.Forsyth return s; 2900e96539fSCharles.Forsyth} 2910e96539fSCharles.Forsyth 2920e96539fSCharles.Forsyth# encoding depends on context (eg, &=/: not escaped in `query' string) 2930e96539fSCharles.Forsythenc(s: string, safe: string): string 2940e96539fSCharles.Forsyth{ 2950e96539fSCharles.Forsyth for(i := 0;; i++){ 2960e96539fSCharles.Forsyth if(i >= len s) 2970e96539fSCharles.Forsyth return s; # use as-is 2980e96539fSCharles.Forsyth c := s[i]; 2990e96539fSCharles.Forsyth if(c >= 16r80 || (ctype[c] & F_Esc) != byte 0 && !S->in(c, safe)) 3000e96539fSCharles.Forsyth break; 3010e96539fSCharles.Forsyth } 3020e96539fSCharles.Forsyth t := s[0: i]; 3030e96539fSCharles.Forsyth b := array of byte s[i:]; 3040e96539fSCharles.Forsyth for(i = 0; i < len b; i++){ 3050e96539fSCharles.Forsyth c := int b[i]; 3060e96539fSCharles.Forsyth if((ctype[c] & F_Esc) != byte 0 && !S->in(c, safe)) 3070e96539fSCharles.Forsyth t += sys->sprint("%%%.2X", c); 3080e96539fSCharles.Forsyth else 3090e96539fSCharles.Forsyth t[len t] = c; 3100e96539fSCharles.Forsyth } 3110e96539fSCharles.Forsyth return t; 3120e96539fSCharles.Forsyth} 3130e96539fSCharles.Forsyth 3140e96539fSCharles.ForsythURI.eq(u: self ref URI, v: ref URI): int 3150e96539fSCharles.Forsyth{ 3160e96539fSCharles.Forsyth if(v == nil) 3170e96539fSCharles.Forsyth return 0; 3180e96539fSCharles.Forsyth return u.scheme == v.scheme && u.userinfo == v.userinfo && 3190e96539fSCharles.Forsyth u.host == v.host && u.port == v.port && u.path == v.path && # path might need canon 3200e96539fSCharles.Forsyth u.query == v.query; # not fragment 3210e96539fSCharles.Forsyth} 3220e96539fSCharles.Forsyth 3230e96539fSCharles.ForsythURI.eqf(u: self ref URI, v: ref URI): int 3240e96539fSCharles.Forsyth{ 3250e96539fSCharles.Forsyth return u.eq(v) && u.fragment == v.fragment; 3260e96539fSCharles.Forsyth} 327