| Mojolicious documentation | Contained in the Mojolicious distribution. |
Mojo::Content::MultiPart - HTTP 1.1 MultiPart Content Container
use Mojo::Content::MultiPart;
my $content = Mojo::Content::MultiPart->new;
$content->parse('Content-Type: multipart/mixed; boundary=---foobar');
my $part = $content->parts->[4];
Mojo::Content::MultiPart is a container for HTTP 1.1 multipart content as described in RFC 2616.
Mojo::Content::MultiPart inherits all attributes from Mojo::Content and implements the following new ones.
partsmy $parts = $content->parts;
Content parts embedded in this multipart content, usually Mojo::Content::Single objects.
Mojo::Content::MultiPart inherits all methods from Mojo::Content and implements the following new ones.
body_contains my $found = $content->body_contains('foobarbaz');
Check if content parts contain a specific string.
body_sizemy $size = $content->body_size;
Content size in bytes.
build_boundarymy $boundary = $content->build_boundary;
Generate a suitable boundary for content.
get_body_chunkmy $chunk = $content->get_body_chunk(0);
Get a chunk of content starting from a specfic position.
parse $content = $content->parse('Content-Type: multipart/mixed');
Parse content chunk.
Mojolicious, Mojolicious::Guides, http://mojolicio.us.
| Mojolicious documentation | Contained in the Mojolicious distribution. |
package Mojo::Content::MultiPart; use Mojo::Base 'Mojo::Content'; use Mojo::Util 'b64_encode'; has parts => sub { [] }; sub body_contains { my ($self, $chunk) = @_; # Check parts my $found = 0; for my $part (@{$self->parts}) { my $headers = $part->build_headers; $found += 1 if $headers =~ /$chunk/g; $found += $part->body_contains($chunk); } $found ? 1 : 0; } sub body_size { my $self = shift; # Check for Content-Lenght header my $content_length = $self->headers->content_length; return $content_length if $content_length; # Calculate length of whole body my $boundary_length = length($self->build_boundary) + 6; my $len = 0; $len += $boundary_length - 2; for my $part (@{$self->parts}) { # Header $len += $part->header_size; # Body $len += $part->body_size; # Boundary $len += $boundary_length; } $len; } sub build_boundary { my $self = shift; # Check for existing boundary my $headers = $self->headers; my $type = $headers->content_type || ''; my $boundary; $type =~ /boundary=\"?([^\s\"]+)\"?/i and $boundary = $1; return $boundary if $boundary; # Generate and check boundary my $size = 1; while (1) { # Mostly taken from LWP $boundary = join('', map chr(rand(256)), 1 .. $size * 3); b64_encode $boundary; $boundary =~ s/\W/X/g; # Check parts for boundary last unless $self->body_contains($boundary); $size++; } # Add boundary to Content-Type header $type =~ /^(.*multipart\/[^;]+)(.*)$/; my $before = $1 || 'multipart/mixed'; my $after = $2 || ''; $headers->content_type("$before; boundary=$boundary$after"); $boundary; } sub get_body_chunk { my ($self, $offset) = @_; # Body generator return $self->generate_body_chunk($offset) if $self->on_read; # First boundary my $boundary = $self->build_boundary; my $boundary_length = length($boundary) + 6; my $len = $boundary_length - 2; return substr "--$boundary\x0d\x0a", $offset if $len > $offset; # Parts my $parts = $self->parts; for (my $i = 0; $i < @$parts; $i++) { my $part = $parts->[$i]; # Headers my $header_length = $part->header_size; return $part->get_header_chunk($offset - $len) if ($len + $header_length) > $offset; $len += $header_length; # Content my $content_length = $part->body_size; return $part->get_body_chunk($offset - $len) if ($len + $content_length) > $offset; $len += $content_length; # Boundary if (($len + $boundary_length) > $offset) { # Last boundary return substr "\x0d\x0a--$boundary--", $offset - $len if $#{$parts} == $i; # Middle boundary return substr "\x0d\x0a--$boundary\x0d\x0a", $offset - $len; } $len += $boundary_length; } } sub parse { my $self = shift; # Parse headers and chunked body $self->SUPER::parse(@_); # Custom body parser return $self if $self->on_read; # Upgrade state $self->{_multi_state} ||= 'multipart_preamble'; # Parse multipart content $self->_parse_multipart; $self; } sub _parse_multipart { my $self = shift; # Parse my $boundary = $self->is_multipart; while (1) { # Done last if $self->is_done; # Preamble if (($self->{_multi_state} || '') eq 'multipart_preamble') { last unless $self->_parse_multipart_preamble($boundary); } # Boundary elsif (($self->{_multi_state} || '') eq 'multipart_boundary') { last unless $self->_parse_multipart_boundary($boundary); } # Body elsif (($self->{_multi_state} || '') eq 'multipart_body') { last unless $self->_parse_multipart_body($boundary); } } } sub _parse_multipart_body { my ($self, $boundary) = @_; # Whole part in buffer my $pos = index $self->{_b2}, "\x0d\x0a--$boundary"; if ($pos < 0) { my $len = length($self->{_b2}) - (length($boundary) + 8); return unless $len > 0; # Store chunk my $chunk = substr $self->{_b2}, 0, $len, ''; $self->parts->[-1] = $self->parts->[-1]->parse($chunk); return; } # Store chunk my $chunk = substr $self->{_b2}, 0, $pos, ''; $self->parts->[-1] = $self->parts->[-1]->parse($chunk); $self->{_multi_state} = 'multipart_boundary'; 1; } sub _parse_multipart_boundary { my ($self, $boundary) = @_; # Boundary begins if ((index $self->{_b2}, "\x0d\x0a--$boundary\x0d\x0a") == 0) { substr $self->{_b2}, 0, length($boundary) + 6, ''; # New part push @{$self->parts}, Mojo::Content::Single->new(relaxed => 1); $self->{_multi_state} = 'multipart_body'; return 1; } # Boundary ends my $end = "\x0d\x0a--$boundary--"; if ((index $self->{_b2}, $end) == 0) { substr $self->{_b2}, 0, length $end, ''; # Done $self->{_state} = $self->{_multi_state} = 'done'; } undef; } sub _parse_multipart_preamble { my ($self, $boundary) = @_; # Replace preamble with carriage return and line feed my $pos = index $self->{_b2}, "--$boundary"; unless ($pos < 0) { substr $self->{_b2}, 0, $pos, "\x0d\x0a"; # Parse boundary $self->{_multi_state} = 'multipart_boundary'; return 1; } # No boundary yet undef; } 1; __END__