HTTP.pm revision 1.8
1136849Sscottl#! /usr/bin/perl
2136849Sscottl# ex:ts=8 sw=4:
3136849Sscottl# $OpenBSD: HTTP.pm,v 1.8 2011/07/18 21:09:17 espie Exp $
4136849Sscottl#
5136849Sscottl# Copyright (c) 2011 Marc Espie <espie@openbsd.org>
6136849Sscottl#
7136849Sscottl# Permission to use, copy, modify, and distribute this software for any
8136849Sscottl# purpose with or without fee is hereby granted, provided that the above
9136849Sscottl# copyright notice and this permission notice appear in all copies.
10136849Sscottl#
11136849Sscottl# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12136849Sscottl# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13136849Sscottl# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14136849Sscottl# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15136849Sscottl# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16136849Sscottl# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17136849Sscottl# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18136849Sscottl
19136849Sscottluse strict;
20136849Sscottluse warnings;
21136849Sscottl
22136849Sscottlpackage OpenBSD::Repository::HTTP;
23136849Sscottlsub urlscheme
24136849Sscottl{
25142988Sscottl	return 'http';
26142988Sscottl}
27136849Sscottl
28136849Sscottlsub initiate
29136849Sscottl{
30136849Sscottl	my $self = shift;
31136849Sscottl	my ($rdfh, $wrfh);
32136849Sscottl	pipe($self->{getfh}, $rdfh);
33136849Sscottl	pipe($wrfh, $self->{cmdfh});
34136849Sscottl	my $pid = fork();
35136849Sscottl	if ($pid == 0) {
36136849Sscottl		close($self->{getfh});
37136849Sscottl		close($self->{cmdfh});
38136849Sscottl		close(STDOUT);
39136849Sscottl		close(STDIN);
40136849Sscottl		open(STDOUT, '>&', $wrfh);
41136849Sscottl		open(STDIN, '<&', $rdfh);
42136849Sscottl		_Proxy::main($self);
43136849Sscottl	} else {
44136849Sscottl		close($rdfh);
45136849Sscottl		close($wrfh);
46136849Sscottl		$self->{controller} = $pid;
47136849Sscottl	}
48136849Sscottl}
49136849Sscottl
50136849Sscottlpackage _Proxy::Connection;
51136849Sscottlsub new
52136849Sscottl{
53136849Sscottl	my ($class, $host, $port) = @_;
54136849Sscottl	require IO::Socket::INET;
55136849Sscottl	my $o = IO::Socket::INET->new(
56136849Sscottl		PeerHost => $host,
57136849Sscottl		PeerPort => $port);
58136849Sscottl	my $old = select($o);
59136849Sscottl	$| = 1;
60136849Sscottl	select($old);
61136849Sscottl	bless {fh => $o, host => $host, buffer => ''}, $class;
62136849Sscottl}
63136849Sscottl
64136849Sscottlsub getline
65136849Sscottl{
66136849Sscottl	my $self = shift;
67136849Sscottl	while (1) {
68136849Sscottl		if ($self->{buffer} =~ s/^(.*?)\015\012//) {
69136849Sscottl			return $1;
70136849Sscottl		}
71136849Sscottl		my $buffer;
72136849Sscottl		$self->{fh}->recv($buffer, 1024);
73136849Sscottl		$self->{buffer}.=$buffer;
74136849Sscottl    	}
75136849Sscottl}
76136849Sscottl
77136849Sscottlsub retrieve
78136849Sscottl{
79136849Sscottl	my ($self, $sz) = @_;
80136849Sscottl	while(length($self->{buffer}) < $sz) {
81136849Sscottl		my $buffer;
82136849Sscottl		$self->{fh}->recv($buffer, $sz - length($self->{buffer}));
83136849Sscottl		$self->{buffer}.=$buffer;
84136849Sscottl	}
85136849Sscottl	my $result= substr($self->{buffer}, 0, $sz);
86136849Sscottl	$self->{buffer} = substr($self->{buffer}, $sz);
87136849Sscottl	return $result;
88136849Sscottl}
89136849Sscottl
90136849Sscottlsub retrieve_chunked
91136849Sscottl{
92136849Sscottl	my $self = shift;
93136849Sscottl	my $result = '';
94136849Sscottl	while (1) {
95136849Sscottl		my $sz = $self->getline;
96136849Sscottl		if ($sz =~ m/^([0-9a-fA-F]+)/) {
97136849Sscottl			my $realsize = hex($1);
98136849Sscottl			last if $realsize == 0;
99136849Sscottl			$result .= $self->retrieve($realsize);
100136849Sscottl		}
101136849Sscottl	}
102136849Sscottl	return $result;
103136849Sscottl}
104136849Sscottl
105136849Sscottlsub retrieve_response
106136849Sscottl{
107136849Sscottl	my ($self, $h) = @_;
108136849Sscottl
109136849Sscottl	if (defined $h->{'Content-Length'}) {
110136849Sscottl		return $self->retrieve($h->{'Content-Length'});
111136849Sscottl	}
112136849Sscottl	if (($h->{'Transfer-Encoding'}//'') eq 'chunked') {
113136849Sscottl		return $self->retrieve_chunked;
114136849Sscottl	}
115136849Sscottl	return undef;
116136849Sscottl}
117136849Sscottl
118136849Sscottlsub print
119136849Sscottl{
120136849Sscottl	my ($self, @l) = @_;
121136849Sscottl	print {$self->{fh}} @l;
122136849Sscottl}
123136849Sscottl
124136849Sscottlpackage _Proxy;
125136849Sscottl
126136849Sscottlmy $pid;
127136849Sscottlmy $token = 0;
128136849Sscottl
129136849Sscottlsub batch(&)
130136849Sscottl{
131136849Sscottl	my $code = shift;
132136849Sscottl	if (defined $pid) {
133136849Sscottl		waitpid($pid, 0);
134136849Sscottl		undef $pid;
135136849Sscottl	}
136136849Sscottl	$token++;
137136849Sscottl	$pid = fork();
138136849Sscottl	if (!defined $pid) {
139136849Sscottl		print "ERROR: fork failed: $!\n";
140136849Sscottl	}
141136849Sscottl	if ($pid == 0) {
142136849Sscottl		&$code();
143136849Sscottl		exit(0);
144136849Sscottl	}
145136849Sscottl}
146136849Sscottl
147136849Sscottlsub abort_batch()
148136849Sscottl{
149136849Sscottl	if (defined $pid) {
150136849Sscottl		kill 1, $pid;
151136849Sscottl		waitpid($pid, 0);
152136849Sscottl		undef $pid;
153136849Sscottl	}
154136849Sscottl	print "\nABORTED $token\n";
155136849Sscottl}
156136849Sscottl
157136849Sscottlsub get_directory
158136849Sscottl{
159136849Sscottl	my ($o, $dname) = @_;
160136849Sscottl	local $SIG{'HUP'} = 'IGNORE';
161136849Sscottl	my $crlf="\015\012";
162136849Sscottl	$o->print("GET $dname/ HTTP/1.1", $crlf,
163136849Sscottl	    "Host: ", $o->{host}, $crlf, $crlf);
164136849Sscottl	# get header
165136849Sscottl
166136849Sscottl	my $_ = $o->getline;
167136849Sscottl	if (!m,^HTTP/1\.1\s+(\d\d\d),) {
168136849Sscottl		print "ERROR\n";
169136849Sscottl		return;
170136849Sscottl	}
171136849Sscottl	my $code = $1;
172136849Sscottl	my $h = {};
173136849Sscottl	while ($_ = $o->getline) {
174136849Sscottl		last if m/^$/;
175136849Sscottl		if (m/^([\w\-]+)\:\s*(.*)$/) {
176136849Sscottl			print STDERR "$1 => $2\n";
177136849Sscottl			$h->{$1} = $2;
178136849Sscottl		} else {
179136849Sscottl			print STDERR "unknown line: $_\n";
180136849Sscottl		}
181136849Sscottl	}
182136849Sscottl	my $r = $o->retrieve_response($h);
183136849Sscottl	if (!defined $r) {
184136849Sscottl		print "ERROR: can't decode response\n";
185136849Sscottl	}
186136849Sscottl	if ($code != 200) {
187136849Sscottl			print "ERROR: code was $code\n";
188136849Sscottl			exit 1;
189136849Sscottl	}
190136849Sscottl	print "SUCCESS: directory $dname\n";
191136849Sscottl	for my $pkg ($r =~ m/\<A\s+HREF=\"(.+?)\.tgz\"\>/gio) {
192136849Sscottl		$pkg = $1 if $pkg =~ m|^.*/(.*)$|;
193136849Sscottl		# decode uri-encoding; from URI::Escape
194136849Sscottl		$pkg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
195136849Sscottl		print $pkg, "\n";
196136849Sscottl	}
197136849Sscottl	print "\n";
198136849Sscottl	return;
199136849Sscottl}
200136849Sscottl
201136849Sscottluse File::Basename;
202136849Sscottl
203136849Sscottlsub get_file
204136849Sscottl{
205136849Sscottl	my ($o, $fname) = @_;
206136849Sscottl
207136849Sscottl	my $crlf="\015\012";
208136849Sscottl	my $bailout = 0;
209136849Sscottl	$SIG{'HUP'} = sub {
210136849Sscottl		$bailout++;
211136849Sscottl	};
212136849Sscottl	my $first = 1;
213136849Sscottl	my $start = 0;
214136849Sscottl	my $end = 2000;
215136849Sscottl	my $total_size = 0;
216136849Sscottl	open my $fh, ">", basename($fname);
217136849Sscottl
218136849Sscottl	do {
219136849Sscottl		$end *= 2;
220136849Sscottl		$o->print("GET $fname HTTP/1.1", $crlf,
221136849Sscottl		    "Host: ", $o->{host}, $crlf,
222136849Sscottl		    "Range: bytes=",$start, "-", $end-1, $crlf, $crlf);
223136849Sscottl		# get header
224136849Sscottl
225136849Sscottl		my $_ = $o->getline;
226136849Sscottl		if (!m,^HTTP/1\.1\s+(\d\d\d),) {
227136849Sscottl			print "ERROR\n";
228136849Sscottl			exit 1;
229136849Sscottl		}
230136849Sscottl		my $code = $1;
231136849Sscottl		my $h = {};
232136849Sscottl		while ($_ = $o->getline) {
233136849Sscottl			last if m/^$/;
234136849Sscottl			if (m/^([\w\-]+)\:\s*(.*)$/) {
235136849Sscottl				print STDERR "$1 => $2\n";
236136849Sscottl				$h->{$1} = $2;
237136849Sscottl			} else {
238136849Sscottl				print STDERR "unknown line: $_\n";
239136849Sscottl			}
240136849Sscottl		}
241136849Sscottl		if (defined $h->{'Content-Range'} && $h->{'Content-Range'} =~
242136849Sscottl			m/^bytes\s+\d+\-\d+\/(\d+)/) {
243136849Sscottl				$total_size = $1;
244136849Sscottl		}
245136849Sscottl		if ($first) {
246136849Sscottl			print "TRANSFER: $total_size\n";
247136849Sscottl			$first = 0;
248136849Sscottl		}
249136849Sscottl		my $r = $o->retrieve_response($h);
250136849Sscottl		if (!defined $r) {
251136849Sscottl			print "ERROR: can't decode response\n";
252136849Sscottl		}
253136849Sscottl		if ($code != 200 && $code != 206) {
254136849Sscottl			print "ERROR: code was $code\n";
255136849Sscottl			exit 1;
256136849Sscottl		}
257136849Sscottl		print $fh $r;
258136849Sscottl		$start = $end;
259136849Sscottl		if ($bailout) {
260136849Sscottl			exit 0;
261136849Sscottl		}
262136849Sscottl	} while ($end < $total_size);
263136849Sscottl}
264136849Sscottl
265136849Sscottlsub main
266136849Sscottl{
267136849Sscottl	my $self = shift;
268	my $o = _Proxy::Connection->new($self->{host}, "www");
269	while (<STDIN>) {
270		chomp;
271		if (m/^LIST\s+(.*)$/o) {
272			my $dname = $1;
273			batch(sub {get_directory($o, $dname);});
274		} elsif (m/^GET\s+(.*)$/o) {
275			my $fname = $1;
276			batch(sub { get_file($o, $fname);});
277		} elsif (m/^BYE$/o) {
278			exit(0);
279		} elsif (m/^ABORT$/o) {
280			abort_batch();
281		} else {
282			print "ERROR: Unknown command\n";
283		}
284	}
285}
286
2871;
288