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