Blob Blame History Raw
/*
 * $LynxId: HTTP.c,v 1.175 2018/05/04 20:07:43 Elliot.Thomas Exp $
 *
 * HyperText Tranfer Protocol	- Client implementation		HTTP.c
 * ==========================
 * Modified:
 * 27 Jan 1994	PDM  Added Ari Luotonen's Fix for Reload when using proxy
 *		     servers.
 * 28 Apr 1997	AJL,FM Do Proxy Authorisation.
 */

#include <HTUtils.h>
#include <HTTP.h>
#include <LYUtils.h>

#ifdef USE_SSL
#include <HTNews.h>
#endif

#define HTTP_PORT   80
#define HTTPS_PORT  443
#define SNEWS_PORT  563

#define INIT_LINE_SIZE		1536	/* Start with line buffer this big */
#define LINE_EXTEND_THRESH	256	/* Minimum read size */
#define VERSION_LENGTH		20	/* for returned protocol version */

#include <HTParse.h>
#include <HTTCP.h>
#include <HTFormat.h>
#include <HTFile.h>
#include <HTAlert.h>
#include <HTMIME.h>
#include <HTML.h>
#include <HTInit.h>
#include <HTAABrow.h>
#include <HTAccess.h>		/* Are we using an HTTP gateway? */

#include <LYCookie.h>
#include <LYGlobalDefs.h>
#include <GridText.h>
#include <LYStrings.h>
#include <LYUtils.h>
#include <LYrcFile.h>
#include <LYLeaks.h>
#include <LYCurses.h>

#ifdef USE_SSL

#ifdef USE_OPENSSL_INCL
#include <openssl/x509v3.h>
#endif

#if defined(LIBRESSL_VERSION_NUMBER)
/* OpenSSL and LibreSSL version numbers do not correspond */

#if LIBRESSL_VERSION_NUMBER >= 0x2060100fL
#define SSL_set_no_TLSV1()		SSL_set_min_proto_version(handle, TLS1_1_VERSION)
#endif

#elif defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10100000L)

#define SSLEAY_VERSION_NUMBER		OPENSSL_VERSION_NUMBER
#undef  SSL_load_error_strings
#undef  SSLeay_add_ssl_algorithms
#define ASN1_STRING_data		ASN1_STRING_get0_data
#define TLS_client_method()		SSLv23_client_method()
#define SSL_load_error_strings()	/* nothing */
#define SSLeay_add_ssl_algorithms()	/* nothing */
#define SSL_set_no_TLSV1()		SSL_set_min_proto_version(handle, TLS1_1_VERSION)

#elif defined(SSLEAY_VERSION_NUMBER)

#define TLS_client_method()		SSLv23_client_method()

#endif

#ifndef SSL_set_no_TLSV1
#define SSL_set_no_TLSV1()		SSL_set_options(handle, SSL_OP_NO_TLSv1)
#endif

#ifdef USE_GNUTLS_INCL
#include <gnutls/x509.h>
#endif

#endif /* USE_SSL */

BOOLEAN reloading = FALSE;	/* Reloading => send no-cache pragma to proxy */
char *redirecting_url = NULL;	/* Location: value. */
BOOL permanent_redirection = FALSE;	/* Got 301 status? */
BOOL redirect_post_content = FALSE;	/* Don't convert to GET? */

#ifdef USE_SSL
SSL_CTX *ssl_ctx = NULL;	/* SSL ctx */
SSL *SSL_handle = NULL;
static int ssl_okay;

static void free_ssl_ctx(void)
{
    if (ssl_ctx != NULL)
	SSL_CTX_free(ssl_ctx);
}

static BOOL needs_limit(const char *actual)
{
    return ((int) strlen(actual) > LYcols - 7) ? TRUE : FALSE;
}

static char *limited_string(const char *source, const char *actual)
{
    int limit = ((int) strlen(source)
		 - ((int) strlen(actual) - (LYcols - 10)));
    char *temp = NULL;

    StrAllocCopy(temp, source);
    if (limit < 0)
	limit = 0;
    strcpy(temp + limit, "...");
    return temp;
}

/*
 * If the error message is too long to fit in the line, truncate that to fit
 * within the limits for prompting.
 */
static void SSL_single_prompt(char **target, const char *source)
{
    HTSprintf0(target, SSL_FORCED_PROMPT, source);
    if (needs_limit(*target)) {
	char *temp = limited_string(source, *target);

	*target = NULL;
	HTSprintf0(target, SSL_FORCED_PROMPT, temp);
	free(temp);
    }
}

static void SSL_double_prompt(char **target, const char *format, const char
			      *arg1, const char *arg2)
{
    HTSprintf0(target, format, arg1, arg2);
    if (needs_limit(*target)) {
	char *parg2 = limited_string(arg2, *target);

	*target = NULL;
	HTSprintf0(target, format, arg1, parg2);
	if (needs_limit(*target)) {
	    char *parg1 = limited_string(arg1, *target);

	    *target = NULL;
	    HTSprintf0(target, format, parg1, parg2);
	    free(parg1);
	}
	free(parg2);
    }
}

static int HTSSLCallback(int preverify_ok, X509_STORE_CTX * x509_ctx GCC_UNUSED)
{
    char *msg = NULL;
    int result = 1;

#ifdef USE_X509_SUPPORT
    HTSprintf0(&msg,
	       gettext("SSL callback:%s, preverify_ok=%d, ssl_okay=%d"),
	       X509_verify_cert_error_string((long) X509_STORE_CTX_get_error(x509_ctx)),
	       preverify_ok, ssl_okay);
    _HTProgress(msg);
    FREE(msg);
#endif

#ifndef USE_NSS_COMPAT_INCL
    if (!(preverify_ok || ssl_okay || ssl_noprompt)) {
#ifdef USE_X509_SUPPORT
	SSL_single_prompt(&msg,
			  X509_verify_cert_error_string((long)
							X509_STORE_CTX_get_error(x509_ctx)));
	if (HTForcedPrompt(ssl_noprompt, msg, NO))
	    ssl_okay = 1;
	else
	    result = 0;
#endif

	FREE(msg);
    }
#endif
    return result;
}

SSL *HTGetSSLHandle(void)
{
#ifdef USE_GNUTLS_INCL
    static char *certfile = NULL;
#endif
    static char *client_keyfile = NULL;
    static char *client_certfile = NULL;

    if (ssl_ctx == NULL) {
	/*
	 * First time only.
	 */
#if SSLEAY_VERSION_NUMBER < 0x0800
	if ((ssl_ctx = SSL_CTX_new()) != NULL) {
	    X509_set_default_verify_paths(ssl_ctx->cert);
	}
#else
	SSLeay_add_ssl_algorithms();
	if ((ssl_ctx = SSL_CTX_new(TLS_client_method())) != NULL) {
#ifdef SSL_OP_NO_SSLv2
	    SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
#else
	    SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL);
#endif
#ifdef SSL_OP_NO_COMPRESSION
	    SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_COMPRESSION);
#endif
#ifdef SSL_MODE_AUTO_RETRY
	    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
#endif
#ifdef SSL_MODE_RELEASE_BUFFERS
	    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
#endif
	    SSL_CTX_set_default_verify_paths(ssl_ctx);
	    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, HTSSLCallback);
	}
#endif /* SSLEAY_VERSION_NUMBER < 0x0800 */
#if defined(USE_PROGRAM_DIR) & !defined(USE_GNUTLS_INCL)
	if (ssl_ctx != NULL) {
	    X509_LOOKUP *lookup;

	    lookup = X509_STORE_add_lookup(ssl_ctx->cert_store,
					   X509_LOOKUP_file());
	    if (lookup != NULL) {
		char *certfile = NULL;

		HTSprintf0(&certfile, "%s\\cert.pem", program_dir);
		X509_LOOKUP_load_file(lookup, certfile, X509_FILETYPE_PEM);
		FREE(certfile);
	    }
	}
#endif
#ifdef USE_GNUTLS_INCL
	if ((certfile = LYGetEnv("SSL_CERT_FILE")) != NULL) {
	    CTRACE((tfp,
		    "HTGetSSLHandle: certfile is set to %s by SSL_CERT_FILE\n",
		    certfile));
	} else {
	    if (non_empty(SSL_cert_file)) {
		certfile = SSL_cert_file;
		CTRACE((tfp,
			"HTGetSSLHandle: certfile is set to %s by config SSL_CERT_FILE\n",
			certfile));
	    }
#if defined(USE_PROGRAM_DIR)
	    else {
		HTSprintf0(&(certfile), "%s\\cert.pem", program_dir);
		CTRACE((tfp,
			"HTGetSSLHandle: certfile is set to %s by installed directory\n", certfile));
	    }
#endif
	}
#endif
	atexit(free_ssl_ctx);
    }

    if (non_empty(SSL_client_key_file)) {
	client_keyfile = SSL_client_key_file;
	CTRACE((tfp,
		"HTGetSSLHandle: client key file is set to %s by config SSL_CLIENT_KEY_FILE\n",
		client_keyfile));
    }

    if (non_empty(SSL_client_cert_file)) {
	client_certfile = SSL_client_cert_file;
	CTRACE((tfp,
		"HTGetSSLHandle: client cert file is set to %s by config SSL_CLIENT_CERT_FILE\n",
		client_certfile));
    }
#ifdef USE_GNUTLS_INCL
    ssl_ctx->certfile = certfile;
    ssl_ctx->certfile_type = GNUTLS_X509_FMT_PEM;
    ssl_ctx->client_keyfile = client_keyfile;
    ssl_ctx->client_keyfile_type = GNUTLS_X509_FMT_PEM;
    ssl_ctx->client_certfile = client_certfile;
    ssl_ctx->client_certfile_type = GNUTLS_X509_FMT_PEM;
#elif SSLEAY_VERSION_NUMBER >= 0x0930
    if (client_certfile != NULL) {
	if (client_keyfile == NULL) {
	    client_keyfile = client_certfile;
	}
	SSL_CTX_use_certificate_chain_file(ssl_ctx, client_certfile);
	SSL_CTX_use_PrivateKey_file(ssl_ctx, client_keyfile, SSL_FILETYPE_PEM);
    }
#endif
    ssl_okay = 0;
    return (SSL_new(ssl_ctx));
}

void HTSSLInitPRNG(void)
{
#if SSLEAY_VERSION_NUMBER >= 0x00905100
    if (RAND_status() == 0) {
	char rand_file[256];
	time_t t;
	long l, seed;

#ifndef _WINDOWS
	pid_t pid;

#else
	DWORD pid;
#endif

	t = time(NULL);

#ifndef _WINDOWS
	pid = getpid();
#else
	pid = GetCurrentThreadId();
#endif

	RAND_file_name(rand_file, 256L);
	CTRACE((tfp, "HTTP: Seeding PRNG\n"));
	/* Seed as much as 1024 bytes from RAND_file_name */
	RAND_load_file(rand_file, 1024L);
	/* Seed in time (mod_ssl does this) */
	RAND_seed((unsigned char *) &t, (int) sizeof(time_t));

	/* Seed in pid (mod_ssl does this) */
	RAND_seed((unsigned char *) &pid, (int) sizeof(pid));
	/* Initialize system's random number generator */
	RAND_bytes((unsigned char *) &seed, (int) sizeof(long));

	lynx_srand((unsigned) seed);
	while (RAND_status() == 0) {
	    /* Repeatedly seed the PRNG using the system's random number generator until it has been seeded with enough data */
	    l = lynx_rand();
	    RAND_seed((unsigned char *) &l, (int) sizeof(long));
	}
	/* Write a rand_file */
	RAND_write_file(rand_file);
    }
#endif /* SSLEAY_VERSION_NUMBER >= 0x00905100 */
    return;
}

#define HTTP_NETREAD(sock, buff, size, handle) \
	(handle \
	 ? SSL_read(handle, buff, size) \
	 : NETREAD(sock, buff, size))

#define HTTP_NETWRITE(sock, buff, size, handle) \
	(handle \
	 ? SSL_write(handle, buff, size) \
	 : NETWRITE(sock, buff, size))

#define HTTP_NETCLOSE(sock, handle)  \
	{ (void)NETCLOSE(sock); \
	  if (handle) \
	      SSL_free(handle); \
	  SSL_handle = handle = NULL; \
	}

#else
#define HTTP_NETREAD(a, b, c, d)   NETREAD(a, b, c)
#define HTTP_NETWRITE(a, b, c, d)  NETWRITE(a, b, c)
#define HTTP_NETCLOSE(a, b)  (void)NETCLOSE(a)
#endif /* USE_SSL */

#ifdef _WINDOWS			/* 1997/11/06 (Thu) 13:00:08 */

#define	BOX_TITLE	"Lynx " __FILE__
#define	BOX_FLAG	(MB_ICONINFORMATION | MB_SETFOREGROUND)

typedef struct {
    int fd;
    char *buf;
    int len;
} recv_data_t;

int ws_read_per_sec = 0;
static int ws_errno = 0;

static DWORD g_total_times = 0;
static DWORD g_total_bytes = 0;

/* The same like read, but takes care of EINTR and uses select to
   timeout the stale connections.  */

static int ws_read(int fd, char *buf, int len)
{
    int res;
    int retry = 3;

    do {
	res = recv(fd, buf, len, 0);
	if (WSAEWOULDBLOCK == WSAGetLastError()) {
	    Sleep(100);
	    if (retry-- > 0)
		continue;
	}
    } while (res == SOCKET_ERROR && SOCKET_ERRNO == EINTR);

    return res;
}

#define DWORD_ERR ((DWORD)-1)

static DWORD __stdcall _thread_func(void *p)
{
    DWORD result;
    int i, val;
    recv_data_t *q = (recv_data_t *) p;

    i = 0;
    i++;
    val = ws_read(q->fd, q->buf, q->len);

    if (val == SOCKET_ERROR) {
	ws_errno = WSAGetLastError();
#if 0
	char buff[256];

	sprintf(buff, "Thread read: %d, error (%ld), fd = %d, len = %d",
		i, ws_errno, q->fd, q->len);
	MessageBox(NULL, buff, BOX_TITLE, BOX_FLAG);
#endif
	result = DWORD_ERR;
    } else {
	result = val;
    }

    return result;
}

/* The same like read, but takes care of EINTR and uses select to
   timeout the stale connections.  */

int ws_netread(int fd, char *buf, int len)
{
    int i;
    char buff[256];

    /* 1998/03/30 (Mon) 09:01:21 */
    HANDLE hThread;
    DWORD dwThreadID;
    DWORD exitcode = 0;
    DWORD ret_val = DWORD_ERR;
    DWORD val, process_time, now_TickCount, save_TickCount;

    static recv_data_t para;

#define TICK	5
#define STACK_SIZE	0x2000uL

    EnterCriticalSection(&critSec_READ);

    para.fd = fd;
    para.buf = buf;
    para.len = len;

    ws_read_per_sec = 0;
    save_TickCount = GetTickCount();

    hThread = CreateThread(NULL, STACK_SIZE,
			   _thread_func,
			   (void *) &para, 0UL, &dwThreadID);

    if (hThread == 0) {
	HTInfoMsg("CreateThread Failed (read)");
	goto read_exit;
    }

    i = 0;
    while (1) {
	val = WaitForSingleObject(hThread, 1000 / TICK);
	i++;
	if (val == WAIT_FAILED) {
	    HTInfoMsg("Wait Failed");
	    ret_val = DWORD_ERR;
	    break;
	} else if (val == WAIT_TIMEOUT) {
	    i++;
	    if (i / TICK > (AlertSecs + 2)) {
		sprintf(buff, "Read Waiting (%2d.%01d) for %d Bytes",
			i / TICK, (i % TICK) * 10 / TICK, len);
		SetConsoleTitle(buff);
	    }
	    if (win32_check_interrupt() || ((i / TICK) > lynx_timeout)) {
		if (CloseHandle(hThread) == FALSE) {
		    HTInfoMsg("Thread terminate Failed");
		}
		WSASetLastError(ETIMEDOUT);
		ret_val = HT_INTERRUPTED;
		break;
	    }
	} else if (val == WAIT_OBJECT_0) {
	    if (GetExitCodeThread(hThread, &exitcode) == FALSE) {
		exitcode = DWORD_ERR;
	    }
	    if (CloseHandle(hThread) == FALSE) {
		HTInfoMsg("Thread terminate Failed");
	    }
	    now_TickCount = GetTickCount();
	    if (now_TickCount >= save_TickCount)
		process_time = now_TickCount - save_TickCount;
	    else
		process_time = now_TickCount + (0xffffffff - save_TickCount);

	    if (process_time == 0)
		process_time = 1;
	    g_total_times += process_time;

	    /*
	     * DWORD is unsigned, and could be an error code which is signed.
	     */
	    if ((long) exitcode > 0)
		g_total_bytes += exitcode;

	    ws_read_per_sec = g_total_bytes;
	    if (ws_read_per_sec > 2000000) {
		if (g_total_times > 1000)
		    ws_read_per_sec /= (g_total_times / 1000);
	    } else {
		ws_read_per_sec *= 1000;
		ws_read_per_sec /= g_total_times;
	    }

	    ret_val = exitcode;
	    break;
	}
    }				/* end while(1) */

  read_exit:
    LeaveCriticalSection(&critSec_READ);
    return ret_val;
}
#endif /* _WINDOWS */

/*
 * RFC-1738 says we can have user/password using these ASCII characters
 *    safe           = "$" | "-" | "_" | "." | "+"
 *    extra          = "!" | "*" | "'" | "(" | ")" | ","
 *    hex            = digit | "A" | "B" | "C" | "D" | "E" | "F" |
 *                             "a" | "b" | "c" | "d" | "e" | "f"
 *    escape         = "%" hex hex
 *    unreserved     = alpha | digit | safe | extra
 *    uchar          = unreserved | escape
 *    user           = *[ uchar | ";" | "?" | "&" | "=" ]
 *    password       = *[ uchar | ";" | "?" | "&" | "=" ]
 * and we cannot have a password without user, i.e., no leading ":"
 * and ":", "@", "/" must be encoded, i.e., will not appear as such.
 *
 * However, in a URL
 *    //<user>:<password>@<host>:<port>/<url-path>
 * valid characters in the host are different, not allowing most of those
 * punctuation characters.
 *
 * RFC-3986 amends this, using
 *     userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
 *     unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
 *     reserved      = gen-delims / sub-delims
 *     gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
 *     sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
 *                     / "*" / "+" / "," / ";" / "="
 * and
 *     host          = IP-literal / IPv4address / reg-name
 *     reg-name      = *( unreserved / pct-encoded / sub-delims )
 */
char *HTSkipToAt(char *host, int *gen_delims)
{
    char *result = 0;
    char *s = host;
    int pass = 0;
    int ch;
    int last = -1;

    *gen_delims = 0;
    while ((ch = UCH(*s)) != '\0') {
	if (ch == ':') {
	    if (pass++)
		break;
	} else if (ch == '@') {
	    if (s != host && last != ':')
		result = s;
	    break;
	} else if (RFC_3986_GEN_DELIMS(ch)) {
	    *gen_delims += 1;
	    if (!RFC_3986_GEN_DELIMS(s[1]))
		break;
	} else if (ch == '%') {
	    if (!(isxdigit(UCH(s[1])) && isxdigit(UCH(s[2]))))
		break;
	} else if (!(RFC_3986_UNRESERVED(ch) ||
		     RFC_3986_SUB_DELIMS(ch))) {
	    break;
	}
	++s;
	last = ch;
    }
    return result;
}

static char *fake_hostname(char *auth)
{
    char *result = NULL;
    char *colon = NULL;

    StrAllocCopy(result, auth);
    if ((colon = strchr(result, ':')) != 0)
	*colon = '\0';
    if (strchr(result, '.') == 0)
	FREE(result);
    return result;
}

/*
 * Strip any username from the given string so we retain only the host.
 */
void strip_userid(char *host, int parse_only)
{
    int gen_delims = 0;
    char *p1 = host;
    char *p2 = HTSkipToAt(host, &gen_delims);

    if (p2 != 0) {
	char *msg = NULL;
	char *auth = NULL;
	char *fake = NULL;
	char *p3 = p2;
	int sub_delims = 0;
	int my_delimit = UCH(*p2);
	int do_trimming = (my_delimit == '@');

	*p2++ = '\0';

	StrAllocCopy(auth, host);

	/*
	 * Trailing "gen-delims" demonstrates that there is no user/password.
	 */
	while ((p3 != host) && RFC_3986_GEN_DELIMS(p3[-1])) {
	    *(--p3) = '\0';
	}
	/*
	 * While legal, punctuation-only user/password is questionable.
	 */
	while ((p3 != host) && RFC_3986_SUB_DELIMS(p3[-1])) {
	    ++sub_delims;
	    *(--p3) = '\0';
	}
	/*
	 * Trim trailing "gen-delims" from the real hostname.
	 */
	for (p3 = p2; *p3 != '\0'; ++p3) {
	    if (RFC_3986_GEN_DELIMS(*p3)) {
		*p3 = '\0';
		break;
	    }
	}
	CTRACE((tfp, "trim auth:    result:`%s'\n", host));

	if (gen_delims || strcmp(host, auth)) {
	    do_trimming = !gen_delims;
	}
	if (*host == '\0' && sub_delims) {
	    HTSprintf0(&msg,
		       gettext("User/password contains only punctuation: %s"),
		       auth);
	} else if ((fake = fake_hostname(host)) != NULL) {
	    HTSprintf0(&msg,
		       gettext("User/password may be confused with hostname: '%s' (e.g, '%s')"),
		       auth, fake);
	}
	if (msg != 0 && !parse_only)
	    HTAlert(msg);
	if (do_trimming) {
	    while ((*p1++ = *p2++) != '\0') {
		;
	    }
	    CTRACE((tfp, "trim host:    result:`%s'\n", host));
	}
	FREE(fake);
	FREE(auth);
	FREE(msg);
    }
}

/*
 * Check if the user's options specified to use the given encoding.  Normally
 * all encodings with compiled-in support are specified (encodingALL).
 */
static BOOL acceptEncoding(int code)
{
    BOOL result = FALSE;

    if ((code & LYAcceptEncoding) != 0) {
	const char *program = 0;

	switch (code) {
	case encodingGZIP:
	    program = HTGetProgramPath(ppGZIP);
	    break;
	case encodingDEFLATE:
	    program = HTGetProgramPath(ppINFLATE);
	    break;
	case encodingCOMPRESS:
	    program = HTGetProgramPath(ppCOMPRESS);
	    break;
	case encodingBZIP2:
	    program = HTGetProgramPath(ppBZIP2);
	    break;
	default:
	    break;
	}
	/*
	 * FIXME:  if lynx did not rely upon external programs to decompress
	 * files for external viewers, this check could be relaxed.
	 */
	result = (BOOL) (program != 0);
    }
    return result;
}

#ifdef USE_SSL
static void show_cert_issuer(X509 * peer_cert GCC_UNUSED)
{
#if defined(USE_OPENSSL_INCL) || defined(USE_GNUTLS_FUNCS)
    char ssl_dn[1024];
    char *msg = NULL;

    X509_NAME_oneline(X509_get_issuer_name(peer_cert), ssl_dn, (int) sizeof(ssl_dn));
    HTSprintf0(&msg, gettext("Certificate issued by: %s"), ssl_dn);
    _HTProgress(msg);
    FREE(msg);
#elif defined(USE_GNUTLS_INCL)
    /* the OpenSSL "compat" code compiles but dumps core with GNU TLS */
#endif
}
#endif

/*
 * Remove IPv6 brackets (and any port-number) from the given host-string.
 */
#ifdef USE_SSL
static char *StripIpv6Brackets(char *host)
{
    int port_number;
    char *p;

    if ((p = HTParsePort(host, &port_number)) != 0)
	*p = '\0';

    if (*host == '[') {
	p = host + strlen(host) - 1;
	if (*p == ']') {
	    *p = '\0';
	    ++host;
	}
    }
    return host;
}
#endif

/*		Load Document from HTTP Server			HTLoadHTTP()
 *		==============================
 *
 *	Given a hypertext address, this routine loads a document.
 *
 *
 *  On entry,
 *	arg	is the hypertext reference of the article to be loaded.
 *
 *  On exit,
 *	returns >=0	If no error, a good socket number
 *		<0	Error.
 *
 *	The socket must be closed by the caller after the document has been
 *	read.
 *
 */
static int HTLoadHTTP(const char *arg,
		      HTParentAnchor *anAnchor,
		      HTFormat format_out,
		      HTStream *sink)
{
    static char empty[1];
    int s;			/* Socket number for returned data */
    const char *url = arg;	/* The URL which get_physical() returned */
    bstring *command = NULL;	/* The whole command */
    char *eol;			/* End of line if found */
    char *start_of_data;	/* Start of body of reply */
    int status;			/* tcp return */
    off_t bytes_already_read;
    char crlf[3];		/* A CR LF equivalent string */
    HTStream *target;		/* Unconverted data */
    HTFormat format_in;		/* Format arriving in the message */
    BOOL do_head = FALSE;	/* Whether or not we should do a head */
    BOOL do_post = FALSE;	/* ARE WE posting ? */
    const char *METHOD;

    char *line_buffer = NULL;
    char *line_kept_clean = NULL;

#ifdef SH_EX			/* FIX BUG by kaz@maczuka.hitachi.ibaraki.jp */
    int real_length_of_line = 0;
#endif
    BOOL extensions;		/* Assume good HTTP server */
    char *linebuf = NULL;
    char temp[80];
    BOOL first_Accept = TRUE;
    BOOL show_401 = FALSE;
    BOOL show_407 = FALSE;
    BOOL auth_proxy = NO;	/* Generate a proxy authorization. - AJL */

    int length, rawlength, rv;
    int server_status = 0;
    BOOL doing_redirect, already_retrying = FALSE;
    int len = 0;

#ifdef USE_SSL
    unsigned long SSLerror;
    BOOL do_connect = FALSE;	/* ARE WE going to use a proxy tunnel ? */
    BOOL did_connect = FALSE;	/* ARE WE actually using a proxy tunnel ? */
    const char *connect_url = NULL;	/* The URL being proxied */
    char *connect_host = NULL;	/* The host being proxied */
    SSL *handle = NULL;		/* The SSL handle */
    X509 *peer_cert;		/* The peer certificate */
    char ssl_dn[1024];
    char *cert_host;
    char *ssl_host;
    char *p;
    char *msg = NULL;
    int status_sslcertcheck;
    char *ssl_dn_start;
    char *ssl_all_cns = NULL;

#ifdef USE_GNUTLS_INCL
    int ret;
    unsigned tls_status;
#endif

#if (SSLEAY_VERSION_NUMBER >= 0x0900) && !defined(USE_GNUTLS_FUNCS)
    BOOL try_tls = TRUE;
#endif /* SSLEAY_VERSION_NUMBER >= 0x0900 */
    SSL_handle = NULL;
#else
    void *handle = NULL;
#endif /* USE_SSL */

    if (anAnchor->isHEAD)
	do_head = TRUE;
    else if (anAnchor->post_data)
	do_post = TRUE;

    if (!url) {
	status = -3;
	_HTProgress(BAD_REQUEST);
	goto done;
    }
    if (!*url) {
	status = -2;
	_HTProgress(BAD_REQUEST);
	goto done;
    }
#ifdef USE_SSL
    if (using_proxy && !StrNCmp(url, "http://", 7)) {
	int portnumber;

	if ((connect_url = strstr((url + 7), "https://"))) {
	    do_connect = TRUE;
	    connect_host = HTParse(connect_url, "https", PARSE_HOST);
	    if (!HTParsePort(connect_host, &portnumber)) {
		sprintf(temp, ":%d", HTTPS_PORT);
		StrAllocCat(connect_host, temp);
	    }
	    CTRACE((tfp, "HTTP: connect_url = '%s'\n", connect_url));
	    CTRACE((tfp, "HTTP: connect_host = '%s'\n", connect_host));
	} else if ((connect_url = strstr((url + 7), "snews://"))) {
	    do_connect = TRUE;
	    connect_host = HTParse(connect_url, "snews", PARSE_HOST);
	    if (!HTParsePort(connect_host, &portnumber)) {
		sprintf(temp, ":%d", SNEWS_PORT);
		StrAllocCat(connect_host, temp);
	    }
	    CTRACE((tfp, "HTTP: connect_url = '%s'\n", connect_url));
	    CTRACE((tfp, "HTTP: connect_host = '%s'\n", connect_host));
	}
    }
#endif /* USE_SSL */

    sprintf(crlf, "%c%c", CR, LF);

    /*
     * At this point, we're talking HTTP/1.0.
     */
    extensions = YES;

  try_again:
    /*
     * All initializations are moved down here from up above, so we can start
     * over here...
     */
    eol = 0;
    length = 0;
    doing_redirect = FALSE;
    permanent_redirection = FALSE;
    redirect_post_content = FALSE;
    target = NULL;
    line_buffer = NULL;
    line_kept_clean = NULL;

#ifdef USE_SSL
    if (!StrNCmp(url, "https", 5))
	status = HTDoConnect(url, "HTTPS", HTTPS_PORT, &s);
    else
	status = HTDoConnect(url, "HTTP", HTTP_PORT, &s);
#else
    if (!StrNCmp(url, "https", 5)) {
	HTAlert(gettext("This client does not contain support for HTTPS URLs."));
	status = HT_NOT_LOADED;
	goto done;
    }
    status = HTDoConnect(arg, "HTTP", HTTP_PORT, &s);
#endif /* USE_SSL */
    if (status == HT_INTERRUPTED) {
	/*
	 * Interrupt cleanly.
	 */
	CTRACE((tfp, "HTTP: Interrupted on connect; recovering cleanly.\n"));
	_HTProgress(CONNECTION_INTERRUPTED);
	status = HT_NOT_LOADED;
	goto done;
    }
    if (status < 0) {
#ifdef _WINDOWS
	CTRACE((tfp, "HTTP: Unable to connect to remote host for `%s'\n"
		" (status = %d, sock_errno = %d).\n",
		url, status, SOCKET_ERRNO));
#else
	CTRACE((tfp,
		"HTTP: Unable to connect to remote host for `%s' (errno = %d).\n",
		url, SOCKET_ERRNO));
#endif
	HTAlert(gettext("Unable to connect to remote host."));
	status = HT_NOT_LOADED;
	goto done;
    }
#ifdef USE_SSL
  use_tunnel:
    /*
     * If this is an https document, then do the SSL stuff here.
     */
    if (did_connect || !StrNCmp(url, "https", 5)) {
	SSL_handle = handle = HTGetSSLHandle();
	SSL_set_fd(handle, s);
	/* get host we're connecting to */
	ssl_host = HTParse(url, "", PARSE_HOST);
	ssl_host = StripIpv6Brackets(ssl_host);
#if defined(USE_GNUTLS_FUNCS)
	ret = gnutls_server_name_set(handle->gnutls_state,
				     GNUTLS_NAME_DNS,
				     ssl_host, strlen(ssl_host));
	CTRACE((tfp, "...called gnutls_server_name_set(%s) ->%d\n", ssl_host, ret));
#elif SSLEAY_VERSION_NUMBER >= 0x0900
#ifndef USE_NSS_COMPAT_INCL
	if (!try_tls) {
	    SSL_set_no_TLSV1();
	    CTRACE((tfp, "...adding SSL_OP_NO_TLSv1\n"));
	}
#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
	else {
	    int ret = (int) SSL_set_tlsext_host_name(handle, ssl_host);

	    CTRACE((tfp, "...called SSL_set_tlsext_host_name(%s) ->%d\n",
		    ssl_host, ret));
	}
#endif
#endif
#endif /* SSLEAY_VERSION_NUMBER >= 0x0900 */
	HTSSLInitPRNG();
	status = SSL_connect(handle);

	if (status <= 0) {
#if (SSLEAY_VERSION_NUMBER >= 0x0900)
#if !defined(USE_GNUTLS_FUNCS)
	    if (try_tls) {
		_HTProgress(gettext("Retrying connection without TLS."));
		try_tls = FALSE;
		if (did_connect)
		    HTTP_NETCLOSE(s, handle);
		goto try_again;
	    } else
#endif
	    {
		CTRACE((tfp,
			"HTTP: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
			url, status));
		SSL_load_error_strings();
		while ((SSLerror = ERR_get_error()) != 0) {
		    CTRACE((tfp, "HTTP: SSL: %s\n", ERR_error_string(SSLerror, NULL)));
		}
		HTAlert("Unable to make secure connection to remote host.");
		if (did_connect)
		    HTTP_NETCLOSE(s, handle);
		status = HT_NOT_LOADED;
		goto done;
	    }
#else
	    unsigned long SSLerror;

	    CTRACE((tfp,
		    "HTTP: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
		    url, status));
	    SSL_load_error_strings();
	    while ((SSLerror = ERR_get_error()) != 0) {
		CTRACE((tfp, "HTTP: SSL: %s\n", ERR_error_string(SSLerror, NULL)));
	    }
	    HTAlert("Unable to make secure connection to remote host.");
	    if (did_connect)
		HTTP_NETCLOSE(s, handle);
	    status = HT_NOT_LOADED;
	    goto done;
#endif /* SSLEAY_VERSION_NUMBER >= 0x0900 */
	}
#ifdef USE_GNUTLS_INCL
	gnutls_certificate_set_verify_flags(handle->gnutls_cred,
					    GNUTLS_VERIFY_DO_NOT_ALLOW_SAME |
					    GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
	ret = gnutls_certificate_verify_peers2(handle->gnutls_state, &tls_status);
	if (ret < 0 || tls_status != 0) {
	    int flag_continue = 1;

#if GNUTLS_VERSION_NUMBER >= 0x030104
	    int type;
	    gnutls_datum_t out;

	    if (ret < 0) {
		SSL_single_prompt(&msg,
				  gettext("GnuTLS error when trying to verify certificate."));
	    } else {
		type = gnutls_certificate_type_get(handle->gnutls_state);
		(void) gnutls_certificate_verification_status_print(tls_status,
								    type,
								    &out, 0);
		SSL_single_prompt(&msg, (const char *) out.data);
		gnutls_free(out.data);
	    }
#else
	    char *msg2;

	    if (ret == 0 && tls_status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
		msg2 = gettext("the certificate has no known issuer");
	    } else if (tls_status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
		msg2 = gettext("no issuer was found");
	    } else if (tls_status & GNUTLS_CERT_SIGNER_NOT_CA) {
		msg2 = gettext("issuer is not a CA");
	    } else if (tls_status & GNUTLS_CERT_REVOKED) {
		msg2 = gettext("the certificate has been revoked");
	    } else {
		msg2 = gettext("the certificate is not trusted");
	    }
	    SSL_single_prompt(&msg, msg2);
#endif
	    CTRACE((tfp, "HTLoadHTTP: %s\n", msg));
	    if (!ssl_noprompt) {
		if (!HTForcedPrompt(ssl_noprompt, msg, NO)) {
		    flag_continue = 0;
		}
	    } else if (ssl_noprompt == FORCE_PROMPT_NO) {
		flag_continue = 0;
	    }
	    FREE(msg);
	    if (flag_continue == 0) {
		status = HT_NOT_LOADED;
		FREE(msg);
		goto done;
	    }
	}
#endif

	peer_cert = (X509 *) SSL_get_peer_certificate(handle);
#if defined(USE_OPENSSL_INCL) || defined(USE_GNUTLS_FUNCS)
	X509_NAME_oneline(X509_get_subject_name(peer_cert),
			  ssl_dn, (int) sizeof(ssl_dn));
#elif defined(USE_GNUTLS_INCL)
	X509_NAME_oneline(X509_get_subject_name(peer_cert),
			  ssl_dn + 1, (int) sizeof(ssl_dn) - 1);

	/* Iterate over DN in incompatible GnuTLS format to bring it into OpenSSL format */
	ssl_dn[0] = '/';
	ssl_dn_start = ssl_dn;
	while (*ssl_dn_start) {
	    if ((*ssl_dn_start == ',') && (*(ssl_dn_start + 1) == ' ')) {
		*ssl_dn_start++ = '/';
		if (*(p = ssl_dn_start) != 0) {
		    while ((p[0] = p[1]) != 0)
			++p;
		}
	    } else {
		ssl_dn_start++;
	    }
	}
#endif

	/*
	 * X.509 DN validation taking ALL CN fields into account
	 * (c) 2006 Thorsten Glaser <tg@mirbsd.de>
	 */

	/* initialise status information */
	status_sslcertcheck = 0;	/* 0 = no CN found in DN */
	ssl_dn_start = ssl_dn;

	/* validate all CNs found in DN */
	CTRACE((tfp, "Validating CNs in '%s'\n", ssl_dn_start));
	while ((cert_host = strstr(ssl_dn_start, "/CN=")) != NULL) {
	    status_sslcertcheck = 1;	/* 1 = could not verify CN */
	    /* start of CommonName */
	    cert_host += 4;
	    /* find next part of DistinguishedName */
	    if ((p = StrChr(cert_host, '/')) != NULL) {
		*p = '\0';
		ssl_dn_start = p;	/* yes this points to the NUL byte */
	    } else
		ssl_dn_start = NULL;
	    cert_host = StripIpv6Brackets(cert_host);

	    /* verify this CN */
	    CTRACE((tfp, "Matching\n\tssl_host  '%s'\n\tcert_host '%s'\n",
		    ssl_host, cert_host));
	    if (!strcasecomp_asterisk(ssl_host, cert_host)) {
		status_sslcertcheck = 2;	/* 2 = verified peer */
		/* I think this is cool to have in the logs -TG */
		HTSprintf0(&msg,
			   gettext("Verified connection to %s (cert=%s)"),
			   ssl_host, cert_host);
		_HTProgress(msg);
		FREE(msg);
		/* no need to continue the verification loop */
		break;
	    }

	    /* add this CN to list of failed CNs */
	    if (ssl_all_cns == NULL)
		StrAllocCopy(ssl_all_cns, "CN<");
	    else
		StrAllocCat(ssl_all_cns, ":CN<");
	    StrAllocCat(ssl_all_cns, cert_host);
	    StrAllocCat(ssl_all_cns, ">");
	    /* if we cannot retry, don't try it */
	    if (ssl_dn_start == NULL)
		break;
	    /* now retry next CN found in DN */
	    *ssl_dn_start = '/';	/* formerly NUL byte */
	}

	/* check the X.509v3 Subject Alternative Name */
#ifdef USE_GNUTLS_INCL
	if (status_sslcertcheck < 2) {
	    int i;
	    size_t size;
	    gnutls_x509_crt_t cert;
	    static char buf[2048];

	    /* import the certificate to the x509_crt format */
	    if (gnutls_x509_crt_init(&cert) == 0) {

		if (gnutls_x509_crt_import(cert, peer_cert,
					   GNUTLS_X509_FMT_DER) < 0) {
		    gnutls_x509_crt_deinit(cert);
		    goto done;
		}

		ret = 0;
		for (i = 0; !(ret < 0); i++) {
		    size = sizeof(buf);
		    ret = gnutls_x509_crt_get_subject_alt_name(cert,
							       (unsigned) i,
							       buf, &size,
							       NULL);

		    if (strcasecomp_asterisk(ssl_host, buf) == 0) {
			status_sslcertcheck = 2;
			HTSprintf0(&msg,
				   gettext("Verified connection to %s (subj=%s)"),
				   ssl_host, buf);
			_HTProgress(msg);
			FREE(msg);
			break;
		    }

		}
	    }
	}
#endif
#ifdef USE_OPENSSL_INCL
	if (status_sslcertcheck < 2) {
	    STACK_OF(GENERAL_NAME) * gens;
	    int i, numalts;
	    const GENERAL_NAME *gn;

	    gens = (STACK_OF(GENERAL_NAME) *)
		X509_get_ext_d2i(peer_cert, NID_subject_alt_name, NULL, NULL);

	    if (gens != NULL) {
		numalts = sk_GENERAL_NAME_num(gens);
		for (i = 0; i < numalts; ++i) {
		    gn = sk_GENERAL_NAME_value(gens, i);
		    if (gn->type == GEN_DNS)
			cert_host = (char *) ASN1_STRING_data(gn->d.ia5);
		    else if (gn->type == GEN_IPADD) {
			/* XXX untested -TG */
			size_t j = (size_t) ASN1_STRING_length(gn->d.ia5);

			cert_host = (char *) malloc(j + 1);
			MemCpy(cert_host, ASN1_STRING_data(gn->d.ia5), j);
			cert_host[j] = '\0';
		    } else
			continue;
		    status_sslcertcheck = 1;	/* got at least one */
		    /* verify this SubjectAltName (see above) */
		    cert_host = StripIpv6Brackets(cert_host);
		    if (!(gn->type == GEN_IPADD ? strcasecomp :
			  strcasecomp_asterisk) (ssl_host, cert_host)) {
			status_sslcertcheck = 2;
			HTSprintf0(&msg,
				   gettext("Verified connection to %s (subj=%s)"),
				   ssl_host, cert_host);
			_HTProgress(msg);
			FREE(msg);
			if (gn->type == GEN_IPADD)
			    free(cert_host);
			break;
		    }
		    /* add to list of failed CNs */
		    if (ssl_all_cns == NULL)
			StrAllocCopy(ssl_all_cns, "SAN<");
		    else
			StrAllocCat(ssl_all_cns, ":SAN<");
		    if (gn->type == GEN_DNS)
			StrAllocCat(ssl_all_cns, "DNS=");
		    else if (gn->type == GEN_IPADD)
			StrAllocCat(ssl_all_cns, "IP=");
		    StrAllocCat(ssl_all_cns, cert_host);
		    StrAllocCat(ssl_all_cns, ">");
		    if (gn->type == GEN_IPADD)
			free(cert_host);
		}
		sk_GENERAL_NAME_free(gens);
	    }
	}
#endif /* USE_OPENSSL_INCL */

	/* if an error occurred, format the appropriate message */
	if (status_sslcertcheck == 0) {
	    SSL_single_prompt(&msg,
			      gettext("Can't find common name in certificate"));
	} else if (status_sslcertcheck == 1) {
	    SSL_double_prompt(&msg,
			      gettext("SSL error:host(%s)!=cert(%s)-Continue?"),
			      ssl_host, ssl_all_cns);
	}

	/* if an error occurred, let the user decide how much he trusts */
	if (status_sslcertcheck < 2) {
	    if (msg == NULL)
		StrAllocCopy(msg, gettext("SSL error"));
	    if (!HTForcedPrompt(ssl_noprompt, msg, NO)) {
		status = HT_NOT_LOADED;
		FREE(msg);
		FREE(ssl_all_cns);
		goto done;
	    }
	    SSL_double_prompt(&msg,
			      gettext("UNVERIFIED connection to %s (cert=%s)"),
			      ssl_host, ssl_all_cns ? ssl_all_cns : "NONE");
	    _HTProgress(msg);
	    FREE(msg);
	}

	show_cert_issuer(peer_cert);

	HTSprintf0(&msg,
		   gettext("Secure %d-bit %s (%s) HTTP connection"),
		   SSL_get_cipher_bits(handle, NULL),
		   SSL_get_cipher_version(handle),
		   SSL_get_cipher(handle));
	_HTProgress(msg);
	FREE(msg);
	FREE(ssl_all_cns);
	FREE(ssl_host);
    }
#endif /* USE_SSL */

    /* Ask that node for the document, omitting the host name & anchor
     */
    {
	char *p1 = (HTParse(url, "", PARSE_PATH | PARSE_PUNCTUATION));

#ifdef USE_SSL
	if (do_connect) {
	    METHOD = "CONNECT";
	    BStrCopy0(command, "CONNECT ");
	} else
#endif /* USE_SSL */
	if (do_post) {
	    METHOD = "POST";
	    BStrCopy0(command, "POST ");
	} else if (do_head) {
	    METHOD = "HEAD";
	    BStrCopy0(command, "HEAD ");
	} else {
	    METHOD = "GET";
	    BStrCopy0(command, "GET ");
	}

	/*
	 * If we are using a proxy gateway don't copy in the first slash of
	 * say:  /gopher://a;lkdjfl;ajdf;lkj/;aldk/adflj so that just
	 * gopher://....  is sent.
	 */
#ifdef USE_SSL
	if (using_proxy && !did_connect) {
	    if (do_connect)
		BStrCat0(command, connect_host);
	    else
		BStrCat0(command, p1 + 1);
	}
#else
	if (using_proxy)
	    BStrCat0(command, p1 + 1);
#endif /* USE_SSL */
	else
	    BStrCat0(command, p1);
	FREE(p1);
    }
    if (extensions) {
	BStrCat0(command, " ");
	BStrCat0(command, ((HTprotocolLevel == HTTP_1_0)
			   ? "HTTP/1.0"
			   : "HTTP/1.1"));
    }

    BStrCat0(command, crlf);	/* CR LF, as in rfc 977 */

    if (extensions) {
	int n, i;
	char *host = NULL;

	if ((host = HTParse(anAnchor->address, "", PARSE_HOST)) != NULL) {
	    strip_userid(host, TRUE);
	    HTBprintf(&command, "Host: %s%c%c", host, CR, LF);
	    FREE(host);
	}
	if (HTprotocolLevel >= HTTP_1_1) {
	    HTBprintf(&command, "Connection: close%c%c", CR, LF);
	}

	if (!HTPresentations)
	    HTFormatInit();
	n = HTList_count(HTPresentations);

	first_Accept = TRUE;
	len = 0;
	for (i = 0; i < n; i++) {
	    HTPresentation *pres =
	    (HTPresentation *) HTList_objectAt(HTPresentations, i);

	    if (pres->get_accept) {
		if (pres->quality < 1.0) {
		    if (pres->maxbytes > 0) {
			sprintf(temp, ";q=%4.3f;mxb=%" PRI_off_t "",
				pres->quality, CAST_off_t (pres->maxbytes));
		    } else {
			sprintf(temp, ";q=%4.3f", pres->quality);
		    }
		} else if (pres->maxbytes > 0) {
		    sprintf(temp, ";mxb=%" PRI_off_t "", CAST_off_t (pres->maxbytes));
		} else {
		    temp[0] = '\0';
		}
		HTSprintf0(&linebuf, "%s%s%s",
			   (first_Accept ?
			    "Accept: " : ", "),
			   HTAtom_name(pres->rep),
			   temp);
		len += (int) strlen(linebuf);
		if (len > 252 && !first_Accept) {
		    BStrCat0(command, crlf);
		    HTSprintf0(&linebuf, "Accept: %s%s",
			       HTAtom_name(pres->rep),
			       temp);
		    len = (int) strlen(linebuf);
		}
		BStrCat0(command, linebuf);
		first_Accept = FALSE;
	    }
	}
	HTBprintf(&command, "%s*/*;q=0.01%c%c",
		  (first_Accept ?
		   "Accept: " : ", "), CR, LF);

	/*
	 * FIXME:  suppressing the "Accept-Encoding" in this case is done to
	 * work around limitations of the presentation logic used for the
	 * command-line "-base" option.  The remote site may transmit the
	 * document gzip'd, but the ensuing logic in HTSaveToFile() would see
	 * the mime-type as gzip rather than text/html, and not prepend the
	 * base URL.  This is less efficient than accepting the compressed data
	 * and uncompressing it, adding the base URL but is simpler than
	 * augmenting the dump's presentation logic -TD
	 */
	if (LYPrependBaseToSource && dump_output_immediately) {
	    CTRACE((tfp,
		    "omit Accept-Encoding to work-around interaction with -source\n"));
	} else {
	    char *list = 0;
	    int j, k;

	    for (j = 1; j < encodingALL; j <<= 1) {
		if (acceptEncoding(j)) {
		    for (k = 0; tbl_preferred_encoding[k].name != 0; ++k) {
			if (tbl_preferred_encoding[k].value == j) {
			    if (list != 0)
				StrAllocCat(list, ", ");
			    StrAllocCat(list, tbl_preferred_encoding[k].name);
			    break;
			}
		    }
		}
	    }

	    if (list != 0) {
		HTBprintf(&command, "Accept-Encoding: %s%c%c", list, CR, LF);
		free(list);
	    }
	}

	if (non_empty(language)) {
	    HTBprintf(&command, "Accept-Language: %s%c%c", language, CR, LF);
	}

	if (non_empty(pref_charset)) {
	    BStrCat0(command, "Accept-Charset: ");
	    StrAllocCopy(linebuf, pref_charset);
	    if (linebuf[strlen(linebuf) - 1] == ',')
		linebuf[strlen(linebuf) - 1] = '\0';
	    LYLowerCase(linebuf);
	    if (strstr(linebuf, "iso-8859-1") == NULL)
		StrAllocCat(linebuf, ", iso-8859-1;q=0.01");
	    if (strstr(linebuf, "us-ascii") == NULL)
		StrAllocCat(linebuf, ", us-ascii;q=0.01");
	    BStrCat0(command, linebuf);
	    HTBprintf(&command, "%c%c", CR, LF);
	}
#if 0
	/*
	 * Promote 300 (Multiple Choices) replies, if supported, over 406 (Not
	 * Acceptable) replies.  - FM
	 *
	 * This used to be done in versions 2.7 and 2.8*, but violates the
	 * specs for transparent content negotiation and has the effect that
	 * servers supporting those specs will send 300 (Multiple Choices)
	 * instead of a normal response (e.g.  200 OK), since they will assume
	 * that the client wants to make the choice.  It is not clear whether
	 * there are any servers or sites for which sending this header really
	 * improves anything.
	 *
	 * If there ever is a need to send "Negotiate:  trans" and really mean
	 * it, we should send "Negotiate:  trans,trans" or similar, since that
	 * is semantically equivalent and some servers may ignore "Negotiate:
	 * trans" as a special case when it comes from Lynx (to work around the
	 * old faulty behavior).  - kw
	 *
	 * References:
	 * RFC 2295 (see also RFC 2296), and mail to lynx-dev and
	 * new-httpd@apache.org from Koen Holtman, Jan 1999.
	 */
	if (!do_post) {
	    HTBprintf(&command, "Negotiate: trans%c%c", CR, LF);
	}
#endif /* 0 */

	/*
	 * When reloading give no-cache pragma to proxy server to make it
	 * refresh its cache.  -- Ari L.  <luotonen@dxcern.cern.ch>
	 *
	 * Also send it as a Cache-Control header for HTTP/1.1.  - FM
	 */
	if (reloading) {
	    HTBprintf(&command, "Pragma: no-cache%c%c", CR, LF);
	    HTBprintf(&command, "Cache-Control: no-cache%c%c", CR, LF);
	}

	if (LYSendUserAgent || no_useragent) {
	    if (non_empty(LYUserAgent)) {
		char *cp = LYSkipBlanks(LYUserAgent);

		/* Won't send it at all if all blank - kw */
		if (*cp != '\0')
		    HTBprintf(&command, "User-Agent: %.*s%c%c",
			      INIT_LINE_SIZE - 15, LYUserAgent, CR, LF);
	    } else {
		HTBprintf(&command, "User-Agent: %s/%s  libwww-FM/%s%c%c",
			  HTAppName ? HTAppName : "unknown",
			  HTAppVersion ? HTAppVersion : "0.0",
			  HTLibraryVersion, CR, LF);
	    }
	}

	if (non_empty(personal_mail_address) && !LYNoFromHeader) {
	    HTBprintf(&command, "From: %s%c%c", personal_mail_address, CR, LF);
	}

	if (!(LYUserSpecifiedURL ||
	      LYNoRefererHeader || LYNoRefererForThis) &&
	    strcmp(HTLoadedDocumentURL(), "")) {
	    const char *cp = LYRequestReferer;

	    if (!cp)
		cp = HTLoadedDocumentURL();	/* @@@ Try both? - kw */
	    BStrCat0(command, "Referer: ");
	    if (isLYNXIMGMAP(cp)) {
		char *pound = findPoundSelector(cp);
		int nn = (pound ? (int) (pound - cp) : (int) strlen(cp));

		HTSABCat(&command, cp + LEN_LYNXIMGMAP, nn);
	    } else {
		BStrCat0(command, cp);
	    }
	    HTBprintf(&command, "%c%c", CR, LF);
	} {
	    char *abspath;
	    char *docname;
	    char *hostname;
	    char *colon;
	    int portnumber;
	    char *auth, *cookie = NULL;
	    BOOL secure = (BOOL) (StrNCmp(anAnchor->address, "https", 5)
				  ? FALSE
				  : TRUE);

	    abspath = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION);
	    docname = HTParse(arg, "", PARSE_PATH);
	    hostname = HTParse(arg, "", PARSE_HOST);
	    if (hostname &&
		NULL != (colon = HTParsePort(hostname, &portnumber))) {
		*colon = '\0';	/* Chop off port number */
	    } else if (!StrNCmp(arg, "https", 5)) {
		portnumber = HTTPS_PORT;
	    } else {
		portnumber = HTTP_PORT;
	    }

	    /*
	     * Add Authorization, Proxy-Authorization, and/or Cookie headers,
	     * if applicable.
	     */
	    if (using_proxy) {
		/*
		 * If we are using a proxy, first determine if we should
		 * include an Authorization header and/or Cookie header for the
		 * ultimate target of this request.  - FM & AJL
		 */
		char *host2 = NULL, *path2 = NULL;
		int port2 = (StrNCmp(docname, "https", 5) ?
			     HTTP_PORT : HTTPS_PORT);

		host2 = HTParse(docname, "", PARSE_HOST);
		path2 = HTParse(docname, "", PARSE_PATH | PARSE_PUNCTUATION);
		if ((colon = HTParsePort(host2, &port2)) != NULL) {
		    /* Use non-default port number */
		    *colon = '\0';
		}

		/*
		 * This composeAuth() does file access, i.e., for the ultimate
		 * target of the request.  - AJL
		 */
		auth_proxy = NO;
		auth = HTAA_composeAuth(host2, port2, path2, auth_proxy);
		if (auth == NULL) {
		    CTRACE((tfp, "HTTP: Not sending authorization (yet).\n"));
		} else if (*auth != '\0') {
		    /*
		     * We have an Authorization header to be included.
		     */
		    HTBprintf(&command, "%s%c%c", auth, CR, LF);
		    CTRACE((tfp, "HTTP: Sending authorization: %s\n", auth));
		} else {
		    /*
		     * The user either cancelled or made a mistake with the
		     * username and password prompt.
		     */
		    if (!(traversal || dump_output_immediately) &&
			HTConfirm(CONFIRM_WO_PASSWORD)) {
			show_401 = TRUE;
		    } else {
			if (traversal || dump_output_immediately)
			    HTAlert(FAILED_NEED_PASSWD);
#ifdef USE_SSL
			if (did_connect)
			    HTTP_NETCLOSE(s, handle);
#endif /* USE_SSL */
			BStrFree(command);
			FREE(hostname);
			FREE(docname);
			FREE(abspath);
			FREE(host2);
			FREE(path2);
			status = HT_NOT_LOADED;
			goto done;
		    }
		}
		/*
		 * Add 'Cookie:' header, if it's HTTP or HTTPS document being
		 * proxied.
		 */
		if (!StrNCmp(docname, "http", 4)) {
		    cookie = LYAddCookieHeader(host2, path2, port2, secure);
		}
		FREE(host2);
		FREE(path2);
		/*
		 * The next composeAuth() will be for the proxy.  - AJL
		 */
		auth_proxy = YES;
	    } else {
		/*
		 * Add cookie for a non-proxied request.  - FM
		 */
		cookie = LYAddCookieHeader(hostname, abspath, portnumber, secure);
		auth_proxy = NO;
	    }
	    /*
	     * If we do have a cookie set, add it to the request buffer.  - FM
	     */
	    if (cookie != NULL) {
		if (*cookie != '$') {
		    /*
		     * It's a historical cookie, so signal to the server that
		     * we support modern cookies.  - FM
		     */
		    BStrCat0(command, "Cookie2: $Version=\"1\"");
		    BStrCat0(command, crlf);
		    CTRACE((tfp, "HTTP: Sending Cookie2: $Version =\"1\"\n"));
		}
		if (*cookie != '\0') {
		    /*
		     * It's not a zero-length string, so add the header.  Note
		     * that any folding of long strings has been done already
		     * in LYCookie.c.  - FM
		     */
		    BStrCat0(command, "Cookie: ");
		    BStrCat0(command, cookie);
		    BStrCat0(command, crlf);
		    CTRACE((tfp, "HTTP: Sending Cookie: %s\n", cookie));
		}
		FREE(cookie);
	    }
	    FREE(abspath);

	    /*
	     * If we are using a proxy, auth_proxy should be YES, and we check
	     * here whether we want a Proxy-Authorization header for it.  If we
	     * are not using a proxy, auth_proxy should still be NO, and we
	     * check here for whether we want an Authorization header.  - FM &
	     * AJL
	     */
	    if ((auth = HTAA_composeAuth(hostname,
					 portnumber,
					 docname,
					 auth_proxy)) != NULL &&
		*auth != '\0') {
		/*
		 * If auth is not NULL nor zero-length, it's an Authorization
		 * or Proxy-Authorization header to be included.  - FM
		 */
		HTBprintf(&command, "%s%c%c", auth, CR, LF);
		CTRACE((tfp, (auth_proxy ?
			      "HTTP: Sending proxy authorization: %s\n" :
			      "HTTP: Sending authorization: %s\n"),
			auth));
	    } else if (auth && *auth == '\0') {
		/*
		 * If auth is a zero-length string, the user either cancelled
		 * or goofed at the username and password prompt.  - FM
		 */
		if (!(traversal || dump_output_immediately) && HTConfirm(CONFIRM_WO_PASSWORD)) {
		    if (auth_proxy == TRUE) {
			show_407 = TRUE;
		    } else {
			show_401 = TRUE;
		    }
		} else {
		    if (traversal || dump_output_immediately)
			HTAlert(FAILED_NEED_PASSWD);
		    BStrFree(command);
		    FREE(hostname);
		    FREE(docname);
		    status = HT_NOT_LOADED;
		    goto done;
		}
	    } else {
		CTRACE((tfp, (auth_proxy ?
			      "HTTP: Not sending proxy authorization (yet).\n" :
			      "HTTP: Not sending authorization (yet).\n")));
	    }
	    FREE(hostname);
	    FREE(docname);
	}
    }

    if (
#ifdef USE_SSL
	   !do_connect &&
#endif /* USE_SSL */
	   do_post) {
	CTRACE((tfp, "HTTP: Doing post, content-type '%s'\n",
		anAnchor->post_content_type
		? anAnchor->post_content_type
		: "lose"));
	HTBprintf(&command, "Content-type: %s%c%c",
		  anAnchor->post_content_type
		  ? anAnchor->post_content_type
		  : "lose",
		  CR, LF);

	HTBprintf(&command, "Content-length: %d%c%c",
		  !isBEmpty(anAnchor->post_data)
		  ? BStrLen(anAnchor->post_data)
		  : 0,
		  CR, LF);

	BStrCat0(command, crlf);	/* Blank line means "end" of headers */

	BStrCat(command, anAnchor->post_data);
    } else
	BStrCat0(command, crlf);	/* Blank line means "end" of headers */

    if (TRACE) {
	CTRACE((tfp, "Writing:\n"));
	trace_bstring(command);
#ifdef USE_SSL
	CTRACE((tfp, "%s",
		(anAnchor->post_data && !do_connect ? crlf : "")));
#else
	CTRACE((tfp, "%s",
		(anAnchor->post_data ? crlf : "")));
#endif /* USE_SSL */
	CTRACE((tfp, "----------------------------------\n"));
    }

    _HTProgress(gettext("Sending HTTP request."));

#ifdef    NOT_ASCII		/* S/390 -- gil -- 0548 */
    {
	char *p2;

	for (p2 = BStrData(command);
	     p2 < BStrData(command) + BStrLen(command);
	     p2++)
	    *p2 = TOASCII(*p2);
    }
#endif /* NOT_ASCII */
    status = (int) HTTP_NETWRITE(s,
				 BStrData(command),
				 BStrLen(command),
				 handle);
    BStrFree(command);
    FREE(linebuf);
    if (status <= 0) {
	if (status == 0) {
	    CTRACE((tfp, "HTTP: Got status 0 in initial write\n"));
	    /* Do nothing. */
	} else if ((SOCKET_ERRNO == ENOTCONN ||
		    SOCKET_ERRNO == ECONNRESET ||
		    SOCKET_ERRNO == EPIPE) &&
		   !already_retrying &&
	    /* Don't retry if we're posting. */ !do_post) {
	    /*
	     * Arrrrgh, HTTP 0/1 compatibility problem, maybe.
	     */
	    CTRACE((tfp,
		    "HTTP: BONZO ON WRITE Trying again with HTTP0 request.\n"));
	    _HTProgress(RETRYING_AS_HTTP0);
	    HTTP_NETCLOSE(s, handle);
	    extensions = NO;
	    already_retrying = TRUE;
	    goto try_again;
	} else {
	    CTRACE((tfp,
		    "HTTP: Hit unexpected network WRITE error; aborting connection.\n"));
	    HTTP_NETCLOSE(s, handle);
	    status = -1;
	    HTAlert(gettext("Unexpected network write error; connection aborted."));
	    goto done;
	}
    }

    CTRACE((tfp, "HTTP: WRITE delivered OK\n"));
    _HTProgress(gettext("HTTP request sent; waiting for response."));

    /*    Read the first line of the response
     * -----------------------------------
     */
    {
	/* Get numeric status etc */
	BOOL end_of_file = NO;
	int buffer_length = INIT_LINE_SIZE;

	line_buffer = typecallocn(char, (size_t) buffer_length);

	if (line_buffer == NULL)
	    outofmem(__FILE__, "HTLoadHTTP");

	HTReadProgress(bytes_already_read = 0, (off_t) 0);
	do {			/* Loop to read in the first line */
	    /*
	     * Extend line buffer if necessary for those crazy WAIS URLs ;-)
	     */
	    if (buffer_length - length < LINE_EXTEND_THRESH) {
		buffer_length = buffer_length + buffer_length;
		line_buffer =
		    (char *) realloc(line_buffer, ((unsigned) buffer_length *
						   sizeof(char)));

		if (line_buffer == NULL)
		    outofmem(__FILE__, "HTLoadHTTP");
	    }
	    CTRACE((tfp, "HTTP: Trying to read %d\n", buffer_length - length - 1));
	    status = HTTP_NETREAD(s,
				  line_buffer + length,
				  (buffer_length - length - 1),
				  handle);
	    CTRACE((tfp, "HTTP: Read %d\n", status));
	    if (status <= 0) {
		/*
		 * Retry if we get nothing back too.
		 * Bomb out if we get nothing twice.
		 */
		if (status == HT_INTERRUPTED) {
		    CTRACE((tfp, "HTTP: Interrupted initial read.\n"));
		    _HTProgress(CONNECTION_INTERRUPTED);
		    HTTP_NETCLOSE(s, handle);
		    status = HT_NO_DATA;
		    goto clean_up;
		} else if (status < 0 &&
			   (SOCKET_ERRNO == ENOTCONN ||
#ifdef _WINDOWS			/* 1997/11/09 (Sun) 16:59:58 */
			    SOCKET_ERRNO == ETIMEDOUT ||
#endif
			    SOCKET_ERRNO == ECONNRESET ||
			    SOCKET_ERRNO == EPIPE) &&
			   !already_retrying && !do_post) {
		    /*
		     * Arrrrgh, HTTP 0/1 compability problem, maybe.
		     */
		    CTRACE((tfp,
			    "HTTP: BONZO Trying again with HTTP0 request.\n"));
		    HTTP_NETCLOSE(s, handle);
		    FREE(line_buffer);
		    FREE(line_kept_clean);

		    extensions = NO;
		    already_retrying = TRUE;
		    _HTProgress(RETRYING_AS_HTTP0);
		    goto try_again;
		}
#ifdef USE_SSL
		else if ((SSLerror = ERR_get_error()) != 0) {
		    CTRACE((tfp,
			    "HTTP: Hit unexpected network read error; aborting connection; status %d:%s.\n",
			    status, ERR_error_string(SSLerror, NULL)));
		    HTAlert(gettext("Unexpected network read error; connection aborted."));
		    HTTP_NETCLOSE(s, handle);
		    status = -1;
		    goto clean_up;
		}
#endif
		else {
		    CTRACE((tfp,
			    "HTTP: Hit unexpected network read error; aborting connection; status %d.\n",
			    status));
		    HTAlert(gettext("Unexpected network read error; connection aborted."));
		    HTTP_NETCLOSE(s, handle);
		    status = -1;
		    goto clean_up;
		}
	    }
#ifdef    NOT_ASCII		/* S/390 -- gil -- 0564 */
	    {
		char *p2;

		for (p2 = line_buffer + length;
		     p2 < line_buffer + length + status;
		     p2++)
		    *p2 = FROMASCII(*p2);
	    }
#endif /* NOT_ASCII */

	    bytes_already_read += status;
	    HTReadProgress(bytes_already_read, (off_t) 0);

#ifdef UCX			/* UCX returns -1 on EOF */
	    if (status == 0 || status == -1)
#else
	    if (status == 0)
#endif
	    {
		break;
	    }
	    line_buffer[length + status] = 0;

	    if (line_buffer) {
		FREE(line_kept_clean);
		line_kept_clean = (char *) malloc((unsigned) buffer_length *
						  sizeof(char));

		if (line_kept_clean == NULL)
		    outofmem(__FILE__, "HTLoadHTTP");
		MemCpy(line_kept_clean, line_buffer, buffer_length);
#ifdef SH_EX			/* FIX BUG by kaz@maczuka.hitachi.ibaraki.jp */
		real_length_of_line = length + status;
#endif
	    }

	    eol = StrChr(line_buffer + length, LF);
	    /* Do we *really* want to do this? */
	    if (eol && eol != line_buffer && *(eol - 1) == CR)
		*(eol - 1) = ' ';

	    length = length + status;

	    /* Do we really want to do *this*? */
	    if (eol)
		*eol = 0;	/* Terminate the line */
	}
	/* All we need is the first line of the response.  If it's a HTTP/1.0
	 * response, then the first line will be absurdly short and therefore
	 * we can safely gate the number of bytes read through this code (as
	 * opposed to below) to ~1000.
	 *
	 * Well, let's try 100.
	 */
	while (!eol && !end_of_file && bytes_already_read < 100);
    }				/* Scope of loop variables */

    /* save total length, in case we decide later to show it all - kw */
    rawlength = length;

    /*    We now have a terminated unfolded line.  Parse it.
     * --------------------------------------------------
     */
    CTRACE((tfp, "HTTP: Rx: %s\n", line_buffer));

    /*
     * Kludge to work with old buggy servers and the VMS Help gateway.  They
     * can't handle the third word, so we try again without it.
     */
    if (extensions &&		/* Old buggy server or Help gateway? */
	(0 == StrNCmp(line_buffer, "<TITLE>Bad File Request</TITLE>", 31) ||
	 0 == StrNCmp(line_buffer, "Address should begin with", 25) ||
	 0 == StrNCmp(line_buffer, "<TITLE>Help ", 12) ||
	 0 == strcmp(line_buffer,
		     "Document address invalid or access not authorised"))) {
	FREE(line_buffer);
	FREE(line_kept_clean);
	extensions = NO;
	already_retrying = TRUE;
	CTRACE((tfp, "HTTP: close socket %d to retry with HTTP0\n", s));
	HTTP_NETCLOSE(s, handle);
	/* print a progress message */
	_HTProgress(RETRYING_AS_HTTP0);
	goto try_again;
    } {
	int fields;
	char server_version[VERSION_LENGTH + 1];

	server_version[0] = 0;

	fields = sscanf(line_buffer, "%20s %d",
			server_version,
			&server_status);

	CTRACE((tfp, "HTTP: Scanned %d fields from line_buffer\n", fields));

	if (non_empty(http_error_file)) {
	    /* Make the status code externally available */
	    FILE *error_file;

#ifdef SERVER_STATUS_ONLY
	    error_file = fopen(http_error_file, TXT_W);
	    if (error_file) {	/* Managed to open the file */
		fprintf(error_file, "error=%d\n", server_status);
		fclose(error_file);
	    }
#else
	    error_file = fopen(http_error_file, TXT_A);
	    if (error_file) {	/* Managed to open the file */
		fprintf(error_file, "   URL=%s (%s)\n", url, METHOD);
		fprintf(error_file, "STATUS=%s\n", line_buffer);
		fclose(error_file);
	    }
#endif /* SERVER_STATUS_ONLY */
	}

	/*
	 * Rule out a non-HTTP/1.n reply as best we can.
	 */
	if (fields < 2 || !server_version[0] || server_version[0] != 'H' ||
	    server_version[1] != 'T' || server_version[2] != 'T' ||
	    server_version[3] != 'P' || server_version[4] != '/' ||
	    server_version[6] != '.') {
	    /*
	     * Ugh!  An HTTP0 reply,
	     */
	    HTAtom *encoding;

	    CTRACE((tfp, "--- Talking HTTP0.\n"));

	    format_in = HTFileFormat(url, &encoding, NULL);
	    /*
	     * Treat all plain text as HTML.  This sucks but its the only
	     * solution without without looking at content.
	     */
	    if (!StrNCmp(HTAtom_name(format_in), STR_PLAINTEXT, 10)) {
		CTRACE((tfp, "HTTP: format_in being changed to text/HTML\n"));
		format_in = WWW_HTML;
	    }
	    if (!IsUnityEnc(encoding)) {
		/*
		 * Change the format to that for "www/compressed".
		 */
		CTRACE((tfp, "HTTP: format_in is '%s',\n", HTAtom_name(format_in)));
		StrAllocCopy(anAnchor->content_type, HTAtom_name(format_in));
		StrAllocCopy(anAnchor->content_encoding, HTAtom_name(encoding));
		format_in = HTAtom_for("www/compressed");
		CTRACE((tfp, "        Treating as '%s' with encoding '%s'\n",
			"www/compressed", HTAtom_name(encoding)));
	    }

	    start_of_data = line_kept_clean;
	} else {
	    /*
	     * Set up to decode full HTTP/1.n response.  - FM
	     */
	    format_in = HTAtom_for("www/mime");
	    CTRACE((tfp, "--- Talking HTTP1.\n"));

	    /*
	     * We set start_of_data to "" when !eol here because there will be
	     * a put_block done below; we do *not* use the value of
	     * start_of_data (as a pointer) in the computation of length (or
	     * anything else) when !eol.  Otherwise, set the value of length to
	     * what we have beyond eol (i.e., beyond the status line).  - FM
	     */
	    if (eol != 0) {
		start_of_data = (eol + 1);
	    } else {
		start_of_data = empty;
	    }
	    length = (eol
		      ? length - (int) (start_of_data - line_buffer)
		      : 0);

	    /*
	     * Trim trailing spaces in line_buffer so that we can use it in
	     * messages which include the status line.  - FM
	     */
	    while (line_buffer[strlen(line_buffer) - 1] == ' ')
		line_buffer[strlen(line_buffer) - 1] = '\0';

	    /*
	     * Take appropriate actions based on the status.  - FM
	     */
	    switch (server_status / 100) {
	    case 1:
		/*
		 * HTTP/1.1 Informational statuses.
		 * 100 Continue.
		 * 101 Switching Protocols.
		 * > 101 is unknown.
		 * We should never get these, and they have only the status
		 * line and possibly other headers, so we'll deal with them by
		 * showing the full header to the user as text/plain.  - FM
		 */
		HTAlert(gettext("Got unexpected Informational Status."));
		do_head = TRUE;
		break;

	    case 2:
		/*
		 * Good:  Got MIME object!  (Successful) - FM
		 */
		if (do_head) {
		    /*
		     * If HEAD was requested, show headers (and possibly bogus
		     * body) for all 2xx status codes as text/plain - KW
		     */
		    HTProgress(line_buffer);
		    break;
		}
		switch (server_status) {
		case 204:
		    /*
		     * No Content.
		     */
		    HTAlert(line_buffer);
		    HTTP_NETCLOSE(s, handle);
		    HTNoDataOK = 1;
		    status = HT_NO_DATA;
		    goto clean_up;

		case 205:
		    /*
		     * Reset Content.  The server has fulfilled the request but
		     * nothing is returned and we should reset any form
		     * content.  We'll instruct the user to do that, and
		     * restore the current document.  - FM
		     */
		    HTAlert(gettext("Request fulfilled.  Reset Content."));
		    HTTP_NETCLOSE(s, handle);
		    status = HT_NO_DATA;
		    goto clean_up;

		case 206:
		    /*
		     * Partial Content.  We didn't send a Range so something
		     * went wrong somewhere.  Show the status message and
		     * restore the current document.  - FM
		     */
		    HTAlert(line_buffer);
		    HTTP_NETCLOSE(s, handle);
		    status = HT_NO_DATA;
		    goto clean_up;

		default:
		    /*
		     * 200 OK.
		     * 201 Created.
		     * 202 Accepted.
		     * 203 Non-Authoritative Information.
		     * > 206 is unknown.
		     * All should return something to display.
		     */
#if defined(USE_SSL)		/* && !defined(DISABLE_NEWS)  _H */
		    if (do_connect) {
			CTRACE((tfp,
				"HTTP: Proxy tunnel to '%s' established.\n",
				connect_host));
			do_connect = FALSE;
			url = connect_url;
			FREE(line_buffer);
			FREE(line_kept_clean);
#ifndef DISABLE_NEWS
			if (!StrNCmp(connect_url, "snews", 5)) {
			    CTRACE((tfp,
				    "      Will attempt handshake and snews connection.\n"));
			    status = HTNewsProxyConnect(s, url, anAnchor,
							format_out, sink);
			    goto done;
			}
#endif /* DISABLE_NEWS */
			did_connect = TRUE;
			already_retrying = TRUE;
			eol = 0;
			length = 0;
			doing_redirect = FALSE;
			permanent_redirection = FALSE;
			target = NULL;
			CTRACE((tfp,
				"      Will attempt handshake and resubmit headers.\n"));
			goto use_tunnel;
		    }
#endif /* USE_SSL */
		    HTProgress(line_buffer);
		}		/* case 2 switch */
		break;

	    case 3:
		/*
		 * Various forms of Redirection.  - FM
		 * 300 Multiple Choices.
		 * 301 Moved Permanently.
		 * 302 Found (temporary; we can, and do, use GET).
		 * 303 See Other (temporary; always use GET).
		 * 304 Not Modified.
		 * 305 Use Proxy.
		 * 306 Set Proxy.
		 * 307 Temporary Redirect with method retained.
		 * > 308 is unknown.
		 */
		if (no_url_redirection || do_head || keep_mime_headers) {
		    /*
		     * If any of these flags are set, we do not redirect, but
		     * instead show what was returned to the user as
		     * text/plain.  - FM
		     */
		    HTProgress(line_buffer);
		    break;
		}

		if (server_status == 300) {	/* Multiple Choices */
		    /*
		     * For client driven content negotiation.  The server
		     * should be sending some way for the user-agent to make a
		     * selection, so we'll show the user whatever the server
		     * returns.  There might be a Location:  header with the
		     * server's preference present, but the choice should be up
		     * to the user, someday based on an Alternates:  header,
		     * and a body always should be present with descriptions
		     * and links for the choices (i.e., we use the latter, for
		     * now).  - FM
		     */
		    HTAlert(line_buffer);
		    if (traversal) {
			HTTP_NETCLOSE(s, handle);
			status = -1;
			goto clean_up;
		    }
		    if (!dump_output_immediately &&
			format_out == HTAtom_for("www/download")) {
			/*
			 * Convert a download request to a presentation request
			 * for interactive users.  - FM
			 */
			format_out = WWW_PRESENT;
		    }
		    break;
		}

		if (server_status == 304) {	/* Not Modified */
		    /*
		     * We didn't send an "If-Modified-Since" header, so this
		     * status is inappropriate.  We'll deal with it by showing
		     * the full header to the user as text/plain.  - FM
		     */
		    HTAlert(gettext("Got unexpected 304 Not Modified status."));
		    do_head = TRUE;
		    break;
		}

		if (server_status == 305 ||
		    server_status == 306 ||
		    server_status > 307) {
		    /*
		     * Show user the content, if any, for 305, 306, or unknown
		     * status.  - FM
		     */
		    HTAlert(line_buffer);
		    if (traversal) {
			HTTP_NETCLOSE(s, handle);
			status = -1;
			goto clean_up;
		    }
		    if (!dump_output_immediately &&
			format_out == HTAtom_for("www/download")) {
			/*
			 * Convert a download request to a presentation request
			 * for interactive users.  - FM
			 */
			format_out = WWW_PRESENT;
		    }
		    break;
		}

		/*
		 * We do not load the file, but read the headers for the
		 * "Location:", check out that redirecting_url and if it's
		 * acceptible (e.g., not a telnet URL when we have that
		 * disabled), initiate a new fetch.  If that's another
		 * redirecting_url, we'll repeat the checks, and fetch
		 * initiations if acceptible, until we reach the actual URL, or
		 * the redirection limit set in HTAccess.c is exceeded.  If the
		 * status was 301 indicating that the relocation is permanent,
		 * we set the permanent_redirection flag to make it permanent
		 * for the current anchor tree (i.e., will persist until the
		 * tree is freed or the client exits).  If the redirection
		 * would include POST content, we seek confirmation from an
		 * interactive user, with option to use 303 for 301 (but not
		 * for 307), and otherwise refuse the redirection.  We also
		 * don't allow permanent redirection if we keep POST content.
		 * If we don't find the Location header or it's value is
		 * zero-length, we display whatever the server returned, and
		 * the user should RELOAD that to try again, or make a
		 * selection from it if it contains links, or Left-Arrow to the
		 * previous document.  - FM
		 */
		{
		    if ((dump_output_immediately || traversal) &&
			do_post &&
			server_status != 303 &&
			server_status != 302 &&
			server_status != 301) {
			/*
			 * Don't redirect POST content without approval from an
			 * interactive user.  - FM
			 */
			HTTP_NETCLOSE(s, handle);
			status = -1;
			HTAlert(gettext("Redirection of POST content requires user approval."));
			if (traversal)
			    HTProgress(line_buffer);
			goto clean_up;
		    }

		    HTProgress(line_buffer);
		    if (server_status == 301) {		/* Moved Permanently */
			if (do_post) {
			    /*
			     * Don't make the redirection permanent if we have
			     * POST content.  - FM
			     */
			    CTRACE((tfp,
				    "HTTP: Have POST content.  Treating 301 (Permanent) as Temporary.\n"));
			    HTAlert(gettext("Have POST content.  Treating Permanent Redirection as Temporary.\n"));
			} else {
			    permanent_redirection = TRUE;
			}
		    }
		    doing_redirect = TRUE;

		    break;
		}

	    case 4:
		/*
		 * "I think I goofed!" (Client Error) - FM
		 */
		switch (server_status) {
		case 401:	/* Unauthorized */
		    /*
		     * Authorization for origin server required.  If show_401
		     * is set, proceed to showing the 401 body.  Otherwise, if
		     * we can set up authorization based on the
		     * WWW-Authenticate header, and the user provides a
		     * username and password, try again.  Otherwise, check
		     * whether to show the 401 body or restore the current
		     * document - FM
		     */
		    if (show_401)
			break;
		    if (HTAA_shouldRetryWithAuth(start_of_data, (size_t)
						 length, s, NO)) {

			HTTP_NETCLOSE(s, handle);
			if (dump_output_immediately &&
			    !HTAA_HaveUserinfo(HTParse(arg, "", PARSE_HOST)) &&
			    !authentication_info[0]) {
			    fprintf(stderr,
				    "HTTP: Access authorization required.\n");
			    fprintf(stderr,
				    "       Use the -auth=id:pw parameter.\n");
			    status = HT_NO_DATA;
			    goto clean_up;
			}

			CTRACE((tfp, "%s %d %s\n",
				"HTTP: close socket", s,
				"to retry with Access Authorization"));

			_HTProgress(gettext("Retrying with access authorization information."));
			FREE(line_buffer);
			FREE(line_kept_clean);
#ifdef USE_SSL
			if (using_proxy && !StrNCmp(url, "https://", 8)) {
			    url = arg;
			    do_connect = TRUE;
			    did_connect = FALSE;
			}
#endif /* USE_SSL */
			goto try_again;
		    } else if (!(traversal || dump_output_immediately) &&
			       HTConfirm(gettext("Show the 401 message body?"))) {
			break;
		    } else {
			if (traversal || dump_output_immediately)
			    HTAlert(FAILED_RETRY_WITH_AUTH);
			HTTP_NETCLOSE(s, handle);
			status = -1;
			goto clean_up;
		    }

		case 407:
		    /*
		     * Authorization for proxy server required.  If we are not
		     * in fact using a proxy, or show_407 is set, proceed to
		     * showing the 407 body.  Otherwise, if we can set up
		     * authorization based on the Proxy-Authenticate header,
		     * and the user provides a username and password, try
		     * again.  Otherwise, check whether to show the 401 body or
		     * restore the current document.  - FM & AJL
		     */
		    if (!using_proxy || show_407)
			break;
		    if (HTAA_shouldRetryWithAuth(start_of_data, (size_t)
						 length, s, YES)) {

			HTTP_NETCLOSE(s, handle);
			if (dump_output_immediately && !proxyauth_info[0]) {
			    fprintf(stderr,
				    "HTTP: Proxy authorization required.\n");
			    fprintf(stderr,
				    "       Use the -pauth=id:pw parameter.\n");
			    status = HT_NO_DATA;
			    goto clean_up;
			}

			CTRACE((tfp, "%s %d %s\n",
				"HTTP: close socket", s,
				"to retry with Proxy Authorization"));

			_HTProgress(HTTP_RETRY_WITH_PROXY);
			FREE(line_buffer);
			FREE(line_kept_clean);
			goto try_again;
		    } else if (!(traversal || dump_output_immediately) &&
			       HTConfirm(gettext("Show the 407 message body?"))) {
			if (!dump_output_immediately &&
			    format_out == HTAtom_for("www/download")) {
			    /*
			     * Convert a download request to a presentation
			     * request for interactive users.  - FM
			     */
			    format_out = WWW_PRESENT;
			}
			break;
		    } else {
			if (traversal || dump_output_immediately)
			    HTAlert(FAILED_RETRY_WITH_PROXY);
			HTTP_NETCLOSE(s, handle);
			status = -1;
			goto clean_up;
		    }

		case 408:
		    /*
		     * Request Timeout.  Show the status message and restore
		     * the current document.  - FM
		     */
		    HTAlert(line_buffer);
		    HTTP_NETCLOSE(s, handle);
		    status = HT_NO_DATA;
		    goto clean_up;

		default:
		    /*
		     * 400 Bad Request.
		     * 402 Payment Required.
		     * 403 Forbidden.
		     * 404 Not Found.
		     * 405 Method Not Allowed.
		     * 406 Not Acceptable.
		     * 409 Conflict.
		     * 410 Gone.
		     * 411 Length Required.
		     * 412 Precondition Failed.
		     * 413 Request Entity Too Large.
		     * 414 Request-URI Too Long.
		     * 415 Unsupported Media Type.
		     * 416 List Response (for content negotiation).
		     * > 416 is unknown.
		     * Show the status message, and display the returned text
		     * if we are not doing a traversal.  - FM
		     */
		    HTAlert(line_buffer);
		    if (traversal) {
			HTTP_NETCLOSE(s, handle);
			status = -1;
			goto clean_up;
		    }
		    if (!dump_output_immediately &&
			format_out == HTAtom_for("www/download")) {
			/*
			 * Convert a download request to a presentation request
			 * for interactive users.  - FM
			 */
			format_out = WWW_PRESENT;
		    }
		    break;
		}		/* case 4 switch */
		break;

	    case 5:
		/*
		 * "I think YOU goofed!" (server error)
		 * 500 Internal Server Error
		 * 501 Not Implemented
		 * 502 Bad Gateway
		 * 503 Service Unavailable
		 * 504 Gateway Timeout
		 * 505 HTTP Version Not Supported
		 * > 505 is unknown.
		 * Should always include a message, which we always should
		 * display.  - FM
		 */
		HTAlert(line_buffer);
		if (traversal) {
		    HTTP_NETCLOSE(s, handle);
		    status = -1;
		    goto clean_up;
		}
		if (!dump_output_immediately &&
		    format_out == HTAtom_for("www/download")) {
		    /*
		     * Convert a download request to a presentation request for
		     * interactive users.  - FM
		     */
		    format_out = WWW_PRESENT;
		}
		break;

	    default:
		/*
		 * Bad or unknown server_status number.  Take a chance and hope
		 * there is something to display.  - FM
		 */
		HTAlert(gettext("Unknown status reply from server!"));
		HTAlert(line_buffer);
		if (traversal) {
		    HTTP_NETCLOSE(s, handle);
		    status = -1;
		    goto clean_up;
		}
		if (!dump_output_immediately &&
		    format_out == HTAtom_for("www/download")) {
		    /*
		     * Convert a download request to a presentation request for
		     * interactive users.  - FM
		     */
		    format_out = WWW_PRESENT;
		}
		break;
	    }			/* Switch on server_status/100 */

	}			/* Full HTTP reply */
    }				/* scope of fields */

    /*
     * The user may have pressed the 'z'ap key during the pause caused by one
     * of the HTAlerts above if the server reported an error, to skip loading
     * of the error response page.  Checking here before setting up the stream
     * stack and feeding it data avoids doing unnecessary work, it also can
     * avoid unnecessarily pushing a loaded document out of the cache to make
     * room for the unwanted error page.  - kw
     */
    if (HTCheckForInterrupt()) {
	HTTP_NETCLOSE(s, handle);
	if (doing_redirect) {
	    /*
	     * Impatient user.  - FM
	     */
	    CTRACE((tfp, "HTTP: Interrupted followup read.\n"));
	    _HTProgress(CONNECTION_INTERRUPTED);
	}
	status = HT_INTERRUPTED;
	goto clean_up;
    }
    /*
     * Set up the stream stack to handle the body of the message.
     */
    if (do_head || keep_mime_headers) {
	/*
	 * It was a HEAD request, or we want the headers and source.
	 */
	start_of_data = line_kept_clean;
#ifdef SH_EX			/* FIX BUG by kaz@maczuka.hitachi.ibaraki.jp */
/* GIF file contains \0, so strlen does not return the data length */
	length = real_length_of_line;
#else
	length = rawlength;
#endif
	format_in = HTAtom_for(STR_PLAINTEXT);

    } else if (doing_redirect) {

	format_in = HTAtom_for("message/x-http-redirection");
	StrAllocCopy(anAnchor->content_type, HTAtom_name(format_in));
	if (traversal) {
	    format_out = WWW_DEBUG;
	    if (!sink)
		sink = HTErrorStream();
	} else if (!dump_output_immediately &&
		   format_out == HTAtom_for("www/download")) {
	    /*
	     * Convert a download request to a presentation request for
	     * interactive users.  - FM
	     */
	    format_out = WWW_PRESENT;
	}
    }

    target = HTStreamStack(format_in,
			   format_out,
			   sink, anAnchor);

    if (target == NULL) {
	char *buffer = NULL;

	HTTP_NETCLOSE(s, handle);
	HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
		   HTAtom_name(format_in), HTAtom_name(format_out));
	_HTProgress(buffer);
	FREE(buffer);
	status = -1;
	goto clean_up;
    }

    /*
     * Recycle the first chunk of data, in all cases.
     */
    (*target->isa->put_block) (target, start_of_data, length);

    /*
     * Go pull the bulk of the data down.
     */
    rv = HTCopy(anAnchor, s, (void *) handle, target);

    /*
     * If we get here with doing_redirect set, it means that we were looking
     * for a Location header.  We either have got it now in redirecting_url -
     * in that case the stream should not have loaded any data.  Or we didn't
     * get it, in that case the stream may have presented the message body
     * normally.  - kw
     */

    if (rv == -1) {
	/*
	 * Intentional interrupt before data were received, not an error
	 */
	if (doing_redirect && traversal)
	    status = -1;
	else
	    status = HT_INTERRUPTED;
	HTTP_NETCLOSE(s, handle);
	goto clean_up;
    }

    if (rv == -2) {
	/*
	 * Aw hell, a REAL error, maybe cuz it's a dumb HTTP0 server
	 */
	(*target->isa->_abort) (target, NULL);
	if (doing_redirect && redirecting_url) {
	    /*
	     * Got a location before the error occurred?  Then consider it an
	     * interrupt but proceed below as normal.  - kw
	     */
	    /* do nothing here */
	} else {
	    HTTP_NETCLOSE(s, handle);
	    if (!doing_redirect && !already_retrying && !do_post) {
		CTRACE((tfp, "HTTP: Trying again with HTTP0 request.\n"));
		/*
		 * May as well consider it an interrupt -- right?
		 */
		FREE(line_buffer);
		FREE(line_kept_clean);
		extensions = NO;
		already_retrying = TRUE;
		_HTProgress(RETRYING_AS_HTTP0);
		goto try_again;
	    } else {
		status = HT_NOT_LOADED;
		goto clean_up;
	    }
	}
    }

    /*
     * Free if complete transmission (socket was closed before return).  Close
     * socket if partial transmission (was freed on abort).
     */
    if (rv != HT_INTERRUPTED && rv != -2) {
	(*target->isa->_free) (target);
    } else {
	HTTP_NETCLOSE(s, handle);
    }

    if (doing_redirect) {
	if (redirecting_url) {
	    /*
	     * Set up for checking redirecting_url in LYGetFile.c for
	     * restrictions before we seek the document at that Location.  - FM
	     */
	    CTRACE((tfp, "HTTP: Picked up location '%s'\n",
		    redirecting_url));
	    if (rv == HT_INTERRUPTED) {
		/*
		 * Intentional interrupt after data were received, not an error
		 * (probably).  We take it as a user request to abandon the
		 * redirection chain.
		 *
		 * This could reasonably be changed (by just removing this
		 * block), it would make sense if there are redirecting
		 * resources that "hang" after sending the headers.  - kw
		 */
		FREE(redirecting_url);
		CTRACE((tfp, "HTTP: Interrupted followup read.\n"));
		status = HT_INTERRUPTED;
		goto clean_up;
	    }
	    HTProgress(line_buffer);
	    if (server_status == 305) {		/* Use Proxy */
		/*
		 * Make sure the proxy field ends with a slash.  - FM
		 */
		if (redirecting_url[strlen(redirecting_url) - 1]
		    != '/')
		    StrAllocCat(redirecting_url, "/");
		/*
		 * Append our URL.  - FM
		 */
		StrAllocCat(redirecting_url, anAnchor->address);
		CTRACE((tfp, "HTTP: Proxy URL is '%s'\n",
			redirecting_url));
	    }
	    if (!do_post ||
		server_status == 303 ||
		server_status == 302) {
		/*
		 * We don't have POST content (nor support PUT or DELETE), or
		 * the status is "See Other" or "General Redirection" and we
		 * can convert to GET, so go back and check out the new URL.  -
		 * FM
		 */
		status = HT_REDIRECTING;
		goto clean_up;
	    }
	    /*
	     * Make sure the user wants to redirect the POST content, or treat
	     * as GET - FM & DK
	     */
	    switch (HTConfirmPostRedirect(redirecting_url,
					  server_status)) {
		/*
		 * User failed to confirm.  Abort the fetch.
		 */
	    case 0:
		FREE(redirecting_url);
		status = HT_NO_DATA;
		goto clean_up;

		/*
		 * User wants to treat as GET with no content.  Go back to
		 * check out the URL.
		 */
	    case 303:
		break;

		/*
		 * Set the flag to retain the POST content and go back to check
		 * out the URL.  - FM
		 */
	    default:
		redirect_post_content = TRUE;
	    }

	    /* Lou's old comment:  - FM */
	    /* OK, now we've got the redirection URL temporarily stored
	       in external variable redirecting_url, exported from HTMIME.c,
	       since there's no straightforward way to do this in the library
	       currently.  Do the right thing. */

	    status = HT_REDIRECTING;

	} else {
	    status = traversal ? -1 : HT_LOADED;
	}

    } else {
	/*
	 * If any data were received, treat as a complete transmission
	 */
	status = HT_LOADED;
    }

    /*
     * Clean up
     */
  clean_up:
    FREE(line_buffer);
    FREE(line_kept_clean);

  done:
    /*
     * Clear out on exit, just in case.
     */
    reloading = FALSE;
#ifdef USE_SSL
    FREE(connect_host);
    if (handle) {
	SSL_free(handle);
	SSL_handle = handle = NULL;
    }
#endif /* USE_SSL */
    dump_server_status = server_status;
    return status;
}

/*	Protocol descriptor
*/
#ifdef GLOBALDEF_IS_MACRO
#define _HTTP_C_GLOBALDEF_1_INIT { "http", HTLoadHTTP, 0}
GLOBALDEF(HTProtocol, HTTP, _HTTP_C_GLOBALDEF_1_INIT);
#define _HTTP_C_GLOBALDEF_2_INIT { "https", HTLoadHTTP, 0}
GLOBALDEF(HTProtocol, HTTPS, _HTTP_C_GLOBALDEF_2_INIT);
#else
GLOBALDEF HTProtocol HTTP =
{"http", HTLoadHTTP, 0};
GLOBALDEF HTProtocol HTTPS =
{"https", HTLoadHTTP, 0};
#endif /* GLOBALDEF_IS_MACRO */