xref: /freebsd/lib/libfetch/http.c (revision 35f723db8a2b56ae106d46295dbac5d2ac3b5519)
1 /*-
2  * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * $FreeBSD$
29  */
30 
31 /*
32  * The base64 code in this file is based on code from MIT fetch, which
33  * has the following copyright and license:
34  *
35  *-
36  * Copyright 1997 Massachusetts Institute of Technology
37  *
38  * Permission to use, copy, modify, and distribute this software and
39  * its documentation for any purpose and without fee is hereby
40  * granted, provided that both the above copyright notice and this
41  * permission notice appear in all copies, that both the above
42  * copyright notice and this permission notice appear in all
43  * supporting documentation, and that the name of M.I.T. not be used
44  * in advertising or publicity pertaining to distribution of the
45  * software without specific, written prior permission.	 M.I.T. makes
46  * no representations about the suitability of this software for any
47  * purpose.  It is provided "as is" without express or implied
48  * warranty.
49  *
50  * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
51  * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
52  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
53  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
54  * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
55  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
56  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
57  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
58  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
59  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
60  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
61  * SUCH DAMAGE. */
62 
63 #include <sys/param.h>
64 #include <sys/socket.h>
65 
66 #include <err.h>
67 #include <ctype.h>
68 #include <locale.h>
69 #include <netdb.h>
70 #include <stdarg.h>
71 #include <stdio.h>
72 #include <stdlib.h>
73 #include <string.h>
74 #include <time.h>
75 #include <unistd.h>
76 
77 #include "fetch.h"
78 #include "common.h"
79 #include "httperr.h"
80 
81 extern char *__progname;
82 
83 #define ENDL "\r\n"
84 
85 #define HTTP_OK		200
86 #define HTTP_PARTIAL	206
87 #define HTTP_MOVED	302
88 
89 struct cookie
90 {
91     FILE *real_f;
92 #define ENC_NONE 0
93 #define ENC_CHUNKED 1
94     int encoding;			/* 1 = chunked, 0 = none */
95 #define HTTPCTYPELEN 59
96     char content_type[HTTPCTYPELEN+1];
97     char *buf;
98     int b_cur, eof;
99     unsigned b_len, chunksize;
100 };
101 
102 /*
103  * Send a formatted line; optionally echo to terminal
104  */
105 static int
106 _http_cmd(FILE *f, char *fmt, ...)
107 {
108     va_list ap;
109 
110     va_start(ap, fmt);
111     vfprintf(f, fmt, ap);
112     DEBUG(fprintf(stderr, "\033[1m>>> "));
113     DEBUG(vfprintf(stderr, fmt, ap));
114     DEBUG(fprintf(stderr, "\033[m"));
115     va_end(ap);
116 
117     return 0; /* XXX */
118 }
119 
120 /*
121  * Fill the input buffer, do chunk decoding on the fly
122  */
123 static char *
124 _http_fillbuf(struct cookie *c)
125 {
126     char *ln;
127     unsigned int len;
128 
129     if (c->eof)
130 	return NULL;
131 
132     if (c->encoding == ENC_NONE) {
133 	c->buf = fgetln(c->real_f, &(c->b_len));
134 	c->b_cur = 0;
135     } else if (c->encoding == ENC_CHUNKED) {
136 	if (c->chunksize == 0) {
137 	    ln = fgetln(c->real_f, &len);
138 	    if (len <= 2)
139 		return NULL;
140 	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: "
141 			  "%*.*s\033[m\n", (int)len-2, (int)len-2, ln));
142 	    sscanf(ln, "%x", &(c->chunksize));
143 	    if (!c->chunksize) {
144 		DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
145 			      "end of last chunk\033[m\n"));
146 		c->eof = 1;
147 		return NULL;
148 	    }
149 	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
150 			  "new chunk: %X\033[m\n", c->chunksize));
151 	}
152 	c->buf = fgetln(c->real_f, &(c->b_len));
153 	if (c->b_len > c->chunksize)
154 	    c->b_len = c->chunksize;
155 	c->chunksize -= c->b_len;
156 	c->b_cur = 0;
157     }
158     else return NULL; /* unknown encoding */
159     return c->buf;
160 }
161 
162 /*
163  * Read function
164  */
165 static int
166 _http_readfn(struct cookie *c, char *buf, int len)
167 {
168     int l, pos = 0;
169     while (len) {
170 	/* empty buffer */
171 	if (!c->buf || (c->b_cur == c->b_len))
172 	    if (!_http_fillbuf(c))
173 		break;
174 
175 	l = c->b_len - c->b_cur;
176 	if (len < l) l = len;
177 	memcpy(buf + pos, c->buf + c->b_cur, l);
178 	c->b_cur += l;
179 	pos += l;
180 	len -= l;
181     }
182 
183     if (ferror(c->real_f))
184 	return -1;
185     else return pos;
186 }
187 
188 /*
189  * Write function
190  */
191 static int
192 _http_writefn(struct cookie *c, const char *buf, int len)
193 {
194     size_t r = fwrite(buf, 1, (size_t)len, c->real_f);
195     return r ? r : -1;
196 }
197 
198 /*
199  * Close function
200  */
201 static int
202 _http_closefn(struct cookie *c)
203 {
204     int r = fclose(c->real_f);
205     free(c);
206     return (r == EOF) ? -1 : 0;
207 }
208 
209 /*
210  * Extract content type from cookie
211  */
212 char *
213 fetchContentType(FILE *f)
214 {
215     /*
216      * We have no way of making sure this really *is* one of our cookies,
217      * so just check for a null pointer and hope for the best.
218      */
219     return f->_cookie ? (((struct cookie *)f->_cookie)->content_type) : NULL;
220 }
221 
222 /*
223  * Base64 encoding
224  */
225 static char *
226 _http_base64(char *src)
227 {
228     static const char base64[] =
229 	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
230 	"abcdefghijklmnopqrstuvwxyz"
231 	"0123456789+/";
232     char *str, *dst;
233     size_t l;
234     int t, r;
235 
236     l = strlen(src);
237     if ((str = malloc(((l + 2) / 3) * 4)) == NULL)
238 	return NULL;
239     dst = str;
240     r = 0;
241 
242     while (l >= 3) {
243 	t = (src[0] << 16) | (src[1] << 8) | src[2];
244 	dst[0] = base64[(t >> 18) & 0x3f];
245 	dst[1] = base64[(t >> 12) & 0x3f];
246 	dst[2] = base64[(t >> 6) & 0x3f];
247 	dst[3] = base64[(t >> 0) & 0x3f];
248 	src += 3; l -= 3;
249 	dst += 4; r += 4;
250     }
251 
252     switch (l) {
253     case 2:
254 	t = (src[0] << 16) | (src[1] << 8);
255 	dst[0] = base64[(t >> 18) & 0x3f];
256 	dst[1] = base64[(t >> 12) & 0x3f];
257 	dst[2] = base64[(t >> 6) & 0x3f];
258 	dst[3] = '=';
259 	dst += 4;
260 	r += 4;
261 	break;
262     case 1:
263 	t = src[0] << 16;
264 	dst[0] = base64[(t >> 18) & 0x3f];
265 	dst[1] = base64[(t >> 12) & 0x3f];
266 	dst[2] = dst[3] = '=';
267 	dst += 4;
268 	r += 4;
269 	break;
270     case 0:
271 	break;
272     }
273 
274     *dst = 0;
275     return str;
276 }
277 
278 /*
279  * Encode username and password
280  */
281 static int
282 _http_basic_auth(FILE *f, char *hdr, char *usr, char *pwd)
283 {
284     char *upw, *auth;
285     int r;
286 
287     if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
288 	return -1;
289     auth = _http_base64(upw);
290     free(upw);
291     if (auth == NULL)
292 	return -1;
293     r = _http_cmd(f, "%s: Basic %s" ENDL, hdr, auth);
294     free(auth);
295     return r;
296 }
297 
298 /*
299  * Send an authorization header
300  */
301 static int
302 _http_authorize(FILE *f, char *hdr, char *p)
303 {
304     /* basic authorization */
305     if (strncasecmp(p, "basic:", 6) == 0) {
306 	char *user, *pwd, *str;
307 	int r;
308 
309 	/* skip realm */
310 	for (p += 6; *p && *p != ':'; ++p)
311 	    /* nothing */ ;
312 	if (!*p || strchr(++p, ':') == NULL)
313 	    return -1;
314 	if ((str = strdup(p)) == NULL)
315 	    return -1; /* XXX */
316 	user = str;
317 	pwd = strchr(str, ':');
318 	*pwd++ = '\0';
319 	r = _http_basic_auth(f, hdr, user, pwd);
320 	free(str);
321 	return r;
322     }
323     return -1;
324 }
325 
326 /*
327  * Connect to server or proxy
328  */
329 static FILE *
330 _http_connect(struct url *URL, char *flags, int *proxy)
331 {
332     int direct, sd = -1, verbose;
333 #ifdef INET6
334     int af = AF_UNSPEC;
335 #else
336     int af = AF_INET;
337 #endif
338     size_t len;
339     char *px;
340     FILE *f;
341 
342     direct = (flags && strchr(flags, 'd'));
343     verbose = (flags && strchr(flags, 'v'));
344     if ((flags && strchr(flags, '4')))
345 	af = AF_INET;
346     else if ((flags && strchr(flags, '6')))
347 	af = AF_INET6;
348 
349     /* check port */
350     if (!URL->port) {
351 	struct servent *se;
352 
353 	if (strcasecmp(URL->scheme, "ftp") == 0)
354 	    if ((se = getservbyname("ftp", "tcp")) != NULL)
355 		URL->port = ntohs(se->s_port);
356 	    else
357 		URL->port = 21;
358 	else
359 	    if ((se = getservbyname("http", "tcp")) != NULL)
360 		URL->port = ntohs(se->s_port);
361 	    else
362 		URL->port = 80;
363     }
364 
365     /* attempt to connect to proxy server */
366     if (!direct && (px = getenv("HTTP_PROXY")) != NULL) {
367 	char host[MAXHOSTNAMELEN];
368 	int port = 0;
369 
370 	/* measure length */
371 #ifdef INET6
372 	if (px[0] != '[' ||
373 	    (len = strcspn(px, "]")) >= strlen(px) ||
374 	    (px[++len] != '\0' && px[len] != ':'))
375 #endif
376 	    len = strcspn(px, ":");
377 
378 	/* get port (XXX atoi is a little too tolerant perhaps?) */
379 	if (px[len] == ':') {
380 	    if (strspn(px+len+1, "0123456789") != strlen(px+len+1)
381 		|| strlen(px+len+1) > 5) {
382 		/* XXX we should emit some kind of warning */
383 	    }
384 	    port = atoi(px+len+1);
385 	    if (port < 1 || port > 65535) {
386 		/* XXX we should emit some kind of warning */
387 	    }
388 	}
389 	if (!port) {
390 #if 0
391 	    /*
392 	     * commented out, since there is currently no service name
393 	     * for HTTP proxies
394 	     */
395 	    struct servent *se;
396 
397 	    if ((se = getservbyname("xxxx", "tcp")) != NULL)
398 		port = ntohs(se->s_port);
399 	    else
400 #endif
401 		port = 3128;
402 	}
403 
404 	/* get host name */
405 #ifdef INET6
406 	if (len > 1 && px[0] == '[' && px[len - 1] == ']') {
407 	    px++;
408 	    len -= 2;
409 	}
410 #endif
411 	if (len >= MAXHOSTNAMELEN)
412 	    len = MAXHOSTNAMELEN - 1;
413 	strncpy(host, px, len);
414 	host[len] = 0;
415 
416 	/* connect */
417 	sd = _fetch_connect(host, port, af, verbose);
418     }
419 
420     /* if no proxy is configured or could be contacted, try direct */
421     *proxy = (sd != -1);
422     if (!*proxy) {
423 	if (strcasecmp(URL->scheme, "ftp") == 0)
424 	    goto ouch;
425 	if ((sd = _fetch_connect(URL->host, URL->port, af, verbose)) == -1)
426 	    goto ouch;
427     }
428 
429     /* reopen as stream */
430     if ((f = fdopen(sd, "r+")) == NULL)
431 	goto ouch;
432 
433     return f;
434 
435 ouch:
436     if (sd >= 0)
437 	close(sd);
438     _http_seterr(999); /* XXX do this properly RSN */
439     return NULL;
440 }
441 
442 /*
443  * Check a header line
444  */
445 static char *
446 _http_match(char *str, char *hdr)
447 {
448     while (*str && *hdr && tolower(*str++) == tolower(*hdr++))
449 	/* nothing */;
450     if (*str || *hdr != ':')
451 	return NULL;
452     while (*hdr && isspace(*++hdr))
453 	/* nothing */;
454     return hdr;
455 }
456 
457 /*
458  * Send a HEAD or GET request
459  */
460 static int
461 _http_request(FILE *f, char *op, struct url *URL, char *flags, int proxy)
462 {
463     int e, verbose;
464     char *ln, *p;
465     size_t len;
466     char *host;
467 #ifdef INET6
468     char hbuf[MAXHOSTNAMELEN + 1];
469 #endif
470 
471     verbose = (flags && strchr(flags, 'v'));
472 
473     host = URL->host;
474 #ifdef INET6
475     if (strchr(URL->host, ':')) {
476 	snprintf(hbuf, sizeof(hbuf), "[%s]", URL->host);
477 	host = hbuf;
478     }
479 #endif
480 
481     /* send request (proxies require absolute form) */
482     if (verbose)
483 	_fetch_info("requesting %s://%s:%d%s",
484 		    URL->scheme, host, URL->port, URL->doc);
485     if (proxy)
486 	_http_cmd(f, "%s %s://%s:%d%s HTTP/1.1" ENDL,
487 		  op, URL->scheme, host, URL->port, URL->doc);
488     else
489 	_http_cmd(f, "%s %s HTTP/1.1" ENDL, op, URL->doc);
490 
491     /* start sending headers away */
492     if (URL->user[0] || URL->pwd[0])
493 	_http_basic_auth(f, "Authorization",
494 			 URL->user ? URL->user : "",
495 			 URL->pwd ? URL->pwd : "");
496     else if ((p = getenv("HTTP_AUTH")) != NULL)
497 	_http_authorize(f, "Authorization", p);
498     if (proxy &&  (p = getenv("HTTP_PROXY_AUTH")) != NULL)
499 	_http_authorize(f, "Proxy-Authorization", p);
500     _http_cmd(f, "Host: %s:%d" ENDL, host, URL->port);
501     _http_cmd(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname);
502     if (URL->offset)
503 	_http_cmd(f, "Range: bytes=%lld-" ENDL, URL->offset);
504     _http_cmd(f, "Connection: close" ENDL ENDL);
505 
506     /* get response */
507     if ((ln = fgetln(f, &len)) == NULL)
508 	return 999;
509     DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n",
510 		  (int)len-2, (int)len-2, ln));
511 
512     /* we can't use strchr() and friends since ln isn't NUL-terminated */
513     p = ln;
514     while ((p < ln + len) && !isspace(*p))
515 	p++;
516     while ((p < ln + len) && !isdigit(*p))
517 	p++;
518     if (!isdigit(*p))
519 	return 999;
520 
521     e = atoi(p);
522     DEBUG(fprintf(stderr, "code:     [\033[1m%d\033[m]\n", e));
523     return e;
524 }
525 
526 /*
527  * Retrieve a file by HTTP
528  */
529 FILE *
530 fetchGetHTTP(struct url *URL, char *flags)
531 {
532     int e, enc = ENC_NONE, i, noredirect, proxy;
533     struct cookie *c;
534     char *ln, *p, *q;
535     FILE *f, *cf;
536     size_t len;
537     off_t pos = 0;
538 
539     noredirect = (flags && strchr(flags, 'A'));
540 
541     /* allocate cookie */
542     if ((c = calloc(1, sizeof *c)) == NULL)
543 	return NULL;
544 
545     /* connect */
546     if ((f = _http_connect(URL, flags, &proxy)) == NULL) {
547 	free(c);
548 	return NULL;
549     }
550     c->real_f = f;
551 
552     e = _http_request(f, "GET", URL, flags, proxy);
553     if (e != (URL->offset ? HTTP_PARTIAL : HTTP_OK)
554 	&& (e != HTTP_MOVED || noredirect)) {
555 	_http_seterr(e);
556 	free(c);
557 	fclose(f);
558 	return NULL;
559     }
560 
561     /* browse through header */
562     while (1) {
563 	if ((ln = fgetln(f, &len)) == NULL)
564 	    goto fouch;
565 	if ((ln[0] == '\r') || (ln[0] == '\n'))
566 	    break;
567 	while (isspace(ln[len-1]))
568 	    --len;
569 	ln[len] = '\0'; /* XXX */
570 	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
571 	if ((p = _http_match("Location", ln)) != NULL) {
572 	    struct url *url;
573 
574 	    for (q = p; *q && !isspace(*q); q++)
575 		/* VOID */ ;
576 	    *q = 0;
577 	    if ((url = fetchParseURL(p)) == NULL)
578 		goto fouch;
579 	    url->offset = URL->offset;
580 	    url->length = URL->length;
581 	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
582 	    cf = fetchGetHTTP(url, flags);
583 	    fetchFreeURL(url);
584 	    fclose(f);
585 	    return cf;
586 	} else if ((p = _http_match("Transfer-Encoding", ln)) != NULL) {
587 	    for (q = p; *q && !isspace(*q); q++)
588 		/* VOID */ ;
589 	    *q = 0;
590 	    if (strcasecmp(p, "chunked") == 0)
591 		enc = ENC_CHUNKED;
592 	    DEBUG(fprintf(stderr, "transfer encoding:  [\033[1m%s\033[m]\n", p));
593 	} else if ((p = _http_match("Content-Type", ln)) != NULL) {
594 	    for (i = 0; *p && i < HTTPCTYPELEN; p++, i++)
595 		    c->content_type[i] = *p;
596 	    do c->content_type[i--] = 0; while (isspace(c->content_type[i]));
597 	    DEBUG(fprintf(stderr, "content type: [\033[1m%s\033[m]\n",
598 			  c->content_type));
599 	} else if ((p = _http_match("Content-Range", ln)) != NULL) {
600 	    if (strncasecmp(p, "bytes ", 6) != 0)
601 		goto fouch;
602 	    p += 6;
603 	    while (*p && isdigit(*p))
604 		pos = pos * 10 + (*p++ - '0');
605 	    /* XXX wouldn't hurt to be slightly more paranoid here */
606 	    DEBUG(fprintf(stderr, "content range: [\033[1m%lld-\033[m]\n", pos));
607 	    if (pos > URL->offset)
608 		goto fouch;
609 	}
610     }
611 
612     /* only body remains */
613     c->encoding = enc;
614     cf = funopen(c,
615 		 (int (*)(void *, char *, int))_http_readfn,
616 		 (int (*)(void *, const char *, int))_http_writefn,
617 		 (fpos_t (*)(void *, fpos_t, int))NULL,
618 		 (int (*)(void *))_http_closefn);
619     if (cf == NULL)
620 	goto fouch;
621 
622     while (pos < URL->offset)
623 	if (fgetc(cf) == EOF)
624 	    goto cfouch;
625 
626     return cf;
627 
628 fouch:
629     fclose(f);
630     free(c);
631     _http_seterr(999); /* XXX do this properly RSN */
632     return NULL;
633 cfouch:
634     fclose(cf);
635     _http_seterr(999); /* XXX do this properly RSN */
636     return NULL;
637 }
638 
639 FILE *
640 fetchPutHTTP(struct url *URL, char *flags)
641 {
642     warnx("fetchPutHTTP(): not implemented");
643     return NULL;
644 }
645 
646 /*
647  * Get an HTTP document's metadata
648  */
649 int
650 fetchStatHTTP(struct url *URL, struct url_stat *us, char *flags)
651 {
652     int e, noredirect, proxy;
653     size_t len;
654     char *ln, *p, *q;
655     FILE *f;
656 
657     noredirect = (flags && strchr(flags, 'A'));
658 
659     us->size = -1;
660     us->atime = us->mtime = 0;
661 
662     /* connect */
663     if ((f = _http_connect(URL, flags, &proxy)) == NULL)
664 	return -1;
665 
666     e = _http_request(f, "HEAD", URL, flags, proxy);
667     if (e != HTTP_OK && (e != HTTP_MOVED || noredirect)) {
668 	_http_seterr(e);
669 	fclose(f);
670 	return -1;
671     }
672 
673     while (1) {
674 	if ((ln = fgetln(f, &len)) == NULL)
675 	    goto fouch;
676 	if ((ln[0] == '\r') || (ln[0] == '\n'))
677 	    break;
678 	while (isspace(ln[len-1]))
679 	    --len;
680 	ln[len] = '\0'; /* XXX */
681 	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
682 	if ((p = _http_match("Location", ln)) != NULL) {
683 	    struct url *url;
684 
685 	    for (q = p; *q && !isspace(*q); q++)
686 		/* VOID */ ;
687 	    *q = 0;
688 	    if ((url = fetchParseURL(p)) == NULL)
689 		goto ouch;
690 	    url->offset = URL->offset;
691 	    url->length = URL->length;
692 	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
693 	    e = fetchStatHTTP(url, us, flags);
694 	    fetchFreeURL(url);
695 	    fclose(f);
696 	    return e;
697 	} else if ((p = _http_match("Last-Modified", ln)) != NULL) {
698 	    struct tm tm;
699 	    char locale[64];
700 
701 	    strncpy(locale, setlocale(LC_TIME, NULL), sizeof locale);
702 	    setlocale(LC_TIME, "C");
703 	    strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
704 	    /* XXX should add support for date-2 and date-3 */
705 	    setlocale(LC_TIME, locale);
706 	    us->atime = us->mtime = timegm(&tm);
707 	    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
708 			  "%02d:%02d:%02d\033[m]\n",
709 			  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
710 			  tm.tm_hour, tm.tm_min, tm.tm_sec));
711 	} else if ((p = _http_match("Content-Length", ln)) != NULL) {
712 	    us->size = 0;
713 	    while (*p && isdigit(*p))
714 		us->size = us->size * 10 + (*p++ - '0');
715 	    DEBUG(fprintf(stderr, "content length: [\033[1m%lld\033[m]\n", us->size));
716 	}
717     }
718 
719     fclose(f);
720     return 0;
721  ouch:
722     _http_seterr(999); /* XXX do this properly RSN */
723  fouch:
724     fclose(f);
725     return -1;
726 }
727 
728 /*
729  * List a directory
730  */
731 struct url_ent *
732 fetchListHTTP(struct url *url, char *flags)
733 {
734     warnx("fetchListHTTP(): not implemented");
735     return NULL;
736 }
737