xref: /plan9/sys/src/cmd/ip/httpd/save.c (revision 9a747e4fd48b9f4522c70c07e8f882a15030f964)
1 /*
2  * for GET or POST to /magic/save/foo.
3  * add incoming data to foo.data.
4  * send foo.html as reply.
5  *
6  * supports foo.data with "exclusive use" mode to prevent interleaved saves.
7  * thus http://cm.bell-labs.com/magic/save/t?args should access:
8  * -lrw-rw--w- M 21470 ehg web 1533 May 21 18:19 /usr/web/save/t.data
9  * --rw-rw-r-- M 21470 ehg web   73 May 21 18:17 /usr/web/save/t.html
10 */
11 #include <u.h>
12 #include <libc.h>
13 #include <bio.h>
14 #include "httpd.h"
15 #include "httpsrv.h"
16 
17 enum
18 {
19 	MaxLog		= 24*1024,		/* limit on length of any one log request */
20 	LockSecs	= MaxLog/500,		/* seconds to wait before giving up on opening the data file */
21 };
22 
23 static int
dangerous(char * s)24 dangerous(char *s)
25 {
26 	if(s == nil)
27 		return 1;
28 
29 	/*
30 	 * This check shouldn't be needed;
31 	 * filename folding is already supposed to have happened.
32 	 * But I'm paranoid.
33 	 */
34 	while(s = strchr(s,'/')){
35 		if(s[1]=='.' && s[2]=='.')
36 			return 1;
37 		s++;
38 	}
39 	return 0;
40 }
41 
42 /*
43  * open a file which might be locked.
44  * if it is, spin until available
45  */
46 int
openLocked(char * file,int mode)47 openLocked(char *file, int mode)
48 {
49 	char buf[ERRMAX];
50 	int tries, fd;
51 
52 	for(tries = 0; tries < LockSecs*2; tries++){
53 		fd = open(file, mode);
54 		if(fd >= 0)
55 			return fd;
56 		errstr(buf, sizeof buf);
57 		if(strstr(buf, "locked") == nil)
58 			break;
59 		sleep(500);
60 	}
61 	return -1;
62 }
63 
64 void
main(int argc,char ** argv)65 main(int argc, char **argv)
66 {
67 	HConnect *c;
68 	Dir *dir;
69 	Hio *hin, *hout;
70 	char *s, *t, *fn;
71 	int n, nfn, datafd, htmlfd;
72 
73 	c = init(argc, argv);
74 
75 	if(dangerous(c->req.uri)){
76 		hfail(c, HSyntax);
77 		exits("failed");
78 	}
79 
80 	if(hparseheaders(c, HSTIMEOUT) < 0)
81 		exits("failed");
82 	hout = &c->hout;
83 	if(c->head.expectother){
84 		hfail(c, HExpectFail, nil);
85 		exits("failed");
86 	}
87 	if(c->head.expectcont){
88 		hprint(hout, "100 Continue\r\n");
89 		hprint(hout, "\r\n");
90 		hflush(hout);
91 	}
92 
93 	s = nil;
94 	if(strcmp(c->req.meth, "POST") == 0){
95 		hin = hbodypush(&c->hin, c->head.contlen, c->head.transenc);
96 		if(hin != nil){
97 			alarm(HSTIMEOUT);
98 			s = hreadbuf(hin, hin->pos);
99 			alarm(0);
100 		}
101 		if(s == nil){
102 			hfail(c, HBadReq, nil);
103 			exits("failed");
104 		}
105 		t = strchr(s, '\n');
106 		if(t != nil)
107 			*t = '\0';
108 	}else if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0){
109 		hunallowed(c, "GET, HEAD, PUT");
110 		exits("unallowed");
111 	}else
112 		s = c->req.search;
113 	if(s == nil){
114 		hfail(c, HNoData, "save");
115 		exits("failed");
116 	}
117 
118 	if(strlen(s) > MaxLog)
119 		s[MaxLog] = '\0';
120 	n = snprint(c->xferbuf, HBufSize, "at %ld %s\n", time(0), s);
121 
122 
123 	nfn = strlen(c->req.uri) + 64;
124 	fn = halloc(c, nfn);
125 
126 	/*
127 	 * open file descriptors & write log line
128 	 */
129 	snprint(fn, nfn, "/usr/web/save/%s.html", c->req.uri);
130 	htmlfd = open(fn, OREAD);
131 	if(htmlfd < 0 || (dir = dirfstat(htmlfd)) == nil){
132 		hfail(c, HNotFound, c->req.uri);
133 		exits("failed");
134 		return;
135 	}
136 
137 	snprint(fn, nfn, "/usr/web/save/%s.data", c->req.uri);
138 	datafd = openLocked(fn, OWRITE);
139 	if(datafd < 0){
140 		errstr(c->xferbuf, sizeof c->xferbuf);
141 		if(strstr(c->xferbuf, "locked") != nil)
142 			hfail(c, HTempFail, c->req.uri);
143 		else
144 			hfail(c, HNotFound, c->req.uri);
145 		exits("failed");
146 	}
147 	seek(datafd, 0, 2);
148 	write(datafd, c->xferbuf, n);
149 	close(datafd);
150 
151 	sendfd(c, htmlfd, dir, hmkcontent(c, "text", "html", nil), nil);
152 
153 	exits(nil);
154 }
155