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