HTTP.pm revision 1.9
1#! /usr/bin/perl
2# ex:ts=8 sw=4:
3# $OpenBSD: HTTP.pm,v 1.9 2011/07/19 17:27:43 espie Exp $
4#
5# Copyright (c) 2011 Marc Espie <espie@openbsd.org>
6#
7# Permission to use, copy, modify, and distribute this software for any
8# purpose with or without fee is hereby granted, provided that the above
9# copyright notice and this permission notice appear in all copies.
10#
11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
19use strict;
20use warnings;
21
22package OpenBSD::Repository::HTTP;
23sub urlscheme
24{
25	return 'http';
26}
27
28sub initiate
29{
30	my $self = shift;
31	my ($rdfh, $wrfh);
32	pipe($self->{getfh}, $rdfh);
33	pipe($wrfh, $self->{cmdfh});
34	my $pid = fork();
35	if ($pid == 0) {
36		close($self->{getfh});
37		close($self->{cmdfh});
38		close(STDOUT);
39		close(STDIN);
40		open(STDOUT, '>&', $wrfh);
41		open(STDIN, '<&', $rdfh);
42		_Proxy::main($self);
43	} else {
44		close($rdfh);
45		close($wrfh);
46		$self->{controller} = $pid;
47	}
48}
49
50package _Proxy::Header;
51
52sub new
53{
54	my $class = shift;
55	bless {}, $class;
56}
57
58sub code
59{
60	my $self = shift;
61	return $self->{code};
62}
63
64package _Proxy::Connection;
65sub new
66{
67	my ($class, $host, $port) = @_;
68	require IO::Socket::INET;
69	my $o = IO::Socket::INET->new(
70		PeerHost => $host,
71		PeerPort => $port);
72	my $old = select($o);
73	$| = 1;
74	select($old);
75	bless {fh => $o, host => $host, buffer => ''}, $class;
76}
77
78sub send_header
79{
80	my ($o, $document, %extra) = @_;
81	my $crlf="\015\012";
82	$o->print("GET $document HTTP/1.1", $crlf,
83	    "Host: ", $o->{host}, $crlf);
84	if (defined $extra{range}) {
85		my ($a, $b) = @{$extra{range}};
86	    	$o->print("Range: bytes=$a-$b", $crlf);
87	}
88	$o->print($crlf);
89}
90
91sub get_header
92{
93	my $o = shift;
94	my $_ = $o->getline;
95	if (!m,^HTTP/1\.1\s+(\d\d\d),) {
96		return undef;
97	}
98	my $h = _Proxy::Header->new;
99	$h->{code} = $1;
100	while ($_ = $o->getline) {
101		last if m/^$/;
102		if (m/^([\w\-]+)\:\s*(.*)$/) {
103			$h->{$1} = $2;
104		} else {
105			print STDERR "unknown line: $_\n";
106		}
107	}
108	if (defined $h->{'Content-Length'}) {
109		$h->{length} = $h->{'Content-Length'}
110	} elsif (defined $h->{'Transfer-Encoding'} &&
111	    $h->{'Transfer-Encoding'} eq 'chunked') {
112		$h->{chunked} = 1;
113	}
114	if (defined $h->{'Content-Range'} &&
115	    $h->{'Content-Range'} =~ m/^bytes\s+(\d+)\-(\d+)\/(\d+)/) {
116		($h->{start}, $h->{end}, $h->{size}) = ($1, $2, $3);
117	}
118	$o->{header} = $h;
119	return $h;
120}
121
122sub getline
123{
124	my $self = shift;
125	while (1) {
126		if ($self->{buffer} =~ s/^(.*?)\015\012//) {
127			return $1;
128		}
129		my $buffer;
130		$self->{fh}->recv($buffer, 1024);
131		$self->{buffer}.=$buffer;
132    	}
133}
134
135sub retrieve
136{
137	my ($self, $sz) = @_;
138	while(length($self->{buffer}) < $sz) {
139		my $buffer;
140		$self->{fh}->recv($buffer, $sz - length($self->{buffer}));
141		$self->{buffer}.=$buffer;
142	}
143	my $result= substr($self->{buffer}, 0, $sz);
144	$self->{buffer} = substr($self->{buffer}, $sz);
145	return $result;
146}
147
148sub retrieve_chunked
149{
150	my $self = shift;
151	my $result = '';
152	while (1) {
153		my $sz = $self->getline;
154		if ($sz =~ m/^([0-9a-fA-F]+)/) {
155			my $realsize = hex($1);
156			last if $realsize == 0;
157			$result .= $self->retrieve($realsize);
158		}
159	}
160	return $result;
161}
162
163sub retrieve_response
164{
165	my ($self, $h) = @_;
166
167	if ($h->{chunked}) {
168		return $self->retrieve_chunked;
169	}
170	if ($h->{length}) {
171		return $self->retrieve($h->{length});
172	}
173	return undef;
174}
175
176sub print
177{
178	my ($self, @l) = @_;
179#	print STDERR "Before print\n";
180	if (!print {$self->{fh}} @l) {
181		print STDERR "network print failed with $!\n";
182	}
183#	print STDERR "After print\n";
184}
185
186package _Proxy;
187
188my $pid;
189my $token = 0;
190
191sub batch(&)
192{
193	my $code = shift;
194	if (defined $pid) {
195		waitpid($pid, 0);
196		undef $pid;
197	}
198	$token++;
199	$pid = fork();
200	if (!defined $pid) {
201		print "ERROR: fork failed: $!\n";
202	}
203	if ($pid == 0) {
204		&$code();
205		exit(0);
206	}
207}
208
209sub abort_batch()
210{
211	if (defined $pid) {
212		kill 1, $pid;
213		waitpid($pid, 0);
214		undef $pid;
215	}
216	print "\nABORTED $token\n";
217}
218
219sub get_directory
220{
221	my ($o, $dname) = @_;
222	local $SIG{'HUP'} = 'IGNORE';
223	$o->send_header("$dname/");
224	my $h = $o->get_header;
225	if (!defined $h) {
226		print "ERROR: can't decode header\n";
227		exit 1;
228	}
229
230	my $r = $o->retrieve_response($h);
231	if (!defined $r) {
232		print "ERROR: can't decode response\n";
233	}
234	if ($h->code != 200) {
235			print "ERROR: code was ", $h->code, "\n";
236			exit 1;
237	}
238	print "SUCCESS: directory $dname\n";
239	for my $pkg ($r =~ m/\<A\s+HREF=\"(.+?)\.tgz\"\>/gio) {
240		$pkg = $1 if $pkg =~ m|^.*/(.*)$|;
241		# decode uri-encoding; from URI::Escape
242		$pkg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
243		print $pkg, "\n";
244	}
245	print "\n";
246	return;
247}
248
249use File::Basename;
250
251sub get_file
252{
253	my ($o, $fname) = @_;
254
255	my $bailout = 0;
256	$SIG{'HUP'} = sub {
257		$bailout++;
258	};
259	my $first = 1;
260	my $start = 0;
261	my $end = 2000;
262	my $total_size = 0;
263	open my $fh, ">", basename($fname);
264
265	do {
266		$end *= 2;
267		$o->send_header($fname, range => [$start, $end-1]);
268		my $h = $o->get_header;
269		if (!defined $h) {
270			print "ERROR\n";
271			exit 1;
272		}
273		if (defined $h->{size}) {
274			$total_size = $h->{size};
275		}
276		if ($h->code != 200 && $h->code != 206) {
277			print "ERROR: code was ", $h->code, "\n";
278			my $r = $o->retrieve_response($h);
279			exit 1;
280		}
281		if ($first) {
282			print "TRANSFER: $total_size\n";
283			$first = 0;
284		}
285		my $r = $o->retrieve_response($h);
286		if (!defined $r) {
287			print "ERROR: can't decode response\n";
288		}
289		print $fh $r;
290		$start = $end;
291		if ($bailout) {
292			exit 0;
293		}
294	} while ($end < $total_size);
295}
296
297sub main
298{
299	my $self = shift;
300	my $o = _Proxy::Connection->new($self->{host}, "www");
301	while (<STDIN>) {
302		chomp;
303		if (m/^LIST\s+(.*)$/o) {
304			my $dname = $1;
305			batch(sub {get_directory($o, $dname);});
306		} elsif (m/^GET\s+(.*)$/o) {
307			my $fname = $1;
308			batch(sub { get_file($o, $fname);});
309		} elsif (m/^BYE$/o) {
310			exit(0);
311		} elsif (m/^ABORT$/o) {
312			abort_batch();
313		} else {
314			print "ERROR: Unknown command\n";
315		}
316	}
317}
318
3191;
320