1package Util; 2 3use strict; 4use warnings; 5 6use IO::File qw(SEEK_SET SEEK_END); 7use IO::Dir; 8 9BEGIN { 10 our @EXPORT_OK = qw( 11 rewind 12 tmpfile 13 dir_list 14 slurp 15 parse_case 16 hashify 17 sort_headers 18 connect_args 19 clear_socket_source 20 set_socket_source 21 monkey_patch 22 $CRLF 23 $LF 24 ); 25 26 require Exporter; 27 *import = \&Exporter::import; 28} 29 30our $CRLF = "\x0D\x0A"; 31our $LF = "\x0A"; 32 33sub rewind(*) { 34 seek($_[0], 0, SEEK_SET) 35 || die(qq/Couldn't rewind file handle: '$!'/); 36} 37 38sub tmpfile { 39 my $fh = IO::File->new_tmpfile 40 || die(qq/Couldn't create a new temporary file: '$!'/); 41 42 binmode($fh) 43 || die(qq/Couldn't binmode temporary file handle: '$!'/); 44 45 if (@_) { 46 print({$fh} @_) 47 || die(qq/Couldn't write to temporary file handle: '$!'/); 48 49 seek($fh, 0, SEEK_SET) 50 || die(qq/Couldn't rewind temporary file handle: '$!'/); 51 } 52 53 return $fh; 54} 55 56sub dir_list { 57 my ($dir, $filter) = @_; 58 $filter ||= qr/./; 59 my $d = IO::Dir->new($dir) 60 or return; 61 return map { "$dir/$_" } sort grep { /$filter/ } grep { /^[^.]/ } $d->read; 62} 63 64sub slurp (*) { 65 my ($fh) = @_; 66 67 seek($fh, 0, SEEK_END) 68 || die(qq/Couldn't navigate to EOF on file handle: '$!'/); 69 70 my $exp = tell($fh); 71 72 rewind($fh); 73 74 binmode($fh) 75 || die(qq/Couldn't binmode file handle: '$!'/); 76 77 my $buf = do { local $/; <$fh> }; 78 my $got = length $buf; 79 80 ($exp == $got) 81 || die(qq[I/O read mismatch (expexted: $exp got: $got)]); 82 83 return $buf; 84} 85 86sub parse_case { 87 my ($case) = @_; 88 my %args; 89 my $key = ''; 90 for my $line ( split "\n", $case ) { 91 chomp $line; 92 if ( substr($line,0,1) eq q{ } ) { 93 $line =~ s/^\s+//; 94 push @{$args{$key}}, $line; 95 } 96 else { 97 $key = $line; 98 } 99 } 100 return \%args; 101} 102 103sub hashify { 104 my ($lines) = @_; 105 return unless $lines; 106 my %hash; 107 for my $line ( @$lines ) { 108 my ($k,$v) = ($line =~ m{^([^:]+): (.*)$}g); 109 $hash{$k} = [ $hash{$k} ] if exists $hash{$k} && ref $hash{$k} ne 'ARRAY'; 110 if ( ref($hash{$k}) eq 'ARRAY' ) { 111 push @{$hash{$k}}, $v; 112 } 113 else { 114 $hash{$k} = $v; 115 } 116 } 117 return %hash; 118} 119 120sub sort_headers { 121 my ($text) = shift; 122 my @lines = split /$CRLF/, $text; 123 my $request = shift(@lines) || ''; 124 my @headers; 125 while (my $line = shift @lines) { 126 last unless length $line; 127 push @headers, $line; 128 } 129 @headers = sort @headers; 130 return join($CRLF, $request, @headers, '', @lines); 131} 132 133{ 134 my (@req_fh, @res_fh, $monkey_host, $monkey_port); 135 136 sub clear_socket_source { 137 @req_fh = (); 138 @res_fh = (); 139 } 140 141 sub set_socket_source { 142 my ($req_fh, $res_fh) = @_; 143 push @req_fh, $req_fh; 144 push @res_fh, $res_fh; 145 } 146 147 sub connect_args { return ($monkey_host, $monkey_port) } 148 149 sub monkey_patch { 150 no warnings qw/redefine once/; 151 *HTTP::Tiny::Handle::can_read = sub {1}; 152 *HTTP::Tiny::Handle::can_write = sub {1}; 153 *HTTP::Tiny::Handle::connect = sub { 154 my ($self, $scheme, $host, $port, $peer) = @_; 155 $self->{host} = $monkey_host = $host; 156 $self->{port} = $monkey_port = $port; 157 $self->{peer} = $peer; 158 $self->{scheme} = $scheme; 159 $self->{fh} = shift @req_fh; 160 $self->{pid} = $$; 161 $self->{tid} = HTTP::Tiny::Handle::_get_tid(); 162 return $self; 163 }; 164 my $original_write_request = \&HTTP::Tiny::Handle::write_request; 165 *HTTP::Tiny::Handle::write_request = sub { 166 my ($self, $request) = @_; 167 $original_write_request->($self, $request); 168 $self->{fh} = shift @res_fh; 169 }; 170 *HTTP::Tiny::Handle::close = sub { 1 }; # don't close our temps 171 172 # don't try to proxy in mock-mode 173 delete $ENV{$_} for map { $_, uc($_) } qw/http_proxy https_proxy all_proxy/; 174 } 175} 176 1771; 178 179 180# vim: et ts=4 sts=4 sw=4: 181