http.c revision 60376
137535Sdes/*-
237535Sdes * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
337535Sdes * All rights reserved.
437535Sdes *
537535Sdes * Redistribution and use in source and binary forms, with or without
637535Sdes * modification, are permitted provided that the following conditions
737535Sdes * are met:
837535Sdes * 1. Redistributions of source code must retain the above copyright
937535Sdes *    notice, this list of conditions and the following disclaimer
1037535Sdes *    in this position and unchanged.
1137535Sdes * 2. Redistributions in binary form must reproduce the above copyright
1237535Sdes *    notice, this list of conditions and the following disclaimer in the
1337535Sdes *    documentation and/or other materials provided with the distribution.
1437535Sdes * 3. The name of the author may not be used to endorse or promote products
1537535Sdes *    derived from this software without specific prior written permission
1637535Sdes *
1737535Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1837535Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1937535Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2037535Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2137535Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2237535Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2337535Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2437535Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2537535Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2637535Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2737535Sdes *
2850476Speter * $FreeBSD: head/lib/libfetch/http.c 60376 2000-05-11 13:31:02Z des $
2937535Sdes */
3037535Sdes
3137608Sdes/*
3237608Sdes * The base64 code in this file is based on code from MIT fetch, which
3337608Sdes * has the following copyright and license:
3437608Sdes *
3537608Sdes *-
3637608Sdes * Copyright 1997 Massachusetts Institute of Technology
3737608Sdes *
3837608Sdes * Permission to use, copy, modify, and distribute this software and
3937608Sdes * its documentation for any purpose and without fee is hereby
4037608Sdes * granted, provided that both the above copyright notice and this
4137608Sdes * permission notice appear in all copies, that both the above
4237608Sdes * copyright notice and this permission notice appear in all
4337608Sdes * supporting documentation, and that the name of M.I.T. not be used
4437608Sdes * in advertising or publicity pertaining to distribution of the
4560189Sdes * software without specific, written prior permission.	 M.I.T. makes
4637608Sdes * no representations about the suitability of this software for any
4737608Sdes * purpose.  It is provided "as is" without express or implied
4837608Sdes * warranty.
4937608Sdes *
5037608Sdes * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
5137608Sdes * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
5237608Sdes * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
5337608Sdes * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
5437608Sdes * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
5537608Sdes * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
5637608Sdes * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
5737608Sdes * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
5837608Sdes * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
5937608Sdes * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
6037608Sdes * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6137608Sdes * SUCH DAMAGE. */
6237608Sdes
6337535Sdes#include <sys/param.h>
6437535Sdes
6537535Sdes#include <err.h>
6637535Sdes#include <ctype.h>
6760376Sdes#include <locale.h>
6860189Sdes#include <netdb.h>
6937608Sdes#include <stdarg.h>
7037535Sdes#include <stdio.h>
7137535Sdes#include <stdlib.h>
7237535Sdes#include <string.h>
7360376Sdes#include <time.h>
7437535Sdes#include <unistd.h>
7537535Sdes
7637535Sdes#include "fetch.h"
7740939Sdes#include "common.h"
7841862Sdes#include "httperr.h"
7937535Sdes
8037535Sdesextern char *__progname;
8137535Sdes
8237535Sdes#define ENDL "\r\n"
8337535Sdes
8460196Sdes#define HTTP_OK		200
8560196Sdes#define HTTP_PARTIAL	206
8660196Sdes
8737535Sdesstruct cookie
8837535Sdes{
8937535Sdes    FILE *real_f;
9037535Sdes#define ENC_NONE 0
9137535Sdes#define ENC_CHUNKED 1
9237535Sdes    int encoding;			/* 1 = chunked, 0 = none */
9337535Sdes#define HTTPCTYPELEN 59
9437535Sdes    char content_type[HTTPCTYPELEN+1];
9537535Sdes    char *buf;
9637535Sdes    int b_cur, eof;
9737535Sdes    unsigned b_len, chunksize;
9837535Sdes};
9937535Sdes
10037608Sdes/*
10137608Sdes * Send a formatted line; optionally echo to terminal
10237608Sdes */
10337608Sdesstatic int
10437608Sdes_http_cmd(FILE *f, char *fmt, ...)
10537608Sdes{
10637608Sdes    va_list ap;
10737608Sdes
10837608Sdes    va_start(ap, fmt);
10937608Sdes    vfprintf(f, fmt, ap);
11037608Sdes#ifndef NDEBUG
11137608Sdes    fprintf(stderr, "\033[1m>>> ");
11237608Sdes    vfprintf(stderr, fmt, ap);
11337608Sdes    fprintf(stderr, "\033[m");
11437608Sdes#endif
11537608Sdes    va_end(ap);
11637608Sdes
11737608Sdes    return 0; /* XXX */
11837608Sdes}
11937608Sdes
12037608Sdes/*
12137608Sdes * Fill the input buffer, do chunk decoding on the fly
12237608Sdes */
12337535Sdesstatic char *
12437535Sdes_http_fillbuf(struct cookie *c)
12537535Sdes{
12637535Sdes    char *ln;
12737535Sdes    unsigned int len;
12837535Sdes
12937535Sdes    if (c->eof)
13037535Sdes	return NULL;
13137535Sdes
13237535Sdes    if (c->encoding == ENC_NONE) {
13337535Sdes	c->buf = fgetln(c->real_f, &(c->b_len));
13437535Sdes	c->b_cur = 0;
13537535Sdes    } else if (c->encoding == ENC_CHUNKED) {
13637535Sdes	if (c->chunksize == 0) {
13737535Sdes	    ln = fgetln(c->real_f, &len);
13837535Sdes	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: "
13937535Sdes			  "%*.*s\033[m\n", (int)len-2, (int)len-2, ln));
14037535Sdes	    sscanf(ln, "%x", &(c->chunksize));
14137535Sdes	    if (!c->chunksize) {
14237535Sdes		DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
14337535Sdes			      "end of last chunk\033[m\n"));
14437535Sdes		c->eof = 1;
14537535Sdes		return NULL;
14637535Sdes	    }
14737535Sdes	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
14837535Sdes			  "new chunk: %X\033[m\n", c->chunksize));
14937535Sdes	}
15037535Sdes	c->buf = fgetln(c->real_f, &(c->b_len));
15137535Sdes	if (c->b_len > c->chunksize)
15237535Sdes	    c->b_len = c->chunksize;
15337535Sdes	c->chunksize -= c->b_len;
15437535Sdes	c->b_cur = 0;
15537535Sdes    }
15637535Sdes    else return NULL; /* unknown encoding */
15737535Sdes    return c->buf;
15837535Sdes}
15937535Sdes
16037608Sdes/*
16137608Sdes * Read function
16237608Sdes */
16337535Sdesstatic int
16437535Sdes_http_readfn(struct cookie *c, char *buf, int len)
16537535Sdes{
16637535Sdes    int l, pos = 0;
16737535Sdes    while (len) {
16837535Sdes	/* empty buffer */
16937535Sdes	if (!c->buf || (c->b_cur == c->b_len))
17037535Sdes	    if (!_http_fillbuf(c))
17137535Sdes		break;
17237535Sdes
17337535Sdes	l = c->b_len - c->b_cur;
17437535Sdes	if (len < l) l = len;
17537535Sdes	memcpy(buf + pos, c->buf + c->b_cur, l);
17637535Sdes	c->b_cur += l;
17737535Sdes	pos += l;
17837535Sdes	len -= l;
17937535Sdes    }
18037535Sdes
18137535Sdes    if (ferror(c->real_f))
18237535Sdes	return -1;
18337535Sdes    else return pos;
18437535Sdes}
18537535Sdes
18637608Sdes/*
18737608Sdes * Write function
18837608Sdes */
18937535Sdesstatic int
19037535Sdes_http_writefn(struct cookie *c, const char *buf, int len)
19137535Sdes{
19237535Sdes    size_t r = fwrite(buf, 1, (size_t)len, c->real_f);
19337535Sdes    return r ? r : -1;
19437535Sdes}
19537535Sdes
19637608Sdes/*
19737608Sdes * Close function
19837608Sdes */
19937535Sdesstatic int
20037535Sdes_http_closefn(struct cookie *c)
20137535Sdes{
20237535Sdes    int r = fclose(c->real_f);
20337535Sdes    free(c);
20437535Sdes    return (r == EOF) ? -1 : 0;
20537535Sdes}
20637535Sdes
20737608Sdes/*
20837608Sdes * Extract content type from cookie
20937608Sdes */
21037535Sdeschar *
21137535SdesfetchContentType(FILE *f)
21237535Sdes{
21337535Sdes    /*
21437535Sdes     * We have no way of making sure this really *is* one of our cookies,
21537535Sdes     * so just check for a null pointer and hope for the best.
21637535Sdes     */
21737535Sdes    return f->_cookie ? (((struct cookie *)f->_cookie)->content_type) : NULL;
21837535Sdes}
21937535Sdes
22037608Sdes/*
22137608Sdes * Base64 encoding
22237608Sdes */
22337608Sdesint
22437608Sdes_http_base64(char *dst, char *src, int l)
22537608Sdes{
22637608Sdes    static const char base64[] =
22737608Sdes	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
22837608Sdes	"abcdefghijklmnopqrstuvwxyz"
22937608Sdes	"0123456789+/";
23037608Sdes    int t, r = 0;
23137608Sdes
23237608Sdes    while (l >= 3) {
23337608Sdes	t = (src[0] << 16) | (src[1] << 8) | src[2];
23437608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
23537608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
23637608Sdes	dst[2] = base64[(t >> 6) & 0x3f];
23737608Sdes	dst[3] = base64[(t >> 0) & 0x3f];
23837608Sdes	src += 3; l -= 3;
23937608Sdes	dst += 4; r += 4;
24037608Sdes    }
24137608Sdes
24237608Sdes    switch (l) {
24337608Sdes    case 2:
24437608Sdes	t = (src[0] << 16) | (src[1] << 8);
24537608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
24637608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
24737608Sdes	dst[2] = base64[(t >> 6) & 0x3f];
24837608Sdes	dst[3] = '=';
24937608Sdes	dst += 4;
25037608Sdes	r += 4;
25137608Sdes	break;
25237608Sdes    case 1:
25337608Sdes	t = src[0] << 16;
25437608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
25537608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
25637608Sdes	dst[2] = dst[3] = '=';
25737608Sdes	dst += 4;
25837608Sdes	r += 4;
25937608Sdes	break;
26037608Sdes    case 0:
26137608Sdes	break;
26237608Sdes    }
26337608Sdes
26437608Sdes    *dst = 0;
26537608Sdes    return r;
26637608Sdes}
26737608Sdes
26837608Sdes/*
26937608Sdes * Encode username and password
27037608Sdes */
27137608Sdeschar *
27237608Sdes_http_auth(char *usr, char *pwd)
27337608Sdes{
27437608Sdes    int len, lu, lp;
27537608Sdes    char *str, *s;
27637608Sdes
27737608Sdes    lu = strlen(usr);
27837608Sdes    lp = strlen(pwd);
27937608Sdes
28037608Sdes    len = (lu * 4 + 2) / 3	/* user name, round up */
28137608Sdes	+ 1			/* colon */
28237608Sdes	+ (lp * 4 + 2) / 3	/* password, round up */
28337608Sdes	+ 1;			/* null */
28437608Sdes
28537608Sdes    if ((s = str = (char *)malloc(len)) == NULL)
28637608Sdes	return NULL;
28737608Sdes
28837608Sdes    s += _http_base64(s, usr, lu);
28937608Sdes    *s++ = ':';
29037608Sdes    s += _http_base64(s, pwd, lp);
29137608Sdes    *s = 0;
29237608Sdes
29337608Sdes    return str;
29437608Sdes}
29537608Sdes
29637608Sdes/*
29760376Sdes * Connect to server or proxy
29837608Sdes */
29937535SdesFILE *
30060376Sdes_http_connect(struct url *URL, char *flags)
30137535Sdes{
30260376Sdes    int direct, sd = -1, verbose;
30337535Sdes    size_t len;
30460376Sdes    char *px;
30560376Sdes    FILE *f;
30660376Sdes
30755544Sdes    direct = (flags && strchr(flags, 'd'));
30855544Sdes    verbose = (flags && strchr(flags, 'v'));
30941862Sdes
31037535Sdes    /* check port */
31160189Sdes    if (!URL->port) {
31260189Sdes	struct servent *se;
31360189Sdes
31460189Sdes	if ((se = getservbyname("http", "tcp")) != NULL)
31560189Sdes	    URL->port = ntohs(se->s_port);
31660189Sdes	else
31760189Sdes	    URL->port = 80;
31860189Sdes    }
31937535Sdes
32037535Sdes    /* attempt to connect to proxy server */
32155544Sdes    if (!direct && (px = getenv("HTTP_PROXY")) != NULL) {
32241863Sdes	char host[MAXHOSTNAMELEN];
32360189Sdes	int port = 0;
32437535Sdes
32537535Sdes	/* measure length */
32637535Sdes	len = strcspn(px, ":");
32737535Sdes
32855544Sdes	/* get port (XXX atoi is a little too tolerant perhaps?) */
32960189Sdes	if (px[len] == ':') {
33060189Sdes	    if (strspn(px+len+1, "0123456789") != strlen(px+len+1)
33160189Sdes		|| strlen(px+len+1) > 5) {
33260189Sdes		/* XXX we should emit some kind of warning */
33360189Sdes	    }
33437535Sdes	    port = atoi(px+len+1);
33560189Sdes	    if (port < 1 || port > 65535) {
33660189Sdes		/* XXX we should emit some kind of warning */
33760189Sdes	    }
33860189Sdes	}
33960189Sdes	if (!port) {
34060189Sdes#if 0
34160189Sdes	    /*
34260189Sdes	     * commented out, since there is currently no service name
34360189Sdes	     * for HTTP proxies
34460189Sdes	     */
34560189Sdes	    struct servent *se;
34660189Sdes
34760189Sdes	    if ((se = getservbyname("xxxx", "tcp")) != NULL)
34860189Sdes		port = ntohs(se->s_port);
34960189Sdes	    else
35060189Sdes#endif
35160189Sdes		port = 3128;
35260189Sdes	}
35337535Sdes
35437535Sdes	/* get host name */
35537535Sdes	if (len >= MAXHOSTNAMELEN)
35637535Sdes	    len = MAXHOSTNAMELEN - 1;
35737535Sdes	strncpy(host, px, len);
35837535Sdes	host[len] = 0;
35937535Sdes
36037535Sdes	/* connect */
36141923Sdes	sd = _fetch_connect(host, port, verbose);
36237535Sdes    }
36337535Sdes
36437535Sdes    /* if no proxy is configured or could be contacted, try direct */
36538394Sdes    if (sd == -1) {
36641923Sdes	if ((sd = _fetch_connect(URL->host, URL->port, verbose)) == -1)
36737535Sdes	    goto ouch;
36837535Sdes    }
36937535Sdes
37037535Sdes    /* reopen as stream */
37137571Sdes    if ((f = fdopen(sd, "r+")) == NULL)
37237535Sdes	goto ouch;
37360376Sdes
37460376Sdes    return f;
37537535Sdes
37660376Sdesouch:
37760376Sdes    if (sd >= 0)
37860376Sdes	close(sd);
37960376Sdes    _http_seterr(999); /* XXX do this properly RSN */
38060376Sdes    return NULL;
38160376Sdes}
38260376Sdes
38360376Sdes/*
38460376Sdes * Send a HEAD or GET request
38560376Sdes */
38660376Sdesint
38760376Sdes_http_request(FILE *f, char *op, struct url *URL, char *flags)
38860376Sdes{
38960376Sdes    int e, verbose;
39060376Sdes    char *ln, *p;
39160376Sdes    size_t len;
39260376Sdes
39360376Sdes    verbose = (flags && strchr(flags, 'v'));
39460376Sdes
39537535Sdes    /* send request (proxies require absolute form, so use that) */
39641862Sdes    if (verbose)
39741862Sdes	_fetch_info("requesting http://%s:%d%s",
39841862Sdes		    URL->host, URL->port, URL->doc);
39960376Sdes    _http_cmd(f, "%s %s://%s:%d%s HTTP/1.1" ENDL,
40060376Sdes	      op, URL->scheme, URL->host, URL->port, URL->doc);
40137535Sdes
40237535Sdes    /* start sending headers away */
40337535Sdes    if (URL->user[0] || URL->pwd[0]) {
40437608Sdes	char *auth_str = _http_auth(URL->user, URL->pwd);
40537608Sdes	if (!auth_str)
40660376Sdes	    return 999; /* XXX wrong */
40737608Sdes	_http_cmd(f, "Authorization: Basic %s" ENDL, auth_str);
40837608Sdes	free(auth_str);
40937535Sdes    }
41037608Sdes    _http_cmd(f, "Host: %s:%d" ENDL, URL->host, URL->port);
41137608Sdes    _http_cmd(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname);
41260196Sdes    if (URL->offset)
41360196Sdes	_http_cmd(f, "Range: bytes=%lld-" ENDL, URL->offset);
41437608Sdes    _http_cmd(f, "Connection: close" ENDL ENDL);
41537535Sdes
41637535Sdes    /* get response */
41737535Sdes    if ((ln = fgetln(f, &len)) == NULL)
41860376Sdes	return 999;
41937535Sdes    DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n",
42037535Sdes		  (int)len-2, (int)len-2, ln));
42137535Sdes
42237535Sdes    /* we can't use strchr() and friends since ln isn't NUL-terminated */
42337535Sdes    p = ln;
42437535Sdes    while ((p < ln + len) && !isspace(*p))
42537535Sdes	p++;
42637535Sdes    while ((p < ln + len) && !isdigit(*p))
42737535Sdes	p++;
42837535Sdes    if (!isdigit(*p))
42960376Sdes	return 999;
43060376Sdes
43141863Sdes    e = atoi(p);
43241863Sdes    DEBUG(fprintf(stderr, "code:     [\033[1m%d\033[m]\n", e));
43360376Sdes    return e;
43460376Sdes}
43560376Sdes
43660376Sdes/*
43760376Sdes * Check a header line
43860376Sdes */
43960376Sdeschar *
44060376Sdes_http_match(char *str, char *hdr)
44160376Sdes{
44260376Sdes    while (*str && *hdr && tolower(*str++) == tolower(*hdr++))
44360376Sdes	/* nothing */;
44460376Sdes    if (*str || *hdr != ':')
44560376Sdes	return NULL;
44660376Sdes    while (*hdr && isspace(*++hdr))
44760376Sdes	/* nothing */;
44860376Sdes    return hdr;
44960376Sdes}
45060376Sdes
45160376Sdes/*
45260376Sdes * Retrieve a file by HTTP
45360376Sdes */
45460376SdesFILE *
45560376SdesfetchGetHTTP(struct url *URL, char *flags)
45660376Sdes{
45760376Sdes    int e, enc = ENC_NONE, i, verbose;
45860376Sdes    struct cookie *c;
45960376Sdes    char *ln, *p, *q;
46060376Sdes    FILE *f, *cf;
46160376Sdes    size_t len;
46260376Sdes    off_t pos = 0;
46360376Sdes
46460376Sdes    verbose = (flags && strchr(flags, 'v'));
46537535Sdes
46660376Sdes    /* allocate cookie */
46760376Sdes    if ((c = calloc(1, sizeof *c)) == NULL)
46860376Sdes	return NULL;
46960376Sdes
47060376Sdes    /* connect */
47160376Sdes    if ((f = _http_connect(URL, flags)) == NULL) {
47260376Sdes	free(c);
47360376Sdes	return NULL;
47460376Sdes    }
47560376Sdes    c->real_f = f;
47660376Sdes
47760376Sdes    e = _http_request(f, "GET", URL, flags);
47860376Sdes
47937535Sdes    /* add code to handle redirects later */
48060196Sdes    if (e != (URL->offset ? HTTP_PARTIAL : HTTP_OK)) {
48141863Sdes	_http_seterr(e);
48237535Sdes	goto fouch;
48337571Sdes    }
48437535Sdes
48537535Sdes    /* browse through header */
48637535Sdes    while (1) {
48737535Sdes	if ((ln = fgetln(f, &len)) == NULL)
48837535Sdes	    goto fouch;
48937535Sdes	if ((ln[0] == '\r') || (ln[0] == '\n'))
49037535Sdes	    break;
49160376Sdes	while (isspace(ln[len-1]))
49260376Sdes	    --len;
49360376Sdes	ln[len] = '\0'; /* XXX */
49460376Sdes	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
49560376Sdes	if ((p = _http_match("Transfer-Encoding", ln)) != NULL) {
49660376Sdes	    for (q = p; *q && !isspace(*q); q++)
49737535Sdes		/* VOID */ ;
49837535Sdes	    *q = 0;
49937535Sdes	    if (strcasecmp(p, "chunked") == 0)
50037535Sdes		enc = ENC_CHUNKED;
50160376Sdes	    DEBUG(fprintf(stderr, "transfer encoding:  [\033[1m%s\033[m]\n", p));
50260376Sdes	} else if ((p = _http_match("Content-Type", ln)) != NULL) {
50360376Sdes	    for (i = 0; *p && i < HTTPCTYPELEN; p++, i++)
50460376Sdes		    c->content_type[i] = *p;
50537535Sdes	    do c->content_type[i--] = 0; while (isspace(c->content_type[i]));
50660376Sdes	    DEBUG(fprintf(stderr, "content type: [\033[1m%s\033[m]\n",
50737535Sdes			  c->content_type));
50860376Sdes	} else if ((p = _http_match("Content-Range", ln)) != NULL) {
50960376Sdes	    if (strncasecmp(p, "bytes ", 6) != 0)
51060196Sdes		goto fouch;
51160376Sdes	    p += 6;
51260376Sdes	    while (*p && isdigit(*p))
51360196Sdes		pos = pos * 10 + (*p++ - '0');
51460196Sdes	    /* XXX wouldn't hurt to be slightly more paranoid here */
51560376Sdes	    DEBUG(fprintf(stderr, "content range: [\033[1m%lld-\033[m]\n", pos));
51660196Sdes	    if (pos > URL->offset)
51760196Sdes		goto fouch;
51837535Sdes	}
51937535Sdes    }
52037535Sdes
52137535Sdes    /* only body remains */
52237535Sdes    c->encoding = enc;
52337535Sdes    cf = funopen(c,
52437535Sdes		 (int (*)(void *, char *, int))_http_readfn,
52537535Sdes		 (int (*)(void *, const char *, int))_http_writefn,
52637535Sdes		 (fpos_t (*)(void *, fpos_t, int))NULL,
52737535Sdes		 (int (*)(void *))_http_closefn);
52837535Sdes    if (cf == NULL)
52937535Sdes	goto fouch;
53060189Sdes
53160196Sdes    while (pos < URL->offset)
53260196Sdes	if (fgetc(cf) == EOF)
53360196Sdes	    goto cfouch;
53460196Sdes
53537535Sdes    return cf;
53637535Sdes
53737535Sdesfouch:
53837535Sdes    fclose(f);
53937535Sdes    free(c);
54041862Sdes    _http_seterr(999); /* XXX do this properly RSN */
54137535Sdes    return NULL;
54260196Sdescfouch:
54360196Sdes    fclose(cf);
54460196Sdes    _http_seterr(999); /* XXX do this properly RSN */
54560196Sdes    return NULL;
54637535Sdes}
54737535Sdes
54837535SdesFILE *
54940975SdesfetchPutHTTP(struct url *URL, char *flags)
55037535Sdes{
55137535Sdes    warnx("fetchPutHTTP(): not implemented");
55237535Sdes    return NULL;
55337535Sdes}
55440975Sdes
55540975Sdes/*
55640975Sdes * Get an HTTP document's metadata
55740975Sdes */
55840975Sdesint
55960376SdesfetchStatHTTP(struct url *URL, struct url_stat *us, char *flags)
56040975Sdes{
56160376Sdes    int e, verbose;
56260376Sdes    size_t len;
56360376Sdes    char *ln, *p;
56460376Sdes    FILE *f;
56560376Sdes
56660376Sdes    verbose = (flags && strchr(flags, 'v'));
56760376Sdes
56860376Sdes    /* connect */
56960376Sdes    if ((f = _http_connect(URL, flags)) == NULL)
57060376Sdes	return -1;
57160376Sdes
57260376Sdes    if ((e = _http_request(f, "HEAD", URL, flags)) != HTTP_OK) {
57360376Sdes	_http_seterr(e);
57460376Sdes	goto ouch;
57560376Sdes    }
57660376Sdes
57760376Sdes    while (1) {
57860376Sdes	if ((ln = fgetln(f, &len)) == NULL)
57960376Sdes	    goto fouch;
58060376Sdes	if ((ln[0] == '\r') || (ln[0] == '\n'))
58160376Sdes	    break;
58260376Sdes	while (isspace(ln[len-1]))
58360376Sdes	    --len;
58460376Sdes	ln[len] = '\0'; /* XXX */
58560376Sdes	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
58660376Sdes	if ((p = _http_match("Last-Modified", ln)) != NULL) {
58760376Sdes	    struct tm tm;
58860376Sdes	    char locale[64];
58960376Sdes
59060376Sdes	    strncpy(locale, setlocale(LC_TIME, NULL), sizeof locale);
59160376Sdes	    setlocale(LC_TIME, "C");
59260376Sdes	    strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
59360376Sdes	    /* XXX should add support for date-2 and date-3 */
59460376Sdes	    setlocale(LC_TIME, locale);
59560376Sdes	    us->atime = us->mtime = timegm(&tm);
59660376Sdes	    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
59760376Sdes			  "%02d:%02d:%02d\033[m]\n",
59860376Sdes			  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
59960376Sdes			  tm.tm_hour, tm.tm_min, tm.tm_sec));
60060376Sdes	} else if ((p = _http_match("Content-Length", ln)) != NULL) {
60160376Sdes	    us->size = 0;
60260376Sdes	    while (*p && isdigit(*p))
60360376Sdes		us->size = us->size * 10 + (*p++ - '0');
60460376Sdes	    DEBUG(fprintf(stderr, "content length: [\033[1m%lld\033[m]\n", us->size));
60560376Sdes	}
60660376Sdes    }
60760376Sdes
60860376Sdes    return 0;
60960376Sdes ouch:
61060376Sdes    _http_seterr(999); /* XXX do this properly RSN */
61160376Sdes fouch:
61260376Sdes    fclose(f);
61360376Sdes    return -1;
61440975Sdes}
61541989Sdes
61641989Sdes/*
61741989Sdes * List a directory
61841989Sdes */
61941989Sdesstruct url_ent *
62041989SdesfetchListHTTP(struct url *url, char *flags)
62141989Sdes{
62241989Sdes    warnx("fetchListHTTP(): not implemented");
62341989Sdes    return NULL;
62441989Sdes}
625