]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/webfs/http.c
fix POST/303 redirection loop
[plan9front.git] / sys / src / cmd / webfs / http.c
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
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                 free(hs->c->contenttype);
42         hs->c->contenttype = estrdup(value);
43 }
44
45 static void
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*
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*
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
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
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
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
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
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 303:       /* See Other */
374                 c->havepostbody = 0;
375         case 301:       /* Moved Permanently */
376         case 302:       /* Moved Temporarily */
377         case 307: /* Temporary Redirect  */
378                 redirect = 1;
379                 break;
380
381         case 304:       /* Not Modified */
382                 break;
383
384         case 400:       /* Bad Request */
385                 werrstr("Bad Request (400)");
386                 goto Error;
387
388         case 401:       /* Unauthorized */
389                 if(c->authenticate){
390                         werrstr("Authentication failed (401)");
391                         goto Error;
392                 }
393                 authenticate = 1;
394                 break;
395         case 402:       /* Payment Required */
396                 werrstr("Payment Required (402)");
397                 goto Error;
398
399         case 403:       /* Forbidden */
400                 werrstr("Forbidden by server (403)");
401                 goto Error;
402
403         case 404:       /* Not Found */
404                 werrstr("Not found on server (404)");
405                 goto Error;
406
407         case 405:       /* Method Not Allowed  */
408                 werrstr("Method not allowed (405)");
409                 goto Error;
410
411         case 406: /* Not Acceptable */
412                 werrstr("Not Acceptable (406)");
413                 goto Error;
414
415         case 407:       /* Proxy auth */
416                 werrstr("Proxy authentication required (407)");
417                 goto Error;
418
419         case 408: /* Request Timeout */
420                 werrstr("Request Timeout (408)");
421                 goto Error;
422
423         case 409: /* Conflict */
424                 werrstr("Conflict  (409)");
425                 goto Error;
426         
427         case 410: /* Gone */
428                 werrstr("Gone  (410)");
429                 goto Error;
430         
431         case 411: /* Length Required */
432                 werrstr("Length Required  (411)");
433                 goto Error;
434         
435         case 412: /* Precondition Failed */
436                 werrstr("Precondition Failed  (412)");
437                 goto Error;
438         
439         case 413: /* Request Entity Too Large */
440                 werrstr("Request Entity Too Large  (413)");
441                 goto Error;
442         
443         case 414: /* Request-URI Too Long */
444                 werrstr("Request-URI Too Long  (414)");
445                 goto Error;
446         
447         case 415: /* Unsupported Media Type */
448                 werrstr("Unsupported Media Type  (415)");
449                 goto Error;
450         
451         case 416: /* Requested Range Not Satisfiable */
452                 werrstr("Requested Range Not Satisfiable  (416)");
453                 goto Error;
454         
455         case 417: /* Expectation Failed */
456                 werrstr("Expectation Failed  (417)");
457                 goto Error;
458
459         case 500:       /* Internal server error */
460                 werrstr("Server choked (500)");
461                 goto Error;
462
463         case 501:       /* Not implemented */
464                 werrstr("Server can't do it (501)");
465                 goto Error;
466
467         case 502:       /* Bad gateway */
468                 werrstr("Bad gateway (502)");
469                 goto Error;
470
471         case 503:       /* Service unavailable */
472                 werrstr("Service unavailable (503)");
473                 goto Error;
474         
475         default:
476                 /* Bogus: we should treat unknown code XYZ as code X00 */
477                 werrstr("Unknown response code %d", code);
478                 goto Error;
479         }
480
481         if(httpheaders(hs) < 0)
482                 goto Error;
483         if(c->ctl.acceptcookies && hs->setcookie)
484                 httpsetcookie(hs->setcookie, url->host, url->path);
485         if(authenticate){
486                 if(!hs->credentials){
487                         if(hs->autherror[0])
488                                 werrstr("%s", hs->autherror);
489                         else
490                                 werrstr("unauthorized; no www-authenticate: header");
491                         goto Error;
492                 }
493                 c->authenticate = hs->credentials;
494                 hs->credentials = nil;
495         }else if(c->authenticate)
496                 c->authenticate = 0;
497         if(redirect){
498                 if(!hs->location){
499                         werrstr("redirection without Location: header");
500                         goto Error;
501                 }
502                 c->redirect = hs->location;
503                 hs->location = nil;
504         }
505         return 0;
506 }
507
508 int
509 httpread(Client *c, Req *r)
510 {
511         HttpState *hs;
512         long n;
513
514         hs = c->aux;
515         n = readibuf(&hs->b, r->ofcall.data, r->ifcall.count);
516         if(n < 0)
517                 return -1;
518
519         r->ofcall.count = n;
520         return 0;
521 }
522
523 void
524 httpclose(Client *c)
525 {
526         HttpState *hs;
527
528         hs = c->aux;
529         if(hs == nil)
530                 return;
531         if(hs->fd >= 0)
532                 ioclose(c->io, hs->fd);
533         hs->fd = -1;
534         free(hs->location);
535         free(hs->setcookie);
536         free(hs->netaddr);
537         free(hs->credentials);
538         free(hs);
539         c->aux = nil;
540 }