;#
;# Copyright (c) 1995-1997
;#	Ikuo Nakagawa. All rights reserved.
;#
;# Redistribution and use in source and binary forms, with or without
;# modification, are permitted provided that the following conditions
;# are met:
;#
;# 1. Redistributions of source code must retain the above copyright
;#    notice unmodified, this list of conditions, and the following
;#    disclaimer.
;# 2. Redistributions in binary form must reproduce the above copyright
;#    notice, this list of conditions and the following disclaimer in the
;#    documentation and/or other materials provided with the distribution.
;#
;# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
;# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
;# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
;# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
;# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
;# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
;# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
;# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
;# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;#
;# $Id: HTTP.pm,v 1.16 1997/11/06 12:15:16 ikuo Exp $
;#
;# Description:
;#	HTTP.pm - HTTP Class definitions
;#	 - Socket based operations.
;#	 - Multiple addresses for a single server are supported.
;#	 - Multiple sessions are supported.
;#
;# Usage:
;#
package Fan::HTTP;

use strict;
use vars qw($VERSION @ISA $LOG $http_port);

use Carp;
use Fan::Cool;
use Fan::TCP;
use AutoLoader 'AUTOLOAD';

@ISA = qw(Fan::TCP);
$VERSION = '0.02';
$LOG = 5 unless defined($LOG);

;# A special marker for AutoSplit.
1;
__END__

;# destroy an object.
sub DESTROY ($) {
	my $self = shift;

	# log...
	carp("HTTP DESTROYING $self") if $LOG >= 6;

	# destroy this object in SUPER class.
	Fan::TCP::DESTROY($self);
}

;# creat a new object.
sub new ($%) {
	my $this = shift;
	my $class = ref($this) || $this;
	my %param = @_;

	# if we required verbose log, set tcp_debug
	$param{tcp_debug} = 1 if $LOG > 5;

	# bless myself.
	my $self = bless Fan::TCP->new(%param), $class;
	ref($self) or return undef;

	# log...
	carp("HTTP CREATING $self") if $LOG >= 6;

	# result is myself.
	$self;
}

;# connecting the server, and get contents.
sub get ($$;$) {
	my $self = shift;
	my ($proto, $server, $port, $document) = &parse_url(shift);
	my $out = shift;
	my $tmp;
	my $need_rename = 0;
	my $fh;
	local *OUTPUT;

	if ($document !~ /^\//) {
		carp("$self: document must begin with a slash"),
		return undef;
	}

	if ($out eq '') {
		$fh = \*STDOUT;
	} elsif (ref($out) eq 'GLOB') {
		$fh = $out;
	} else {
		($tmp = $out) =~ s%[^/]+$%.in.$&%;
		$need_rename++;

		CORE::open(OUTPUT, ">$tmp") or
			carp("$self: open($tmp) - $!"), return undef;
		$fh = \*OUTPUT;
	}

	$proto = $self->{http_proto} if $proto eq '';
	$proto = 'http' if $proto eq '';

	$server = $self->{http_server} if $server eq '';
	if ($server eq '') {
		carp("$self: host not specified"), return undef;
	}

	if ($self->{http_proxy} eq '') {
		if ($proto ne 'http') {
			carp("$self: protocol $proto is not supported");
			return undef;
		}
		$port = 80 if $port eq '';
	} else {
		if (defined($port)) { # normal port
			$document = sprintf("%s://%s:%d%s",
				$proto, $server, $port, $document);
		} else {
			$document = sprintf("%s://%s%s",
				$proto, $server, $document);
		}
		$server = $self->{http_proxy};
		$port = $server =~ s/:(\d+)$// ? $1 : 80;
	}

# carp("$self: ($proto, $server, $port, $document)") if $LOG > 6;

warn("HTTP: try to connect $server:$port\n") if $LOG > 6;
warn("HTTP: try to get $document\n") if $LOG > 6;

	unless ($self->do_client(tcp_host => $server, tcp_port => $port)) {
		# error string was stored by do_client.
		$self->close;
		return undef;
	}

	$self->putln("GET $document HTTP/1.0")		&&
	$self->putln("Accept: */*")			&&
	$self->putln("User-Agent: HTTP.pm/$VERSION")	&&
	$self->putln("")				or
	$self->close, return undef;

	defined($_ = $self->getln)			&&
	/^HTTP\/\d+\.\d+ (\d\d\d) (.+)$/		or
	$self->error("wrong response"), $self->close, return undef;

	my $result = $1;
	my $reason = $2;
	my @headers = ();

warn("HTTP: result=$result reason=$2\n") if $LOG > 6;

	my $null = 0;
	while (defined($_ = $self->getln)) {
		$null++, last if /^$/;
		$_ = pop(@headers).$_ if /^\s/;
		push(@headers, $_);
	}

	unless ($null) {
		$self->error("unexpected end of file");
		$self->close;
		return undef;
	}

	# parsing headers
	while (@headers) {
		$_ = shift(@headers);

		unless (s/^([a-zA-Z\-]+):\s+//) {
			$self->error("wrong header");
			$self->clsoe;
			return undef;
		}
		my $tag = "\L$1";
                if ($tag eq 'content-type') {
			if (/\s*([-\w]+)\/([-\w]+)\s*(;\s*([^\s;]+)\s*)*$/) {
				$self->{content_type} = $1;
				$self->{content_subtype} = $2;
			}
                } elsif ($tag eq 'content-length') {
			$self->{content_length} = $_;
                } elsif ($tag eq 'content-encoding') {
			$self->{content_encoding} = $_;
		} elsif ($tag eq 'content-transfer-encoding') {
			$self->{content_transfer_encoding} = $_;
		} elsif ($tag eq 'last-modified') {
			$self->{last_modified} = $_;
                } else {
			# simply ignored
		}
	}

	# get total length of this contents
	my $length = defined($self->{content_length}) ?
		$self->{content_length} + 0 : undef;

	# try to read real data.
	my $len = 0;
	my $ll = defined($length) && $length < 10240 ? $length : 10240;
	while ($ll > 0 && defined(my $data = $self->getdata($ll))) {
		unless (print $fh $data) {
			$self->error($!.'');
			$self->close;
			return undef;
		}
		$len += length($data);
		$ll = defined($length) && $length - $len < 10240
			? $length - $len : 10240;
	}

	# o.k., now close this session.
	$self->close;
	if ($self->error) {
		return undef;
	}

	# checking result...
	unless ($result == 200) {
		$self->error("\"$reason\"");
		return undef;
	}

	# check length...
	if (defined($length) && $length != $len) {
		$self->error('length mismatch');
		return undef;
	}

	# rename if we wrote to a plain file
	if ($need_rename) {
		unless (rename($tmp, $out)) {
			my $e = $!."";
			$self->error("rename($out): $e");
			return undef;
		}
	}

	# success
	1;
}

;#
sub parse_url ($) {
	local $_ = shift;
        my $proto = s|^(\w+)://|| ? $1 : undef;
        my $server = s|^([^/]+)|| ? $1 : undef;
	my $port = $server =~ s|:(\d+)$|| ? $1 : undef;
	my $document = m|^/| ? $_ : '/';
carp("HTTP parse_url: ($proto, $server, $port, $document)") if $LOG > 6;
	($proto, $server, $port, $document);
}

;# end of Fan::HTTP module
