diff --git a/libwvdrmengine/cdm/core/test/url_request.cpp b/libwvdrmengine/cdm/core/test/url_request.cpp index 2f89e07a..e33aed8c 100644 --- a/libwvdrmengine/cdm/core/test/url_request.cpp +++ b/libwvdrmengine/cdm/core/test/url_request.cpp @@ -21,6 +21,10 @@ const int kConnectTimeoutMs = 15000; const int kWriteTimeoutMs = 12000; const int kReadTimeoutMs = 12000; +const std::string kGoogleHeaderUpper("X-Google"); +const std::string kGoogleHeaderLower("x-google"); +const std::string kCrLf("\r\n"); + // Concatenate all chunks into one blob and returns the response with // header information. void ConcatenateChunkedResponse(const std::string http_response, @@ -48,7 +52,6 @@ void ConcatenateChunkedResponse(const std::string http_response, // chunk size\r\n // chunk data\r\n // 0\r\n - const std::string kCrLf = "\r\n"; size_t chunk_pos = http_response.find(kCrLf, chunk_size_pos); modified_response->assign(http_response, 0, chunk_size_pos); @@ -131,6 +134,45 @@ int UrlRequest::GetStatusCode(const std::string& response) { return status_code; } +// static +bool UrlRequest::GetDebugHeaderFields( + const std::string& response, std::map* fields) { + if (fields == nullptr) return false; + fields->clear(); + + const auto find_next = [&](size_t pos) -> size_t { + // Some of Google's HTTPS return header fields in lower case, + // this lambda will check for both and return the position that + // that is next or npos if none are found. + const size_t lower_pos = response.find(kGoogleHeaderLower, pos + 1); + const size_t upper_pos = response.find(kGoogleHeaderUpper, pos + 1); + if (lower_pos == std::string::npos) return upper_pos; + if (upper_pos == std::string::npos || lower_pos < upper_pos) + return lower_pos; + return upper_pos; + }; + + // Search is relatively simple, and may pick up additional matches within + // the body of the request. This is not anticiapted for the limited use + // cases of parsing provisioning/license/renewal responses. + for (size_t key_pos = find_next(0); key_pos != std::string::npos; + key_pos = find_next(key_pos)) { + const size_t end_key_pos = response.find(":", key_pos); + const size_t end_value_pos = response.find(kCrLf, key_pos); + // Skip if the colon cannot be found. Technically possible to find + // "X-Google" inside the value of a nother header field. + if (end_key_pos == std::string::npos || end_value_pos == std::string::npos) + continue; + const size_t value_pos = end_key_pos + 2; + if (end_value_pos < value_pos) continue; + const std::string key = response.substr(key_pos, end_key_pos - key_pos); + const std::string value = + response.substr(value_pos, end_value_pos - value_pos); + fields->insert({key, value}); + } + return !fields->empty(); +} + bool UrlRequest::PostRequestWithPath(const std::string& path, const std::string& data) { std::string request; diff --git a/libwvdrmengine/cdm/core/test/url_request.h b/libwvdrmengine/cdm/core/test/url_request.h index 4289598e..550894f1 100644 --- a/libwvdrmengine/cdm/core/test/url_request.h +++ b/libwvdrmengine/cdm/core/test/url_request.h @@ -5,7 +5,9 @@ #ifndef CDM_TEST_URL_REQUEST_H_ #define CDM_TEST_URL_REQUEST_H_ +#include #include + #include "disallow_copy_and_assign.h" #include "http_socket.h" @@ -27,6 +29,10 @@ class UrlRequest { bool GetResponse(std::string* message); static int GetStatusCode(const std::string& response); + static bool GetDebugHeaderFields( + const std::string& response, + std::map* header_fields); + private: bool PostRequestWithPath(const std::string& path, const std::string& data); diff --git a/libwvdrmengine/cdm/core/test/url_request_unittest.cpp b/libwvdrmengine/cdm/core/test/url_request_unittest.cpp new file mode 100644 index 00000000..18261a79 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/url_request_unittest.cpp @@ -0,0 +1,104 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "url_request.h" + +#include +#include + +#include "test_base.h" + +namespace wvcdm { + +namespace { + +// A sample HTTP response which contains an HTTP header with several +// standard and non-standard header fields. The fields of interests +// those beginning with "X-Google-" or "x-google-". +const std::string kSampleResponse = + "HTTP/2 301\r\n" + "Location: https://www.google.com/\r\b" + "Content-Type: text/html; charset=UTF-8\r\n" + "X-Google-GFE-Backend-Request-Info: eid=7js14zjC48OpziR0VCF02a0\r\n" + "x-google-signals: FRAMEWORK=TEST\r\n" + "X-Google-Binary-Version: 322287520\r\n" + "X-Google-Request-Cost: 2.50\r\n" + "Date: Thu, 23 Jul 2020 22:52:55 GMT\r\n" + "Expires: Sat, 22 Aug 2020 22:52:55 GMT\r\n" + "Cache-Control: public, max-age=2592000\r\n" + "x-google-data-version: 322866295\r\n" + "Server: gws\r\n" + "X-Google-Debug-Label: /srv/alt/job/service.number/1380\r\n" + "Content-Length: 220\r\n" + "X-XSS-Protection: 0\r\n" + "X-Frame-Options: SAMEORIGIN\r\n" + "X-Google-Service: web\r\n" + "X-Google-DOS-Service-Trace: main:home\r\n" + "Alt-Svc: h3-29=\":443\"; ma=2592000,h3-27=\":443\"; " + "ma=2592000,h3-25=\":443\"; ma=2592000,h3-T050=\":443\"; " + "ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; " + "ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; " + "v=\"46,43\"\r\n" + "\r\n" + "\r\n" + "301 Moved\r\n" + "

301 Moved

\r\n" + "The document has moved\r\n" + "here.\r\n" + "\r\n\r\n"; + +// A map containing all the Google debug header fields found in +// |kSampleResponse|. +const std::map kSampleFields = { + {"X-Google-GFE-Backend-Request-Info", "eid=7js14zjC48OpziR0VCF02a0"}, + {"x-google-signals", "FRAMEWORK=TEST"}, + {"X-Google-Binary-Version", "322287520"}, + {"X-Google-Request-Cost", "2.50"}, + {"x-google-data-version", "322866295"}, + {"X-Google-Debug-Label", "/srv/alt/job/service.number/1380"}, + {"X-Google-Service", "web"}, + {"X-Google-DOS-Service-Trace", "main:home"}}; + +// A sample HTTP response which does not contain any special header fields. +const std::string kFieldlessResponse = + "HTTP/2 301\r\n" + "Location: https://www.google.com/\r\b" + "Content-Type: text/html; charset=UTF-8\r\n" + "Date: Thu, 23 Jul 2020 22:52:55 GMT\r\n" + "Content-Length: 220\r\n" + "\r\n" + "\r\n" + "301 Moved\r\n" + "

301 Moved

\r\n" + "The document has moved\r\n" + "here.\r\n" + "\r\n\r\n"; + +} // namespace + +class UrlRequestTest : public WvCdmTestBase { + protected: + void SetUp() override { WvCdmTestBase::SetUp(); } + void TearDown() override {} +}; + +TEST_F(UrlRequestTest, ParseDebugHeader) { + // Output map cannot be null. + EXPECT_FALSE(UrlRequest::GetDebugHeaderFields(kSampleResponse, nullptr)); + std::map fields; + // Expect false if no debug information can be determined. + EXPECT_FALSE(UrlRequest::GetDebugHeaderFields(kFieldlessResponse, &fields)); + // Expect success. + EXPECT_TRUE(UrlRequest::GetDebugHeaderFields(kSampleResponse, &fields)); + EXPECT_EQ(kSampleFields.size(), fields.size()); + for (auto it = kSampleFields.cbegin(); it != kSampleFields.cend(); ++it) { + auto field = fields.find(it->first); + EXPECT_NE(fields.end(), field); + EXPECT_EQ(it->second, field->second) << "Key: \"" << it->first << "\""; + } +} + +} // namespace wvcdm