xref: /plan9/sys/src/cmd/ip/httpd/redirect.c (revision 6aadf53974765d29c796d3ce5d6c69eb7318871e)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include "httpd.h"
5 #include "httpsrv.h"
6 
7 enum
8 {
9 	HASHSIZE = 1019,
10 };
11 
12 typedef struct Redir	Redir;
13 struct Redir
14 {
15 	Redir	*next;
16 	char	*pat;
17 	char	*repl;
18 	uint	flags;		/* generated from repl's decorations */
19 };
20 
21 static Redir *redirtab[HASHSIZE];
22 static Redir *vhosttab[HASHSIZE];
23 static char emptystring[1];
24 /* these two arrays must be kept in sync */
25 static char decorations[] = { Modsilent, Modperm, Modsubord, Modonly, '\0' };
26 static uint redirflags[] = { Redirsilent, Redirperm, Redirsubord, Redironly, };
27 
28 /* replacement field decorated with redirection modifiers? */
29 static int
isdecorated(char * repl)30 isdecorated(char *repl)
31 {
32 	return strchr(decorations, repl[0]) != nil;
33 }
34 
35 static uint
decor2flags(char * repl)36 decor2flags(char *repl)
37 {
38 	uint flags;
39 	char *p;
40 
41 	flags = 0;
42 	while ((p = strchr(decorations, *repl++)) != nil)
43 		flags |= redirflags[p - decorations];
44 	return flags;
45 }
46 
47 /* return replacement without redirection modifiers */
48 char *
undecorated(char * repl)49 undecorated(char *repl)
50 {
51 	while (isdecorated(repl))
52 		repl++;
53 	return repl;
54 }
55 
56 static int
hashasu(char * key,int n)57 hashasu(char *key, int n)
58 {
59         ulong h;
60 
61 	h = 0;
62         while(*key != 0)
63                 h = 65599*h + *(uchar*)key++;
64         return h % n;
65 }
66 
67 static void
insert(Redir ** tab,char * pat,char * repl)68 insert(Redir **tab, char *pat, char *repl)
69 {
70 	Redir **l;
71 	Redir *srch;
72 	ulong hash;
73 
74 	hash = hashasu(pat, HASHSIZE);
75 	for(l = &tab[hash]; *l; l = &(*l)->next)
76 		;
77 	*l = srch = ezalloc(sizeof(Redir));
78 	srch->pat = pat;
79 	srch->flags = decor2flags(repl);
80 	srch->repl = undecorated(repl);
81 	srch->next = 0;
82 }
83 
84 static void
cleartab(Redir ** tab)85 cleartab(Redir **tab)
86 {
87 	Redir *t;
88 	int i;
89 
90 	for(i = 0; i < HASHSIZE; i++){
91 		while((t = tab[i]) != nil){
92 			tab[i] = t->next;
93 			free(t->pat);
94 			free(t->repl);
95 			free(t);
96 		}
97 	}
98 }
99 
100 void
redirectinit(void)101 redirectinit(void)
102 {
103 	static Biobuf *b = nil;
104 	static Qid qid;
105 	char *file, *line, *s, *host, *field[3];
106 	static char pfx[] = "http://";
107 
108 	file = "/sys/lib/httpd.rewrite";
109 	if(b != nil){
110 		if(updateQid(Bfildes(b), &qid) == 0)
111 			return;
112 		Bterm(b);
113 	}
114 	b = Bopen(file, OREAD);
115 	if(b == nil)
116 		sysfatal("can't read from %s", file);
117 	updateQid(Bfildes(b), &qid);
118 
119 	cleartab(redirtab);
120 	cleartab(vhosttab);
121 
122 	while((line = Brdline(b, '\n')) != nil){
123 		line[Blinelen(b)-1] = 0;
124 		s = strchr(line, '#');
125 		if(s != nil && (s == line || s[-1] == ' ' || s[-1] == '\t'))
126 			*s = '\0'; 	/* chop comment iff after whitespace */
127 		if(tokenize(line, field, nelem(field)) == 2){
128 			if(strncmp(field[0], pfx, STRLEN(pfx)) == 0 &&
129 			   strncmp(undecorated(field[1]), pfx, STRLEN(pfx)) != 0){
130 				/* url -> filename */
131 				host = field[0] + STRLEN(pfx);
132 				s = strrchr(host, '/');
133 				if(s)
134 					*s = 0;  /* chop trailing slash */
135 
136 				insert(vhosttab, estrdup(host), estrdup(field[1]));
137 			}else{
138 				insert(redirtab, estrdup(field[0]), estrdup(field[1]));
139 			}
140 		}
141 	}
142 	syslog(0, HTTPLOG, "redirectinit pid=%d", getpid());
143 }
144 
145 static Redir*
lookup(Redir ** tab,char * pat,int count)146 lookup(Redir **tab, char *pat, int count)
147 {
148 	Redir *srch;
149 	ulong hash;
150 
151 	hash = hashasu(pat,HASHSIZE);
152 	for(srch = tab[hash]; srch != nil; srch = srch->next)
153 		if(strcmp(pat, srch->pat) == 0) {
154 			/* only exact match wanted? */
155 			if (!(srch->flags & Redironly) || count == 0)
156 				return srch;
157 		}
158 	return nil;
159 }
160 
161 static char*
prevslash(char * p,char * s)162 prevslash(char *p, char *s)
163 {
164 	while(--s > p)
165 		if(*s == '/')
166 			break;
167 	return s;
168 }
169 
170 /*
171  * find the longest match of path against the redirection table,
172  * chopping off the rightmost path component until success or
173  * there's nothing left.  return a copy of the replacement string
174  * concatenated with a slash and the portion of the path *not* matched.
175  * So a match of /who/gre/some/stuff.html matched against
176  *	/who/gre	http://gremlinsrus.org
177  * returns
178  *	http://gremlinsrus.org/some/stuff.html
179  *
180  * further flags: if Redironly, match only the named page and no
181  * subordinate ones.  if Redirsubord, map the named patch and any
182  * subordinate ones to the same replacement URL.
183  */
184 char*
redirect(HConnect * hc,char * path,uint * flagp)185 redirect(HConnect *hc, char *path, uint *flagp)
186 {
187 	Redir *redir;
188 	char *s, *newpath, *repl;
189 	int c, n, count;
190 
191 	count = 0;
192 	for(s = strchr(path, '\0'); s > path; s = prevslash(path, s)){
193 		c = *s;
194 		*s = '\0';
195 		redir = lookup(redirtab, path, count++);
196 		*s = c;
197 		if(redir != nil){
198 			if (flagp)
199 				*flagp = redir->flags;
200 			repl = redir->repl;
201 			if(redir->flags & Redirsubord)
202 				/* don't append s, all matches map to repl */
203 				s = "";
204 			n = strlen(repl) + strlen(s) + 2 + UTFmax;
205 			newpath = halloc(hc, n);
206 			snprint(newpath, n, "%s%s", repl, s);
207 			return newpath;
208 		}
209 	}
210 	return nil;
211 }
212 
213 /*
214  * if host is virtual, return implicit prefix for URI within webroot.
215  * if not, return empty string.
216  * return value should not be freed by caller.
217  */
218 char*
masquerade(char * host)219 masquerade(char *host)
220 {
221 	Redir *redir;
222 
223 	redir = lookup(vhosttab, host, 0);
224 	if(redir == nil)
225 		return emptystring;
226 	return redir->repl;
227 }
228