Skip to content
This repository has been archived by the owner on Jun 23, 2022. It is now read-only.

fix(http): add uri decoder for http_server #357

Merged
merged 27 commits into from
Dec 13, 2019
Merged
15 changes: 15 additions & 0 deletions src/dist/http/http_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "root_http_service.h"
#include "pprof_http_service.h"
#include "perf_counter_http_service.h"
#include "uri_decoder.h"

namespace dsn {

Expand Down Expand Up @@ -98,6 +99,13 @@ void http_server::add_service(http_service *service)
unresolved_path.resize(data_length + 1);
strncpy(&unresolved_path[0], ret.full_url.data() + u.field_data[UF_PATH].off, data_length);
unresolved_path[data_length] = '\0';

// decode resolved path
auto decoded_unresolved_path = uri::decode(unresolved_path);
if (!decoded_unresolved_path.is_ok()) {
return decoded_unresolved_path.get_error();
}
unresolved_path = decoded_unresolved_path.get_value();
}

std::string unresolved_query;
Expand All @@ -106,6 +114,13 @@ void http_server::add_service(http_service *service)
unresolved_query.resize(data_length);
strncpy(
&unresolved_query[0], ret.full_url.data() + u.field_data[UF_QUERY].off, data_length);

// decode resolved query
auto decoded_unresolved_query = uri::decode(unresolved_query);
if (!decoded_unresolved_query.is_ok()) {
return decoded_unresolved_query.get_error();
}
unresolved_query = decoded_unresolved_query.get_value();
}

std::vector<std::string> args;
Expand Down
78 changes: 78 additions & 0 deletions src/dist/http/test/uri_decoder_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) 2018, Xiaomi, Inc. All rights reserved.
// This source code is licensed under the Apache License Version 2.0, which
// can be found in the LICENSE file in the root directory of this source tree.

#include <gtest/gtest.h>
#include <dsn/utility/error_code.h>
#include <dist/http/uri_decoder.h>

namespace dsn {
namespace uri {

class uri_decoder_test : public testing::Test
{
};

TEST_F(uri_decoder_test, decode)
{
/// Extract from https://github.com/cpp-netlib/uri/blob/master/test/uri_encoding_test.cpp
struct test_case
{
std::string to_decode_uri;
error_code err;
std::string decoded_uri;
std::string description;
} tests[] = {
{"http%3A%2F%2F127.0.0.1%3A34101%2FperfCounter%3Fname%3Dcollector*app%23_all_",
ERR_OK,
"http://127.0.0.1:34101/perfCounter?name=collector*app#_all_",
"ERR_OK"},
{"%EB%B2%95%EC%A0%95%EB%8F%99", ERR_OK, "\xEB\xB2\x95\xEC\xA0\x95\xEB\x8F\x99", "ERR_OK"},
{"%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D",
ERR_OK,
"!#$&'()*+,/:;=?@[]",
"ERR_OK"},
{"%",
ERR_INVALID_PARAMETERS,
"",
"ERR_INVALID_PARAMETERS: Encountered partial escape sequence at end of string"},
{"%2",
ERR_INVALID_PARAMETERS,
"",
"ERR_INVALID_PARAMETERS: Encountered partial escape sequence at end of string"},
{"%%%",
ERR_INVALID_PARAMETERS,
"",
"ERR_INVALID_PARAMETERS: The characters %% do not form a hex value. "
"Please escape it or pass a valid hex value"},
{"%2%",
ERR_INVALID_PARAMETERS,
"",
"ERR_INVALID_PARAMETERS: The characters 2% do not form a hex value. "
"Please escape it or pass a valid hex value"},
{"%G0",
ERR_INVALID_PARAMETERS,
"",
"ERR_INVALID_PARAMETERS: The characters G0 do not form a hex value. "
"Please escape it or pass a valid hex value"},
{"%0G",
ERR_INVALID_PARAMETERS,
"",
"ERR_INVALID_PARAMETERS: The characters 0G do not form a hex value. "
"Please escape it or pass a valid hex value"},
{"%20", ERR_OK, "\x20", "ERR_OK"},
{"%80", ERR_OK, "\x80", "ERR_OK"}};

for (auto test : tests) {
auto decode_res = decode(test.to_decode_uri);

ASSERT_EQ(decode_res.get_error().code(), test.err);
if (ERR_OK == test.err) {
ASSERT_EQ(decode_res.get_value(), test.decoded_uri);
}
ASSERT_EQ(decode_res.get_error().description(), test.description);
}
}

} // namespace dsn
} // namespace uri
69 changes: 69 additions & 0 deletions src/dist/http/uri_decoder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) 2017-present, Xiaomi, Inc. All rights reserved.
// This source code is licensed under the Apache License Version 2.0, which
// can be found in the LICENSE file in the root directory of this source tree.

#include <fmt/format.h>
#include "uri_decoder.h"

namespace dsn {
namespace uri {

error_with<char> from_hex(const char c)
{
switch (c) {
case '0' ... '9':
return c - '0';
case 'a' ... 'f':
return c - 'a' + 10;
case 'A' ... 'F':
return c - 'A' + 10;
default:
return error_s::make(ERR_INVALID_PARAMETERS);
}
}

error_with<char> decode_char(const string_view &hex)
{
assert(2 == hex.size());

auto high = from_hex(hex[0]);
levy5307 marked this conversation as resolved.
Show resolved Hide resolved
auto low = from_hex(hex[1]);
if (high.is_ok() && low.is_ok()) {
return (high.get_value() << 4) | low.get_value();
}

return error_s::make(ERR_INVALID_PARAMETERS);
}

error_with<std::string> decode(const string_view &encoded_uri)
{
std::string decoded_uri;
for (size_t i = 0; i < encoded_uri.size(); ++i) {
// '%' is followed by 2 hex chars
if ('%' == encoded_uri[i]) {
if (i + 2 >= encoded_uri.size()) {
return error_s::make(ERR_INVALID_PARAMETERS,
"Encountered partial escape sequence at end of string");
}

const string_view encoded_char(encoded_uri.data() + i + 1, 2);
auto decoded_char = decode_char(encoded_char);
if (!decoded_char.is_ok()) {
return error_s::make(
ERR_INVALID_PARAMETERS,
fmt::format("The characters {} do not "
"form a hex value. Please escape it or pass a valid hex value",
encoded_char.data()));
}
decoded_uri += decoded_char.get_value();
i += 2;
} else {
decoded_uri += encoded_uri[i];
}
}

return decoded_uri;
}

} // namespace uri
} // namespace dsn
18 changes: 18 additions & 0 deletions src/dist/http/uri_decoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2017-present, Xiaomi, Inc. All rights reserved.
// This source code is licensed under the Apache License Version 2.0, which
// can be found in the LICENSE file in the root directory of this source tree.

#pragma once

#include <string>
#include <dsn/utility/errors.h>

namespace dsn {
namespace uri {

/// \brief Decodes a sequence according to the percent decoding rules.
/// \returns the decoded uri path
error_with<std::string> decode(const string_view &encoded_uri);

} // namespace uri
} // namespace dsn