int hsettype(HConnect *c, char *type) { Hio *hout; int r; r = preq(c); if(r < 0) return r; hout = &c->hout; if(c->req.vermaj){ hokheaders(c); hprint(hout, "Content-type: %s\r\n", type); if(http11(c)) hprint(hout, "Transfer-Encoding: chunked\r\n"); hprint(hout, "\r\n"); } if(http11(c)) hxferenc(hout, 1); else c->head.closeit = 1; return 0; }
/* * check time and entity tag conditions. */ int checkreq(HConnect *c, HContent *type, HContent *enc, long mtime, char *etag) { Hio *hout; int m; hout = &c->hout; if(c->req.vermaj >= 1 && c->req.vermin >= 1 && !hcheckcontent(type, c->head.oktype, "Content-Type", 0)) return notaccept(c, type, enc, "Content-Type"); if(c->req.vermaj >= 1 && c->req.vermin >= 1 && !hcheckcontent(enc, c->head.okencode, "Content-Encoding", 0)) return notaccept(c, type, enc, "Content-Encoding"); /* * can use weak match only with get or head; * this always uses strong matches */ m = etagmatch(1, c->head.ifnomatch, etag); if(m && strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0 || c->head.ifunmodsince && c->head.ifunmodsince < mtime || c->head.ifmatch != nil && !etagmatch(1, c->head.ifmatch, etag)){ hprint(hout, "%s 412 Precondition Failed\r\n", hversion); hprint(hout, "Server: Plan9\r\n"); hprint(hout, "Date: %D\r\n", time(nil)); hprint(hout, "Content-Type: text/html\r\n"); hprint(hout, "Content-Length: %d\r\n", STRLEN(UNMATCHED)); if(c->head.closeit) hprint(hout, "Connection: close\r\n"); else if(!http11(c)) hprint(hout, "Connection: Keep-Alive\r\n"); hprint(hout, "\r\n"); if(strcmp(c->req.meth, "HEAD") != 0) hprint(hout, "%s", UNMATCHED); writelog(c, "Reply: 412 Precondition Failed\n"); return hflush(hout); } if(c->head.ifmodsince >= mtime && (m || c->head.ifnomatch == nil)){ /* * can only send back Date, ETag, Content-Location, * Expires, Cache-Control, and Vary entity-headers */ hprint(hout, "%s 304 Not Modified\r\n", hversion); hprint(hout, "Server: Plan9\r\n"); hprint(hout, "Date: %D\r\n", time(nil)); hprint(hout, "ETag: %s\r\n", etag); if(c->head.closeit) hprint(hout, "Connection: close\r\n"); else if(!http11(c)) hprint(hout, "Connection: Keep-Alive\r\n"); hprint(hout, "\r\n"); writelog(c, "Reply: 304 Not Modified\n"); return hflush(hout); } return 1; }
/* * write initial part of successful header */ void hokheaders(HConnect *c) { Hio *hout; hout = &c->hout; hprint(hout, "%s 200 OK\r\n", hversion); hprint(hout, "Server: Plan9\r\n"); hprint(hout, "Date: %D\r\n", time(nil)); if(c->head.closeit) hprint(hout, "Connection: close\r\n"); else if(!http11(c)) hprint(hout, "Connection: Keep-Alive\r\n"); }
static int herror(HConnect *c) { int n; Hio *hout; hout = &c->hout; n = snprint(c->xferbuf, HBufSize, "<html><head><title>Error</title></head>\n<body><h1>Error</h1>\n<pre>%r</pre>\n</body></html>"); hprint(hout, "%s %s\r\n", hversion, "400 Bad Request"); hprint(hout, "Date: %D\r\n", time(nil)); hprint(hout, "Server: Venti\r\n"); hprint(hout, "Content-Type: text/html\r\n"); hprint(hout, "Content-Length: %d\r\n", n); if(c->head.closeit) hprint(hout, "Connection: close\r\n"); else if(!http11(c)) hprint(hout, "Connection: Keep-Alive\r\n"); hprint(hout, "\r\n"); if(c->req.meth == nil || strcmp(c->req.meth, "HEAD") != 0) hwrite(hout, c->xferbuf, n); return hflush(hout); }
/* * send back a nice error message if the content is unacceptable * to get this message in ie, go to tools, internet options, advanced, * and turn off Show Friendly HTTP Error Messages under the Browsing category */ static int notaccept(HConnect *c, HContent *type, HContent *enc, char *which) { Hio *hout; char *s, *e; hout = &c->hout; e = &c->xferbuf[HBufSize]; s = c->xferbuf; s = seprint(s, e, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"); s = seprint(s, e, "<html>\n<title>Unacceptable %s</title>\n<body>\n", which); s = seprint(s, e, "Your browser will not accept this data, %H, because of its %s.<br>\n", c->req.uri, which); s = seprint(s, e, "Its Content-Type is %s/%s", type->generic, type->specific); if(enc != nil) s = seprint(s, e, ", and Content-Encoding is %s", enc->generic); s = seprint(s, e, ".<br>\n\n"); s = acceptcont(s, e, c->head.oktype, "Content-Type"); s = acceptcont(s, e, c->head.okencode, "Content-Encoding"); s = seprint(s, e, "</body>\n</html>\n"); hprint(hout, "%s 406 Not Acceptable\r\n", hversion); hprint(hout, "Server: Plan9\r\n"); hprint(hout, "Date: %D\r\n", time(nil)); hprint(hout, "Content-Type: text/html\r\n"); hprint(hout, "Content-Length: %lud\r\n", s - c->xferbuf); if(c->head.closeit) hprint(hout, "Connection: close\r\n"); else if(!http11(c)) hprint(hout, "Connection: Keep-Alive\r\n"); hprint(hout, "\r\n"); if(strcmp(c->req.meth, "HEAD") != 0) hwrite(hout, c->xferbuf, s - c->xferbuf); writelog(c, "Reply: 406 Not Acceptable\nReason: %s\n", which); return hflush(hout); }
/* * parse the next request line * returns: * 1 ok * 0 eof * -1 error */ int hparsereq(HConnect *c, int timeout) { Strings ss; char *vs, *v, *search, *uri, *origuri, *extra; if(c->bin != nil){ hfail(c, HInternal); return -1; } /* * serve requests until a magic request. * later requests have to come quickly. * only works for http/1.1 or later. */ if(timeout) alarm(timeout); if(hgethead(c, 0) < 0) return -1; if(timeout) alarm(0); c->reqtime = time(nil); c->req.meth = getword(c); if(c->req.meth == nil){ hfail(c, HSyntax); return -1; } uri = getword(c); if(uri == nil || strlen(uri) == 0){ hfail(c, HSyntax); return -1; } v = getword(c); if(v == nil){ if(strcmp(c->req.meth, "GET") != 0){ hfail(c, HUnimp, c->req.meth); return -1; } c->req.vermaj = 0; c->req.vermin = 9; }else{ vs = v; if(strncmp(vs, "HTTP/", 5) != 0){ hfail(c, HUnkVers, vs); return -1; } vs += 5; c->req.vermaj = strtoul(vs, &vs, 10); if(*vs != '.' || c->req.vermaj != 1){ hfail(c, HUnkVers, vs); return -1; } vs++; c->req.vermin = strtoul(vs, &vs, 10); if(*vs != '\0'){ hfail(c, HUnkVers, vs); return -1; } extra = getword(c); if(extra != nil){ hfail(c, HSyntax); return -1; } } /* * the fragment is not supposed to be sent * strip it 'cause some clients send it */ origuri = uri; uri = strchr(origuri, '#'); if(uri != nil) *uri = 0; /* * http/1.1 requires the server to accept absolute * or relative uri's. convert to relative with an absolute path */ if(http11(c)){ ss = parseuri(c, origuri); uri = ss.s1; c->req.urihost = ss.s2; if(uri == nil){ hfail(c, HBadReq, uri); return -1; } origuri = uri; } /* * munge uri for search, protection, and magic */ ss = stripsearch(origuri); origuri = ss.s1; search = ss.s2; uri = hurlunesc(c, origuri); uri = abspath(c, uri, "/"); if(uri == nil || uri[0] == '\0'){ hfail(c, HNotFound, "no object specified"); return -1; } c->req.uri = uri; c->req.search = search; if(search) c->req.searchpairs = hparsequery(c, hstrdup(c, search)); return 1; }
void test_nghttp2_npn(void) { http2(); http11(); no_overlap(); }
/* * fd references a file which has been authorized & checked for relocations. * send back the headers & its contents. * includes checks for conditional requests & ranges. */ int sendfd(HConnect *c, int fd, Dir *dir, HContent *type, HContent *enc) { Qid qid; HRange *r; HContents conts; Hio *hout; char *boundary, etag[32]; long mtime; ulong tr; int n, nw, multir, ok; vlong wrote, length; hout = &c->hout; length = dir->length; mtime = dir->mtime; qid = dir->qid; free(dir); /* * figure out the type of file and send headers */ n = -1; r = nil; multir = 0; boundary = nil; if(c->req.vermaj){ if(type == nil && enc == nil){ conts = uriclass(c, c->req.uri); type = conts.type; enc = conts.encoding; if(type == nil && enc == nil){ n = read(fd, c->xferbuf, HBufSize-1); if(n > 0){ c->xferbuf[n] = '\0'; conts = dataclass(c, c->xferbuf, n); type = conts.type; enc = conts.encoding; } } } if(type == nil) type = hmkcontent(c, "application", "octet-stream", nil); snprint(etag, sizeof(etag), "\"%lluxv%lux\"", qid.path, qid.vers); ok = checkreq(c, type, enc, mtime, etag); if(ok <= 0){ close(fd); return ok; } /* * check for if-range requests */ if(c->head.range == nil || c->head.ifrangeetag != nil && !etagmatch(1, c->head.ifrangeetag, etag) || c->head.ifrangedate != 0 && c->head.ifrangedate != mtime){ c->head.range = nil; c->head.ifrangeetag = nil; c->head.ifrangedate = 0; } if(c->head.range != nil){ c->head.range = fixrange(c->head.range, length); if(c->head.range == nil){ if(c->head.ifrangeetag == nil && c->head.ifrangedate == 0){ hprint(hout, "%s 416 Request range not satisfiable\r\n", hversion); hprint(hout, "Date: %D\r\n", time(nil)); hprint(hout, "Server: Plan9\r\n"); hprint(hout, "Content-Range: bytes */%lld\r\n", length); hprint(hout, "Content-Length: %d\r\n", STRLEN(BADRANGE)); hprint(hout, "Content-Type: text/html\r\n"); if(c->head.closeit) hprint(hout, "Connection: close\r\n"); else if(!http11(c)) hprint(hout, "Connection: Keep-Alive\r\n"); hprint(hout, "\r\n"); if(strcmp(c->req.meth, "HEAD") != 0) hprint(hout, "%s", BADRANGE); hflush(hout); writelog(c, "Reply: 416 Request range not satisfiable\n"); close(fd); return 1; } c->head.ifrangeetag = nil; c->head.ifrangedate = 0; } } if(c->head.range == nil) hprint(hout, "%s 200 OK\r\n", hversion); else hprint(hout, "%s 206 Partial Content\r\n", hversion); hprint(hout, "Server: Plan9\r\n"); hprint(hout, "Date: %D\r\n", time(nil)); hprint(hout, "ETag: %s\r\n", etag); /* * can't send some entity headers if partially responding * to an if-range: etag request */ r = c->head.range; if(r == nil) hprint(hout, "Content-Length: %lld\r\n", length); else if(r->next == nil){ hprint(hout, "Content-Range: bytes %ld-%ld/%lld\r\n", r->start, r->stop, length); hprint(hout, "Content-Length: %ld\r\n", r->stop - r->start); }else{ multir = 1; boundary = hmkmimeboundary(c); hprint(hout, "Content-Type: multipart/byteranges; boundary=%s\r\n", boundary); } if(c->head.ifrangeetag == nil){ hprint(hout, "Last-Modified: %D\r\n", mtime); if(!multir) printtype(hout, type, enc); if(c->head.fresh_thresh) hintprint(c, hout, c->req.uri, c->head.fresh_thresh, c->head.fresh_have); } if(c->head.closeit) hprint(hout, "Connection: close\r\n"); else if(!http11(c)) hprint(hout, "Connection: Keep-Alive\r\n"); hprint(hout, "\r\n"); } if(strcmp(c->req.meth, "HEAD") == 0){ if(c->head.range == nil) writelog(c, "Reply: 200 file 0\n"); else writelog(c, "Reply: 206 file 0\n"); hflush(hout); close(fd); return 1; } /* * send the file if it's a normal file */ if(r == nil){ hflush(hout); wrote = 0; if(n > 0) wrote = write(hout->fd, c->xferbuf, n); if(n <= 0 || wrote == n){ while((n = read(fd, c->xferbuf, HBufSize)) > 0){ nw = write(hout->fd, c->xferbuf, n); if(nw != n){ if(nw > 0) wrote += nw; break; } wrote += nw; } } writelog(c, "Reply: 200 file %lld %lld\n", length, wrote); close(fd); if(length == wrote) return 1; return -1; } /* * for multipart/byterange messages, * it is not ok for the boundary string to appear within a message part. * however, it probably doesn't matter, since there are lengths for every part. */ wrote = 0; ok = 1; for(; r != nil; r = r->next){ if(multir){ hprint(hout, "\r\n--%s\r\n", boundary); printtype(hout, type, enc); hprint(hout, "Content-Range: bytes %ld-%ld/%lld\r\n", r->start, r->stop, length); hprint(hout, "Content-Length: %ld\r\n", r->stop - r->start); hprint(hout, "\r\n"); } hflush(hout); if(seek(fd, r->start, 0) != r->start){ ok = -1; break; } for(tr = r->stop - r->start + 1; tr; tr -= n){ n = tr; if(n > HBufSize) n = HBufSize; if(read(fd, c->xferbuf, n) != n){ ok = -1; goto breakout; } nw = write(hout->fd, c->xferbuf, n); if(nw != n){ if(nw > 0) wrote += nw; ok = -1; goto breakout; } wrote += nw; } } breakout:; if(r == nil){ if(multir){ hprint(hout, "--%s--\r\n", boundary); hflush(hout); } writelog(c, "Reply: 206 partial content %lld %lld\n", length, wrote); }else writelog(c, "Reply: 206 partial content, early termination %lld %lld\n", length, wrote); close(fd); return ok; }