Merge "Log X-Google fields on failed HTTP requests."

This commit is contained in:
Alex Dale
2020-08-10 20:34:12 +00:00
committed by Android (Google) Code Review
3 changed files with 153 additions and 1 deletions

View File

@@ -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<std::string, std::string>* 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;

View File

@@ -5,7 +5,9 @@
#ifndef CDM_TEST_URL_REQUEST_H_
#define CDM_TEST_URL_REQUEST_H_
#include <map>
#include <string>
#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<std::string, std::string>* header_fields);
private:
bool PostRequestWithPath(const std::string& path, const std::string& data);

View File

@@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#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"
"<HTML><HEAD><meta http-equiv=\"content-type\" "
"content=\"text/html;charset=utf-8\">\r\n"
"<TITLE>301 Moved</TITLE></HEAD><BODY>\r\n"
"<H1>301 Moved</H1>\r\n"
"The document has moved\r\n"
"<A HREF=\"https://www.google.com/\">here</A>.\r\n"
"</BODY></HTML>\r\n\r\n";
// A map containing all the Google debug header fields found in
// |kSampleResponse|.
const std::map<std::string, std::string> 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"
"<HTML><HEAD><meta http-equiv=\"content-type\" "
"content=\"text/html;charset=utf-8\">\r\n"
"<TITLE>301 Moved</TITLE></HEAD><BODY>\r\n"
"<H1>301 Moved</H1>\r\n"
"The document has moved\r\n"
"<A HREF=\"https://www.google.com/\">here</A>.\r\n"
"</BODY></HTML>\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<std::string, std::string> 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