/*
* $LynxId: HTTP.c,v 1.148 2015/12/16 01:54:03 tom 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_VERSION "HTTP/1.0"
#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
#ifdef USE_GNUTLS_INCL
#include <gnutls/x509.h>
#endif
#endif
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
char *certfile = NULL;
char *client_keyfile = NULL;
char *client_certfile = NULL;
#endif
if (ssl_ctx == NULL) {
/*
* First time only.
*/
#if SSLEAY_VERSION_NUMBER < 0x0800
ssl_ctx = SSL_CTX_new();
X509_set_default_verify_paths(ssl_ctx->cert);
#else
SSLeay_add_ssl_algorithms();
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
#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)
{
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);
}
#ifdef USE_GNUTLS_INCL
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));
}
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;
#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
InitializeCriticalSection(&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 *) ¶, 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 */
/*
* Strip any username from the given string so we retain only the host.
*/
static void strip_userid(char *host)
{
char *p1 = host;
char *p2 = StrChr(host, '@');
char *fake;
if (p2 != 0) {
*p2++ = '\0';
if ((fake = HTParse(host, "", PARSE_HOST)) != NULL) {
char *msg = NULL;
CTRACE((tfp, "parsed:%s\n", fake));
HTSprintf0(&msg, gettext("Address contains a username: %s"), host);
HTAlert(msg);
FREE(msg);
}
while ((*p1++ = *p2++) != '\0') {
;
}
}
}
/*
* 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) {
handle->options |= SSL_OP_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);
}
#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, HTTP_VERSION);
}
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);
HTBprintf(&command, "Host: %s%c%c", host, CR, LF);
FREE(host);
}
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 (language && *language) {
HTBprintf(&command, "Accept-Language: %s%c%c", language, CR, LF);
}
if (pref_charset && *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 (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 (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), "text/plain", 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)
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);
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;
}
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 && !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("text/plain");
} 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 */