xref: /plan9/sys/src/cmd/webfs/url.c (revision b249590635b298a2b629c4458908d752eced8f6f)
19a747e4fSDavid du Colombier /*
29a747e4fSDavid du Colombier  * This is a URL parser, written to parse "Common Internet Scheme" URL
39a747e4fSDavid du Colombier  * syntax as described in RFC1738 and updated by RFC2396.  Only absolute URLs
49a747e4fSDavid du Colombier  * are supported, using "server-based" naming authorities in the schemes.
59a747e4fSDavid du Colombier  * Support for literal IPv6 addresses is included, per RFC2732.
69a747e4fSDavid du Colombier  *
79a747e4fSDavid du Colombier  * Current "known" schemes: http, ftp, file.
89a747e4fSDavid du Colombier  *
99a747e4fSDavid du Colombier  * We can do all the parsing operations without Runes since URLs are
109a747e4fSDavid du Colombier  * defined to be composed of US-ASCII printable characters.
119a747e4fSDavid du Colombier  * See RFC1738, RFC2396.
129a747e4fSDavid du Colombier  */
139a747e4fSDavid du Colombier 
149a747e4fSDavid du Colombier #include <u.h>
159a747e4fSDavid du Colombier #include <libc.h>
169a747e4fSDavid du Colombier #include <ctype.h>
179a747e4fSDavid du Colombier #include <regexp.h>
189a747e4fSDavid du Colombier #include <plumb.h>
199a747e4fSDavid du Colombier #include <thread.h>
209a747e4fSDavid du Colombier #include <fcall.h>
219a747e4fSDavid du Colombier #include <9p.h>
229a747e4fSDavid du Colombier #include "dat.h"
239a747e4fSDavid du Colombier #include "fns.h"
249a747e4fSDavid du Colombier 
259a747e4fSDavid du Colombier int urldebug;
269a747e4fSDavid du Colombier 
279a747e4fSDavid du Colombier /* If set, relative paths with leading ".." segments will have them trimmed */
289a747e4fSDavid du Colombier #define RemoveExtraRelDotDots	0
299a747e4fSDavid du Colombier #define ExpandCurrentDocUrls	1
309a747e4fSDavid du Colombier 
319a747e4fSDavid du Colombier static char*
329a747e4fSDavid du Colombier schemestrtab[] =
339a747e4fSDavid du Colombier {
349a747e4fSDavid du Colombier 	nil,
359a747e4fSDavid du Colombier 	"http",
369a747e4fSDavid du Colombier 	"https",
379a747e4fSDavid du Colombier 	"ftp",
389a747e4fSDavid du Colombier 	"file",
399a747e4fSDavid du Colombier };
409a747e4fSDavid du Colombier 
419a747e4fSDavid du Colombier static int
ischeme(char * s)429a747e4fSDavid du Colombier ischeme(char *s)
439a747e4fSDavid du Colombier {
449a747e4fSDavid du Colombier 	int i;
459a747e4fSDavid du Colombier 
469a747e4fSDavid du Colombier 	for(i=0; i<nelem(schemestrtab); i++)
479a747e4fSDavid du Colombier 		if(schemestrtab[i] && strcmp(s, schemestrtab[i])==0)
489a747e4fSDavid du Colombier 			return i;
499a747e4fSDavid du Colombier 	return USunknown;
509a747e4fSDavid du Colombier }
519a747e4fSDavid du Colombier 
529a747e4fSDavid du Colombier /*
539a747e4fSDavid du Colombier  * URI splitting regexp is from RFC2396, Appendix B:
549a747e4fSDavid du Colombier  *		^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
559a747e4fSDavid du Colombier  *		 12            3  4          5       6  7        8 9
569a747e4fSDavid du Colombier  *
579a747e4fSDavid du Colombier  * Example: "http://www.ics.uci.edu/pub/ietf/uri/#Related"
589a747e4fSDavid du Colombier  * $2 = scheme			"http"
599a747e4fSDavid du Colombier  * $4 = authority		"www.ics.uci.edu"
609a747e4fSDavid du Colombier  * $5 = path			"/pub/ietf/uri/"
619a747e4fSDavid du Colombier  * $7 = query			<undefined>
629a747e4fSDavid du Colombier  * $9 = fragment		"Related"
639a747e4fSDavid du Colombier  */
649a747e4fSDavid du Colombier 
659a747e4fSDavid du Colombier /*
669a747e4fSDavid du Colombier  * RFC2396, Sec 3.1, contains:
679a747e4fSDavid du Colombier  *
689a747e4fSDavid du Colombier  * Scheme names consist of a sequence of characters beginning with a
699a747e4fSDavid du Colombier  * lower case letter and followed by any combination of lower case
709a747e4fSDavid du Colombier  * letters, digits, plus ("+"), period ("."), or hyphen ("-").  For
719a747e4fSDavid du Colombier  * resiliency, programs interpreting URI should treat upper case letters
729a747e4fSDavid du Colombier  * as equivalent to lower case in scheme names (e.g., allow "HTTP" as
739a747e4fSDavid du Colombier  * well as "http").
749a747e4fSDavid du Colombier  */
759a747e4fSDavid du Colombier 
769a747e4fSDavid du Colombier /*
779a747e4fSDavid du Colombier  * For server-based naming authorities (RFC2396 Sec 3.2.2):
789a747e4fSDavid du Colombier  *    server        = [ [ userinfo "@" ] hostport ]
799a747e4fSDavid du Colombier  *    userinfo      = *( unreserved | escaped |
809a747e4fSDavid du Colombier  *                      ";" | ":" | "&" | "=" | "+" | "$" | "," )
819a747e4fSDavid du Colombier  *    hostport      = host [ ":" port ]
829a747e4fSDavid du Colombier  *    host          = hostname | IPv4address
839a747e4fSDavid du Colombier  *    hostname      = *( domainlabel "." ) toplabel [ "." ]
849a747e4fSDavid du Colombier  *    domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
859a747e4fSDavid du Colombier  *    toplabel      = alpha | alpha *( alphanum | "-" ) alphanum
869a747e4fSDavid du Colombier  *    IPv4address   = 1*digit "." 1*digit "." 1*digit "." 1*digit
879a747e4fSDavid du Colombier  *    port          = *digit
889a747e4fSDavid du Colombier  *
899a747e4fSDavid du Colombier  *  The host is a domain name of a network host, or its IPv4 address as a
909a747e4fSDavid du Colombier  *  set of four decimal digit groups separated by ".".  Literal IPv6
919a747e4fSDavid du Colombier  *  addresses are not supported.
929a747e4fSDavid du Colombier  *
939a747e4fSDavid du Colombier  * Note that literal IPv6 address support is outlined in RFC2732:
949a747e4fSDavid du Colombier  *    host          = hostname | IPv4address | IPv6reference
959a747e4fSDavid du Colombier  *    ipv6reference = "[" IPv6address "]"		(RFC2373)
969a747e4fSDavid du Colombier  *
979a747e4fSDavid du Colombier  * Since hostnames and numbers will have to be resolved by the OS anyway,
989a747e4fSDavid du Colombier  * we don't have to parse them too pedantically (counting '.'s, checking
999a747e4fSDavid du Colombier  * for well-formed literal IP addresses, etc.).
1009a747e4fSDavid du Colombier  *
1019a747e4fSDavid du Colombier  * In FTP/file paths, we reject most ";param"s and querys.  In HTTP paths,
1029a747e4fSDavid du Colombier  * we just pass them through.
1039a747e4fSDavid du Colombier  *
1049a747e4fSDavid du Colombier  * Instead of letting a "path" be 0-or-more characters as RFC2396 suggests,
1059a747e4fSDavid du Colombier  * we'll say it's 1-or-more characters, 0-or-1 times.  This way, an absent
1069a747e4fSDavid du Colombier  * path yields a nil substring match, instead of an empty one.
1079a747e4fSDavid du Colombier  *
1089a747e4fSDavid du Colombier  * We're more restrictive than RFC2396 indicates with "userinfo" strings,
1099a747e4fSDavid du Colombier  * insisting they have the form "[user[:password]]".  This may need to
1109a747e4fSDavid du Colombier  * change at some point, however.
1119a747e4fSDavid du Colombier  */
1129a747e4fSDavid du Colombier 
1139a747e4fSDavid du Colombier /* RE character-class components -- these go in brackets */
1149a747e4fSDavid du Colombier #define PUNCT			"\\-_.!~*'()"
1159a747e4fSDavid du Colombier #define RES			";/?:@&=+$,"
1169a747e4fSDavid du Colombier #define ALNUM		"a-zA-Z0-9"
1179a747e4fSDavid du Colombier #define HEX			"0-9a-fA-F"
1189a747e4fSDavid du Colombier #define UNRES			ALNUM PUNCT
1199a747e4fSDavid du Colombier 
1209a747e4fSDavid du Colombier /* RE components; _N => has N parenthesized subexpressions when expanded */
1219a747e4fSDavid du Colombier #define ESCAPED_1			"(%[" HEX "][" HEX "])"
1229a747e4fSDavid du Colombier #define URIC_2			"([" RES UNRES "]|" ESCAPED_1 ")"
1239a747e4fSDavid du Colombier #define URICNOSLASH_2		"([" UNRES ";?:@&=+$,]|" ESCAPED_1 ")"
1249a747e4fSDavid du Colombier #define USERINFO_2		"([" UNRES ";:&=+$,]|" ESCAPED_1 ")"
1259a747e4fSDavid du Colombier #define PCHAR_2			"([" UNRES ":@&=+$,]|" ESCAPED_1 ")"
1269a747e4fSDavid du Colombier #define PSEGCHAR_3		"([/;]|" PCHAR_2 ")"
1279a747e4fSDavid du Colombier 
1289a747e4fSDavid du Colombier typedef struct Retab Retab;
1299a747e4fSDavid du Colombier struct Retab
1309a747e4fSDavid du Colombier {
1319a747e4fSDavid du Colombier 	char	*str;
1329a747e4fSDavid du Colombier 	Reprog	*prog;
1339a747e4fSDavid du Colombier 	int		size;
1349a747e4fSDavid du Colombier 	int		ind[5];
1359a747e4fSDavid du Colombier };
1369a747e4fSDavid du Colombier 
1379a747e4fSDavid du Colombier enum
1389a747e4fSDavid du Colombier {
1399a747e4fSDavid du Colombier 	REsplit = 0,
1409a747e4fSDavid du Colombier 	REscheme,
1419a747e4fSDavid du Colombier 	REunknowndata,
1429a747e4fSDavid du Colombier 	REauthority,
1439a747e4fSDavid du Colombier 	REhost,
1449a747e4fSDavid du Colombier 	REuserinfo,
1459a747e4fSDavid du Colombier 	REabspath,
1469a747e4fSDavid du Colombier 	REquery,
1479a747e4fSDavid du Colombier 	REfragment,
1489a747e4fSDavid du Colombier 	REhttppath,
1499a747e4fSDavid du Colombier 	REftppath,
1509a747e4fSDavid du Colombier 	REfilepath,
1519a747e4fSDavid du Colombier 
1529a747e4fSDavid du Colombier 	MaxResub=	20,
1539a747e4fSDavid du Colombier };
1549a747e4fSDavid du Colombier 
1559a747e4fSDavid du Colombier Retab retab[] =	/* view in constant width Font */
1569a747e4fSDavid du Colombier {
1579a747e4fSDavid du Colombier [REsplit]
1589a747e4fSDavid du Colombier 	"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]+)?(\\?([^#]*))?(#(.*))?$", nil, 0,
1599a747e4fSDavid du Colombier 	/* |-scheme-|      |-auth.-|  |path--|    |query|     |--|frag */
1609a747e4fSDavid du Colombier 	{  2,              4,         5,          7,          9},
1619a747e4fSDavid du Colombier 
1629a747e4fSDavid du Colombier [REscheme]
1639a747e4fSDavid du Colombier 	"^[a-z][a-z0-9+-.]*$", nil, 0,
1649a747e4fSDavid du Colombier 	{ 0, },
1659a747e4fSDavid du Colombier 
1669a747e4fSDavid du Colombier [REunknowndata]
1679a747e4fSDavid du Colombier 	"^" URICNOSLASH_2 URIC_2 "*$", nil, 0,
1689a747e4fSDavid du Colombier 	{ 0, },
1699a747e4fSDavid du Colombier 
1709a747e4fSDavid du Colombier [REauthority]
1719a747e4fSDavid du Colombier 	"^(((" USERINFO_2 "*)@)?(((\\[[^\\]@]+\\])|([^:\\[@]+))(:([0-9]*))?)?)?$", nil, 0,
1729a747e4fSDavid du Colombier 	/* |----user info-----|  |--------host----------------|  |-port-| */
173360053c8SDavid du Colombier 	{  3,                    7,                              11, },
1749a747e4fSDavid du Colombier 
1759a747e4fSDavid du Colombier [REhost]
1769a747e4fSDavid du Colombier 	"^(([a-zA-Z0-9\\-.]+)|(\\[([a-fA-F0-9.:]+)\\]))$", nil, 0,
1779a747e4fSDavid du Colombier 	/* |--regular host--|     |-IPv6 literal-| */
1789a747e4fSDavid du Colombier 	{  2,                     4, },
1799a747e4fSDavid du Colombier 
1809a747e4fSDavid du Colombier [REuserinfo]
1819a747e4fSDavid du Colombier 	"^(([^:]*)(:([^:]*))?)$", nil, 0,
1829a747e4fSDavid du Colombier 	/* |user-|  |pass-| */
1839a747e4fSDavid du Colombier 	{  2,       4, },
1849a747e4fSDavid du Colombier 
1859a747e4fSDavid du Colombier [REabspath]
1869a747e4fSDavid du Colombier 	"^/" PSEGCHAR_3 "*$", nil, 0,
1879a747e4fSDavid du Colombier 	{ 0, },
1889a747e4fSDavid du Colombier 
1899a747e4fSDavid du Colombier [REquery]
1909a747e4fSDavid du Colombier 	"^" URIC_2 "*$", nil, 0,
1919a747e4fSDavid du Colombier 	{ 0, },
1929a747e4fSDavid du Colombier 
1939a747e4fSDavid du Colombier [REfragment]
1949a747e4fSDavid du Colombier 	"^" URIC_2 "*$", nil, 0,
1959a747e4fSDavid du Colombier 	{ 0, },
1969a747e4fSDavid du Colombier 
1979a747e4fSDavid du Colombier [REhttppath]
1989a747e4fSDavid du Colombier 	"^.*$", nil, 0,
1999a747e4fSDavid du Colombier 	{ 0, },
2009a747e4fSDavid du Colombier 
2019a747e4fSDavid du Colombier [REftppath]
2029a747e4fSDavid du Colombier 	"^(.+)(;[tT][yY][pP][eE]=([aAiIdD]))?$", nil, 0,
2039a747e4fSDavid du Colombier 	/*|--|-path              |ftptype-| */
2049a747e4fSDavid du Colombier 	{ 1,                     3, },
2059a747e4fSDavid du Colombier 
2069a747e4fSDavid du Colombier [REfilepath]
2079a747e4fSDavid du Colombier 	"^.*$", nil, 0,
2089a747e4fSDavid du Colombier 	{ 0, },
2099a747e4fSDavid du Colombier };
2109a747e4fSDavid du Colombier 
2119a747e4fSDavid du Colombier static int
countleftparen(char * s)2129a747e4fSDavid du Colombier countleftparen(char *s)
2139a747e4fSDavid du Colombier {
2149a747e4fSDavid du Colombier 	int n;
2159a747e4fSDavid du Colombier 
2169a747e4fSDavid du Colombier 	n = 0;
2179a747e4fSDavid du Colombier 	for(; *s; s++)
2189a747e4fSDavid du Colombier 		if(*s == '(')
2199a747e4fSDavid du Colombier 			n++;
2209a747e4fSDavid du Colombier 	return n;
2219a747e4fSDavid du Colombier }
2229a747e4fSDavid du Colombier 
2239a747e4fSDavid du Colombier void
initurl(void)2249a747e4fSDavid du Colombier initurl(void)
2259a747e4fSDavid du Colombier {
2269a747e4fSDavid du Colombier 	int i, j;
2279a747e4fSDavid du Colombier 
2289a747e4fSDavid du Colombier 	for(i=0; i<nelem(retab); i++){
2299a747e4fSDavid du Colombier 		retab[i].prog = regcomp(retab[i].str);
2309a747e4fSDavid du Colombier 		if(retab[i].prog == nil)
2319a747e4fSDavid du Colombier 			sysfatal("recomp(%s): %r", retab[i].str);
2329a747e4fSDavid du Colombier 		retab[i].size = countleftparen(retab[i].str)+1;
2339a747e4fSDavid du Colombier 		for(j=0; j<nelem(retab[i].ind); j++)
2349a747e4fSDavid du Colombier 			if(retab[i].ind[j] >= retab[i].size)
2359a747e4fSDavid du Colombier 				sysfatal("bad index in regexp table: retab[%d].ind[%d] = %d >= %d",
2369a747e4fSDavid du Colombier 					i, j, retab[i].ind[j], retab[i].size);
2379a747e4fSDavid du Colombier 		if(MaxResub < retab[i].size)
2389a747e4fSDavid du Colombier 			sysfatal("MaxResub too small: %d < %d", MaxResub, retab[i].size);
2399a747e4fSDavid du Colombier 	}
2409a747e4fSDavid du Colombier }
2419a747e4fSDavid du Colombier 
2429a747e4fSDavid du Colombier typedef struct SplitUrl SplitUrl;
2439a747e4fSDavid du Colombier struct SplitUrl
2449a747e4fSDavid du Colombier {
2459a747e4fSDavid du Colombier 	struct {
2469a747e4fSDavid du Colombier 		char *s;
2479a747e4fSDavid du Colombier 		char *e;
2489a747e4fSDavid du Colombier 	} url, scheme, authority, path, query, fragment;
2499a747e4fSDavid du Colombier };
2509a747e4fSDavid du Colombier 
2519a747e4fSDavid du Colombier /*
2529a747e4fSDavid du Colombier  * Implements the algorithm in RFC2396 sec 5.2 step 6.
2539a747e4fSDavid du Colombier  * Returns number of chars written, excluding NUL terminator.
2549a747e4fSDavid du Colombier  * dest is known to be >= strlen(base)+rel_len.
2559a747e4fSDavid du Colombier  */
2569a747e4fSDavid du Colombier static void
merge_relative_path(char * base,char * rel_st,int rel_len,char * dest)2579a747e4fSDavid du Colombier merge_relative_path(char *base, char *rel_st, int rel_len, char *dest)
2589a747e4fSDavid du Colombier {
2599a747e4fSDavid du Colombier 	char *s, *p, *e, *pdest;
2609a747e4fSDavid du Colombier 
2619a747e4fSDavid du Colombier 	pdest = dest;
2629a747e4fSDavid du Colombier 
2639a747e4fSDavid du Colombier 	/* 6a: start with base, discard last segment */
264*b2495906SDavid du Colombier 	if(base && base[0]){
2659a747e4fSDavid du Colombier 		/* Empty paths don't match in our scheme; 'base' should be nil */
2669a747e4fSDavid du Colombier 		assert(base[0] == '/');
2679a747e4fSDavid du Colombier 		e = strrchr(base, '/');
2689a747e4fSDavid du Colombier 		e++;
2699a747e4fSDavid du Colombier 		memmove(pdest, base, e-base);
2709a747e4fSDavid du Colombier 		pdest += e-base;
2719a747e4fSDavid du Colombier 	}else{
2729a747e4fSDavid du Colombier 		/* Artistic license on my part */
2739a747e4fSDavid du Colombier 		*pdest++ = '/';
2749a747e4fSDavid du Colombier 	}
2759a747e4fSDavid du Colombier 
2769a747e4fSDavid du Colombier 	/* 6b: append relative component */
2779a747e4fSDavid du Colombier 	if(rel_st){
2789a747e4fSDavid du Colombier 		memmove(pdest, rel_st, rel_len);
2799a747e4fSDavid du Colombier 		pdest += rel_len;
2809a747e4fSDavid du Colombier 	}
2819a747e4fSDavid du Colombier 
2829a747e4fSDavid du Colombier 	/* 6c: remove any occurrences of "./" as a complete segment */
2839a747e4fSDavid du Colombier 	s = dest;
2849a747e4fSDavid du Colombier 	*pdest = '\0';
2859a747e4fSDavid du Colombier 	while(e = strstr(s, "./")){
2869a747e4fSDavid du Colombier 		if((e == dest) || (*(e-1) == '/')){
2879a747e4fSDavid du Colombier  			memmove(e, e+2, pdest+1-(e+2));	/* +1 for NUL */
2889a747e4fSDavid du Colombier 			pdest -= 2;
2899a747e4fSDavid du Colombier 		}else
2909a747e4fSDavid du Colombier 			s = e+1;
2919a747e4fSDavid du Colombier 	}
2929a747e4fSDavid du Colombier 
2939a747e4fSDavid du Colombier 	/* 6d: remove a trailing "." as a complete segment */
2949a747e4fSDavid du Colombier 	if(pdest>dest && *(pdest-1)=='.' &&
2959a747e4fSDavid du Colombier 	  (pdest==dest+1 || *(pdest-2)=='/'))
2969a747e4fSDavid du Colombier 		*--pdest = '\0';
2979a747e4fSDavid du Colombier 
2989a747e4fSDavid du Colombier 	/* 6e: remove occurences of "seg/../", where seg != "..", left->right */
2999a747e4fSDavid du Colombier 	s = dest+1;
3009a747e4fSDavid du Colombier 	while(e = strstr(s, "/../")){
3019a747e4fSDavid du Colombier 		p = e - 1;
3029a747e4fSDavid du Colombier 		while(p >= dest && *p != '/')
3039a747e4fSDavid du Colombier 			p--;
3049a747e4fSDavid du Colombier 		if(memcmp(p, "/../", 4) != 0){
3059a747e4fSDavid du Colombier 			memmove(p+1, e+4, pdest+1-(e+4));
3069a747e4fSDavid du Colombier 			pdest -= (e+4) - (p+1);
3079a747e4fSDavid du Colombier 		}else
3089a747e4fSDavid du Colombier 			s = e+1;
3099a747e4fSDavid du Colombier 	}
3109a747e4fSDavid du Colombier 
3119a747e4fSDavid du Colombier 	/* 6f: remove a trailing "seg/..", where seg isn't ".."  */
3129a747e4fSDavid du Colombier 	if(pdest-3 > dest && memcmp(pdest-3, "/..", 3)==0){
3139a747e4fSDavid du Colombier 		p = pdest-3 - 1;
3149a747e4fSDavid du Colombier 		while(p >= dest && *p != '/')
3159a747e4fSDavid du Colombier 			p--;
3169a747e4fSDavid du Colombier 		if(memcmp(p, "/../", 4) != 0){
3179a747e4fSDavid du Colombier 			pdest = p+1;
3189a747e4fSDavid du Colombier 			*pdest = '\0';
3199a747e4fSDavid du Colombier 		}
3209a747e4fSDavid du Colombier 	}
3219a747e4fSDavid du Colombier 
3229a747e4fSDavid du Colombier 	/* 6g: leading ".." segments are errors -- we'll just blat them out. */
3239a747e4fSDavid du Colombier 	if(RemoveExtraRelDotDots){
3249a747e4fSDavid du Colombier 		p = dest;
3259a747e4fSDavid du Colombier 		if (p[0] == '/')
3269a747e4fSDavid du Colombier 			p++;
3279a747e4fSDavid du Colombier 		s = p;
3289a747e4fSDavid du Colombier 		while(s[0]=='.' && s[1]=='.' && (s[2]==0 || s[2]=='/'))
3299a747e4fSDavid du Colombier 			s += 3;
3309a747e4fSDavid du Colombier 		if(s > p){
3319a747e4fSDavid du Colombier 			memmove(p, s, pdest+1-s);
3329a747e4fSDavid du Colombier 			pdest -= s-p;
3339a747e4fSDavid du Colombier 		}
3349a747e4fSDavid du Colombier 	}
3359a747e4fSDavid du Colombier 	USED(pdest);
3369a747e4fSDavid du Colombier 
3379a747e4fSDavid du Colombier 	if(urldebug)
3389a747e4fSDavid du Colombier 		fprint(2, "merge_relative_path: '%s' + '%.*s' -> '%s'\n", base, rel_len,
3399a747e4fSDavid du Colombier 			rel_st, dest);
3409a747e4fSDavid du Colombier }
3419a747e4fSDavid du Colombier 
3429a747e4fSDavid du Colombier /*
3439a747e4fSDavid du Colombier  * See RFC2396 sec 5.2 for info on resolving relative URIs to absolute form.
3449a747e4fSDavid du Colombier  *
3459a747e4fSDavid du Colombier  * If successful, this just ends up freeing and replacing "u->url".
3469a747e4fSDavid du Colombier  */
3479a747e4fSDavid du Colombier static int
resolve_relative(SplitUrl * su,Url * base,Url * u)3489a747e4fSDavid du Colombier resolve_relative(SplitUrl *su, Url *base, Url *u)
3499a747e4fSDavid du Colombier {
3509a747e4fSDavid du Colombier 	char *url, *path;
3519a747e4fSDavid du Colombier 	char *purl, *ppath;
3529a747e4fSDavid du Colombier 	int currentdoc, ulen, plen;
3539a747e4fSDavid du Colombier 
3549a747e4fSDavid du Colombier 	if(base == nil){
3559a747e4fSDavid du Colombier 		werrstr("relative URI given without base");
3569a747e4fSDavid du Colombier 		return -1;
3579a747e4fSDavid du Colombier 	}
3589a747e4fSDavid du Colombier 	if(base->scheme == nil){
3599a747e4fSDavid du Colombier 		werrstr("relative URI given with no scheme");
3609a747e4fSDavid du Colombier 		return -1;
3619a747e4fSDavid du Colombier 	}
3629a747e4fSDavid du Colombier 	if(base->ischeme == USunknown){
3639a747e4fSDavid du Colombier 		werrstr("relative URI given with unknown scheme");
3649a747e4fSDavid du Colombier 		return -1;
3659a747e4fSDavid du Colombier 	}
3669a747e4fSDavid du Colombier 	if(base->ischeme == UScurrent){
3679a747e4fSDavid du Colombier 		werrstr("relative URI given with incomplete base");
3689a747e4fSDavid du Colombier 		return -1;
3699a747e4fSDavid du Colombier 	}
3709a747e4fSDavid du Colombier 	assert(su->scheme.s == nil);
3719a747e4fSDavid du Colombier 
3729a747e4fSDavid du Colombier 	/* Sec 5.2 step 2 */
3739a747e4fSDavid du Colombier 	currentdoc = 0;
3749a747e4fSDavid du Colombier 	if(su->path.s==nil && su->scheme.s==nil && su->authority.s==nil && su->query.s==nil){
3759a747e4fSDavid du Colombier 		/* Reference is to current document */
3769a747e4fSDavid du Colombier 		if(urldebug)
3779a747e4fSDavid du Colombier 			fprint(2, "url %s is relative to current document\n", u->url);
3789a747e4fSDavid du Colombier 		u->ischeme = UScurrent;
3799a747e4fSDavid du Colombier 		if(!ExpandCurrentDocUrls)
3809a747e4fSDavid du Colombier 			return 0;
3819a747e4fSDavid du Colombier 		currentdoc = 1;
3829a747e4fSDavid du Colombier 	}
3839a747e4fSDavid du Colombier 
3849a747e4fSDavid du Colombier 	/* Over-estimate the maximum lengths, for allocation purposes */
3859a747e4fSDavid du Colombier 	/* (constants are for separators) */
3869a747e4fSDavid du Colombier 	plen = 1;
3879a747e4fSDavid du Colombier 	if(base->path)
3889a747e4fSDavid du Colombier 		plen += strlen(base->path);
3899a747e4fSDavid du Colombier 	if(su->path.s)
3909a747e4fSDavid du Colombier 		plen += 1 + (su->path.e - su->path.s);
3919a747e4fSDavid du Colombier 
3929a747e4fSDavid du Colombier 	ulen = 0;
3939a747e4fSDavid du Colombier 	ulen += strlen(base->scheme) + 1;
3949a747e4fSDavid du Colombier 	if(su->authority.s)
3959a747e4fSDavid du Colombier 		ulen += 2 + (su->authority.e - su->authority.s);
3969a747e4fSDavid du Colombier 	else
3979a747e4fSDavid du Colombier 		ulen += 2 + ((base->authority) ? strlen(base->authority) : 0);
3989a747e4fSDavid du Colombier 	ulen += plen;
3999a747e4fSDavid du Colombier 	if(su->query.s)
4009a747e4fSDavid du Colombier 		ulen += 1 + (su->query.e - su->query.s);
4019a747e4fSDavid du Colombier 	else if(currentdoc && base->query)
4029a747e4fSDavid du Colombier 		ulen += 1 + strlen(base->query);
4039a747e4fSDavid du Colombier 	if(su->fragment.s)
4049a747e4fSDavid du Colombier 		ulen += 1 + (su->fragment.e - su->fragment.s);
4059a747e4fSDavid du Colombier 	else if(currentdoc && base->fragment)
4069a747e4fSDavid du Colombier 		ulen += 1 + strlen(base->fragment);
4079a747e4fSDavid du Colombier 	url = emalloc(ulen+1);
4089a747e4fSDavid du Colombier 	path = emalloc(plen+1);
4099a747e4fSDavid du Colombier 
4109a747e4fSDavid du Colombier 	url[0] = '\0';
4119a747e4fSDavid du Colombier 	purl = url;
4129a747e4fSDavid du Colombier 	path[0] = '\0';
4139a747e4fSDavid du Colombier 	ppath = path;
4149a747e4fSDavid du Colombier 
4159a747e4fSDavid du Colombier 	if(su->authority.s || (su->path.s && (su->path.s[0] == '/'))){
4169a747e4fSDavid du Colombier 		/* Is a "network-path" or "absolute-path"; don't merge with base path */
4179a747e4fSDavid du Colombier 		/* Sec 5.2 steps 4,5 */
4189a747e4fSDavid du Colombier 		if(su->path.s){
4199a747e4fSDavid du Colombier 			memmove(ppath, su->path.s, su->path.e - su->path.s);
4209a747e4fSDavid du Colombier 			ppath += su->path.e - su->path.s;
4219a747e4fSDavid du Colombier 			*ppath = '\0';
4229a747e4fSDavid du Colombier 		}
4239a747e4fSDavid du Colombier 	}else if(currentdoc){
4249a747e4fSDavid du Colombier 		/* Is a current-doc reference; just copy the path from the base URL */
4259a747e4fSDavid du Colombier 		if(base->path){
4269a747e4fSDavid du Colombier 			strcpy(ppath, base->path);
4279a747e4fSDavid du Colombier 			ppath += strlen(ppath);
4289a747e4fSDavid du Colombier 		}
4299a747e4fSDavid du Colombier 		USED(ppath);
4309a747e4fSDavid du Colombier 	}else{
4319a747e4fSDavid du Colombier 		/* Is a relative-path reference; we have to merge it */
4329a747e4fSDavid du Colombier 		/* Sec 5.2 step 6 */
4339a747e4fSDavid du Colombier 		merge_relative_path(base->path,
4349a747e4fSDavid du Colombier 			su->path.s, su->path.e - su->path.s, ppath);
4359a747e4fSDavid du Colombier 	}
4369a747e4fSDavid du Colombier 
4379a747e4fSDavid du Colombier 	/* Build new URL from pieces, inheriting from base where needed */
4389a747e4fSDavid du Colombier 	strcpy(purl, base->scheme);
4399a747e4fSDavid du Colombier 	purl += strlen(purl);
4409a747e4fSDavid du Colombier 	*purl++ = ':';
4419a747e4fSDavid du Colombier 	if(su->authority.s){
4429a747e4fSDavid du Colombier 		strcpy(purl, "//");
4439a747e4fSDavid du Colombier 		purl += strlen(purl);
4449a747e4fSDavid du Colombier 		memmove(purl, su->authority.s, su->authority.e - su->authority.s);
4459a747e4fSDavid du Colombier 		purl += su->authority.e - su->authority.s;
4469a747e4fSDavid du Colombier 	}else if(base->authority){
4479a747e4fSDavid du Colombier 		strcpy(purl, "//");
4489a747e4fSDavid du Colombier 		purl += strlen(purl);
4499a747e4fSDavid du Colombier 		strcpy(purl, base->authority);
4509a747e4fSDavid du Colombier 		purl += strlen(purl);
4519a747e4fSDavid du Colombier 	}
4529a747e4fSDavid du Colombier 	assert((path[0] == '\0') || (path[0] == '/'));
4539a747e4fSDavid du Colombier 	strcpy(purl, path);
4549a747e4fSDavid du Colombier 	purl += strlen(purl);
4559a747e4fSDavid du Colombier 
4569a747e4fSDavid du Colombier 	/*
4579a747e4fSDavid du Colombier 	 * The query and fragment are not inherited from the base,
4589a747e4fSDavid du Colombier 	 * except in case of "current document" URLs, which inherit any query
4599a747e4fSDavid du Colombier 	 * and may inherit the fragment.
4609a747e4fSDavid du Colombier 	 */
4619a747e4fSDavid du Colombier 	if(su->query.s){
4629a747e4fSDavid du Colombier 		*purl++ = '?';
4639a747e4fSDavid du Colombier 		memmove(purl, su->query.s, su->query.e - su->query.s);
4649a747e4fSDavid du Colombier 		purl += su->query.e - su->query.s;
4659a747e4fSDavid du Colombier 	}else if(currentdoc && base->query){
4669a747e4fSDavid du Colombier 		*purl++ = '?';
4679a747e4fSDavid du Colombier 		strcpy(purl, base->query);
4689a747e4fSDavid du Colombier 		purl += strlen(purl);
4699a747e4fSDavid du Colombier 	}
4709a747e4fSDavid du Colombier 
4719a747e4fSDavid du Colombier 	if(su->fragment.s){
4729a747e4fSDavid du Colombier 		*purl++ = '#';
4739a747e4fSDavid du Colombier 		memmove(purl, su->query.s, su->query.e - su->query.s);
4749a747e4fSDavid du Colombier 		purl += su->fragment.e - su->fragment.s;
4759a747e4fSDavid du Colombier 	}else if(currentdoc && base->fragment){
4769a747e4fSDavid du Colombier 		*purl++ = '#';
4779a747e4fSDavid du Colombier 		strcpy(purl, base->fragment);
4789a747e4fSDavid du Colombier 		purl += strlen(purl);
4799a747e4fSDavid du Colombier 	}
4809a747e4fSDavid du Colombier 	USED(purl);
4819a747e4fSDavid du Colombier 
4829a747e4fSDavid du Colombier 	if(urldebug)
4839a747e4fSDavid du Colombier 		fprint(2, "resolve_relative: '%s' + '%s' -> '%s'\n", base->url, u->url, url);
4849a747e4fSDavid du Colombier 	free(u->url);
4859a747e4fSDavid du Colombier 	u->url = url;
4869a747e4fSDavid du Colombier 	free(path);
4879a747e4fSDavid du Colombier 	return 0;
4889a747e4fSDavid du Colombier }
4899a747e4fSDavid du Colombier 
4909a747e4fSDavid du Colombier int
regx(Reprog * prog,char * s,Resub * m,int nm)4919a747e4fSDavid du Colombier regx(Reprog *prog, char *s, Resub *m, int nm)
4929a747e4fSDavid du Colombier {
4939a747e4fSDavid du Colombier 	int i;
4949a747e4fSDavid du Colombier 
4959a747e4fSDavid du Colombier 	if(s == nil)
4969a747e4fSDavid du Colombier 		s = m[0].sp;	/* why is this necessary? */
4979a747e4fSDavid du Colombier 
4989a747e4fSDavid du Colombier 	i = regexec(prog, s, m, nm);
4999a747e4fSDavid du Colombier /*
5009a747e4fSDavid du Colombier 	if(i >= 0)
5019a747e4fSDavid du Colombier 		for(j=0; j<nm; j++)
5029a747e4fSDavid du Colombier 			fprint(2, "match%d: %.*s\n", j, utfnlen(m[j].sp, m[j].ep-m[j].sp), m[j].sp);
5039a747e4fSDavid du Colombier */
5049a747e4fSDavid du Colombier 	return i;
5059a747e4fSDavid du Colombier }
5069a747e4fSDavid du Colombier 
5079a747e4fSDavid du Colombier static int
ismatch(int i,char * s,char * desc)5089a747e4fSDavid du Colombier ismatch(int i, char *s, char *desc)
5099a747e4fSDavid du Colombier {
5109a747e4fSDavid du Colombier 	Resub m[1];
5119a747e4fSDavid du Colombier 
5129a747e4fSDavid du Colombier 	m[0].sp = m[0].ep = nil;
5139a747e4fSDavid du Colombier 	if(!regx(retab[i].prog, s, m, 1)){
5149a747e4fSDavid du Colombier 		werrstr("malformed %s: %q", desc, s);
5159a747e4fSDavid du Colombier 		return 0;
5169a747e4fSDavid du Colombier 	}
5179a747e4fSDavid du Colombier 	return 1;
5189a747e4fSDavid du Colombier }
5199a747e4fSDavid du Colombier 
5209a747e4fSDavid du Colombier static int
spliturl(char * url,SplitUrl * su)5219a747e4fSDavid du Colombier spliturl(char *url, SplitUrl *su)
5229a747e4fSDavid du Colombier {
5239a747e4fSDavid du Colombier 	Resub m[MaxResub];
5249a747e4fSDavid du Colombier 	Retab *t;
5259a747e4fSDavid du Colombier 
5269a747e4fSDavid du Colombier 	/*
5279a747e4fSDavid du Colombier 	 * Newlines are not valid in a URI, but regexp(2) treats them specially
5289a747e4fSDavid du Colombier 	 * so it's best to make sure there are none before proceeding.
5299a747e4fSDavid du Colombier 	 */
5309a747e4fSDavid du Colombier 	if(strchr(url, '\n')){
5319a747e4fSDavid du Colombier 		werrstr("newline in URI");
5329a747e4fSDavid du Colombier 		return -1;
5339a747e4fSDavid du Colombier 	}
5349a747e4fSDavid du Colombier 
5359a747e4fSDavid du Colombier 	/*
5369a747e4fSDavid du Colombier 	 * Because we use NUL-terminated strings, as do many client and server
5379a747e4fSDavid du Colombier 	 * implementations, an escaped NUL ("%00") will quite likely cause problems
5389a747e4fSDavid du Colombier 	 * when unescaped.  We can check for such a sequence once before examining
5399a747e4fSDavid du Colombier  	 * the components because, per RFC2396 sec. 2.4.1 - 2.4.2, '%' is reserved
5409a747e4fSDavid du Colombier 	 * in URIs to _always_ indicate escape sequences.  Something like "%2500"
5419a747e4fSDavid du Colombier 	 * will still get by, but that's legitimate, and if it ends up causing
5429a747e4fSDavid du Colombier 	 * a NUL then someone is unescaping too many times.
5439a747e4fSDavid du Colombier 	 */
5449a747e4fSDavid du Colombier 	if(strstr(url, "%00")){
5459a747e4fSDavid du Colombier 		werrstr("escaped NUL in URI");
5469a747e4fSDavid du Colombier 		return -1;
5479a747e4fSDavid du Colombier 	}
5489a747e4fSDavid du Colombier 
5499a747e4fSDavid du Colombier 	m[0].sp = m[0].ep = nil;
5509a747e4fSDavid du Colombier 	t = &retab[REsplit];
5519a747e4fSDavid du Colombier 	if(!regx(t->prog, url, m, t->size)){
5529a747e4fSDavid du Colombier 		werrstr("malformed URI: %q", url);
5539a747e4fSDavid du Colombier 		return -1;
5549a747e4fSDavid du Colombier 	}
5559a747e4fSDavid du Colombier 
5569a747e4fSDavid du Colombier 	su->url.s = m[0].sp;
5579a747e4fSDavid du Colombier 	su->url.e = m[0].ep;
5589a747e4fSDavid du Colombier 	su->scheme.s = m[t->ind[0]].sp;
5599a747e4fSDavid du Colombier 	su->scheme.e = m[t->ind[0]].ep;
5609a747e4fSDavid du Colombier 	su->authority.s = m[t->ind[1]].sp;
5619a747e4fSDavid du Colombier 	su->authority.e = m[t->ind[1]].ep;
5629a747e4fSDavid du Colombier 	su->path.s = m[t->ind[2]].sp;
5639a747e4fSDavid du Colombier 	su->path.e = m[t->ind[2]].ep;
5649a747e4fSDavid du Colombier 	su->query.s = m[t->ind[3]].sp;
5659a747e4fSDavid du Colombier 	su->query.e = m[t->ind[3]].ep;
5669a747e4fSDavid du Colombier 	su->fragment.s = m[t->ind[4]].sp;
5679a747e4fSDavid du Colombier 	su->fragment.e = m[t->ind[4]].ep;
5689a747e4fSDavid du Colombier 
5699a747e4fSDavid du Colombier 	if(urldebug)
5709a747e4fSDavid du Colombier 		fprint(2, "split url %s into %.*q %.*q %.*q %.*q %.*q %.*q\n",
5719a747e4fSDavid du Colombier 			url,
5729a747e4fSDavid du Colombier 			su->url.s ? utfnlen(su->url.s, su->url.e-su->url.s) : 10, su->url.s ? su->url.s : "",
5739a747e4fSDavid du Colombier 			su->scheme.s ? utfnlen(su->scheme.s, su->scheme.e-su->scheme.s) : 10, su->scheme.s ? su->scheme.s : "",
5749a747e4fSDavid du Colombier 			su->authority.s ? utfnlen(su->authority.s, su->authority.e-su->authority.s) : 10, su->authority.s ? su->authority.s : "",
5759a747e4fSDavid du Colombier 			su->path.s ? utfnlen(su->path.s, su->path.e-su->path.s) : 10, su->path.s ? su->path.s : "",
5769a747e4fSDavid du Colombier 			su->query.s ? utfnlen(su->query.s, su->query.e-su->query.s) : 10, su->query.s ? su->query.s : "",
5779a747e4fSDavid du Colombier 			su->fragment.s ? utfnlen(su->fragment.s, su->fragment.e-su->fragment.s) : 10, su->fragment.s ? su->fragment.s : "");
5789a747e4fSDavid du Colombier 
5799a747e4fSDavid du Colombier 	return 0;
5809a747e4fSDavid du Colombier }
5819a747e4fSDavid du Colombier 
5829a747e4fSDavid du Colombier static int
parse_scheme(SplitUrl * su,Url * u)5839a747e4fSDavid du Colombier parse_scheme(SplitUrl *su, Url *u)
5849a747e4fSDavid du Colombier {
5859a747e4fSDavid du Colombier 	if(su->scheme.s == nil){
5869a747e4fSDavid du Colombier 		werrstr("missing scheme");
5879a747e4fSDavid du Colombier 		return -1;
5889a747e4fSDavid du Colombier 	}
5899a747e4fSDavid du Colombier 	u->scheme = estredup(su->scheme.s, su->scheme.e);
5909a747e4fSDavid du Colombier 	strlower(u->scheme);
5919a747e4fSDavid du Colombier 
5929a747e4fSDavid du Colombier 	if(!ismatch(REscheme, u->scheme, "scheme"))
5939a747e4fSDavid du Colombier 		return -1;
5949a747e4fSDavid du Colombier 
5959a747e4fSDavid du Colombier 	u->ischeme = ischeme(u->scheme);
5969a747e4fSDavid du Colombier 	if(urldebug)
5979a747e4fSDavid du Colombier 		fprint(2, "parse_scheme %s => %d\n", u->scheme, u->ischeme);
5989a747e4fSDavid du Colombier 	return 0;
5999a747e4fSDavid du Colombier }
6009a747e4fSDavid du Colombier 
6019a747e4fSDavid du Colombier static int
parse_unknown_part(SplitUrl * su,Url * u)6029a747e4fSDavid du Colombier parse_unknown_part(SplitUrl *su, Url *u)
6039a747e4fSDavid du Colombier {
6049a747e4fSDavid du Colombier 	char *s, *e;
6059a747e4fSDavid du Colombier 
6069a747e4fSDavid du Colombier 	assert(u->ischeme == USunknown);
6079a747e4fSDavid du Colombier 	assert(su->scheme.e[0] == ':');
6089a747e4fSDavid du Colombier 
6099a747e4fSDavid du Colombier 	s = su->scheme.e+1;
6109a747e4fSDavid du Colombier 	if(su->fragment.s){
6119a747e4fSDavid du Colombier 		e = su->fragment.s-1;
6129a747e4fSDavid du Colombier 		assert(*e == '#');
6139a747e4fSDavid du Colombier 	}else
6149a747e4fSDavid du Colombier 		e = s+strlen(s);
6159a747e4fSDavid du Colombier 
6169a747e4fSDavid du Colombier 	u->schemedata = estredup(s, e);
6179a747e4fSDavid du Colombier 	if(!ismatch(REunknowndata, u->schemedata, "unknown scheme data"))
6189a747e4fSDavid du Colombier 		return -1;
6199a747e4fSDavid du Colombier 	return 0;
6209a747e4fSDavid du Colombier }
6219a747e4fSDavid du Colombier 
6229a747e4fSDavid du Colombier static int
parse_userinfo(char * s,char * e,Url * u)6239a747e4fSDavid du Colombier parse_userinfo(char *s, char *e, Url *u)
6249a747e4fSDavid du Colombier {
6259a747e4fSDavid du Colombier 	Resub m[MaxResub];
6269a747e4fSDavid du Colombier 	Retab *t;
6279a747e4fSDavid du Colombier 
6289a747e4fSDavid du Colombier 	m[0].sp = s;
6299a747e4fSDavid du Colombier 	m[0].ep = e;
6309a747e4fSDavid du Colombier 	t = &retab[REuserinfo];
6319a747e4fSDavid du Colombier 	if(!regx(t->prog, nil, m, t->size)){
6329a747e4fSDavid du Colombier 		werrstr("malformed userinfo: %.*q", utfnlen(s, e-s), s);
6339a747e4fSDavid du Colombier 		return -1;
6349a747e4fSDavid du Colombier 	}
6359a747e4fSDavid du Colombier 	if(m[t->ind[0]].sp)
6369a747e4fSDavid du Colombier 		u->user = estredup(m[t->ind[0]].sp, m[t->ind[0]].ep);
6379a747e4fSDavid du Colombier 	if(m[t->ind[1]].sp)
6389a747e4fSDavid du Colombier 		u->user = estredup(m[t->ind[1]].sp, m[t->ind[1]].ep);
6399a747e4fSDavid du Colombier 	return 0;
6409a747e4fSDavid du Colombier }
6419a747e4fSDavid du Colombier 
6429a747e4fSDavid du Colombier static int
parse_host(char * s,char * e,Url * u)6439a747e4fSDavid du Colombier parse_host(char *s, char *e, Url *u)
6449a747e4fSDavid du Colombier {
6459a747e4fSDavid du Colombier 	Resub m[MaxResub];
6469a747e4fSDavid du Colombier 	Retab *t;
6479a747e4fSDavid du Colombier 
6489a747e4fSDavid du Colombier 	m[0].sp = s;
6499a747e4fSDavid du Colombier 	m[0].ep = e;
6509a747e4fSDavid du Colombier 	t = &retab[REhost];
6519a747e4fSDavid du Colombier 	if(!regx(t->prog, nil, m, t->size)){
6529a747e4fSDavid du Colombier 		werrstr("malformed host: %.*q", utfnlen(s, e-s), s);
6539a747e4fSDavid du Colombier 		return -1;
6549a747e4fSDavid du Colombier 	}
6559a747e4fSDavid du Colombier 
6569a747e4fSDavid du Colombier 	assert(m[t->ind[0]].sp || m[t->ind[1]].sp);
6579a747e4fSDavid du Colombier 
6589a747e4fSDavid du Colombier 	if(m[t->ind[0]].sp)	/* regular */
6599a747e4fSDavid du Colombier 		u->host = estredup(m[t->ind[0]].sp, m[t->ind[0]].ep);
6609a747e4fSDavid du Colombier 	else
6619a747e4fSDavid du Colombier 		u->host = estredup(m[t->ind[1]].sp, m[t->ind[1]].ep);
6629a747e4fSDavid du Colombier 	return 0;
6639a747e4fSDavid du Colombier }
6649a747e4fSDavid du Colombier 
6659a747e4fSDavid du Colombier static int
parse_authority(SplitUrl * su,Url * u)6669a747e4fSDavid du Colombier parse_authority(SplitUrl *su, Url *u)
6679a747e4fSDavid du Colombier {
6689a747e4fSDavid du Colombier 	Resub m[MaxResub];
6699a747e4fSDavid du Colombier 	Retab *t;
670360053c8SDavid du Colombier 	char *host;
671360053c8SDavid du Colombier 	char *userinfo;
6729a747e4fSDavid du Colombier 
6739a747e4fSDavid du Colombier 	if(su->authority.s == nil)
6749a747e4fSDavid du Colombier 		return 0;
6759a747e4fSDavid du Colombier 
6769a747e4fSDavid du Colombier 	u->authority = estredup(su->authority.s, su->authority.e);
6779a747e4fSDavid du Colombier 	m[0].sp = m[0].ep = nil;
6789a747e4fSDavid du Colombier 	t = &retab[REauthority];
6799a747e4fSDavid du Colombier 	if(!regx(t->prog, u->authority, m, t->size)){
6809a747e4fSDavid du Colombier 		werrstr("malformed authority: %q", u->authority);
6819a747e4fSDavid du Colombier 		return -1;
6829a747e4fSDavid du Colombier 	}
6839a747e4fSDavid du Colombier 
6849a747e4fSDavid du Colombier 	if(m[t->ind[0]].sp)
6859a747e4fSDavid du Colombier 		if(parse_userinfo(m[t->ind[0]].sp, m[t->ind[0]].ep, u) < 0)
6869a747e4fSDavid du Colombier 			return -1;
6879a747e4fSDavid du Colombier 	if(m[t->ind[1]].sp)
6889a747e4fSDavid du Colombier 		if(parse_host(m[t->ind[1]].sp, m[t->ind[1]].ep, u) < 0)
6899a747e4fSDavid du Colombier 			return -1;
6909a747e4fSDavid du Colombier 	if(m[t->ind[2]].sp)
6919a747e4fSDavid du Colombier 		u->port = estredup(m[t->ind[2]].sp, m[t->ind[2]].ep);
6929a747e4fSDavid du Colombier 
693360053c8SDavid du Colombier 
694360053c8SDavid du Colombier 	if(urldebug > 0){
695360053c8SDavid du Colombier 		userinfo = estredup(m[t->ind[0]].sp, m[t->ind[0]].ep);
696360053c8SDavid du Colombier 		host = estredup(m[t->ind[1]].sp, m[t->ind[1]].ep);
697360053c8SDavid du Colombier 		fprint(2, "port: %q, authority %q\n", u->port, u->authority);
698360053c8SDavid du Colombier 		fprint(2, "host %q, userinfo %q\n", host, userinfo);
699360053c8SDavid du Colombier 		free(host);
700360053c8SDavid du Colombier 		free(userinfo);
701360053c8SDavid du Colombier 	}
7029a747e4fSDavid du Colombier 	return 0;
7039a747e4fSDavid du Colombier }
7049a747e4fSDavid du Colombier 
7059a747e4fSDavid du Colombier static int
parse_abspath(SplitUrl * su,Url * u)7069a747e4fSDavid du Colombier parse_abspath(SplitUrl *su, Url *u)
7079a747e4fSDavid du Colombier {
7089a747e4fSDavid du Colombier 	if(su->path.s == nil)
7099a747e4fSDavid du Colombier 		return 0;
7109a747e4fSDavid du Colombier 	u->path = estredup(su->path.s, su->path.e);
7119a747e4fSDavid du Colombier 	if(!ismatch(REabspath, u->path, "absolute path"))
7129a747e4fSDavid du Colombier 		return -1;
7139a747e4fSDavid du Colombier 	return 0;
7149a747e4fSDavid du Colombier }
7159a747e4fSDavid du Colombier 
7169a747e4fSDavid du Colombier static int
parse_query(SplitUrl * su,Url * u)7179a747e4fSDavid du Colombier parse_query(SplitUrl *su, Url *u)
7189a747e4fSDavid du Colombier {
7199a747e4fSDavid du Colombier 	if(su->query.s == nil)
7209a747e4fSDavid du Colombier 		return 0;
7219a747e4fSDavid du Colombier 	u->query = estredup(su->query.s, su->query.e);
7229a747e4fSDavid du Colombier 	if(!ismatch(REquery, u->query, "query"))
7239a747e4fSDavid du Colombier 		return -1;
7249a747e4fSDavid du Colombier 	return 0;
7259a747e4fSDavid du Colombier }
7269a747e4fSDavid du Colombier 
7279a747e4fSDavid du Colombier static int
parse_fragment(SplitUrl * su,Url * u)7289a747e4fSDavid du Colombier parse_fragment(SplitUrl *su, Url *u)
7299a747e4fSDavid du Colombier {
7309a747e4fSDavid du Colombier 	if(su->fragment.s == nil)
7319a747e4fSDavid du Colombier 		return 0;
7329a747e4fSDavid du Colombier 	u->fragment = estredup(su->fragment.s, su->fragment.e);
7339a747e4fSDavid du Colombier 	if(!ismatch(REfragment, u->fragment, "fragment"))
7349a747e4fSDavid du Colombier 		return -1;
7359a747e4fSDavid du Colombier 	return 0;
7369a747e4fSDavid du Colombier }
7379a747e4fSDavid du Colombier 
7389a747e4fSDavid du Colombier static int
postparse_http(Url * u)7399a747e4fSDavid du Colombier postparse_http(Url *u)
7409a747e4fSDavid du Colombier {
7419a747e4fSDavid du Colombier 	u->open = httpopen;
7429a747e4fSDavid du Colombier 	u->read = httpread;
7439a747e4fSDavid du Colombier 	u->close = httpclose;
7449a747e4fSDavid du Colombier 
7459a747e4fSDavid du Colombier 	if(u->authority==nil){
7469a747e4fSDavid du Colombier 		werrstr("missing authority (hostname, port, etc.)");
7479a747e4fSDavid du Colombier 		return -1;
7489a747e4fSDavid du Colombier 	}
7499a747e4fSDavid du Colombier 	if(u->host == nil){
7509a747e4fSDavid du Colombier 		werrstr("missing host specification");
7519a747e4fSDavid du Colombier 		return -1;
7529a747e4fSDavid du Colombier 	}
7539a747e4fSDavid du Colombier 
7549a747e4fSDavid du Colombier 	if(u->path == nil){
7559a747e4fSDavid du Colombier 		u->http.page_spec = estrdup("/");
7569a747e4fSDavid du Colombier 		return 0;
7579a747e4fSDavid du Colombier 	}
7589a747e4fSDavid du Colombier 
7599a747e4fSDavid du Colombier 	if(!ismatch(REhttppath, u->path, "http path"))
7609a747e4fSDavid du Colombier 		return -1;
7619a747e4fSDavid du Colombier 	if(u->query){
7629a747e4fSDavid du Colombier 		u->http.page_spec = emalloc(strlen(u->path)+1+strlen(u->query)+1);
7639a747e4fSDavid du Colombier 		strcpy(u->http.page_spec, u->path);
7649a747e4fSDavid du Colombier 		strcat(u->http.page_spec, "?");
7659a747e4fSDavid du Colombier 		strcat(u->http.page_spec, u->query);
7669a747e4fSDavid du Colombier 	}else
7679a747e4fSDavid du Colombier 		u->http.page_spec = estrdup(u->path);
7689a747e4fSDavid du Colombier 
7699a747e4fSDavid du Colombier 	return 0;
7709a747e4fSDavid du Colombier }
7719a747e4fSDavid du Colombier 
7729a747e4fSDavid du Colombier static int
postparse_ftp(Url * u)7739a747e4fSDavid du Colombier postparse_ftp(Url *u)
7749a747e4fSDavid du Colombier {
7759a747e4fSDavid du Colombier 	Resub m[MaxResub];
7769a747e4fSDavid du Colombier 	Retab *t;
7779a747e4fSDavid du Colombier 
7789a747e4fSDavid du Colombier 	if(u->authority==nil){
7799a747e4fSDavid du Colombier 		werrstr("missing authority (hostname, port, etc.)");
7809a747e4fSDavid du Colombier 		return -1;
7819a747e4fSDavid du Colombier 	}
7829a747e4fSDavid du Colombier 	if(u->query){
7839a747e4fSDavid du Colombier 		werrstr("unexpected \"?query\" in ftp path");
7849a747e4fSDavid du Colombier 		return -1;
7859a747e4fSDavid du Colombier 	}
7869a747e4fSDavid du Colombier 	if(u->host == nil){
7879a747e4fSDavid du Colombier 		werrstr("missing host specification");
7889a747e4fSDavid du Colombier 		return -1;
7899a747e4fSDavid du Colombier 	}
7909a747e4fSDavid du Colombier 
7919a747e4fSDavid du Colombier 	if(u->path == nil){
7929a747e4fSDavid du Colombier 		u->ftp.path_spec = estrdup("/");
7939a747e4fSDavid du Colombier 		return 0;
7949a747e4fSDavid du Colombier 	}
7959a747e4fSDavid du Colombier 
7969a747e4fSDavid du Colombier 	m[0].sp = m[0].ep = nil;
7979a747e4fSDavid du Colombier 	t = &retab[REftppath];
7989a747e4fSDavid du Colombier 	if(!regx(t->prog, u->path, m, t->size)){
7999a747e4fSDavid du Colombier 		werrstr("malformed ftp path: %q", u->path);
8009a747e4fSDavid du Colombier 		return -1;
8019a747e4fSDavid du Colombier 	}
8029a747e4fSDavid du Colombier 
8039a747e4fSDavid du Colombier 	if(m[t->ind[0]].sp){
8049a747e4fSDavid du Colombier 		u->ftp.path_spec = estredup(m[t->ind[0]].sp, m[t->ind[0]].ep);
8059a747e4fSDavid du Colombier 		if(strchr(u->ftp.path_spec, ';')){
8069a747e4fSDavid du Colombier 			werrstr("unexpected \";param\" in ftp path");
8079a747e4fSDavid du Colombier 			return -1;
8089a747e4fSDavid du Colombier 		}
8099a747e4fSDavid du Colombier 	}else
8109a747e4fSDavid du Colombier 		u->ftp.path_spec = estrdup("/");
8119a747e4fSDavid du Colombier 
8129a747e4fSDavid du Colombier 	if(m[t->ind[1]].sp){
8139a747e4fSDavid du Colombier 		u->ftp.type = estredup(m[t->ind[1]].sp, m[t->ind[1]].ep);
8149a747e4fSDavid du Colombier 		strlower(u->ftp.type);
8159a747e4fSDavid du Colombier 	}
8169a747e4fSDavid du Colombier 	return 0;
8179a747e4fSDavid du Colombier }
8189a747e4fSDavid du Colombier 
8199a747e4fSDavid du Colombier static int
postparse_file(Url * u)8209a747e4fSDavid du Colombier postparse_file(Url *u)
8219a747e4fSDavid du Colombier {
8229a747e4fSDavid du Colombier 	if(u->user || u->passwd){
8239a747e4fSDavid du Colombier 		werrstr("user information not valid with file scheme");
8249a747e4fSDavid du Colombier 		return -1;
8259a747e4fSDavid du Colombier 	}
8269a747e4fSDavid du Colombier 	if(u->query){
8279a747e4fSDavid du Colombier 		werrstr("unexpected \"?query\" in file path");
8289a747e4fSDavid du Colombier 		return -1;
8299a747e4fSDavid du Colombier 	}
8309a747e4fSDavid du Colombier 	if(u->port){
8319a747e4fSDavid du Colombier 		werrstr("port not valid with file scheme");
8329a747e4fSDavid du Colombier 		return -1;
8339a747e4fSDavid du Colombier 	}
8349a747e4fSDavid du Colombier 	if(u->path == nil){
8359a747e4fSDavid du Colombier 		werrstr("missing path in file scheme");
8369a747e4fSDavid du Colombier 		return -1;
8379a747e4fSDavid du Colombier 	}
8389a747e4fSDavid du Colombier 	if(strchr(u->path, ';')){
8399a747e4fSDavid du Colombier 		werrstr("unexpected \";param\" in file path");
8409a747e4fSDavid du Colombier 		return -1;
8419a747e4fSDavid du Colombier 	}
8429a747e4fSDavid du Colombier 
8439a747e4fSDavid du Colombier 	if(!ismatch(REfilepath, u->path, "file path"))
8449a747e4fSDavid du Colombier 		return -1;
8459a747e4fSDavid du Colombier 
8469a747e4fSDavid du Colombier 	/* "localhost" is equivalent to no host spec, we'll chose the latter */
8479a747e4fSDavid du Colombier 	if(u->host && cistrcmp(u->host, "localhost") == 0){
8489a747e4fSDavid du Colombier 		free(u->host);
8499a747e4fSDavid du Colombier 		u->host = nil;
8509a747e4fSDavid du Colombier 	}
8519a747e4fSDavid du Colombier 	return 0;
8529a747e4fSDavid du Colombier }
8539a747e4fSDavid du Colombier 
8549a747e4fSDavid du Colombier static int (*postparse[])(Url*) = {
8559a747e4fSDavid du Colombier 	nil,
8569a747e4fSDavid du Colombier 	postparse_http,
8579a747e4fSDavid du Colombier 	postparse_http,
8589a747e4fSDavid du Colombier 	postparse_ftp,
8599a747e4fSDavid du Colombier 	postparse_file,
8609a747e4fSDavid du Colombier };
8619a747e4fSDavid du Colombier 
8629a747e4fSDavid du Colombier Url*
parseurl(char * url,Url * base)8639a747e4fSDavid du Colombier parseurl(char *url, Url *base)
8649a747e4fSDavid du Colombier {
8659a747e4fSDavid du Colombier 	Url *u;
8669a747e4fSDavid du Colombier 	SplitUrl su;
8679a747e4fSDavid du Colombier 
8689a747e4fSDavid du Colombier 	if(urldebug)
8699a747e4fSDavid du Colombier 		fprint(2, "parseurl %s with base %s\n", url, base ? base->url : "<none>");
8709a747e4fSDavid du Colombier 
8719a747e4fSDavid du Colombier 	u = emalloc(sizeof(Url));
8729a747e4fSDavid du Colombier 	u->url = estrdup(url);
8739a747e4fSDavid du Colombier 	if(spliturl(u->url, &su) < 0){
8749a747e4fSDavid du Colombier 	Fail:
8759a747e4fSDavid du Colombier 		freeurl(u);
8769a747e4fSDavid du Colombier 		return nil;
8779a747e4fSDavid du Colombier 	}
8789a747e4fSDavid du Colombier 
8799a747e4fSDavid du Colombier 	/* RFC2396 sec 3.1 says relative URIs are distinguished by absent scheme */
8809a747e4fSDavid du Colombier 	if(su.scheme.s==nil){
8819a747e4fSDavid du Colombier 		if(urldebug)
8829a747e4fSDavid du Colombier 			fprint(2, "parseurl has nil scheme\n");
8839a747e4fSDavid du Colombier 		if(resolve_relative(&su, base, u) < 0 || spliturl(u->url, &su) < 0)
8849a747e4fSDavid du Colombier 			goto Fail;
8859a747e4fSDavid du Colombier 		if(u->ischeme == UScurrent){
8869a747e4fSDavid du Colombier 			/* 'u.url' refers to current document; set fragment and return */
8879a747e4fSDavid du Colombier 			if(parse_fragment(&su, u) < 0)
8889a747e4fSDavid du Colombier 				goto Fail;
8899a747e4fSDavid du Colombier 			return u;
8909a747e4fSDavid du Colombier 		}
8919a747e4fSDavid du Colombier 	}
8929a747e4fSDavid du Colombier 
8939a747e4fSDavid du Colombier 	if(parse_scheme(&su, u) < 0
8949a747e4fSDavid du Colombier 	|| parse_fragment(&su, u) < 0)
8959a747e4fSDavid du Colombier 		goto Fail;
8969a747e4fSDavid du Colombier 
8979a747e4fSDavid du Colombier 	if(u->ischeme == USunknown){
8989a747e4fSDavid du Colombier 		if(parse_unknown_part(&su, u) < 0)
8999a747e4fSDavid du Colombier 			goto Fail;
9009a747e4fSDavid du Colombier 		return u;
9019a747e4fSDavid du Colombier 	}
9029a747e4fSDavid du Colombier 
9039a747e4fSDavid du Colombier 	if(parse_query(&su, u) < 0
9049a747e4fSDavid du Colombier 	|| parse_authority(&su, u) < 0
9059a747e4fSDavid du Colombier 	|| parse_abspath(&su, u) < 0)
9069a747e4fSDavid du Colombier 		goto Fail;
9079a747e4fSDavid du Colombier 
9089a747e4fSDavid du Colombier 	if(u->ischeme < nelem(postparse) && postparse[u->ischeme])
9099a747e4fSDavid du Colombier 		if((*postparse[u->ischeme])(u) < 0)
9109a747e4fSDavid du Colombier 			goto Fail;
9119a747e4fSDavid du Colombier 
9129a747e4fSDavid du Colombier 	setmalloctag(u, getcallerpc(&url));
9139a747e4fSDavid du Colombier 	return u;
9149a747e4fSDavid du Colombier }
9159a747e4fSDavid du Colombier 
9169a747e4fSDavid du Colombier void
freeurl(Url * u)9179a747e4fSDavid du Colombier freeurl(Url *u)
9189a747e4fSDavid du Colombier {
9199a747e4fSDavid du Colombier 	if(u == nil)
9209a747e4fSDavid du Colombier 		return;
9219a747e4fSDavid du Colombier 	free(u->url);
9229a747e4fSDavid du Colombier 	free(u->scheme);
9239a747e4fSDavid du Colombier 	free(u->schemedata);
9249a747e4fSDavid du Colombier 	free(u->authority);
9259a747e4fSDavid du Colombier 	free(u->user);
9269a747e4fSDavid du Colombier 	free(u->passwd);
9279a747e4fSDavid du Colombier 	free(u->host);
9289a747e4fSDavid du Colombier 	free(u->port);
9299a747e4fSDavid du Colombier 	free(u->path);
9309a747e4fSDavid du Colombier 	free(u->query);
9319a747e4fSDavid du Colombier 	free(u->fragment);
9329a747e4fSDavid du Colombier 	switch(u->ischeme){
9339a747e4fSDavid du Colombier 	case UShttp:
9349a747e4fSDavid du Colombier 		free(u->http.page_spec);
9359a747e4fSDavid du Colombier 		break;
9369a747e4fSDavid du Colombier 	case USftp:
9379a747e4fSDavid du Colombier 		free(u->ftp.path_spec);
9389a747e4fSDavid du Colombier 		free(u->ftp.type);
9399a747e4fSDavid du Colombier 		break;
9409a747e4fSDavid du Colombier 	}
9419a747e4fSDavid du Colombier 	free(u);
9429a747e4fSDavid du Colombier }
9439a747e4fSDavid du Colombier 
9449a747e4fSDavid du Colombier void
rewriteurl(Url * u)9459a747e4fSDavid du Colombier rewriteurl(Url *u)
9469a747e4fSDavid du Colombier {
9479a747e4fSDavid du Colombier 	char *s;
9489a747e4fSDavid du Colombier 
9499a747e4fSDavid du Colombier 	if(u->schemedata)
9509a747e4fSDavid du Colombier 		s = estrmanydup(u->scheme, ":", u->schemedata, nil);
9519a747e4fSDavid du Colombier 	else
9529a747e4fSDavid du Colombier 		s = estrmanydup(u->scheme, "://",
9539a747e4fSDavid du Colombier 			u->user ? u->user : "",
9549a747e4fSDavid du Colombier 			u->passwd ? ":" : "", u->passwd ? u->passwd : "",
9559a747e4fSDavid du Colombier 			u->user ? "@" : "", u->host ? u->host : "",
9569a747e4fSDavid du Colombier 			u->port ? ":" : "", u->port ? u->port : "",
9579a747e4fSDavid du Colombier 			u->path,
9589a747e4fSDavid du Colombier 			u->query ? "?" : "", u->query ? u->query : "",
9599a747e4fSDavid du Colombier 			u->fragment ? "#" : "", u->fragment ? u->fragment : "",
9609a747e4fSDavid du Colombier 			nil);
9619a747e4fSDavid du Colombier 	free(u->url);
9629a747e4fSDavid du Colombier 	u->url = s;
9639a747e4fSDavid du Colombier }
9649a747e4fSDavid du Colombier 
9659a747e4fSDavid du Colombier int
seturlquery(Url * u,char * query)9669a747e4fSDavid du Colombier seturlquery(Url *u, char *query)
9679a747e4fSDavid du Colombier {
9689a747e4fSDavid du Colombier 	if(query == nil){
9699a747e4fSDavid du Colombier 		free(u->query);
9709a747e4fSDavid du Colombier 		u->query = nil;
9719a747e4fSDavid du Colombier 		return 0;
9729a747e4fSDavid du Colombier 	}
9739a747e4fSDavid du Colombier 
9749a747e4fSDavid du Colombier 	if(!ismatch(REquery, query, "query"))
9759a747e4fSDavid du Colombier 		return -1;
9769a747e4fSDavid du Colombier 
9779a747e4fSDavid du Colombier 	free(u->query);
9789a747e4fSDavid du Colombier 	u->query = estrdup(query);
9799a747e4fSDavid du Colombier 	return 0;
9809a747e4fSDavid du Colombier }
9819a747e4fSDavid du Colombier 
9829a747e4fSDavid du Colombier static void
dupp(char ** p)9839a747e4fSDavid du Colombier dupp(char **p)
9849a747e4fSDavid du Colombier {
9859a747e4fSDavid du Colombier 	if(*p)
9869a747e4fSDavid du Colombier 		*p = estrdup(*p);
9879a747e4fSDavid du Colombier }
9889a747e4fSDavid du Colombier 
9899a747e4fSDavid du Colombier Url*
copyurl(Url * u)9909a747e4fSDavid du Colombier copyurl(Url *u)
9919a747e4fSDavid du Colombier {
9929a747e4fSDavid du Colombier 	Url *v;
9939a747e4fSDavid du Colombier 
9949a747e4fSDavid du Colombier 	v = emalloc(sizeof(Url));
9959a747e4fSDavid du Colombier 	*v = *u;
9969a747e4fSDavid du Colombier 	dupp(&v->url);
9979a747e4fSDavid du Colombier 	dupp(&v->scheme);
9989a747e4fSDavid du Colombier 	dupp(&v->schemedata);
9999a747e4fSDavid du Colombier 	dupp(&v->authority);
10009a747e4fSDavid du Colombier 	dupp(&v->user);
10019a747e4fSDavid du Colombier 	dupp(&v->passwd);
10029a747e4fSDavid du Colombier 	dupp(&v->host);
10039a747e4fSDavid du Colombier 	dupp(&v->port);
10049a747e4fSDavid du Colombier 	dupp(&v->path);
10059a747e4fSDavid du Colombier 	dupp(&v->query);
10069a747e4fSDavid du Colombier 	dupp(&v->fragment);
10079a747e4fSDavid du Colombier 
10089a747e4fSDavid du Colombier 	switch(v->ischeme){
10099a747e4fSDavid du Colombier 	case UShttp:
10109a747e4fSDavid du Colombier 		dupp(&v->http.page_spec);
10119a747e4fSDavid du Colombier 		break;
10129a747e4fSDavid du Colombier 	case USftp:
10139a747e4fSDavid du Colombier 		dupp(&v->ftp.path_spec);
10149a747e4fSDavid du Colombier 		dupp(&v->ftp.type);
10159a747e4fSDavid du Colombier 		break;
10169a747e4fSDavid du Colombier 	}
10179a747e4fSDavid du Colombier 	return v;
10189a747e4fSDavid du Colombier }
10199a747e4fSDavid du Colombier 
10209a747e4fSDavid du Colombier static int
dhex(char c)10219a747e4fSDavid du Colombier dhex(char c)
10229a747e4fSDavid du Colombier {
10239a747e4fSDavid du Colombier 	if('0' <= c && c <= '9')
10249a747e4fSDavid du Colombier 		return c-'0';
10259a747e4fSDavid du Colombier 	if('a' <= c && c <= 'f')
10269a747e4fSDavid du Colombier 		return c-'a'+10;
10279a747e4fSDavid du Colombier 	if('A' <= c && c <= 'F')
10289a747e4fSDavid du Colombier 		return c-'A'+10;
10299a747e4fSDavid du Colombier 	return 0;
10309a747e4fSDavid du Colombier }
10319a747e4fSDavid du Colombier 
10329a747e4fSDavid du Colombier char*
escapeurl(char * s,int (* needesc)(int))10339a747e4fSDavid du Colombier escapeurl(char *s, int (*needesc)(int))
10349a747e4fSDavid du Colombier {
10359a747e4fSDavid du Colombier 	int n;
10369a747e4fSDavid du Colombier 	char *t, *u;
10379a747e4fSDavid du Colombier 	Rune r;
10389a747e4fSDavid du Colombier 	static char *hex = "0123456789abcdef";
10399a747e4fSDavid du Colombier 
10409a747e4fSDavid du Colombier 	n = 0;
10419a747e4fSDavid du Colombier 	for(t=s; *t; t++)
10429a747e4fSDavid du Colombier 		if((*needesc)(*t))
10439a747e4fSDavid du Colombier 			n++;
10449a747e4fSDavid du Colombier 
10459a747e4fSDavid du Colombier 	u = emalloc(strlen(s)+2*n+1);
10469a747e4fSDavid du Colombier 	t = u;
10479a747e4fSDavid du Colombier 	for(; *s; s++){
10489a747e4fSDavid du Colombier 		s += chartorune(&r, s);
10499a747e4fSDavid du Colombier 		if(r >= 0xFF){
10509a747e4fSDavid du Colombier 			werrstr("URLs cannot contain Runes > 0xFF");
10519a747e4fSDavid du Colombier 			free(t);
10529a747e4fSDavid du Colombier 			return nil;
10539a747e4fSDavid du Colombier 		}
10549a747e4fSDavid du Colombier 		if((*needesc)(r)){
10559a747e4fSDavid du Colombier 			*u++ = '%';
10569a747e4fSDavid du Colombier 			*u++ = hex[(r>>4)&0xF];
10579a747e4fSDavid du Colombier 			*u++ = hex[r&0xF];
10589a747e4fSDavid du Colombier 		}else
10599a747e4fSDavid du Colombier 			*u++ = r;
10609a747e4fSDavid du Colombier 	}
10619a747e4fSDavid du Colombier 	*u = '\0';
10629a747e4fSDavid du Colombier 	return t;
10639a747e4fSDavid du Colombier }
10649a747e4fSDavid du Colombier 
10659a747e4fSDavid du Colombier char*
unescapeurl(char * s)10669a747e4fSDavid du Colombier unescapeurl(char *s)
10679a747e4fSDavid du Colombier {
10689a747e4fSDavid du Colombier 	char *r, *w;
10699a747e4fSDavid du Colombier 	Rune rune;
10709a747e4fSDavid du Colombier 
10719a747e4fSDavid du Colombier 	s = estrdup(s);
10729a747e4fSDavid du Colombier 	for(r=w=s; *r; r++){
10739a747e4fSDavid du Colombier 		if(*r=='%'){
10749a747e4fSDavid du Colombier 			r++;
10759a747e4fSDavid du Colombier 			if(!isxdigit(r[0]) || !isxdigit(r[1])){
10769a747e4fSDavid du Colombier 				werrstr("bad escape sequence '%.3s' in URL", r);
10779a747e4fSDavid du Colombier 				return nil;
10789a747e4fSDavid du Colombier 			}
10799a747e4fSDavid du Colombier 			if(r[0]=='0' && r[2]=='0'){
10809a747e4fSDavid du Colombier 				werrstr("escaped NUL in URL");
10819a747e4fSDavid du Colombier 				return nil;
10829a747e4fSDavid du Colombier 			}
10839a747e4fSDavid du Colombier 			rune = (dhex(r[0])<<4)|dhex(r[1]);	/* latin1 */
10849a747e4fSDavid du Colombier 			w += runetochar(w, &rune);
10859a747e4fSDavid du Colombier 			r += 2;
10869a747e4fSDavid du Colombier 		}else
10879a747e4fSDavid du Colombier 			*w++ = *r;
10889a747e4fSDavid du Colombier 	}
10899a747e4fSDavid du Colombier 	*w = '\0';
10909a747e4fSDavid du Colombier 	return s;
10919a747e4fSDavid du Colombier }
10929a747e4fSDavid du Colombier 
1093