shithub: paste

ref: cf032667921f9b25f5b6f0b7ade743087dc64e5c
dir: /bin/paste.c/

View raw version
#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 char hex[] = "0123456789abcdef";
static char Hex[] = "0123456789ABCDEF";

static int
hexdigit(int c)
{
	char *p;

	if(c > 0){
		if((p = strchr(Hex, c)) != 0)
			return p - Hex;
		if((p = strchr(hex, c)) != 0)
			return p - hex;
	}
	return -1;
}

static char*
urldecode(char *dst, int len, char *src)
{
	char c;
	char *p;

	p = dst;
	while((c = *src++) != 0){
		if(len < 2)
			return nil;
		if(c == '%'){
			int c1, c2, x1, x2;

			if((c1 = *src++) == 0)
				break;
			if((x1 = hexdigit(c1)) < 0){
				src--;
				*p++ = c1;
				len--;
				continue;
			}
			if((c2 = *src++) == 0)
				break;
			if((x2 = hexdigit(c2)) < 0){
				if(len < 3)
					return nil;
				src--;
				*p++ = c;
				*p++ = c1;
				len -= 2;
				continue;
			}
			c = x1<<4 | x2;
		} else if(c == '+')
			c = ' ';
		*p++ = c;
		len--;
	}
	*p = 0;
	return dst;
}

static char*
urlencode(char *dst, int len, char *src)
{
	char c;
	char *p;

	p = dst;
	while((c = *src++) != 0){
		if(len < 2)
			return nil;
		len--;
		if(c>0 && strchr("/$-_@.!*'(),", c)
		|| 'a'<=c && c<='z'
		|| 'A'<=c && c<='Z'
		|| '0'<=c && c<='9')
			*p++ = c;
		else if(c == ' ')
			*p++ = '+';
		else {
			*p++ = '%';
			if(len < 3)
				return nil;
			len -= 2;
			*p++ = Hex[c>>4];
			*p++ = Hex[c&15];
		}
	}
	*p = 0;
	return dst;
}

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
error400(void)
{
	error(400, "Bad request");
}

static void
error403(void)
{
	error(403, "Forbidden");
}

static void
error404(void)
{
	error(404, "Not found");
}

static void
error500(void)
{
		error(500, "Internal server error");
}

static void
permredirect(char *location)
{
	char buf[1024];

	if(urlencode(buf, sizeof(buf), location) != nil){
		fprint(1, "Status: 301 Moved Permanently\r\n");
		fprint(1, "Location: %s\r\n", buf);
		fprint(1, "\r\n");
	}else
		error500();
	exits(0);
}

static void
postredirect(char *location)
{
	char buf[1024];

	if(urlencode(buf, sizeof(buf), location) != nil){
		fprint(1, "Status: 303 See Other\r\n");
		fprint(1, "Location: %s\r\n", buf);
		fprint(1, "\r\n");
	}else
		error500();
	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
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;
}

static int
skipto(char *marker)
{
	return copydata(nil, marker);
}

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");
		error500();
		exits("missing CONTENT_TYPE");
	}
	if(strncmp(ctype, "multipart/form-data;", 20) != 0){
		fprint(2, "expected multipart/form-data content-type; got %s\n", ctype);
		error400();
		exits("unexpected content-type");
	}
	boundary = findboundary(ctype);
	if(boundary == nil){
		fprint(2, "could not find  boundary in CONTENT_TYPE\n");
		error400();
		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");
		error500();
		exits("mktemp");
	}
	target = nil;
	i = 0;
	nfiles = 0;
	filename = nil;
	if(skipto(mark) != 1){
		error400();
		exits(0);
	}
	for(;;){
		if(dashdash())
			break;
		if(!crlf()){
			fprint(2, "expected CRLF sequence after boundary marker\n");
			error400();
			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 = strrchr(val, '/');
								if(filename == nil)
									filename = val;
								else
									filename++;
								filename = smprint("%s/%s", dir, filename);
							}
						}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);
				error500();
				exits("open");
			}
			if(copydata(out, mark) < 0){
				fprint(2, "error writing to %s: %r\n", filename);
				error500();
				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(skipto(mark) < 0){
			fprint(2, "unexpected EOF\n");
			error400();
			exits(0);
		}
	}
	postredirect(target);
	exits(0);
}

static void
dirindex(int fd)
{
	char url[1024];
	long n;
	Dir *d, *dirs;

	n = dirreadall(fd, &dirs);
	if(n == 0)
		return;
	if(n < 0){
		fprint(2, "paste: dirreadall: %r\n");
		error500();
		exits("dirreadall");
	}

	fprint(1, "Status: 200 OK\r\n");
	fprint(1, "\r\n");
	fprint(1, "<!DOCTYPE html>\r\n");

	d = dirs;
	while(n-- > 0){
			if(urlencode(url, sizeof(url), d->name) != nil)
				fprint(1, "<a href=\"%s\">%s</a><br>\n", url, d->name);
			else
				fprint(1, "%s<br>\n", 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){
		error404();
		exits(0);
	}
	if((d->mode&DMDIR) != 0){
		dirindex(fd);
		exits(0);
	}

	fprint(1, "Status: 200 OK\r\n");
	if(strcmp(d->name, "text") == 0)
		fprint(1, "Content-type: text/plain; charset=utf-8\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 buf[1024];
	char *requri;

	ARGBEGIN{
	default:
		usage();
	}ARGEND;

	srand(nsec());
	requri = getenv("REQUEST_URI");
	if(requri == nil){
		fprint(2, "CGI variable REQUEST_URI not set!\n");
		error500();
		exits("missing REQUEST_URI");
	}
	requri = urldecode(buf, sizeof(buf), requri);
	if(requri == nil){
		error500();
		exits(0);
	}
	if(strcmp(requri, "/") == 0)
		permredirect("/index.html");
	if(strcmp(requri, "/uploads") == 0)
		permredirect("/uploads/");
	if(strcmp(requri, "/uploads/") == 0){
		error403();
		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();
	error404();
}