<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf8">
<title>/usr/web/sources/contrib/lejatorn/url.c - Plan 9 from Bell Labs</title>
<!-- THIS FILE IS AUTOMATICALLY GENERATED. -->
<!-- EDIT sources.tr INSTEAD. -->
</meta>
</head>
<body>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"><a href="/plan9/">Plan 9 from Bell Labs</a>&rsquo;s /usr/web/sources/contrib/lejatorn/url.c</span></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
Copyright © 2009 Alcatel-Lucent.<br />
Distributed under the
<a href="/plan9/license.html">Lucent Public License version 1.02</a>.
<br />
<a href="/plan9/download.html">Download the Plan 9 distribution.</a>
</font>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<table width="100%" cellspacing=0 border=0><tr><td align="center">
<table cellspacing=0 cellpadding=5 bgcolor="#eeeeff"><tr><td align="left">
<pre>
<!-- END HEADER -->
/*
 * This is a URL parser, written to parse "Common Internet Scheme" URL
 * syntax as described in RFC1738 and updated by RFC2396.  Only absolute URLs 
 * are supported, using "server-based" naming authorities in the schemes.
 * Support for literal IPv6 addresses is included, per RFC2732.
 *
 * Current "known" schemes: http, ftp, file.
 *
 * We can do all the parsing operations without Runes since URLs are
 * defined to be composed of US-ASCII printable characters.
 * See RFC1738, RFC2396.
 */

#include &lt;u.h&gt;
#include &lt;libc.h&gt;
#include &lt;ctype.h&gt;
#include &lt;regexp.h&gt;
#include &lt;plumb.h&gt;
#include &lt;thread.h&gt;
#include &lt;fcall.h&gt;
#include &lt;9p.h&gt;
#include "dat.h"
#include "fns.h"

int urldebug;

/* If set, relative paths with leading ".." segments will have them trimmed */
#define RemoveExtraRelDotDots	0
#define ExpandCurrentDocUrls	1

static char*
schemestrtab[] =
{
	nil,
	"http",
	"https",
	"ftp",
	"file",
};

static int
ischeme(char *s)
{
	int i;

	for(i=0; i&lt;nelem(schemestrtab); i++)
		if(schemestrtab[i] &amp;&amp; strcmp(s, schemestrtab[i])==0)
			return i;
	return USunknown;
}

/*
 * URI splitting regexp is from RFC2396, Appendix B: 
 *		^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
 *		 12            3  4          5       6  7        8 9
 *
 * Example: "http://www.ics.uci.edu/pub/ietf/uri/#Related"
 * $2 = scheme			"http"
 * $4 = authority		"www.ics.uci.edu"
 * $5 = path			"/pub/ietf/uri/"
 * $7 = query			&lt;undefined&gt;
 * $9 = fragment		"Related"
 */

/*
 * RFC2396, Sec 3.1, contains:
 *
 * Scheme names consist of a sequence of characters beginning with a
 * lower case letter and followed by any combination of lower case
 * letters, digits, plus ("+"), period ("."), or hyphen ("-").  For
 * resiliency, programs interpreting URI should treat upper case letters
 * as equivalent to lower case in scheme names (e.g., allow "HTTP" as
 * well as "http").
 */

/*
 * For server-based naming authorities (RFC2396 Sec 3.2.2):
 *    server        = [ [ userinfo "@" ] hostport ]
 *    userinfo      = *( unreserved | escaped |
 *                      ";" | ":" | "&amp;" | "=" | "+" | "$" | "," )
 *    hostport      = host [ ":" port ]
 *    host          = hostname | IPv4address
 *    hostname      = *( domainlabel "." ) toplabel [ "." ]
 *    domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
 *    toplabel      = alpha | alpha *( alphanum | "-" ) alphanum
 *    IPv4address   = 1*digit "." 1*digit "." 1*digit "." 1*digit
 *    port          = *digit
 *
 *  The host is a domain name of a network host, or its IPv4 address as a
 *  set of four decimal digit groups separated by ".".  Literal IPv6
 *  addresses are not supported.
 *
 * Note that literal IPv6 address support is outlined in RFC2732:
 *    host          = hostname | IPv4address | IPv6reference
 *    ipv6reference = "[" IPv6address "]"		(RFC2373)
 *
 * Since hostnames and numbers will have to be resolved by the OS anyway,
 * we don't have to parse them too pedantically (counting '.'s, checking 
 * for well-formed literal IP addresses, etc.).
 *
 * In FTP/file paths, we reject most ";param"s and querys.  In HTTP paths,
 * we just pass them through.
 *
 * Instead of letting a "path" be 0-or-more characters as RFC2396 suggests, 
 * we'll say it's 1-or-more characters, 0-or-1 times.  This way, an absent
 * path yields a nil substring match, instead of an empty one.
 *
 * We're more restrictive than RFC2396 indicates with "userinfo" strings,
 * insisting they have the form "[user[:password]]".  This may need to
 * change at some point, however.
 */

/* RE character-class components -- these go in brackets */
#define PUNCT			"\\-_.!~*'()"
#define RES			";/?:@&amp;=+$,"
#define ALNUM		"a-zA-Z0-9"
#define HEX			"0-9a-fA-F"
#define UNRES			ALNUM PUNCT

/* RE components; _N =&gt; has N parenthesized subexpressions when expanded */
#define ESCAPED_1			"(%[" HEX "][" HEX "])"
#define URIC_2			"([" RES UNRES "]|" ESCAPED_1 ")"
#define URICNOSLASH_2		"([" UNRES ";?:@&amp;=+$,]|" ESCAPED_1 ")"
#define USERINFO_2		"([" UNRES ";:&amp;=+$,]|" ESCAPED_1 ")"
#define PCHAR_2			"([" UNRES ":@&amp;=+$,]|" ESCAPED_1 ")"
#define PSEGCHAR_3		"([/;]|" PCHAR_2 ")"

typedef struct Retab Retab;
struct Retab
{
	char	*str;
	Reprog	*prog;
	int		size;
	int		ind[5];
};

enum
{
	REsplit = 0,
	REscheme,
	REunknowndata,
	REauthority,
	REhost,
	REuserinfo,
	REabspath,
	REquery,
	REfragment,
	REhttppath,
	REftppath,
	REfilepath,

	MaxResub=	20,
};

Retab retab[] =	/* view in constant width Font */
{
[REsplit]
	"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]+)?(\\?([^#]*))?(#(.*))?$", nil, 0,
	/* |-scheme-|      |-auth.-|  |path--|    |query|     |--|frag */
	{  2,              4,         5,          7,          9},

[REscheme]
	"^[a-z][a-z0-9+-.]*$", nil, 0,
	{ 0, },

[REunknowndata]
	"^" URICNOSLASH_2 URIC_2 "*$", nil, 0,
	{ 0, },

[REauthority]
	"^(((" USERINFO_2 "*)@)?(((\\[[^\\]@]+\\])|([^:\\[@]+))(:([0-9]*))?)?)?$", nil, 0,
	/* |----user info-----|  |--------host----------------|  |-port-| */
	{  3,                    7,                              11, },

[REhost]
	"^(([a-zA-Z0-9\\-.]+)|(\\[([a-fA-F0-9.:]+)\\]))$", nil, 0,
	/* |--regular host--|     |-IPv6 literal-| */
	{  2,                     4, },

[REuserinfo]
	"^(([^:]*)(:([^:]*))?)$", nil, 0,
	/* |user-|  |pass-| */
	{  2,       4, },

[REabspath]
	"^/" PSEGCHAR_3 "*$", nil, 0,
	{ 0, },

[REquery]
	"^" URIC_2 "*$", nil, 0,
	{ 0, },

[REfragment]
	"^" URIC_2 "*$", nil, 0,
	{ 0, },

[REhttppath]
	"^.*$", nil, 0,
	{ 0, },

[REftppath]
	"^(.+)(;[tT][yY][pP][eE]=([aAiIdD]))?$", nil, 0,
	/*|--|-path              |ftptype-| */
	{ 1,                     3, }, 

[REfilepath]
	"^.*$", nil, 0,
	{ 0, },
};

static int
countleftparen(char *s)
{
	int n;

	n = 0;
	for(; *s; s++)
		if(*s == '(')
			n++;
	return n;
}

void
initurl(void)
{
	int i, j;

	for(i=0; i&lt;nelem(retab); i++){
		retab[i].prog = regcomp(retab[i].str);
		if(retab[i].prog == nil)
			sysfatal("recomp(%s): %r", retab[i].str);
		retab[i].size = countleftparen(retab[i].str)+1;
		for(j=0; j&lt;nelem(retab[i].ind); j++)
			if(retab[i].ind[j] &gt;= retab[i].size)
				sysfatal("bad index in regexp table: retab[%d].ind[%d] = %d &gt;= %d",
					i, j, retab[i].ind[j], retab[i].size);
		if(MaxResub &lt; retab[i].size)
			sysfatal("MaxResub too small: %d &lt; %d", MaxResub, retab[i].size);
	}
}

typedef struct SplitUrl SplitUrl;
struct SplitUrl
{
	struct {
		char *s;
		char *e;
	} url, scheme, authority, path, query, fragment;
};

/*
 * Implements the algorithm in RFC2396 sec 5.2 step 6.
 * Returns number of chars written, excluding NUL terminator.
 * dest is known to be &gt;= strlen(base)+rel_len.
 */
static void
merge_relative_path(char *base, char *rel_st, int rel_len, char *dest)
{
	char *s, *p, *e, *pdest;

	pdest = dest;

	/* 6a: start with base, discard last segment */
	if(base){
		/* Empty paths don't match in our scheme; 'base' should be nil */
		assert(base[0] == '/');
		e = strrchr(base, '/');
		e++;
		memmove(pdest, base, e-base);
		pdest += e-base;
	}else{
		/* Artistic license on my part */
		*pdest++ = '/';
	}

	/* 6b: append relative component */
	if(rel_st){
		memmove(pdest, rel_st, rel_len);
		pdest += rel_len;
	}

	/* 6c: remove any occurrences of "./" as a complete segment */
	s = dest;
	*pdest = '\0';
	while(e = strstr(s, "./")){
		if((e == dest) || (*(e-1) == '/')){
 			memmove(e, e+2, pdest+1-(e+2));	/* +1 for NUL */
			pdest -= 2;
		}else
			s = e+1;
	}

	/* 6d: remove a trailing "." as a complete segment */
	if(pdest&gt;dest &amp;&amp; *(pdest-1)=='.' &amp;&amp; 
	  (pdest==dest+1 || *(pdest-2)=='/'))
		*--pdest = '\0';

	/* 6e: remove occurences of "seg/../", where seg != "..", left-&gt;right */
	s = dest+1;
	while(e = strstr(s, "/../")){
		p = e - 1;
		while(p &gt;= dest &amp;&amp; *p != '/')
			p--;
		if(memcmp(p, "/../", 4) != 0){
			memmove(p+1, e+4, pdest+1-(e+4));
			pdest -= (e+4) - (p+1);
		}else
			s = e+1;
	}

	/* 6f: remove a trailing "seg/..", where seg isn't ".."  */
	if(pdest-3 &gt; dest &amp;&amp; memcmp(pdest-3, "/..", 3)==0){
		p = pdest-3 - 1;
		while(p &gt;= dest &amp;&amp; *p != '/')
			p--;
		if(memcmp(p, "/../", 4) != 0){
			pdest = p+1;
			*pdest = '\0';
		}
	}

	/* 6g: leading ".." segments are errors -- we'll just blat them out. */
	if(RemoveExtraRelDotDots){
		p = dest;
		if (p[0] == '/')
			p++;
		s = p;
		while(s[0]=='.' &amp;&amp; s[1]=='.' &amp;&amp; (s[2]==0 || s[2]=='/'))
			s += 3;
		if(s &gt; p){
			memmove(p, s, pdest+1-s);
			pdest -= s-p;
		}
	}
	USED(pdest);

	if(urldebug)
		fprint(2, "merge_relative_path: '%s' + '%.*s' -&gt; '%s'\n", base, rel_len, 
			rel_st, dest);
}

/*
 * See RFC2396 sec 5.2 for info on resolving relative URIs to absolute form.
 *
 * If successful, this just ends up freeing and replacing "u-&gt;url".
 */
static int
resolve_relative(SplitUrl *su, Url *base, Url *u)
{
	char *url, *path;
	char *purl, *ppath;
	int currentdoc, ulen, plen;

	if(base == nil){
		werrstr("relative URI given without base");
		return -1;
	}
	if(base-&gt;scheme == nil){
		werrstr("relative URI given with no scheme");
		return -1;
	}
	if(base-&gt;ischeme == USunknown){
		werrstr("relative URI given with unknown scheme");
		return -1;
	}
	if(base-&gt;ischeme == UScurrent){
		werrstr("relative URI given with incomplete base");
		return -1;
	}
	assert(su-&gt;scheme.s == nil);

	/* Sec 5.2 step 2 */
	currentdoc = 0;
	if(su-&gt;path.s==nil &amp;&amp; su-&gt;scheme.s==nil &amp;&amp; su-&gt;authority.s==nil &amp;&amp; su-&gt;query.s==nil){
		/* Reference is to current document */
		if(urldebug)
			fprint(2, "url %s is relative to current document\n", u-&gt;url);
		u-&gt;ischeme = UScurrent;
		if(!ExpandCurrentDocUrls)
			return 0;
		currentdoc = 1;
	}
	
	/* Over-estimate the maximum lengths, for allocation purposes */
	/* (constants are for separators) */
	plen = 1;
	if(base-&gt;path)
		plen += strlen(base-&gt;path);
	if(su-&gt;path.s)
		plen += 1 + (su-&gt;path.e - su-&gt;path.s);

	ulen = 0;
	ulen += strlen(base-&gt;scheme) + 1;
	if(su-&gt;authority.s)
		ulen += 2 + (su-&gt;authority.e - su-&gt;authority.s);
	else
		ulen += 2 + ((base-&gt;authority) ? strlen(base-&gt;authority) : 0);
	ulen += plen;
	if(su-&gt;query.s)
		ulen += 1 + (su-&gt;query.e - su-&gt;query.s);
	else if(currentdoc &amp;&amp; base-&gt;query)
		ulen += 1 + strlen(base-&gt;query);
	if(su-&gt;fragment.s)
		ulen += 1 + (su-&gt;fragment.e - su-&gt;fragment.s);
	else if(currentdoc &amp;&amp; base-&gt;fragment)
		ulen += 1 + strlen(base-&gt;fragment);
	url = emalloc(ulen+1);
	path = emalloc(plen+1);

	url[0] = '\0';
	purl = url;
	path[0] = '\0';
	ppath = path;

	if(su-&gt;authority.s || (su-&gt;path.s &amp;&amp; (su-&gt;path.s[0] == '/'))){
		/* Is a "network-path" or "absolute-path"; don't merge with base path */
		/* Sec 5.2 steps 4,5 */
		if(su-&gt;path.s){
			memmove(ppath, su-&gt;path.s, su-&gt;path.e - su-&gt;path.s);
			ppath += su-&gt;path.e - su-&gt;path.s;
			*ppath = '\0';
		}
	}else if(currentdoc){
		/* Is a current-doc reference; just copy the path from the base URL */
		if(base-&gt;path){
			strcpy(ppath, base-&gt;path);
			ppath += strlen(ppath);
		}
		USED(ppath);
	}else{
		/* Is a relative-path reference; we have to merge it */
		/* Sec 5.2 step 6 */
		merge_relative_path(base-&gt;path,
			su-&gt;path.s, su-&gt;path.e - su-&gt;path.s, ppath);
	}

	/* Build new URL from pieces, inheriting from base where needed */
	strcpy(purl, base-&gt;scheme);
	purl += strlen(purl);
	*purl++ = ':';
	if(su-&gt;authority.s){
		strcpy(purl, "//");
		purl += strlen(purl);
		memmove(purl, su-&gt;authority.s, su-&gt;authority.e - su-&gt;authority.s);
		purl += su-&gt;authority.e - su-&gt;authority.s;
	}else if(base-&gt;authority){
		strcpy(purl, "//");
		purl += strlen(purl);
		strcpy(purl, base-&gt;authority);
		purl += strlen(purl);
	}
	assert((path[0] == '\0') || (path[0] == '/'));
	strcpy(purl, path);
	purl += strlen(purl);

	/*
	 * The query and fragment are not inherited from the base,
	 * except in case of "current document" URLs, which inherit any query
	 * and may inherit the fragment.
	 */
	if(su-&gt;query.s){
		*purl++ = '?';
		memmove(purl, su-&gt;query.s, su-&gt;query.e - su-&gt;query.s);
		purl += su-&gt;query.e - su-&gt;query.s;
	}else if(currentdoc &amp;&amp; base-&gt;query){
		*purl++ = '?';
		strcpy(purl, base-&gt;query);
		purl += strlen(purl);
	}

	if(su-&gt;fragment.s){
		*purl++ = '#';
		memmove(purl, su-&gt;query.s, su-&gt;query.e - su-&gt;query.s);
		purl += su-&gt;fragment.e - su-&gt;fragment.s;
	}else if(currentdoc &amp;&amp; base-&gt;fragment){
		*purl++ = '#';
		strcpy(purl, base-&gt;fragment);
		purl += strlen(purl);
	}
	USED(purl);

	if(urldebug)
		fprint(2, "resolve_relative: '%s' + '%s' -&gt; '%s'\n", base-&gt;url, u-&gt;url, url);
	free(u-&gt;url);
	u-&gt;url = url;
	free(path);
	return 0;
}

int
regx(Reprog *prog, char *s, Resub *m, int nm)
{
	int i;

	if(s == nil)
		s = m[0].sp;	/* why is this necessary? */

	i = regexec(prog, s, m, nm);
/*
	if(i &gt;= 0)
		for(j=0; j&lt;nm; j++)
			fprint(2, "match%d: %.*s\n", j, utfnlen(m[j].sp, m[j].ep-m[j].sp), m[j].sp);
*/
	return i;
}

static int
ismatch(int i, char *s, char *desc)
{
	Resub m[1];

	m[0].sp = m[0].ep = nil;
	if(!regx(retab[i].prog, s, m, 1)){
		werrstr("malformed %s: %q", desc, s);
		return 0;
	}
	return 1;
}

static int
spliturl(char *url, SplitUrl *su)
{
	Resub m[MaxResub];
	Retab *t;

	/*
	 * Newlines are not valid in a URI, but regexp(2) treats them specially 
	 * so it's best to make sure there are none before proceeding.
	 */
	if(strchr(url, '\n')){
		werrstr("newline in URI");
		return -1;
	}

	/*
	 * Because we use NUL-terminated strings, as do many client and server
	 * implementations, an escaped NUL ("%00") will quite likely cause problems
	 * when unescaped.  We can check for such a sequence once before examining
 	 * the components because, per RFC2396 sec. 2.4.1 - 2.4.2, '%' is reserved
	 * in URIs to _always_ indicate escape sequences.  Something like "%2500"
	 * will still get by, but that's legitimate, and if it ends up causing
	 * a NUL then someone is unescaping too many times.
	 */
	/*
	if(strstr(url, "%00")){
		werrstr("escaped NUL in URI");
		return -1;
	}
	*/

	m[0].sp = m[0].ep = nil;
	t = &amp;retab[REsplit];
	if(!regx(t-&gt;prog, url, m, t-&gt;size)){
		werrstr("malformed URI: %q", url);
		return -1;
	}

	su-&gt;url.s = m[0].sp;
	su-&gt;url.e = m[0].ep;
	su-&gt;scheme.s = m[t-&gt;ind[0]].sp;
	su-&gt;scheme.e = m[t-&gt;ind[0]].ep;
	su-&gt;authority.s = m[t-&gt;ind[1]].sp;
	su-&gt;authority.e = m[t-&gt;ind[1]].ep;
	su-&gt;path.s = m[t-&gt;ind[2]].sp;
	su-&gt;path.e = m[t-&gt;ind[2]].ep;
	su-&gt;query.s = m[t-&gt;ind[3]].sp;
	su-&gt;query.e = m[t-&gt;ind[3]].ep;
	su-&gt;fragment.s = m[t-&gt;ind[4]].sp;
	su-&gt;fragment.e = m[t-&gt;ind[4]].ep;

	if(urldebug)
		fprint(2, "split url %s into %.*q %.*q %.*q %.*q %.*q %.*q\n",
			url,
			su-&gt;url.s ? utfnlen(su-&gt;url.s, su-&gt;url.e-su-&gt;url.s) : 10, su-&gt;url.s ? su-&gt;url.s : "",
			su-&gt;scheme.s ? utfnlen(su-&gt;scheme.s, su-&gt;scheme.e-su-&gt;scheme.s) : 10, su-&gt;scheme.s ? su-&gt;scheme.s : "",
			su-&gt;authority.s ? utfnlen(su-&gt;authority.s, su-&gt;authority.e-su-&gt;authority.s) : 10, su-&gt;authority.s ? su-&gt;authority.s : "",
			su-&gt;path.s ? utfnlen(su-&gt;path.s, su-&gt;path.e-su-&gt;path.s) : 10, su-&gt;path.s ? su-&gt;path.s : "",
			su-&gt;query.s ? utfnlen(su-&gt;query.s, su-&gt;query.e-su-&gt;query.s) : 10, su-&gt;query.s ? su-&gt;query.s : "",
			su-&gt;fragment.s ? utfnlen(su-&gt;fragment.s, su-&gt;fragment.e-su-&gt;fragment.s) : 10, su-&gt;fragment.s ? su-&gt;fragment.s : "");

	return 0;
}

static int
parse_scheme(SplitUrl *su, Url *u)
{
	if(su-&gt;scheme.s == nil){
		werrstr("missing scheme");
		return -1;
	}
	u-&gt;scheme = estredup(su-&gt;scheme.s, su-&gt;scheme.e);
	strlower(u-&gt;scheme);

	if(!ismatch(REscheme, u-&gt;scheme, "scheme"))
		return -1;

	u-&gt;ischeme = ischeme(u-&gt;scheme);
	if(urldebug)
		fprint(2, "parse_scheme %s =&gt; %d\n", u-&gt;scheme, u-&gt;ischeme);
	return 0;
}

static int
parse_unknown_part(SplitUrl *su, Url *u)
{
	char *s, *e;

	assert(u-&gt;ischeme == USunknown);
	assert(su-&gt;scheme.e[0] == ':');

	s = su-&gt;scheme.e+1;
	if(su-&gt;fragment.s){
		e = su-&gt;fragment.s-1;
		assert(*e == '#');
	}else
		e = s+strlen(s);

	u-&gt;schemedata = estredup(s, e);
	if(!ismatch(REunknowndata, u-&gt;schemedata, "unknown scheme data"))
		return -1;
	return 0;
}

static int
parse_userinfo(char *s, char *e, Url *u)
{
	Resub m[MaxResub];
	Retab *t;

	m[0].sp = s;
	m[0].ep = e;
	t = &amp;retab[REuserinfo];
	if(!regx(t-&gt;prog, nil, m, t-&gt;size)){
		werrstr("malformed userinfo: %.*q", utfnlen(s, e-s), s);
		return -1;
	}
	if(m[t-&gt;ind[0]].sp)
		u-&gt;user = estredup(m[t-&gt;ind[0]].sp, m[t-&gt;ind[0]].ep);
	if(m[t-&gt;ind[1]].sp)
		u-&gt;user = estredup(m[t-&gt;ind[1]].sp, m[t-&gt;ind[1]].ep);
	return 0;
}

static int
parse_host(char *s, char *e, Url *u)
{
	Resub m[MaxResub];
	Retab *t;

	m[0].sp = s;
	m[0].ep = e;
	t = &amp;retab[REhost];
	if(!regx(t-&gt;prog, nil, m, t-&gt;size)){
		werrstr("malformed host: %.*q", utfnlen(s, e-s), s);
		return -1;
	}

	assert(m[t-&gt;ind[0]].sp || m[t-&gt;ind[1]].sp);

	if(m[t-&gt;ind[0]].sp)	/* regular */
		u-&gt;host = estredup(m[t-&gt;ind[0]].sp, m[t-&gt;ind[0]].ep);
	else
		u-&gt;host = estredup(m[t-&gt;ind[1]].sp, m[t-&gt;ind[1]].ep);
	return 0;
}

static int
parse_authority(SplitUrl *su, Url *u)
{
	Resub m[MaxResub];
	Retab *t;
	char *host;
	char *userinfo;

	if(su-&gt;authority.s == nil)
		return 0;

	u-&gt;authority = estredup(su-&gt;authority.s, su-&gt;authority.e);
	m[0].sp = m[0].ep = nil;
	t = &amp;retab[REauthority];
	if(!regx(t-&gt;prog, u-&gt;authority, m, t-&gt;size)){
		werrstr("malformed authority: %q", u-&gt;authority);
		return -1;
	}

	if(m[t-&gt;ind[0]].sp)
		if(parse_userinfo(m[t-&gt;ind[0]].sp, m[t-&gt;ind[0]].ep, u) &lt; 0)
			return -1;
	if(m[t-&gt;ind[1]].sp)
		if(parse_host(m[t-&gt;ind[1]].sp, m[t-&gt;ind[1]].ep, u) &lt; 0)
			return -1;
	if(m[t-&gt;ind[2]].sp)
		u-&gt;port = estredup(m[t-&gt;ind[2]].sp, m[t-&gt;ind[2]].ep);


	if(urldebug &gt; 0){
		userinfo = estredup(m[t-&gt;ind[0]].sp, m[t-&gt;ind[0]].ep); 
		host = estredup(m[t-&gt;ind[1]].sp, m[t-&gt;ind[1]].ep);
		fprint(2, "port: %q, authority %q\n", u-&gt;port, u-&gt;authority);
		fprint(2, "host %q, userinfo %q\n", host, userinfo);
		free(host);
		free(userinfo);
	}
	return 0;
}

static int
parse_abspath(SplitUrl *su, Url *u)
{
	if(su-&gt;path.s == nil)
		return 0;
	u-&gt;path = estredup(su-&gt;path.s, su-&gt;path.e);
	if(!ismatch(REabspath, u-&gt;path, "absolute path"))
		return -1;
	return 0;
}

static int
parse_query(SplitUrl *su, Url *u)
{
	if(su-&gt;query.s == nil)
		return 0;
	u-&gt;query = estredup(su-&gt;query.s, su-&gt;query.e);
	if(!ismatch(REquery, u-&gt;query, "query"))
		return -1;
	return 0;
}

static int
parse_fragment(SplitUrl *su, Url *u)
{
	if(su-&gt;fragment.s == nil)
		return 0;
	u-&gt;fragment = estredup(su-&gt;fragment.s, su-&gt;fragment.e);
	if(!ismatch(REfragment, u-&gt;fragment, "fragment"))
		return -1;
	return 0;
}

static int
postparse_http(Url *u)
{
	u-&gt;open = httpopen;
	u-&gt;read = httpread;
	u-&gt;close = httpclose;

	if(u-&gt;authority==nil){
		werrstr("missing authority (hostname, port, etc.)");
		return -1;
	}
	if(u-&gt;host == nil){
		werrstr("missing host specification");
		return -1;
	}

	if(u-&gt;path == nil){
		u-&gt;http.page_spec = estrdup("/");
		return 0;
	}

	if(!ismatch(REhttppath, u-&gt;path, "http path"))
		return -1;
	if(u-&gt;query){
		u-&gt;http.page_spec = emalloc(strlen(u-&gt;path)+1+strlen(u-&gt;query)+1);
		strcpy(u-&gt;http.page_spec, u-&gt;path);
		strcat(u-&gt;http.page_spec, "?");
		strcat(u-&gt;http.page_spec, u-&gt;query);
	}else
		u-&gt;http.page_spec = estrdup(u-&gt;path);

	return 0;
}

static int
postparse_ftp(Url *u)
{
	Resub m[MaxResub];
	Retab *t;

	if(u-&gt;authority==nil){
		werrstr("missing authority (hostname, port, etc.)");
		return -1;
	}
	if(u-&gt;query){
		werrstr("unexpected \"?query\" in ftp path");
		return -1;
	}
	if(u-&gt;host == nil){
		werrstr("missing host specification");
		return -1;
	}

	if(u-&gt;path == nil){
		u-&gt;ftp.path_spec = estrdup("/");
		return 0;
	}

	m[0].sp = m[0].ep = nil;
	t = &amp;retab[REftppath];
	if(!regx(t-&gt;prog, u-&gt;path, m, t-&gt;size)){
		werrstr("malformed ftp path: %q", u-&gt;path);
		return -1;
	}

	if(m[t-&gt;ind[0]].sp){
		u-&gt;ftp.path_spec = estredup(m[t-&gt;ind[0]].sp, m[t-&gt;ind[0]].ep);
		if(strchr(u-&gt;ftp.path_spec, ';')){
			werrstr("unexpected \";param\" in ftp path");
			return -1;
		}
	}else
		u-&gt;ftp.path_spec = estrdup("/");

	if(m[t-&gt;ind[1]].sp){
		u-&gt;ftp.type = estredup(m[t-&gt;ind[1]].sp, m[t-&gt;ind[1]].ep);
		strlower(u-&gt;ftp.type);
	}
	return 0;
}

static int
postparse_file(Url *u)
{
	if(u-&gt;user || u-&gt;passwd){
		werrstr("user information not valid with file scheme");
		return -1;
	}
	if(u-&gt;query){
		werrstr("unexpected \"?query\" in file path");
		return -1;
	}
	if(u-&gt;port){
		werrstr("port not valid with file scheme");
		return -1;
	}
	if(u-&gt;path == nil){
		werrstr("missing path in file scheme");
		return -1;
	}
	if(strchr(u-&gt;path, ';')){
		werrstr("unexpected \";param\" in file path");
		return -1;
	}

	if(!ismatch(REfilepath, u-&gt;path, "file path"))
		return -1;

	/* "localhost" is equivalent to no host spec, we'll chose the latter */
	if(u-&gt;host &amp;&amp; cistrcmp(u-&gt;host, "localhost") == 0){
		free(u-&gt;host);
		u-&gt;host = nil;
	}
	return 0;
}

static int (*postparse[])(Url*) = {
	nil,
	postparse_http,
	postparse_http,
	postparse_ftp,
	postparse_file,
};

Url*
parseurl(char *url, Url *base)
{
	Url *u;
	SplitUrl su;

	if(urldebug)
		fprint(2, "parseurl %s with base %s\n", url, base ? base-&gt;url : "&lt;none&gt;");

	u = emalloc(sizeof(Url));
	u-&gt;url = estrdup(url);
	if(spliturl(u-&gt;url, &amp;su) &lt; 0){
	Fail:
		freeurl(u);
		return nil;
	}

	/* RFC2396 sec 3.1 says relative URIs are distinguished by absent scheme */ 
	if(su.scheme.s==nil){
		if(urldebug)
			fprint(2, "parseurl has nil scheme\n");
		if(resolve_relative(&amp;su, base, u) &lt; 0 || spliturl(u-&gt;url, &amp;su) &lt; 0)
			goto Fail;
		if(u-&gt;ischeme == UScurrent){
			/* 'u.url' refers to current document; set fragment and return */
			if(parse_fragment(&amp;su, u) &lt; 0)
				goto Fail;
			return u;
		}
	}

	if(parse_scheme(&amp;su, u) &lt; 0
	|| parse_fragment(&amp;su, u) &lt; 0)
		goto Fail;

	if(u-&gt;ischeme == USunknown){
		if(parse_unknown_part(&amp;su, u) &lt; 0)
			goto Fail;
		return u;
	}

	if(parse_query(&amp;su, u) &lt; 0
	|| parse_authority(&amp;su, u) &lt; 0
	|| parse_abspath(&amp;su, u) &lt; 0)
		goto Fail;

	if(u-&gt;ischeme &lt; nelem(postparse) &amp;&amp; postparse[u-&gt;ischeme])
		if((*postparse[u-&gt;ischeme])(u) &lt; 0)
			goto Fail;

	setmalloctag(u, getcallerpc(&amp;url));
	return u;
}

void
freeurl(Url *u)
{
	if(u == nil)
		return;
	free(u-&gt;url);
	free(u-&gt;scheme);
	free(u-&gt;schemedata);
	free(u-&gt;authority);
	free(u-&gt;user);
	free(u-&gt;passwd);
	free(u-&gt;host);
	free(u-&gt;port);
	free(u-&gt;path);
	free(u-&gt;query);
	free(u-&gt;fragment);
	switch(u-&gt;ischeme){
	case UShttp:
		free(u-&gt;http.page_spec);
		break;
	case USftp:
		free(u-&gt;ftp.path_spec);
		free(u-&gt;ftp.type);
		break;
	}
	free(u);
}

void
rewriteurl(Url *u)
{
	char *s;

	if(u-&gt;schemedata)
		s = estrmanydup(u-&gt;scheme, ":", u-&gt;schemedata, nil);
	else
		s = estrmanydup(u-&gt;scheme, "://", 
			u-&gt;user ? u-&gt;user : "",
			u-&gt;passwd ? ":" : "", u-&gt;passwd ? u-&gt;passwd : "",
			u-&gt;user ? "@" : "", u-&gt;host ? u-&gt;host : "", 
			u-&gt;port ? ":" : "", u-&gt;port ? u-&gt;port : "",
			u-&gt;path,
			u-&gt;query ? "?" : "", u-&gt;query ? u-&gt;query : "",
			u-&gt;fragment ? "#" : "", u-&gt;fragment ? u-&gt;fragment : "",
			nil);
	free(u-&gt;url);
	u-&gt;url = s;
}

int
seturlquery(Url *u, char *query)
{
	if(query == nil){
		free(u-&gt;query);
		u-&gt;query = nil;
		return 0;
	}

	if(!ismatch(REquery, query, "query"))
		return -1;

	free(u-&gt;query);
	u-&gt;query = estrdup(query);
	return 0;
}

static void
dupp(char **p)
{
	if(*p)
		*p = estrdup(*p);
}

Url*
copyurl(Url *u)
{
	Url *v;

	v = emalloc(sizeof(Url));
	*v = *u;
	dupp(&amp;v-&gt;url);
	dupp(&amp;v-&gt;scheme);
	dupp(&amp;v-&gt;schemedata);
	dupp(&amp;v-&gt;authority);
	dupp(&amp;v-&gt;user);
	dupp(&amp;v-&gt;passwd);
	dupp(&amp;v-&gt;host);
	dupp(&amp;v-&gt;port);
	dupp(&amp;v-&gt;path);
	dupp(&amp;v-&gt;query);
	dupp(&amp;v-&gt;fragment);

	switch(v-&gt;ischeme){
	case UShttp:
		dupp(&amp;v-&gt;http.page_spec);
		break;
	case USftp:
		dupp(&amp;v-&gt;ftp.path_spec);
		dupp(&amp;v-&gt;ftp.type);
		break;
	}
	return v;
}

static int
dhex(char c)
{
	if('0' &lt;= c &amp;&amp; c &lt;= '9')
		return c-'0';
	if('a' &lt;= c &amp;&amp; c &lt;= 'f')
		return c-'a'+10;
	if('A' &lt;= c &amp;&amp; c &lt;= 'F')
		return c-'A'+10;
	return 0;
}

char*
escapeurl(char *s, int (*needesc)(int))
{
	int n;
	char *t, *u;
	Rune r;
	static char *hex = "0123456789abcdef";

	n = 0;
	for(t=s; *t; t++)
		if((*needesc)(*t))
			n++;

	u = emalloc(strlen(s)+2*n+1);
	t = u;
	for(; *s; s++){
		s += chartorune(&amp;r, s);
		if(r &gt;= 0xFF){
			werrstr("URLs cannot contain Runes &gt; 0xFF");
			free(t);
			return nil;
		}
		if((*needesc)(r)){
			*u++ = '%';
			*u++ = hex[(r&gt;&gt;4)&amp;0xF];
			*u++ = hex[r&amp;0xF];
		}else
			*u++ = r;
	}
	*u = '\0';
	return t;
}

char*
unescapeurl(char *s)
{
	char *r, *w;
	Rune rune;

	s = estrdup(s);
	for(r=w=s; *r; r++){
		if(*r=='%'){
			r++;
			if(!isxdigit(r[0]) || !isxdigit(r[1])){
				werrstr("bad escape sequence '%.3s' in URL", r);
				return nil;
			}
			if(r[0]=='0' &amp;&amp; r[2]=='0'){
				werrstr("escaped NUL in URL");
				return nil;
			}
			rune = (dhex(r[0])&lt;&lt;4)|dhex(r[1]);	/* latin1 */
			w += runetochar(w, &amp;rune);
			r += 2;
		}else
			*w++ = *r;
	}
	*w = '\0';
	return s;
}

<!-- BEGIN TAIL -->
</pre>
</td></tr></table>
</td></tr></table>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"></span></p>
<p style="margin-top: 0; margin-bottom: 0.50in"></p>
<p style="margin-top: 0; margin-bottom: 0.33in"></p>
<center><table border="0"><tr>
<td valign="middle"><a href="http://www.alcatel-lucent.com/"><img border="0" src="/plan9/img/logo_ft.gif" alt="Bell Labs" />
</a></td>
<td valign="middle"><a href="http://www.opensource.org"><img border="0" alt="OSI certified" src="/plan9/img/osi-certified-60x50.gif" />
</a></td>
<td><img style="padding-right: 45px;" alt="Powered by Plan 9" src="/plan9/img/power36.gif" />
</td>
</tr></table></center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center>
<span style="font-size: 10pt">(<a href="/plan9/">Return to Plan 9 Home Page</a>)</span>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
<span style="font-size: 10pt"><a href="http://www.lucent.com/copyright.html">Copyright</a></span>
<span style="font-size: 10pt">© 2009 Alcatel-Lucent.</span>
<span style="font-size: 10pt">All Rights Reserved.</span>
<br />
<span style="font-size: 10pt">Comments to</span>
<span style="font-size: 10pt"><a href="mailto:webmaster@plan9.bell-labs.com">webmaster@plan9.bell-labs.com</a>.</span>
</font></center>
</body>
</html>

