xref: /plan9/sys/src/cmd/ip/httpfile.c (revision b39189fd423aed869c5cf5189bc504918cff969b)
1 /* contributed by 20h@r-36.net, September 2005 */
2 
3 #include <u.h>
4 #include <libc.h>
5 #include <bio.h>
6 #include <ndb.h>
7 #include <thread.h>
8 #include <fcall.h>
9 #include <9p.h>
10 #include <mp.h>
11 #include <libsec.h>
12 
13 enum
14 {
15 	Blocksize = 64*1024,
16 	Stacksize = 8192,
17 };
18 
19 char *host;
20 char *file;
21 char *port;
22 char *url;
23 char *get;
24 char *user;
25 char *net = "net";
26 
27 vlong size;
28 int usetls;
29 int debug;
30 int ncache;
31 int mcache;
32 
33 void
usage(void)34 usage(void)
35 {
36 	fprint(2, "usage: httpfile [-Dd] [-c count] [-f file] [-m mtpt] [-s srvname] [-x net] url\n");
37 	exits("usage");
38 }
39 
40 enum
41 {
42 	Qroot,
43 	Qfile,
44 };
45 
46 #define PATH(type, n)		((type)|((n)<<8))
47 #define TYPE(path)			((int)(path) & 0xFF)
48 #define NUM(path)			((uint)(path)>>8)
49 
50 Channel *reqchan;
51 Channel *httpchan;
52 Channel *finishchan;
53 ulong time0;
54 
55 typedef struct Block Block;
56 struct Block
57 {
58 	uchar *p;
59 	vlong off;
60 	vlong len;
61 	Block *link;
62 	long lastuse;
63 	Req *rq;
64 	Req **erq;
65 };
66 
67 typedef struct Blocklist Blocklist;
68 struct Blocklist
69 {
70 	Block *first;
71 	Block **end;
72 };
73 
74 Blocklist cache;
75 Blocklist inprogress;
76 
77 void
queuereq(Block * b,Req * r)78 queuereq(Block *b, Req *r)
79 {
80 	if(b->rq==nil)
81 		b->erq = &b->rq;
82 	*b->erq = r;
83 	r->aux = nil;
84 	b->erq = (Req**)&r->aux;
85 }
86 
87 void
addblock(Blocklist * l,Block * b)88 addblock(Blocklist *l, Block *b)
89 {
90 	if(debug)
91 		print("adding: %p %lld\n", b, b->off);
92 
93 	if(l->first == nil)
94 		l->end = &l->first;
95 	*l->end = b;
96 	b->link = nil;
97 	l->end = &b->link;
98 	b->lastuse = time(0);
99 }
100 
101 void
delreq(Block * b,Req * r)102 delreq(Block *b, Req *r)
103 {
104 	Req **l;
105 
106 	for(l = &b->rq; *l; l = (Req**)&(*l)->aux){
107 		if(*l == r){
108 			*l = r->aux;
109 			if(*l == nil)
110 				b->erq = l;
111 			free(r);
112 			return;
113 		}
114 	}
115 }
116 
117 void
evictblock(Blocklist * cache)118 evictblock(Blocklist *cache)
119 {
120 	Block **l, **oldest, *b;
121 
122 	if(cache->first == nil)
123 		return;
124 
125 	oldest = nil;
126 	for(l=&cache->first; *l; l=&(*l)->link)
127 		if(oldest == nil || (*oldest)->lastuse > (*l)->lastuse)
128 			oldest = l;
129 
130 	b = *oldest;
131 	*oldest = (*oldest)->link;
132 	if(*oldest == nil)
133 		cache->end = oldest;
134 	free(b->p);
135 	free(b);
136 	ncache--;
137 }
138 
139 Block *
findblock(Blocklist * s,vlong off)140 findblock(Blocklist *s, vlong off)
141 {
142 	Block *b;
143 
144 	for(b = s->first; b != nil; b = b->link){
145 		if(b->off <= off && off < b->off + Blocksize){
146 			if(debug)
147 				print("found: %lld -> %lld\n", off, b->off);
148 			b->lastuse = time(0);
149 			return b;
150 		}
151 	}
152 
153 	return nil;
154 }
155 
156 void
readfrom(Req * r,Block * b)157 readfrom(Req *r, Block *b)
158 {
159 	int d, n;
160 
161 	b->lastuse = time(0);
162 
163 	n = r->ifcall.count;
164 	d = r->ifcall.offset - b->off;
165 	if(b->off + d + n > b->off + b->len)
166 		n = b->len - d;
167 	if(debug)
168 		print("Reading from: %p %d %d\n", b->p, d, n);
169 	memmove(r->ofcall.data, b->p + d, n);
170 	r->ofcall.count = n;
171 
172 	respond(r, nil);
173 }
174 
175 void
hangupclient(Srv *)176 hangupclient(Srv*)
177 {
178 	if(debug)
179 		print("Hangup.\n");
180 
181 	threadexitsall("done");
182 }
183 
184 int
dotls(int fd)185 dotls(int fd)
186 {
187 	TLSconn conn;
188 
189 	if((fd=tlsClient(fd, &conn)) < 0)
190 		sysfatal("tlsclient: %r");
191 
192 	if(conn.cert != nil)
193 		free(conn.cert);
194 
195 	return fd;
196 }
197 
198 char*
nocr(char * s)199 nocr(char *s)
200 {
201 	char *r, *w;
202 
203 	for(r=w=s; *r; r++)
204 		if(*r != '\r')
205 			*w++ = *r;
206 	*w = 0;
207 	return s;
208 }
209 
210 char*
readhttphdr(Biobuf * netbio,vlong * size)211 readhttphdr(Biobuf *netbio, vlong *size)
212 {
213 	char *s, *stat;
214 
215 	stat = nil;
216 	while((s = Brdstr(netbio, '\n', 1)) != nil && s[0] != '\r'
217 			&& s[0] != '\0'){
218 		if(stat == nil)
219 			stat = estrdup9p(s);
220 		if(strncmp(s, "Content-Length: ", 16) == 0 && size != nil)
221 			*size = atoll(s + 16);
222 		free(s);
223 	}
224 	if(stat)
225 		nocr(stat);
226 
227 	return stat;
228 }
229 
230 int
dialhttp(Biobuf * netbio)231 dialhttp(Biobuf *netbio)
232 {
233 	int netfd;
234 
235 	netfd = dial(netmkaddr(host, net, port), 0, 0, 0);
236 	if(netfd < 0)
237 		sysfatal("dial: %r");
238 	if(usetls)
239 		netfd = dotls(netfd);
240 	Binit(netbio, netfd, OREAD);
241 
242 	return netfd;
243 }
244 
245 uchar*
getrange(Block * b)246 getrange(Block *b)
247 {
248 	uchar *data;
249 	char *status;
250 	int netfd;
251 	static Biobuf netbio;
252 
253 	b->len = Blocksize;
254 	if(b->off + b->len > size)
255 		b->len = size - b->off;
256 
257 	if(debug)
258 		print("getrange: %lld %lld\n", b->off, b->len);
259 
260 	netfd = dialhttp(&netbio);
261 
262 	fprint(netfd,
263 		"GET %s HTTP/1.1\r\n"
264 		"Host: %s\r\n"
265 		"Accept-Encoding:\r\n"
266 		"Range: bytes=%lld-%lld\r\n"
267 		"\r\n",
268 		get, host, b->off, b->off+b->len);
269 	Bflush(&netbio);
270 
271 	status = readhttphdr(&netbio, nil);
272 	if(status == nil)
273 		return nil;
274 
275 	/*
276 	 * Some servers (e.g., www.google.com) return 200 OK
277 	 * when you ask for the entire page in one range.
278 	 */
279 	if(strstr(status, "206 Partial Content")==nil
280 	&& (b->off!=0 || b->len!=size || strstr(status, "200 OK")==nil)){
281 		free(status);
282 		close(netfd);
283 		werrstr("did not get requested range");
284 		return nil;
285 	}
286 	free(status);
287 
288 	data = emalloc9p(b->len);
289 	if(Bread(&netbio, data, b->len) != b->len){
290 		free(data);
291 		close(netfd);
292 		werrstr("not enough bytes read");
293 		return nil;
294 	}
295 
296 	b->p = data;
297 
298 	close(netfd);
299 	return data;
300 }
301 
302 void
httpfilereadproc(void *)303 httpfilereadproc(void*)
304 {
305 	Block *b;
306 
307 	threadsetname("httpfilereadproc");
308 
309 	for(;;){
310 		b = recvp(httpchan);
311 		if(b == nil)
312 			continue;
313 		if(getrange(b) == nil)
314 			sysfatal("getrange: %r");
315 		sendp(finishchan, b);
316 	}
317 }
318 
319 typedef struct Tab Tab;
320 struct Tab
321 {
322 	char *name;
323 	ulong mode;
324 };
325 
326 Tab tab[] =
327 {
328 	"/",		DMDIR|0555,
329 	nil,		0444,
330 };
331 
332 static void
fillstat(Dir * d,uvlong path)333 fillstat(Dir *d, uvlong path)
334 {
335 	Tab *t;
336 
337 	memset(d, 0, sizeof(*d));
338 	d->uid = estrdup9p(user);
339 	d->gid = estrdup9p(user);
340 	d->qid.path = path;
341 	d->atime = d->mtime = time0;
342 	t = &tab[TYPE(path)];
343 	d->name = estrdup9p(t->name);
344 	d->length = size;
345 	d->qid.type = t->mode>>24;
346 	d->mode = t->mode;
347 }
348 
349 static void
fsattach(Req * r)350 fsattach(Req *r)
351 {
352 	if(r->ifcall.aname && r->ifcall.aname[0]){
353 		respond(r, "invalid attach specifier");
354 		return;
355 	}
356 	r->fid->qid.path = PATH(Qroot, 0);
357 	r->fid->qid.type = QTDIR;
358 	r->fid->qid.vers = 0;
359 	r->ofcall.qid = r->fid->qid;
360 	respond(r, nil);
361 }
362 
363 static void
fsstat(Req * r)364 fsstat(Req *r)
365 {
366 	fillstat(&r->d, r->fid->qid.path);
367 	respond(r, nil);
368 }
369 
370 static int
rootgen(int i,Dir * d,void *)371 rootgen(int i, Dir *d, void*)
372 {
373 	i += Qroot + 1;
374 	if(i <= Qfile){
375 		fillstat(d, i);
376 		return 0;
377 	}
378 	return -1;
379 }
380 
381 static char*
fswalk1(Fid * fid,char * name,Qid * qid)382 fswalk1(Fid *fid, char *name, Qid *qid)
383 {
384 	int i;
385 	ulong path;
386 
387 	path = fid->qid.path;
388 	if(!(fid->qid.type & QTDIR))
389 		return "walk in non-directory";
390 
391 	if(strcmp(name, "..") == 0){
392 		switch(TYPE(path)){
393 		case Qroot:
394 			return nil;
395 		default:
396 			return "bug in fswalk1";
397 		}
398 	}
399 
400 	i = TYPE(path) + 1;
401 	while(i < nelem(tab)){
402 		if(strcmp(name, tab[i].name) == 0){
403 			qid->path = PATH(i, NUM(path));
404 			qid->type = tab[i].mode>>24;
405 			return nil;
406 		}
407 		if(tab[i].mode & DMDIR)
408 			break;
409 		i++;
410 	}
411 	return "directory entry not found";
412 }
413 
414 vlong
getfilesize(void)415 getfilesize(void)
416 {
417 	char *status;
418 	vlong size;
419 	int netfd;
420 	static Biobuf netbio;
421 
422 	netfd = dialhttp(&netbio);
423 
424 	fprint(netfd,
425 		"HEAD %s HTTP/1.1\r\n"
426 		"Host: %s\r\n"
427 		"Accept-Encoding:\r\n"
428 		"\r\n",
429 		get, host);
430 
431 	status = readhttphdr(&netbio, &size);
432 	if(strstr(status, "200 OK") == nil){
433 		werrstr("%s", status);
434 		size = -1;
435 	}
436 	free(status);
437 
438 	close(netfd);
439 	return size;
440 }
441 
442 void
fileread(Req * r)443 fileread(Req *r)
444 {
445 	Block *b;
446 
447 	if(r->ifcall.offset > size){
448 		respond(r, nil);
449 		return;
450 	}
451 
452 	if((b = findblock(&cache, r->ifcall.offset)) != nil){
453 		readfrom(r, b);
454 		return;
455 	}
456 	if((b = findblock(&inprogress, r->ifcall.offset)) == nil){
457 		b = emalloc9p(sizeof(Block));
458 		b->off = r->ifcall.offset - (r->ifcall.offset % Blocksize);
459 		addblock(&inprogress, b);
460 		if(inprogress.first == b)
461 			sendp(httpchan, b);
462 	}
463 	queuereq(b, r);
464 }
465 
466 static void
fsopen(Req * r)467 fsopen(Req *r)
468 {
469 	if(r->ifcall.mode != OREAD){
470 		respond(r, "permission denied");
471 		return;
472 	}
473 	respond(r, nil);
474 }
475 
476 void
finishthread(void *)477 finishthread(void*)
478 {
479 	Block *b;
480 	Req *r, *nextr;
481 
482 	threadsetname("finishthread");
483 
484 	for(;;){
485 		b = recvp(finishchan);
486 		assert(b == inprogress.first);
487 		inprogress.first = b->link;
488 		ncache++;
489 		if(ncache >= mcache)
490 			evictblock(&cache);
491 		addblock(&cache, b);
492 		for(r=b->rq; r; r=nextr){
493 			nextr = r->aux;
494 			readfrom(r, b);
495 		}
496 		b->rq = nil;
497 		if(inprogress.first)
498 			sendp(httpchan, inprogress.first);
499 	}
500 }
501 
502 void
fsnetproc(void *)503 fsnetproc(void*)
504 {
505 	Req *r;
506 	Block *b;
507 
508 	threadcreate(finishthread, nil, 8192);
509 
510 	threadsetname("fsnetproc");
511 
512 	for(;;){
513 		r = recvp(reqchan);
514 		switch(r->ifcall.type){
515 		case Tflush:
516 			b = findblock(&inprogress, r->ifcall.offset);
517 			delreq(b, r->oldreq);
518 			respond(r->oldreq, "interrupted");
519 			respond(r, nil);
520 			break;
521 		case Tread:
522 			fileread(r);
523 			break;
524 		default:
525 			respond(r, "bug in fsthread");
526 			break;
527 		}
528 	}
529 }
530 
531 static void
fsflush(Req * r)532 fsflush(Req *r)
533 {
534 	sendp(reqchan, r);
535 }
536 
537 static void
fsread(Req * r)538 fsread(Req *r)
539 {
540 	char e[ERRMAX];
541 	ulong path;
542 
543 	path = r->fid->qid.path;
544 	switch(TYPE(path)){
545 	case Qroot:
546 		dirread9p(r, rootgen, nil);
547 		respond(r, nil);
548 		break;
549 	case Qfile:
550 		sendp(reqchan, r);
551 		break;
552 	default:
553 		snprint(e, sizeof(e), "bug in fsread path=%lux", path);
554 		respond(r, e);
555 		break;
556 	}
557 }
558 
559 Srv fs =
560 {
561 .attach=		fsattach,
562 .walk1=		fswalk1,
563 .open=		fsopen,
564 .read=		fsread,
565 .stat=		fsstat,
566 .flush=		fsflush,
567 .end=		hangupclient,
568 };
569 
570 void
threadmain(int argc,char ** argv)571 threadmain(int argc, char **argv)
572 {
573 	char *defport, *mtpt, *srvname, *p;
574 
575 	mtpt = nil;
576 	srvname = nil;
577 	ARGBEGIN{
578 	case 'D':
579 		chatty9p++;
580 		break;
581 	case 'd':
582 		debug++;
583 		break;
584 	case 's':
585 		srvname = EARGF(usage());
586 		break;
587 	case 'm':
588 		mtpt = EARGF(usage());
589 		break;
590 	case 'c':
591 		mcache = atoi(EARGF(usage()));
592 		break;
593 	case 'f':
594 		file = EARGF(usage());
595 		break;
596 	case 'x':
597 		net = smprint("%s/net", EARGF(usage()));
598 		break;
599 	default:
600 		usage();
601 	}ARGEND;
602 
603 	if(srvname == nil && mtpt == nil)
604 		mtpt = ".";
605 
606 	if(argc < 1)
607 		usage();
608 	if(mcache <= 0)
609 		mcache = 32;
610 
611 	time0 = time(0);
612 	host = url = estrdup9p(argv[0]);
613 
614 	defport = nil;
615 	if(!cistrncmp(url, "https://", 8)){
616 		host += 8;
617 		usetls = 1;
618 		defport = "https";
619 	}else if(!cistrncmp(url, "http://", 7)){
620 		host += 7;
621 		defport = "http";
622 	}else
623 		sysfatal("unsupported url: %s", url);
624 
625 	if((p = strchr(host, '/')) != nil){
626 		get = estrdup9p(p);
627 		*p = '\0';
628 	}else
629 		get = "/";
630 
631 	port = strchr(host, ':');
632 	if(port != nil)
633 		*port++ = '\0';
634 	else
635 		port = defport;
636 
637 	if(file == nil){
638 		file = strrchr(get, '/')+1;
639 		if(*file == 0)
640 			file = "index";
641 	}
642 
643 	tab[Qfile].name = file;
644 	user = getuser();
645 	size = getfilesize();
646 	if(size < 0)
647 		sysfatal("getfilesize: %r");
648 
649 	reqchan = chancreate(sizeof(Req*), 0);
650 	httpchan = chancreate(sizeof(Block*), 0);
651 	finishchan = chancreate(sizeof(Block*), 0);
652 
653 	procrfork(fsnetproc, nil, Stacksize, RFNAMEG|RFNOTEG);
654 	procrfork(httpfilereadproc, nil, Stacksize, RFNAMEG|RFNOTEG);
655 
656 	threadpostmountsrv(&fs, srvname, mtpt, MBEFORE);
657 	threadexits(0);
658 }
659