1 #include <u.h> 2 #include <libc.h> 3 #include <auth.h> 4 #include "httpd.h" 5 #include "httpsrv.h" 6 7 static void printtype(Hio *hout, HContent *type, HContent *enc); 8 9 /* 10 * these should be done better; see the reponse codes in /lib/rfc/rfc2616 for 11 * more info on what should be included. 12 */ 13 #define UNAUTHED "You are not authorized to see this area.\n" 14 #define NOCONTENT "No acceptable type of data is available.\n" 15 #define NOENCODE "No acceptable encoding of the contents is available.\n" 16 #define UNMATCHED "The entity requested does not match the existing entity.\n" 17 #define BADRANGE "No bytes are avaible for the range you requested.\n" 18 19 /* 20 * fd references a file which has been authorized & checked for relocations. 21 * send back the headers & its contents. 22 * includes checks for conditional requests & ranges. 23 */ 24 int 25 sendfd(HConnect *c, int fd, Dir *dir, HContent *type, HContent *enc) 26 { 27 Qid qid; 28 HRange *r; 29 HContents conts; 30 Hio *hout; 31 char *boundary, etag[32]; 32 long mtime; 33 ulong tr; 34 int n, nw, multir, ok; 35 vlong wrote, length; 36 37 hout = &c->hout; 38 length = dir->length; 39 mtime = dir->mtime; 40 qid = dir->qid; 41 free(dir); 42 43 /* 44 * figure out the type of file and send headers 45 */ 46 n = -1; 47 r = nil; 48 multir = 0; 49 boundary = nil; 50 if(c->req.vermaj){ 51 if(type == nil && enc == nil){ 52 conts = uriclass(c, c->req.uri); 53 type = conts.type; 54 enc = conts.encoding; 55 if(type == nil && enc == nil){ 56 n = read(fd, c->xferbuf, HBufSize-1); 57 if(n > 0){ 58 c->xferbuf[n] = '\0'; 59 conts = dataclass(c, c->xferbuf, n); 60 type = conts.type; 61 enc = conts.encoding; 62 } 63 } 64 } 65 if(type == nil) 66 type = hmkcontent(c, "application", "octet-stream", nil); 67 68 snprint(etag, sizeof(etag), "\"%lluxv%lux\"", qid.path, qid.vers); 69 ok = checkreq(c, type, enc, mtime, etag); 70 if(ok <= 0){ 71 close(fd); 72 return ok; 73 } 74 75 /* 76 * check for if-range requests 77 */ 78 if(c->head.range == nil 79 || c->head.ifrangeetag != nil && !etagmatch(1, c->head.ifrangeetag, etag) 80 || c->head.ifrangedate != 0 && c->head.ifrangedate != mtime){ 81 c->head.range = nil; 82 c->head.ifrangeetag = nil; 83 c->head.ifrangedate = 0; 84 } 85 86 if(c->head.range != nil){ 87 c->head.range = fixrange(c->head.range, length); 88 if(c->head.range == nil){ 89 if(c->head.ifrangeetag == nil && c->head.ifrangedate == 0){ 90 hprint(hout, "%s 416 Request range not satisfiable\r\n", hversion); 91 hprint(hout, "Date: %D\r\n", time(nil)); 92 hprint(hout, "Server: Plan9\r\n"); 93 hprint(hout, "Content-Range: bytes */%lld\r\n", length); 94 hprint(hout, "Content-Length: %d\r\n", STRLEN(BADRANGE)); 95 hprint(hout, "Content-Type: text/html\r\n"); 96 if(c->head.closeit) 97 hprint(hout, "Connection: close\r\n"); 98 else if(!http11(c)) 99 hprint(hout, "Connection: Keep-Alive\r\n"); 100 hprint(hout, "\r\n"); 101 if(strcmp(c->req.meth, "HEAD") != 0) 102 hprint(hout, "%s", BADRANGE); 103 hflush(hout); 104 writelog(c, "Reply: 416 Request range not satisfiable\n"); 105 close(fd); 106 return 1; 107 } 108 c->head.ifrangeetag = nil; 109 c->head.ifrangedate = 0; 110 } 111 } 112 if(c->head.range == nil) 113 hprint(hout, "%s 200 OK\r\n", hversion); 114 else 115 hprint(hout, "%s 206 Partial Content\r\n", hversion); 116 117 hprint(hout, "Server: Plan9\r\n"); 118 hprint(hout, "Date: %D\r\n", time(nil)); 119 hprint(hout, "ETag: %s\r\n", etag); 120 121 /* 122 * can't send some entity headers if partially responding 123 * to an if-range: etag request 124 */ 125 r = c->head.range; 126 if(r == nil) 127 hprint(hout, "Content-Length: %lld\r\n", length); 128 else if(r->next == nil){ 129 hprint(hout, "Content-Range: bytes %ld-%ld/%lld\r\n", r->start, r->stop, length); 130 hprint(hout, "Content-Length: %ld\r\n", r->stop - r->start); 131 }else{ 132 multir = 1; 133 boundary = hmkmimeboundary(c); 134 hprint(hout, "Content-Type: multipart/byteranges; boundary=%s\r\n", boundary); 135 } 136 if(c->head.ifrangeetag == nil){ 137 hprint(hout, "Last-Modified: %D\r\n", mtime); 138 if(!multir) 139 printtype(hout, type, enc); 140 if(c->head.fresh_thresh) 141 hintprint(c, hout, c->req.uri, c->head.fresh_thresh, c->head.fresh_have); 142 } 143 144 if(c->head.closeit) 145 hprint(hout, "Connection: close\r\n"); 146 else if(!http11(c)) 147 hprint(hout, "Connection: Keep-Alive\r\n"); 148 hprint(hout, "\r\n"); 149 } 150 if(strcmp(c->req.meth, "HEAD") == 0){ 151 if(c->head.range == nil) 152 writelog(c, "Reply: 200 file 0\n"); 153 else 154 writelog(c, "Reply: 206 file 0\n"); 155 hflush(hout); 156 close(fd); 157 return 1; 158 } 159 160 /* 161 * send the file if it's a normal file 162 */ 163 if(r == nil){ 164 hflush(hout); 165 166 wrote = 0; 167 if(n > 0) 168 wrote = write(hout->fd, c->xferbuf, n); 169 if(n <= 0 || wrote == n){ 170 while((n = read(fd, c->xferbuf, HBufSize)) > 0){ 171 nw = write(hout->fd, c->xferbuf, n); 172 if(nw != n){ 173 if(nw > 0) 174 wrote += nw; 175 break; 176 } 177 wrote += nw; 178 } 179 } 180 writelog(c, "Reply: 200 file %lld %lld\n", length, wrote); 181 close(fd); 182 if(length == wrote) 183 return 1; 184 return -1; 185 } 186 187 /* 188 * for multipart/byterange messages, 189 * it is not ok for the boundary string to appear within a message part. 190 * however, it probably doesn't matter, since there are lengths for every part. 191 */ 192 wrote = 0; 193 ok = 1; 194 for(; r != nil; r = r->next){ 195 if(multir){ 196 hprint(hout, "\r\n--%s\r\n", boundary); 197 printtype(hout, type, enc); 198 hprint(hout, "Content-Range: bytes %ld-%ld/%lld\r\n", r->start, r->stop, length); 199 hprint(hout, "Content-Length: %ld\r\n", r->stop - r->start); 200 hprint(hout, "\r\n"); 201 } 202 hflush(hout); 203 204 if(seek(fd, r->start, 0) != r->start){ 205 ok = -1; 206 break; 207 } 208 for(tr = r->stop - r->start + 1; tr; tr -= n){ 209 n = tr; 210 if(n > HBufSize) 211 n = HBufSize; 212 if(read(fd, c->xferbuf, n) != n){ 213 ok = -1; 214 goto breakout; 215 } 216 nw = write(hout->fd, c->xferbuf, n); 217 if(nw != n){ 218 if(nw > 0) 219 wrote += nw; 220 ok = -1; 221 goto breakout; 222 } 223 wrote += nw; 224 } 225 } 226 breakout:; 227 if(r == nil){ 228 if(multir){ 229 hprint(hout, "--%s--\r\n", boundary); 230 hflush(hout); 231 } 232 writelog(c, "Reply: 206 partial content %lld %lld\n", length, wrote); 233 }else 234 writelog(c, "Reply: 206 partial content, early termination %lld %lld\n", length, wrote); 235 close(fd); 236 return ok; 237 } 238 239 static void 240 printtype(Hio *hout, HContent *type, HContent *enc) 241 { 242 hprint(hout, "Content-Type: %s/%s", type->generic, type->specific); 243 /* 244 if(cistrcmp(type->generic, "text") == 0) 245 hprint(hout, ";charset=utf-8"); 246 */ 247 hprint(hout, "\r\n"); 248 if(enc != nil) 249 hprint(hout, "Content-Encoding: %s\r\n", enc->generic); 250 } 251 252 int 253 etagmatch(int strong, HETag *tags, char *e) 254 { 255 char *s, *t; 256 257 for(; tags != nil; tags = tags->next){ 258 if(strong && tags->weak) 259 continue; 260 s = tags->etag; 261 if(s[0] == '*' && s[1] == '\0') 262 return 1; 263 264 t = e + 1; 265 while(*t != '"'){ 266 if(*s != *t) 267 break; 268 s++; 269 t++; 270 } 271 272 if(*s == '\0' && *t == '"') 273 return 1; 274 } 275 return 0; 276 } 277 278 static char * 279 acceptcont(char *s, char *e, HContent *ok, char *which) 280 { 281 char *sep; 282 283 if(ok == nil) 284 return seprint(s, e, "Your browser accepts any %s.<br>\n", which); 285 s = seprint(s, e, "Your browser accepts %s: ", which); 286 sep = ""; 287 for(; ok != nil; ok = ok->next){ 288 if(ok->specific) 289 s = seprint(s, e, "%s%s/%s", sep, ok->generic, ok->specific); 290 else 291 s = seprint(s, e, "%s%s", sep, ok->generic); 292 sep = ", "; 293 } 294 return seprint(s, e, ".<br>\n"); 295 } 296 297 /* 298 * send back a nice error message if the content is unacceptable 299 * to get this message in ie, go to tools, internet options, advanced, 300 * and turn off Show Friendly HTTP Error Messages under the Browsing category 301 */ 302 static int 303 notaccept(HConnect *c, HContent *type, HContent *enc, char *which) 304 { 305 Hio *hout; 306 char *s, *e; 307 308 hout = &c->hout; 309 e = &c->xferbuf[HBufSize]; 310 s = c->xferbuf; 311 s = seprint(s, e, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"); 312 s = seprint(s, e, "<html>\n<title>Unacceptable %s</title>\n<body>\n", which); 313 s = seprint(s, e, "Your browser will not accept this data, %H, because of its %s.<br>\n", c->req.uri, which); 314 s = seprint(s, e, "Its Content-Type is %s/%s", type->generic, type->specific); 315 if(enc != nil) 316 s = seprint(s, e, ", and Content-Encoding is %s", enc->generic); 317 s = seprint(s, e, ".<br>\n\n"); 318 319 s = acceptcont(s, e, c->head.oktype, "Content-Type"); 320 s = acceptcont(s, e, c->head.okencode, "Content-Encoding"); 321 s = seprint(s, e, "</body>\n</html>\n"); 322 323 hprint(hout, "%s 406 Not Acceptable\r\n", hversion); 324 hprint(hout, "Server: Plan9\r\n"); 325 hprint(hout, "Date: %D\r\n", time(nil)); 326 hprint(hout, "Content-Type: text/html\r\n"); 327 hprint(hout, "Content-Length: %lud\r\n", s - c->xferbuf); 328 if(c->head.closeit) 329 hprint(hout, "Connection: close\r\n"); 330 else if(!http11(c)) 331 hprint(hout, "Connection: Keep-Alive\r\n"); 332 hprint(hout, "\r\n"); 333 if(strcmp(c->req.meth, "HEAD") != 0) 334 hwrite(hout, c->xferbuf, s - c->xferbuf); 335 writelog(c, "Reply: 406 Not Acceptable\nReason: %s\n", which); 336 return hflush(hout); 337 } 338 339 /* 340 * check time and entity tag conditions. 341 */ 342 int 343 checkreq(HConnect *c, HContent *type, HContent *enc, long mtime, char *etag) 344 { 345 Hio *hout; 346 int m; 347 348 hout = &c->hout; 349 if(c->req.vermaj >= 1 && c->req.vermin >= 1 && !hcheckcontent(type, c->head.oktype, "Content-Type", 0)) 350 return notaccept(c, type, enc, "Content-Type"); 351 if(c->req.vermaj >= 1 && c->req.vermin >= 1 && !hcheckcontent(enc, c->head.okencode, "Content-Encoding", 0)) 352 return notaccept(c, type, enc, "Content-Encoding"); 353 354 /* 355 * can use weak match only with get or head; 356 * this always uses strong matches 357 */ 358 m = etagmatch(1, c->head.ifnomatch, etag); 359 360 if(m && strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0 361 || c->head.ifunmodsince && c->head.ifunmodsince < mtime 362 || c->head.ifmatch != nil && !etagmatch(1, c->head.ifmatch, etag)){ 363 hprint(hout, "%s 412 Precondition Failed\r\n", hversion); 364 hprint(hout, "Server: Plan9\r\n"); 365 hprint(hout, "Date: %D\r\n", time(nil)); 366 hprint(hout, "Content-Type: text/html\r\n"); 367 hprint(hout, "Content-Length: %d\r\n", STRLEN(UNMATCHED)); 368 if(c->head.closeit) 369 hprint(hout, "Connection: close\r\n"); 370 else if(!http11(c)) 371 hprint(hout, "Connection: Keep-Alive\r\n"); 372 hprint(hout, "\r\n"); 373 if(strcmp(c->req.meth, "HEAD") != 0) 374 hprint(hout, "%s", UNMATCHED); 375 writelog(c, "Reply: 412 Precondition Failed\n"); 376 return hflush(hout); 377 } 378 379 if(c->head.ifmodsince >= mtime 380 && (m || c->head.ifnomatch == nil)){ 381 /* 382 * can only send back Date, ETag, Content-Location, 383 * Expires, Cache-Control, and Vary entity-headers 384 */ 385 hprint(hout, "%s 304 Not Modified\r\n", hversion); 386 hprint(hout, "Server: Plan9\r\n"); 387 hprint(hout, "Date: %D\r\n", time(nil)); 388 hprint(hout, "ETag: %s\r\n", etag); 389 if(c->head.closeit) 390 hprint(hout, "Connection: close\r\n"); 391 else if(!http11(c)) 392 hprint(hout, "Connection: Keep-Alive\r\n"); 393 hprint(hout, "\r\n"); 394 writelog(c, "Reply: 304 Not Modified\n"); 395 return hflush(hout); 396 } 397 return 1; 398 } 399 400 /* 401 * length is the actual length of the entity requested. 402 * discard any range requests which are invalid, 403 * ie start after the end, or have stop before start. 404 * rewrite suffix requests 405 */ 406 HRange* 407 fixrange(HRange *h, long length) 408 { 409 HRange *r, *rr; 410 411 if(length == 0) 412 return nil; 413 414 /* 415 * rewrite each range to reflect the actual length of the file 416 * toss out any invalid ranges 417 */ 418 rr = nil; 419 for(r = h; r != nil; r = r->next){ 420 if(r->suffix){ 421 r->start = length - r->stop; 422 if(r->start >= length) 423 r->start = 0; 424 r->stop = length - 1; 425 r->suffix = 0; 426 } 427 if(r->stop >= length) 428 r->stop = length - 1; 429 if(r->start > r->stop){ 430 if(rr == nil) 431 h = r->next; 432 else 433 rr->next = r->next; 434 }else 435 rr = r; 436 } 437 438 /* 439 * merge consecutive overlapping or abutting ranges 440 * 441 * not clear from rfc2616 how much merging needs to be done. 442 * this code merges only if a range is adjacent to a later starting, 443 * over overlapping or abutting range. this allows a client 444 * to request wanted data first, followed by other data. 445 * this may be useful then fetching part of a page, then the adjacent regions. 446 */ 447 if(h == nil) 448 return h; 449 r = h; 450 for(;;){ 451 rr = r->next; 452 if(rr == nil) 453 break; 454 if(r->start <= rr->start && r->stop + 1 >= rr->start){ 455 if(r->stop < rr->stop) 456 r->stop = rr->stop; 457 r->next = rr->next; 458 }else 459 r = rr; 460 } 461 return h; 462 } 463