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