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