Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fixed RFC 7230 compliance bugs in Mojo::Headers
  • Loading branch information
kraih committed Jun 13, 2014
1 parent a5d43b3 commit 79c941f
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 40 deletions.
3 changes: 2 additions & 1 deletion Changes
@@ -1,5 +1,6 @@

5.07 2014-06-12
5.07 2014-06-13
- Fixed RFC 7230 compliance bugs in Mojo::Headers.

5.06 2014-06-11
- Added deserialize and serialize attributes to Mojolicious::Sessions.
Expand Down
17 changes: 7 additions & 10 deletions lib/Mojo/Headers.pm
Expand Up @@ -28,7 +28,7 @@ sub add {
# Make sure we have a normal case entry for name
my $key = lc $name;
$self->{normalcase}{$key} //= $name unless $NORMALCASE{$key};
push @{$self->{headers}{$key}}, map { ref $_ eq 'ARRAY' ? $_ : [$_] } @_;
push @{$self->{headers}{$key}}, @_;

return $self;
}
Expand Down Expand Up @@ -63,7 +63,7 @@ sub header {
return $self->remove($name)->add($name, @_) if @_;

return undef unless my $headers = $self->{headers}{lc $name};
return join ', ', map { join ' ', @$_ } @$headers;
return join ', ', @$headers;
}

sub is_finished { (shift->{state} // '') eq 'finished' }
Expand Down Expand Up @@ -95,10 +95,10 @@ sub parse {
}

# New header
if ($line =~ /^(\S[^:]*)\s*:\s*(.*)$/) { push @$headers, $1, [$2] }
if ($line =~ /^(\S[^:]*)\s*:\s*(.*)$/) { push @$headers, $1, $2 }

# Multiline
elsif (@$headers && $line =~ s/^\s+//) { push @{$headers->[-1]}, $line }
elsif (@$headers && $line =~ s/^\s+//) { $headers->[-1] .= " $line" }

# Empty line
else {
Expand Down Expand Up @@ -134,8 +134,7 @@ sub to_string {
# Make sure multiline values are formatted correctly
my @headers;
for my $name (@{$self->names}) {
push @headers, "$name: " . join("\x0d\x0a ", @$_)
for @{$self->{headers}{lc $name}};
push @headers, "$name: $_" for @{$self->{headers}{lc $name}};
}

return join "\x0d\x0a", @headers;
Expand Down Expand Up @@ -228,7 +227,6 @@ Shortcut for the C<Accept-Ranges> header.
$headers = $headers->add(Foo => 'one value');
$headers = $headers->add(Foo => 'first value', 'second value');
$headers = $headers->add(Foo => ['first line', 'second line']);
Add one or more header values with one or more lines.
Expand Down Expand Up @@ -374,7 +372,6 @@ Parse headers from a hash reference, an empty hash removes all headers.
my $value = $headers->header('Foo');
$headers = $headers->header(Foo => 'one value');
$headers = $headers->header(Foo => 'first value', 'second value');
$headers = $headers->header(Foo => ['first line', 'second line']);
Get or replace the current header values.
Expand Down Expand Up @@ -566,8 +563,8 @@ Shortcut for the C<TE> header.
my $single = $headers->to_hash;
my $multi = $headers->to_hash(1);
Turn headers into hash reference, nested array references to represent
multiple headers with the same name are disabled by default.
Turn headers into hash reference, array references to represent multiple
headers with the same name are disabled by default.
say $headers->to_hash->{DNT};
Expand Down
2 changes: 1 addition & 1 deletion lib/Mojo/Server/PSGI.pm
Expand Up @@ -26,7 +26,7 @@ sub run {
my @headers;
my $hash = $headers->to_hash(1);
for my $name (keys %$hash) {
push @headers, map { $name => $_ } map {@$_} @{$hash->{$name}};
push @headers, map { $name => $_ } @{$hash->{$name}};
}

# PSGI response
Expand Down
44 changes: 16 additions & 28 deletions t/mojo/headers.t
Expand Up @@ -21,9 +21,9 @@ is $hash->{Connection}, 'close', 'right value';
is $hash->{Expect}, 'continue-100', 'right value';
is $hash->{'Content-Type'}, 'text/html', 'right value';
$hash = $headers->to_hash(1);
is_deeply $hash->{Connection}, [['close']], 'right structure';
is_deeply $hash->{Expect}, [['continue-100']], 'right structure';
is_deeply $hash->{'Content-Type'}, [['text/html']], 'right structure';
is_deeply $hash->{Connection}, ['close'], 'right structure';
is_deeply $hash->{Expect}, ['continue-100'], 'right structure';
is_deeply $hash->{'Content-Type'}, ['text/html'], 'right structure';
is_deeply [sort @{$headers->names}], [qw(Connection Content-Type Expect)],
'right structure';
$headers->expires('Thu, 01 Dec 1994 16:00:00 GMT');
Expand Down Expand Up @@ -116,19 +116,6 @@ is $clone->expect, 'nothing', 'right value';
$clone = Mojo::Headers->new->add(Foo => [qw(bar baz)])->clone;
is_deeply $clone->to_hash(1)->{Foo}, [[qw(bar baz)]], 'right structure';

# Multiline values
$headers = Mojo::Headers->new;
$headers->header('X-Test', [23, 24], 'single line', [25, 26]);
is $headers->to_string,
"X-Test: 23\x0d\x0a 24\x0d\x0a"
. "X-Test: single line\x0d\x0a"
. "X-Test: 25\x0d\x0a 26", 'right format';
is_deeply $headers->to_hash(1),
{'X-Test' => [[23, 24], ['single line'], [25, 26]]}, 'right structure';
is_deeply $headers->to_hash, {'X-Test' => '23 24, single line, 25 26'},
'right structure';
is $headers->header('X-Test'), "23 24, single line, 25 26", 'right format';

# Parse headers
$headers = Mojo::Headers->new;
isa_ok $headers->parse(<<'EOF'), 'Mojo::Headers', 'right return value';
Expand Down Expand Up @@ -160,9 +147,9 @@ Foo: first again
EOF
ok $headers->is_finished, 'parser is finished';
$hash = {
'Content-Type' => [['text/plain']],
Foo => [['first', 'second', 'third'], ['first again', 'second ":again"']],
'Foo Bar' => [['baz']]
'Content-Type' => ['text/plain'],
Foo => ['first second third', 'first again second ":again"'],
'Foo Bar' => ['baz']
};
is_deeply $headers->to_hash(1), $hash, 'right structure';
is $headers->header('Foo'), 'first second third, first again second ":again"',
Expand Down Expand Up @@ -199,23 +186,24 @@ $headers->append(Vary => 'Accept-Encoding');
is $headers->vary, 'Accept, Accept-Encoding', 'right value';
$headers = Mojo::Headers->new;
$headers->add(Vary => 'Accept', 'Accept-Encoding');
is_deeply $headers->to_hash(1), {Vary => [['Accept'], ['Accept-Encoding']]},
is_deeply $headers->to_hash(1), {Vary => ['Accept', 'Accept-Encoding']},
'right structure';
$headers->append(Vary => 'Accept-Language');
is_deeply $headers->to_hash(1),
{Vary => [['Accept, Accept-Encoding, Accept-Language']]}, 'right structure';
{Vary => ['Accept, Accept-Encoding, Accept-Language']}, 'right structure';

# Multiline
# Multiple headers with the same name
$headers = Mojo::Headers->new;
$headers->from_hash(
{'X-Test' => [[23, 24], ['single line'], [25, 26]], 'X-Test2' => 'foo'});
$headers->from_hash({'X-Test' => [23, 24], 'X-Test2' => 'foo'});
$hash = $headers->to_hash;
is $hash->{'X-Test'}, '23 24, single line, 25 26', 'right value';
is $hash->{'X-Test2'}, 'foo', 'right value';
is $hash->{'X-Test'}, '23, 24', 'right value';
is $hash->{'X-Test2'}, 'foo', 'right value';
$hash = $headers->to_hash(1);
is_deeply $hash->{'X-Test'}, [[23, 24], ['single line'], [25, 26]],
is_deeply $hash->{'X-Test'}, [23, 24], 'right structure';
is_deeply $hash->{'X-Test2'}, ['foo'], 'right structure';
$headers = Mojo::Headers->new->parse($headers->to_string . "\x0d\x0a\x0d\x0a");
is_deeply $headers->to_hash(1), {'X-Test' => [23, 24], 'X-Test2' => ['foo']},
'right structure';
is_deeply $hash->{'X-Test2'}, [['foo']], 'right structure';

# Headers in chunks
$headers = Mojo::Headers->new;
Expand Down

0 comments on commit 79c941f

Please sign in to comment.