HTTP.pm revision 1.4
1#! /usr/bin/perl 2# ex:ts=8 sw=4: 3# $OpenBSD: HTTP.pm,v 1.4 2011/07/18 20:03:12 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::Connection; 51sub new 52{ 53 my ($class, $host, $port) = @_; 54 require IO::Socket::INET; 55 my $o = IO::Socket::INET->new( 56 PeerHost => $host, 57 PeerPort => $port); 58 bless {fh => $o, host => $host, buffer => ''}, $class; 59} 60 61sub getline 62{ 63 my $self = shift; 64 while (1) { 65 if ($self->{buffer} =~ s/^(.*?)\015\012//) { 66 return $1; 67 } 68 my $buffer; 69 $self->{fh}->recv($buffer, 1024); 70 $self->{buffer}.=$buffer; 71 } 72} 73 74sub retrieve 75{ 76 my ($self, $sz) = @_; 77 while(length($self->{buffer}) < $sz) { 78 my $buffer; 79 $self->{fh}->recv($buffer, $sz - length($self->{buffer})); 80 $self->{buffer}.=$buffer; 81 } 82 my $result= substr($self->{buffer}, 0, $sz); 83 $self->{buffer} = substr($self->{buffer}, $sz); 84 return $result; 85} 86 87sub retrieve_chunked 88{ 89 my $self = shift; 90 my $result = ''; 91 while (1) { 92 my $sz = $self->getline; 93 if ($sz =~ m/^([0-9a-fA-F]+)/) { 94 my $realsize = hex($1); 95 last if $realsize == 0; 96 $result .= $self->retrieve($realsize); 97 } 98 } 99 return $result; 100} 101 102sub print 103{ 104 my ($self, @l) = @_; 105 print {$self->{fh}} @l; 106} 107 108package _Proxy; 109 110my $pid; 111my $token = 0; 112 113sub batch(&) 114{ 115 my $code = shift; 116 if (defined $pid) { 117 waitpid($pid, 0); 118 undef $pid; 119 } 120 $token++; 121 $pid = fork(); 122 if (!defined $pid) { 123 print "ERROR: fork failed: $!\n"; 124 } 125 if ($pid == 0) { 126 &$code(); 127 exit(0); 128 } 129} 130 131sub abort_batch() 132{ 133 if (defined $pid) { 134 kill 1, $pid; 135 waitpid($pid, 0); 136 undef $pid; 137 } 138 print "\nABORTED $token\n"; 139} 140 141sub get_directory 142{ 143 my ($o, $dname) = @_; 144 my $crlf="\015\012"; 145 $o->print("GET $dname/ HTTP/1.1", $crlf, 146 "Host: ", $o->{host}, $crlf, $crlf); 147 # get header 148 149 my $_ = $o->getline; 150 if (!m,^HTTP/1\.1\s+(\d\d\d),) { 151 print "ERROR\n"; 152 return; 153 } 154 my $code = $1; 155 my $h = {}; 156 while ($_ = $o->getline) { 157 last if m/^$/; 158 if (m/^([\w\-]+)\:\s*(.*)$/) { 159 print STDERR "$1 => $2\n"; 160 $h->{$1} = $2; 161 } else { 162 print STDERR "unknown line: $_\n"; 163 } 164 } 165 if ($h->{'Transfer-Encoding'} eq 'chunked') { 166 my $buffer = $o->retrieve_chunked; 167 if ($code == 200) { 168 print "SUCCESS: directory $dname\n"; 169 for my $pkg ($buffer =~ m/\<A\s+HREF=\"(.+?)\.tgz\"\>/gio) { 170 $pkg = $1 if $pkg =~ m|^.*/(.*)$|; 171 # decode uri-encoding; from URI::Escape 172 $pkg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; 173 print $pkg, "\n"; 174 } 175 print "\n"; 176 return; 177 } else { 178 print "ERROR: code was $code\n"; 179 return; 180 } 181 } else { 182 print "ERROR: can't decode non-chunked\n"; 183 } 184} 185 186 187sub main 188{ 189 my $self = shift; 190 my $o = _Proxy::Connection->new($self->{host}, "www"); 191 while (<STDIN>) { 192 chomp; 193 if (m/^LIST\s+(.*)$/o) { 194 my $dname = $1; 195 batch(sub {get_directory($o, $dname);}); 196 } elsif (m/^GET\s+(.*)$/o) { 197 my $fname = $1; 198 batch(sub { 199 if (open(my $fh, '<', $fname)) { 200 my $size = (stat $fh)[7]; 201 print "TRANSFER: $size\n"; 202 my $buffer = ''; 203 while (read($fh, $buffer, 1024 * 1024) > 0) { 204 print $buffer; 205 } 206 close($fh); 207 } else { 208 print "ERROR: bad file $fname $!\n"; 209 } 210 }); 211 } elsif (m/^BYE$/o) { 212 exit(0); 213 } elsif (m/^ABORT$/o) { 214 abort_batch(); 215 } else { 216 print "ERROR: Unknown command\n"; 217 } 218 } 219} 220 221 222sub get_file 223{ 224 my ($o, $file) = @_; 225 my $crlf="\015\012"; 226 open my $fh, '>', $file; 227 228 my $start = 0; 229 my $end = 4000; 230 my $total_size = 0; 231 232 do { 233 $end *= 2; 234 $o->print("GET /pub/OpenBSD/snapshots/packages/amd64/$file HTTP/1.1$crlf", 235 "Host: www.w3.org$crlf", 236 "Range: bytes=",$start, "-", $end-1, $crlf, $crlf); 237 238 # get header 239 240 my $_ = $o->getline; 241 if (m,^HTTP/1\.1\s+(\d\d\d),) { 242 my $code = $1; 243 print "Code: $code\n"; 244 } else { 245 print $_, "\n"; 246 } 247 my $h = {}; 248 while ($_ = $o->getline) { 249 last if m/^$/; 250 if (m/^([\w\-]+)\:\s*(.*)$/) { 251 print "$1 => $2\n"; 252 $h->{$1} = $2; 253 } else { 254 print "unknown line: $_\n"; 255 } 256 } 257 258 if (defined $h->{'Content-Range'} && $h->{'Content-Range'} =~ 259 m/^bytes\s+\d+\-\d+\/(\d+)/) { 260 $total_size = $1; 261 } 262 print "END OF HEADER\n"; 263 264 if (defined $h->{'Content-Length'}) { 265 my $v = $o->retrieve($h->{'Content-Length'}); 266 print $fh $v; 267 } 268 $start = $end; 269 } while ($end < $total_size); 270} 271 2721; 273