shithub: paste

Download patch

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;
+}
--