xref: /plan9/sys/src/cmd/webfs/http.c (revision b85a83648eec38fe82b6f00adfd7828ceec5ee8d)
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 <libsec.h>
10 #include <auth.h>
11 #include "dat.h"
12 #include "fns.h"
13 
14 char PostContentType[] = "application/octet-stream";
15 int httpdebug;
16 
17 typedef struct HttpState HttpState;
18 struct HttpState
19 {
20 	int fd;
21 	Client *c;
22 	char *location;
23 	char *setcookie;
24 	char *netaddr;
25 	char *credentials;
26 	char autherror[ERRMAX];
27 	Ibuf	b;
28 };
29 
30 static void
31 location(HttpState *hs, char *value)
32 {
33 	if(hs->location == nil)
34 		hs->location = estrdup(value);
35 }
36 
37 static void
38 contenttype(HttpState *hs, char *value)
39 {
40 	if(hs->c->contenttype == nil)
41 		hs->c->contenttype = estrdup(value);
42 }
43 
44 static void
45 setcookie(HttpState *hs, char *value)
46 {
47 	char *s, *t;
48 	Fmt f;
49 
50 	s = hs->setcookie;
51 	fmtstrinit(&f);
52 	if(s)
53 		fmtprint(&f, "%s", s);
54 	fmtprint(&f, "set-cookie: ");
55 	fmtprint(&f, "%s", value);
56 	fmtprint(&f, "\n");
57 	t = fmtstrflush(&f);
58 	if(t){
59 		free(s);
60 		hs->setcookie = t;
61 	}
62 }
63 
64 static char*
65 unquote(char *s, char **ps)
66 {
67 	char *p;
68 
69 	if(*s != '"'){
70 		p = strpbrk(s, " \t\r\n");
71 		*p++ = 0;
72 		*ps = p;
73 		return s;
74 	}
75 	for(p=s+1; *p; p++){
76 		if(*p == '\"'){
77 			*p++ = 0;
78 			break;
79 		}
80 		if(*p == '\\' && *(p+1)){
81 			p++;
82 			continue;
83 		}
84 	}
85 	memmove(s, s+1, p-(s+1));
86 	s[p-(s+1)] = 0;
87 	*ps = p;
88 	return s;
89 }
90 
91 static char*
92 servername(char *addr)
93 {
94 	char *p;
95 
96 	if(strncmp(addr, "tcp!", 4) == 0
97 	|| strncmp(addr, "net!", 4) == 0)
98 		addr += 4;
99 	addr = estrdup(addr);
100 	p = addr+strlen(addr);
101 	if(p>addr && *(p-1) == 's')
102 		p--;
103 	if(p>addr+5 && strcmp(p-5, "!http") == 0)
104 		p[-5] = 0;
105 	return addr;
106 }
107 
108 void
109 wwwauthenticate(HttpState *hs, char *line)
110 {
111 	char cred[64], *user, *pass, *realm, *s, *spec, *name;
112 	Fmt fmt;
113 	UserPasswd *up;
114 
115 	spec = nil;
116 	up = nil;
117 	cred[0] = 0;
118 	hs->autherror[0] = 0;
119 	if(cistrncmp(line, "basic ", 6) != 0){
120 		werrstr("unknown auth: %s", line);
121 		goto error;
122 	}
123 	line += 6;
124 	if(cistrncmp(line, "realm=", 6) != 0){
125 		werrstr("missing realm: %s", line);
126 		goto error;
127 	}
128 	line += 6;
129 	user = hs->c->url->user;
130 	pass = hs->c->url->passwd;
131 	if(user==nil || pass==nil){
132 		realm = unquote(line, &line);
133 		fmtstrinit(&fmt);
134 		name = servername(hs->netaddr);
135 		fmtprint(&fmt, "proto=pass service=http server=%q realm=%q", name, realm);
136 		free(name);
137 		if(hs->c->url->user)
138 			fmtprint(&fmt, " user=%q", hs->c->url->user);
139 		spec = fmtstrflush(&fmt);
140 		if(spec == nil)
141 			goto error;
142 		if((up = auth_getuserpasswd(nil, "%s", spec)) == nil)
143 			goto error;
144 		user = up->user;
145 		pass = up->passwd;
146 	}
147 	if((s = smprint("%s:%s", user, pass)) == nil)
148 		goto error;
149 	free(up);
150 	enc64(cred, sizeof(cred), (uchar*)s, strlen(s));
151 	memset(s, 0, strlen(s));
152 	free(s);
153 	hs->credentials = smprint("Basic %s", cred);
154 	if(hs->credentials == nil)
155 		goto error;
156 	return;
157 
158 error:
159 	free(up);
160 	free(spec);
161 	snprint(hs->autherror, sizeof hs->autherror, "%r");
162 }
163 
164 struct {
165 	char *name;									/* Case-insensitive */
166 	void (*fn)(HttpState *hs, char *value);
167 } hdrtab[] = {
168 	{ "location:", location },
169 	{ "content-type:", contenttype },
170 	{ "set-cookie:", setcookie },
171 	{ "www-authenticate:", wwwauthenticate },
172 };
173 
174 static int
175 httprcode(HttpState *hs)
176 {
177 	int n;
178 	char *p;
179 	char buf[256];
180 
181 	n = readline(&hs->b, buf, sizeof(buf)-1);
182 	if(n <= 0)
183 		return n;
184 	if(httpdebug)
185 		fprint(2, "-> %s\n", buf);
186 	p = strchr(buf, ' ');
187 	if(memcmp(buf, "HTTP/", 5) != 0 || p == nil){
188 		werrstr("bad response from server");
189 		return -1;
190 	}
191 	buf[n] = 0;
192 	return atoi(p+1);
193 }
194 
195 /*
196  *  read a single mime header, collect continuations.
197  *
198  *  this routine assumes that there is a blank line twixt
199  *  the header and the message body, otherwise bytes will
200  *  be lost.
201  */
202 static int
203 getheader(HttpState *hs, char *buf, int n)
204 {
205 	char *p, *e;
206 	int i;
207 
208 	n--;
209 	p = buf;
210 	for(e = p + n; ; p += i){
211 		i = readline(&hs->b, p, e-p);
212 		if(i < 0)
213 			return i;
214 
215 		if(p == buf){
216 			/* first line */
217 			if(strchr(buf, ':') == nil)
218 				break;		/* end of headers */
219 		} else {
220 			/* continuation line */
221 			if(*p != ' ' && *p != '\t'){
222 				unreadline(&hs->b, p);
223 				*p = 0;
224 				break;		/* end of this header */
225 			}
226 		}
227 	}
228 
229 	if(httpdebug)
230 		fprint(2, "-> %s\n", buf);
231 	return p-buf;
232 }
233 
234 static int
235 httpheaders(HttpState *hs)
236 {
237 	char buf[2048];
238 	char *p;
239 	int i, n;
240 
241 	for(;;){
242 		n = getheader(hs, buf, sizeof(buf));
243 		if(n < 0)
244 			return -1;
245 		if(n == 0)
246 			return 0;
247 		//	print("http header: '%.*s'\n", n, buf);
248 		for(i = 0; i < nelem(hdrtab); i++){
249 			n = strlen(hdrtab[i].name);
250 			if(cistrncmp(buf, hdrtab[i].name, n) == 0){
251 				/* skip field name and leading white */
252 				p = buf + n;
253 				while(*p == ' ' || *p == '\t')
254 					p++;
255 				(*hdrtab[i].fn)(hs, p);
256 				break;
257 			}
258 		}
259 	}
260 }
261 
262 int
263 httpopen(Client *c, Url *url)
264 {
265 	int fd, code, redirect, authenticate;
266 	char *cookies;
267 	Ioproc *io;
268 	HttpState *hs;
269 
270 	if(httpdebug)
271 		fprint(2, "httpopen\n");
272 	io = c->io;
273 	hs = emalloc(sizeof(*hs));
274 	hs->c = c;
275 	hs->netaddr = estrdup(netmkaddr(url->host, 0, url->scheme));
276 	c->aux = hs;
277 	if(httpdebug)
278 		fprint(2, "dial %s\n", hs->netaddr);
279 	fd = iotlsdial(io, hs->netaddr, 0, 0, 0, url->ischeme==UShttps);
280 	if(fd < 0){
281 	Error:
282 		if(httpdebug)
283 			fprint(2, "iodial: %r\n");
284 		free(hs->netaddr);
285 		close(hs->fd);
286 		hs->fd = -1;
287 		free(hs);
288 		c->aux = nil;
289 		return -1;
290 	}
291 	hs->fd = fd;
292 	if(httpdebug)
293 		fprint(2, "<- %s %s HTTP/1.0\n<- Host: %s\n",
294 			c->havepostbody? "POST": " GET", url->http.page_spec, url->host);
295 	ioprint(io, fd, "%s %s HTTP/1.0\r\nHost: %s\r\n",
296 		c->havepostbody? "POST" : "GET", url->http.page_spec, url->host);
297 	if(httpdebug)
298 		fprint(2, "<- User-Agent: %s\n", c->ctl.useragent);
299 	if(c->ctl.useragent)
300 		ioprint(io, fd, "User-Agent: %s\r\n", c->ctl.useragent);
301 	if(c->ctl.sendcookies){
302 		/* should we use url->page here?  sometimes it is nil. */
303 		cookies = httpcookies(url->host, url->http.page_spec, 0);
304 		if(cookies && cookies[0])
305 			ioprint(io, fd, "%s", cookies);
306 		if(httpdebug)
307 			fprint(2, "<- %s", cookies);
308 		free(cookies);
309 	}
310 	if(c->havepostbody){
311 		ioprint(io, fd, "Content-type: %s\r\n", PostContentType);
312 		ioprint(io, fd, "Content-length: %ud\r\n", c->npostbody);
313 		if(httpdebug){
314 			fprint(2, "<- Content-type: %s\n", PostContentType);
315 			fprint(2, "<- Content-length: %ud\n", c->npostbody);
316 		}
317 	}
318 	if(c->authenticate){
319 		ioprint(io, fd, "Authorization: %s\r\n", c->authenticate);
320 		if(httpdebug)
321 			fprint(2, "<- Authorization: %s\n", c->authenticate);
322 	}
323 	ioprint(io, fd, "\r\n");
324 	if(c->havepostbody)
325 		if(iowrite(io, fd, c->postbody, c->npostbody) != c->npostbody)
326 			goto Error;
327 
328 	c->havepostbody = 0;
329 	redirect = 0;
330 	authenticate = 0;
331 	initibuf(&hs->b, io, fd);
332 	code = httprcode(hs);
333 
334 	switch(code){
335 	case -1:	/* connection timed out */
336 		goto Error;
337 
338 /*
339 	case Eof:
340 		werrstr("EOF from HTTP server");
341 		goto Error;
342 */
343 
344 	case 200:	/* OK */
345 	case 201:	/* Created */
346 	case 202:	/* Accepted */
347 	case 204:	/* No Content */
348 #ifdef NOT_DEFINED
349 		if(ofile == nil && r->start != 0)
350 			sysfatal("page changed underfoot");
351 #endif
352 		break;
353 
354 	case 206:	/* Partial Content */
355 		werrstr("Partial Content (206)");
356 		goto Error;
357 
358 	case 301:	/* Moved Permanently */
359 	case 302:	/* Moved Temporarily */
360 		redirect = 1;
361 		break;
362 
363 	case 304:	/* Not Modified */
364 		break;
365 
366 	case 400:	/* Bad Request */
367 		werrstr("Bad Request (400)");
368 		goto Error;
369 
370 	case 401:	/* Unauthorized */
371 		if(c->authenticate){
372 			werrstr("Authentication failed (401)");
373 			goto Error;
374 		}
375 		authenticate = 1;
376 		break;
377 	case 402:	/* ??? */
378 		werrstr("Unauthorized (402)");
379 		goto Error;
380 
381 	case 403:	/* Forbidden */
382 		werrstr("Forbidden by server (403)");
383 		goto Error;
384 
385 	case 404:	/* Not Found */
386 		werrstr("Not found on server (404)");
387 		goto Error;
388 
389 	case 405:	/* Method Not Allowed  */
390 		werrstr("Method not allowed (405)");
391 		goto Error;
392 
393 	case 407:	/* Proxy auth */
394 		werrstr("Proxy authentication required (407)");
395 		goto Error;
396 
397 	case 500:	/* Internal server error */
398 		werrstr("Server choked (500)");
399 		goto Error;
400 
401 	case 501:	/* Not implemented */
402 		werrstr("Server can't do it (501)");
403 		goto Error;
404 
405 	case 502:	/* Bad gateway */
406 		werrstr("Bad gateway (502)");
407 		goto Error;
408 
409 	case 503:	/* Service unavailable */
410 		werrstr("Service unavailable (503)");
411 		goto Error;
412 
413 	default:
414 		/* Bogus: we should treat unknown code XYZ as code X00 */
415 		werrstr("Unknown response code %d", code);
416 		goto Error;
417 	}
418 
419 	if(httpheaders(hs) < 0)
420 		goto Error;
421 	if(c->ctl.acceptcookies && hs->setcookie)
422 		httpsetcookie(hs->setcookie, url->host, url->path);
423 	if(authenticate){
424 		if(!hs->credentials){
425 			if(hs->autherror[0])
426 				werrstr("%s", hs->autherror);
427 			else
428 				werrstr("unauthorized; no www-authenticate: header");
429 			return -1;
430 		}
431 		c->authenticate = hs->credentials;
432 		hs->credentials = nil;
433 	}
434 	if(redirect){
435 		if(!hs->location){
436 			werrstr("redirection without Location: header");
437 			return -1;
438 		}
439 		c->redirect = hs->location;
440 		hs->location = nil;
441 	}
442 	return 0;
443 }
444 
445 int
446 httpread(Client *c, Req *r)
447 {
448 	char *dst;
449 	HttpState *hs;
450 	int n;
451 	long rlen, tot, len;
452 
453 	hs = c->aux;
454 	dst = r->ofcall.data;
455 	len = r->ifcall.count;
456 	tot = 0;
457 	while (tot < len){
458 		rlen = len - tot;
459 		n = readibuf(&hs->b, dst + tot, rlen);
460 		if(n == 0)
461 			break;
462 		else if(n < 0){
463 			if(tot == 0)
464 				return -1;
465 			else
466 				return tot;
467 		}
468 		tot += n;
469 	}
470 	r->ofcall.count = tot;
471 	return 0;
472 }
473 
474 void
475 httpclose(Client *c)
476 {
477 	HttpState *hs;
478 
479 	hs = c->aux;
480 	if(hs == nil)
481 		return;
482 	ioclose(c->io, hs->fd);
483 	hs->fd = -1;
484 	free(hs->location);
485 	free(hs->setcookie);
486 	free(hs->netaddr);
487 	free(hs->credentials);
488 	free(hs);
489 	c->aux = nil;
490 }
491 
492