/***************************************

    This is part of frox: A simple transparent FTP proxy
    Copyright (C) 2000 James Hollingshead

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

  httpcache.c -- Stuff for requesting files through an external cache 
                 (eg. squid)


  ***************************************/
#include <fcntl.h>
#include "common.h"
#include "cache.h"
#include "control.h"
#include "vscan.h"

static enum { STARTING, SUCCEEDING, SUCCESS, FAILURE, NONE } cache_status =
	NONE;
static int offset;
static int proxy_dl_ok(int fd);

/* ------------------------------------------------------------- **
** If we are to retrieve through squid then return a fd. If not, or on
** error, return -1. 
** ------------------------------------------------------------- */
int s_retr_start(const sstr * host, const sstr * file, const sstr * mdtm,
		 int size, int offst, int type)
{
	sstr *buf;
	int i, squidfd;

	offset = offst;
	if(config.mincachesize > size) {
		cache_status = NONE;
		write_log(VERBOSE,
			  "File too small for cache - retrieving directly");
		return (-1);
	}

	/*Set up the HTTP command */
	cache_status = STARTING;

	buf = sstr_init(0);
	i = sstr_apprintf(buf,
			  "GET ftp://%s%s%s%s%s%s%s HTTP/1.0\r\n"
			  "Host: %s\r\n"
			  "User-Agent: %s\r\n"
			  "X-Forwarded-For: %s\r\n",
			  info->anonymous ? "" : sstr_buf(info->username),
			  info->anonymous ? "" : ":",
			  info->anonymous ? "" : sstr_buf(info->passwd),
			  info->anonymous ? "" : "@",
			  sstr_buf(host), sstr_buf(file),
			  type == 1 ? (config.strictcache ? ";type=i" : "")
			  : ";type=an",
			  sstr_buf(host), "Frox/0.7",
			  inet_ntoa(info->client_control.address.sin_addr));
	if(offset > 0)
		sstr_apprintf(buf, "Range: bytes=%d-\r\n", offset);
	sstr_ncat2(buf, "\r\n", 2);

	if(i < 0 || i > BUF_LEN)
		die(ERROR, "Failure building HTTP string",
		    421, "Failure building HTTP string", -1);

	write_log(VERBOSE, "HTTP string = %s", sstr_buf(buf));

	if((squidfd = connect_to_socket(&config.httpproxy, NULL,
					config.pasvports)) == -1) {
		write_log(ERROR,
			  "Unable to contact HTTP proxy. Retrieving directly");
		cache_status = NONE;
		sstr_free(buf);
		return (-1);
	}

	/*Send the message to squid. We could block for a while here,
	 * but no-one should be sending us anything important...*/
/*	fcntl(squidfd, F_SETFL, O_NONBLOCK);*/
	sstr_write(squidfd, buf, 0);
	sstr_free(buf);

	/* Now wait for squid's answer. This is a nasty hack as it means
	 * more blocking.*/
	if(!proxy_dl_ok(squidfd))
		return -1;

	vscan_new(size);
	if(!vscan_parsed_reply(150, NULL))
		send_cmessage(150, "Starting transfer");

	/*And set everything up as if the connection was normal */

	return (squidfd);
}


/* Start downloading from proxy. If failure then close fd. If success
 * then wait until header is downloaded and return. All processing of
 * inc_data stream can be done from here.
 * 
 * Nasty hack. Proper solution involves changing cache.c and
 * control.c/data.c to allow deferring this decision to later. Then we
 * could use the central select loop rather than blocking here.*/
static int proxy_dl_ok(int fd)
{
	int i;
	sstr *buf = sstr_init(4096);

	do {
		fd_set readfds;

		FD_ZERO(&readfds);
		FD_SET(fd, &readfds);
		if(select(fd + 1, &readfds, NULL, NULL, NULL) == -1) {
			if(errno == EINTR)
				continue;
			debug_perr("select");
			die(0, NULL, 0, NULL, -1);
		}

		i = sstr_append_read(fd, buf, 4095);
		if(i == -1) {
			write_log(ERROR, "Error reading from http cache. "
				  "Aborting caching.");
			cache_status = NONE;
			close(fd);
			sstr_free(buf);
			return 0;
		}
		s_inc_data(buf);
	} while(cache_status == STARTING || cache_status == SUCCEEDING);
	switch (cache_status) {
	case SUCCESS:
		sstr_cat(info->client_data.buf, buf);
		sstr_free(buf);
		return 1;
	case FAILURE:
		close(fd);
		cache_status = NONE;
		write_log(ERROR, "Http cache unable to download file. "
			  "Aborting caching.");
		sstr_free(buf);
		return 0;
	default:
		die(ERROR, "Internal error, proxy_dl_ok()", 0, 0, -1);
		break;
	}
	return 0;
}

/* ------------------------------------------------------------- **
** Strip out HTTP lines from inc, and set cache_status according
** to whether the requests fails or not. Leave any message body
** in inc.
** ------------------------------------------------------------- */
void s_inc_data(sstr * inc)
{
	int i;
	sstr *buf = NULL;

	if(cache_status == NONE)
		return;
	if(cache_status == SUCCESS)
		return;
	if(cache_status == FAILURE) {	/*Discard message body */
		write_log(VERBOSE, "Discarding HTTP body after failure.");
		sstr_empty(inc);
		return;
	}

	/*Parse HTTP reply. FIXME - parse it properly! */
	do {
		i = sstr_chr(inc, '\n');
		if(i == -1)
			return;	/*Incomplete line */

		buf = sstr_init(MAX_LINE_LEN);
		sstr_split(inc, buf, 0, i + 1);
		write_log(VERBOSE, "HTTP: %s", sstr_buf(buf));
		if(!sstr_ncasecmp2(buf, "HTTP", 4)) {	/*Status Line */
			sstr_token(buf, NULL, " ", 0);
			i = sstr_atoi(buf);
			if((i / 100) == 2 && (offset == 0 || i == 206))
				cache_status = SUCCEEDING;
			else {	/*Failure - discard rest of message. */
				sstr_empty(inc);
				cache_status = FAILURE;
				sstr_free(buf);
				return;
			}
		} else if(sstr_getchar(buf, 0) == '\r')
			cache_status =
				(cache_status ==
				 SUCCEEDING) ? SUCCESS : FAILURE;

	} while(cache_status != SUCCESS && cache_status != FAILURE);

	sstr_free(buf);
	return;
}

int s_retr_end(void)
{
	if(cache_status == NONE)
		return (-1);
	if(cache_status == SUCCESS) {
		if(!vscan_parsed_reply(226, NULL))
			send_cmessage(226, "Transfer complete");
	} else {
		if(!vscan_parsed_reply(226, NULL))
			send_cmessage(426, "Unable to transfer file");
	}

	cache_status = NONE;
	return (-1);
}


syntax highlighted by Code2HTML, v. 0.9.1