http.c revision 60737
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 60737 2000-05-20 18:23:51Z ume $
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>
6460737Sume#include <sys/socket.h>
6537535Sdes
6637535Sdes#include <err.h>
6737535Sdes#include <ctype.h>
6860376Sdes#include <locale.h>
6960189Sdes#include <netdb.h>
7037608Sdes#include <stdarg.h>
7137535Sdes#include <stdio.h>
7237535Sdes#include <stdlib.h>
7337535Sdes#include <string.h>
7460376Sdes#include <time.h>
7537535Sdes#include <unistd.h>
7637535Sdes
7737535Sdes#include "fetch.h"
7840939Sdes#include "common.h"
7941862Sdes#include "httperr.h"
8037535Sdes
8137535Sdesextern char *__progname;
8237535Sdes
8337535Sdes#define ENDL "\r\n"
8437535Sdes
8560196Sdes#define HTTP_OK		200
8660196Sdes#define HTTP_PARTIAL	206
8760196Sdes
8837535Sdesstruct cookie
8937535Sdes{
9037535Sdes    FILE *real_f;
9137535Sdes#define ENC_NONE 0
9237535Sdes#define ENC_CHUNKED 1
9337535Sdes    int encoding;			/* 1 = chunked, 0 = none */
9437535Sdes#define HTTPCTYPELEN 59
9537535Sdes    char content_type[HTTPCTYPELEN+1];
9637535Sdes    char *buf;
9737535Sdes    int b_cur, eof;
9837535Sdes    unsigned b_len, chunksize;
9937535Sdes};
10037535Sdes
10137608Sdes/*
10237608Sdes * Send a formatted line; optionally echo to terminal
10337608Sdes */
10437608Sdesstatic int
10537608Sdes_http_cmd(FILE *f, char *fmt, ...)
10637608Sdes{
10737608Sdes    va_list ap;
10837608Sdes
10937608Sdes    va_start(ap, fmt);
11037608Sdes    vfprintf(f, fmt, ap);
11137608Sdes#ifndef NDEBUG
11237608Sdes    fprintf(stderr, "\033[1m>>> ");
11337608Sdes    vfprintf(stderr, fmt, ap);
11437608Sdes    fprintf(stderr, "\033[m");
11537608Sdes#endif
11637608Sdes    va_end(ap);
11737608Sdes
11837608Sdes    return 0; /* XXX */
11937608Sdes}
12037608Sdes
12137608Sdes/*
12237608Sdes * Fill the input buffer, do chunk decoding on the fly
12337608Sdes */
12437535Sdesstatic char *
12537535Sdes_http_fillbuf(struct cookie *c)
12637535Sdes{
12737535Sdes    char *ln;
12837535Sdes    unsigned int len;
12937535Sdes
13037535Sdes    if (c->eof)
13137535Sdes	return NULL;
13237535Sdes
13337535Sdes    if (c->encoding == ENC_NONE) {
13437535Sdes	c->buf = fgetln(c->real_f, &(c->b_len));
13537535Sdes	c->b_cur = 0;
13637535Sdes    } else if (c->encoding == ENC_CHUNKED) {
13737535Sdes	if (c->chunksize == 0) {
13837535Sdes	    ln = fgetln(c->real_f, &len);
13960707Sdes	    if (len <= 2)
14060707Sdes		return NULL;
14137535Sdes	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: "
14237535Sdes			  "%*.*s\033[m\n", (int)len-2, (int)len-2, ln));
14337535Sdes	    sscanf(ln, "%x", &(c->chunksize));
14437535Sdes	    if (!c->chunksize) {
14537535Sdes		DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
14637535Sdes			      "end of last chunk\033[m\n"));
14737535Sdes		c->eof = 1;
14837535Sdes		return NULL;
14937535Sdes	    }
15037535Sdes	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
15137535Sdes			  "new chunk: %X\033[m\n", c->chunksize));
15237535Sdes	}
15337535Sdes	c->buf = fgetln(c->real_f, &(c->b_len));
15437535Sdes	if (c->b_len > c->chunksize)
15537535Sdes	    c->b_len = c->chunksize;
15637535Sdes	c->chunksize -= c->b_len;
15737535Sdes	c->b_cur = 0;
15837535Sdes    }
15937535Sdes    else return NULL; /* unknown encoding */
16037535Sdes    return c->buf;
16137535Sdes}
16237535Sdes
16337608Sdes/*
16437608Sdes * Read function
16537608Sdes */
16637535Sdesstatic int
16737535Sdes_http_readfn(struct cookie *c, char *buf, int len)
16837535Sdes{
16937535Sdes    int l, pos = 0;
17037535Sdes    while (len) {
17137535Sdes	/* empty buffer */
17237535Sdes	if (!c->buf || (c->b_cur == c->b_len))
17337535Sdes	    if (!_http_fillbuf(c))
17437535Sdes		break;
17537535Sdes
17637535Sdes	l = c->b_len - c->b_cur;
17737535Sdes	if (len < l) l = len;
17837535Sdes	memcpy(buf + pos, c->buf + c->b_cur, l);
17937535Sdes	c->b_cur += l;
18037535Sdes	pos += l;
18137535Sdes	len -= l;
18237535Sdes    }
18337535Sdes
18437535Sdes    if (ferror(c->real_f))
18537535Sdes	return -1;
18637535Sdes    else return pos;
18737535Sdes}
18837535Sdes
18937608Sdes/*
19037608Sdes * Write function
19137608Sdes */
19237535Sdesstatic int
19337535Sdes_http_writefn(struct cookie *c, const char *buf, int len)
19437535Sdes{
19537535Sdes    size_t r = fwrite(buf, 1, (size_t)len, c->real_f);
19637535Sdes    return r ? r : -1;
19737535Sdes}
19837535Sdes
19937608Sdes/*
20037608Sdes * Close function
20137608Sdes */
20237535Sdesstatic int
20337535Sdes_http_closefn(struct cookie *c)
20437535Sdes{
20537535Sdes    int r = fclose(c->real_f);
20637535Sdes    free(c);
20737535Sdes    return (r == EOF) ? -1 : 0;
20837535Sdes}
20937535Sdes
21037608Sdes/*
21137608Sdes * Extract content type from cookie
21237608Sdes */
21337535Sdeschar *
21437535SdesfetchContentType(FILE *f)
21537535Sdes{
21637535Sdes    /*
21737535Sdes     * We have no way of making sure this really *is* one of our cookies,
21837535Sdes     * so just check for a null pointer and hope for the best.
21937535Sdes     */
22037535Sdes    return f->_cookie ? (((struct cookie *)f->_cookie)->content_type) : NULL;
22137535Sdes}
22237535Sdes
22337608Sdes/*
22437608Sdes * Base64 encoding
22537608Sdes */
22637608Sdesint
22737608Sdes_http_base64(char *dst, char *src, int l)
22837608Sdes{
22937608Sdes    static const char base64[] =
23037608Sdes	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
23137608Sdes	"abcdefghijklmnopqrstuvwxyz"
23237608Sdes	"0123456789+/";
23337608Sdes    int t, r = 0;
23437608Sdes
23537608Sdes    while (l >= 3) {
23637608Sdes	t = (src[0] << 16) | (src[1] << 8) | src[2];
23737608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
23837608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
23937608Sdes	dst[2] = base64[(t >> 6) & 0x3f];
24037608Sdes	dst[3] = base64[(t >> 0) & 0x3f];
24137608Sdes	src += 3; l -= 3;
24237608Sdes	dst += 4; r += 4;
24337608Sdes    }
24437608Sdes
24537608Sdes    switch (l) {
24637608Sdes    case 2:
24737608Sdes	t = (src[0] << 16) | (src[1] << 8);
24837608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
24937608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
25037608Sdes	dst[2] = base64[(t >> 6) & 0x3f];
25137608Sdes	dst[3] = '=';
25237608Sdes	dst += 4;
25337608Sdes	r += 4;
25437608Sdes	break;
25537608Sdes    case 1:
25637608Sdes	t = src[0] << 16;
25737608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
25837608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
25937608Sdes	dst[2] = dst[3] = '=';
26037608Sdes	dst += 4;
26137608Sdes	r += 4;
26237608Sdes	break;
26337608Sdes    case 0:
26437608Sdes	break;
26537608Sdes    }
26637608Sdes
26737608Sdes    *dst = 0;
26837608Sdes    return r;
26937608Sdes}
27037608Sdes
27137608Sdes/*
27237608Sdes * Encode username and password
27337608Sdes */
27437608Sdeschar *
27537608Sdes_http_auth(char *usr, char *pwd)
27637608Sdes{
27737608Sdes    int len, lu, lp;
27837608Sdes    char *str, *s;
27937608Sdes
28037608Sdes    lu = strlen(usr);
28137608Sdes    lp = strlen(pwd);
28237608Sdes
28337608Sdes    len = (lu * 4 + 2) / 3	/* user name, round up */
28437608Sdes	+ 1			/* colon */
28537608Sdes	+ (lp * 4 + 2) / 3	/* password, round up */
28637608Sdes	+ 1;			/* null */
28737608Sdes
28837608Sdes    if ((s = str = (char *)malloc(len)) == NULL)
28937608Sdes	return NULL;
29037608Sdes
29137608Sdes    s += _http_base64(s, usr, lu);
29237608Sdes    *s++ = ':';
29337608Sdes    s += _http_base64(s, pwd, lp);
29437608Sdes    *s = 0;
29537608Sdes
29637608Sdes    return str;
29737608Sdes}
29837608Sdes
29937608Sdes/*
30060376Sdes * Connect to server or proxy
30137608Sdes */
30237535SdesFILE *
30360376Sdes_http_connect(struct url *URL, char *flags)
30437535Sdes{
30560376Sdes    int direct, sd = -1, verbose;
30660737Sume#ifdef INET6
30760737Sume    int af = AF_UNSPEC;
30860737Sume#else
30960737Sume    int af = AF_INET;
31060737Sume#endif
31137535Sdes    size_t len;
31260376Sdes    char *px;
31360376Sdes    FILE *f;
31460376Sdes
31555544Sdes    direct = (flags && strchr(flags, 'd'));
31655544Sdes    verbose = (flags && strchr(flags, 'v'));
31760737Sume    if ((flags && strchr(flags, '4')))
31860737Sume	af = AF_INET;
31960737Sume    else if ((flags && strchr(flags, '6')))
32060737Sume	af = AF_INET6;
32141862Sdes
32237535Sdes    /* check port */
32360189Sdes    if (!URL->port) {
32460189Sdes	struct servent *se;
32560189Sdes
32660587Sume	if (strcasecmp(URL->scheme, "ftp") == 0)
32760587Sume	    if ((se = getservbyname("ftp", "tcp")) != NULL)
32860587Sume		URL->port = ntohs(se->s_port);
32960587Sume	    else
33060587Sume		URL->port = 21;
33160189Sdes	else
33260587Sume	    if ((se = getservbyname("http", "tcp")) != NULL)
33360587Sume		URL->port = ntohs(se->s_port);
33460587Sume	    else
33560587Sume		URL->port = 80;
33660189Sdes    }
33737535Sdes
33837535Sdes    /* attempt to connect to proxy server */
33955544Sdes    if (!direct && (px = getenv("HTTP_PROXY")) != NULL) {
34041863Sdes	char host[MAXHOSTNAMELEN];
34160189Sdes	int port = 0;
34237535Sdes
34337535Sdes	/* measure length */
34460737Sume#ifdef INET6
34560737Sume	if (px[0] != '[' ||
34660737Sume	    (len = strcspn(px, "]")) >= strlen(px) ||
34760737Sume	    (px[++len] != '\0' && px[len] != ':'))
34860737Sume#endif
34960737Sume	    len = strcspn(px, ":");
35037535Sdes
35155544Sdes	/* get port (XXX atoi is a little too tolerant perhaps?) */
35260189Sdes	if (px[len] == ':') {
35360189Sdes	    if (strspn(px+len+1, "0123456789") != strlen(px+len+1)
35460189Sdes		|| strlen(px+len+1) > 5) {
35560189Sdes		/* XXX we should emit some kind of warning */
35660189Sdes	    }
35737535Sdes	    port = atoi(px+len+1);
35860189Sdes	    if (port < 1 || port > 65535) {
35960189Sdes		/* XXX we should emit some kind of warning */
36060189Sdes	    }
36160189Sdes	}
36260189Sdes	if (!port) {
36360189Sdes#if 0
36460189Sdes	    /*
36560189Sdes	     * commented out, since there is currently no service name
36660189Sdes	     * for HTTP proxies
36760189Sdes	     */
36860189Sdes	    struct servent *se;
36960189Sdes
37060189Sdes	    if ((se = getservbyname("xxxx", "tcp")) != NULL)
37160189Sdes		port = ntohs(se->s_port);
37260189Sdes	    else
37360189Sdes#endif
37460189Sdes		port = 3128;
37560189Sdes	}
37637535Sdes
37737535Sdes	/* get host name */
37860737Sume#ifdef INET6
37960737Sume	if (len > 1 && px[0] == '[' && px[len - 1] == ']') {
38060737Sume	    px++;
38160737Sume	    len -= 2;
38260737Sume	}
38360737Sume#endif
38437535Sdes	if (len >= MAXHOSTNAMELEN)
38537535Sdes	    len = MAXHOSTNAMELEN - 1;
38637535Sdes	strncpy(host, px, len);
38737535Sdes	host[len] = 0;
38837535Sdes
38937535Sdes	/* connect */
39060737Sume	sd = _fetch_connect(host, port, af, verbose);
39137535Sdes    }
39237535Sdes
39337535Sdes    /* if no proxy is configured or could be contacted, try direct */
39438394Sdes    if (sd == -1) {
39560587Sume	if (strcasecmp(URL->scheme, "ftp") == 0)
39660587Sume	    goto ouch;
39760737Sume	if ((sd = _fetch_connect(URL->host, URL->port, af, verbose)) == -1)
39837535Sdes	    goto ouch;
39937535Sdes    }
40037535Sdes
40137535Sdes    /* reopen as stream */
40237571Sdes    if ((f = fdopen(sd, "r+")) == NULL)
40337535Sdes	goto ouch;
40460376Sdes
40560376Sdes    return f;
40637535Sdes
40760376Sdesouch:
40860376Sdes    if (sd >= 0)
40960376Sdes	close(sd);
41060376Sdes    _http_seterr(999); /* XXX do this properly RSN */
41160376Sdes    return NULL;
41260376Sdes}
41360376Sdes
41460376Sdes/*
41560376Sdes * Send a HEAD or GET request
41660376Sdes */
41760376Sdesint
41860376Sdes_http_request(FILE *f, char *op, struct url *URL, char *flags)
41960376Sdes{
42060376Sdes    int e, verbose;
42160376Sdes    char *ln, *p;
42260376Sdes    size_t len;
42360737Sume    char *host;
42460737Sume#ifdef INET6
42560737Sume    char hbuf[MAXHOSTNAMELEN + 1];
42660737Sume#endif
42760376Sdes
42860376Sdes    verbose = (flags && strchr(flags, 'v'));
42960737Sume
43060737Sume    host = URL->host;
43160737Sume#ifdef INET6
43260737Sume    if (strchr(URL->host, ':')) {
43360737Sume	snprintf(hbuf, sizeof(hbuf), "[%s]", URL->host);
43460737Sume	host = hbuf;
43560737Sume    }
43660737Sume#endif
43760376Sdes
43837535Sdes    /* send request (proxies require absolute form, so use that) */
43941862Sdes    if (verbose)
44060587Sume	_fetch_info("requesting %s://%s:%d%s",
44160737Sume		    URL->scheme, host, URL->port, URL->doc);
44260376Sdes    _http_cmd(f, "%s %s://%s:%d%s HTTP/1.1" ENDL,
44360737Sume	      op, URL->scheme, host, URL->port, URL->doc);
44437535Sdes
44537535Sdes    /* start sending headers away */
44637535Sdes    if (URL->user[0] || URL->pwd[0]) {
44737608Sdes	char *auth_str = _http_auth(URL->user, URL->pwd);
44837608Sdes	if (!auth_str)
44960376Sdes	    return 999; /* XXX wrong */
45037608Sdes	_http_cmd(f, "Authorization: Basic %s" ENDL, auth_str);
45137608Sdes	free(auth_str);
45237535Sdes    }
45360737Sume    _http_cmd(f, "Host: %s:%d" ENDL, host, URL->port);
45437608Sdes    _http_cmd(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname);
45560196Sdes    if (URL->offset)
45660196Sdes	_http_cmd(f, "Range: bytes=%lld-" ENDL, URL->offset);
45737608Sdes    _http_cmd(f, "Connection: close" ENDL ENDL);
45837535Sdes
45937535Sdes    /* get response */
46037535Sdes    if ((ln = fgetln(f, &len)) == NULL)
46160376Sdes	return 999;
46237535Sdes    DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n",
46337535Sdes		  (int)len-2, (int)len-2, ln));
46437535Sdes
46537535Sdes    /* we can't use strchr() and friends since ln isn't NUL-terminated */
46637535Sdes    p = ln;
46737535Sdes    while ((p < ln + len) && !isspace(*p))
46837535Sdes	p++;
46937535Sdes    while ((p < ln + len) && !isdigit(*p))
47037535Sdes	p++;
47137535Sdes    if (!isdigit(*p))
47260376Sdes	return 999;
47360376Sdes
47441863Sdes    e = atoi(p);
47541863Sdes    DEBUG(fprintf(stderr, "code:     [\033[1m%d\033[m]\n", e));
47660376Sdes    return e;
47760376Sdes}
47860376Sdes
47960376Sdes/*
48060376Sdes * Check a header line
48160376Sdes */
48260376Sdeschar *
48360376Sdes_http_match(char *str, char *hdr)
48460376Sdes{
48560376Sdes    while (*str && *hdr && tolower(*str++) == tolower(*hdr++))
48660376Sdes	/* nothing */;
48760376Sdes    if (*str || *hdr != ':')
48860376Sdes	return NULL;
48960376Sdes    while (*hdr && isspace(*++hdr))
49060376Sdes	/* nothing */;
49160376Sdes    return hdr;
49260376Sdes}
49360376Sdes
49460376Sdes/*
49560376Sdes * Retrieve a file by HTTP
49660376Sdes */
49760376SdesFILE *
49860376SdesfetchGetHTTP(struct url *URL, char *flags)
49960376Sdes{
50060581Sdes    int e, enc = ENC_NONE, i;
50160376Sdes    struct cookie *c;
50260376Sdes    char *ln, *p, *q;
50360376Sdes    FILE *f, *cf;
50460376Sdes    size_t len;
50560376Sdes    off_t pos = 0;
50660376Sdes
50760376Sdes    /* allocate cookie */
50860376Sdes    if ((c = calloc(1, sizeof *c)) == NULL)
50960376Sdes	return NULL;
51060376Sdes
51160376Sdes    /* connect */
51260376Sdes    if ((f = _http_connect(URL, flags)) == NULL) {
51360376Sdes	free(c);
51460376Sdes	return NULL;
51560376Sdes    }
51660376Sdes    c->real_f = f;
51760376Sdes
51860376Sdes    e = _http_request(f, "GET", URL, flags);
51960376Sdes
52037535Sdes    /* add code to handle redirects later */
52160196Sdes    if (e != (URL->offset ? HTTP_PARTIAL : HTTP_OK)) {
52241863Sdes	_http_seterr(e);
52337535Sdes	goto fouch;
52437571Sdes    }
52537535Sdes
52637535Sdes    /* browse through header */
52737535Sdes    while (1) {
52837535Sdes	if ((ln = fgetln(f, &len)) == NULL)
52937535Sdes	    goto fouch;
53037535Sdes	if ((ln[0] == '\r') || (ln[0] == '\n'))
53137535Sdes	    break;
53260376Sdes	while (isspace(ln[len-1]))
53360376Sdes	    --len;
53460376Sdes	ln[len] = '\0'; /* XXX */
53560376Sdes	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
53660376Sdes	if ((p = _http_match("Transfer-Encoding", ln)) != NULL) {
53760376Sdes	    for (q = p; *q && !isspace(*q); q++)
53837535Sdes		/* VOID */ ;
53937535Sdes	    *q = 0;
54037535Sdes	    if (strcasecmp(p, "chunked") == 0)
54137535Sdes		enc = ENC_CHUNKED;
54260376Sdes	    DEBUG(fprintf(stderr, "transfer encoding:  [\033[1m%s\033[m]\n", p));
54360376Sdes	} else if ((p = _http_match("Content-Type", ln)) != NULL) {
54460376Sdes	    for (i = 0; *p && i < HTTPCTYPELEN; p++, i++)
54560376Sdes		    c->content_type[i] = *p;
54637535Sdes	    do c->content_type[i--] = 0; while (isspace(c->content_type[i]));
54760376Sdes	    DEBUG(fprintf(stderr, "content type: [\033[1m%s\033[m]\n",
54837535Sdes			  c->content_type));
54960376Sdes	} else if ((p = _http_match("Content-Range", ln)) != NULL) {
55060376Sdes	    if (strncasecmp(p, "bytes ", 6) != 0)
55160196Sdes		goto fouch;
55260376Sdes	    p += 6;
55360376Sdes	    while (*p && isdigit(*p))
55460196Sdes		pos = pos * 10 + (*p++ - '0');
55560196Sdes	    /* XXX wouldn't hurt to be slightly more paranoid here */
55660376Sdes	    DEBUG(fprintf(stderr, "content range: [\033[1m%lld-\033[m]\n", pos));
55760196Sdes	    if (pos > URL->offset)
55860196Sdes		goto fouch;
55937535Sdes	}
56037535Sdes    }
56137535Sdes
56237535Sdes    /* only body remains */
56337535Sdes    c->encoding = enc;
56437535Sdes    cf = funopen(c,
56537535Sdes		 (int (*)(void *, char *, int))_http_readfn,
56637535Sdes		 (int (*)(void *, const char *, int))_http_writefn,
56737535Sdes		 (fpos_t (*)(void *, fpos_t, int))NULL,
56837535Sdes		 (int (*)(void *))_http_closefn);
56937535Sdes    if (cf == NULL)
57037535Sdes	goto fouch;
57160189Sdes
57260196Sdes    while (pos < URL->offset)
57360196Sdes	if (fgetc(cf) == EOF)
57460196Sdes	    goto cfouch;
57560196Sdes
57637535Sdes    return cf;
57737535Sdes
57837535Sdesfouch:
57937535Sdes    fclose(f);
58037535Sdes    free(c);
58141862Sdes    _http_seterr(999); /* XXX do this properly RSN */
58237535Sdes    return NULL;
58360196Sdescfouch:
58460196Sdes    fclose(cf);
58560196Sdes    _http_seterr(999); /* XXX do this properly RSN */
58660196Sdes    return NULL;
58737535Sdes}
58837535Sdes
58937535SdesFILE *
59040975SdesfetchPutHTTP(struct url *URL, char *flags)
59137535Sdes{
59237535Sdes    warnx("fetchPutHTTP(): not implemented");
59337535Sdes    return NULL;
59437535Sdes}
59540975Sdes
59640975Sdes/*
59740975Sdes * Get an HTTP document's metadata
59840975Sdes */
59940975Sdesint
60060376SdesfetchStatHTTP(struct url *URL, struct url_stat *us, char *flags)
60140975Sdes{
60260581Sdes    int e;
60360376Sdes    size_t len;
60460376Sdes    char *ln, *p;
60560376Sdes    FILE *f;
60660376Sdes
60760581Sdes    us->size = -1;
60860581Sdes    us->atime = us->mtime = 0;
60960376Sdes
61060376Sdes    /* connect */
61160376Sdes    if ((f = _http_connect(URL, flags)) == NULL)
61260376Sdes	return -1;
61360376Sdes
61460376Sdes    if ((e = _http_request(f, "HEAD", URL, flags)) != HTTP_OK) {
61560376Sdes	_http_seterr(e);
61660376Sdes	goto ouch;
61760376Sdes    }
61860376Sdes
61960376Sdes    while (1) {
62060376Sdes	if ((ln = fgetln(f, &len)) == NULL)
62160376Sdes	    goto fouch;
62260376Sdes	if ((ln[0] == '\r') || (ln[0] == '\n'))
62360376Sdes	    break;
62460376Sdes	while (isspace(ln[len-1]))
62560376Sdes	    --len;
62660376Sdes	ln[len] = '\0'; /* XXX */
62760376Sdes	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
62860376Sdes	if ((p = _http_match("Last-Modified", ln)) != NULL) {
62960376Sdes	    struct tm tm;
63060376Sdes	    char locale[64];
63160376Sdes
63260376Sdes	    strncpy(locale, setlocale(LC_TIME, NULL), sizeof locale);
63360376Sdes	    setlocale(LC_TIME, "C");
63460376Sdes	    strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
63560376Sdes	    /* XXX should add support for date-2 and date-3 */
63660376Sdes	    setlocale(LC_TIME, locale);
63760376Sdes	    us->atime = us->mtime = timegm(&tm);
63860376Sdes	    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
63960376Sdes			  "%02d:%02d:%02d\033[m]\n",
64060376Sdes			  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
64160376Sdes			  tm.tm_hour, tm.tm_min, tm.tm_sec));
64260376Sdes	} else if ((p = _http_match("Content-Length", ln)) != NULL) {
64360376Sdes	    us->size = 0;
64460376Sdes	    while (*p && isdigit(*p))
64560376Sdes		us->size = us->size * 10 + (*p++ - '0');
64660376Sdes	    DEBUG(fprintf(stderr, "content length: [\033[1m%lld\033[m]\n", us->size));
64760376Sdes	}
64860376Sdes    }
64960581Sdes
65060581Sdes    fclose(f);
65160376Sdes    return 0;
65260376Sdes ouch:
65360376Sdes    _http_seterr(999); /* XXX do this properly RSN */
65460376Sdes fouch:
65560376Sdes    fclose(f);
65660376Sdes    return -1;
65740975Sdes}
65841989Sdes
65941989Sdes/*
66041989Sdes * List a directory
66141989Sdes */
66241989Sdesstruct url_ent *
66341989SdesfetchListHTTP(struct url *url, char *flags)
66441989Sdes{
66541989Sdes    warnx("fetchListHTTP(): not implemented");
66641989Sdes    return NULL;
66741989Sdes}
668