xref: /inferno-os/appl/lib/w3c/uris.b (revision 35f503c642e9dd127a2b989e4e12a10691cba3d4)
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