Skip to content

Commit

Permalink
added support for multiple uploads sharing the same name to Mojo::Use…
Browse files Browse the repository at this point in the history
…rAgent::Transactor
  • Loading branch information
kraih committed Jan 9, 2013
1 parent 5765f32 commit 13c4e85
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 52 deletions.
2 changes: 2 additions & 0 deletions Changes
@@ -1,5 +1,7 @@

3.76 2013-01-09
- Added support for multiple uploads sharing the same name to
Mojo::UserAgent::Transactor.
- Fixed support for multiple uploads in Mojolicious::Controller.

3.75 2013-01-08
Expand Down
87 changes: 48 additions & 39 deletions lib/Mojo/UserAgent/Transactor.pm
@@ -1,7 +1,7 @@
package Mojo::UserAgent::Transactor;
use Mojo::Base -base;

use File::Spec::Functions 'splitpath';
use File::Basename 'basename';
use Mojo::Asset::File;
use Mojo::Asset::Memory;
use Mojo::Content::MultiPart;
Expand Down Expand Up @@ -46,27 +46,16 @@ sub form {
my $value = $form->{$name};

# Array
if (ref $value eq 'ARRAY') { $params->append($name, $_) for @$value }
if (ref $value eq 'ARRAY') {
for my $value (@$value) {
$params->append($name, $value) and next unless ref $value eq 'HASH';
++$multipart and $params->append($name, _asset($value));
}
}

# Hash
elsif (ref $value eq 'HASH') {

# Enforce "multipart/form-data"
$multipart++;

# File
if (my $file = $value->{file}) {
$value->{file} = Mojo::Asset::File->new(path => $file) if !ref $file;
$value->{filename} ||= (splitpath($value->{file}->path))[2]
if $value->{file}->isa('Mojo::Asset::File');
}

# Memory
elsif (defined(my $content = delete $value->{content})) {
$value->{file} = Mojo::Asset::Memory->new->add_chunk($content);
}

push @{$params->params}, $name, $value;
++$multipart and $params->append($name, _asset($value));
}

# Single value
Expand Down Expand Up @@ -200,39 +189,57 @@ sub websocket {
return $tx;
}

sub _asset {
my $value = shift;

# File
if (my $file = $value->{file}) {
$value->{file} = Mojo::Asset::File->new(path => $file) if !ref $file;
$value->{filename} ||= basename $value->{file}->path
if $value->{file}->isa('Mojo::Asset::File');
}

# Memory
elsif (defined(my $content = delete $value->{content})) {
$value->{file} = Mojo::Asset::Memory->new->add_chunk($content);
}

return $value;
}

sub _multipart {
my ($self, $encoding, $form) = @_;

# Parts
my @parts;
for my $name (sort keys %$form) {
my $values = $form->{$name};
my $part = Mojo::Content::Single->new;

# File
my $filename;
my $headers = $part->headers;
if (ref $values eq 'HASH') {
$filename = delete $values->{filename} || $name;
$filename = encode $encoding, $filename if $encoding;
push @parts, $part->asset(delete $values->{file});
$headers->from_hash($values);
}
for my $value (ref $values eq 'ARRAY' ? @$values : ($values)) {
my $part = Mojo::Content::Single->new;

# Fields
else {
for my $value (ref $values ? @$values : ($values)) {
# File
my $filename;
my $headers = $part->headers;
if (ref $value eq 'HASH') {
$filename = delete $value->{filename} || $name;
$filename = encode $encoding, $filename if $encoding;
push @parts, $part->asset(delete $value->{file});
$headers->from_hash($value);
}

# Fields
else {
push @parts, $part = Mojo::Content::Single->new(headers => $headers);
$value = encode $encoding, $value if $encoding;
$part->asset->add_chunk($value);
}
}

# Content-Disposition
$name = encode $encoding, $name if $encoding;
my $disposition = qq{form-data; name="$name"};
$disposition .= qq{; filename="$filename"} if $filename;
$headers->content_disposition($disposition);
# Content-Disposition
$name = encode $encoding, $name if $encoding;
my $disposition = qq{form-data; name="$name"};
$disposition .= qq{; filename="$filename"} if $filename;
$headers->content_disposition($disposition);
}
}

return \@parts;
Expand Down Expand Up @@ -297,6 +304,8 @@ Actual endpoint for transaction.
my $tx = $t->form('http://kraih.com' => {a => [qw(b c d)]});
my $tx = $t->form('http://kraih.com' => {mytext => {file => '/foo.txt'}});
my $tx = $t->form('http://kraih.com' => {mytext => {content => 'lalala'}});
my $tx = $t->form('http://kraih.com' =>
{mytexts => [{content => 'first'}, {content => 'second'}]});
my $tx = $t->form('http://kraih.com' => {
myzip => {
file => Mojo::Asset::Memory->new->add_chunk('lalala'),
Expand Down
2 changes: 1 addition & 1 deletion lib/Mojolicious/Controller.pm
Expand Up @@ -133,7 +133,7 @@ sub param {
return ref $value eq 'ARRAY' ? wantarray ? @$value : $$value[0] : $value;
}

# Upload
# Uploads
return $req->upload($name) if $req->upload($name);

# Param values
Expand Down
25 changes: 25 additions & 0 deletions t/mojo/transactor.t
Expand Up @@ -206,6 +206,31 @@ is $tx->req->upload('☃')->filename, '☃.jpg', 'right filename';
is $tx->req->upload('')->size, 7, 'right size';
is $tx->req->upload('')->slurp, 'snowman', 'right content';

# Multipart form with multiple uploads sharing the same name
$tx = $t->form(
'http://kraih.com/foo' => {
mytext => [
{content => 'just', filename => 'one.txt'},
{content => 'works', filename => 'two.txt'}
]
}
);
is $tx->req->url->to_abs, 'http://kraih.com/foo', 'right URL';
is $tx->req->method, 'POST', 'right method';
is $tx->req->headers->content_type, 'multipart/form-data',
'right "Content-Type" value';
like $tx->req->content->parts->[0]->headers->content_disposition, qr/mytext/,
'right "Content-Disposition" value';
like $tx->req->content->parts->[0]->headers->content_disposition,
qr/one\.txt/, 'right "Content-Disposition" value';
is $tx->req->content->parts->[0]->asset->slurp, 'just', 'right part';
like $tx->req->content->parts->[1]->headers->content_disposition, qr/mytext/,
'right "Content-Disposition" value';
like $tx->req->content->parts->[1]->headers->content_disposition,
qr/two\.txt/, 'right "Content-Disposition" value';
is $tx->req->content->parts->[1]->asset->slurp, 'works', 'right part';
is $tx->req->content->parts->[2], undef, 'no more parts';

# Simple endpoint
$tx = $t->tx(GET => 'mojolicio.us');
is(($t->endpoint($tx))[0], 'http', 'right scheme');
Expand Down
20 changes: 8 additions & 12 deletions t/mojolicious/upload_lite_app.t
Expand Up @@ -90,17 +90,13 @@ $t->post_form_ok('/multi',
->status_is(200)->content_is('file11111file211112222');

# POST /same_name (multiple file uploads with same name)
my $tx = $t->ua->build_tx(POST => '/same_name');
$tx->req->content(Mojo::Content::MultiPart->new);
$tx->req->headers->content_type('multipart/form-data');
push @{$tx->req->content->parts}, Mojo::Content::Single->new;
$tx->req->content->parts->[-1]
->headers->content_disposition('form-data;name="file";filename="one.txt"');
$tx->req->content->parts->[-1]->asset->add_chunk('just');
push @{$tx->req->content->parts}, Mojo::Content::Single->new;
$tx->req->content->parts->[-1]
->headers->content_disposition('form-data;name="file";filename="two.txt"');
$tx->req->content->parts->[-1]->asset->add_chunk('works');
$t->request_ok($tx)->status_is(200)->content_is('one.txtjusttwo.txtworks');
$t->post_form_ok(
'/same_name' => {
file => [
{content => 'just', filename => 'one.txt'},
{content => 'works', filename => 'two.txt'}
]
}
)->status_is(200)->content_is('one.txtjusttwo.txtworks');

done_testing();

0 comments on commit 13c4e85

Please sign in to comment.