/****************************************************************************
 *                                                                          *
 * The contents of this file are subject to the WebStone Public License     *
 * Version 1.0 (the "License"); you may not use this file except in         *
 * compliance with the License. You may obtain a copy of the License        *
 * at http://www.mindcraft.com/webstone/license10.html                      *
 *                                                                          *
 * Software distributed under the License is distributed on an "AS IS"      *
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See      *
 * the License for the specific language governing rights and limitations   *
 * under the License.                                                       *
 *                                                                          *
 * The Original Code is WebStone 2.5.                                       *
 *                                                                          *
 * The Initial Developer of the Original Code is Silicon Graphics, Inc.     *
 * and Mindcraft, Inc.. Portions created by Silicon Graphics. and           *
 * Mindcraft. are Copyright (C) 1995-1998 Silicon Graphics, Inc. and        *
 * Mindcraft, Inc. All Rights Reserved.                                     *
 *                                                                          *
 * Contributor(s): ______________________________________.                  *
 *                                                                          *
 * @(#) bench.c 2.4@(#)                                                     *
 ***************************************************************************/

 
/* THIS IS WHERE WE GO OUT AND FETCH A URL */

#include <stdio.h>
#include <errno.h>

#ifndef WIN32
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <netdb.h>
#endif /* WIN32 */

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef SUNOS
#include <unistd.h>
#endif
#include <ctype.h>

#ifdef WIN32
#include <io.h>
#include <windows.h>
#include <winsock.h>
#endif /* WIN32 */

#ifdef STONE_SSL
#include <openssl/ssl.h>
#include <openssl/bio.h>
#endif

#include "sysdep.h"
#include "bench.h"
#ifdef STONE_SSL
#include "random.h"
#endif

#define ACCEPT_COMMAND "Accept: */* HTTP/1.0\r\n\r\n"
#define ACCEPT_COMMAND_LEN 	((int)strlen(ACCEPT_COMMAND))
#define MAXCOMMANDLEN	256
#define HEADERBUFSIZ	(8*1024)
#define SAVE_FILE "savefile"

#ifdef STONE_SSL
/* globals defined in webclient.c */
extern int ssl_client_cache_mode;
extern double ssl_mix;
extern double ssl_handshake;
extern NETPORT ssl_portnum;
#endif

#ifndef HAVE_STRNCASECMP
#define UPPER(c) (((c) >= 'a' && (c) <= 'z') ? (c) + 'A' - 'a' : (c))

/* compare two strings with max length, ignoring case */
int strncasecmp(const char *str1, const char *str2, size_t len) {
	int diff;

    while (*str1 && *str2 && len--) {
		if (diff = UPPER(*str1) - UPPER(*str2))
			return diff < 0 ? -1 : 1;
		str1++;
		str2++;
    }
    return 0;
}
#endif /* HAVE_STRNCASECMP */

int
#ifdef STONE_SSL
get(char *loc, NETPORT port, char *url, rqst_timer_t *timer, char* ssl_version,
		char* ssl_cipher)
#else
get(char *loc, NETPORT port, char *url, rqst_timer_t *timer)
#endif
{
    SOCKET	sock = BADSOCKET_VALUE;
    int		writelen;
    int		bytesread;
    int		totalbytesread;
    int		headerlen;
    int		bodylength;
    int		contentlength = 0;
    FILE 	*outputfile = (FILE *)NULL;
    int		status;
    char	getcommand[MAXCOMMANDLEN];
    char	headerbuffer[HEADERBUFSIZ+1];
    char	*offset;
    char	outputfilename[MAXPATHLEN];
    char	version[100];
    int		count;
#ifdef STONE_SSL
	static int init = 0;
	static SSL_CTX* ctx = NULL;
	static SSL* ssl = NULL;
	BIO* bio;
	int do_ssl = RandomChance(ssl_mix); 
	int do_handshake = RandomChance(ssl_handshake);
	D_PRINTF("do_ssl = %d\n", do_ssl); 
#endif


#ifdef ABORTIVE_CLOSE
#error do not enable this option
    struct linger {
		int l_onoff;
		int l_linger;
    } linger_opt;
#endif /* ABORTIVE_CLOSE */

	TRACE("get(): entering: url='%s'\n", url);
    if(GETTIMEOFDAY(&timer->entertime, &timer->entertimezone) != 0)
    {
		returnerr("Error retrieving entertime\n");
		goto error;
    }
    timer->valid = 1;

    if(GETTIMEOFDAY(&timer->beforeconnect, &timer->beforeconnectzone) != 0)
    {
		returnerr("Error retrieving beforeconnect\n");
		goto error;
    }

#ifdef STONE_SSL
	if(do_ssl)
		sock = connectsock(loc, ssl_portnum, "tcp");
	else
#endif
    	sock = connectsock(loc, port, "tcp");

    if (BADSOCKET(sock))
    {
		D_PRINTF( "Call to connectsock returned %d (%s)\n", sock, neterrstr() );
                returnerr("Couldn't connect to WWW server: %s\n", neterrstr());
                goto error;
    }
#ifdef STONE_SSL
	if(!init) {
		ERR_load_crypto_strings();
      OpenSSL_add_all_algorithms();
      OpenSSL_add_ssl_algorithms();

		if(ssl_version != NULL && strcasecmp(ssl_version, "SSLv23") == 0) {
			if((ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
				ERR_print_errors_fp(stderr);
				goto error;
			}
			D_PRINTF( "SSL version = SSLv23\n" );
		}
		else if(ssl_version != NULL && strcasecmp(ssl_version, "SSLv2") == 0) {
			if((ctx = SSL_CTX_new(SSLv2_client_method())) == NULL) {
				ERR_print_errors_fp(stderr);
				goto error;
			}
			D_PRINTF( "SSL version = SSLv2\n" );
		}
		else {
			if((ctx = SSL_CTX_new(SSLv3_client_method())) == NULL) {
				ERR_print_errors_fp(stderr);
				goto error;
			}
			D_PRINTF( "SSL version = SSLv3\n" );
		}
		
		/* set preferred cipher */
		if(ssl_cipher != NULL && strlen(ssl_cipher) > 0) {
			SSL_CTX_set_cipher_list(ctx, ssl_cipher); 
		}

		if(ssl_client_cache_mode > 0) {
			SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT);
			/* SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_NO_AUTO_CLEAR);*/
		}
		D_PRINTF( "SSL client cache mode = %d\n", ssl_client_cache_mode);
		
		if((ssl = SSL_new(ctx)) == NULL) {
			SSL_CTX_free(ctx);
			ERR_print_errors_fp(stderr);
			goto error;
		}
		
		init = 1;
		D_PRINTF("done init\n");
	}

	if(do_ssl) {
	

/*
		if(do_handshake) {
			if(ssl != NULL)
				SSL_free(ssl);

			if((ssl = SSL_new(ctx)) == NULL) {
				SSL_CTX_free(ctx);
				ERR_print_errors_fp(stderr);
				goto error;
			}
		}
*/

		if(do_handshake) {
			SSL_SESSION_free(ssl->session);
			ssl->session = NULL;
		}

		if((bio = BIO_new_socket(sock, BIO_NOCLOSE)) == NULL) {
			ERR_print_errors_fp(stderr);
			goto error;
		}
		
		SSL_set_bio(ssl, bio, bio);

		if(SSL_connect(ssl) <= 0) {
			ERR_print_errors_fp(stderr);
			goto error;
		}
		else {
		}
	}

#endif

#ifdef ABORTIVE_CLOSE    
#error do not enable this option
    /* set up for abortive close */
    linger_opt.l_onoff = 1;
    linger_opt.l_linger = 0;
    if (setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &linger_opt, 
					sizeof(linger_opt)) < 0) 
	{
		fprintf(stderr, "Can't set sockopt SO_LINGER");
		returnerr("Couldn't set SO_LINGER = 0\n");
		goto error;
    }
#endif /* ABORTIVE_CLOSE */

    if(GETTIMEOFDAY(&timer->afterconnect, &timer->afterconnectzone) != 0)
    {
#ifdef STONE_SSL
		if(do_ssl)
			SSL_shutdown(ssl);
#endif

		NETCLOSE(sock);
		GETTIMEOFDAY(&timer->exittime, &timer->exittimezone);
		returnerr("Error retrieving afterconnect\n");	
		goto error;
    }

    /*
     * SEND THE GET AND THE ACCEPT.
     */
    sprintf(getcommand, "GET %s HTTP/1.0\r\n%s", url, ACCEPT_COMMAND);
    D_PRINTF( "Writing to server: %s\n", getcommand );
    writelen = strlen(getcommand);

#ifdef STONE_SSL
	if(do_ssl)
		status = SSL_write(ssl, getcommand, writelen);
	else
#endif
    	status = NETWRITE(sock, getcommand, writelen);

    if(status != writelen)
    {
		returnerr("Error sending command line to server: %s\n", neterrstr());
		goto error;
    }
    /* 
     * WE HAVE NOW SENT THE REQUEST SUCCESSFULLY.  
     * WAIT FOR THE REPLY AND FIND THE HEADER 
     */
    
    if(GETTIMEOFDAY(&timer->beforeheader, &timer->beforeheaderzone) != 0)
    {
		returnerr("Error retrieving beforeheader\n");
		goto error;
    }

    /* read the header and part of the file */
    totalbytesread = 0;
    headerlen = 0;
    while (totalbytesread < HEADERBUFSIZ)
    {
        if (timeexpired)
			goto error;	/* this won't really be counted as an error */

		TRACE("get(): about to read header\n");

#ifdef STONE_SSL
		if(do_ssl)
			bytesread = SSL_read(ssl, headerbuffer+totalbytesread,
							HEADERBUFSIZ-totalbytesread);
		else
#endif
        	bytesread = NETREAD(sock, headerbuffer+totalbytesread, 
							HEADERBUFSIZ-totalbytesread);

        if (timeexpired)
			goto error;	/* this won't really be counted as an error */
        if (bytesread < 0)
        {
			D_PRINTF( "Did not receive full header\n" );
			D_PRINTF( "NETREAD returned %d\n", bytesread );
			returnerr("Did not receive full header: %s\n", neterrstr());
			goto error;
        }
        totalbytesread += bytesread;

		/* search for end of header */
		headerbuffer[totalbytesread] = 0;
		if (offset = strstr(headerbuffer, "\n\n")) 
		{
			headerlen = offset - headerbuffer + 2;
			break;
		} 
		else if (offset = strstr(headerbuffer, "\n\r\n")) 
		{
			headerlen = offset - headerbuffer + 3;
			break;
		}
    }

    if (headerlen == 0) 
	{
		returnerr("Can't find the end of the header in \"%s\"\n", headerbuffer);
		goto error;
    }

    /* get and check status code from the first line of the header */
    count = sscanf(headerbuffer, "HTTP/%s %d", version, &status);
    if (count != 2) 
	{
		returnerr("Bad status line in get(): %s\n", headerbuffer);
		goto error;
    }
    if (status < 200 || status > 300) 
	{
		returnerr("Bad status (%d) in get() for url %s\n", status, url);
		goto error;
    }

    /* get the content length line from the header */
    offset = headerbuffer;
    while (offset < headerbuffer+headerlen && *offset) 
	{
		if (*offset++ != '\n')
			continue;

		if (strncasecmp(offset, CONTENT_LENGTH_STRING, 
						strlen(CONTENT_LENGTH_STRING)) == 0) 
		{
			sscanf(offset+strlen(CONTENT_LENGTH_STRING), "%d", &contentlength);
			D_PRINTF( "Content-Length: %d\n", contentlength );
		}
    }

    if(GETTIMEOFDAY(&timer->afterheader, &timer->afterheaderzone) != 0)
    {
		returnerr("Error retrieving afterheader\n");
		goto error;
    }

    if(savefile)
    {
        sprintf(outputfilename,"%s%d", SAVE_FILE, (int)getpid());
        if((outputfile = fopen(outputfilename, "w")) == (FILE *)NULL)
        {
            D_PRINTF( "outputfile %d %d\n", outputfile, errno ); 
			returnerr("Error saving file: %s\n", strerror(errno)); 
			goto error;
        }
		/* lseek(outputfile,1,SEEK_END); */	/* this is odd... JEF */

		/* if we have part of the file already, save that part */
		if(totalbytesread > headerlen)
			fwrite(headerbuffer+headerlen, 1, totalbytesread-headerlen,
				outputfile);
    }

    /* read the body of the file */
    do
    {
		TRACE("get(): about to read body\n");

#ifdef STONE_SSL
		if(do_ssl)
			bytesread = SSL_read(ssl, headerbuffer, HEADERBUFSIZ);
		else
#endif
			bytesread = NETREAD(sock, headerbuffer, HEADERBUFSIZ);

        if (timeexpired) 
			goto error;	/* this won't really be counted as an error */

		if (bytesread < 0)
		{
#ifdef STONE_SSL
			if(do_ssl)
				ERR_print_errors_fp(stderr);
#endif
			D_PRINTF( "Read returns %d, error: %s\n", bytesread, neterrstr() );
			returnerr("Error during read of page body. Read "
					 "returns %d on socket %d, error: %s\n",
					 bytesread, sock, neterrstr());
			goto error;
		}

		totalbytesread += bytesread;

		if (outputfile != (FILE *)NULL && bytesread)
			fwrite(headerbuffer, 1, bytesread, outputfile);
    } while (bytesread && !timeexpired);
    
    /* done reading body */
    if ( contentlength && (totalbytesread - headerlen) != contentlength)
    {
		D_PRINTF( "Warning: file length (%d) doesn't match Content-length (%d)\n",
	    totalbytesread - headerlen, contentlength);
    }
    
    bodylength = totalbytesread - headerlen;

    if(GETTIMEOFDAY(&timer->afterbody, &timer->afterbodyzone) != 0)
    {
		returnerr("Error retrieving afterbody\n");
		goto error;
    }

#ifdef STONE_SSL
	if(do_ssl)
		SSL_shutdown(ssl);
#endif

    NETCLOSE(sock);

    if (outputfile != (FILE *)NULL)
        fclose(outputfile);

    D_PRINTF( "Read %d bytes, %d of that being body\n",
				totalbytesread, bodylength );

    if(GETTIMEOFDAY(&timer->exittime, &timer->exittimezone) != 0)
    {
		D_PRINTF( "Error retrieving exit time: %s\n", strerror(errno) );
		returnerr("Error retrieving exit time\n");
		goto error;
    }
    timer->valid = 2;
    timer->totalbytes = totalbytesread;
    timer->bodybytes = bodylength;

    D_PRINTF( "get returning totalbytes %d body %d valid %d\n", 
	timer->totalbytes, timer->bodybytes, timer->valid );

    D_PRINTF( "get returning start %d, end %d\n", 
	timer->entertime.tv_sec, timer->exittime.tv_sec	); 

    D_PRINTF( "get returning connect %d, request %d, header %d, body %d\n",
	timer->afterconnect.tv_sec, timer->beforeheader.tv_sec, 
	timer->afterheader.tv_sec, timer->afterbody.tv_sec );

	TRACE("get(): exiting successfully\n");
    return 0;

error:
	TRACE("get(): entering error section\n");
#ifdef STONE_SSL
	if(do_ssl)
        SSL_shutdown(ssl);
#endif
    if (!BADSOCKET(sock)) {
        NETCLOSE(sock);
    }

    if (outputfile != (FILE *)NULL)
		fclose(outputfile);
    GETTIMEOFDAY(&timer->exittime, &timer->exittimezone);
		/* needed for record_all_transactions option in webclient.c */
    return -1;
}  
