ref: cee3145ab92a46565ddbf00c2cc034464bc8002f
author: Tevo <estevan.cps@gmail.com>
date: Thu Nov 5 21:25:29 EST 2020
Somewhat broken prototype
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,21 @@
+</$objtype/mkfile
+
+OBJ=\
+ waffle.$O
+
+%.$O: %.c
+ $CC $CFLAGS $prereq
+
+$O.out: $OBJ
+ $LD $prereq
+
+all:V: $O.out
+
+install:V: all
+ cp $O.out /$objtype/bin/waffle
+
+uninstall:V:
+ rm -f /$objtype/bin/waffle
+
+clean nuke:V:
+ rm -f *.[$OS] $O.out
--- /dev/null
+++ b/waffle.c
@@ -1,0 +1,365 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <bio.h>
+#include <String.h>
+
+char *stubhost = "error.host\t1";
+char *spacetab = " ";
+char *srvroot = "./";
+char *defprog = " \
+ # This is a comment and shouldn't be considered\n\
+ info system is $sysname on $cputype\n\
+ info $objtype \\$notavar $nonexistant \\\\$yay\n\
+ lsdir .\n \
+";
+
+enum
+{
+ OP_COMMENT,
+ OP_LSDIR,
+ 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] %s", argv0);
+ exits("usage");
+}
+
+int
+opforcmd(char* cmd)
+{
+ struct
+ {
+ char *k;
+ int op;
+ } ops[] = {
+ {"#", OP_COMMENT},
+ {"lsdir", OP_LSDIR},
+ {"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;
+}
+
+/*
+ * Like %s, but replaces tabs with spacetab and interpolates variables
+ */
+#pragma varargck type "G" char*
+int
+gopherfmt(Fmt *fmt)
+{
+ String *scratch;
+ char *str, *buf;
+ 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;
+ case '$':
+ s_restart(scratch);
+
+ str++;
+ while(!isspace(*str))
+ s_putc(scratch, *str++);
+ str--;
+ s_terminate(scratch);
+
+ buf = getenv(s_to_c(scratch));
+ if(buf == nil)
+ fprint(2, "getenv: %r\n");
+ else
+ {
+ /* FIXME using %G here resolves variables recursively */
+ fmtprint(fmt, "%G", buf);
+ free(buf);
+ }
+
+ break;
+ case '\\':
+ /* FIXME cannot literally print \$ with this */
+ if(*(str+1) == '$')
+ str++;
+ default:
+ fmtprint(fmt, "%c", *str);
+ }
+
+ s_free(scratch);
+ return ret;
+}
+
+#pragma varargck argpos info 1
+int
+info(char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ fmt = smprint("i%G\t\t%s\r\n", fmt, stubhost);
+ n = vfprint(1, fmt, args);
+ free(fmt);
+ va_end(args);
+
+ return n;
+}
+
+#pragma varargck argpos info 1
+int
+error(char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ fmt = smprint("3%G\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, int port)
+{
+ return print("%c%G\t%G\t%G\t%d\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
+ */
+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);
+}
+
+void
+servedir(char *path)
+{
+ Dir *dir;
+ long n;
+ int fd;
+
+ dir = dirstat(path);
+ if(dir == nil)
+ {
+ error:
+ info("Error: %r");
+ entry(GOPHER_DIR, "/", "", "neptune.shrine", 70);
+ return;
+ }
+ if(dir->qid.type&QTDIR)
+ {
+ free(dir);
+ info("%G", path);
+ fd = open(path, OREAD);
+ if(fd < 0)
+ goto error;
+ n = dirreadall(fd, &dir);
+ if(n < 0)
+ goto error;
+ for(int c = 0; c < n; c++)
+ {
+ char *fpath = smprint("%s/%s", path, dir[c].name);
+ char type = dir[c].mode&DMDIR ? GOPHER_DIR : GOPHER_FILE;
+ entry(type, dir[c].name, fpath, "neptune.shrine", 70);
+ free(fpath);
+ }
+ }
+ else
+ info("not supported...");
+}
+
+String*
+getprog(char *path)
+{
+ /* FIXME actually read index.waffle */
+ return s_copy(defprog);
+}
+
+String*
+nextcomm(String *prog)
+{
+ String *comm;
+
+ while(isspace(*prog->ptr))
+ prog->ptr++;
+
+ if(*prog->ptr == '\0')
+ return nil;
+
+ comm = s_new();
+
+ 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
+interprog(String *prog)
+{
+ int op;
+ String *line, *curtok;
+ s_restart(prog);
+
+ curtok = s_new();
+ while((line = nextcomm(prog)) != nil)
+ {
+ s_parse(line, curtok);
+ switch(op = opforcmd(s_to_c(curtok)))
+ {
+ case OP_COMMENT:
+ break;
+ case OP_LSDIR:
+ servedir("/usr/tevo");
+ break;
+ case GOPHER_INFO:
+ info("%G", line->ptr);
+ break;
+ case GOPHER_ERR:
+ error("%G", line->ptr);
+ break;
+ case -1:
+ default:
+ 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;
+
+ ARGBEGIN {
+ case 'r':
+ srvroot = EARGF(usage());
+ break;
+ case 'd':
+ fprint(2, "defprog: not implemented\n");
+ exits("noimpl");
+ default:
+ usage();
+ } ARGEND;
+
+ fmtinstall('G', gopherfmt);
+
+ req = readrequest();
+ path = parsepath(s_to_c(req));
+ if(path == nil)
+ {
+ error("cannot parse request: %r");
+ goto end;
+ }
+ prog = getprog(path);
+ if(prog == nil)
+ {
+ error("cannot find index for %s: %r", path);
+ goto end;
+ }
+ interprog(prog);
+
+// servedir(path);
+// info("-----");
+// info("waffle on %s (plan9/%s)", getenv("sysname"), getenv("cputype"));
+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);
+}