Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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
}