diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index 5860578f9..48d344532 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -2993,7 +2993,7 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRequest* req, const S track->media_->pt_ = remote_payload.payload_type_; vector 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_; diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 7b37af5aa..7cfece08d 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -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. diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index 844a73ec0..d79a4964d 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -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]; diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index 591f1fde1..538a9c3c0 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -36,6 +36,7 @@ using namespace std; #include #include #include +#include #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::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 diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index 37848a560..efb084a17 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -518,6 +518,7 @@ private: std::string query; std::string username_; std::string password_; + std::map 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) diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index bfd3b5291..8d52bad44 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -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())); + } + +}