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
sendfd(HConnect * c,int fd,Dir * dir,HContent * type,HContent * enc)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
printtype(Hio * hout,HContent * type,HContent * enc)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
etagmatch(int strong,HETag * tags,char * e)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 *
acceptcont(char * s,char * e,HContent * ok,char * which)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
notaccept(HConnect * c,HContent * type,HContent * enc,char * which)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
checkreq(HConnect * c,HContent * type,HContent * enc,long mtime,char * etag)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*
fixrange(HRange * h,long length)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