xref: /plan9-contrib/sys/src/cmd/webfs/http.c (revision a6a9e07217f318acf170f99684a55fba5200524f)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <ip.h>
5 #include <plumb.h>
6 #include <thread.h>
7 #include <fcall.h>
8 #include <9p.h>
9 #include "dat.h"
10 #include "fns.h"
11 
12 char PostContentType[] = "application/octet-stream";
13 int httpdebug;
14 
15 typedef struct HttpState HttpState;
16 struct HttpState
17 {
18 	int fd;
19 	Client *c;
20 	char *location;
21 	char *setcookie;
22 	char *netaddr;
23 	Ibuf	b;
24 };
25 
26 static void
27 location(HttpState *hs, char *value)
28 {
29 	if(hs->location == nil)
30 		hs->location = estrdup(value);
31 }
32 
33 static void
34 contenttype(HttpState *hs, char *value)
35 {
36 	if(hs->c->contenttype == nil)
37 		hs->c->contenttype = estrdup(value);
38 }
39 
40 static void
41 setcookie(HttpState *hs, char *value)
42 {
43 	char *s, *t;
44 	Fmt f;
45 
46 	s = hs->setcookie;
47 	fmtstrinit(&f);
48 	if(s)
49 		fmtprint(&f, "%s", s);
50 	fmtprint(&f, "set-cookie: ");
51 	fmtprint(&f, "%s", value);
52 	fmtprint(&f, "\n");
53 	t = fmtstrflush(&f);
54 	if(t){
55 		free(s);
56 		hs->setcookie = t;
57 	}
58 }
59 
60 struct {
61 	char *name;									/* Case-insensitive */
62 	void (*fn)(HttpState *hs, char *value);
63 } hdrtab[] = {
64 	{ "location:", location },
65 	{ "content-type:", contenttype },
66 	{ "set-cookie:", setcookie },
67 };
68 
69 static int
70 httprcode(HttpState *hs)
71 {
72 	int n;
73 	char *p;
74 	char buf[256];
75 
76 	n = readline(&hs->b, buf, sizeof(buf)-1);
77 	if(n <= 0)
78 		return n;
79 	if(httpdebug)
80 		fprint(2, "-> %s\n", buf);
81 	p = strchr(buf, ' ');
82 	if(memcmp(buf, "HTTP/", 5) != 0 || p == nil){
83 		werrstr("bad response from server");
84 		return -1;
85 	}
86 	buf[n] = 0;
87 	return atoi(p+1);
88 }
89 
90 /*
91  *  read a single mime header, collect continuations.
92  *
93  *  this routine assumes that there is a blank line twixt
94  *  the header and the message body, otherwise bytes will
95  *  be lost.
96  */
97 static int
98 getheader(HttpState *hs, char *buf, int n)
99 {
100 	char *p, *e;
101 	int i;
102 
103 	n--;
104 	p = buf;
105 	for(e = p + n; ; p += i){
106 		i = readline(&hs->b, p, e-p);
107 		if(i < 0)
108 			return i;
109 
110 		if(p == buf){
111 			/* first line */
112 			if(strchr(buf, ':') == nil)
113 				break;		/* end of headers */
114 		} else {
115 			/* continuation line */
116 			if(*p != ' ' && *p != '\t'){
117 				unreadline(&hs->b, p);
118 				*p = 0;
119 				break;		/* end of this header */
120 			}
121 		}
122 	}
123 
124 	if(httpdebug)
125 		fprint(2, "-> %s\n", buf);
126 	return p-buf;
127 }
128 
129 static int
130 httpheaders(HttpState *hs)
131 {
132 	char buf[2048];
133 	char *p;
134 	int i, n;
135 
136 	for(;;){
137 		n = getheader(hs, buf, sizeof(buf));
138 		if(n < 0)
139 			return -1;
140 		if (n == 0)
141 			return 0;
142 //print("http header: '%.*s'\n", n, buf);
143 		for(i = 0; i < nelem(hdrtab); i++){
144 			n = strlen(hdrtab[i].name);
145 			if(cistrncmp(buf, hdrtab[i].name, n) == 0){
146 				/* skip field name and leading white */
147 				p = buf + n;
148 				while(*p == ' ' || *p == '\t')
149 					p++;
150 				(*hdrtab[i].fn)(hs, p);
151 				break;
152 			}
153 		}
154 	}
155 	return 0;
156 }
157 
158 int
159 httpopen(Client *c, Url *url)
160 {
161 	int fd, code, redirect;
162 	char *cookies;
163 	Ioproc *io;
164 	HttpState *hs;
165 
166 	if(httpdebug)
167 		fprint(2, "httpopen\n");
168 	io = c->io;
169 	hs = emalloc(sizeof(*hs));
170 	hs->c = c;
171 	hs->netaddr = estrdup(netmkaddr(url->host, 0, url->scheme));
172 	c->aux = hs;
173 	if(httpdebug)
174 		fprint(2, "dial %s\n", hs->netaddr);
175 	fd = iotlsdial(io, hs->netaddr, 0, 0, 0, url->ischeme==UShttps);
176 	if(fd < 0){
177 	Error:
178 		if(httpdebug)
179 			fprint(2, "iodial: %r\n");
180 		free(hs->netaddr);
181 		free(hs);
182 		close(hs->fd);
183 			hs->fd = -1;
184 		c->aux = nil;
185 		return -1;
186 	}
187 	hs->fd = fd;
188 	if(httpdebug)
189 		fprint(2, "<- %s %s HTTP/1.0\n<- Host: %s\n",
190 			c->havepostbody? "POST": " GET", url->http.page_spec, url->host);
191 	ioprint(io, fd, "%s %s HTTP/1.0\r\nHost: %s\r\n",
192 		c->havepostbody? "POST" : "GET", url->http.page_spec, url->host);
193 	if(httpdebug)
194 		fprint(2, "<- User-Agent: %s\n", c->ctl.useragent);
195 	if(c->ctl.useragent)
196 		ioprint(io, fd, "User-Agent: %s\r\n", c->ctl.useragent);
197 	if(c->ctl.sendcookies){
198 		/* should we use url->page here?  sometimes it is nil. */
199 		cookies = httpcookies(url->host, url->http.page_spec, 0);
200 		if(cookies && cookies[0])
201 			ioprint(io, fd, "%s", cookies);
202 		if(httpdebug)
203 			fprint(2, "<- %s", cookies);
204 		free(cookies);
205 	}
206 	if(c->havepostbody){
207 		ioprint(io, fd, "Content-type: %s\r\n", PostContentType);
208 		ioprint(io, fd, "Content-length: %ud\r\n", c->npostbody);
209 		if(httpdebug){
210 			fprint(2, "<- Content-type: %s\n", PostContentType);
211 			fprint(2, "<- Content-length: %ud\n", c->npostbody);
212 		}
213 	}
214 	ioprint(io, fd, "\r\n");
215 	if(c->havepostbody)
216 		if(iowrite(io, fd, c->postbody, c->npostbody) != c->npostbody)
217 			goto Error;
218 
219 	c->havepostbody = 0;
220 	redirect = 0;
221 	initibuf(&hs->b, io, fd);
222 	code = httprcode(hs);
223 
224 	switch(code){
225 	case -1:	/* connection timed out */
226 		goto Error;
227 
228 /*
229 	case Eof:
230 		werrstr("EOF from HTTP server");
231 		goto Error;
232 */
233 
234 	case 200:	/* OK */
235 	case 201:	/* Created */
236 	case 202:	/* Accepted */
237 	case 204:	/* No Content */
238 #ifdef NOT_DEFINED
239 		if(ofile == nil && r->start != 0)
240 			sysfatal("page changed underfoot");
241 #endif
242 		break;
243 
244 	case 206:	/* Partial Content */
245 		werrstr("Partial Content (206)");
246 		goto Error;
247 
248 	case 301:	/* Moved Permanently */
249 	case 302:	/* Moved Temporarily */
250 		redirect = 1;
251 		break;
252 
253 	case 304:	/* Not Modified */
254 		break;
255 
256 	case 400:	/* Bad Request */
257 		werrstr("Bad Request (400)");
258 		goto Error;
259 
260 	case 401:	/* Unauthorized */
261 	case 402:	/* ??? */
262 		werrstr("Unauthorized (401,402)");
263 		goto Error;
264 
265 	case 403:	/* Forbidden */
266 		werrstr("Forbidden by server (403)");
267 		goto Error;
268 
269 	case 404:	/* Not Found */
270 		werrstr("Not found on server (404)");
271 		goto Error;
272 
273 	case 500:	/* Internal server error */
274 		werrstr("Server choked (500)");
275 		goto Error;
276 
277 	case 501:	/* Not implemented */
278 		werrstr("Server can't do it (501)");
279 		goto Error;
280 
281 	case 502:	/* Bad gateway */
282 		werrstr("Bad gateway (502)");
283 		goto Error;
284 
285 	case 503:	/* Service unavailable */
286 		werrstr("Service unavailable (503)");
287 		goto Error;
288 
289 	default:
290 		/* Bogus: we should treat unknown code XYZ as code X00 */
291 		werrstr("Unknown response code %d", code);
292 		goto Error;
293 	}
294 
295 	if(httpheaders(hs) < 0)
296 		goto Error;
297 	if(c->ctl.acceptcookies && hs->setcookie)
298 		httpsetcookie(hs->setcookie, url->host, url->path);
299 	if(redirect){
300 		if(!hs->location){
301 			werrstr("redirection without Location: header");
302 			return -1;
303 		}
304 		c->redirect = hs->location;
305 		hs->location = nil;
306 	}
307 	return 0;
308 }
309 
310 int
311 httpread(Client *c, Req *r)
312 {
313 	char *dst;
314 	HttpState *hs;
315 	int n;
316 	long rlen, tot, len;
317 
318 	hs = c->aux;
319 	dst = r->ofcall.data;
320 	len = r->ifcall.count;
321 	tot = 0;
322 	while (tot < len){
323 		rlen = len - tot;
324 		n = readibuf(&hs->b, dst + tot, rlen);
325 		if(n == 0)
326 			break;
327 		else if(n < 0){
328 			if(tot == 0)
329 				return -1;
330 			else
331 				return tot;
332 		}
333 		tot += n;
334 	}
335 	r->ofcall.count = tot;
336 	return 0;
337 }
338 
339 void
340 httpclose(Client *c)
341 {
342 	HttpState *hs;
343 
344 	hs = c->aux;
345 	if(hs == nil)
346 		return;
347 	ioclose(c->io, hs->fd);
348 	hs->fd = -1;
349 	free(hs->location);
350 	free(hs->setcookie);
351 	free(hs->netaddr);
352 	free(hs);
353 	c->aux = nil;
354 }
355 
356