ref: 712fcc55cd95148faefb531d9c19cd870725bb96
author: Alex Musolino <alex@musolino.id.au>
date: Wed May 21 10:17:10 EDT 2025
initial commit
--- /dev/null
+++ b/bin/mkfile
@@ -1,0 +1,7 @@
+</$objtype/mkfile
+
+paste: paste.$O
+ $LD $LDFLAGS -o $target $prereq
+
+%.$O: %.c
+ $CC $CFLAGS $stem.c
--- /dev/null
+++ b/bin/paste.c
@@ -1,0 +1,606 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <bio.h>
+
+Biobuf *stdin;
+
+int
+Bpeek(Biobuf *b)
+{
+ int c;
+
+ c = Bgetc(b);
+ if(c >= 0)
+ Bungetc(b);
+ return c;
+}
+
+static void
+error(int code, char *text)
+{
+ fprint(1, "Status: %d %s\r\n", code, text);
+ fprint(1, "\r\n");
+ fprint(1, "%s\r\n", text);
+}
+
+static void
+permredirect(char *location)
+{
+ fprint(1, "Status: 301 Moved Permanently\r\n");
+ fprint(1, "Location: %s\r\n", location);
+ fprint(1, "\r\n");
+ exits(0);
+}
+
+static void
+postredirect(char *location)
+{
+ fprint(1, "Status: 303 See Other\r\n");
+ fprint(1, "Location: %s\r\n", location);
+ fprint(1, "\r\n");
+ exits(0);
+}
+
+static char *
+stredup(char *s, char *e)
+{
+ char *x;
+
+ x = malloc(e - s + 1);
+ strecpy(x, x + (e - s) + 1, s);
+ return x;
+}
+
+static char*
+findboundary(char *s)
+{
+ int quoted;
+ char *e;
+
+ quoted = 0;
+ while(*s){
+ if(*s++ == ';')
+ break;
+ }
+ for(;;){
+ while(isspace(*s))
+ s++;
+ if(*s == 0)
+ return nil;
+ if(strncmp(s, "boundary=", 9) == 0){
+ s += 9;
+ if(*s == '"'){
+ quoted = 1;
+ s++;
+ }
+ e = s;
+ while(*e){
+ if(quoted){
+ if(*e == '"')
+ break;
+ }else{
+ if(isspace(*e))
+ break;
+ }
+ e++;
+ }
+ return stredup(s, e);
+ }
+ while(!isspace(*s))
+ s++;
+ }
+}
+
+static char
+base62(ulong n)
+{
+ n = n % 62;
+ if(n < 10)
+ return '0' + n;
+ if(n < 36)
+ return 'a' + n - 10;
+ return 'A' + n - 36;
+}
+
+static char*
+nmktemp0(char *s, char *as)
+{
+ ulong n;
+
+ strcpy(s, as);
+ n = lrand();
+ while(*s)
+ s++;
+ while(*--s == 'X'){
+ *s = base62(n);
+ n = n/62;
+ if(n == 0)
+ n = lrand();
+ }
+ return s;
+}
+
+static char*
+nmktemp(char *as)
+{
+ int fd;
+ char *s;
+
+ s = malloc(strlen(as)+1);
+ if(s == nil)
+ return "/";
+ for(;;){
+ nmktemp0(s, as);
+ fd = create(s, OEXCL|OREAD, 0755|DMDIR);
+ if(fd < 0){
+ if(access(s, AEXIST) == 0)
+ continue;
+ free(s);
+ return "/";
+ }
+ close(fd);
+ strcpy(as, s);
+ free(s);
+ return as;
+ }
+}
+
+static void
+skipws(void)
+{
+ int c;
+
+ for(;;){
+ c = Bgetc(stdin);
+ if(c < 0)
+ return;
+ if(!isspace(c) || c == '\r' || c == '\n'){
+ Bungetc(stdin);
+ return;
+ }
+ }
+}
+
+static int
+skipto(char *marker)
+{
+ int c, i, buflen;
+ char *buf;
+
+ buflen = strlen(marker);
+ buf = malloc(buflen);
+
+ for(i = 0; i < buflen; i++){
+ c = Bgetc(stdin);
+ if(c < 0){
+ free(buf);
+ return -1;
+ }
+ buf[i] = c;
+ }
+
+ i = 0;
+ for(;;){
+//fprint(2, "buf[%d:] = %.*s\n", i, buflen-i, buf+i);
+//fprint(2, "buf[:%d] = %.*s\n", i, i, buf);
+ if(memcmp(buf+i, marker, buflen-i) == 0)
+ if(memcmp(buf, marker+buflen-i, i) == 0){
+ free(buf);
+ return 1;
+ }
+
+ c = Bgetc(stdin);
+ if(c < 0){
+ free(buf);
+ return -1;
+ }
+ buf[i] = c;
+ i = (i+1)%buflen;
+ }
+}
+
+static int
+copydata(Biobuf *out, char *marker)
+{
+ int c, i, buflen;
+ char *buf;
+
+ buflen = strlen(marker);
+ buf = malloc(buflen);
+
+ for(i = 0; i < buflen; i++){
+ c = Bgetc(stdin);
+ if(c < 0)
+ goto Error;
+ buf[i] = c;
+ }
+
+ i = 0;
+ for(;;){
+//fprint(2, "buf[%d:] = %.*s\n", i, buflen-i, buf+i);
+//fprint(2, "buf[:%d] = %.*s\n", i, i, buf);
+ if(memcmp(buf+i, marker, buflen-i) == 0)
+ if(memcmp(buf, marker+buflen-i, i) == 0)
+ break;
+
+ c = Bgetc(stdin);
+ if(c < 0)
+ goto Error;
+ if(out != nil){
+ if(Bputc(out, buf[i]) < 0)
+ goto Error;
+ }
+ buf[i] = c;
+ i = (i+1)%buflen;
+ }
+
+ free(buf);
+ if(out != nil)
+ if(Bflush(out) < 0)
+ return -1;
+ return 1;
+
+Error:
+ free(buf);
+ return -1;
+}
+
+enum{
+ KeyMax = 256,
+ ValueMax = 1024,
+};
+
+static int
+crlf(void)
+{
+ int c;
+
+ c = Bgetc(stdin);
+ if(c < 0)
+ return 0;
+ if(c != '\r'){
+ Bungetc(stdin);
+ return 0;
+ }
+ c = Bgetc(stdin);
+ if(c < 0)
+ return 0;
+ if(c == '\n')
+ return 1;
+ return 0;
+}
+
+static int
+parsekey(char *key, char sep)
+{
+ int c;
+ char *kp;
+
+ skipws();
+ if(crlf())
+ return 0;
+
+ kp = key;
+ for(;;){
+ c = Bgetc(stdin);
+ if(c < 0 || isspace(c))
+ return 0;
+ if(c == sep){
+ *kp = 0;
+ break;
+ }
+ *kp++ = c;
+ if(kp - key > KeyMax){
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+parsequoted(char *val, char end)
+{
+ int c;
+ char *vp;
+
+ vp = val;
+ for(;;){
+ c = Bgetc(stdin);
+ if(c < 0)
+ return 0;
+ if(c == '"'){
+ skipws();
+ c = Bgetc(stdin);
+ if(c >= 0){
+ if(c == '\r')
+ Bungetc(stdin);
+ else if(c != end)
+ return 0;
+ }
+ *vp = 0;
+ break;
+ }
+ *vp++ = c;
+ if(vp - val > ValueMax){
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+parseval(char *val, char end)
+{
+ int c;
+ char *vp;
+
+ vp = val;
+ skipws();
+
+ c = Bgetc(stdin);
+ if(c < 0){
+ return 0;
+ }
+ if(c == '"')
+ return parsequoted(val, end);
+ Bungetc(stdin);
+
+ for(;;){
+ c = Bgetc(stdin);
+ if(c < 0){
+ return 0;
+ }
+ if(c == '\r' || c == '\n' || c == end){
+ if(c == '\r')
+ Bungetc(stdin);
+ *vp = 0;
+ break;
+ }
+ *vp++ = c;
+ if(vp - val > ValueMax){
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+dashdash(void)
+{
+ int c;
+
+ c = Bgetc(stdin);
+ if(c < 0)
+ return 0;
+ if(c != '-'){
+ Bungetc(stdin);
+ return 0;
+ }
+ c = Bgetc(stdin);
+ if(c < 0 || c != '-')
+ return 0;
+ return 1;
+}
+
+static void
+handlepost(void)
+{
+ int i, nfiles;
+ char *ctype;
+ char *boundary, *mark;
+ char *dir, *filename, *target;
+ char hdr[KeyMax], key[KeyMax], val[ValueMax];
+ Biobuf *out;
+
+ ctype = getenv("CONTENT_TYPE");
+ if(ctype == nil){
+ fprint(2, "CGI variable CONTENT_TYPE not set!\n");
+ error(500, "Internal Server Error");
+ exits("missing CONTENT_TYPE");
+ }
+ if(strncmp(ctype, "multipart/form-data;", 20) != 0){
+ fprint(2, "expected multipart/form-data content-type; got %s\n", ctype);
+ error(400, "Bad Request");
+ exits("unexpected content-type");
+ }
+ boundary = findboundary(ctype);
+ if(boundary == nil){
+ fprint(2, "could not find boundary in CONTENT_TYPE\n");
+ error(400, "Bad Request");
+ exits("missing boundary for multipart/form-data");
+ }
+ mark = smprint("--%s", boundary);
+ free(boundary);
+ dir = smprint("%s/uploads/XXXXXXXXXXX", getenv("FS_ROOT"));
+ if(strcmp(nmktemp(dir), "/") == 0){
+ fprint(2, "could not create upload directory: mktemp: %r\n");
+ error(500, "Internal Server Error");
+ exits("mktemp");
+ }
+ target = nil;
+ i = 0;
+ nfiles = 0;
+ filename = nil;
+ if(skipto(mark) != 1){
+ error(400, "Bad Request");
+ exits(0);
+ }
+ for(;;){
+ if(dashdash())
+ break;
+ if(!crlf()){
+ fprint(2, "expected CRLF sequence after boundary marker\n");
+ error(400, "Bad Request");
+ exits(0);
+ }
+ free(filename);
+ filename = nil;
+ for(;;){
+ if(crlf())
+ break;
+ if(!parsekey(hdr, ':'))
+ break;
+ if(!parseval(val, ';'))
+ break;
+ if(!crlf()){
+ for(;;){
+ if(!parsekey(key, '='))
+ break;
+ if(!parseval(val, ';'))
+ break;
+ if(cistrcmp(hdr, "content-disposition") == 0){
+ if(cistrcmp(key, "filename") == 0){
+ if(strlen(val) > 0){
+ free(filename);
+ filename = smprint("%s/%s", dir, val);
+ }
+ }else if(cistrcmp(key, "name") == 0){
+ if(strcmp(val, "text") == 0)
+ if(filename == nil)
+ filename = smprint("%s/text", dir);
+ }
+ }
+ skipws();
+ if(crlf())
+ break;
+ }
+ }
+ }
+ if(i == 0)
+ mark = smprint("\r\n%s", mark);
+ i++;
+ if(filename != nil && Bpeek(stdin) != '\r'){
+ out = Bopen(filename, OWRITE);
+ if(out == nil){
+ fprint(2, "could not open %s: %r\n", filename);
+ error(500, "Internal Server Error");
+ exits("open");
+ }
+ if(copydata(out, mark) < 0){
+ fprint(2, "error writing to %s: %r\n", filename);
+ error(500, "Internal Server Error");
+ exits("copydata");
+ }
+ Bterm(out);
+ nfiles++;
+ if(nfiles == 1){
+ free(target);
+ target = smprint("/uploads/%s/%s", strrchr(dir, '/')+1, strrchr(filename, '/')+1);
+ }else if(nfiles == 2){
+ free(target);
+ target = smprint("/uploads/%s/", strrchr(dir, '/')+1);
+ }
+ }else if(copydata(nil, mark) < 0){
+ fprint(2, "unexpected EOF\n");
+ error(400, "Bad Request");
+ exits(0);
+ }
+ }
+ postredirect(target);
+ exits(0);
+}
+
+static void
+dirindex(int fd)
+{
+ long n;
+ Dir *d, *dirs;
+
+ n = dirreadall(fd, &dirs);
+ if(n == 0)
+ return;
+ if(n < 0){
+ fprint(2, "paste: dirreadall: %r\n");
+ error(500, "Internal Server Error");
+ exits("dirreadall");
+ }
+
+ fprint(1, "Status: 200 OK\r\n");
+ fprint(1, "\r\n");
+ fprint(1, "<!DOCTYPE html>\r\n");
+
+ d = dirs;
+ while(n-- > 0){
+ fprint(1, "<a href=\"%s\">%s</a><br>\n", d->name, d->name);
+ d++;
+ }
+ free(dirs);
+}
+
+static void
+serve(char *path)
+{
+ int fd, n;
+ Dir *d;
+ char buf[4*1024];
+
+ d = dirstat(path);
+ fd = open(path, OREAD);
+ if(fd < 0){
+ error(404, "Not Found");
+ exits(0);
+ }
+ if((d->mode&DMDIR) != 0){
+ dirindex(fd);
+ exits(0);
+ }
+
+ fprint(1, "Status: 200 OK\r\n");
+ fprint(1, "Content-length: %lld\r\n", d->length);
+ fprint(1, "\r\n");
+
+ for(;;){
+ n = read(fd, buf, sizeof(buf));
+ if(n == 0)
+ exits(0);
+ if(n < 0){
+ fprint(2, "paste: error reading from %s: %r\n", path);
+ exits("read error");
+ }
+ if(write(1, buf, n) != n){
+ fprint(2, "paste: write: %r\n");
+ exits("write error");
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: paste\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *requri;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ srand(nsec());
+ requri = getenv("REQUEST_URI");
+ if(requri == nil){
+ fprint(2, "CGI variable REQUEST_URI not set!\n");
+ error(500, "Internal Server Error");
+ exits("missing REQUEST_URI");
+ }
+ if(strcmp(requri, "/") == 0)
+ permredirect("/index.html");
+ if(strcmp(requri, "/uploads/") == 0){
+ error(403, "Forbidden");
+ exits(0);
+ }
+ stdin = Bfdopen(0, OREAD);
+ if(strcmp(requri, "/index.html") == 0 || strcmp(requri, "/style.css") == 0)
+ serve(smprint("%s/%s", getenv("FS_ROOT"), requri));
+ if(strncmp(requri, "/uploads/", 9) == 0)
+ serve(smprint("%s/%s", getenv("FS_ROOT"), requri));
+ if(strcmp(requri, "/post") == 0)
+ handlepost();
+ error(404, "Not Found");
+}
--- /dev/null
+++ b/index.html
@@ -1,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>OK, Turing... </title>
+ <link rel="stylesheet" href="style.css" type="text/css" media="screen, handheld" title="default">
+ <meta charset="UTF-8">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+ <h1>What'll it be?</h1>
+ <div>
+ <form method="post" action="/post" enctype="multipart/form-data">
+ <textarea name="text" rows="24" cols="80"></textarea>
+ <br>
+ <input type="file" name="file">
+ <br>
+ <input type="submit" name="submit" value="submit">
+ </form>
+ </div>
+</body>
+</html>
--- /dev/null
+++ b/style.css
@@ -1,0 +1,14 @@
+body {
+ font-family: 'Lucida Sans Unicode', 'Lucida Sans', 'Lucida Grande', 'Bitstream Sans Vera', Sans-Serif;
+ background-color: #FFFFFF;
+ color: #000000;
+}
+input, textarea, select, option {
+ font-family: 'Lucida Sans Unicode', 'Lucida Sans', 'Lucida Grande', 'Bitstream Sans Vera', Sans-Serif;
+ font-size:100%;
+ padding: 2px;
+ background : #eee;
+ color : #111;
+ border: 1px solid #fff;
+ margin: 2px;
+}
--
⑨