123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- #include <u.h>
- #include <libc.h>
- #include <auth.h>
- #include "httpd.h"
- #include "httpsrv.h"
- static void printtype(Hio *hout, HContent *type, HContent *enc);
- /*
- * these should be done better; see the reponse codes in /lib/rfc/rfc2616 for
- * more info on what should be included.
- */
- #define UNAUTHED "You are not authorized to see this area.\n"
- #define NOCONTENT "No acceptable type of data is available.\n"
- #define NOENCODE "No acceptable encoding of the contents is available.\n"
- #define UNMATCHED "The entity requested does not match the existing entity.\n"
- #define BADRANGE "No bytes are avaible for the range you requested.\n"
- /*
- * fd references a file which has been authorized & checked for relocations.
- * send back the headers & it's 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;
- }
- static void
- printtype(Hio *hout, HContent *type, HContent *enc)
- {
- hprint(hout, "Content-Type: %s/%s", type->generic, type->specific);
- /*
- if(cistrcmp(type->generic, "text") == 0)
- hprint(hout, ";charset=utf-8");
- */
- hprint(hout, "\r\n");
- if(enc != nil)
- hprint(hout, "Content-Encoding: %s\r\n", enc->generic);
- }
- int
- etagmatch(int strong, HETag *tags, char *e)
- {
- char *s, *t;
- for(; tags != nil; tags = tags->next){
- if(strong && tags->weak)
- continue;
- s = tags->etag;
- if(s[0] == '*' && s[1] == '\0')
- return 1;
- t = e + 1;
- while(*t != '"'){
- if(*s != *t)
- break;
- s++;
- t++;
- }
- if(*s == '\0' && *t == '"')
- return 1;
- }
- return 0;
- }
- static char *
- acceptcont(char *s, char *e, HContent *ok, char *which)
- {
- char *sep;
- if(ok == nil)
- return seprint(s, e, "Your browser accepts any %s.<br>\n", which);
- s = seprint(s, e, "Your browser accepts %s: ", which);
- sep = "";
- for(; ok != nil; ok = ok->next){
- if(ok->specific)
- s = seprint(s, e, "%s%s/%s", sep, ok->generic, ok->specific);
- else
- s = seprint(s, e, "%s%s", sep, ok->generic);
- sep = ", ";
- }
- return seprint(s, e, ".<br>\n");
- }
- /*
- * 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 it's %s.<br>\n", c->req.uri, which);
- s = seprint(s, e, "It's 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);
- }
- /*
- * 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;
- }
- /*
- * length is the actual length of the entity requested.
- * discard any range requests which are invalid,
- * ie start after the end, or have stop before start.
- * rewrite suffix requests
- */
- HRange*
- fixrange(HRange *h, long length)
- {
- HRange *r, *rr;
- if(length == 0)
- return nil;
- /*
- * rewrite each range to reflect the actual length of the file
- * toss out any invalid ranges
- */
- rr = nil;
- for(r = h; r != nil; r = r->next){
- if(r->suffix){
- r->start = length - r->stop;
- if(r->start >= length)
- r->start = 0;
- r->stop = length - 1;
- r->suffix = 0;
- }
- if(r->stop >= length)
- r->stop = length - 1;
- if(r->start > r->stop){
- if(rr == nil)
- h = r->next;
- else
- rr->next = r->next;
- }else
- rr = r;
- }
- /*
- * merge consecutive overlapping or abutting ranges
- *
- * not clear from rfc2616 how much merging needs to be done.
- * this code merges only if a range is adjacent to a later starting,
- * over overlapping or abutting range. this allows a client
- * to request wanted data first, followed by other data.
- * this may be useful then fetching part of a page, then the adjacent regions.
- */
- if(h == nil)
- return h;
- r = h;
- for(;;){
- rr = r->next;
- if(rr == nil)
- break;
- if(r->start <= rr->start && r->stop + 1 >= rr->start){
- if(r->stop < rr->stop)
- r->stop = rr->stop;
- r->next = rr->next;
- }else
- r = rr;
- }
- return h;
- }
|