#include <u.h>
#include <libc.h>
#include <bio.h>
#include "httpd.h"
#include "httpsrv.h"
enum
{
HASHSIZE = 1019,
};
typedef struct Redir Redir;
struct Redir
{
Redir *next;
char *pat;
char *repl;
uint flags; /* generated from repl's decorations */
};
static Redir *redirtab[HASHSIZE];
static Redir *vhosttab[HASHSIZE];
static char emptystring[1];
/* these two arrays must be kept in sync */
static char decorations[] = { Modsilent, Modperm, Modsubord, Modonly, '\0' };
static uint redirflags[] = { Redirsilent, Redirperm, Redirsubord, Redironly, };
/* replacement field decorated with redirection modifiers? */
static int
isdecorated(char *repl)
{
return strchr(decorations, repl[0]) != nil;
}
static uint
decor2flags(char *repl)
{
uint flags;
char *p;
flags = 0;
while ((p = strchr(decorations, *repl++)) != nil)
flags |= redirflags[p - decorations];
return flags;
}
/* return replacement without redirection modifiers */
char *
undecorated(char *repl)
{
while (isdecorated(repl))
repl++;
return repl;
}
static int
hashasu(char *key, int n)
{
ulong h;
h = 0;
while(*key != 0)
h = 65599*h + *(uchar*)key++;
return h % n;
}
static void
insert(Redir **tab, char *pat, char *repl)
{
Redir **l;
Redir *srch;
ulong hash;
hash = hashasu(pat, HASHSIZE);
for(l = &tab[hash]; *l; l = &(*l)->next)
;
*l = srch = ezalloc(sizeof(Redir));
srch->pat = pat;
srch->flags = decor2flags(repl);
srch->repl = undecorated(repl);
srch->next = 0;
}
static void
cleartab(Redir **tab)
{
Redir *t;
int i;
for(i = 0; i < HASHSIZE; i++){
while((t = tab[i]) != nil){
tab[i] = t->next;
free(t->pat);
free(t->repl);
free(t);
}
}
}
void
redirectinit(void)
{
static Biobuf *b = nil;
static Qid qid;
char *file, *line, *s, *host, *field[3];
static char pfx[] = "http://";
file = "/sys/lib/httpd.rewrite";
if(b != nil){
if(updateQid(Bfildes(b), &qid) == 0)
return;
Bterm(b);
}
b = Bopen(file, OREAD);
if(b == nil)
sysfatal("can't read from %s", file);
updateQid(Bfildes(b), &qid);
cleartab(redirtab);
cleartab(vhosttab);
while((line = Brdline(b, '\n')) != nil){
line[Blinelen(b)-1] = 0;
s = strchr(line, '#');
if(s != nil && (s == line || s[-1] == ' ' || s[-1] == '\t'))
*s = '\0'; /* chop comment iff after whitespace */
if(tokenize(line, field, nelem(field)) == 2){
if(strncmp(field[0], pfx, STRLEN(pfx)) == 0 &&
strncmp(undecorated(field[1]), pfx, STRLEN(pfx)) != 0){
/* url -> filename */
host = field[0] + STRLEN(pfx);
s = strrchr(host, '/');
if(s)
*s = 0; /* chop trailing slash */
insert(vhosttab, estrdup(host), estrdup(field[1]));
}else{
insert(redirtab, estrdup(field[0]), estrdup(field[1]));
}
}
}
syslog(0, HTTPLOG, "redirectinit pid=%d", getpid());
}
static Redir*
lookup(Redir **tab, char *pat, int count)
{
Redir *srch;
ulong hash;
hash = hashasu(pat,HASHSIZE);
for(srch = tab[hash]; srch != nil; srch = srch->next)
if(strcmp(pat, srch->pat) == 0) {
/* only exact match wanted? */
if (!(srch->flags & Redironly) || count == 0)
return srch;
}
return nil;
}
static char*
prevslash(char *p, char *s)
{
while(--s > p)
if(*s == '/')
break;
return s;
}
/*
* find the longest match of path against the redirection table,
* chopping off the rightmost path component until success or
* there's nothing left. return a copy of the replacement string
* concatenated with a slash and the portion of the path *not* matched.
* So a match of /who/gre/some/stuff.html matched against
* /who/gre http://gremlinsrus.org
* returns
* http://gremlinsrus.org/some/stuff.html
*
* further flags: if Redironly, match only the named page and no
* subordinate ones. if Redirsubord, map the named patch and any
* subordinate ones to the same replacement URL.
*/
char*
redirect(HConnect *hc, char *path, uint *flagp)
{
Redir *redir;
char *s, *newpath, *repl;
int c, n, count;
count = 0;
for(s = strchr(path, '\0'); s > path; s = prevslash(path, s)){
c = *s;
*s = '\0';
redir = lookup(redirtab, path, count++);
*s = c;
if(redir != nil){
if (flagp)
*flagp = redir->flags;
repl = redir->repl;
if(redir->flags & Redirsubord)
/* don't append s, all matches map to repl */
s = "";
n = strlen(repl) + strlen(s) + 2 + UTFmax;
newpath = halloc(hc, n);
snprint(newpath, n, "%s%s", repl, s);
return newpath;
}
}
return nil;
}
/*
* if host is virtual, return implicit prefix for URI within webroot.
* if not, return empty string.
* return value should not be freed by caller.
*/
char*
masquerade(char *host)
{
Redir *redir;
redir = lookup(vhosttab, host, 0);
if(redir == nil)
return emptystring;
return redir->repl;
}
|