]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/httpd/sendfd.c
b3ef1c13b082c94326619e701ff559b27584df3e
[plan9front.git] / sys / src / cmd / ip / httpd / sendfd.c
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", 1 + 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", 1 + 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 = 1 + r->stop - r->start; 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                         /*
422                          * for suffix, r->stop is a byte *length*
423                          * not the byte *offset* of last byte!
424                          */
425                         r->start = length - r->stop;
426                         if(r->start >= length)
427                                 r->start = 0;
428                         r->stop = length - 1;
429                         r->suffix = 0;
430                 }
431                 if(r->stop >= length)
432                         r->stop = length - 1;
433                 if(r->start > r->stop){
434                         if(rr == nil)
435                                 h = r->next;
436                         else
437                                 rr->next = r->next;
438                 }else
439                         rr = r;
440         }
441
442         /*
443          * merge consecutive overlapping or abutting ranges
444          *
445          * not clear from rfc2616 how much merging needs to be done.
446          * this code merges only if a range is adjacent to a later starting,
447          * over overlapping or abutting range.  this allows a client
448          * to request wanted data first, followed by other data.
449          * this may be useful then fetching part of a page, then the adjacent regions.
450          */
451         if(h == nil)
452                 return h;
453         r = h;
454         for(;;){
455                 rr = r->next;
456                 if(rr == nil)
457                         break;
458                 if(r->start <= rr->start && r->stop + 1 >= rr->start){
459                         if(r->stop < rr->stop)
460                                 r->stop = rr->stop;
461                         r->next = rr->next;
462                 }else
463                         r = rr;
464         }
465         return h;
466 }