Mojolicious:路径占位符的URL解码不一致

时间:2018-02-16 20:08:43

标签: perl mojolicious

我想访问资源标识符包含特殊字符的Restful API中的资源。我因此url_encoding标识符,但我在Mojolicious中为占位符获得了不一致的自动解码行为。

以下是测试一个简单资源标识符的测试脚本,一个包含空格,一个包含加号,另一个包含斜杠。在发送我的请求之前,我正在编写其中的每一个,但是第二个因为不同的原因而失败。

#!/usr/bin/env perl

use strict;
use warnings;
use v5.10;

use Data::Dump qw(pp);
use Mojolicious::Lite;
use Mojo::UserAgent;
use Test::Exception;
use Test::More;
use URL::Encode qw(url_encode url_decode);
use WWW::Mechanize;

# Case 1: Access a resource using Standard Placeholders
get '/my/app/standard_placeholder/:id' => sub {
    my $c = shift;

    my $id_raw     = $c->stash('id');
    my $id_decoded = url_decode($id_raw);

    $c->render( json => { raw => $id_raw, decoded => $id_decoded } );
};

# Shut the server down.
get '/my/api/shutdown' => sub {
    exit 0;
};

# Fork for Client and Server
if ( my $pid = fork ) {
    note "Waiting for the server to start";
    sleep 2;

    run_test();

    # Clean up server process and we’re done
    waitpid( $pid, 0 );

} else {
    local @ARGV = qw(daemon);

    app->log( Mojo::Log->new( path => "$0.log", level => 'debug' ) );
    app->start;
}

exit 0;

### Client

sub run_test {
    plan tests => 2;

    my $ua         = Mojo::UserAgent->new();
    my $server_url = 'http://127.0.0.1:3000';

    # Standard Placeholders:  ([^/.]+)
    # Relaxed Placeholders:   ([^/]+)
    # Wildcard Placeholders:  (.+)

    subtest 'Standard Placeholders testing url_encoding of route identifiers' => sub {
        my @ids = (
            "foobar",    #
            "a space",
            "a+plus",
            "a/slash",
        );

        plan tests => 3 * @ids;

        for my $id (@ids) {
            my $id_encoded = url_encode($id);
            my $tx         = $ua->get("$server_url/my/app/standard_placeholder/$id_encoded");

        SKIP: {
                is( $tx->res->code, 200, "Fetch Resource at " . pp($id) )
                    or skip "Error in response", 2;

                is( $tx->res->json->{raw},     $id_encoded, "json->{raw} eq " . pp($id_encoded) );
                is( $tx->res->json->{decoded}, $id,         "json->{decoded} eq " . pp($id) );
            }
        }
    };

    subtest 'Shutdown the server' => sub {
        plan tests => 2;

        dies_ok {
            my $mech = WWW::Mechanize->new( timeout => 3 );
            $mech->get("$server_url/my/api/shutdown");
        }
        'shutdown occurred';

        like $@, qr{Error GETing .*?shutdown: Server closed connection without sending any data back},
            'detected closed connection';
    };
}

1;

输出:

mhall@dev19:~$ ./mojo_placeholders.pl
# Waiting for the server to start
Server available at http://127.0.0.1:3000
1..2
# Subtest: Standard Placeholders testing url_encoding of route identifiers
    1..12
    ok 1 - Fetch Resource at "foobar"
    ok 2 - json->{raw} eq "foobar"
    ok 3 - json->{decoded} eq "foobar"
    ok 4 - Fetch Resource at "a space"
    ok 5 - json->{raw} eq "a+space"
    ok 6 - json->{decoded} eq "a space"
    ok 7 - Fetch Resource at "a+plus"
    not ok 8 - json->{raw} eq "a%2Bplus"
    #   Failed test 'json->{raw} eq "a%2Bplus"'
    #   at ./mojo_placeholders.pl line 89.
    #          got: 'a+plus'
    #     expected: 'a%2Bplus'
    not ok 9 - json->{decoded} eq "a+plus"
    #   Failed test 'json->{decoded} eq "a+plus"'
    #   at ./mojo_placeholders.pl line 90.
    #          got: 'a plus'
    #     expected: 'a+plus'
    not ok 10 - Fetch Resource at "a/slash"
    #   Failed test 'Fetch Resource at "a/slash"'
    #   at ./mojo_placeholders.pl line 85.
    #          got: '404'
    #     expected: '200'
    ok 11 # skip Error in response
    ok 12 # skip Error in response
    # Looks like you failed 3 tests of 12.
not ok 1 - Standard Placeholders testing url_encoding of route identifiers
#   Failed test 'Standard Placeholders testing url_encoding of route identifiers'
#   at ./mojo_placeholders.pl line 93.
# Subtest: Shutdown the server
    1..2
    ok 1 - shutdown occurred
    ok 2 - detected closed connection
ok 2 - Shutdown the server
# Looks like you failed 1 test of 2.

有关如何处理此事的任何建议?

1 个答案:

答案 0 :(得分:2)

所以这里有几件事情。首先,/ vs %2F通常是一件非常令人担忧的事情。您可以在https://groups.google.com/forum/#!topic/python-web-sig/IAPhwezOJ7I阅读一个有趣的主题,并注意到Mojolicious本身就路径遍历的潜力https://www.cvedetails.com/cve/CVE-2011-1589/发出了CVE。也就是说,如果您使用通配符占位符,您仍然可以在占位符中恢复/。最后你的测试(虽然我确定功能)不使用Mojolicious测试设备,这将避免你的大部分架构,为更多的测试留出空间。这是我的快速清理。

#!/usr/bin/env perl
use Mojo::Base -strict;

use Mojolicious::Lite;
use Mojo::Util qw(url_escape url_unescape);

use Test::More;
use Test::Mojo;

my $cb = sub {
    my $c = shift;

    my $id_raw     = $c->stash('id');
    my $id_decoded = url_unescape $id_raw;

    $c->render( json => { raw => $id_raw, decoded => $id_decoded } );
};
get '/standard/:id' => $cb;
get '/relaxed/#id'  => $cb;
get '/wildcard/*id' => $cb;


# Standard Placeholders:  ([^/.]+)
# Relaxed Placeholders:   ([^/]+)
# Wildcard Placeholders:  (.+)

my $t = Test::Mojo->new;

my @ids = (
    "foobar",
    "a space",
    "a+plus",
    "a/slash",
);

plan tests => 3;

for my $type (qw/standard relaxed wildcard/) {
  subtest "Type $type" => sub {
    plan tests => 4 * @ids;

    for my $id (@ids) {
      my $id_encoded = url_escape $id;
      $t->get_ok("/$type/$id_encoded")
        ->status_is(200)
        ->json_is('/raw',     $id_encoded, "$type raw test for $id_encoded")
        ->json_is('/decoded', $id,         "$type decoded test for $id");
    }
  };
}

我没有更正你的测试,只是将它们移植到书面上(并扩展了所有三种占位符类型)。