http.c revision 209632
137535Sdes/*- 2135546Sdes * Copyright (c) 2000-2004 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 1563012Sdes * 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 */ 2837535Sdes 2984203Sdillon#include <sys/cdefs.h> 3084203Sdillon__FBSDID("$FreeBSD: head/lib/libfetch/http.c 209632 2010-07-01 17:44:33Z des $"); 3184203Sdillon 3263236Sdes/* 3363236Sdes * The following copyright applies to the base64 code: 3463236Sdes * 3563236Sdes *- 3663236Sdes * Copyright 1997 Massachusetts Institute of Technology 3763236Sdes * 3863236Sdes * Permission to use, copy, modify, and distribute this software and 3963236Sdes * its documentation for any purpose and without fee is hereby 4063236Sdes * granted, provided that both the above copyright notice and this 4163236Sdes * permission notice appear in all copies, that both the above 4263236Sdes * copyright notice and this permission notice appear in all 4363236Sdes * supporting documentation, and that the name of M.I.T. not be used 4463236Sdes * in advertising or publicity pertaining to distribution of the 4563236Sdes * software without specific, written prior permission. M.I.T. makes 4663236Sdes * no representations about the suitability of this software for any 4763236Sdes * purpose. It is provided "as is" without express or implied 4863236Sdes * warranty. 4990267Sdes * 5063236Sdes * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS 5163236Sdes * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, 5263236Sdes * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 5363236Sdes * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT 5463236Sdes * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 5563236Sdes * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 5663236Sdes * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 5763236Sdes * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 5863236Sdes * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 5963236Sdes * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 6063236Sdes * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 6163236Sdes * SUCH DAMAGE. 6263236Sdes */ 6363236Sdes 6437535Sdes#include <sys/param.h> 6560737Sume#include <sys/socket.h> 66186124Smurray#include <sys/time.h> 6737535Sdes 6863012Sdes#include <ctype.h> 6937535Sdes#include <err.h> 7063012Sdes#include <errno.h> 7160376Sdes#include <locale.h> 7260189Sdes#include <netdb.h> 7337608Sdes#include <stdarg.h> 7437535Sdes#include <stdio.h> 7537535Sdes#include <stdlib.h> 7637535Sdes#include <string.h> 7760376Sdes#include <time.h> 7837535Sdes#include <unistd.h> 79202613Sdes#include <md5.h> 8037535Sdes 81141958Skbyanc#include <netinet/in.h> 82141958Skbyanc#include <netinet/tcp.h> 83141958Skbyanc 8437535Sdes#include "fetch.h" 8540939Sdes#include "common.h" 8641862Sdes#include "httperr.h" 8737535Sdes 8863012Sdes/* Maximum number of redirects to follow */ 8963012Sdes#define MAX_REDIRECT 5 9037535Sdes 9163012Sdes/* Symbolic names for reply codes we care about */ 9263012Sdes#define HTTP_OK 200 9363012Sdes#define HTTP_PARTIAL 206 9463012Sdes#define HTTP_MOVED_PERM 301 9563012Sdes#define HTTP_MOVED_TEMP 302 9663012Sdes#define HTTP_SEE_OTHER 303 97186124Smurray#define HTTP_NOT_MODIFIED 304 98169386Sdes#define HTTP_TEMP_REDIRECT 307 9963012Sdes#define HTTP_NEED_AUTH 401 10087317Sdes#define HTTP_NEED_PROXY_AUTH 407 101125696Sdes#define HTTP_BAD_RANGE 416 10263012Sdes#define HTTP_PROTOCOL_ERROR 999 10360196Sdes 10463012Sdes#define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \ 10590267Sdes || (xyz) == HTTP_MOVED_TEMP \ 106169386Sdes || (xyz) == HTTP_TEMP_REDIRECT \ 10790267Sdes || (xyz) == HTTP_SEE_OTHER) 10863012Sdes 10988771Sdes#define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599) 11063012Sdes 11190267Sdes 11263012Sdes/***************************************************************************** 11363012Sdes * I/O functions for decoding chunked streams 11463012Sdes */ 11563012Sdes 11697859Sdesstruct httpio 11737535Sdes{ 11897858Sdes conn_t *conn; /* connection */ 11997866Sdes int chunked; /* chunked mode */ 12097858Sdes char *buf; /* chunk buffer */ 12197866Sdes size_t bufsize; /* size of chunk buffer */ 12297866Sdes ssize_t buflen; /* amount of data currently in buffer */ 12397866Sdes int bufpos; /* current read offset in buffer */ 12497858Sdes int eof; /* end-of-file flag */ 12597858Sdes int error; /* error flag */ 12697858Sdes size_t chunksize; /* remaining size of current chunk */ 12763281Sdes#ifndef NDEBUG 12890267Sdes size_t total; 12963012Sdes#endif 13037535Sdes}; 13137535Sdes 13237608Sdes/* 13363012Sdes * Get next chunk header 13437608Sdes */ 13537608Sdesstatic int 136174588Sdeshttp_new_chunk(struct httpio *io) 13737608Sdes{ 13890267Sdes char *p; 13990267Sdes 140174588Sdes if (fetch_getln(io->conn) == -1) 14190267Sdes return (-1); 14290267Sdes 143174761Sdes if (io->conn->buflen < 2 || !isxdigit((unsigned char)*io->conn->buf)) 14490267Sdes return (-1); 14590267Sdes 146174761Sdes for (p = io->conn->buf; *p && !isspace((unsigned char)*p); ++p) { 14790267Sdes if (*p == ';') 14890267Sdes break; 149174761Sdes if (!isxdigit((unsigned char)*p)) 15090267Sdes return (-1); 151174761Sdes if (isdigit((unsigned char)*p)) { 15297859Sdes io->chunksize = io->chunksize * 16 + 15390267Sdes *p - '0'; 15490267Sdes } else { 15597859Sdes io->chunksize = io->chunksize * 16 + 156176036Sdes 10 + tolower((unsigned char)*p) - 'a'; 15790267Sdes } 15890267Sdes } 15990267Sdes 16063281Sdes#ifndef NDEBUG 16190267Sdes if (fetchDebug) { 16297859Sdes io->total += io->chunksize; 16397859Sdes if (io->chunksize == 0) 164106207Sdes fprintf(stderr, "%s(): end of last chunk\n", __func__); 16590267Sdes else 166106207Sdes fprintf(stderr, "%s(): new chunk: %lu (%lu)\n", 167106207Sdes __func__, (unsigned long)io->chunksize, 168106207Sdes (unsigned long)io->total); 16990267Sdes } 17063012Sdes#endif 17190267Sdes 17297859Sdes return (io->chunksize); 17337608Sdes} 17437608Sdes 17537608Sdes/* 17697866Sdes * Grow the input buffer to at least len bytes 17797866Sdes */ 17897866Sdesstatic inline int 179174588Sdeshttp_growbuf(struct httpio *io, size_t len) 18097866Sdes{ 18197866Sdes char *tmp; 18297866Sdes 18397866Sdes if (io->bufsize >= len) 18497866Sdes return (0); 18597866Sdes 18697866Sdes if ((tmp = realloc(io->buf, len)) == NULL) 18797866Sdes return (-1); 18897866Sdes io->buf = tmp; 18997866Sdes io->bufsize = len; 190106044Sdes return (0); 19197866Sdes} 19297866Sdes 19397866Sdes/* 19437608Sdes * Fill the input buffer, do chunk decoding on the fly 19537608Sdes */ 19663012Sdesstatic int 197174588Sdeshttp_fillbuf(struct httpio *io, size_t len) 19837535Sdes{ 19997859Sdes if (io->error) 20090267Sdes return (-1); 20197859Sdes if (io->eof) 20290267Sdes return (0); 20390267Sdes 20497866Sdes if (io->chunked == 0) { 205174588Sdes if (http_growbuf(io, len) == -1) 20697866Sdes return (-1); 207174588Sdes if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) { 208106185Sdes io->error = 1; 20997866Sdes return (-1); 210106185Sdes } 21197866Sdes io->bufpos = 0; 21297866Sdes return (io->buflen); 21397866Sdes } 21497866Sdes 21597859Sdes if (io->chunksize == 0) { 216174588Sdes switch (http_new_chunk(io)) { 21790267Sdes case -1: 21897859Sdes io->error = 1; 21990267Sdes return (-1); 22090267Sdes case 0: 22197859Sdes io->eof = 1; 22290267Sdes return (0); 22390267Sdes } 22437535Sdes } 22563012Sdes 22697866Sdes if (len > io->chunksize) 22797866Sdes len = io->chunksize; 228174588Sdes if (http_growbuf(io, len) == -1) 22990267Sdes return (-1); 230174588Sdes if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) { 231106185Sdes io->error = 1; 23297866Sdes return (-1); 233106185Sdes } 23497866Sdes io->chunksize -= io->buflen; 23590267Sdes 23697859Sdes if (io->chunksize == 0) { 23797856Sdes char endl[2]; 23897856Sdes 239174588Sdes if (fetch_read(io->conn, endl, 2) != 2 || 24097856Sdes endl[0] != '\r' || endl[1] != '\n') 24190267Sdes return (-1); 24290267Sdes } 24390267Sdes 24497866Sdes io->bufpos = 0; 24590267Sdes 24697866Sdes return (io->buflen); 24737535Sdes} 24837535Sdes 24937608Sdes/* 25037608Sdes * Read function 25137608Sdes */ 25237535Sdesstatic int 253174588Sdeshttp_readfn(void *v, char *buf, int len) 25437535Sdes{ 25597859Sdes struct httpio *io = (struct httpio *)v; 25690267Sdes int l, pos; 25763012Sdes 25897859Sdes if (io->error) 25990267Sdes return (-1); 26097859Sdes if (io->eof) 26190267Sdes return (0); 26263012Sdes 26390267Sdes for (pos = 0; len > 0; pos += l, len -= l) { 26490267Sdes /* empty buffer */ 26597866Sdes if (!io->buf || io->bufpos == io->buflen) 266174588Sdes if (http_fillbuf(io, len) < 1) 26790267Sdes break; 26897866Sdes l = io->buflen - io->bufpos; 26990267Sdes if (len < l) 27090267Sdes l = len; 271176105Sdes memcpy(buf + pos, io->buf + io->bufpos, l); 27297866Sdes io->bufpos += l; 27390267Sdes } 27437535Sdes 27597859Sdes if (!pos && io->error) 27690267Sdes return (-1); 27790267Sdes return (pos); 27837535Sdes} 27937535Sdes 28037608Sdes/* 28137608Sdes * Write function 28237608Sdes */ 28337535Sdesstatic int 284174588Sdeshttp_writefn(void *v, const char *buf, int len) 28537535Sdes{ 28697859Sdes struct httpio *io = (struct httpio *)v; 28790267Sdes 288174588Sdes return (fetch_write(io->conn, buf, len)); 28937535Sdes} 29037535Sdes 29137608Sdes/* 29237608Sdes * Close function 29337608Sdes */ 29437535Sdesstatic int 295174588Sdeshttp_closefn(void *v) 29637535Sdes{ 29797859Sdes struct httpio *io = (struct httpio *)v; 29890267Sdes int r; 29963012Sdes 300174588Sdes r = fetch_close(io->conn); 30197859Sdes if (io->buf) 30297859Sdes free(io->buf); 30397859Sdes free(io); 30490267Sdes return (r); 30537535Sdes} 30637535Sdes 30737608Sdes/* 30863012Sdes * Wrap a file descriptor up 30937608Sdes */ 31063012Sdesstatic FILE * 311174588Sdeshttp_funopen(conn_t *conn, int chunked) 31237535Sdes{ 31397859Sdes struct httpio *io; 31490267Sdes FILE *f; 31563012Sdes 316109967Sdes if ((io = calloc(1, sizeof(*io))) == NULL) { 317174588Sdes fetch_syserr(); 31890267Sdes return (NULL); 31990267Sdes } 32097859Sdes io->conn = conn; 32197866Sdes io->chunked = chunked; 322174588Sdes f = funopen(io, http_readfn, http_writefn, NULL, http_closefn); 32390267Sdes if (f == NULL) { 324174588Sdes fetch_syserr(); 32597859Sdes free(io); 32690267Sdes return (NULL); 32790267Sdes } 32890267Sdes return (f); 32963012Sdes} 33063012Sdes 33190267Sdes 33263012Sdes/***************************************************************************** 33363012Sdes * Helper functions for talking to the server and parsing its replies 33463012Sdes */ 33563012Sdes 33663012Sdes/* Header types */ 33763012Sdestypedef enum { 33890267Sdes hdr_syserror = -2, 33990267Sdes hdr_error = -1, 34090267Sdes hdr_end = 0, 34190267Sdes hdr_unknown = 1, 34290267Sdes hdr_content_length, 34390267Sdes hdr_content_range, 34490267Sdes hdr_last_modified, 34590267Sdes hdr_location, 34690267Sdes hdr_transfer_encoding, 347202613Sdes hdr_www_authenticate, 348202613Sdes hdr_proxy_authenticate, 34985093Sdes} hdr_t; 35063012Sdes 35163012Sdes/* Names of interesting headers */ 35263012Sdesstatic struct { 35390267Sdes hdr_t num; 35490267Sdes const char *name; 35563012Sdes} hdr_names[] = { 35690267Sdes { hdr_content_length, "Content-Length" }, 35790267Sdes { hdr_content_range, "Content-Range" }, 35890267Sdes { hdr_last_modified, "Last-Modified" }, 35990267Sdes { hdr_location, "Location" }, 36090267Sdes { hdr_transfer_encoding, "Transfer-Encoding" }, 36190267Sdes { hdr_www_authenticate, "WWW-Authenticate" }, 362202613Sdes { hdr_proxy_authenticate, "Proxy-Authenticate" }, 36390267Sdes { hdr_unknown, NULL }, 36463012Sdes}; 36563012Sdes 36663012Sdes/* 36763012Sdes * Send a formatted line; optionally echo to terminal 36863012Sdes */ 36963012Sdesstatic int 370174588Sdeshttp_cmd(conn_t *conn, const char *fmt, ...) 37163012Sdes{ 37290267Sdes va_list ap; 37390267Sdes size_t len; 37490267Sdes char *msg; 37590267Sdes int r; 37663012Sdes 37790267Sdes va_start(ap, fmt); 37890267Sdes len = vasprintf(&msg, fmt, ap); 37990267Sdes va_end(ap); 38090267Sdes 38190267Sdes if (msg == NULL) { 38290267Sdes errno = ENOMEM; 383174588Sdes fetch_syserr(); 38490267Sdes return (-1); 38590267Sdes } 38690267Sdes 387174588Sdes r = fetch_putln(conn, msg, len); 38890267Sdes free(msg); 38990267Sdes 39090267Sdes if (r == -1) { 391174588Sdes fetch_syserr(); 39290267Sdes return (-1); 39390267Sdes } 39490267Sdes 39590267Sdes return (0); 39663012Sdes} 39763012Sdes 39863012Sdes/* 39963012Sdes * Get and parse status line 40063012Sdes */ 40163012Sdesstatic int 402174588Sdeshttp_get_reply(conn_t *conn) 40363012Sdes{ 40490267Sdes char *p; 40590267Sdes 406174588Sdes if (fetch_getln(conn) == -1) 40790267Sdes return (-1); 40890267Sdes /* 40990267Sdes * A valid status line looks like "HTTP/m.n xyz reason" where m 41090267Sdes * and n are the major and minor protocol version numbers and xyz 41190267Sdes * is the reply code. 41290267Sdes * Unfortunately, there are servers out there (NCSA 1.5.1, to name 41390267Sdes * just one) that do not send a version number, so we can't rely 41490267Sdes * on finding one, but if we do, insist on it being 1.0 or 1.1. 41590267Sdes * We don't care about the reason phrase. 41690267Sdes */ 41797856Sdes if (strncmp(conn->buf, "HTTP", 4) != 0) 41890267Sdes return (HTTP_PROTOCOL_ERROR); 41997856Sdes p = conn->buf + 4; 42090267Sdes if (*p == '/') { 42190267Sdes if (p[1] != '1' || p[2] != '.' || (p[3] != '0' && p[3] != '1')) 42290267Sdes return (HTTP_PROTOCOL_ERROR); 42390267Sdes p += 4; 42490267Sdes } 425174761Sdes if (*p != ' ' || 426174761Sdes !isdigit((unsigned char)p[1]) || 427174761Sdes !isdigit((unsigned char)p[2]) || 428174761Sdes !isdigit((unsigned char)p[3])) 42990267Sdes return (HTTP_PROTOCOL_ERROR); 43090267Sdes 43197856Sdes conn->err = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0'); 43297856Sdes return (conn->err); 43337535Sdes} 43437535Sdes 43537608Sdes/* 43690267Sdes * Check a header; if the type matches the given string, return a pointer 43790267Sdes * to the beginning of the value. 43863012Sdes */ 43975891Sarchiestatic const char * 440174588Sdeshttp_match(const char *str, const char *hdr) 44163012Sdes{ 442176036Sdes while (*str && *hdr && 443176036Sdes tolower((unsigned char)*str++) == tolower((unsigned char)*hdr++)) 44490267Sdes /* nothing */; 44590267Sdes if (*str || *hdr != ':') 44690267Sdes return (NULL); 447174761Sdes while (*hdr && isspace((unsigned char)*++hdr)) 44890267Sdes /* nothing */; 44990267Sdes return (hdr); 45063012Sdes} 45163012Sdes 452202613Sdes 45363012Sdes/* 454202613Sdes * Get the next header and return the appropriate symbolic code. We 455202613Sdes * need to read one line ahead for checking for a continuation line 456202613Sdes * belonging to the current header (continuation lines start with 457202613Sdes * white space). 458202613Sdes * 459202613Sdes * We get called with a fresh line already in the conn buffer, either 460202613Sdes * from the previous http_next_header() invocation, or, the first 461202613Sdes * time, from a fetch_getln() performed by our caller. 462202613Sdes * 463202613Sdes * This stops when we encounter an empty line (we dont read beyond the header 464202613Sdes * area). 465202613Sdes * 466202613Sdes * Note that the "headerbuf" is just a place to return the result. Its 467202613Sdes * contents are not used for the next call. This means that no cleanup 468202613Sdes * is needed when ie doing another connection, just call the cleanup when 469202613Sdes * fully done to deallocate memory. 47063012Sdes */ 471202613Sdes 472202613Sdes/* Limit the max number of continuation lines to some reasonable value */ 473202613Sdes#define HTTP_MAX_CONT_LINES 10 474202613Sdes 475202613Sdes/* Place into which to build a header from one or several lines */ 476202613Sdestypedef struct { 477202613Sdes char *buf; /* buffer */ 478202613Sdes size_t bufsize; /* buffer size */ 479202613Sdes size_t buflen; /* length of buffer contents */ 480202613Sdes} http_headerbuf_t; 481202613Sdes 482202613Sdesstatic void 483202613Sdesinit_http_headerbuf(http_headerbuf_t *buf) 48463012Sdes{ 485202613Sdes buf->buf = NULL; 486202613Sdes buf->bufsize = 0; 487202613Sdes buf->buflen = 0; 488202613Sdes} 48990267Sdes 490202613Sdesstatic void 491202613Sdesclean_http_headerbuf(http_headerbuf_t *buf) 492202613Sdes{ 493202613Sdes if (buf->buf) 494202613Sdes free(buf->buf); 495202613Sdes init_http_headerbuf(buf); 496202613Sdes} 497202613Sdes 498202613Sdes/* Remove whitespace at the end of the buffer */ 499202613Sdesstatic void 500202613Sdeshttp_conn_trimright(conn_t *conn) 501202613Sdes{ 502202613Sdes while (conn->buflen && 503202613Sdes isspace((unsigned char)conn->buf[conn->buflen - 1])) 50497856Sdes conn->buflen--; 50597856Sdes conn->buf[conn->buflen] = '\0'; 506202613Sdes} 507202613Sdes 508202613Sdesstatic hdr_t 509202613Sdeshttp_next_header(conn_t *conn, http_headerbuf_t *hbuf, const char **p) 510202613Sdes{ 511202613Sdes int i, len; 512202613Sdes 513202613Sdes /* 514202613Sdes * Have to do the stripping here because of the first line. So 515202613Sdes * it's done twice for the subsequent lines. No big deal 516202613Sdes */ 517202613Sdes http_conn_trimright(conn); 51897856Sdes if (conn->buflen == 0) 51997856Sdes return (hdr_end); 520202613Sdes 521202613Sdes /* Copy the line to the headerbuf */ 522202613Sdes if (hbuf->bufsize < conn->buflen + 1) { 523202613Sdes if ((hbuf->buf = realloc(hbuf->buf, conn->buflen + 1)) == NULL) 524202613Sdes return (hdr_syserror); 525202613Sdes hbuf->bufsize = conn->buflen + 1; 526202613Sdes } 527202613Sdes strcpy(hbuf->buf, conn->buf); 528202613Sdes hbuf->buflen = conn->buflen; 529202613Sdes 530202613Sdes /* 531202613Sdes * Fetch possible continuation lines. Stop at 1st non-continuation 532202613Sdes * and leave it in the conn buffer 533202613Sdes */ 534202613Sdes for (i = 0; i < HTTP_MAX_CONT_LINES; i++) { 535202613Sdes if (fetch_getln(conn) == -1) 536202613Sdes return (hdr_syserror); 537202613Sdes 538202613Sdes /* 539202613Sdes * Note: we carry on the idea from the previous version 540202613Sdes * that a pure whitespace line is equivalent to an empty 541202613Sdes * one (so it's not continuation and will be handled when 542202613Sdes * we are called next) 543202613Sdes */ 544202613Sdes http_conn_trimright(conn); 545202613Sdes if (conn->buf[0] != ' ' && conn->buf[0] != "\t"[0]) 546202613Sdes break; 547202613Sdes 548202613Sdes /* Got a continuation line. Concatenate to previous */ 549202613Sdes len = hbuf->buflen + conn->buflen; 550202613Sdes if (hbuf->bufsize < len + 1) { 551202613Sdes len *= 2; 552202613Sdes if ((hbuf->buf = realloc(hbuf->buf, len + 1)) == NULL) 553202613Sdes return (hdr_syserror); 554202613Sdes hbuf->bufsize = len + 1; 555202613Sdes } 556202613Sdes strcpy(hbuf->buf + hbuf->buflen, conn->buf); 557202613Sdes hbuf->buflen += conn->buflen; 558202613Sdes } 559202613Sdes 56090267Sdes /* 56190267Sdes * We could check for malformed headers but we don't really care. 56290267Sdes * A valid header starts with a token immediately followed by a 56390267Sdes * colon; a token is any sequence of non-control, non-whitespace 56490267Sdes * characters except "()<>@,;:\\\"{}". 56590267Sdes */ 56690267Sdes for (i = 0; hdr_names[i].num != hdr_unknown; i++) 567202613Sdes if ((*p = http_match(hdr_names[i].name, hbuf->buf)) != NULL) 56890267Sdes return (hdr_names[i].num); 569202613Sdes 57090267Sdes return (hdr_unknown); 57163012Sdes} 57263012Sdes 573202613Sdes/************************** 574202613Sdes * [Proxy-]Authenticate header parsing 575202613Sdes */ 576202613Sdes 577202613Sdes/* 578202613Sdes * Read doublequote-delimited string into output buffer obuf (allocated 579202613Sdes * by caller, whose responsibility it is to ensure that it's big enough) 580202613Sdes * cp points to the first char after the initial '"' 581202613Sdes * Handles \ quoting 582202613Sdes * Returns pointer to the first char after the terminating double quote, or 583202613Sdes * NULL for error. 584202613Sdes */ 585202613Sdesstatic const char * 586202613Sdeshttp_parse_headerstring(const char *cp, char *obuf) 587202613Sdes{ 588202613Sdes for (;;) { 589202613Sdes switch (*cp) { 590202613Sdes case 0: /* Unterminated string */ 591202613Sdes *obuf = 0; 592202613Sdes return (NULL); 593202613Sdes case '"': /* Ending quote */ 594202613Sdes *obuf = 0; 595202613Sdes return (++cp); 596202613Sdes case '\\': 597202613Sdes if (*++cp == 0) { 598202613Sdes *obuf = 0; 599202613Sdes return (NULL); 600202613Sdes } 601202613Sdes /* FALLTHROUGH */ 602202613Sdes default: 603202613Sdes *obuf++ = *cp++; 604202613Sdes } 605202613Sdes } 606202613Sdes} 607202613Sdes 608202613Sdes/* Http auth challenge schemes */ 609202613Sdestypedef enum {HTTPAS_UNKNOWN, HTTPAS_BASIC,HTTPAS_DIGEST} http_auth_schemes_t; 610202613Sdes 611202613Sdes/* Data holder for a Basic or Digest challenge. */ 612202613Sdestypedef struct { 613202613Sdes http_auth_schemes_t scheme; 614202613Sdes char *realm; 615202613Sdes char *qop; 616202613Sdes char *nonce; 617202613Sdes char *opaque; 618202613Sdes char *algo; 619202613Sdes int stale; 620202613Sdes int nc; /* Nonce count */ 621202613Sdes} http_auth_challenge_t; 622202613Sdes 623202613Sdesstatic void 624202613Sdesinit_http_auth_challenge(http_auth_challenge_t *b) 625202613Sdes{ 626202613Sdes b->scheme = HTTPAS_UNKNOWN; 627202613Sdes b->realm = b->qop = b->nonce = b->opaque = b->algo = NULL; 628202613Sdes b->stale = b->nc = 0; 629202613Sdes} 630202613Sdes 631202613Sdesstatic void 632202613Sdesclean_http_auth_challenge(http_auth_challenge_t *b) 633202613Sdes{ 634202613Sdes if (b->realm) 635202613Sdes free(b->realm); 636202613Sdes if (b->qop) 637202613Sdes free(b->qop); 638202613Sdes if (b->nonce) 639202613Sdes free(b->nonce); 640202613Sdes if (b->opaque) 641202613Sdes free(b->opaque); 642202613Sdes if (b->algo) 643202613Sdes free(b->algo); 644202613Sdes init_http_auth_challenge(b); 645202613Sdes} 646202613Sdes 647202613Sdes/* Data holder for an array of challenges offered in an http response. */ 648202613Sdes#define MAX_CHALLENGES 10 649202613Sdestypedef struct { 650202613Sdes http_auth_challenge_t *challenges[MAX_CHALLENGES]; 651202613Sdes int count; /* Number of parsed challenges in the array */ 652202613Sdes int valid; /* We did parse an authenticate header */ 653202613Sdes} http_auth_challenges_t; 654202613Sdes 655202613Sdesstatic void 656202613Sdesinit_http_auth_challenges(http_auth_challenges_t *cs) 657202613Sdes{ 658202613Sdes int i; 659202613Sdes for (i = 0; i < MAX_CHALLENGES; i++) 660202613Sdes cs->challenges[i] = NULL; 661202613Sdes cs->count = cs->valid = 0; 662202613Sdes} 663202613Sdes 664202613Sdesstatic void 665202613Sdesclean_http_auth_challenges(http_auth_challenges_t *cs) 666202613Sdes{ 667202613Sdes int i; 668202613Sdes /* We rely on non-zero pointers being allocated, not on the count */ 669202613Sdes for (i = 0; i < MAX_CHALLENGES; i++) { 670202613Sdes if (cs->challenges[i] != NULL) { 671202613Sdes clean_http_auth_challenge(cs->challenges[i]); 672202613Sdes free(cs->challenges[i]); 673202613Sdes } 674202613Sdes } 675202613Sdes init_http_auth_challenges(cs); 676202613Sdes} 677202613Sdes 678202613Sdes/* 679202613Sdes * Enumeration for lexical elements. Separators will be returned as their own 680202613Sdes * ascii value 681202613Sdes */ 682202613Sdestypedef enum {HTTPHL_WORD=256, HTTPHL_STRING=257, HTTPHL_END=258, 683202613Sdes HTTPHL_ERROR = 259} http_header_lex_t; 684202613Sdes 685202613Sdes/* 686202613Sdes * Determine what kind of token comes next and return possible value 687202613Sdes * in buf, which is supposed to have been allocated big enough by 688202613Sdes * caller. Advance input pointer and return element type. 689202613Sdes */ 690202613Sdesstatic int 691202613Sdeshttp_header_lex(const char **cpp, char *buf) 692202613Sdes{ 693202613Sdes size_t l; 694202613Sdes /* Eat initial whitespace */ 695202613Sdes *cpp += strspn(*cpp, " \t"); 696202613Sdes if (**cpp == 0) 697202613Sdes return (HTTPHL_END); 698202613Sdes 699202613Sdes /* Separator ? */ 700202613Sdes if (**cpp == ',' || **cpp == '=') 701202613Sdes return (*((*cpp)++)); 702202613Sdes 703202613Sdes /* String ? */ 704202613Sdes if (**cpp == '"') { 705202613Sdes *cpp = http_parse_headerstring(++*cpp, buf); 706202613Sdes if (*cpp == NULL) 707202613Sdes return (HTTPHL_ERROR); 708202613Sdes return (HTTPHL_STRING); 709202613Sdes } 710202613Sdes 711202613Sdes /* Read other token, until separator or whitespace */ 712202613Sdes l = strcspn(*cpp, " \t,="); 713202613Sdes memcpy(buf, *cpp, l); 714202613Sdes buf[l] = 0; 715202613Sdes *cpp += l; 716202613Sdes return (HTTPHL_WORD); 717202613Sdes} 718202613Sdes 719202613Sdes/* 720202613Sdes * Read challenges from http xxx-authenticate header and accumulate them 721202613Sdes * in the challenges list structure. 722202613Sdes * 723202613Sdes * Headers with multiple challenges are specified by rfc2617, but 724202613Sdes * servers (ie: squid) often send them in separate headers instead, 725202613Sdes * which in turn is forbidden by the http spec (multiple headers with 726202613Sdes * the same name are only allowed for pure comma-separated lists, see 727202613Sdes * rfc2616 sec 4.2). 728202613Sdes * 729202613Sdes * We support both approaches anyway 730202613Sdes */ 731202613Sdesstatic int 732202613Sdeshttp_parse_authenticate(const char *cp, http_auth_challenges_t *cs) 733202613Sdes{ 734202613Sdes int ret = -1; 735202613Sdes http_header_lex_t lex; 736202613Sdes char *key = malloc(strlen(cp) + 1); 737202613Sdes char *value = malloc(strlen(cp) + 1); 738202613Sdes char *buf = malloc(strlen(cp) + 1); 739202613Sdes 740202613Sdes if (key == NULL || value == NULL || buf == NULL) { 741202613Sdes fetch_syserr(); 742202613Sdes goto out; 743202613Sdes } 744202613Sdes 745202613Sdes /* In any case we've seen the header and we set the valid bit */ 746202613Sdes cs->valid = 1; 747202613Sdes 748202613Sdes /* Need word first */ 749202613Sdes lex = http_header_lex(&cp, key); 750202613Sdes if (lex != HTTPHL_WORD) 751202613Sdes goto out; 752202613Sdes 753202613Sdes /* Loop on challenges */ 754202613Sdes for (; cs->count < MAX_CHALLENGES; cs->count++) { 755202613Sdes cs->challenges[cs->count] = 756202613Sdes malloc(sizeof(http_auth_challenge_t)); 757202613Sdes if (cs->challenges[cs->count] == NULL) { 758202613Sdes fetch_syserr(); 759202613Sdes goto out; 760202613Sdes } 761202613Sdes init_http_auth_challenge(cs->challenges[cs->count]); 762202613Sdes if (!strcasecmp(key, "basic")) { 763202613Sdes cs->challenges[cs->count]->scheme = HTTPAS_BASIC; 764202613Sdes } else if (!strcasecmp(key, "digest")) { 765202613Sdes cs->challenges[cs->count]->scheme = HTTPAS_DIGEST; 766202613Sdes } else { 767202613Sdes cs->challenges[cs->count]->scheme = HTTPAS_UNKNOWN; 768202613Sdes /* 769202613Sdes * Continue parsing as basic or digest may 770202613Sdes * follow, and the syntax is the same for 771202613Sdes * all. We'll just ignore this one when 772202613Sdes * looking at the list 773202613Sdes */ 774202613Sdes } 775202613Sdes 776202613Sdes /* Loop on attributes */ 777202613Sdes for (;;) { 778202613Sdes /* Key */ 779202613Sdes lex = http_header_lex(&cp, key); 780202613Sdes if (lex != HTTPHL_WORD) 781202613Sdes goto out; 782202613Sdes 783202613Sdes /* Equal sign */ 784202613Sdes lex = http_header_lex(&cp, buf); 785202613Sdes if (lex != '=') 786202613Sdes goto out; 787202613Sdes 788202613Sdes /* Value */ 789202613Sdes lex = http_header_lex(&cp, value); 790202613Sdes if (lex != HTTPHL_WORD && lex != HTTPHL_STRING) 791202613Sdes goto out; 792202613Sdes 793202613Sdes if (!strcasecmp(key, "realm")) 794202613Sdes cs->challenges[cs->count]->realm = 795202613Sdes strdup(value); 796202613Sdes else if (!strcasecmp(key, "qop")) 797202613Sdes cs->challenges[cs->count]->qop = 798202613Sdes strdup(value); 799202613Sdes else if (!strcasecmp(key, "nonce")) 800202613Sdes cs->challenges[cs->count]->nonce = 801202613Sdes strdup(value); 802202613Sdes else if (!strcasecmp(key, "opaque")) 803202613Sdes cs->challenges[cs->count]->opaque = 804202613Sdes strdup(value); 805202613Sdes else if (!strcasecmp(key, "algorithm")) 806202613Sdes cs->challenges[cs->count]->algo = 807202613Sdes strdup(value); 808202613Sdes else if (!strcasecmp(key, "stale")) 809202613Sdes cs->challenges[cs->count]->stale = 810202613Sdes strcasecmp(value, "no"); 811202613Sdes /* Else ignore unknown attributes */ 812202613Sdes 813202613Sdes /* Comma or Next challenge or End */ 814202613Sdes lex = http_header_lex(&cp, key); 815202613Sdes /* 816202613Sdes * If we get a word here, this is the beginning of the 817202613Sdes * next challenge. Break the attributes loop 818202613Sdes */ 819202613Sdes if (lex == HTTPHL_WORD) 820202613Sdes break; 821202613Sdes 822202613Sdes if (lex == HTTPHL_END) { 823202613Sdes /* End while looking for ',' is normal exit */ 824202613Sdes cs->count++; 825202613Sdes ret = 0; 826202613Sdes goto out; 827202613Sdes } 828202613Sdes /* Anything else is an error */ 829202613Sdes if (lex != ',') 830202613Sdes goto out; 831202613Sdes 832202613Sdes } /* End attributes loop */ 833202613Sdes } /* End challenge loop */ 834202613Sdes 835202613Sdes /* 836202613Sdes * Challenges max count exceeded. This really can't happen 837202613Sdes * with normal data, something's fishy -> error 838202613Sdes */ 839202613Sdes 840202613Sdesout: 841202613Sdes if (key) 842202613Sdes free(key); 843202613Sdes if (value) 844202613Sdes free(value); 845202613Sdes if (buf) 846202613Sdes free(buf); 847202613Sdes return (ret); 848202613Sdes} 849202613Sdes 850202613Sdes 85163012Sdes/* 85263012Sdes * Parse a last-modified header 85363012Sdes */ 85463716Sdesstatic int 855174588Sdeshttp_parse_mtime(const char *p, time_t *mtime) 85663012Sdes{ 85790267Sdes char locale[64], *r; 85890267Sdes struct tm tm; 85963012Sdes 860109967Sdes strncpy(locale, setlocale(LC_TIME, NULL), sizeof(locale)); 86190267Sdes setlocale(LC_TIME, "C"); 86290267Sdes r = strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm); 86390267Sdes /* XXX should add support for date-2 and date-3 */ 86490267Sdes setlocale(LC_TIME, locale); 86590267Sdes if (r == NULL) 86690267Sdes return (-1); 86790267Sdes DEBUG(fprintf(stderr, "last modified: [%04d-%02d-%02d " 86888769Sdes "%02d:%02d:%02d]\n", 86963012Sdes tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 87063012Sdes tm.tm_hour, tm.tm_min, tm.tm_sec)); 87190267Sdes *mtime = timegm(&tm); 87290267Sdes return (0); 87363012Sdes} 87463012Sdes 87563012Sdes/* 87663012Sdes * Parse a content-length header 87763012Sdes */ 87863716Sdesstatic int 879174588Sdeshttp_parse_length(const char *p, off_t *length) 88063012Sdes{ 88190267Sdes off_t len; 88290267Sdes 883174761Sdes for (len = 0; *p && isdigit((unsigned char)*p); ++p) 88490267Sdes len = len * 10 + (*p - '0'); 88590267Sdes if (*p) 88690267Sdes return (-1); 88790267Sdes DEBUG(fprintf(stderr, "content length: [%lld]\n", 88890267Sdes (long long)len)); 88990267Sdes *length = len; 89090267Sdes return (0); 89163012Sdes} 89263012Sdes 89363012Sdes/* 89463012Sdes * Parse a content-range header 89563012Sdes */ 89663716Sdesstatic int 897174588Sdeshttp_parse_range(const char *p, off_t *offset, off_t *length, off_t *size) 89863012Sdes{ 89990267Sdes off_t first, last, len; 90063716Sdes 90190267Sdes if (strncasecmp(p, "bytes ", 6) != 0) 90290267Sdes return (-1); 903125696Sdes p += 6; 904125696Sdes if (*p == '*') { 905125696Sdes first = last = -1; 906125696Sdes ++p; 907125696Sdes } else { 908174761Sdes for (first = 0; *p && isdigit((unsigned char)*p); ++p) 909125696Sdes first = first * 10 + *p - '0'; 910125696Sdes if (*p != '-') 911125696Sdes return (-1); 912174761Sdes for (last = 0, ++p; *p && isdigit((unsigned char)*p); ++p) 913125696Sdes last = last * 10 + *p - '0'; 914125696Sdes } 91590267Sdes if (first > last || *p != '/') 91690267Sdes return (-1); 917174761Sdes for (len = 0, ++p; *p && isdigit((unsigned char)*p); ++p) 91890267Sdes len = len * 10 + *p - '0'; 91990267Sdes if (*p || len < last - first + 1) 92090267Sdes return (-1); 921125696Sdes if (first == -1) { 922125696Sdes DEBUG(fprintf(stderr, "content range: [*/%lld]\n", 923125696Sdes (long long)len)); 924125696Sdes *length = 0; 925125696Sdes } else { 926125696Sdes DEBUG(fprintf(stderr, "content range: [%lld-%lld/%lld]\n", 927125696Sdes (long long)first, (long long)last, (long long)len)); 928125696Sdes *length = last - first + 1; 929125696Sdes } 93090267Sdes *offset = first; 93190267Sdes *size = len; 93290267Sdes return (0); 93363012Sdes} 93463012Sdes 93590267Sdes 93663012Sdes/***************************************************************************** 93763012Sdes * Helper functions for authorization 93863012Sdes */ 93963012Sdes 94063012Sdes/* 94137608Sdes * Base64 encoding 94237608Sdes */ 94362965Sdesstatic char * 944174588Sdeshttp_base64(const char *src) 94537608Sdes{ 94690267Sdes static const char base64[] = 94790267Sdes "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 94890267Sdes "abcdefghijklmnopqrstuvwxyz" 94990267Sdes "0123456789+/"; 95090267Sdes char *str, *dst; 95190267Sdes size_t l; 95290267Sdes int t, r; 95362965Sdes 95490267Sdes l = strlen(src); 955133280Sdes if ((str = malloc(((l + 2) / 3) * 4 + 1)) == NULL) 95690267Sdes return (NULL); 95790267Sdes dst = str; 95890267Sdes r = 0; 95937608Sdes 96090267Sdes while (l >= 3) { 96190267Sdes t = (src[0] << 16) | (src[1] << 8) | src[2]; 96290267Sdes dst[0] = base64[(t >> 18) & 0x3f]; 96390267Sdes dst[1] = base64[(t >> 12) & 0x3f]; 96490267Sdes dst[2] = base64[(t >> 6) & 0x3f]; 96590267Sdes dst[3] = base64[(t >> 0) & 0x3f]; 96690267Sdes src += 3; l -= 3; 96790267Sdes dst += 4; r += 4; 96890267Sdes } 96937608Sdes 97090267Sdes switch (l) { 97190267Sdes case 2: 97290267Sdes t = (src[0] << 16) | (src[1] << 8); 97390267Sdes dst[0] = base64[(t >> 18) & 0x3f]; 97490267Sdes dst[1] = base64[(t >> 12) & 0x3f]; 97590267Sdes dst[2] = base64[(t >> 6) & 0x3f]; 97690267Sdes dst[3] = '='; 97790267Sdes dst += 4; 97890267Sdes r += 4; 97990267Sdes break; 98090267Sdes case 1: 98190267Sdes t = src[0] << 16; 98290267Sdes dst[0] = base64[(t >> 18) & 0x3f]; 98390267Sdes dst[1] = base64[(t >> 12) & 0x3f]; 98490267Sdes dst[2] = dst[3] = '='; 98590267Sdes dst += 4; 98690267Sdes r += 4; 98790267Sdes break; 98890267Sdes case 0: 98990267Sdes break; 99090267Sdes } 99190267Sdes 99290267Sdes *dst = 0; 99390267Sdes return (str); 99437608Sdes} 99537608Sdes 996202613Sdes 99737608Sdes/* 998202613Sdes * Extract authorization parameters from environment value. 999202613Sdes * The value is like scheme:realm:user:pass 1000202613Sdes */ 1001202613Sdestypedef struct { 1002202613Sdes char *scheme; 1003202613Sdes char *realm; 1004202613Sdes char *user; 1005202613Sdes char *password; 1006202613Sdes} http_auth_params_t; 1007202613Sdes 1008202613Sdesstatic void 1009202613Sdesinit_http_auth_params(http_auth_params_t *s) 1010202613Sdes{ 1011202613Sdes s->scheme = s->realm = s->user = s->password = 0; 1012202613Sdes} 1013202613Sdes 1014202613Sdesstatic void 1015202613Sdesclean_http_auth_params(http_auth_params_t *s) 1016202613Sdes{ 1017202613Sdes if (s->scheme) 1018202613Sdes free(s->scheme); 1019202613Sdes if (s->realm) 1020202613Sdes free(s->realm); 1021202613Sdes if (s->user) 1022202613Sdes free(s->user); 1023202613Sdes if (s->password) 1024202613Sdes free(s->password); 1025202613Sdes init_http_auth_params(s); 1026202613Sdes} 1027202613Sdes 1028202613Sdesstatic int 1029202613Sdeshttp_authfromenv(const char *p, http_auth_params_t *parms) 1030202613Sdes{ 1031202613Sdes int ret = -1; 1032202613Sdes char *v, *ve; 1033202613Sdes char *str = strdup(p); 1034202613Sdes 1035202613Sdes if (str == NULL) { 1036202613Sdes fetch_syserr(); 1037202613Sdes return (-1); 1038202613Sdes } 1039202613Sdes v = str; 1040202613Sdes 1041202613Sdes if ((ve = strchr(v, ':')) == NULL) 1042202613Sdes goto out; 1043202613Sdes 1044202613Sdes *ve = 0; 1045202613Sdes if ((parms->scheme = strdup(v)) == NULL) { 1046202613Sdes fetch_syserr(); 1047202613Sdes goto out; 1048202613Sdes } 1049202613Sdes v = ve + 1; 1050202613Sdes 1051202613Sdes if ((ve = strchr(v, ':')) == NULL) 1052202613Sdes goto out; 1053202613Sdes 1054202613Sdes *ve = 0; 1055202613Sdes if ((parms->realm = strdup(v)) == NULL) { 1056202613Sdes fetch_syserr(); 1057202613Sdes goto out; 1058202613Sdes } 1059202613Sdes v = ve + 1; 1060202613Sdes 1061202613Sdes if ((ve = strchr(v, ':')) == NULL) 1062202613Sdes goto out; 1063202613Sdes 1064202613Sdes *ve = 0; 1065202613Sdes if ((parms->user = strdup(v)) == NULL) { 1066202613Sdes fetch_syserr(); 1067202613Sdes goto out; 1068202613Sdes } 1069202613Sdes v = ve + 1; 1070202613Sdes 1071202613Sdes 1072202613Sdes if ((parms->password = strdup(v)) == NULL) { 1073202613Sdes fetch_syserr(); 1074202613Sdes goto out; 1075202613Sdes } 1076202613Sdes ret = 0; 1077202613Sdesout: 1078202613Sdes if (ret == -1) 1079202613Sdes clean_http_auth_params(parms); 1080202613Sdes if (str) 1081202613Sdes free(str); 1082202613Sdes return (ret); 1083202613Sdes} 1084202613Sdes 1085202613Sdes 1086202613Sdes/* 1087202613Sdes * Digest response: the code to compute the digest is taken from the 1088202613Sdes * sample implementation in RFC2616 1089202613Sdes */ 1090202613Sdes#define IN 1091202613Sdes#define OUT 1092202613Sdes 1093202613Sdes#define HASHLEN 16 1094202613Sdestypedef char HASH[HASHLEN]; 1095202613Sdes#define HASHHEXLEN 32 1096202613Sdestypedef char HASHHEX[HASHHEXLEN+1]; 1097202613Sdes 1098202613Sdesstatic const char *hexchars = "0123456789abcdef"; 1099202613Sdesstatic void 1100202613SdesCvtHex(IN HASH Bin, OUT HASHHEX Hex) 1101202613Sdes{ 1102202613Sdes unsigned short i; 1103202613Sdes unsigned char j; 1104202613Sdes 1105202613Sdes for (i = 0; i < HASHLEN; i++) { 1106202613Sdes j = (Bin[i] >> 4) & 0xf; 1107202613Sdes Hex[i*2] = hexchars[j]; 1108202613Sdes j = Bin[i] & 0xf; 1109202613Sdes Hex[i*2+1] = hexchars[j]; 1110202613Sdes }; 1111202613Sdes Hex[HASHHEXLEN] = '\0'; 1112202613Sdes}; 1113202613Sdes 1114202613Sdes/* calculate H(A1) as per spec */ 1115202613Sdesstatic void 1116202613SdesDigestCalcHA1( 1117202613Sdes IN char * pszAlg, 1118202613Sdes IN char * pszUserName, 1119202613Sdes IN char * pszRealm, 1120202613Sdes IN char * pszPassword, 1121202613Sdes IN char * pszNonce, 1122202613Sdes IN char * pszCNonce, 1123202613Sdes OUT HASHHEX SessionKey 1124202613Sdes ) 1125202613Sdes{ 1126202613Sdes MD5_CTX Md5Ctx; 1127202613Sdes HASH HA1; 1128202613Sdes 1129202613Sdes MD5Init(&Md5Ctx); 1130202613Sdes MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName)); 1131202613Sdes MD5Update(&Md5Ctx, ":", 1); 1132202613Sdes MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm)); 1133202613Sdes MD5Update(&Md5Ctx, ":", 1); 1134202613Sdes MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword)); 1135202613Sdes MD5Final(HA1, &Md5Ctx); 1136202613Sdes if (strcasecmp(pszAlg, "md5-sess") == 0) { 1137202613Sdes 1138202613Sdes MD5Init(&Md5Ctx); 1139202613Sdes MD5Update(&Md5Ctx, HA1, HASHLEN); 1140202613Sdes MD5Update(&Md5Ctx, ":", 1); 1141202613Sdes MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce)); 1142202613Sdes MD5Update(&Md5Ctx, ":", 1); 1143202613Sdes MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce)); 1144202613Sdes MD5Final(HA1, &Md5Ctx); 1145202613Sdes }; 1146202613Sdes CvtHex(HA1, SessionKey); 1147202613Sdes} 1148202613Sdes 1149202613Sdes/* calculate request-digest/response-digest as per HTTP Digest spec */ 1150202613Sdesstatic void 1151202613SdesDigestCalcResponse( 1152202613Sdes IN HASHHEX HA1, /* H(A1) */ 1153202613Sdes IN char * pszNonce, /* nonce from server */ 1154202613Sdes IN char * pszNonceCount, /* 8 hex digits */ 1155202613Sdes IN char * pszCNonce, /* client nonce */ 1156202613Sdes IN char * pszQop, /* qop-value: "", "auth", "auth-int" */ 1157202613Sdes IN char * pszMethod, /* method from the request */ 1158202613Sdes IN char * pszDigestUri, /* requested URL */ 1159202613Sdes IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */ 1160202613Sdes OUT HASHHEX Response /* request-digest or response-digest */ 1161202613Sdes ) 1162202613Sdes{ 1163202613Sdes/* DEBUG(fprintf(stderr, 1164202613Sdes "Calc: HA1[%s] Nonce[%s] qop[%s] method[%s] URI[%s]\n", 1165202613Sdes HA1, pszNonce, pszQop, pszMethod, pszDigestUri));*/ 1166202613Sdes MD5_CTX Md5Ctx; 1167202613Sdes HASH HA2; 1168202613Sdes HASH RespHash; 1169202613Sdes HASHHEX HA2Hex; 1170202613Sdes 1171202613Sdes // calculate H(A2) 1172202613Sdes MD5Init(&Md5Ctx); 1173202613Sdes MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod)); 1174202613Sdes MD5Update(&Md5Ctx, ":", 1); 1175202613Sdes MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri)); 1176202613Sdes if (strcasecmp(pszQop, "auth-int") == 0) { 1177202613Sdes MD5Update(&Md5Ctx, ":", 1); 1178202613Sdes MD5Update(&Md5Ctx, HEntity, HASHHEXLEN); 1179202613Sdes }; 1180202613Sdes MD5Final(HA2, &Md5Ctx); 1181202613Sdes CvtHex(HA2, HA2Hex); 1182202613Sdes 1183202613Sdes // calculate response 1184202613Sdes MD5Init(&Md5Ctx); 1185202613Sdes MD5Update(&Md5Ctx, HA1, HASHHEXLEN); 1186202613Sdes MD5Update(&Md5Ctx, ":", 1); 1187202613Sdes MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce)); 1188202613Sdes MD5Update(&Md5Ctx, ":", 1); 1189202613Sdes if (*pszQop) { 1190202613Sdes MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount)); 1191202613Sdes MD5Update(&Md5Ctx, ":", 1); 1192202613Sdes MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce)); 1193202613Sdes MD5Update(&Md5Ctx, ":", 1); 1194202613Sdes MD5Update(&Md5Ctx, pszQop, strlen(pszQop)); 1195202613Sdes MD5Update(&Md5Ctx, ":", 1); 1196202613Sdes }; 1197202613Sdes MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN); 1198202613Sdes MD5Final(RespHash, &Md5Ctx); 1199202613Sdes CvtHex(RespHash, Response); 1200202613Sdes} 1201202613Sdes 1202202613Sdes/* 1203202613Sdes * Generate/Send a Digest authorization header 1204202613Sdes * This looks like: [Proxy-]Authorization: credentials 1205202613Sdes * 1206202613Sdes * credentials = "Digest" digest-response 1207202613Sdes * digest-response = 1#( username | realm | nonce | digest-uri 1208202613Sdes * | response | [ algorithm ] | [cnonce] | 1209202613Sdes * [opaque] | [message-qop] | 1210202613Sdes * [nonce-count] | [auth-param] ) 1211202613Sdes * username = "username" "=" username-value 1212202613Sdes * username-value = quoted-string 1213202613Sdes * digest-uri = "uri" "=" digest-uri-value 1214202613Sdes * digest-uri-value = request-uri ; As specified by HTTP/1.1 1215202613Sdes * message-qop = "qop" "=" qop-value 1216202613Sdes * cnonce = "cnonce" "=" cnonce-value 1217202613Sdes * cnonce-value = nonce-value 1218202613Sdes * nonce-count = "nc" "=" nc-value 1219202613Sdes * nc-value = 8LHEX 1220202613Sdes * response = "response" "=" request-digest 1221202613Sdes * request-digest = <"> 32LHEX <"> 1222202613Sdes */ 1223202613Sdesstatic int 1224202613Sdeshttp_digest_auth(conn_t *conn, const char *hdr, http_auth_challenge_t *c, 1225202613Sdes http_auth_params_t *parms, struct url *url) 1226202613Sdes{ 1227202613Sdes int r; 1228202613Sdes char noncecount[10]; 1229202613Sdes char cnonce[40]; 1230202613Sdes char *options = 0; 1231202613Sdes 1232202613Sdes if (!c->realm || !c->nonce) { 1233202613Sdes DEBUG(fprintf(stderr, "realm/nonce not set in challenge\n")); 1234202613Sdes return(-1); 1235202613Sdes } 1236202613Sdes if (!c->algo) 1237202613Sdes c->algo = strdup(""); 1238202613Sdes 1239202613Sdes if (asprintf(&options, "%s%s%s%s", 1240202613Sdes *c->algo? ",algorithm=" : "", c->algo, 1241202613Sdes c->opaque? ",opaque=" : "", c->opaque?c->opaque:"")== -1) 1242202613Sdes return (-1); 1243202613Sdes 1244202613Sdes if (!c->qop) { 1245202613Sdes c->qop = strdup(""); 1246202613Sdes *noncecount = 0; 1247202613Sdes *cnonce = 0; 1248202613Sdes } else { 1249202613Sdes c->nc++; 1250202613Sdes sprintf(noncecount, "%08x", c->nc); 1251202613Sdes /* We don't try very hard with the cnonce ... */ 1252202613Sdes sprintf(cnonce, "%x%lx", getpid(), (unsigned long)time(0)); 1253202613Sdes } 1254202613Sdes 1255202613Sdes HASHHEX HA1; 1256202613Sdes DigestCalcHA1(c->algo, parms->user, c->realm, 1257202613Sdes parms->password, c->nonce, cnonce, HA1); 1258202613Sdes DEBUG(fprintf(stderr, "HA1: [%s]\n", HA1)); 1259202613Sdes HASHHEX digest; 1260202613Sdes DigestCalcResponse(HA1, c->nonce, noncecount, cnonce, c->qop, 1261202613Sdes "GET", url->doc, "", digest); 1262202613Sdes 1263202613Sdes if (c->qop[0]) { 1264202613Sdes r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\"," 1265202613Sdes "nonce=\"%s\",uri=\"%s\",response=\"%s\"," 1266202613Sdes "qop=\"auth\", cnonce=\"%s\", nc=%s%s", 1267202613Sdes hdr, parms->user, c->realm, 1268202613Sdes c->nonce, url->doc, digest, 1269202613Sdes cnonce, noncecount, options); 1270202613Sdes } else { 1271202613Sdes r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\"," 1272202613Sdes "nonce=\"%s\",uri=\"%s\",response=\"%s\"%s", 1273202613Sdes hdr, parms->user, c->realm, 1274202613Sdes c->nonce, url->doc, digest, options); 1275202613Sdes } 1276202613Sdes if (options) 1277202613Sdes free(options); 1278202613Sdes return (r); 1279202613Sdes} 1280202613Sdes 1281202613Sdes/* 128237608Sdes * Encode username and password 128337608Sdes */ 128462965Sdesstatic int 1285174588Sdeshttp_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd) 128637608Sdes{ 128790267Sdes char *upw, *auth; 128890267Sdes int r; 128937608Sdes 1290202613Sdes DEBUG(fprintf(stderr, "basic: usr: [%s]\n", usr)); 1291202613Sdes DEBUG(fprintf(stderr, "basic: pwd: [%s]\n", pwd)); 129290267Sdes if (asprintf(&upw, "%s:%s", usr, pwd) == -1) 129390267Sdes return (-1); 1294174588Sdes auth = http_base64(upw); 129590267Sdes free(upw); 129690267Sdes if (auth == NULL) 129790267Sdes return (-1); 1298174588Sdes r = http_cmd(conn, "%s: Basic %s", hdr, auth); 129990267Sdes free(auth); 130090267Sdes return (r); 130162965Sdes} 130262965Sdes 130362965Sdes/* 1304202613Sdes * Chose the challenge to answer and call the appropriate routine to 1305202613Sdes * produce the header. 130662965Sdes */ 130762965Sdesstatic int 1308202613Sdeshttp_authorize(conn_t *conn, const char *hdr, http_auth_challenges_t *cs, 1309202613Sdes http_auth_params_t *parms, struct url *url) 131062965Sdes{ 1311202613Sdes http_auth_challenge_t *basic = NULL; 1312202613Sdes http_auth_challenge_t *digest = NULL; 1313202613Sdes int i; 131462965Sdes 1315202613Sdes /* If user or pass are null we're not happy */ 1316202613Sdes if (!parms->user || !parms->password) { 1317202613Sdes DEBUG(fprintf(stderr, "NULL usr or pass\n")); 1318202613Sdes return (-1); 131990267Sdes } 1320202613Sdes 1321202613Sdes /* Look for a Digest and a Basic challenge */ 1322202613Sdes for (i = 0; i < cs->count; i++) { 1323202613Sdes if (cs->challenges[i]->scheme == HTTPAS_BASIC) 1324202613Sdes basic = cs->challenges[i]; 1325202613Sdes if (cs->challenges[i]->scheme == HTTPAS_DIGEST) 1326202613Sdes digest = cs->challenges[i]; 1327202613Sdes } 1328202613Sdes 1329202613Sdes /* Error if "Digest" was specified and there is no Digest challenge */ 1330202613Sdes if (!digest && (parms->scheme && 1331202613Sdes !strcasecmp(parms->scheme, "digest"))) { 1332202613Sdes DEBUG(fprintf(stderr, 1333202613Sdes "Digest auth in env, not supported by peer\n")); 1334202613Sdes return (-1); 1335202613Sdes } 1336202613Sdes /* 1337202613Sdes * If "basic" was specified in the environment, or there is no Digest 1338202613Sdes * challenge, do the basic thing. Don't need a challenge for this, 1339202613Sdes * so no need to check basic!=NULL 1340202613Sdes */ 1341202613Sdes if (!digest || (parms->scheme && !strcasecmp(parms->scheme,"basic"))) 1342202613Sdes return (http_basic_auth(conn,hdr,parms->user,parms->password)); 1343202613Sdes 1344202613Sdes /* Else, prefer digest. We just checked that it's not NULL */ 1345202613Sdes return (http_digest_auth(conn, hdr, digest, parms, url)); 134637608Sdes} 134737608Sdes 134863012Sdes/***************************************************************************** 134963012Sdes * Helper functions for connecting to a server or proxy 135063012Sdes */ 135163012Sdes 135237608Sdes/* 135390267Sdes * Connect to the correct HTTP server or proxy. 135463012Sdes */ 135597856Sdesstatic conn_t * 1356174588Sdeshttp_connect(struct url *URL, struct url *purl, const char *flags) 135763012Sdes{ 135897856Sdes conn_t *conn; 135990267Sdes int verbose; 1360141958Skbyanc int af, val; 136190267Sdes 136263012Sdes#ifdef INET6 136390267Sdes af = AF_UNSPEC; 136460737Sume#else 136590267Sdes af = AF_INET; 136660737Sume#endif 136790267Sdes 136890267Sdes verbose = CHECK_FLAG('v'); 136990267Sdes if (CHECK_FLAG('4')) 137090267Sdes af = AF_INET; 137167043Sdes#ifdef INET6 137290267Sdes else if (CHECK_FLAG('6')) 137390267Sdes af = AF_INET6; 137467043Sdes#endif 137567043Sdes 137697868Sdes if (purl && strcasecmp(URL->scheme, SCHEME_HTTPS) != 0) { 137790267Sdes URL = purl; 137890267Sdes } else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0) { 137990267Sdes /* can't talk http to an ftp server */ 138090267Sdes /* XXX should set an error code */ 138197856Sdes return (NULL); 138290267Sdes } 138390267Sdes 1384174588Sdes if ((conn = fetch_connect(URL->host, URL->port, af, verbose)) == NULL) 1385174588Sdes /* fetch_connect() has already set an error code */ 138697856Sdes return (NULL); 138797868Sdes if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0 && 1388174588Sdes fetch_ssl(conn, verbose) == -1) { 1389174588Sdes fetch_close(conn); 139097891Sdes /* grrr */ 139197891Sdes errno = EAUTH; 1392174588Sdes fetch_syserr(); 139397868Sdes return (NULL); 139497868Sdes } 1395141958Skbyanc 1396141958Skbyanc val = 1; 1397141958Skbyanc setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val)); 1398141958Skbyanc 139997856Sdes return (conn); 140067043Sdes} 140167043Sdes 140267043Sdesstatic struct url * 1403174752Sdeshttp_get_proxy(struct url * url, const char *flags) 140467043Sdes{ 140590267Sdes struct url *purl; 140690267Sdes char *p; 140790267Sdes 1408112797Sdes if (flags != NULL && strchr(flags, 'd') != NULL) 1409112081Sdes return (NULL); 1410174752Sdes if (fetch_no_proxy_match(url->host)) 1411174752Sdes return (NULL); 141290267Sdes if (((p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) && 1413149414Sdes *p && (purl = fetchParseURL(p))) { 141490267Sdes if (!*purl->scheme) 141590267Sdes strcpy(purl->scheme, SCHEME_HTTP); 141690267Sdes if (!purl->port) 1417174588Sdes purl->port = fetch_default_proxy_port(purl->scheme); 141890267Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 141990267Sdes return (purl); 142090267Sdes fetchFreeURL(purl); 142190267Sdes } 142290267Sdes return (NULL); 142360376Sdes} 142460376Sdes 142588771Sdesstatic void 1426174588Sdeshttp_print_html(FILE *out, FILE *in) 142788771Sdes{ 142890267Sdes size_t len; 142990267Sdes char *line, *p, *q; 143090267Sdes int comment, tag; 143188771Sdes 143290267Sdes comment = tag = 0; 143390267Sdes while ((line = fgetln(in, &len)) != NULL) { 1434174761Sdes while (len && isspace((unsigned char)line[len - 1])) 143590267Sdes --len; 143690267Sdes for (p = q = line; q < line + len; ++q) { 143790267Sdes if (comment && *q == '-') { 143890267Sdes if (q + 2 < line + len && 143990267Sdes strcmp(q, "-->") == 0) { 144090267Sdes tag = comment = 0; 144190267Sdes q += 2; 144290267Sdes } 144390267Sdes } else if (tag && !comment && *q == '>') { 144490267Sdes p = q + 1; 144590267Sdes tag = 0; 144690267Sdes } else if (!tag && *q == '<') { 144790267Sdes if (q > p) 144890267Sdes fwrite(p, q - p, 1, out); 144990267Sdes tag = 1; 145090267Sdes if (q + 3 < line + len && 145190267Sdes strcmp(q, "<!--") == 0) { 145290267Sdes comment = 1; 145390267Sdes q += 3; 145490267Sdes } 145590267Sdes } 145688771Sdes } 145790267Sdes if (!tag && q > p) 145890267Sdes fwrite(p, q - p, 1, out); 145990267Sdes fputc('\n', out); 146088771Sdes } 146188771Sdes} 146288771Sdes 146390267Sdes 146463012Sdes/***************************************************************************** 146563012Sdes * Core 146660954Sdes */ 146760954Sdes 146860954Sdes/* 146963012Sdes * Send a request and process the reply 147097866Sdes * 147197866Sdes * XXX This function is way too long, the do..while loop should be split 147297866Sdes * XXX off into a separate function. 147360376Sdes */ 147467043SdesFILE * 1475174588Sdeshttp_request(struct url *URL, const char *op, struct url_stat *us, 1476202613Sdes struct url *purl, const char *flags) 147760376Sdes{ 1478186124Smurray char timebuf[80]; 1479186124Smurray char hbuf[MAXHOSTNAMELEN + 7], *host; 148097856Sdes conn_t *conn; 148190267Sdes struct url *url, *new; 1482202613Sdes int chunked, direct, ims, noredirect, verbose; 1483143049Skbyanc int e, i, n, val; 148490267Sdes off_t offset, clength, length, size; 148590267Sdes time_t mtime; 148690267Sdes const char *p; 148790267Sdes FILE *f; 148890267Sdes hdr_t h; 1489186124Smurray struct tm *timestruct; 1490202613Sdes http_headerbuf_t headerbuf; 1491202613Sdes http_auth_challenges_t server_challenges; 1492202613Sdes http_auth_challenges_t proxy_challenges; 149363012Sdes 1494202613Sdes /* The following calls don't allocate anything */ 1495202613Sdes init_http_headerbuf(&headerbuf); 1496202613Sdes init_http_auth_challenges(&server_challenges); 1497202613Sdes init_http_auth_challenges(&proxy_challenges); 1498202613Sdes 149990267Sdes direct = CHECK_FLAG('d'); 150090267Sdes noredirect = CHECK_FLAG('A'); 150190267Sdes verbose = CHECK_FLAG('v'); 1502186124Smurray ims = CHECK_FLAG('i'); 150360737Sume 150490267Sdes if (direct && purl) { 150590267Sdes fetchFreeURL(purl); 150690267Sdes purl = NULL; 150790267Sdes } 150863716Sdes 150990267Sdes /* try the provided URL first */ 151090267Sdes url = URL; 151163012Sdes 151290267Sdes /* if the A flag is set, we only get one try */ 151390267Sdes n = noredirect ? 1 : MAX_REDIRECT; 151490267Sdes i = 0; 151563012Sdes 151698422Sdes e = HTTP_PROTOCOL_ERROR; 151790267Sdes do { 151890267Sdes new = NULL; 151990267Sdes chunked = 0; 152090267Sdes offset = 0; 152190267Sdes clength = -1; 152290267Sdes length = -1; 152390267Sdes size = -1; 152490267Sdes mtime = 0; 152590267Sdes 152690267Sdes /* check port */ 152790267Sdes if (!url->port) 1528174588Sdes url->port = fetch_default_port(url->scheme); 152990267Sdes 153090267Sdes /* were we redirected to an FTP URL? */ 153190267Sdes if (purl == NULL && strcmp(url->scheme, SCHEME_FTP) == 0) { 153290267Sdes if (strcmp(op, "GET") == 0) 1533174588Sdes return (ftp_request(url, "RETR", us, purl, flags)); 153490267Sdes else if (strcmp(op, "HEAD") == 0) 1535174588Sdes return (ftp_request(url, "STAT", us, purl, flags)); 153690267Sdes } 153790267Sdes 153890267Sdes /* connect to server or proxy */ 1539174588Sdes if ((conn = http_connect(url, purl, flags)) == NULL) 154090267Sdes goto ouch; 154190267Sdes 154290267Sdes host = url->host; 154360737Sume#ifdef INET6 154490267Sdes if (strchr(url->host, ':')) { 154590267Sdes snprintf(hbuf, sizeof(hbuf), "[%s]", url->host); 154690267Sdes host = hbuf; 154790267Sdes } 154860737Sume#endif 1549174588Sdes if (url->port != fetch_default_port(url->scheme)) { 1550107372Sdes if (host != hbuf) { 1551107372Sdes strcpy(hbuf, host); 1552107372Sdes host = hbuf; 1553107372Sdes } 1554107372Sdes snprintf(hbuf + strlen(hbuf), 1555107372Sdes sizeof(hbuf) - strlen(hbuf), ":%d", url->port); 1556107372Sdes } 155737535Sdes 155890267Sdes /* send request */ 155990267Sdes if (verbose) 1560174588Sdes fetch_info("requesting %s://%s%s", 1561107372Sdes url->scheme, host, url->doc); 156290267Sdes if (purl) { 1563174588Sdes http_cmd(conn, "%s %s://%s%s HTTP/1.1", 1564107372Sdes op, url->scheme, host, url->doc); 156590267Sdes } else { 1566174588Sdes http_cmd(conn, "%s %s HTTP/1.1", 156790267Sdes op, url->doc); 156890267Sdes } 156937535Sdes 1570186124Smurray if (ims && url->ims_time) { 1571186124Smurray timestruct = gmtime((time_t *)&url->ims_time); 1572186124Smurray (void)strftime(timebuf, 80, "%a, %d %b %Y %T GMT", 1573186124Smurray timestruct); 1574186124Smurray if (verbose) 1575186124Smurray fetch_info("If-Modified-Since: %s", timebuf); 1576186124Smurray http_cmd(conn, "If-Modified-Since: %s", timebuf); 1577186124Smurray } 157890267Sdes /* virtual host */ 1579174588Sdes http_cmd(conn, "Host: %s", host); 158090267Sdes 1581202613Sdes /* 1582202613Sdes * Proxy authorization: we only send auth after we received 1583202613Sdes * a 407 error. We do not first try basic anyway (changed 1584202613Sdes * when support was added for digest-auth) 1585202613Sdes */ 1586202613Sdes if (purl && proxy_challenges.valid) { 1587202613Sdes http_auth_params_t aparams; 1588202613Sdes init_http_auth_params(&aparams); 1589202613Sdes if (*purl->user || *purl->pwd) { 1590202613Sdes aparams.user = purl->user ? 1591202613Sdes strdup(purl->user) : strdup(""); 1592202613Sdes aparams.password = purl->pwd? 1593202613Sdes strdup(purl->pwd) : strdup(""); 1594202613Sdes } else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL && 1595202613Sdes *p != '\0') { 1596202613Sdes if (http_authfromenv(p, &aparams) < 0) { 1597202613Sdes http_seterr(HTTP_NEED_PROXY_AUTH); 1598202613Sdes goto ouch; 1599202613Sdes } 1600202613Sdes } 1601202613Sdes http_authorize(conn, "Proxy-Authorization", 1602202613Sdes &proxy_challenges, &aparams, url); 1603202613Sdes clean_http_auth_params(&aparams); 160490267Sdes } 160590267Sdes 1606202613Sdes /* 1607202613Sdes * Server authorization: we never send "a priori" 1608202613Sdes * Basic auth, which used to be done if user/pass were 1609202613Sdes * set in the url. This would be weird because we'd send the 1610202613Sdes * password in the clear even if Digest is finally to be 1611202613Sdes * used (it would have made more sense for the 1612202613Sdes * pre-digest version to do this when Basic was specified 1613202613Sdes * in the environment) 1614202613Sdes */ 1615202613Sdes if (server_challenges.valid) { 1616202613Sdes http_auth_params_t aparams; 1617202613Sdes init_http_auth_params(&aparams); 1618202613Sdes if (*url->user || *url->pwd) { 1619202613Sdes aparams.user = url->user ? 1620202613Sdes strdup(url->user) : strdup(""); 1621202613Sdes aparams.password = url->pwd ? 1622202613Sdes strdup(url->pwd) : strdup(""); 1623202613Sdes } else if ((p = getenv("HTTP_AUTH")) != NULL && 1624202613Sdes *p != '\0') { 1625202613Sdes if (http_authfromenv(p, &aparams) < 0) { 1626202613Sdes http_seterr(HTTP_NEED_AUTH); 1627202613Sdes goto ouch; 1628202613Sdes } 1629202613Sdes } else if (fetchAuthMethod && 1630202613Sdes fetchAuthMethod(url) == 0) { 1631202613Sdes aparams.user = url->user ? 1632202613Sdes strdup(url->user) : strdup(""); 1633202613Sdes aparams.password = url->pwd ? 1634202613Sdes strdup(url->pwd) : strdup(""); 163590267Sdes } else { 1636174588Sdes http_seterr(HTTP_NEED_AUTH); 163790267Sdes goto ouch; 163890267Sdes } 1639202613Sdes http_authorize(conn, "Authorization", 1640202613Sdes &server_challenges, &aparams, url); 1641202613Sdes clean_http_auth_params(&aparams); 164290267Sdes } 164390267Sdes 164490267Sdes /* other headers */ 1645107372Sdes if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') { 1646107372Sdes if (strcasecmp(p, "auto") == 0) 1647174588Sdes http_cmd(conn, "Referer: %s://%s%s", 1648107372Sdes url->scheme, host, url->doc); 1649107372Sdes else 1650174588Sdes http_cmd(conn, "Referer: %s", p); 1651107372Sdes } 165290267Sdes if ((p = getenv("HTTP_USER_AGENT")) != NULL && *p != '\0') 1653174588Sdes http_cmd(conn, "User-Agent: %s", p); 165490267Sdes else 1655174588Sdes http_cmd(conn, "User-Agent: %s " _LIBFETCH_VER, getprogname()); 1656109693Sdes if (url->offset > 0) 1657174588Sdes http_cmd(conn, "Range: bytes=%lld-", (long long)url->offset); 1658174588Sdes http_cmd(conn, "Connection: close"); 1659174588Sdes http_cmd(conn, ""); 166090267Sdes 1661143049Skbyanc /* 1662143049Skbyanc * Force the queued request to be dispatched. Normally, one 1663143049Skbyanc * would do this with shutdown(2) but squid proxies can be 1664143049Skbyanc * configured to disallow such half-closed connections. To 1665143049Skbyanc * be compatible with such configurations, fiddle with socket 1666143049Skbyanc * options to force the pending data to be written. 1667143049Skbyanc */ 1668143049Skbyanc val = 0; 1669143049Skbyanc setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, 1670143049Skbyanc sizeof(val)); 1671143049Skbyanc val = 1; 1672143049Skbyanc setsockopt(conn->sd, IPPROTO_TCP, TCP_NODELAY, &val, 1673143049Skbyanc sizeof(val)); 1674143049Skbyanc 167590267Sdes /* get reply */ 1676174588Sdes switch (http_get_reply(conn)) { 167790267Sdes case HTTP_OK: 167890267Sdes case HTTP_PARTIAL: 1679186124Smurray case HTTP_NOT_MODIFIED: 168090267Sdes /* fine */ 168190267Sdes break; 168290267Sdes case HTTP_MOVED_PERM: 168390267Sdes case HTTP_MOVED_TEMP: 168490267Sdes case HTTP_SEE_OTHER: 168590267Sdes /* 1686125695Sdes * Not so fine, but we still have to read the 1687125695Sdes * headers to get the new location. 168890267Sdes */ 168990267Sdes break; 169090267Sdes case HTTP_NEED_AUTH: 1691202613Sdes if (server_challenges.valid) { 169290267Sdes /* 1693125695Sdes * We already sent out authorization code, 1694125695Sdes * so there's nothing more we can do. 169590267Sdes */ 1696174588Sdes http_seterr(conn->err); 169790267Sdes goto ouch; 169890267Sdes } 169990267Sdes /* try again, but send the password this time */ 170090267Sdes if (verbose) 1701174588Sdes fetch_info("server requires authorization"); 170290267Sdes break; 170390267Sdes case HTTP_NEED_PROXY_AUTH: 1704202613Sdes if (proxy_challenges.valid) { 1705202613Sdes /* 1706202613Sdes * We already sent our proxy 1707202613Sdes * authorization code, so there's 1708202613Sdes * nothing more we can do. */ 1709202613Sdes http_seterr(conn->err); 1710202613Sdes goto ouch; 1711202613Sdes } 1712202613Sdes /* try again, but send the password this time */ 1713202613Sdes if (verbose) 1714202613Sdes fetch_info("proxy requires authorization"); 1715202613Sdes break; 1716125696Sdes case HTTP_BAD_RANGE: 1717125696Sdes /* 1718125696Sdes * This can happen if we ask for 0 bytes because 1719125696Sdes * we already have the whole file. Consider this 1720125696Sdes * a success for now, and check sizes later. 1721125696Sdes */ 1722125696Sdes break; 172390267Sdes case HTTP_PROTOCOL_ERROR: 172490267Sdes /* fall through */ 172590267Sdes case -1: 1726174588Sdes fetch_syserr(); 172790267Sdes goto ouch; 172890267Sdes default: 1729174588Sdes http_seterr(conn->err); 173090267Sdes if (!verbose) 173190267Sdes goto ouch; 173290267Sdes /* fall through so we can get the full error message */ 173390267Sdes } 173490267Sdes 1735202613Sdes /* get headers. http_next_header expects one line readahead */ 1736202613Sdes if (fetch_getln(conn) == -1) { 1737202613Sdes fetch_syserr(); 1738202613Sdes goto ouch; 1739202613Sdes } 174090267Sdes do { 1741202613Sdes switch ((h = http_next_header(conn, &headerbuf, &p))) { 174290267Sdes case hdr_syserror: 1743174588Sdes fetch_syserr(); 174490267Sdes goto ouch; 174590267Sdes case hdr_error: 1746174588Sdes http_seterr(HTTP_PROTOCOL_ERROR); 174790267Sdes goto ouch; 174890267Sdes case hdr_content_length: 1749174588Sdes http_parse_length(p, &clength); 175090267Sdes break; 175190267Sdes case hdr_content_range: 1752174588Sdes http_parse_range(p, &offset, &length, &size); 175390267Sdes break; 175490267Sdes case hdr_last_modified: 1755174588Sdes http_parse_mtime(p, &mtime); 175690267Sdes break; 175790267Sdes case hdr_location: 175897856Sdes if (!HTTP_REDIRECT(conn->err)) 175990267Sdes break; 176090267Sdes if (new) 176190267Sdes free(new); 176290267Sdes if (verbose) 1763174588Sdes fetch_info("%d redirect to %s", conn->err, p); 176490267Sdes if (*p == '/') 176590267Sdes /* absolute path */ 176690267Sdes new = fetchMakeURL(url->scheme, url->host, url->port, p, 176790267Sdes url->user, url->pwd); 176890267Sdes else 176990267Sdes new = fetchParseURL(p); 177090267Sdes if (new == NULL) { 177190267Sdes /* XXX should set an error code */ 177290267Sdes DEBUG(fprintf(stderr, "failed to parse new URL\n")); 177390267Sdes goto ouch; 177490267Sdes } 177590267Sdes if (!*new->user && !*new->pwd) { 177690267Sdes strcpy(new->user, url->user); 177790267Sdes strcpy(new->pwd, url->pwd); 177890267Sdes } 177990267Sdes new->offset = url->offset; 178090267Sdes new->length = url->length; 178190267Sdes break; 178290267Sdes case hdr_transfer_encoding: 178390267Sdes /* XXX weak test*/ 178490267Sdes chunked = (strcasecmp(p, "chunked") == 0); 178590267Sdes break; 178690267Sdes case hdr_www_authenticate: 178797856Sdes if (conn->err != HTTP_NEED_AUTH) 178890267Sdes break; 1789209632Sdes if (http_parse_authenticate(p, &server_challenges)) 1790209632Sdes ++n; 179190267Sdes break; 1792202613Sdes case hdr_proxy_authenticate: 1793202613Sdes if (conn->err != HTTP_NEED_PROXY_AUTH) 1794202613Sdes break; 1795209632Sdes if (http_parse_authenticate(p, &proxy_challenges) == 0); 1796209632Sdes ++n; 1797202613Sdes break; 179890267Sdes case hdr_end: 179990267Sdes /* fall through */ 180090267Sdes case hdr_unknown: 180190267Sdes /* ignore */ 180290267Sdes break; 180390267Sdes } 180490267Sdes } while (h > hdr_end); 180590267Sdes 180690267Sdes /* we need to provide authentication */ 1807202613Sdes if (conn->err == HTTP_NEED_AUTH || 1808202613Sdes conn->err == HTTP_NEED_PROXY_AUTH) { 180998422Sdes e = conn->err; 1810202613Sdes if ((conn->err == HTTP_NEED_AUTH && 1811202613Sdes !server_challenges.valid) || 1812202613Sdes (conn->err == HTTP_NEED_PROXY_AUTH && 1813202613Sdes !proxy_challenges.valid)) { 1814202613Sdes /* 401/7 but no www/proxy-authenticate ?? */ 1815202613Sdes DEBUG(fprintf(stderr, "401/7 and no auth header\n")); 1816202613Sdes goto ouch; 1817202613Sdes } 1818174588Sdes fetch_close(conn); 181997856Sdes conn = NULL; 182090267Sdes continue; 182190267Sdes } 182290267Sdes 1823125696Sdes /* requested range not satisfiable */ 1824125696Sdes if (conn->err == HTTP_BAD_RANGE) { 1825125696Sdes if (url->offset == size && url->length == 0) { 1826125696Sdes /* asked for 0 bytes; fake it */ 1827125696Sdes offset = url->offset; 1828184222Sru clength = -1; 1829125696Sdes conn->err = HTTP_OK; 1830125696Sdes break; 1831125696Sdes } else { 1832174588Sdes http_seterr(conn->err); 1833125696Sdes goto ouch; 1834125696Sdes } 1835125696Sdes } 1836125696Sdes 1837104404Sru /* we have a hit or an error */ 1838186124Smurray if (conn->err == HTTP_OK 1839186124Smurray || conn->err == HTTP_NOT_MODIFIED 1840186124Smurray || conn->err == HTTP_PARTIAL 1841186124Smurray || HTTP_ERROR(conn->err)) 1842104404Sru break; 1843104404Sru 184490267Sdes /* all other cases: we got a redirect */ 184598422Sdes e = conn->err; 1846202613Sdes clean_http_auth_challenges(&server_challenges); 1847174588Sdes fetch_close(conn); 184897856Sdes conn = NULL; 184990267Sdes if (!new) { 185090267Sdes DEBUG(fprintf(stderr, "redirect with no new location\n")); 185190267Sdes break; 185290267Sdes } 185390267Sdes if (url != URL) 185490267Sdes fetchFreeURL(url); 185590267Sdes url = new; 185690267Sdes } while (++i < n); 185790267Sdes 185890267Sdes /* we failed, or ran out of retries */ 185997856Sdes if (conn == NULL) { 1860174588Sdes http_seterr(e); 186163012Sdes goto ouch; 186263012Sdes } 186360376Sdes 186490267Sdes DEBUG(fprintf(stderr, "offset %lld, length %lld," 186590267Sdes " size %lld, clength %lld\n", 186690267Sdes (long long)offset, (long long)length, 186790267Sdes (long long)size, (long long)clength)); 186860376Sdes 1869186124Smurray if (conn->err == HTTP_NOT_MODIFIED) { 1870186124Smurray http_seterr(HTTP_NOT_MODIFIED); 1871186124Smurray return (NULL); 1872186124Smurray } 1873186124Smurray 187490267Sdes /* check for inconsistencies */ 187590267Sdes if (clength != -1 && length != -1 && clength != length) { 1876174588Sdes http_seterr(HTTP_PROTOCOL_ERROR); 187763012Sdes goto ouch; 187863012Sdes } 187990267Sdes if (clength == -1) 188090267Sdes clength = length; 188190267Sdes if (clength != -1) 188290267Sdes length = offset + clength; 188390267Sdes if (length != -1 && size != -1 && length != size) { 1884174588Sdes http_seterr(HTTP_PROTOCOL_ERROR); 188563012Sdes goto ouch; 188690267Sdes } 188790267Sdes if (size == -1) 188890267Sdes size = length; 188960376Sdes 189090267Sdes /* fill in stats */ 189190267Sdes if (us) { 189290267Sdes us->size = size; 189390267Sdes us->atime = us->mtime = mtime; 189490267Sdes } 189563069Sdes 189690267Sdes /* too far? */ 1897109693Sdes if (URL->offset > 0 && offset > URL->offset) { 1898174588Sdes http_seterr(HTTP_PROTOCOL_ERROR); 189990267Sdes goto ouch; 190077238Sdes } 190160376Sdes 190290267Sdes /* report back real offset and size */ 190390267Sdes URL->offset = offset; 190490267Sdes URL->length = clength; 190537535Sdes 190690267Sdes /* wrap it up in a FILE */ 1907174588Sdes if ((f = http_funopen(conn, chunked)) == NULL) { 1908174588Sdes fetch_syserr(); 190990267Sdes goto ouch; 191090267Sdes } 191163716Sdes 191290267Sdes if (url != URL) 191390267Sdes fetchFreeURL(url); 191490267Sdes if (purl) 191590267Sdes fetchFreeURL(purl); 191663567Sdes 191797856Sdes if (HTTP_ERROR(conn->err)) { 1918174588Sdes http_print_html(stderr, f); 191990267Sdes fclose(f); 192090267Sdes f = NULL; 192190267Sdes } 1922202613Sdes clean_http_headerbuf(&headerbuf); 1923202613Sdes clean_http_auth_challenges(&server_challenges); 1924202613Sdes clean_http_auth_challenges(&proxy_challenges); 192590267Sdes return (f); 192688771Sdes 192790267Sdesouch: 192890267Sdes if (url != URL) 192990267Sdes fetchFreeURL(url); 193090267Sdes if (purl) 193190267Sdes fetchFreeURL(purl); 193297856Sdes if (conn != NULL) 1933174588Sdes fetch_close(conn); 1934202613Sdes clean_http_headerbuf(&headerbuf); 1935202613Sdes clean_http_auth_challenges(&server_challenges); 1936202613Sdes clean_http_auth_challenges(&proxy_challenges); 193790267Sdes return (NULL); 193863012Sdes} 193960189Sdes 194090267Sdes 194163012Sdes/***************************************************************************** 194263012Sdes * Entry points 194363012Sdes */ 194463012Sdes 194563012Sdes/* 194663340Sdes * Retrieve and stat a file by HTTP 194763340Sdes */ 194863340SdesFILE * 194975891SarchiefetchXGetHTTP(struct url *URL, struct url_stat *us, const char *flags) 195063340Sdes{ 1951174752Sdes return (http_request(URL, "GET", us, http_get_proxy(URL, flags), flags)); 195263340Sdes} 195363340Sdes 195463340Sdes/* 195563012Sdes * Retrieve a file by HTTP 195663012Sdes */ 195763012SdesFILE * 195875891SarchiefetchGetHTTP(struct url *URL, const char *flags) 195963012Sdes{ 196090267Sdes return (fetchXGetHTTP(URL, NULL, flags)); 196137535Sdes} 196237535Sdes 196363340Sdes/* 196463340Sdes * Store a file by HTTP 196563340Sdes */ 196637535SdesFILE * 196785093SdesfetchPutHTTP(struct url *URL __unused, const char *flags __unused) 196837535Sdes{ 196990267Sdes warnx("fetchPutHTTP(): not implemented"); 197090267Sdes return (NULL); 197137535Sdes} 197240975Sdes 197340975Sdes/* 197440975Sdes * Get an HTTP document's metadata 197540975Sdes */ 197640975Sdesint 197775891SarchiefetchStatHTTP(struct url *URL, struct url_stat *us, const char *flags) 197840975Sdes{ 197990267Sdes FILE *f; 198090267Sdes 1981174752Sdes f = http_request(URL, "HEAD", us, http_get_proxy(URL, flags), flags); 1982112081Sdes if (f == NULL) 198390267Sdes return (-1); 198490267Sdes fclose(f); 198590267Sdes return (0); 198640975Sdes} 198741989Sdes 198841989Sdes/* 198941989Sdes * List a directory 199041989Sdes */ 199141989Sdesstruct url_ent * 199285093SdesfetchListHTTP(struct url *url __unused, const char *flags __unused) 199341989Sdes{ 199490267Sdes warnx("fetchListHTTP(): not implemented"); 199590267Sdes return (NULL); 199641989Sdes} 1997