xref: /plan9/sys/src/cmd/ip/httpd/sendfd.c (revision 593081380c734f70f5bda70998607232e53df600)
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