| 16 | | my $class = shift; |
|---|
| 17 | | my $self = bless {}, $class; |
|---|
| 18 | | $self->load(@_); |
|---|
| 19 | | |
|---|
| 20 | | # Now merge up the parts into the hash itself, so it looks "normal" |
|---|
| 21 | | foreach my $part (@{$self->{parts}}) { |
|---|
| 22 | | if($part->{name}) { |
|---|
| 23 | | if($part->{filename}) { |
|---|
| 24 | | $self->{$part->{name}} = $part; |
|---|
| 25 | | if(exists($part->{payload})) { |
|---|
| 26 | | open($part->{handle}, "<", \$part->{payload}); |
|---|
| 27 | | delete $part->{payload}; |
|---|
| 28 | | } |
|---|
| 29 | | $part->{handle}->seek(0,0) if(UNIVERSAL::can($part->{handle}, 'seek')); |
|---|
| 30 | | } |
|---|
| 31 | | else { |
|---|
| 32 | | $self->{$part->{name}} = $part->{payload}; |
|---|
| 33 | | } |
|---|
| 34 | | } |
|---|
| 35 | | delete $part->{maxmem}; |
|---|
| 36 | | delete $part->{name}; |
|---|
| 37 | | } |
|---|
| 38 | | delete $self->{parts}; |
|---|
| 39 | | $self; |
|---|
| | 21 | my $class = shift; |
|---|
| | 22 | my $self = bless {}, $class; |
|---|
| | 23 | $self->load(@_); |
|---|
| | 24 | |
|---|
| | 25 | # Now merge up the parts into the hash itself, so it looks "normal" |
|---|
| | 26 | foreach my $part (@{$self->{parts}}) { |
|---|
| | 27 | if ($part->{name}) { |
|---|
| | 28 | my $name = $part->{name}; |
|---|
| | 29 | |
|---|
| | 30 | if ($part->{filename}) { |
|---|
| | 31 | # Looks like a file. Trim down the Part a bit. |
|---|
| | 32 | delete $part->{maxmem}; |
|---|
| | 33 | delete $part->{name}; |
|---|
| | 34 | |
|---|
| | 35 | # Prep the Part for reading as a fielhandle. |
|---|
| | 36 | if (exists($part->{payload}) && !exists($part->{handle})) { |
|---|
| | 37 | # So we have a payload, but not a handle? Must have been a small file. |
|---|
| | 38 | # Create a new IO::Handle by opening the payload "in memory" |
|---|
| | 39 | # See perldoc -f open and perldoc perliol |
|---|
| | 40 | open($part->{handle}, "<", \$part->{payload}); |
|---|
| | 41 | delete $part->{payload}; |
|---|
| | 42 | } |
|---|
| | 43 | $part->{handle}->seek(0,0) if(UNIVERSAL::can($part->{handle}, 'seek')); |
|---|
| | 44 | |
|---|
| | 45 | # OK, now store the whole Part. Be careful not to stomp on duplicates. |
|---|
| | 46 | if (exists $self->{$name}) { |
|---|
| | 47 | # We already have a param with this name. Promote to arrayref. |
|---|
| | 48 | if (ref($self->{$name}) eq 'ARRAY') { |
|---|
| | 49 | push @{$self->{$name}}, $part; |
|---|
| | 50 | } else { |
|---|
| | 51 | # Need to make it an arrayref. |
|---|
| | 52 | $self->{$name} = [ $self->{$name}, $part ]; |
|---|
| | 53 | } |
|---|
| | 54 | } else { |
|---|
| | 55 | $self->{$part->{name}} = $part; |
|---|
| | 56 | } |
|---|
| | 57 | |
|---|
| | 58 | } else { |
|---|
| | 59 | # Doesn't look like a file upload. Drop all Part trappings, |
|---|
| | 60 | # and just keep the payload. |
|---|
| | 61 | if (exists $self->{$name}) { |
|---|
| | 62 | # We already have a param with this name. Promote to arrayref. |
|---|
| | 63 | if (ref($self->{$name}) eq 'ARRAY') { |
|---|
| | 64 | push @{$self->{$name}}, $part->{payload}; |
|---|
| | 65 | } else { |
|---|
| | 66 | # Need to make it an arrayref. |
|---|
| | 67 | $self->{$name} = [ $self->{$name}, $part->{payload} ]; |
|---|
| | 68 | } |
|---|
| | 69 | } else { |
|---|
| | 70 | $self->{$part->{name}} = $part->{payload}; |
|---|
| | 71 | } |
|---|
| | 72 | } |
|---|
| | 73 | } else { |
|---|
| | 74 | # Drop nameless parts? |
|---|
| | 75 | } |
|---|
| | 76 | } |
|---|
| | 77 | delete $self->{parts}; |
|---|
| | 78 | |
|---|
| | 79 | return $self; |
|---|
| 43 | | my ($self, $r, $cl, $b) = @_; |
|---|
| 44 | | my $BLOCK = $r->dir_config('PostBlockSize') || $Mungo::DEFAULT_POST_BLOCK; |
|---|
| 45 | | my $MAXSIZE = $r->dir_config('PostMaxSize') || $Mungo::DEFAULT_POST_MAX_SIZE; |
|---|
| 46 | | my $MAXPART = $r->dir_config('PostMaxPart') || $Mungo::DEFAULT_POST_MAX_PART; |
|---|
| 47 | | my $MAXMEM = $r->dir_config('PostMaxInMemory') || |
|---|
| 48 | | $Mungo::DEFAULT_POST_MAX_IN_MEMORY; |
|---|
| 49 | | |
|---|
| 50 | | # I expect to see the boundary as the first thing.. so $BLOCK has to be |
|---|
| 51 | | # at least the length of boundary + CR LF |
|---|
| 52 | | $BLOCK = length($b) + 2 unless($BLOCK > length($b) + 2); |
|---|
| 53 | | |
|---|
| 54 | | my $bytes_read = 0; |
|---|
| 55 | | my $part = ''; |
|---|
| 56 | | my $buffer = "\r\n"; |
|---|
| 57 | | my $new_buffer = ''; |
|---|
| 58 | | my $current_part; |
|---|
| 59 | | while($bytes_read < $cl) { |
|---|
| 60 | | my $to_read = ($BLOCK < $cl - $bytes_read) ? $BLOCK : ($cl - $bytes_read); |
|---|
| 61 | | $r->read($new_buffer, $to_read); |
|---|
| 62 | | $buffer .= $new_buffer; |
|---|
| 63 | | my $pos; |
|---|
| 64 | | while(($pos = index($buffer, "\r\n--$b\r\n")) >= 0) { |
|---|
| 65 | | if($current_part) { |
|---|
| 66 | | $current_part->append(substr($buffer, 0, $pos)); |
|---|
| 67 | | } |
|---|
| 68 | | $current_part = Mungo::MultipartFormData::Part->new($MAXMEM); |
|---|
| 69 | | push @{$self->{parts}}, $current_part; |
|---|
| 70 | | substr($buffer, 0, $pos + length($b) + 6) = ''; |
|---|
| 71 | | } |
|---|
| 72 | | if(!$current_part) { |
|---|
| 73 | | $current_part = Mungo::MultipartFormData::Part->new($MAXMEM); |
|---|
| 74 | | push @{$self->{parts}}, $current_part; |
|---|
| 75 | | } |
|---|
| 76 | | if(($pos = index($buffer, "\r\n--$b--")) >= 0) { |
|---|
| 77 | | $current_part->append(substr($buffer, 0, $pos)); |
|---|
| 78 | | $buffer = ''; |
|---|
| 79 | | } |
|---|
| 80 | | elsif(length($buffer) > length($b) + 6) { |
|---|
| 81 | | # This is to make sure we leave enough to index() in the next pass |
|---|
| 82 | | $current_part->append(substr($buffer, 0, |
|---|
| 83 | | length($buffer) - length($b) - 6)); |
|---|
| 84 | | substr($buffer, 0, length($buffer) - length($b) - 6) = ''; |
|---|
| 85 | | } |
|---|
| 86 | | $bytes_read += length($new_buffer); |
|---|
| 87 | | } |
|---|
| | 83 | my ($self, $r, $cl, $boundary) = @_; |
|---|
| | 84 | my $BLOCK_SIZE = $r->dir_config('PostBlockSize') || $Mungo::DEFAULT_POST_BLOCK_SIZE; |
|---|
| | 85 | my $MAXSIZE = $r->dir_config('PostMaxSize') || $Mungo::DEFAULT_POST_MAX_SIZE; |
|---|
| | 86 | my $MAXPART = $r->dir_config('PostMaxPart') || $Mungo::DEFAULT_POST_MAX_PART; |
|---|
| | 87 | my $MAXMEM = $r->dir_config('PostMaxInMemory') |
|---|
| | 88 | || $Mungo::DEFAULT_POST_MAX_IN_MEMORY; |
|---|
| | 89 | |
|---|
| | 90 | # I expect to see the boundary as the first thing.. so $BLOCK_SIZE has to be |
|---|
| | 91 | # at least the length of boundary + CR LF |
|---|
| | 92 | $BLOCK_SIZE = length($boundary) + 2 unless($BLOCK_SIZE > length($boundary) + 2); |
|---|
| | 93 | |
|---|
| | 94 | my $bytes_read = 0; |
|---|
| | 95 | my $part = ''; |
|---|
| | 96 | my $buffer = "\r\n"; |
|---|
| | 97 | my $new_buffer = ''; |
|---|
| | 98 | my $current_part; |
|---|
| | 99 | while($bytes_read < $cl) { |
|---|
| | 100 | |
|---|
| | 101 | # Read in a chunk |
|---|
| | 102 | my $to_read = ($BLOCK_SIZE < $cl - $bytes_read) ? $BLOCK_SIZE : ($cl - $bytes_read); |
|---|
| | 103 | $r->read($new_buffer, $to_read); |
|---|
| | 104 | $buffer .= $new_buffer; |
|---|
| | 105 | |
|---|
| | 106 | # The chunk may contain one or more inner boundaries, meaning we have |
|---|
| | 107 | # reached the end of a Part. |
|---|
| | 108 | my $pos; |
|---|
| | 109 | while(($pos = index($buffer, "\r\n--$boundary\r\n")) >= 0) { |
|---|
| | 110 | if($current_part) { |
|---|
| | 111 | $current_part->append(substr($buffer, 0, $pos)); |
|---|
| | 112 | } |
|---|
| | 113 | $current_part = Mungo::MultipartFormData::Part->new($MAXMEM); |
|---|
| | 114 | push @{$self->{parts}}, $current_part; |
|---|
| | 115 | # Remove the processed portion of the buffer (lvalue form of substr) |
|---|
| | 116 | substr($buffer, 0, $pos + length("\r\n--$boundary\r\n")) = ''; |
|---|
| | 117 | } |
|---|
| | 118 | |
|---|
| | 119 | # No (more) inner boundaries in the buffer. Make sure |
|---|
| | 120 | if(!$current_part) { |
|---|
| | 121 | $current_part = Mungo::MultipartFormData::Part->new($MAXMEM); |
|---|
| | 122 | push @{$self->{parts}}, $current_part; |
|---|
| | 123 | } |
|---|
| | 124 | |
|---|
| | 125 | # The last boundary will not have a \r\n at the end. Check for that and |
|---|
| | 126 | # append to the current part. |
|---|
| | 127 | if(($pos = index($buffer, "\r\n--$boundary--")) >= 0) { |
|---|
| | 128 | $current_part->append(substr($buffer, 0, $pos)); |
|---|
| | 129 | $buffer = ''; |
|---|
| | 130 | } elsif(length($buffer) > length("\r\n--$boundary--")) { |
|---|
| | 131 | # This is to make sure we leave enough to index() in the next pass |
|---|
| | 132 | $current_part->append(substr($buffer, 0, |
|---|
| | 133 | length($buffer) - length($boundary) - 6)); |
|---|
| | 134 | substr($buffer, 0, length($buffer) - length($boundary) - 6) = ''; |
|---|
| | 135 | } |
|---|
| | 136 | $bytes_read += length($new_buffer); |
|---|
| | 137 | } |
|---|
| 122 | | my $self = shift; |
|---|
| 123 | | my $buffer = shift; |
|---|
| 124 | | $self->{size} += length($buffer); |
|---|
| 125 | | if(exists($self->{handle})) { |
|---|
| 126 | | $self->{handle}->print($buffer); |
|---|
| 127 | | } |
|---|
| 128 | | else { |
|---|
| 129 | | $self->{payload} .= $buffer; |
|---|
| 130 | | $self->extract_headers(); |
|---|
| 131 | | if(length($self->{payload}) > $self->{maxmem}) { |
|---|
| 132 | | my($fh, $file) = tmpnam(); |
|---|
| 133 | | my $seekable = IO::File->new($file, "r+") if($fh); |
|---|
| 134 | | if(!$seekable) { |
|---|
| 135 | | print STDERR "Could not create tmpfile (for POST storage)\n"; |
|---|
| 136 | | return undef; |
|---|
| 137 | | } |
|---|
| 138 | | $fh->close(); |
|---|
| 139 | | unlink($file); |
|---|
| 140 | | $self->{handle} = $seekable; |
|---|
| 141 | | $self->{handle}->print($self->{payload}) || die "cannot write to tmpfile"; |
|---|
| 142 | | delete $self->{payload}; |
|---|
| 143 | | } |
|---|
| 144 | | } |
|---|
| | 172 | my $self = shift; |
|---|
| | 173 | my $buffer = shift; |
|---|
| | 174 | $self->{size} += length($buffer); |
|---|
| | 175 | |
|---|
| | 176 | # If we've already gotten so big that we store in a tempfile, just write to it. |
|---|
| | 177 | if (exists($self->{handle})) { |
|---|
| | 178 | $self->{handle}->print($buffer); |
|---|
| | 179 | } else { |
|---|
| | 180 | $self->{payload} .= $buffer; |
|---|
| | 181 | $self->extract_headers(); |
|---|
| | 182 | if (length($self->{payload}) > $self->{maxmem}) { |
|---|
| | 183 | # We've gotten too big for our britches. |
|---|
| | 184 | my ($fh, $file) = tmpnam(); |
|---|
| | 185 | |
|---|
| | 186 | # Upgrade the filehandle returned by tmpname so we can seek on it |
|---|
| | 187 | my $seekable; |
|---|
| | 188 | $seekable = IO::File->new($file, "r+") if ($fh); |
|---|
| | 189 | if(!$seekable) { |
|---|
| | 190 | print STDERR "Could not create tmpfile (for POST storage)\n"; |
|---|
| | 191 | return undef; |
|---|
| | 192 | } |
|---|
| | 193 | $self->{handle} = $seekable; |
|---|
| | 194 | |
|---|
| | 195 | # Cleanup |
|---|
| | 196 | $fh->close(); # We're done with the fh returned by tmpname (we have a seekable version in $self->handle) |
|---|
| | 197 | unlink($file); # Unlink the file. Now the only reference is our handle; when that does away, the inode will be freed. |
|---|
| | 198 | |
|---|
| | 199 | # OK, send the payload to the filehandle. |
|---|
| | 200 | $self->{handle}->print($self->{payload}) || die "cannot write to tmpfile $file"; |
|---|
| | 201 | delete $self->{payload}; |
|---|
| | 202 | |
|---|
| | 203 | # Next time we append, since we have $self->{handle}, we'll print imemdiately. |
|---|
| | 204 | } |
|---|
| | 205 | } |
|---|