14 char PostContentType[] = "application/x-www-form-urlencoded";
17 typedef struct HttpState HttpState;
26 char autherror[ERRMAX];
31 location(HttpState *hs, char *value)
33 if(hs->location == nil)
34 hs->location = estrdup(value);
38 contenttype(HttpState *hs, char *value)
40 if(hs->c->contenttype != nil)
41 free(hs->c->contenttype);
42 hs->c->contenttype = estrdup(value);
46 setcookie(HttpState *hs, char *value)
54 fmtprint(&f, "%s", s);
55 fmtprint(&f, "set-cookie: ");
56 fmtprint(&f, "%s", value);
66 unquote(char *s, char **ps)
71 p = strpbrk(s, " \t\r\n");
81 if(*p == '\\' && *(p+1)){
86 memmove(s, s+1, p-(s+1));
93 servername(char *addr)
97 if(strncmp(addr, "tcp!", 4) == 0
98 || strncmp(addr, "net!", 4) == 0)
100 addr = estrdup(addr);
101 p = addr+strlen(addr);
102 if(p>addr && *(p-1) == 's')
104 if(p>addr+5 && strcmp(p-5, "!http") == 0)
110 wwwauthenticate(HttpState *hs, char *line)
112 char cred[64], *user, *pass, *realm, *s, *spec, *name;
119 hs->autherror[0] = 0;
120 if(cistrncmp(line, "basic ", 6) != 0){
121 werrstr("unknown auth: %s", line);
125 if(cistrncmp(line, "realm=", 6) != 0){
126 werrstr("missing realm: %s", line);
130 user = hs->c->url->user;
131 pass = hs->c->url->passwd;
132 if(user==nil || pass==nil){
133 realm = unquote(line, &line);
135 name = servername(hs->netaddr);
136 fmtprint(&fmt, "proto=pass service=http server=%q realm=%q", name, realm);
139 fmtprint(&fmt, " user=%q", hs->c->url->user);
140 spec = fmtstrflush(&fmt);
143 if((up = auth_getuserpasswd(nil, "%s", spec)) == nil)
148 if((s = smprint("%s:%s", user, pass)) == nil)
151 enc64(cred, sizeof(cred), (uchar*)s, strlen(s));
152 memset(s, 0, strlen(s));
154 hs->credentials = smprint("Basic %s", cred);
155 if(hs->credentials == nil)
162 snprint(hs->autherror, sizeof hs->autherror, "%r");
163 fprint(2, "%s: Authentication failed: %r\n", argv0);
167 char *name; /* Case-insensitive */
168 void (*fn)(HttpState *hs, char *value);
170 { "location:", location },
171 { "content-type:", contenttype },
172 { "set-cookie:", setcookie },
173 { "www-authenticate:", wwwauthenticate },
177 httprcode(HttpState *hs)
183 n = readline(&hs->b, buf, sizeof(buf)-1);
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");
198 * read a single mime header, collect continuations.
200 * this routine assumes that there is a blank line twixt
201 * the header and the message body, otherwise bytes will
205 getheader(HttpState *hs, char *buf, int n)
212 for(e = p + n; ; p += i){
213 i = readline(&hs->b, p, e-p);
219 if(strchr(buf, ':') == nil)
220 break; /* end of headers */
222 /* continuation line */
223 if(*p != ' ' && *p != '\t'){
224 unreadline(&hs->b, p);
226 break; /* end of this header */
232 fprint(2, "-> %s\n", buf);
237 httpheaders(HttpState *hs)
244 n = getheader(hs, buf, sizeof(buf));
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 */
255 while(*p == ' ' || *p == '\t')
257 (*hdrtab[i].fn)(hs, p);
265 httpopen(Client *c, Url *url)
267 int fd, code, redirect, authenticate;
274 fprint(2, "httpopen\n");
276 hs = emalloc(sizeof(*hs));
282 service = url->scheme;
283 hs->netaddr = estrdup(netmkaddr(url->host, 0, service));
286 fprint(2, "dial %s\n", hs->netaddr);
287 fprint(2, "dial port: %s\n", url->port);
289 fd = iotlsdial(io, hs->netaddr, 0, 0, 0, url->ischeme==UShttps);
293 fprint(2, "iodial: %r\n");
297 free(hs->credentials);
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);
312 fprint(2, "<- User-Agent: %s\n", 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);
322 fprint(2, "<- %s", cookies);
326 ioprint(io, fd, "Content-type: %s\r\n", PostContentType);
327 ioprint(io, fd, "Content-length: %ud\r\n", c->npostbody);
329 fprint(2, "<- Content-type: %s\n", PostContentType);
330 fprint(2, "<- Content-length: %ud\n", c->npostbody);
334 ioprint(io, fd, "Authorization: %s\r\n", c->authenticate);
336 fprint(2, "<- Authorization: %s\n", c->authenticate);
338 ioprint(io, fd, "\r\n");
340 if(iowrite(io, fd, c->postbody, c->npostbody) != c->npostbody)
345 initibuf(&hs->b, io, fd);
346 code = httprcode(hs);
349 case -1: /* connection timed out */
354 werrstr("EOF from HTTP server");
359 case 201: /* Created */
360 case 202: /* Accepted */
361 case 204: /* No Content */
362 case 205: /* Reset Content */
364 if(ofile == nil && r->start != 0)
365 sysfatal("page changed underfoot");
369 case 206: /* Partial Content */
370 werrstr("Partial Content (206)");
373 case 303: /* See Other */
375 case 301: /* Moved Permanently */
376 case 302: /* Moved Temporarily */
377 case 307: /* Temporary Redirect */
381 case 304: /* Not Modified */
384 case 400: /* Bad Request */
385 werrstr("Bad Request (400)");
388 case 401: /* Unauthorized */
390 werrstr("Authentication failed (401)");
395 case 402: /* Payment Required */
396 werrstr("Payment Required (402)");
399 case 403: /* Forbidden */
400 werrstr("Forbidden by server (403)");
403 case 404: /* Not Found */
404 werrstr("Not found on server (404)");
407 case 405: /* Method Not Allowed */
408 werrstr("Method not allowed (405)");
411 case 406: /* Not Acceptable */
412 werrstr("Not Acceptable (406)");
415 case 407: /* Proxy auth */
416 werrstr("Proxy authentication required (407)");
419 case 408: /* Request Timeout */
420 werrstr("Request Timeout (408)");
423 case 409: /* Conflict */
424 werrstr("Conflict (409)");
428 werrstr("Gone (410)");
431 case 411: /* Length Required */
432 werrstr("Length Required (411)");
435 case 412: /* Precondition Failed */
436 werrstr("Precondition Failed (412)");
439 case 413: /* Request Entity Too Large */
440 werrstr("Request Entity Too Large (413)");
443 case 414: /* Request-URI Too Long */
444 werrstr("Request-URI Too Long (414)");
447 case 415: /* Unsupported Media Type */
448 werrstr("Unsupported Media Type (415)");
451 case 416: /* Requested Range Not Satisfiable */
452 werrstr("Requested Range Not Satisfiable (416)");
455 case 417: /* Expectation Failed */
456 werrstr("Expectation Failed (417)");
459 case 500: /* Internal server error */
460 werrstr("Server choked (500)");
463 case 501: /* Not implemented */
464 werrstr("Server can't do it (501)");
467 case 502: /* Bad gateway */
468 werrstr("Bad gateway (502)");
471 case 503: /* Service unavailable */
472 werrstr("Service unavailable (503)");
476 /* Bogus: we should treat unknown code XYZ as code X00 */
477 werrstr("Unknown response code %d", code);
481 if(httpheaders(hs) < 0)
483 if(c->ctl.acceptcookies && hs->setcookie)
484 httpsetcookie(hs->setcookie, url->host, url->path);
486 if(!hs->credentials){
488 werrstr("%s", hs->autherror);
490 werrstr("unauthorized; no www-authenticate: header");
493 c->authenticate = hs->credentials;
494 hs->credentials = nil;
495 }else if(c->authenticate)
499 werrstr("redirection without Location: header");
502 c->redirect = hs->location;
509 httpread(Client *c, Req *r)
515 n = readibuf(&hs->b, r->ofcall.data, r->ifcall.count);
532 ioclose(c->io, hs->fd);
537 free(hs->credentials);