1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

support query parsing and escape

This commit is contained in:
莫战 2020-12-01 16:27:37 +08:00 committed by winlin
parent dc7124cd05
commit b38f30c3ee
6 changed files with 468 additions and 7 deletions

View file

@ -2993,7 +2993,7 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRequest* req, const S
track->media_->pt_ = remote_payload.payload_type_;
vector<SrsMediaPayloadType> red_pts = remote_media_desc.find_media_with_encoding_name("red");
if (!red_pts.empty() && !track->red_) {
if (!red_pts.empty() && track->red_) {
SrsMediaPayloadType red_pt = red_pts.at(0);
track->red_->pt_of_publisher_ = track->red_->pt_;

View file

@ -287,6 +287,7 @@
#define ERROR_INOTIFY_CREATE 3092
#define ERROR_INOTIFY_OPENFD 3093
#define ERROR_INOTIFY_WATCH 3094
#define ERROR_HTTP_URL_UNESCAPE 3095
///////////////////////////////////////////////////////
// HTTP/StreamCaster protocol error.

View file

@ -1056,9 +1056,6 @@ srs_error_t srs_av_base64_encode(std::string plaintext, std::string& cipher)
uint8_t* p = (uint8_t*)plaintext.c_str();
while(si < n) {
// Convert 3x 8bit source bytes into 4 bytes
uint32_t v1 = uint32_t(p[si+0]) << 16;
uint32_t v2 = uint32_t(p[si+1]) << 8;
uint32_t v3 = uint32_t(p[si+2]);
val = (uint32_t(p[si + 0]) << 16) | (uint32_t(p[si + 1])<< 8) | uint32_t(p[si + 2]);
cipher += encoder[val>>18&0x3f];

View file

@ -36,6 +36,7 @@ using namespace std;
#include <srs_kernel_file.hpp>
#include <srs_protocol_json.hpp>
#include <srs_core_autofree.hpp>
#include <srs_protocol_utility.hpp>
#define SRS_HTTP_DEFAULT_PAGE "index.html"
@ -898,8 +899,6 @@ SrsHttpUri::~SrsHttpUri()
srs_error_t SrsHttpUri::initialize(string _url)
{
srs_error_t err = srs_success;
schema = host = path = query = "";
url = _url;
@ -944,7 +943,7 @@ srs_error_t SrsHttpUri::initialize(string _url)
username_ = username_.substr(0, pos);
}
return err;
return parse_query();
}
void SrsHttpUri::set_schema(std::string v)
@ -988,6 +987,15 @@ string SrsHttpUri::get_query()
return query;
}
string SrsHttpUri::get_query_by_key(std::string key)
{
map<string, string>::iterator it = query_values_.find(key);
if(it == query_values_.end()) {
return "";
}
return it->second;
}
std::string SrsHttpUri::username()
{
return username_;
@ -1013,6 +1021,331 @@ string SrsHttpUri::get_uri_field(string uri, void* php_u, int ifield)
return uri.substr(offset, len);
}
srs_error_t SrsHttpUri::parse_query()
{
srs_error_t err = srs_success;
if(query.empty()) {
return err;
}
size_t begin = query.find("?");
if(string::npos != begin) {
begin++;
} else {
begin = 0;
}
string query_str = query.substr(begin);
query_values_.clear();
srs_parse_query_string(query_str, query_values_);
return err;
}
// @see golang net/url/url.go
namespace {
enum EncodeMode {
encodePath,
encodePathSegment,
encodeHost,
encodeZone,
encodeUserPassword,
encodeQueryComponent,
encodeFragment,
};
bool should_escape(uint8_t c, EncodeMode mode) {
// §2.3 Unreserved characters (alphanum)
if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) {
return false;
}
if(encodeHost == mode || encodeZone == mode) {
// §3.2.2 Host allows
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
// as part of reg-name.
// We add : because we include :port as part of host.
// We add [ ] because we include [ipv6]:port as part of host.
// We add < > because they're the only characters left that
// we could possibly allow, and Parse will reject them if we
// escape them (because hosts can't use %-encoding for
// ASCII bytes).
switch(c) {
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
case ':':
case '[':
case ']':
case '<':
case '>':
case '"':
return false;
}
}
switch(c) {
case '-':
case '_':
case '.':
case '~': // §2.3 Unreserved characters (mark)
return false;
case '$':
case '&':
case '+':
case ',':
case '/':
case ':':
case ';':
case '=':
case '?':
case '@': // §2.2 Reserved characters (reserved)
// Different sections of the URL allow a few of
// the reserved characters to appear unescaped.
switch (mode) {
case encodePath: // §3.3
// The RFC allows : @ & = + $ but saves / ; , for assigning
// meaning to individual path segments. This package
// only manipulates the path as a whole, so we allow those
// last three as well. That leaves only ? to escape.
return c == '?';
case encodePathSegment: // §3.3
// The RFC allows : @ & = + $ but saves / ; , for assigning
// meaning to individual path segments.
return c == '/' || c == ';' || c == ',' || c == '?';
case encodeUserPassword: // §3.2.1
// The RFC allows ';', ':', '&', '=', '+', '$', and ',' in
// userinfo, so we must escape only '@', '/', and '?'.
// The parsing of userinfo treats ':' as special so we must escape
// that too.
return c == '@' || c == '/' || c == '?' || c == ':';
case encodeQueryComponent: // §3.4
// The RFC reserves (so we must escape) everything.
return true;
case encodeFragment: // §4.1
// The RFC text is silent but the grammar allows
// everything, so escape nothing.
return false;
default:
break;
}
}
if(mode == encodeFragment) {
// RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are
// included in reserved from RFC 2396 §2.2. The remaining sub-delims do not
// need to be escaped. To minimize potential breakage, we apply two restrictions:
// (1) we always escape sub-delims outside of the fragment, and (2) we always
// escape single quote to avoid breaking callers that had previously assumed that
// single quotes would be escaped. See issue #19917.
switch (c) {
case '!':
case '(':
case ')':
case '*':
return false;
}
}
// Everything else must be escaped.
return true;
}
bool ishex(uint8_t c) {
if( '0' <= c && c <= '9') {
return true;
} else if('a' <= c && c <= 'f') {
return true;
} else if( 'A' <= c && c <= 'F') {
return true;
}
return false;
}
uint8_t hex_to_num(uint8_t c) {
if('0' <= c && c <= '9') {
return c - '0';
} else if('a' <= c && c <= 'f') {
return c - 'a' + 10;
} else if('A' <= c && c <= 'F') {
return c - 'A' + 10;
}
return 0;
}
srs_error_t unescapse(string s, string& value, EncodeMode mode) {
srs_error_t err = srs_success;
int n = 0;
bool has_plus = false;
int i = 0;
// Count %, check that they're well-formed.
while(i < s.length()) {
switch (s.at(i)) {
case '%':
{
n++;
if((i+2) >= s.length() || !ishex(s.at(i+1)) || !ishex(s.at(i+2))) {
string msg = s.substr(i);
if(msg.length() > 3) {
msg = msg.substr(0, 3);
}
return srs_error_new(ERROR_HTTP_URL_UNESCAPE, "invalid URL escape: %s", msg.c_str());
}
// Per https://tools.ietf.org/html/rfc3986#page-21
// in the host component %-encoding can only be used
// for non-ASCII bytes.
// But https://tools.ietf.org/html/rfc6874#section-2
// introduces %25 being allowed to escape a percent sign
// in IPv6 scoped-address literals. Yay.
if(encodeHost == mode && hex_to_num(s.at(i+1)) < 8 && s.substr(i, 3) != "%25") {
return srs_error_new(ERROR_HTTP_URL_UNESCAPE, "invalid URL escap: %s", s.substr(i, 3).c_str());
}
if(encodeZone == mode) {
// RFC 6874 says basically "anything goes" for zone identifiers
// and that even non-ASCII can be redundantly escaped,
// but it seems prudent to restrict %-escaped bytes here to those
// that are valid host name bytes in their unescaped form.
// That is, you can use escaping in the zone identifier but not
// to introduce bytes you couldn't just write directly.
// But Windows puts spaces here! Yay.
uint8_t v = (hex_to_num(s.at(i+1)) << 4) | (hex_to_num(s.at(i+2)));
if("%25" != s.substr(i, 3) && ' ' != v && should_escape(v, encodeHost)) {
return srs_error_new(ERROR_HTTP_URL_UNESCAPE, "invalid URL escap: %s", s.substr(i, 3).c_str());
}
}
i += 3;
}
break;
case '+':
has_plus = encodeQueryComponent == mode;
i++;
break;
default:
if((encodeHost == mode || encodeZone == mode) && ((uint8_t)s.at(i) < 0x80) && should_escape(s.at(i), mode)) {
return srs_error_new(ERROR_HTTP_URL_UNESCAPE, "invalid character %u in host name", s.at(i));
}
i++;
break;
}
}
if(0 == n && !has_plus) {
value = s;
return err;
}
value.clear();
//value.resize(s.length() - 2*n);
for(int i = 0; i < s.length(); ++i) {
switch(s.at(i)) {
case '%':
value += (hex_to_num(s.at(i+1))<<4 | hex_to_num(s.at(i+2)));
i += 2;
break;
case '+':
if(encodeQueryComponent == mode) {
value += " ";
} else {
value += "+";
}
break;
default:
value += s.at(i);
break;
}
}
return srs_success;
}
string escape(string s, EncodeMode mode) {
int space_count = 0;
int hex_count = 0;
for(int i = 0; i < s.length(); ++i) {
uint8_t c = s.at(i);
if(should_escape(c, mode)) {
if(' ' == c && encodeQueryComponent == mode) {
space_count++;
} else {
hex_count++;
}
}
}
if(0 == space_count && 0 == hex_count) {
return s;
}
string value;
if(0 == hex_count) {
value = s;
for(int i = 0; i < s.length(); ++i) {
if(' ' == s.at(i)) {
value[i] = '+';
}
}
return value;
}
//value.resize(s.length() + 2*hex_count);
const char escape_code[] = "0123456789ABCDEF";
//int j = 0;
for(int i = 0; i < s.length(); ++i) {
uint8_t c = s.at(i);
if(' ' == c && encodeQueryComponent == mode) {
value += '+';
} else if (should_escape(c, mode)) {
value += '%';
value += escape_code[c>>4];
value += escape_code[c&15];
//j += 3;
} else {
value += s[i];
}
}
return value;
}
}
string SrsHttpUri::query_escape(std::string s)
{
return escape(s, encodeQueryComponent);
}
string SrsHttpUri::path_escape(std::string s)
{
return escape(s, encodePathSegment);
}
srs_error_t SrsHttpUri::query_unescape(std::string s, std::string& value)
{
return unescapse(s, value, encodeQueryComponent);
}
srs_error_t SrsHttpUri::path_unescape(std::string s, std::string& value)
{
return unescapse(s, value, encodePathSegment);
}
// For #if !defined(SRS_EXPORT_LIBRTMP)
#endif

View file

@ -518,6 +518,7 @@ private:
std::string query;
std::string username_;
std::string password_;
std::map<std::string, std::string> query_values_;
public:
SrsHttpUri();
virtual ~SrsHttpUri();
@ -533,12 +534,19 @@ public:
virtual int get_port();
virtual std::string get_path();
virtual std::string get_query();
virtual std::string get_query_by_key(std::string key);
virtual std::string username();
virtual std::string password();
private:
// Get the parsed url field.
// @return return empty string if not set.
virtual std::string get_uri_field(std::string uri, void* hp_u, int field);
srs_error_t parse_query();
public:
static std::string query_escape(std::string s);
static std::string path_escape(std::string s);
static srs_error_t query_unescape(std::string s, std::string& value);
static srs_error_t path_unescape(std::string s, std::string& value);
};
// For #if !defined(SRS_EXPORT_LIBRTMP)

View file

@ -1903,3 +1903,125 @@ VOID TEST(ProtocolHTTPTest, ParseUri)
EXPECT_STREQ("/0", uri.get_path().c_str());
}
}
VOID TEST(ProtocolHTTPTest, ParseHttpUri)
{
srs_error_t err = srs_success;
std::string url_str = "http://me.com";
url_str += "?xxxIdxxx=xxxxxCPrzDUzxxxxx&Action=DescribeXXX";
url_str += "&XXXUid=000140000&AppId=xxxx2rxxx&Caller=rtc&Format=JSON&QueryAppId=xxx9xrxxx";
url_str += "&Region=cn-hangzhou&RequestId=xxx6i4bmxxx74kxxx" ;
url_str += "&SignatureMethod=HMAC-SHA1&SignatureNonce=xxxk1q0t42v37ske24j329xxxx";
url_str += "&SignatureVersion=1.0&Timestamp=2020-11-02T09:10:28Z&Version=2018-01-11";
url_str += "&Signature=xxxGXBBGnoR4vHsTcUxxx+tRM";
SrsHttpUri uri;
HELPER_ASSERT_SUCCESS(uri.initialize(url_str));
EXPECT_STREQ("http", uri.get_schema().c_str());
EXPECT_EQ(80, uri.get_port());
EXPECT_STREQ("me.com", uri.get_host().c_str());
EXPECT_STREQ("", uri.get_path().c_str());
EXPECT_STREQ("xxxxxCPrzDUzxxxxx", uri.get_query_by_key("xxxIdxxx").c_str());
EXPECT_STREQ("DescribeXXX", uri.get_query_by_key("Action").c_str());
EXPECT_STREQ("000140000", uri.get_query_by_key("XXXUid").c_str());
EXPECT_STREQ("rtc", uri.get_query_by_key("Caller").c_str());
EXPECT_STREQ("xxxx2rxxx", uri.get_query_by_key("AppId").c_str());
EXPECT_STREQ("JSON", uri.get_query_by_key("Format").c_str());
EXPECT_STREQ("xxx9xrxxx", uri.get_query_by_key("QueryAppId").c_str());
EXPECT_STREQ("cn-hangzhou", uri.get_query_by_key("Region").c_str());
EXPECT_STREQ("xxx6i4bmxxx74kxxx", uri.get_query_by_key("RequestId").c_str());
EXPECT_STREQ("HMAC-SHA1", uri.get_query_by_key("SignatureMethod").c_str());
EXPECT_STREQ("xxxk1q0t42v37ske24j329xxxx", uri.get_query_by_key("SignatureNonce").c_str());
EXPECT_STREQ("1.0", uri.get_query_by_key("SignatureVersion").c_str());
EXPECT_STREQ("2020-11-02T09:10:28Z", uri.get_query_by_key("Timestamp").c_str());
EXPECT_STREQ("2018-01-11", uri.get_query_by_key("Version").c_str());
EXPECT_STREQ("xxxGXBBGnoR4vHsTcUxxx+tRM", uri.get_query_by_key("Signature").c_str());
}
struct EscapeTest {
string in;
string out;
srs_error_t err;
};
VOID TEST(ProtocolHTTPTest, QueryEscape)
{
srs_error_t err = srs_success;
//Test query unescapse
if(true) {
struct EscapeTest unescape[] = {
{"", "", srs_success},
{"abc", "abc", srs_success},
{"1%41", "1A", srs_success},
{"1%41%42%43", "1ABC", srs_success},
{"%4a", "J", srs_success},
{"%6F", "o", srs_success},
{"%"/* not enough characters after %*/, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%")},
{"%a"/* not enough characters after % */, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%a")},
{"%1" /* not enough characters after % */, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%1")},
{"123%45%6"/* not enough characters after % */, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%6")},
{"%zzzzz"/* invalid hex digits */, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%zz")},
{"a+b", "a b", srs_success},
{"a%20b", "a b", srs_success}
};
for(int i = 0; i < (sizeof(unescape) / sizeof(struct EscapeTest)); ++i) {
struct EscapeTest& d = unescape[i];
string value;
if(srs_success == d.err) {
HELPER_ASSERT_SUCCESS(SrsHttpUri::query_unescape(d.in, value));
EXPECT_STREQ(d.out.c_str(), value.c_str());
} else {
HELPER_ASSERT_FAILED(SrsHttpUri::query_unescape(d.in, value));
}
}
}
//Test Escape
if(true) {
struct EscapeTest escape[] = {
{"", "", srs_success},
{"abc", "abc", srs_success},
{"one two", "one+two", srs_success},
{"10%", "10%25", srs_success},
{" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;",
"+%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A%2F%40%24%27%28%29%2A%2C%3B", srs_success},
};
for(int i = 0; i < (sizeof(escape) / sizeof(struct EscapeTest)); ++i) {
struct EscapeTest& d = escape[i];
EXPECT_STREQ(d.out.c_str(), SrsHttpUri::query_escape(d.in).c_str());
string value;
HELPER_ASSERT_SUCCESS(SrsHttpUri::query_unescape(d.out, value));
EXPECT_STREQ(d.in.c_str(), value.c_str());
}
}
}
VOID TEST(ProtocolHTTPTest, PathEscape)
{
srs_error_t err = srs_success;
struct EscapeTest path[] = {
{"", "", srs_success},
{"abc", "abc", srs_success},
{"abc+def", "abc+def", srs_success},
{"a/b", "a%2Fb", srs_success},
{"one two", "one%20two", srs_success},
{"10%", "10%25", srs_success},
{" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;",
"%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B",
srs_success},
};
for(int i = 0; i < (sizeof(path) / sizeof(struct EscapeTest)); ++i) {
struct EscapeTest& d = path[i];
EXPECT_STREQ(d.out.c_str(), SrsHttpUri::path_escape(d.in).c_str());
string value;
HELPER_ASSERT_SUCCESS(SrsHttpUri::path_unescape(d.out, value));
EXPECT_STREQ(d.in.c_str(), value.c_str());
EXPECT_EQ(0, memcmp(d.in.c_str(), value.c_str(), d.in.length()));
}
}