http.c revision 62812
1189251Ssam/*- 2214734Srpaulo * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 3214734Srpaulo * All rights reserved. 4189251Ssam * 5252726Srpaulo * Redistribution and use in source and binary forms, with or without 6252726Srpaulo * modification, are permitted provided that the following conditions 7189251Ssam * are met: 8189251Ssam * 1. Redistributions of source code must retain the above copyright 9189251Ssam * notice, this list of conditions and the following disclaimer 10189251Ssam * in this position and unchanged. 11189251Ssam * 2. Redistributions in binary form must reproduce the above copyright 12189251Ssam * notice, this list of conditions and the following disclaimer in the 13189251Ssam * documentation and/or other materials provided with the distribution. 14189251Ssam * 3. The name of the author may not be used to endorse or promote products 15189251Ssam * derived from this software without specific prior written permission 16189251Ssam * 17189251Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18189251Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19189251Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20189251Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21189251Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22189251Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23189251Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24189251Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25189251Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26189251Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27189251Ssam * 28189251Ssam * $FreeBSD: head/lib/libfetch/http.c 62811 2000-07-08 08:08:58Z des $ 29189251Ssam */ 30189251Ssam 31189251Ssam/* 32189251Ssam * The base64 code in this file is based on code from MIT fetch, which 33189251Ssam * has the following copyright and license: 34189251Ssam * 35189251Ssam *- 36189251Ssam * Copyright 1997 Massachusetts Institute of Technology 37189251Ssam * 38189251Ssam * Permission to use, copy, modify, and distribute this software and 39189251Ssam * its documentation for any purpose and without fee is hereby 40189251Ssam * granted, provided that both the above copyright notice and this 41189251Ssam * permission notice appear in all copies, that both the above 42189251Ssam * copyright notice and this permission notice appear in all 43189251Ssam * supporting documentation, and that the name of M.I.T. not be used 44189251Ssam * in advertising or publicity pertaining to distribution of the 45189251Ssam * software without specific, written prior permission. M.I.T. makes 46189251Ssam * no representations about the suitability of this software for any 47189251Ssam * purpose. It is provided "as is" without express or implied 48189251Ssam * warranty. 49189251Ssam * 50189251Ssam * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS 51189251Ssam * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, 52189251Ssam * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 53189251Ssam * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT 54189251Ssam * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 55189251Ssam * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 56189251Ssam * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 57189251Ssam * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 58189251Ssam * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 59189251Ssam * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 60189251Ssam * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 61189251Ssam * SUCH DAMAGE. */ 62189251Ssam 63189251Ssam#include <sys/param.h> 64189251Ssam#include <sys/socket.h> 65189251Ssam 66189251Ssam#include <err.h> 67252726Srpaulo#include <ctype.h> 68252726Srpaulo#include <locale.h> 69252726Srpaulo#include <netdb.h> 70252726Srpaulo#include <stdarg.h> 71252726Srpaulo#include <stdio.h> 72252726Srpaulo#include <stdlib.h> 73252726Srpaulo#include <string.h> 74252726Srpaulo#include <time.h> 75189251Ssam#include <unistd.h> 76252726Srpaulo 77252726Srpaulo#include "fetch.h" 78189251Ssam#include "common.h" 79189251Ssam#include "httperr.h" 80189251Ssam 81189251Ssamextern char *__progname; 82189251Ssam 83189251Ssam#define ENDL "\r\n" 84189251Ssam 85189251Ssam#define HTTP_OK 200 86189251Ssam#define HTTP_PARTIAL 206 87189251Ssam#define HTTP_MOVED 302 88189251Ssam 89189251Ssamstruct cookie 90189251Ssam{ 91189251Ssam FILE *real_f; 92189251Ssam#define ENC_NONE 0 93189251Ssam#define ENC_CHUNKED 1 94189251Ssam int encoding; /* 1 = chunked, 0 = none */ 95189251Ssam#define HTTPCTYPELEN 59 96189251Ssam char content_type[HTTPCTYPELEN+1]; 97189251Ssam char *buf; 98189251Ssam int b_cur, eof; 99189251Ssam unsigned b_len, chunksize; 100189251Ssam}; 101189251Ssam 102189251Ssam/* 103189251Ssam * Send a formatted line; optionally echo to terminal 104189251Ssam */ 105189251Ssamstatic int 106189251Ssam_http_cmd(FILE *f, char *fmt, ...) 107189251Ssam{ 108189251Ssam va_list ap; 109189251Ssam 110189251Ssam va_start(ap, fmt); 111189251Ssam vfprintf(f, fmt, ap); 112189251Ssam#ifndef NDEBUG 113189251Ssam fprintf(stderr, "\033[1m>>> "); 114189251Ssam vfprintf(stderr, fmt, ap); 115189251Ssam fprintf(stderr, "\033[m"); 116189251Ssam#endif 117189251Ssam va_end(ap); 118189251Ssam 119189251Ssam return 0; /* XXX */ 120189251Ssam} 121189251Ssam 122189251Ssam/* 123189251Ssam * Fill the input buffer, do chunk decoding on the fly 124189251Ssam */ 125189251Ssamstatic char * 126189251Ssam_http_fillbuf(struct cookie *c) 127189251Ssam{ 128189251Ssam char *ln; 129189251Ssam unsigned int len; 130189251Ssam 131189251Ssam if (c->eof) 132189251Ssam return NULL; 133189251Ssam 134189251Ssam if (c->encoding == ENC_NONE) { 135189251Ssam c->buf = fgetln(c->real_f, &(c->b_len)); 136189251Ssam c->b_cur = 0; 137189251Ssam } else if (c->encoding == ENC_CHUNKED) { 138189251Ssam if (c->chunksize == 0) { 139189251Ssam ln = fgetln(c->real_f, &len); 140189251Ssam if (len <= 2) 141189251Ssam return NULL; 142189251Ssam DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: " 143189251Ssam "%*.*s\033[m\n", (int)len-2, (int)len-2, ln)); 144189251Ssam sscanf(ln, "%x", &(c->chunksize)); 145189251Ssam if (!c->chunksize) { 146189251Ssam DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): " 147189251Ssam "end of last chunk\033[m\n")); 148189251Ssam c->eof = 1; 149189251Ssam return NULL; 150189251Ssam } 151189251Ssam DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): " 152189251Ssam "new chunk: %X\033[m\n", c->chunksize)); 153189251Ssam } 154189251Ssam c->buf = fgetln(c->real_f, &(c->b_len)); 155189251Ssam if (c->b_len > c->chunksize) 156189251Ssam c->b_len = c->chunksize; 157189251Ssam c->chunksize -= c->b_len; 158189251Ssam c->b_cur = 0; 159189251Ssam } 160189251Ssam else return NULL; /* unknown encoding */ 161189251Ssam return c->buf; 162189251Ssam} 163189251Ssam 164189251Ssam/* 165189251Ssam * Read function 166189251Ssam */ 167189251Ssamstatic int 168189251Ssam_http_readfn(struct cookie *c, char *buf, int len) 169189251Ssam{ 170189251Ssam int l, pos = 0; 171189251Ssam while (len) { 172189251Ssam /* empty buffer */ 173189251Ssam if (!c->buf || (c->b_cur == c->b_len)) 174189251Ssam if (!_http_fillbuf(c)) 175189251Ssam break; 176189251Ssam 177189251Ssam l = c->b_len - c->b_cur; 178189251Ssam if (len < l) l = len; 179189251Ssam memcpy(buf + pos, c->buf + c->b_cur, l); 180189251Ssam c->b_cur += l; 181189251Ssam pos += l; 182189251Ssam len -= l; 183252726Srpaulo } 184252726Srpaulo 185252726Srpaulo if (ferror(c->real_f)) 186252726Srpaulo return -1; 187252726Srpaulo else return pos; 188252726Srpaulo} 189252726Srpaulo 190252726Srpaulo/* 191252726Srpaulo * Write function 192252726Srpaulo */ 193252726Srpaulostatic int 194252726Srpaulo_http_writefn(struct cookie *c, const char *buf, int len) 195252726Srpaulo{ 196252726Srpaulo size_t r = fwrite(buf, 1, (size_t)len, c->real_f); 197252726Srpaulo return r ? r : -1; 198252726Srpaulo} 199252726Srpaulo 200252726Srpaulo/* 201189251Ssam * Close function 202252726Srpaulo */ 203189251Ssamstatic int 204189251Ssam_http_closefn(struct cookie *c) 205189251Ssam{ 206189251Ssam int r = fclose(c->real_f); 207189251Ssam free(c); 208189251Ssam return (r == EOF) ? -1 : 0; 209189251Ssam} 210189251Ssam 211189251Ssam/* 212189251Ssam * Extract content type from cookie 213189251Ssam */ 214189251Ssamchar * 215189251SsamfetchContentType(FILE *f) 216189251Ssam{ 217189251Ssam /* 218189251Ssam * We have no way of making sure this really *is* one of our cookies, 219189251Ssam * so just check for a null pointer and hope for the best. 220189251Ssam */ 221189251Ssam return f->_cookie ? (((struct cookie *)f->_cookie)->content_type) : NULL; 222189251Ssam} 223189251Ssam 224189251Ssam/* 225189251Ssam * Base64 encoding 226189251Ssam */ 227189251Ssamint 228189251Ssam_http_base64(char *dst, char *src, int l) 229189251Ssam{ 230189251Ssam static const char base64[] = 231189251Ssam "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 232189251Ssam "abcdefghijklmnopqrstuvwxyz" 233189251Ssam "0123456789+/"; 234189251Ssam int t, r = 0; 235189251Ssam 236189251Ssam while (l >= 3) { 237189251Ssam t = (src[0] << 16) | (src[1] << 8) | src[2]; 238189251Ssam dst[0] = base64[(t >> 18) & 0x3f]; 239189251Ssam dst[1] = base64[(t >> 12) & 0x3f]; 240189251Ssam dst[2] = base64[(t >> 6) & 0x3f]; 241189251Ssam dst[3] = base64[(t >> 0) & 0x3f]; 242189251Ssam src += 3; l -= 3; 243189251Ssam dst += 4; r += 4; 244189251Ssam } 245189251Ssam 246189251Ssam switch (l) { 247189251Ssam case 2: 248189251Ssam t = (src[0] << 16) | (src[1] << 8); 249189251Ssam dst[0] = base64[(t >> 18) & 0x3f]; 250189251Ssam dst[1] = base64[(t >> 12) & 0x3f]; 251189251Ssam dst[2] = base64[(t >> 6) & 0x3f]; 252189251Ssam dst[3] = '='; 253189251Ssam dst += 4; 254189251Ssam r += 4; 255189251Ssam break; 256189251Ssam case 1: 257189251Ssam t = src[0] << 16; 258189251Ssam dst[0] = base64[(t >> 18) & 0x3f]; 259189251Ssam dst[1] = base64[(t >> 12) & 0x3f]; 260189251Ssam dst[2] = dst[3] = '='; 261189251Ssam dst += 4; 262189251Ssam r += 4; 263189251Ssam break; 264189251Ssam case 0: 265189251Ssam break; 266189251Ssam } 267189251Ssam 268189251Ssam *dst = 0; 269189251Ssam return r; 270189251Ssam} 271189251Ssam 272189251Ssam/* 273189251Ssam * Encode username and password 274189251Ssam */ 275189251Ssamchar * 276189251Ssam_http_auth(char *usr, char *pwd) 277189251Ssam{ 278189251Ssam int len, lup; 279189251Ssam char *uandp, *str = NULL; 280189251Ssam 281189251Ssam lup = strlen(usr) + 1 + strlen(pwd);/* length of "usr:pwd" */ 282189251Ssam uandp = (char*)malloc(lup + 1); 283189251Ssam if (uandp) { 284189251Ssam len = ((lup + 2) / 3) * 4; /* length of base64 encoded "usr:pwd" incl. padding */ 285189251Ssam str = (char*)malloc(len + 1); 286189251Ssam if (str) { 287189251Ssam strcpy(uandp, usr); 288189251Ssam strcat(uandp, ":"); 289189251Ssam strcat(uandp, pwd); 290189251Ssam _http_base64(str, uandp, lup); 291189251Ssam } 292189251Ssam free(uandp); 293189251Ssam } 294189251Ssam return str; 295189251Ssam} 296189251Ssam 297189251Ssam/* 298189251Ssam * Connect to server or proxy 299189251Ssam */ 300189251SsamFILE * 301189251Ssam_http_connect(struct url *URL, char *flags) 302189251Ssam{ 303189251Ssam int direct, sd = -1, verbose; 304189251Ssam#ifdef INET6 305189251Ssam int af = AF_UNSPEC; 306189251Ssam#else 307189251Ssam int af = AF_INET; 308189251Ssam#endif 309189251Ssam size_t len; 310189251Ssam char *px; 311189251Ssam FILE *f; 312189251Ssam 313189251Ssam direct = (flags && strchr(flags, 'd')); 314189251Ssam verbose = (flags && strchr(flags, 'v')); 315189251Ssam if ((flags && strchr(flags, '4'))) 316189251Ssam af = AF_INET; 317189251Ssam else if ((flags && strchr(flags, '6'))) 318189251Ssam af = AF_INET6; 319189251Ssam 320189251Ssam /* check port */ 321189251Ssam if (!URL->port) { 322189251Ssam struct servent *se; 323189251Ssam 324189251Ssam if (strcasecmp(URL->scheme, "ftp") == 0) 325189251Ssam if ((se = getservbyname("ftp", "tcp")) != NULL) 326189251Ssam URL->port = ntohs(se->s_port); 327189251Ssam else 328189251Ssam URL->port = 21; 329189251Ssam else 330189251Ssam if ((se = getservbyname("http", "tcp")) != NULL) 331189251Ssam URL->port = ntohs(se->s_port); 332189251Ssam else 333189251Ssam URL->port = 80; 334189251Ssam } 335189251Ssam 336189251Ssam /* attempt to connect to proxy server */ 337189251Ssam if (!direct && (px = getenv("HTTP_PROXY")) != NULL) { 338189251Ssam char host[MAXHOSTNAMELEN]; 339189251Ssam int port = 0; 340189251Ssam 341189251Ssam /* measure length */ 342189251Ssam#ifdef INET6 343189251Ssam if (px[0] != '[' || 344189251Ssam (len = strcspn(px, "]")) >= strlen(px) || 345189251Ssam (px[++len] != '\0' && px[len] != ':')) 346189251Ssam#endif 347189251Ssam len = strcspn(px, ":"); 348189251Ssam 349189251Ssam /* get port (XXX atoi is a little too tolerant perhaps?) */ 350189251Ssam if (px[len] == ':') { 351189251Ssam if (strspn(px+len+1, "0123456789") != strlen(px+len+1) 352189251Ssam || strlen(px+len+1) > 5) { 353189251Ssam /* XXX we should emit some kind of warning */ 354189251Ssam } 355189251Ssam port = atoi(px+len+1); 356189251Ssam if (port < 1 || port > 65535) { 357189251Ssam /* XXX we should emit some kind of warning */ 358189251Ssam } 359189251Ssam } 360189251Ssam if (!port) { 361189251Ssam#if 0 362189251Ssam /* 363189251Ssam * commented out, since there is currently no service name 364189251Ssam * for HTTP proxies 365189251Ssam */ 366189251Ssam struct servent *se; 367189251Ssam 368189251Ssam if ((se = getservbyname("xxxx", "tcp")) != NULL) 369189251Ssam port = ntohs(se->s_port); 370189251Ssam else 371189251Ssam#endif 372189251Ssam port = 3128; 373189251Ssam } 374189251Ssam 375189251Ssam /* get host name */ 376189251Ssam#ifdef INET6 377189251Ssam if (len > 1 && px[0] == '[' && px[len - 1] == ']') { 378189251Ssam px++; 379189251Ssam len -= 2; 380189251Ssam } 381189251Ssam#endif 382189251Ssam if (len >= MAXHOSTNAMELEN) 383189251Ssam len = MAXHOSTNAMELEN - 1; 384189251Ssam strncpy(host, px, len); 385189251Ssam host[len] = 0; 386189251Ssam 387189251Ssam /* connect */ 388189251Ssam sd = _fetch_connect(host, port, af, verbose); 389189251Ssam } 390189251Ssam 391189251Ssam /* if no proxy is configured or could be contacted, try direct */ 392189251Ssam if (sd == -1) { 393189251Ssam if (strcasecmp(URL->scheme, "ftp") == 0) 394189251Ssam goto ouch; 395189251Ssam if ((sd = _fetch_connect(URL->host, URL->port, af, verbose)) == -1) 396189251Ssam goto ouch; 397189251Ssam } 398189251Ssam 399189251Ssam /* reopen as stream */ 400189251Ssam if ((f = fdopen(sd, "r+")) == NULL) 401189251Ssam goto ouch; 402189251Ssam 403189251Ssam return f; 404189251Ssam 405214734Srpauloouch: 406214734Srpaulo if (sd >= 0) 407214734Srpaulo close(sd); 408214734Srpaulo _http_seterr(999); /* XXX do this properly RSN */ 409214734Srpaulo return NULL; 410214734Srpaulo} 411189251Ssam 412189251Ssam/* 413189251Ssam * Check a header line 414189251Ssam */ 415189251Ssamchar * 416189251Ssam_http_match(char *str, char *hdr) 417189251Ssam{ 418189251Ssam while (*str && *hdr && tolower(*str++) == tolower(*hdr++)) 419189251Ssam /* nothing */; 420214734Srpaulo if (*str || *hdr != ':') 421214734Srpaulo return NULL; 422214734Srpaulo while (*hdr && isspace(*++hdr)) 423214734Srpaulo /* nothing */; 424214734Srpaulo return hdr; 425214734Srpaulo} 426214734Srpaulo 427214734Srpaulo/* 428189251Ssam * Send a HEAD or GET request 429189251Ssam */ 430189251Ssamint 431189251Ssam_http_request(FILE *f, char *op, struct url *URL, char *flags) 432189251Ssam{ 433189251Ssam int e, verbose; 434189251Ssam char *ln, *p; 435189251Ssam size_t len; 436189251Ssam char *host; 437189251Ssam#ifdef INET6 438189251Ssam char hbuf[MAXHOSTNAMELEN + 1]; 439189251Ssam#endif 440189251Ssam 441189251Ssam verbose = (flags && strchr(flags, 'v')); 442189251Ssam 443189251Ssam host = URL->host; 444189251Ssam#ifdef INET6 445189251Ssam if (strchr(URL->host, ':')) { 446189251Ssam snprintf(hbuf, sizeof(hbuf), "[%s]", URL->host); 447189251Ssam host = hbuf; 448189251Ssam } 449189251Ssam#endif 450189251Ssam 451189251Ssam /* send request (proxies require absolute form, so use that) */ 452189251Ssam if (verbose) 453189251Ssam _fetch_info("requesting %s://%s:%d%s", 454189251Ssam URL->scheme, host, URL->port, URL->doc); 455189251Ssam _http_cmd(f, "%s %s://%s:%d%s HTTP/1.1" ENDL, 456189251Ssam op, URL->scheme, host, URL->port, URL->doc); 457189251Ssam 458189251Ssam /* start sending headers away */ 459189251Ssam if (URL->user[0] || URL->pwd[0]) { 460189251Ssam char *auth_str = _http_auth(URL->user, URL->pwd); 461189251Ssam if (!auth_str) 462189251Ssam return 999; /* XXX wrong */ 463189251Ssam _http_cmd(f, "Authorization: Basic %s" ENDL, auth_str); 464189251Ssam free(auth_str); 465189251Ssam } 466189251Ssam if (p = getenv("HTTP_PROXY_AUTH")) { 467189251Ssam char *auth; 468189251Ssam 469189251Ssam /* skip leading "basic:*:", if present */ 470189251Ssam if (strncmp(p, "basic:*:", 6 + 2) == 0) 471189251Ssam p += 6 + 2; 472189251Ssam auth = strchr(p, ':'); 473189251Ssam if (auth != NULL) { 474189251Ssam int len = auth - p; 475189251Ssam char *user; 476189251Ssam char *auth_str; 477189251Ssam 478189251Ssam if ((user = (char*)malloc(len + 1)) == NULL) { 479189251Ssam free(auth); 480189251Ssam return 999; /* XXX wrong */ 481189251Ssam } 482189251Ssam strncpy(user, p, len); 483189251Ssam user[len] = 0; 484189251Ssam auth++; 485189251Ssam auth_str = _http_auth(user, auth); 486189251Ssam free(user); 487189251Ssam if (auth_str == NULL) 488189251Ssam return 999; /* XXX wrong */ 489252726Srpaulo _http_cmd(f, "Proxy-Authorization: Basic %s" ENDL, auth_str); 490252726Srpaulo free(auth_str); 491252726Srpaulo } else { 492252726Srpaulo return 999; /* XXX wrong */ 493252726Srpaulo } 494252726Srpaulo } 495252726Srpaulo _http_cmd(f, "Host: %s:%d" ENDL, host, URL->port); 496252726Srpaulo _http_cmd(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname); 497189251Ssam if (URL->offset) 498189251Ssam _http_cmd(f, "Range: bytes=%lld-" ENDL, URL->offset); 499189251Ssam _http_cmd(f, "Connection: close" ENDL ENDL); 500189251Ssam 501189251Ssam /* get response */ 502189251Ssam if ((ln = fgetln(f, &len)) == NULL) 503189251Ssam return 999; 504189251Ssam DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n", 505189251Ssam (int)len-2, (int)len-2, ln)); 506189251Ssam 507189251Ssam /* we can't use strchr() and friends since ln isn't NUL-terminated */ 508189251Ssam p = ln; 509189251Ssam while ((p < ln + len) && !isspace(*p)) 510189251Ssam p++; 511189251Ssam while ((p < ln + len) && !isdigit(*p)) 512189251Ssam p++; 513189251Ssam if (!isdigit(*p)) 514189251Ssam return 999; 515189251Ssam 516189251Ssam e = atoi(p); 517189251Ssam DEBUG(fprintf(stderr, "code: [\033[1m%d\033[m]\n", e)); 518189251Ssam return e; 519189251Ssam} 520189251Ssam 521189251Ssam/* 522189251Ssam * Retrieve a file by HTTP 523189251Ssam */ 524189251SsamFILE * 525189251SsamfetchGetHTTP(struct url *URL, char *flags) 526189251Ssam{ 527189251Ssam int e, enc = ENC_NONE, i, noredirect; 528189251Ssam struct cookie *c; 529189251Ssam char *ln, *p, *q; 530189251Ssam FILE *f, *cf; 531189251Ssam size_t len; 532189251Ssam off_t pos = 0; 533189251Ssam 534189251Ssam noredirect = (flags && strchr(flags, 'A')); 535189251Ssam 536189251Ssam /* allocate cookie */ 537189251Ssam if ((c = calloc(1, sizeof *c)) == NULL) 538189251Ssam return NULL; 539189251Ssam 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