http.c revision 62812
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: head/lib/libfetch/http.c 62811 2000-07-08 08:08:58Z des $
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
81extern 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
89struct 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 */
105static 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#ifndef NDEBUG
113    fprintf(stderr, "\033[1m>>> ");
114    vfprintf(stderr, fmt, ap);
115    fprintf(stderr, "\033[m");
116#endif
117    va_end(ap);
118
119    return 0; /* XXX */
120}
121
122/*
123 * Fill the input buffer, do chunk decoding on the fly
124 */
125static char *
126_http_fillbuf(struct cookie *c)
127{
128    char *ln;
129    unsigned int len;
130
131    if (c->eof)
132	return NULL;
133
134    if (c->encoding == ENC_NONE) {
135	c->buf = fgetln(c->real_f, &(c->b_len));
136	c->b_cur = 0;
137    } else if (c->encoding == ENC_CHUNKED) {
138	if (c->chunksize == 0) {
139	    ln = fgetln(c->real_f, &len);
140	    if (len <= 2)
141		return NULL;
142	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: "
143			  "%*.*s\033[m\n", (int)len-2, (int)len-2, ln));
144	    sscanf(ln, "%x", &(c->chunksize));
145	    if (!c->chunksize) {
146		DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
147			      "end of last chunk\033[m\n"));
148		c->eof = 1;
149		return NULL;
150	    }
151	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
152			  "new chunk: %X\033[m\n", c->chunksize));
153	}
154	c->buf = fgetln(c->real_f, &(c->b_len));
155	if (c->b_len > c->chunksize)
156	    c->b_len = c->chunksize;
157	c->chunksize -= c->b_len;
158	c->b_cur = 0;
159    }
160    else return NULL; /* unknown encoding */
161    return c->buf;
162}
163
164/*
165 * Read function
166 */
167static int
168_http_readfn(struct cookie *c, char *buf, int len)
169{
170    int l, pos = 0;
171    while (len) {
172	/* empty buffer */
173	if (!c->buf || (c->b_cur == c->b_len))
174	    if (!_http_fillbuf(c))
175		break;
176
177	l = c->b_len - c->b_cur;
178	if (len < l) l = len;
179	memcpy(buf + pos, c->buf + c->b_cur, l);
180	c->b_cur += l;
181	pos += l;
182	len -= l;
183    }
184
185    if (ferror(c->real_f))
186	return -1;
187    else return pos;
188}
189
190/*
191 * Write function
192 */
193static int
194_http_writefn(struct cookie *c, const char *buf, int len)
195{
196    size_t r = fwrite(buf, 1, (size_t)len, c->real_f);
197    return r ? r : -1;
198}
199
200/*
201 * Close function
202 */
203static int
204_http_closefn(struct cookie *c)
205{
206    int r = fclose(c->real_f);
207    free(c);
208    return (r == EOF) ? -1 : 0;
209}
210
211/*
212 * Extract content type from cookie
213 */
214char *
215fetchContentType(FILE *f)
216{
217    /*
218     * We have no way of making sure this really *is* one of our cookies,
219     * so just check for a null pointer and hope for the best.
220     */
221    return f->_cookie ? (((struct cookie *)f->_cookie)->content_type) : NULL;
222}
223
224/*
225 * Base64 encoding
226 */
227int
228_http_base64(char *dst, char *src, int l)
229{
230    static const char base64[] =
231	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
232	"abcdefghijklmnopqrstuvwxyz"
233	"0123456789+/";
234    int t, r = 0;
235
236    while (l >= 3) {
237	t = (src[0] << 16) | (src[1] << 8) | src[2];
238	dst[0] = base64[(t >> 18) & 0x3f];
239	dst[1] = base64[(t >> 12) & 0x3f];
240	dst[2] = base64[(t >> 6) & 0x3f];
241	dst[3] = base64[(t >> 0) & 0x3f];
242	src += 3; l -= 3;
243	dst += 4; r += 4;
244    }
245
246    switch (l) {
247    case 2:
248	t = (src[0] << 16) | (src[1] << 8);
249	dst[0] = base64[(t >> 18) & 0x3f];
250	dst[1] = base64[(t >> 12) & 0x3f];
251	dst[2] = base64[(t >> 6) & 0x3f];
252	dst[3] = '=';
253	dst += 4;
254	r += 4;
255	break;
256    case 1:
257	t = src[0] << 16;
258	dst[0] = base64[(t >> 18) & 0x3f];
259	dst[1] = base64[(t >> 12) & 0x3f];
260	dst[2] = dst[3] = '=';
261	dst += 4;
262	r += 4;
263	break;
264    case 0:
265	break;
266    }
267
268    *dst = 0;
269    return r;
270}
271
272/*
273 * Encode username and password
274 */
275char *
276_http_auth(char *usr, char *pwd)
277{
278    int len, lup;
279    char *uandp, *str = NULL;
280
281    lup = strlen(usr) + 1 + strlen(pwd);/* length of "usr:pwd" */
282    uandp = (char*)malloc(lup + 1);
283    if (uandp) {
284	len = ((lup + 2) / 3) * 4;	/* length of base64 encoded "usr:pwd" incl. padding */
285	str = (char*)malloc(len + 1);
286	if (str) {
287	    strcpy(uandp, usr);
288	    strcat(uandp, ":");
289	    strcat(uandp, pwd);
290	    _http_base64(str, uandp, lup);
291	}
292	free(uandp);
293    }
294    return str;
295}
296
297/*
298 * Connect to server or proxy
299 */
300FILE *
301_http_connect(struct url *URL, char *flags)
302{
303    int direct, sd = -1, verbose;
304#ifdef INET6
305    int af = AF_UNSPEC;
306#else
307    int af = AF_INET;
308#endif
309    size_t len;
310    char *px;
311    FILE *f;
312
313    direct = (flags && strchr(flags, 'd'));
314    verbose = (flags && strchr(flags, 'v'));
315    if ((flags && strchr(flags, '4')))
316	af = AF_INET;
317    else if ((flags && strchr(flags, '6')))
318	af = AF_INET6;
319
320    /* check port */
321    if (!URL->port) {
322	struct servent *se;
323
324	if (strcasecmp(URL->scheme, "ftp") == 0)
325	    if ((se = getservbyname("ftp", "tcp")) != NULL)
326		URL->port = ntohs(se->s_port);
327	    else
328		URL->port = 21;
329	else
330	    if ((se = getservbyname("http", "tcp")) != NULL)
331		URL->port = ntohs(se->s_port);
332	    else
333		URL->port = 80;
334    }
335
336    /* attempt to connect to proxy server */
337    if (!direct && (px = getenv("HTTP_PROXY")) != NULL) {
338	char host[MAXHOSTNAMELEN];
339	int port = 0;
340
341	/* measure length */
342#ifdef INET6
343	if (px[0] != '[' ||
344	    (len = strcspn(px, "]")) >= strlen(px) ||
345	    (px[++len] != '\0' && px[len] != ':'))
346#endif
347	    len = strcspn(px, ":");
348
349	/* get port (XXX atoi is a little too tolerant perhaps?) */
350	if (px[len] == ':') {
351	    if (strspn(px+len+1, "0123456789") != strlen(px+len+1)
352		|| strlen(px+len+1) > 5) {
353		/* XXX we should emit some kind of warning */
354	    }
355	    port = atoi(px+len+1);
356	    if (port < 1 || port > 65535) {
357		/* XXX we should emit some kind of warning */
358	    }
359	}
360	if (!port) {
361#if 0
362	    /*
363	     * commented out, since there is currently no service name
364	     * for HTTP proxies
365	     */
366	    struct servent *se;
367
368	    if ((se = getservbyname("xxxx", "tcp")) != NULL)
369		port = ntohs(se->s_port);
370	    else
371#endif
372		port = 3128;
373	}
374
375	/* get host name */
376#ifdef INET6
377	if (len > 1 && px[0] == '[' && px[len - 1] == ']') {
378	    px++;
379	    len -= 2;
380	}
381#endif
382	if (len >= MAXHOSTNAMELEN)
383	    len = MAXHOSTNAMELEN - 1;
384	strncpy(host, px, len);
385	host[len] = 0;
386
387	/* connect */
388	sd = _fetch_connect(host, port, af, verbose);
389    }
390
391    /* if no proxy is configured or could be contacted, try direct */
392    if (sd == -1) {
393	if (strcasecmp(URL->scheme, "ftp") == 0)
394	    goto ouch;
395	if ((sd = _fetch_connect(URL->host, URL->port, af, verbose)) == -1)
396	    goto ouch;
397    }
398
399    /* reopen as stream */
400    if ((f = fdopen(sd, "r+")) == NULL)
401	goto ouch;
402
403    return f;
404
405ouch:
406    if (sd >= 0)
407	close(sd);
408    _http_seterr(999); /* XXX do this properly RSN */
409    return NULL;
410}
411
412/*
413 * Check a header line
414 */
415char *
416_http_match(char *str, char *hdr)
417{
418    while (*str && *hdr && tolower(*str++) == tolower(*hdr++))
419	/* nothing */;
420    if (*str || *hdr != ':')
421	return NULL;
422    while (*hdr && isspace(*++hdr))
423	/* nothing */;
424    return hdr;
425}
426
427/*
428 * Send a HEAD or GET request
429 */
430int
431_http_request(FILE *f, char *op, struct url *URL, char *flags)
432{
433    int e, verbose;
434    char *ln, *p;
435    size_t len;
436    char *host;
437#ifdef INET6
438    char hbuf[MAXHOSTNAMELEN + 1];
439#endif
440
441    verbose = (flags && strchr(flags, 'v'));
442
443    host = URL->host;
444#ifdef INET6
445    if (strchr(URL->host, ':')) {
446	snprintf(hbuf, sizeof(hbuf), "[%s]", URL->host);
447	host = hbuf;
448    }
449#endif
450
451    /* send request (proxies require absolute form, so use that) */
452    if (verbose)
453	_fetch_info("requesting %s://%s:%d%s",
454		    URL->scheme, host, URL->port, URL->doc);
455    _http_cmd(f, "%s %s://%s:%d%s HTTP/1.1" ENDL,
456	      op, URL->scheme, host, URL->port, URL->doc);
457
458    /* start sending headers away */
459    if (URL->user[0] || URL->pwd[0]) {
460	char *auth_str = _http_auth(URL->user, URL->pwd);
461	if (!auth_str)
462	    return 999; /* XXX wrong */
463	_http_cmd(f, "Authorization: Basic %s" ENDL, auth_str);
464	free(auth_str);
465    }
466    if (p = getenv("HTTP_PROXY_AUTH")) {
467	char *auth;
468
469	/* skip leading "basic:*:", if present */
470	if (strncmp(p, "basic:*:", 6 + 2) == 0)
471	    p += 6 + 2;
472	auth = strchr(p, ':');
473	if (auth != NULL) {
474	    int len = auth - p;
475	    char *user;
476	    char *auth_str;
477
478	    if ((user = (char*)malloc(len + 1)) == NULL) {
479		free(auth);
480		return 999; /* XXX wrong */
481	    }
482	    strncpy(user, p, len);
483	    user[len] = 0;
484	    auth++;
485	    auth_str = _http_auth(user, auth);
486	    free(user);
487	    if (auth_str == NULL)
488		return 999; /* XXX wrong */
489	    _http_cmd(f, "Proxy-Authorization: Basic %s" ENDL, auth_str);
490	    free(auth_str);
491	} else {
492	    return 999; /* XXX wrong */
493	}
494    }
495    _http_cmd(f, "Host: %s:%d" ENDL, host, URL->port);
496    _http_cmd(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname);
497    if (URL->offset)
498	_http_cmd(f, "Range: bytes=%lld-" ENDL, URL->offset);
499    _http_cmd(f, "Connection: close" ENDL ENDL);
500
501    /* get response */
502    if ((ln = fgetln(f, &len)) == NULL)
503	return 999;
504    DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n",
505		  (int)len-2, (int)len-2, ln));
506
507    /* we can't use strchr() and friends since ln isn't NUL-terminated */
508    p = ln;
509    while ((p < ln + len) && !isspace(*p))
510	p++;
511    while ((p < ln + len) && !isdigit(*p))
512	p++;
513    if (!isdigit(*p))
514	return 999;
515
516    e = atoi(p);
517    DEBUG(fprintf(stderr, "code:     [\033[1m%d\033[m]\n", e));
518    return e;
519}
520
521/*
522 * Retrieve a file by HTTP
523 */
524FILE *
525fetchGetHTTP(struct url *URL, char *flags)
526{
527    int e, enc = ENC_NONE, i, noredirect;
528    struct cookie *c;
529    char *ln, *p, *q;
530    FILE *f, *cf;
531    size_t len;
532    off_t pos = 0;
533
534    noredirect = (flags && strchr(flags, 'A'));
535
536    /* allocate cookie */
537    if ((c = calloc(1, sizeof *c)) == NULL)
538	return NULL;
539
540    /* connect */
541    if ((f = _http_connect(URL, flags)) == NULL) {
542	free(c);
543	return NULL;
544    }
545    c->real_f = f;
546
547    e = _http_request(f, "GET", URL, flags);
548    if (e != (URL->offset ? HTTP_PARTIAL : HTTP_OK)
549	&& (e != HTTP_MOVED || noredirect)) {
550	_http_seterr(e);
551	free(c);
552	fclose(f);
553	return NULL;
554    }
555
556    /* browse through header */
557    while (1) {
558	if ((ln = fgetln(f, &len)) == NULL)
559	    goto fouch;
560	if ((ln[0] == '\r') || (ln[0] == '\n'))
561	    break;
562	while (isspace(ln[len-1]))
563	    --len;
564	ln[len] = '\0'; /* XXX */
565	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
566	if ((p = _http_match("Location", ln)) != NULL) {
567	    struct url *url;
568
569	    for (q = p; *q && !isspace(*q); q++)
570		/* VOID */ ;
571	    *q = 0;
572	    if ((url = fetchParseURL(p)) == NULL)
573		goto fouch;
574	    url->offset = URL->offset;
575	    url->length = URL->length;
576	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
577	    cf = fetchGetHTTP(url, flags);
578	    fetchFreeURL(url);
579	    fclose(f);
580	    return cf;
581	} else if ((p = _http_match("Transfer-Encoding", ln)) != NULL) {
582	    for (q = p; *q && !isspace(*q); q++)
583		/* VOID */ ;
584	    *q = 0;
585	    if (strcasecmp(p, "chunked") == 0)
586		enc = ENC_CHUNKED;
587	    DEBUG(fprintf(stderr, "transfer encoding:  [\033[1m%s\033[m]\n", p));
588	} else if ((p = _http_match("Content-Type", ln)) != NULL) {
589	    for (i = 0; *p && i < HTTPCTYPELEN; p++, i++)
590		    c->content_type[i] = *p;
591	    do c->content_type[i--] = 0; while (isspace(c->content_type[i]));
592	    DEBUG(fprintf(stderr, "content type: [\033[1m%s\033[m]\n",
593			  c->content_type));
594	} else if ((p = _http_match("Content-Range", ln)) != NULL) {
595	    if (strncasecmp(p, "bytes ", 6) != 0)
596		goto fouch;
597	    p += 6;
598	    while (*p && isdigit(*p))
599		pos = pos * 10 + (*p++ - '0');
600	    /* XXX wouldn't hurt to be slightly more paranoid here */
601	    DEBUG(fprintf(stderr, "content range: [\033[1m%lld-\033[m]\n", pos));
602	    if (pos > URL->offset)
603		goto fouch;
604	}
605    }
606
607    /* only body remains */
608    c->encoding = enc;
609    cf = funopen(c,
610		 (int (*)(void *, char *, int))_http_readfn,
611		 (int (*)(void *, const char *, int))_http_writefn,
612		 (fpos_t (*)(void *, fpos_t, int))NULL,
613		 (int (*)(void *))_http_closefn);
614    if (cf == NULL)
615	goto fouch;
616
617    while (pos < URL->offset)
618	if (fgetc(cf) == EOF)
619	    goto cfouch;
620
621    return cf;
622
623fouch:
624    fclose(f);
625    free(c);
626    _http_seterr(999); /* XXX do this properly RSN */
627    return NULL;
628cfouch:
629    fclose(cf);
630    _http_seterr(999); /* XXX do this properly RSN */
631    return NULL;
632}
633
634FILE *
635fetchPutHTTP(struct url *URL, char *flags)
636{
637    warnx("fetchPutHTTP(): not implemented");
638    return NULL;
639}
640
641/*
642 * Get an HTTP document's metadata
643 */
644int
645fetchStatHTTP(struct url *URL, struct url_stat *us, char *flags)
646{
647    int e, noredirect;
648    size_t len;
649    char *ln, *p, *q;
650    FILE *f;
651
652    noredirect = (flags && strchr(flags, 'A'));
653
654    us->size = -1;
655    us->atime = us->mtime = 0;
656
657    /* connect */
658    if ((f = _http_connect(URL, flags)) == NULL)
659	return -1;
660
661    e = _http_request(f, "HEAD", URL, flags);
662    if (e != HTTP_OK && (e != HTTP_MOVED || noredirect)) {
663	_http_seterr(e);
664	fclose(f);
665	return -1;
666    }
667
668    while (1) {
669	if ((ln = fgetln(f, &len)) == NULL)
670	    goto fouch;
671	if ((ln[0] == '\r') || (ln[0] == '\n'))
672	    break;
673	while (isspace(ln[len-1]))
674	    --len;
675	ln[len] = '\0'; /* XXX */
676	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
677	if ((p = _http_match("Location", ln)) != NULL) {
678	    struct url *url;
679
680	    for (q = p; *q && !isspace(*q); q++)
681		/* VOID */ ;
682	    *q = 0;
683	    if ((url = fetchParseURL(p)) == NULL)
684		goto ouch;
685	    url->offset = URL->offset;
686	    url->length = URL->length;
687	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
688	    e = fetchStatHTTP(url, us, flags);
689	    fetchFreeURL(url);
690	    fclose(f);
691	    return e;
692	} else if ((p = _http_match("Last-Modified", ln)) != NULL) {
693	    struct tm tm;
694	    char locale[64];
695
696	    strncpy(locale, setlocale(LC_TIME, NULL), sizeof locale);
697	    setlocale(LC_TIME, "C");
698	    strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
699	    /* XXX should add support for date-2 and date-3 */
700	    setlocale(LC_TIME, locale);
701	    us->atime = us->mtime = timegm(&tm);
702	    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
703			  "%02d:%02d:%02d\033[m]\n",
704			  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
705			  tm.tm_hour, tm.tm_min, tm.tm_sec));
706	} else if ((p = _http_match("Content-Length", ln)) != NULL) {
707	    us->size = 0;
708	    while (*p && isdigit(*p))
709		us->size = us->size * 10 + (*p++ - '0');
710	    DEBUG(fprintf(stderr, "content length: [\033[1m%lld\033[m]\n", us->size));
711	}
712    }
713
714    fclose(f);
715    return 0;
716 ouch:
717    _http_seterr(999); /* XXX do this properly RSN */
718 fouch:
719    fclose(f);
720    return -1;
721}
722
723/*
724 * List a directory
725 */
726struct url_ent *
727fetchListHTTP(struct url *url, char *flags)
728{
729    warnx("fetchListHTTP(): not implemented");
730    return NULL;
731}
732