(This is a merge of http://go/wvgerrit/134316.) This patch fixes code that would trigger -Wshorten-64-to-32 by implicitly narrowing a variable from 64 to 32 bits. Most of the time, it does this by making the implicit conversion explicit. Occasionally, where it makes sense, it does this by expanding the code to operate on a 64-bit value. This patch removes LicenseKeysTest::NumContentKeys(), which no one was using, as all the tests access content_key_count_ directly. Bug: 194971260 Test: x86-64 Change-Id: Iae7685c10b9db989253b349cab693728b438798d
225 lines
7.4 KiB
C++
225 lines
7.4 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine License
|
|
// Agreement.
|
|
|
|
#include "url_request.h"
|
|
|
|
#include <errno.h>
|
|
#include <sstream>
|
|
|
|
#include "http_socket.h"
|
|
#include "log.h"
|
|
#include "string_conversions.h"
|
|
|
|
namespace wvcdm {
|
|
|
|
namespace {
|
|
|
|
const int kMaxConnectAttempts = 3;
|
|
const int kReadBufferSize = 1024;
|
|
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,
|
|
std::string* modified_response) {
|
|
if (http_response.empty()) return;
|
|
|
|
modified_response->clear();
|
|
const std::string kChunkedTag = "Transfer-Encoding: chunked\r\n\r\n";
|
|
size_t chunked_tag_pos = http_response.find(kChunkedTag);
|
|
if (std::string::npos != chunked_tag_pos) {
|
|
// processes chunked encoding
|
|
size_t chunk_size = 0;
|
|
size_t chunk_size_pos = chunked_tag_pos + kChunkedTag.size();
|
|
sscanf(&http_response[chunk_size_pos], "%zx", &chunk_size);
|
|
if (chunk_size > http_response.size()) {
|
|
// precaution, in case we misread chunk size
|
|
LOGE("Invalid chunk size %zu", chunk_size);
|
|
return;
|
|
}
|
|
|
|
// Search for chunks in the following format:
|
|
// header
|
|
// chunk size\r\n <-- chunk_size_pos @ beginning of chunk size
|
|
// chunk data\r\n <-- chunk_pos @ beginning of chunk data
|
|
// chunk size\r\n
|
|
// chunk data\r\n
|
|
// 0\r\n
|
|
size_t chunk_pos = http_response.find(kCrLf, chunk_size_pos);
|
|
modified_response->assign(http_response, 0, chunk_size_pos);
|
|
|
|
while ((chunk_size > 0) && (std::string::npos != chunk_pos)) {
|
|
chunk_pos += kCrLf.size();
|
|
modified_response->append(http_response, chunk_pos, chunk_size);
|
|
|
|
// Search for next chunk
|
|
chunk_size_pos = chunk_pos + chunk_size + kCrLf.size();
|
|
sscanf(&http_response[chunk_size_pos], "%zx", &chunk_size);
|
|
if (chunk_size > http_response.size()) {
|
|
// precaution, in case we misread chunk size
|
|
LOGE("Invalid chunk size %zu", chunk_size);
|
|
break;
|
|
}
|
|
chunk_pos = http_response.find(kCrLf, chunk_size_pos);
|
|
}
|
|
} else {
|
|
// Response is not chunked encoded
|
|
modified_response->assign(http_response);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
UrlRequest::UrlRequest(const std::string& url)
|
|
: is_connected_(false), socket_(url) {
|
|
Reconnect();
|
|
}
|
|
|
|
UrlRequest::~UrlRequest() {}
|
|
|
|
void UrlRequest::Reconnect() {
|
|
for (uint32_t i = 0; i < kMaxConnectAttempts && !is_connected_; ++i) {
|
|
socket_.CloseSocket();
|
|
if (socket_.ConnectAndLogErrors(kConnectTimeoutMs)) {
|
|
is_connected_ = true;
|
|
} else {
|
|
LOGE("Failed to connect: url = %s, port = %d, attempt = %u",
|
|
socket_.domain_name().c_str(), socket_.port(), i);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UrlRequest::GetResponse(std::string* message) {
|
|
std::string response;
|
|
|
|
// Keep reading until end of stream (0 bytes read) or timeout. Partial
|
|
// buffers worth of data can and do happen, especially with OpenSSL in
|
|
// non-blocking mode.
|
|
while (true) {
|
|
char read_buffer[kReadBufferSize];
|
|
const int bytes = socket_.ReadAndLogErrors(read_buffer, sizeof(read_buffer),
|
|
kReadTimeoutMs);
|
|
if (bytes > 0) {
|
|
response.append(read_buffer, bytes);
|
|
} else if (bytes < 0) {
|
|
LOGE("Read error, errno = %d", errno);
|
|
return false;
|
|
} else {
|
|
// end of stream.
|
|
break;
|
|
}
|
|
}
|
|
|
|
ConcatenateChunkedResponse(response, message);
|
|
LOGV("HTTP response from %s://%s:%d%s: (%zu): %s", socket_.scheme().c_str(),
|
|
socket_.domain_name().c_str(), socket_.port(),
|
|
socket_.resource_path().c_str(), message->size(), message->c_str());
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
int UrlRequest::GetStatusCode(const std::string& response) {
|
|
const std::string kHttpVersion("HTTP/1.1 ");
|
|
|
|
int status_code = -1;
|
|
size_t pos = response.find(kHttpVersion);
|
|
if (pos != std::string::npos) {
|
|
pos += kHttpVersion.size();
|
|
sscanf(response.substr(pos).c_str(), "%d", &status_code);
|
|
}
|
|
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;
|
|
|
|
request.append("POST ");
|
|
request.append(path);
|
|
request.append(" HTTP/1.1\r\n");
|
|
|
|
request.append("Host: ");
|
|
request.append(socket_.domain_name());
|
|
request.append("\r\n");
|
|
|
|
request.append("Connection: close\r\n");
|
|
request.append("User-Agent: Widevine CDM v1.0\r\n");
|
|
|
|
// buffer to store length of data as a string
|
|
char data_size_buffer[32] = {0};
|
|
snprintf(data_size_buffer, sizeof(data_size_buffer), "%zu", data.size());
|
|
|
|
request.append("Content-Length: ");
|
|
request.append(data_size_buffer); // appends size of data
|
|
request.append("\r\n");
|
|
|
|
request.append("\r\n"); // empty line to terminate headers
|
|
|
|
request.append(data);
|
|
|
|
const int ret = socket_.WriteAndLogErrors(
|
|
request.c_str(), static_cast<int>(request.size()), kWriteTimeoutMs);
|
|
LOGV("HTTP request: (%zu): %s", request.size(), b2a_hex(request).c_str());
|
|
return ret != -1;
|
|
}
|
|
|
|
bool UrlRequest::PostRequest(const std::string& data) {
|
|
return PostRequestWithPath(socket_.resource_path(), data);
|
|
}
|
|
|
|
bool UrlRequest::PostCertRequestInQueryString(const std::string& data) {
|
|
std::string path = socket_.resource_path();
|
|
path.append((path.find('?') == std::string::npos) ? "?" : "&");
|
|
path.append("signedRequest=");
|
|
path.append(data);
|
|
return PostRequestWithPath(path, "");
|
|
}
|
|
|
|
} // namespace wvcdm
|