ref: dc359256b98b9aa563e73066e2c3c3c1204c5b2a
dir: /waffle.c/
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <stdio.h>
#include <bio.h>
#include <String.h>
char *querystr;
char *stubhost = "error.host\t1";
char *gopherhost = "localhost";
char *spacetab = " ";
char *srvroot = "./";
char *defprog = "\n\
info you seem to be lost...\n\
dir 'back home' /\n\
";
enum
{
OP_COMMENT,
OP_EXEC,
};
enum
{
/* Unofficial */
GOPHER_INFO = 'i',
GOPHER_DOC = 'd',
GOPHER_HTML = 'h',
GOPHER_AUDIO = 's',
/* RFC1436 */
GOPHER_FILE = '0',
GOPHER_DIR,
GOPHER_PHONEBOOK,
GOPHER_ERR,
GOPHER_MAC_FILE,
GOPHER_DOS_FILE,
GOPHER_UNIX_FILE,
GOPHER_SEARCH,
GOPHER_TELNET,
GOPHER_BIN,
GOPHER_REDUNDANT = '+',
GOPHER_TN3270 = 'T',
GOPHER_GIF = 'g',
GOPHER_IMAGE = 'I',
};
void
usage(void)
{
fprint(2, "usage: [-r root] [-d defprog] [-h hostaddr] %s", argv0);
exits("usage");
}
int
opforcmd(char* cmd)
{
struct
{
char *k;
int op;
} ops[] = {
{"--", OP_COMMENT},
{"exec", OP_EXEC},
{"info", GOPHER_INFO},
{"doc", GOPHER_DOC},
{"html", GOPHER_HTML},
{"file", GOPHER_FILE},
{"dir", GOPHER_DIR},
{"phonebook", GOPHER_PHONEBOOK},
{"error", GOPHER_ERR},
/* {MAC,UNIX,DOS}-FILE */
{"search", GOPHER_SEARCH},
{"telnet", GOPHER_TELNET},
{"bin", GOPHER_BIN},
/* REDUNDANT, TN3270 */
{"gif", GOPHER_GIF},
{"img", GOPHER_IMAGE},
{nil, 0},
};
for(int c = 0; ops[c].k != nil; c++)
if(strcmp(ops[c].k, cmd) == 0)
return ops[c].op;
if(strlen(cmd) == 1)
return *cmd;
return -1;
}
char*
readall(char *path)
{
char *buf;
int fd, bsize = 4096;
if((fd = open(path, OREAD)) < 0)
sysfatal("open: %r");
buf = malloc(bsize);
if(buf == nil)
sysfatal("malloc: %r");
for(int c = 0; read(fd, buf+(c*bsize), bsize) != 0; c++)
{
buf = realloc(buf, ((c+2)*bsize));
if(buf == nil)
sysfatal("realloc: %r");
}
close(fd);
return buf;
}
/*
* Funny bug, sometimes strings allocated on multiple invocations
* of the same function ends up with leftovers from previous invocations,
* probably getting assigned the same memory location as before (which
* wasn't cleaned up, so it's full of garbage), and the string "picks it up".
* s_terminate doesn't seem to do it's job, somehow, or I'm doing something
* wrong.
* FIXME investigate this
*/
void
s_cleanup(String *str)
{
for(char *c = str->base; c < str->end; c++)
*c = 0;
}
/*
* Like %s, but replaces tabs with spacetab
*/
#pragma varargck type "G" char*
int
gopherfmt(Fmt *fmt)
{
String *scratch;
char *str;
int ret = 0;
scratch = s_new();
str = va_arg(fmt->args, char*);
for(; ret >= 0 && *str != '\0'; str++)
switch(*str)
{
case '\t':
ret = fmtprint(fmt, "%s", spacetab);
break;
default:
fmtprint(fmt, "%c", *str);
}
s_free(scratch);
return ret;
}
/*
* Like %G, but interpolates variables
*/
#pragma varargck type "V" char*
int
varfmt(Fmt *fmt)
{
String *out, *scratch;
char *str, *buf;
int ret;
scratch = s_new();
out = s_new();
str = va_arg(fmt->args, char*);
for(; *str != '\0'; str++)
switch(*str)
{
case '$':
s_restart(scratch);
str++;
while(isalnum(*str))
s_putc(scratch, *str++);
str--;
s_terminate(scratch);
buf = getenv(s_to_c(scratch));
if(buf == nil)
fprint(2, "getenv: %r\n");
else
{
s_append(out, buf);
free(buf);
}
break;
case '\\':
/* FIXME cannot literally print \$ with this */
if(*(str+1) == '$')
str++;
default:
s_putc(out, *str);
}
s_terminate(out);
ret = fmtprint(fmt, "%G", s_to_c(out));
s_free(scratch);
s_free(out);
return ret;
}
#pragma varargck argpos info 1
int
info(char *fmt, ...)
{
int n;
va_list args;
va_start(args, fmt);
fmt = smprint("i%V\t\t%s\r\n", fmt, stubhost);
n = vfprint(1, fmt, args);
free(fmt);
va_end(args);
return n;
}
#pragma varargck argpos error 1
int
error(char *fmt, ...)
{
int n;
va_list args;
va_start(args, fmt);
fmt = smprint("3%V\t\t%s\r\n", fmt, stubhost);
n = vfprint(1, fmt, args);
free(fmt);
va_end(args);
return n;
}
int
entry(char type, char *name, char *path, char *host, char *port)
{
return print("%c%V\t%V\t%V\t%V\r\n", type, name, path, host, port);
}
/*
* FIXME we should timeout at some point, so a user can't take us down
* by opening connections but never sending a CRLF.
* Maybe it's not our job.
*/
String*
readrequest(void)
{
String *str = s_new();
int c;
while((c = getchar()) != EOF)
{
s_putc(str, c);
if(str->ptr[-2] == '\r' && str->ptr[-1] == '\n')
break;
}
str->ptr -= 2;
s_terminate(str);
return str;
}
/* TODO canonize path */
char*
parsepath(char* req)
{
if(strlen(req) == 0)
return strdup("/");
return strdup(req);
}
/* FIXME disallow requests outside gopherroot */
String*
getprog(char *path)
{
String *prog;
char *apath, buf[4096];
int fd, n;
apath = smprint("%s/%s", srvroot, path);
if(apath == nil)
sysfatal("smprint: %r");
if(chdir(apath) != 0)
{
free(apath);
return s_copy(defprog);
}
free(apath);
if((fd = open("index.waffle", OREAD)) < 0)
return s_copy(defprog);
prog = s_new();
while((n = read(fd, buf, sizeof(buf))) != 0)
s_memappend(prog, buf, n);
close(fd);
return prog;
}
String*
nextcomm(String *prog)
{
String *comm;
while(isspace(*prog->ptr))
prog->ptr++;
if(*prog->ptr == '\0')
return nil;
comm = s_new();
s_cleanup(comm);
for(; *prog->ptr != '\0'; prog->ptr++)
{
if(*prog->ptr == '\n')
break;
s_putc(comm, *prog->ptr);
}
s_terminate(comm);
s_restart(comm);
return comm;
}
void
parseentry(int op, String *line)
{
char *defval[] = {
nil, nil,
[2] = gopherhost,
[3] = "70",
};
String *p[] = {nil, nil, nil, nil};
for(int c = 0; c < 4; c++)
{
p[c] = s_new();
s_parse(line, p[c]);
s_terminate(p[c]);
if(strlen(s_to_c(p[c])) == 0)
if(defval[c] != nil)
s_append(p[c], defval[c]);
else
{
fprint(2, "incomplete command '%c'\n", op);
goto cleanup;
}
}
entry((char)op, s_to_c(p[0]), s_to_c(p[1]), s_to_c(p[2]), s_to_c(p[3]));
cleanup:
for(int c = 0; c < 4; c++)
if(p[c] != nil)
s_free(p[c]);
}
void
shellexec(char *cmd)
{
Waitmsg *msg;
char *argv[] = { "/bin/rc", "-c", cmd };
int rcin[2];
pipe(rcin);
switch(rfork(RFFDG|RFPROC|RFMEM|RFNAMEG|RFNOTEG|RFREND))
{
case 0:
close(0);
dup(rcin[0], 0);
close(rcin[0]);
exec("/bin/rc", argv);
sysfatal("exec: %r");
break;
case -1:
sysfatal("rfork: %r");
break;
default:
write(rcin[1], querystr, strlen(querystr));
msg = wait();
if(strlen(msg->msg) != 0)
fprint(2, "rc: %s\n", msg->msg);
}
}
void
interprog(String *prog)
{
int op;
String *line, *curtok;
s_restart(prog);
curtok = s_new();
while((line = nextcomm(prog)) != nil)
{
s_parse(line, curtok);
s_terminate(curtok);
switch(op = opforcmd(s_to_c(curtok)))
{
case OP_COMMENT:
break;
case OP_EXEC:
shellexec(line->ptr);
break;
case GOPHER_INFO:
info("%V", line->ptr);
break;
case GOPHER_ERR:
error("%V", line->ptr);
break;
default:
parseentry(op, line);
break;
case -1:
error("command not implemented: %G", s_to_c(curtok));
}
s_reset(curtok);
s_free(line);
}
s_free(curtok);
}
void
main(int argc, char **argv)
{
String *req, *prog;
char *path, pwd[512];
ARGBEGIN {
case 'r':
srvroot = EARGF(usage());
break;
case 'd':
defprog = readall(EARGF(usage()));
if(defprog == nil)
sysfatal("readall: %r");
break;
case 'h':
gopherhost = EARGF(usage());
break;
default:
usage();
} ARGEND;
fmtinstall('G', gopherfmt);
fmtinstall('V', varfmt);
if(chdir(srvroot) != 0)
sysfatal("chdir: %r");
if(getwd(pwd, sizeof(pwd)) == 0)
sysfatal("getwd: %r");
req = readrequest();
querystr = s_to_c(req);
path = parsepath(s_to_c(req));
if(path == nil)
{
error("cannot parse request: %r");
goto end;
}
putenv("gopherroot", pwd);
putenv("gopherhost", gopherhost);
putenv("querystr", s_to_c(req));
putenv("pathstr", path);
prog = getprog(path);
if(prog == nil)
{
error("cannot find index for %G: %r", path);
goto end;
}
interprog(prog);
end:
print(".");
/*
* we're quitting, don't think it would be bad to just leave
* those dangling for the os to cleanup
*/
s_free(req);
free(path);
exits(0);
}