/* 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 #include #include #ifdef USE_SSL #include #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 #include #include #include #include #include #include #include #include #include /* Are we using an HTTP gateway? */ #include #include #include #include #include struct _HTStream { HTStreamClass * isa; }; extern char * HTAppName; /* Application name: please supply */ extern char * HTAppVersion; /* Application version: please supply */ PUBLIC BOOL reloading = FALSE; /* Reloading => send no-cache pragma to proxy */ PUBLIC char * redirecting_url = NULL; /* Location: value. */ PUBLIC BOOL permanent_redirection = FALSE; /* Got 301 status? */ PUBLIC BOOL redirect_post_content = FALSE; /* Don't convert to GET? */ #ifdef USE_SSL PUBLIC SSL_CTX * ssl_ctx = NULL; /* SSL ctx */ PUBLIC SSL * SSL_handle = NULL; PUBLIC int ssl_okay; PRIVATE void free_ssl_ctx NOARGS { if (ssl_ctx != NULL) SSL_CTX_free(ssl_ctx); } PRIVATE int HTSSLCallback(int preverify_ok, X509_STORE_CTX *x509_ctx) { char *msg = NULL; int result = 1; if (!(preverify_ok || ssl_okay || ssl_noprompt)) { #ifdef USE_X509_SUPPORT HTSprintf0(&msg, "SSL error:%s-Continue?", X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx))); if (HTForcedPrompt(ssl_noprompt, msg, YES)) ssl_okay = 1; else result = 0; #endif FREE(msg); } return result; } PUBLIC SSL * HTGetSSLHandle NOARGS { 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()); SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL); SSL_CTX_set_default_verify_paths(ssl_ctx); SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, HTSSLCallback); #endif /* SSLEAY_VERSION_NUMBER < 0x0800 */ atexit(free_ssl_ctx); } ssl_okay = 0; return(SSL_new(ssl_ctx)); } PUBLIC void HTSSLInitPRNG NOARGS { #if SSLEAY_VERSION_NUMBER >= 0x00905100 if (RAND_status() == 0) { char rand_file[256]; time_t t; int pid; long l,seed; t = time(NULL); pid = getpid(); RAND_file_name(rand_file, 256); CTRACE((tfp,"HTTP: Seeding PRNG\n")); if(rand_file != NULL) { /* Seed as much as 1024 bytes from RAND_file_name */ RAND_load_file(rand_file, 1024); } /* Seed in time (mod_ssl does this) */ RAND_seed((unsigned char *)&t, sizeof(time_t)); /* Seed in pid (mod_ssl does this) */ RAND_seed((unsigned char *)&pid, sizeof(pid)); /* Initialize system's random number generator */ RAND_bytes((unsigned char *)&seed, sizeof(long)); lynx_srand(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, sizeof(long)); } if (rand_file != NULL) { /* 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; PUBLIC int ws_read_per_sec = 0; PRIVATE int ws_errno = 0; PRIVATE DWORD g_total_times = 0; PRIVATE DWORD g_total_bytes = 0; PUBLIC char * str_speed(void) { static char buff[32]; if (ws_read_per_sec > 1000) sprintf(buff, "%d.%03dkB", ws_read_per_sec / 1000, (ws_read_per_sec % 1000) ); else sprintf(buff, "%3d", ws_read_per_sec); return buff; } /* The same like read, but takes care of EINTR and uses select to timeout the stale connections. */ PRIVATE 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; } PRIVATE void _thread_func (void *p) { int i, val, ret; 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 ret = -1; } else { ret = val; } ExitThread((DWORD)ret); } /* The same like read, but takes care of EINTR and uses select to timeout the stale connections. */ PUBLIC 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 = -1, val, process_time, now_TickCount, save_TickCount; static recv_data_t para; extern int win32_check_interrupt(void); /* LYUtil.c */ extern int lynx_timeout; /* LYMain.c */ extern CRITICAL_SECTION critSec_READ; /* LYMain.c */ #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((void *)NULL, STACK_SIZE, (LPTHREAD_START_ROUTINE)_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 = -1; 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 = -1; } 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); g_total_times += process_time; g_total_bytes += exitcode; if (g_total_bytes > 2000000) { ws_read_per_sec = g_total_bytes / (g_total_times/1000); } else { ws_read_per_sec = g_total_bytes * 1000 / g_total_times; } ret_val = exitcode; break; } } /* end while(1) */ read_exit: LeaveCriticalSection(&critSec_READ); return ret_val; } #endif /* * Strip any username from the given string so we retain only the host. * If the */ PRIVATE void strip_userid ARGS1( 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, "FIXME:%s\n", fake)); HTSprintf0(&msg, gettext("Address contains a username: %s"), host); HTAlert(msg); FREE(msg); } while ((*p1++ = *p2++) != '\0') { ; } } } /* 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. ** */ PRIVATE int HTLoadHTTP ARGS4 ( CONST char *, arg, HTParentAnchor *, anAnchor, HTFormat, format_out, HTStream*, sink) { 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 */ int 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 ? */ char *METHOD; BOOL had_header; /* Have we had at least one header? */ char *line_buffer; char *line_kept_clean; int real_length_of_line; 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; BOOL doing_redirect, already_retrying = FALSE; int len = 0; #ifdef USE_SSL 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 */ char ssl_dn[256]; char *cert_host; char *ssl_host; char *p; char *msg = NULL; #if SSLEAY_VERSION_NUMBER >= 0x0900 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)) { if ((connect_url = strstr((url+7), "https://"))) { do_connect = TRUE; connect_host = HTParse(connect_url, "https", PARSE_HOST); if (!strchr(connect_host, ':')) { 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 (!strchr(connect_host, ':')) { 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; had_header = NO; 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; } /* *sob* All this needs to be converted to handle binary strings * if we're going to be able to handle binary form uploads... * This is a nice long function as well. *sigh* -RJP */ #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); #if SSLEAY_VERSION_NUMBER >= 0x0900 if (!try_tls) handle->options|=SSL_OP_NO_TLSv1; #endif /* SSLEAY_VERSION_NUMBER >= 0x0900 */ HTSSLInitPRNG(); status = SSL_connect(handle); if (status <= 0) { #if SSLEAY_VERSION_NUMBER >= 0x0900 if (try_tls) { _HTProgress(gettext("Retrying connection without TLS.")); try_tls = FALSE; if (did_connect) HTTP_NETCLOSE(s, handle); goto try_again; } 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; } #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 */ } X509_NAME_oneline(X509_get_subject_name(SSL_get_peer_certificate(handle)), ssl_dn, sizeof(ssl_dn)); if ((cert_host = strstr(ssl_dn, "/CN=")) == NULL) { HTSprintf0(&msg, gettext("SSL error:Can't find common name in certificate-Continue?")); if (! HTForcedPrompt(ssl_noprompt, msg, YES)) { status = HT_NOT_LOADED; FREE(msg); goto done; } } else { cert_host += 4; if ((p = strchr(cert_host, '/')) != NULL) *p = '\0'; if ((p = strchr(cert_host, ':')) != NULL) *p = '\0'; ssl_host = HTParse(url, "", PARSE_HOST); if ((p = strchr(ssl_host, ':')) != NULL) *p = '\0'; if (strcasecomp(ssl_host, cert_host)) { HTSprintf0(&msg, gettext("SSL error:host(%s)!=cert(%s)-Continue?"), ssl_host, cert_host); if (! HTForcedPrompt(ssl_noprompt, msg, YES)) { status = HT_NOT_LOADED; FREE(msg); goto done; } } } 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=%ld", pres->quality, pres->maxbytes); } else { sprintf(temp, ";q=%4.3f", pres->quality); } } else if (pres->maxbytes > 0) { sprintf(temp, ";mxb=%ld", pres->maxbytes); } else { temp[0] = '\0'; } HTSprintf0(&linebuf, "%s%s%s", (first_Accept ? "Accept: " : ", "), HTAtom_name(pres->rep), temp); len += strlen(linebuf); if (len > 252 && !first_Accept) { BStrCat0(command, crlf); HTSprintf0(&linebuf, "Accept: %s%s", HTAtom_name(pres->rep), temp); len = strlen(linebuf); } BStrCat0(command, linebuf); first_Accept = FALSE; } } HTBprintf(&command, "%s*/*;q=0.01%c%c", (first_Accept ? "Accept: " : ", "), CR, LF); first_Accept = FALSE; len = 0; /* * 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; #if defined(USE_ZLIB) || defined(GZIP_PATH) StrAllocCopy(list, "gzip"); #endif #if defined(USE_ZLIB) || defined(COMPRESS_PATH) if (list != 0) StrAllocCat(list, ", "); StrAllocCat(list, "compress"); #endif 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. ** ** 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 (LYUserAgent && *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(), "")) { char *cp = LYRequestReferer; if (!cp) cp = HTLoadedDocumentURL(); /* @@@ Try both? - kw */ BStrCat0(command, "Referer: "); if (isLYNXIMGMAP(cp)) { char *cp1 = trimPoundSelector(cp); BStrCat0(command, cp + LEN_LYNXIMGMAP); restorePoundSelector(cp1); } 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 = strchr(hostname, ':'))) { *(colon++) = '\0'; /* Chop off port number */ portnumber = atoi(colon); } 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 (host2) { if ((colon = strchr(host2, ':')) != NULL) { /* Use non-default port number */ *colon = '\0'; colon++; port2 = atoi(colon); } } /* ** This composeAuth() does file access, i.e., for ** the ultimate target of the request. - AJL */ auth_proxy = NO; if ((auth = HTAA_composeAuth(host2, port2, path2, auth_proxy)) != NULL && *auth != '\0') { /* ** If auth is not NULL nor zero-length, it's ** an Authorization header to be included. - FM */ HTBprintf(&command, "%s%c%c", auth, CR, LF); CTRACE((tfp, "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)) { 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; } } else { CTRACE((tfp, "HTTP: Not sending authorization (yet).\n")); } /* ** 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); } auth_proxy = NO; } 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 *p; for ( p = BStrData(command); p < BStrData(command) + BStrLen(command); p++ ) *p = TOASCII(*p); } #endif /* NOT_ASCII */ status = 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, buffer_length); if (line_buffer == NULL) outofmem(__FILE__, "HTLoadHTTP"); HTReadProgress (bytes_already_read = 0, 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, (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; } 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 *p; for ( p = line_buffer + length; p < line_buffer + length + status; p++ ) *p = FROMASCII(*p); } #endif /* NOT_ASCII */ bytes_already_read += status; HTReadProgress (bytes_already_read, 0); #ifdef UCX /* UCX returns -1 on EOF */ if (status == 0 || status == -1) #else if (status == 0) #endif { end_of_file = YES; break; } line_buffer[length+status] = 0; if (line_buffer) { FREE(line_kept_clean); line_kept_clean = (char *)malloc(buffer_length * sizeof(char)); if (line_kept_clean == NULL) outofmem(__FILE__, "HTLoadHTTP"); memcpy(line_kept_clean, line_buffer, buffer_length); real_length_of_line = length + status; } 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,"Bad File Request",31) || 0==strncmp(line_buffer,"Address should begin with",25) || 0==strncmp(line_buffer,"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 */ start_of_data = eol ? eol + 1 : ""; length = eol ? length - (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; bytes_already_read = 0; had_header = NO; 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 orgin 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, 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, 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 done; 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 || 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 */ /* (*target->isa->_abort)(target, NULL); */ /* already done in HTCopy */ 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: doing_redirect = FALSE; 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. */ do_head = FALSE; do_post = FALSE; reloading = FALSE; #ifdef USE_SSL do_connect = FALSE; did_connect = FALSE; FREE(connect_host); if (handle) { SSL_free(handle); SSL_handle = handle = NULL; } #endif /* USE_SSL */ 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 PUBLIC HTProtocol HTTP = { "http", HTLoadHTTP, 0 }; GLOBALDEF PUBLIC HTProtocol HTTPS = { "https", HTLoadHTTP, 0 }; #endif /* GLOBALDEF_IS_MACRO */