Merge Changes from CDM repository

This CL merges the following changes from the Widevine repository:

Avoid CdmSession reinitialization
https://widevine-internal-review.googlesource.com/#/c/10530/

Fix timer-related unit tests.
https://widevine-internal-review.googlesource.com/#/c/10510/

Correct return statement
bug: 15590802
https://widevine-internal-review.googlesource.com/#/c/10553/

Usage reporting fixes
bug/15388863
https://widevine-internal-review.googlesource.com/#/c/10460/

Make public methods virtual
https://widevine-internal-review.googlesource.com/#/c/10500/

Fix the SetTimer contract in the CDM.
https://widevine-internal-review.googlesource.com/#/c/10493/

Move inline CDM methods, add OVERRIDE.
https://widevine-internal-review.googlesource.com/#/c/10475/

Simplify storage APIs related cleanup.
https://widevine-internal-review.googlesource.com/#/c/10473/

Duration values are not correctly reported when queried
b/15592374
https://widevine-internal-review.googlesource.com/#/c/10437/

Propagate IsKeyValid() through ContentDecryptionModule.
https://widevine-internal-review.googlesource.com/#/c/10483/

Minor clean up in config_test_env.
https://widevine-internal-review.googlesource.com/#/c/10440/

General clean up.
https://widevine-internal-review.googlesource.com/#/c/10441/

Refactor HttpSocket and simplify UrlRequest interface.
https://widevine-internal-review.googlesource.com/#/c/10410/

Install good keybox at end of unit tests
b/15385981
https://widevine-internal-review.googlesource.com/#/c/10374/

Privacy crypto fixes
b/15475012
https://widevine-internal-review.googlesource.com/#/c/10383/

Incorporate header files to resolve build issued based on customers feedback.
https://widevine-internal-review.googlesource.com/#/c/10420/

Support unprovisioning
b/12247651
https://widevine-internal-review.googlesource.com/#/c/10356/

Correct usage of Host::Allocate and Cdm::Decrypt.
https://widevine-internal-review.googlesource.com/#/c/10378/

Fix logging bug, arguments in wrong order.
https://widevine-internal-review.googlesource.com/#/c/10380/

Rename types that look like constants.
https://widevine-internal-review.googlesource.com/#/c/10379/

Fix offline test failures
b/13909635
https://widevine-internal-review.googlesource.com/#/c/10348/

Add -DUNIT_TEST to the unit test makefile for Android
https://widevine-internal-review.googlesource.com/#/c/10375/

Refactor privacy-crypto and add dummy version.
https://widevine-internal-review.googlesource.com/#/c/10353/

Remove References to Apiary
https://widevine-internal-review.googlesource.com/#/c/9924/

Delete oldest entry in usage table when full
bug: 15184824
https://widevine-internal-review.googlesource.com/#/c/10295/

Port DeviceFiles to iOS.
https://widevine-internal-review.googlesource.com/#/c/10355/

Make testing functions in DeviceFiles private.
https://widevine-internal-review.googlesource.com/#/c/10354/

Add RSA encryption to haystack
https://widevine-internal-review.googlesource.com/#/c/10280/

Add string and vector includes to CDM header.
https://widevine-internal-review.googlesource.com/#/c/10352/

First version of oemcrypto logging
https://widevine-internal-review.googlesource.com/#/c/10252/

Update Names of Secure Stop Methods
bug: 11987015
https://widevine-internal-review.googlesource.com/#/c/10152/

Adjust timing on the Usage Table unit test
https://widevine-internal-review.googlesource.com/#/c/10307/

Fix all compiler warnings in CDM source release.
https://widevine-internal-review.googlesource.com/#/c/10293/

Fix memset bug: args in wrong order
https://widevine-internal-review.googlesource.com/#/c/10292/

Partial revert of 'Remove refs to test prov server, Level3 support...'
https://widevine-internal-review.googlesource.com/#/c/10281/

Pack structure OEMCrypto_PST_Report
https://widevine-internal-review.googlesource.com/#/c/10243/

Remove refs to test prov server, Level3 support; remove dead code
https://widevine-internal-review.googlesource.com/#/c/10220/

Partial revert of 'Document data strings; clean up license server parameters.'
https://widevine-internal-review.googlesource.com/#/c/10188/

Document data strings; clean up license server parameters.
https://widevine-internal-review.googlesource.com/#/c/10120/

Fix broken build after partner branch merge.
https://widevine-internal-review.googlesource.com/#/c/10181/

TODO Cleanup - core/src, core/include
https://widevine-internal-review.googlesource.com/#/c/9965/

TODO Cleanup - cdm, chromium, core/test.
https://widevine-internal-review.googlesource.com/#/c/9419/

Remove unneeded properties.
https://widevine-internal-review.googlesource.com/#/c/10162/

Change-Id: If2bb9d743a562a3875bebb91933c0aaadea286b2
This commit is contained in:
Fred Gylys-Colwell
2014-06-25 13:02:54 -07:00
parent 8a8feb747c
commit b5e8b87fed
66 changed files with 2927 additions and 1998 deletions

View File

@@ -33,13 +33,13 @@ wvcdm::KeyId g_key_id_pssh;
wvcdm::KeyId g_key_id_unwrapped;
wvcdm::CdmKeySystem g_key_system;
std::string g_license_server;
std::string g_port;
wvcdm::KeyId g_wrong_key_id;
int g_use_full_path = 0; // cannot use boolean in getopt_long
// This is the RSA certificate from the provisioning server. The client
// sends this certificate to a license server as verification in the
// provisioning test case.
// This is an RSA certificate message from the provisioning server.
// The client sends this certificate to a license server for device
// authentication by the license server.
// This certificate is used to test the CDM engine's provisioning
// response handling.
static wvcdm::CdmProvisioningResponse kValidJsonProvisioningResponse =
"{\"signedResponse\": {"
"\"message\": \"CrAJYTyIdLPiA2jBzMskbE_gFQj69wv23VlJ2e3MBKtK4nJwKyNYGyyluqKo"
@@ -136,16 +136,16 @@ class WvCdmEngineTest : public testing::Test {
std::string GetKeyRequestResponse(const std::string& server_url,
const std::string& client_auth) {
// Use secure connection and chunk transfer coding.
UrlRequest url_request(server_url + client_auth, g_port, true, true);
UrlRequest url_request(server_url + client_auth);
if (!url_request.is_connected()) {
return "";
}
url_request.PostRequest(key_msg_);
std::string response;
int resp_bytes = url_request.GetResponse(&response);
LOGD("response:\r\n%s", response.c_str());
LOGD("end %d bytes response dump", resp_bytes);
bool ok = url_request.GetResponse(&response);
LOGD("response: %s\n", response.c_str());
EXPECT_TRUE(ok);
int status_code = url_request.GetStatusCode(response);
EXPECT_EQ(kHttpOk, status_code);
@@ -252,41 +252,38 @@ int main(int argc, char **argv) {
// The following variables are configurable through command line options.
g_license_server.assign(config.license_server());
g_key_id_pssh.assign(config.key_id());
g_port.assign(config.port());
std::string license_server(g_license_server);
int show_usage = 0;
static const struct option long_options[] = {
{ "use_full_path", no_argument, &g_use_full_path, 0 },
{ "keyid", required_argument, NULL, 'k' },
{ "port", required_argument, NULL, 'p' },
{ "server", required_argument, NULL, 's' },
{ "vmodule", required_argument, NULL, 0 },
{ "v", required_argument, NULL, 0 },
{ NULL, 0, NULL, '\0' }
};
int option_index = 0;
int opt = 0;
while ((opt = getopt_long(argc, argv, "k:p:s:u", long_options, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "k:s:v", long_options, &option_index)) != -1) {
switch (opt) {
case 'k': {
g_key_id_pssh.clear();
g_key_id_pssh.assign(optarg);
break;
}
case 'p': {
g_port.clear();
g_port.assign(optarg);
break;
}
case 's': {
g_license_server.clear();
g_license_server.assign(optarg);
break;
}
case 'u': {
g_use_full_path = 1;
case 'v': {
// This option _may_ have already been consumed by wvcdm::InitLogging()
// above, depending on the platform-specific logging implementation.
// We only tell getopt about it so that it is not an error. We ignore
// the option here when seen.
// TODO: Stop passing argv to InitLogging, and instead set the log
// level here through the logging API. We should keep all command-line
// parsing at the application level, rather than split between various
// apps and various platform-specific logging implementations.
break;
}
case '?': {
@@ -302,11 +299,6 @@ int main(int argc, char **argv) {
std::cout << " enclose multiple arguments in '' when using adb shell" << std::endl;
std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl;
std::cout << std::setw(30) << std::left << " --port=<connection port>";
std::cout << "specifies the port number, in decimal format" << std::endl;
std::cout << std::setw(30) << std::left << " ";
std::cout << "default: " << g_port << std::endl;
std::cout << std::setw(30) << std::left << " --server=<server_url>";
std::cout << "configure the license server url, please include http[s] in the url" << std::endl;
std::cout << std::setw(30) << std::left << " ";
@@ -316,21 +308,15 @@ int main(int argc, char **argv) {
std::cout << "configure the key id or pssh, in hex format" << std::endl;
std::cout << std::setw(30) << std::left << " default keyid:";
std::cout << g_key_id_pssh << std::endl;
std::cout << std::setw(30) << std::left << " --use_full_path";
std::cout << "specify server url is not a proxy server" << std::endl;
std::cout << std::endl;
return 0;
}
std::cout << std::endl;
std::cout << "Server: " << g_license_server << std::endl;
std::cout << "Port: " << g_port << std::endl;
std::cout << "KeyID: " << g_key_id_pssh << std::endl << std::endl;
g_key_id_pssh = wvcdm::a2bs_hex(g_key_id_pssh);
config.set_license_server(g_license_server);
config.set_port(g_port);
config.set_key_id(g_key_id_pssh);
// Extract the key ID from the PSSH box.

View File

@@ -5,42 +5,50 @@
namespace {
const std::string kWidevineKeySystem = "com.widevine.alpha";
// Youtube Content Protection license server data
const std::string kYtCpLicenseServer =
// Content Protection license server data
const std::string kCpLicenseServer =
"http://wv-ref-eme-player.appspot.com/proxy";
const std::string kYtCpClientAuth = "";
const std::string kYtCpKeyId =
"000000427073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id
"08011a0d7769646576696e655f7465737422" // pssh data (streaming)
"0f73747265616d696e675f636c697031";
const std::string kYtCpOfflineKeyId =
"000000407073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000020" // Widevine system id
"08011a0d7769646576696e655f7465737422" //pssh data (offline)
"0d6f66666c696e655f636c697031";
// Youtube license server data
const std::string kYtLicenseServer =
"https://www.youtube.com/api/drm/"
"widevine?video_id=03681262dc412c06&source=YOUTUBE";
const std::string kYtClientAuth = "";
const std::string kYtKeyId =
"000000347073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
"0801121093789920E8D6520098577DF8F2DD5546"; // pssh data
const std::string kCpClientAuth = "";
const std::string kCpKeyId =
"00000042" // blob size
"70737368" // "pssh"
"00000000" // flags
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
"00000022" // pssh data size
// pssh data:
"08011a0d7769646576696e655f746573"
"74220f73747265616d696e675f636c69"
"7031";
const std::string kCpOfflineKeyId =
"00000040" // blob size
"70737368" // "pssh"
"00000000" // flags
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
"00000020" // pssh data size
// pssh data:
"08011a0d7769646576696e655f746573"
"74220d6f66666c696e655f636c697031";
// Google Play license server data
const std::string kGpLicenseServer =
"https://jmt17.google.com/video/license/GetCencLicense";
// Test client authorization string.
// NOTE: Append a userdata attribute to place a unique marker that the
// server team can use to track down specific requests during debugging
// e.g., "<existing-client-auth-string>&userdata=<your-ldap>.<your-tag>"
// "<existing-client-auth-string>&userdata=jbmr2.dev"
const std::string kGpClientAuth =
"?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine";
const std::string kGpKeyId =
"00000034" // blob size
"70737368" // "pssh"
"00000000" // flags
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
"00000014" // pssh data size
// pssh data:
"08011210e02562e04cd55351b14b3d74"
"8d36ed8e";
const std::string kGpOfflineKeyId = kGpKeyId;
const std::string kGpClientOfflineQueryParameters =
"&offline=true";
@@ -49,33 +57,30 @@ const std::string kGpClientOfflineRenewalQueryParameters =
const std::string kGpClientOfflineReleaseQueryParameters =
"&offline=true&release=true";
const std::string kGpKeyId =
"000000347073736800000000" // blob size and pssh
"edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id
"08011210e02562e04cd55351b14b3d748d36ed8e"; // pssh data
// An invalid key id, expected to fail
const std::string kWrongKeyId =
"000000347073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
"0901121094889920E8D6520098577DF8F2DD5546"; // pssh data
"00000034" // blob size
"70737368" // "pssh"
"00000000" // flags
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
"00000014" // pssh data size
// pssh data:
"0901121094889920e8d6520098577df8"
"f2dd5546";
// Url returned by GetProvisioningRequest()
// URL of provisioning server (returned by GetProvisioningRequest())
const std::string kProductionProvisioningServerUrl =
"https://www.googleapis.com/"
"certificateprovisioning/v1/devicecertificates/create"
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
const std::string kServerSdkLicenseServer =
"http://kir03fcpg174.widevine.net/widevine/cgi-bin/drm.cgi";
const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = {
{ wvcdm::kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId,
kGpKeyId, kDefaultHttpsPort, true, true },
{ wvcdm::kYouTubeContentProtectionServer, kYtCpLicenseServer,
kYtCpClientAuth, kYtCpKeyId, kYtCpOfflineKeyId, kDefaultHttpPort,
false, false }
{ wvcdm::kGooglePlayServer, kGpLicenseServer,
kGpClientAuth, kGpKeyId, kGpOfflineKeyId },
{ wvcdm::kContentProtectionServer, kCpLicenseServer,
kCpClientAuth, kCpKeyId, kCpOfflineKeyId },
};
} // namespace
namespace wvcdm {
@@ -113,11 +118,7 @@ void ConfigTestEnv::Init(LicenseServerId server_id) {
key_id_ = license_servers[server_id].key_id;
key_system_ = kWidevineKeySystem;
license_server_ = license_servers[server_id].url;
port_ = license_servers[server_id].port;
provisioning_server_url_ = kProductionProvisioningServerUrl;
server_sdk_license_server_ = kServerSdkLicenseServer;
use_chunked_transfer_ = license_servers[server_id].use_chunked_transfer;
use_secure_transfer_ = license_servers[server_id].use_secure_transfer;
wrong_key_id_= kWrongKeyId;
}

View File

@@ -6,15 +6,10 @@
#include <string>
#include "wv_cdm_types.h"
namespace {
const std::string kDefaultHttpsPort = "443";
const std::string kDefaultHttpPort = "80";
}
namespace wvcdm {
typedef enum {
kGooglePlayServer,
kYouTubeContentProtectionServer
kContentProtectionServer,
} LicenseServerId;
// Configures default test environment.
@@ -26,9 +21,6 @@ class ConfigTestEnv {
std::string client_tag;
std::string key_id;
std::string offline_key_id;
std::string port;
bool use_chunked_transfer;
bool use_secure_transfer;
} LicenseServerConfiguration;
explicit ConfigTestEnv(LicenseServerId server_id);
@@ -41,15 +33,9 @@ class ConfigTestEnv {
const KeyId& key_id() const { return key_id_; }
const CdmKeySystem& key_system() const { return key_system_; }
const std::string& license_server() const { return license_server_; }
const std::string& port() const { return port_; }
const std::string& provisioning_server_url() const {
return provisioning_server_url_;
}
const std::string& server_sdk_license_server() const {
return server_sdk_license_server_;
}
bool use_chunked_transfer() { return use_chunked_transfer_; }
bool use_secure_transfer() { return use_secure_transfer_; }
const KeyId& wrong_key_id() const { return wrong_key_id_; }
void set_key_id(KeyId& key_id) { key_id_.assign(key_id); }
@@ -59,7 +45,6 @@ class ConfigTestEnv {
void set_license_server(std::string& license_server) {
license_server_.assign(license_server);
}
void set_port(std::string& port) { port_.assign(port); }
private:
void Init(LicenseServerId server_id);
@@ -68,11 +53,7 @@ class ConfigTestEnv {
KeyId key_id_;
CdmKeySystem key_system_;
std::string license_server_;
std::string port_;
std::string provisioning_server_url_;
std::string server_sdk_license_server_;
bool use_chunked_transfer_;
bool use_secure_transfer_;
KeyId wrong_key_id_;
CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv);

View File

@@ -26,10 +26,15 @@ using ::testing::StrEq;
namespace {
const uint32_t kCertificateLen = 700;
const uint32_t kWrappedKeyLen = 500;
const uint32_t kProtobufEstimatedOverhead = 75;
const uint32_t kLicenseRequestLen = 300;
const uint32_t kLicenseLen = 500;
const uint32_t kProviderSessionTokenLen = 128;
// Structurally valid test certificate.
// The data elements in this module are used to test the storage and
// retrieval of certificates and licenses
const std::string kTestCertificate =
"124B035F3D256A656F0E505A085E7A6C482B61035E0C4A540F7803137F4C3B45206B7F33"
"347F4D7A005E56400F0955011F4E07072D0D46781817460974326A516E3944385760280E"
@@ -51,6 +56,10 @@ const std::string kTestCertificate =
"523B102906195E003C2D111A7D4740122C6941003726602B59263B5C09473D4E025E3541"
"701B122D340A3D145436137002687E4C470D2F6F4C357A3245384D737B734E2274301179"
"402473486311156E5A0C78644C593273";
// A Wrapped Private Key
// The data elements in this module are used to test the storage and
// retrieval of certificates and licenses
const std::string kTestWrappedPrivateKey =
"4F724B065326371A2F5F6F51467C2E26555C453B5C7C1B4F2738454B782E3E7B5340435A"
"66374D0612052C521A233D7A67194871751C78575E5177070130264C4F037633320E667B"
@@ -66,6 +75,10 @@ const std::string kTestWrappedPrivateKey =
"5F7F3D6B64525E7227165948101540243C19495C4C702F37490F26613353797825624143"
"263043020E1E6760123D51056F2F1E482F2E3D021B27677D3E7E3C0C11757C3448275E08"
"382E111263644C6D224714706D760A054A586E17505C3429575A41043F184209";
// The test certificate in file storage format.
// The data elements in this module are used to test the storage and
// retrieval of certificates and licenses
const std::string kTestCertificateFileData =
"0ABD09080110011AB6090ABC05124B035F3D256A656F0E505A085E7A6C482B61035E0C4A"
"540F7803137F4C3B45206B7F33347F4D7A005E56400F0955011F4E07072D0D4678181746"
@@ -115,10 +128,13 @@ struct LicenseInfo {
std::string file_data;
};
// Sample data to test storage and retrieval of license-related data
// The license data and URLs in this test are not real.
size_t kNumberOfLicenses = 3;
// Sample license data and related data for storage and use for offline
// playback. The license data and URLs in this test are not real. Test
// storage and retrieval of license-related data.
LicenseInfo license_test_data[] = {
// license 0
{"ksid54C57C966E23CEF5", DeviceFiles::kLicenseStateActive,
wvcdm::a2bs_hex("0801121030313233343536373839414243444546"),
@@ -310,6 +326,7 @@ LicenseInfo license_test_data[] = {
"652E636F6D2F766964656F2D6465762F6C6963656E73652F47657443656E"
"634C6963656E73651220F6974C1CFFD00E3144488FC092D3DF4F6007A3CA"
"C4756EB046DC74B1C2E512CC")},
// license 1
{"ksidC8EAA2579A282EB0", DeviceFiles::kLicenseStateReleasing,
wvcdm::a2bs_hex("0801121030313233343536373839414243444546"),
@@ -502,6 +519,7 @@ LicenseInfo license_test_data[] = {
"3D3033363831323632646334313263303626736F757263653D594F555455"
"42451220EC449C6B026C43004743061B3A3DCB7208B2AD11600254841B96"
"1CFA1AD57172")},
// license 2
{"ksidE8C37662C88DC673", DeviceFiles::kLicenseStateReleasing,
wvcdm::a2bs_hex("0801121030313233343536373839414243444546"),
@@ -693,6 +711,9 @@ LicenseInfo license_test_data[] = {
"72702E676F6F676C652E636F6D3A383838382F64726D12205CD2C43C618C"
"CA27BBCB2EEBDE32B57CBD51B424FD85DAB715B7F5A87546FD40")}};
// Sample license data and related data for storage and use for offline
// playback. The license data and URLs in this test are not real.
// The data is used to test license-related functions.
LicenseInfo license_update_test_data[] = {
// active license
{"key_set_id_: ksid2A048BC7FAEC885A", DeviceFiles::kLicenseStateActive,

View File

@@ -45,10 +45,10 @@ class FileTest : public testing::Test {
TEST_F(FileTest, FileExists) {
File file;
EXPECT_TRUE(file.Exists(test_vectors::kFileExists));
EXPECT_TRUE(file.Exists(test_vectors::kDirExists));
EXPECT_FALSE(file.Exists(test_vectors::kFileDoesNotExist));
EXPECT_FALSE(file.Exists(test_vectors::kDirDoesNotExist));
EXPECT_TRUE(file.Exists(test_vectors::kExistentFile));
EXPECT_TRUE(file.Exists(test_vectors::kExistentDir));
EXPECT_FALSE(file.Exists(test_vectors::kNonExistentFile));
EXPECT_FALSE(file.Exists(test_vectors::kNonExistentDir));
}
TEST_F(FileTest, CreateDirectory) {

View File

@@ -5,17 +5,35 @@
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <string.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include "log.h"
#include "openssl/bio.h"
#include "openssl/err.h"
#include "openssl/x509.h"
namespace wvcdm {
namespace {
SSL_CTX* HttpSocket::InitSslContext(void) {
// Helper function to tokenize a string. This makes it easier to avoid silly
// parsing bugs that creep in easily when each part of the string is parsed
// with its own piece of code.
bool Tokenize(const std::string& source, const std::string& delim,
const size_t offset, std::string* substring_output,
size_t* next_offset) {
size_t start_of_delim = source.find(delim, offset);
if (start_of_delim == std::string::npos) {
return false;
}
substring_output->assign(source, offset, start_of_delim - offset);
*next_offset = start_of_delim + delim.size();
return true;
}
SSL_CTX* InitSslContext() {
const SSL_METHOD* method;
SSL_CTX* ctx;
@@ -23,21 +41,18 @@ SSL_CTX* HttpSocket::InitSslContext(void) {
SSL_load_error_strings();
method = SSLv3_client_method();
ctx = SSL_CTX_new(method);
if (NULL == ctx) {
if (!ctx)
LOGE("failed to create SSL context");
}
return ctx;
}
void HttpSocket::ShowServerCertificate(const SSL* ssl) {
X509* cert;
char* line;
// unused, may be useful for debugging SSL-related issues.
void ShowServerCertificate(const SSL* ssl) {
// gets the server certificate
cert = SSL_get_peer_certificate(ssl);
if (cert != NULL) {
X509* cert = SSL_get_peer_certificate(ssl);
if (cert) {
char* line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
LOGV("server certificate:");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
LOGV("subject: %s", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
@@ -49,183 +64,252 @@ void HttpSocket::ShowServerCertificate(const SSL* ssl) {
}
}
HttpSocket::HttpSocket()
: secure_connect_(true),
socket_fd_(-1),
// Wait for a socket to be ready for reading or writing.
// Establishing a connection counts as "ready for write".
// Returns false on select error or timeout.
// Returns true when the socket is ready.
bool SocketWait(int fd, bool for_read, int timeout_in_ms) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv;
tv.tv_sec = timeout_in_ms / 1000;
tv.tv_usec = (timeout_in_ms % 1000) * 1000;
fd_set *read_fds = NULL;
fd_set *write_fds = NULL;
if (for_read) {
read_fds = &fds;
} else {
write_fds = &fds;
}
int ret = select(fd + 1, read_fds, write_fds, NULL, &tv);
if (ret == 0) {
LOGE("socket timed out");
return false;
} else if (ret == -1) {
LOGE("select failed, errno = %d", errno);
return false;
}
// socket ready.
return true;
}
} // namespace
namespace wvcdm {
// Parses the URL and extracts all relevant information.
// static
bool HttpSocket::ParseUrl(const std::string& url,
std::string* scheme,
bool* secure_connect,
std::string* domain_name,
int* port,
std::string* path) {
size_t offset = 0;
if (!Tokenize(url, "://", offset, scheme, &offset)) {
LOGE("Invalid URL, scheme not found: %s", url.c_str());
return false;
}
// If the scheme is http or https, set secure_connect and port accordingly.
// Otherwise, consider the scheme unsupported and fail.
if (*scheme == "http") {
*secure_connect = false;
*port = 80;
} else if (*scheme == "https") {
*secure_connect = true;
*port = 443;
} else {
LOGE("Invalid URL, scheme not supported: %s", url.c_str());
return false;
}
if (!Tokenize(url, "/", offset, domain_name, &offset)) {
// The rest of the URL belongs to the domain name.
domain_name->assign(url, offset, std::string::npos);
// No explicit path after the domain name.
path->assign("/");
} else {
// The rest of the URL, including the preceding slash, belongs to the path.
path->assign(url, offset - 1, std::string::npos);
}
// The domain name may optionally contain a port which overrides the default.
std::string domain_name_without_port;
size_t port_offset;
if (Tokenize(*domain_name, ":", 0, &domain_name_without_port,
&port_offset)) {
*port = atoi(domain_name->c_str() + port_offset);
if (*port <= 0 || *port >= 65536) {
LOGE("Invalid URL, port not valid: %s", url.c_str());
return false;
}
domain_name->assign(domain_name_without_port);
}
return true;
}
HttpSocket::HttpSocket(const std::string& url)
: socket_fd_(-1),
ssl_(NULL),
ssl_ctx_(NULL),
timeout_enabled_(false) {
ssl_ctx_(NULL) {
valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_,
&resource_path_);
SSL_library_init();
}
HttpSocket::~HttpSocket() { CloseSocket(); }
HttpSocket::~HttpSocket() {
CloseSocket();
}
void HttpSocket::CloseSocket() {
if (socket_fd_ != -1) {
close(socket_fd_);
socket_fd_ = -1;
}
if (secure_connect_) {
if (ssl_) {
SSL_free(ssl_);
ssl_ = NULL;
}
if (ssl_ctx_) {
CloseSslContext(ssl_ctx_);
ssl_ctx_ = NULL;
}
if (ssl_) {
SSL_free(ssl_);
ssl_ = NULL;
}
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
ssl_ctx_ = NULL;
}
}
// Extracts the domain name and resource path from the input url parameter.
// The results are put in domain_name and resource_path respectively.
// The format of the url can begin with <protocol/scheme>:://domain server/...
// or dowmain server/resource_path
void HttpSocket::GetDomainNameAndPathFromUrl(const std::string& url,
std::string& domain_name,
std::string& resource_path) {
domain_name.clear();
resource_path.clear();
size_t start = url.find("//");
size_t end = url.npos;
if (start != url.npos) {
end = url.find("/", start + 2);
if (end != url.npos) {
domain_name.assign(url, start + 2, end - start - 2);
resource_path.assign(url, end + 1, url.npos);
} else {
domain_name.assign(url, start + 2, url.npos);
}
} else {
// no scheme/protocol in url
end = url.find("/");
if (end != url.npos) {
domain_name.assign(url, 0, end);
resource_path.assign(url, end + 1, url.npos);
} else {
domain_name.assign(url);
}
bool HttpSocket::Connect(int timeout_in_ms) {
if (!valid_url_) {
return false;
}
// strips port number if present, e.g. https://www.domain.com:8888/...
end = domain_name.find(":");
if (end != domain_name.npos) {
domain_name.erase(end);
}
}
bool HttpSocket::Connect(const char* url, const std::string& port,
bool enable_timeout, bool secure_connection) {
secure_connect_ = secure_connection;
if (secure_connect_) ssl_ctx_ = InitSslContext();
GetDomainNameAndPathFromUrl(url, domain_name_, resource_path_);
// get a socket
socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd_ < 0) {
LOGE("cannot open socket %d", errno);
LOGE("cannot open socket, errno = %d", errno);
return false;
}
int reuse = 1;
if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) ==
-1) {
// set the socket in non-blocking mode
int original_flags = fcntl(socket_fd_, F_GETFL, 0);
if (original_flags == -1) {
LOGE("fcntl error, errno = %d", errno);
CloseSocket();
return false;
}
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
LOGE("fcntl error, errno = %d", errno);
CloseSocket();
LOGE("setsockopt error %d", errno);
return false;
}
// lookup the server IP
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo* addr_info = NULL;
bool status = true;
int ret = getaddrinfo(domain_name_.c_str(), port.c_str(), &hints, &addr_info);
int ret = getaddrinfo(domain_name_.c_str(), NULL, &hints, &addr_info);
if (ret != 0) {
CloseSocket();
LOGE("getaddrinfo failed with %d", ret);
status = false;
LOGE("getaddrinfo failed, errno = %d", ret);
return false;
}
// set the port
struct sockaddr_in* addr_ipv4 = reinterpret_cast<struct sockaddr_in*>(
addr_info->ai_addr);
addr_ipv4->sin_port = htons(port_);
// connect to the server
ret = connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen);
freeaddrinfo(addr_info);
if (ret == 0) {
// connected right away.
} else {
if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) {
if (errno != EINPROGRESS) {
// failed right away.
LOGE("cannot connect to %s, errno = %d", domain_name_.c_str(), errno);
CloseSocket();
LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(),
errno);
status = false;
return false;
} else {
// in progress. block until timeout expired or connection established.
if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) {
LOGE("cannot connect to %s", domain_name_.c_str());
CloseSocket();
return false;
}
}
}
timeout_enabled_ = enable_timeout;
if (addr_info != NULL) {
freeaddrinfo(addr_info);
}
if (!status) return false;
// set up SSL if needed
if (secure_connect_) {
ssl_ctx_ = InitSslContext();
if (!ssl_ctx_) {
CloseSocket();
return false;
}
// secures connection
if (secure_connect_ && ssl_ctx_) {
ssl_ = SSL_new(ssl_ctx_);
if (!ssl_) {
LOGE("failed SSL_new");
CloseSocket();
return false;
}
BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE);
if (!a_bio) {
LOGE("BIO_new_socket error");
CloseSocket();
return false;
}
SSL_set_bio(ssl_, a_bio, a_bio);
int ret = SSL_connect(ssl_);
if (1 != ret) {
char buf[256];
LOGE("SSL_connect error:%s", ERR_error_string(ERR_get_error(), buf));
return false;
}
do {
ret = SSL_connect(ssl_);
if (ret != 1) {
int ssl_err = SSL_get_error(ssl_, ret);
if (ssl_err != SSL_ERROR_WANT_READ &&
ssl_err != SSL_ERROR_WANT_WRITE) {
char buf[256];
LOGE("SSL_connect error: %s", ERR_error_string(ERR_get_error(), buf));
CloseSocket();
return false;
}
bool for_read = ssl_err == SSL_ERROR_WANT_READ;
if (!SocketWait(socket_fd_, for_read, timeout_in_ms)) {
LOGE("cannot connect to %s", domain_name_.c_str());
CloseSocket();
return false;
}
}
} while (ret != 1);
}
return true;
}
int HttpSocket::Read(char* data, int len) { return (Read(data, len, 0)); }
// makes non-blocking mode only during read, it supports timeout for read
// returns -1 for error, number of bytes read for success
// Returns -1 for error, number of bytes read for success.
// The timeout here only applies to the span between packets of data, for the
// sake of simplicity.
int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
bool use_timeout = (timeout_enabled_ && (timeout_in_ms > 0));
int original_flags = 0;
if (use_timeout) {
original_flags = fcntl(socket_fd_, F_GETFL, 0);
if (original_flags == -1) {
LOGE("fcntl error %d", errno);
return -1;
}
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
LOGE("fcntl error %d", errno);
return -1;
}
}
int total_read = 0;
int read = 0;
int to_read = len;
while (to_read > 0) {
if (use_timeout) {
fd_set read_fds;
struct timeval tv;
tv.tv_sec = timeout_in_ms / 1000;
tv.tv_usec = (timeout_in_ms % 1000) * 1000;
FD_ZERO(&read_fds);
FD_SET(socket_fd_, &read_fds);
if (select(socket_fd_ + 1, &read_fds, NULL, NULL, &tv) == -1) {
LOGE("select failed");
break;
}
if (!FD_ISSET(socket_fd_, &read_fds)) {
LOGD("socket read timeout");
break;
}
if (!SocketWait(socket_fd_, /* for_read */ true, timeout_in_ms)) {
LOGE("unable to read from %s", domain_name_.c_str());
return -1;
}
int read;
if (secure_connect_)
read = SSL_read(ssl_, data, to_read);
else
@@ -236,27 +320,26 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
data += read;
total_read += read;
} else if (read == 0) {
// in blocking mode, zero read mean's peer closed.
// in non-blocking mode, select said that there is data. so it should not
// happen
// The connection has been closed. No more data.
break;
} else {
LOGE("recv returned %d, error = %d", read, errno);
break;
LOGE("recv returned %d, errno = %d", read, errno);
return -1;
}
}
if (use_timeout) {
fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again
}
return total_read;
}
int HttpSocket::Write(const char* data, int len) {
// Returns -1 for error, number of bytes written for success.
// The timeout here only applies to the span between packets of data, for the
// sake of simplicity.
int HttpSocket::Write(const char* data, int len, int timeout_in_ms) {
int total_sent = 0;
int sent = 0;
int to_send = len;
while (to_send > 0) {
int sent;
if (secure_connect_)
sent = SSL_write(ssl_, data, to_send);
else
@@ -267,11 +350,17 @@ int HttpSocket::Write(const char* data, int len) {
data += sent;
total_sent += sent;
} else if (sent == 0) {
usleep(10); // retry later
// We filled up the pipe. Wait for room to write.
if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) {
LOGE("unable to write to %s", domain_name_.c_str());
return -1;
}
} else {
LOGE("send returned error %d", errno);
LOGE("send returned %d, errno = %d", sent, errno);
return -1;
}
}
return total_sent;
}

View File

@@ -4,43 +4,52 @@
#define CDM_TEST_HTTP_SOCKET_H_
#include <string>
#include "openssl/ssl.h"
#include "wv_cdm_types.h"
#include <gtest/gtest_prod.h>
#include <openssl/ssl.h>
#include "wv_cdm_types.h" // CORE_DISALLOW_COPY_AND_ASSIGN
namespace wvcdm {
// Provides basic Linux based TCP socket interface.
class HttpSocket {
public:
HttpSocket();
// A scheme (http:// or https://) is required for the URL.
explicit HttpSocket(const std::string& url);
~HttpSocket();
bool Connect(int timeout_in_ms);
void CloseSocket();
bool Connect(const char* url, const std::string& port, bool enable_timeout,
bool secure_connection);
void GetDomainNameAndPathFromUrl(const std::string& url,
std::string& domain_name,
std::string& resource_path);
const std::string& domain_name() const { return domain_name_; };
const std::string& resource_path() const { return resource_path_; };
int Read(char* data, int len);
const std::string& scheme() const { return scheme_; }
bool secure_connect() const { return secure_connect_; }
const std::string& domain_name() const { return domain_name_; }
int port() const { return port_; }
const std::string& resource_path() const { return resource_path_; }
int Read(char* data, int len, int timeout_in_ms);
int Write(const char* data, int len);
int Write(const char* data, int len, int timeout_in_ms);
private:
void CloseSslContext(SSL_CTX* ctx) const {
if (ctx) SSL_CTX_free(ctx);
}
SSL_CTX* InitSslContext(void);
void ShowServerCertificate(const SSL* ssl);
static bool ParseUrl(const std::string& url,
std::string* scheme,
bool* secure_connect,
std::string* domain_name,
int* port,
std::string* path);
FRIEND_TEST(HttpSocketTest, ParseUrlTest);
std::string domain_name_;
std::string scheme_;
bool secure_connect_;
std::string domain_name_;
int port_;
std::string resource_path_;
bool valid_url_;
int socket_fd_;
SSL* ssl_;
SSL_CTX* ssl_ctx_;
bool timeout_enabled_;
CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket);
};

View File

@@ -3,6 +3,7 @@
#include <errno.h>
#include "gtest/gtest.h"
#include "http_socket.h"
#include "scoped_ptr.h"
#include "log.h"
#include "string_conversions.h"
#include "url_request.h"
@@ -10,10 +11,14 @@
namespace {
// Arbitrary URL for tests.
const std::string kHttpsTestServer("https://www.google.com");
const std::string kHttpTestServer("http://www.google.com");
// This URL and data are used by RoundTripTest, and can be overridden on the
// command line.
std::string gTestServer(kHttpsTestServer);
std::string gTestData("Hello");
// Arbitrary buffer size and timeout settings.
const int kHttpBufferSize = 4096;
char gBuffer[kHttpBufferSize];
const int kTimeout = 3000;
}
namespace wvcdm {
@@ -21,153 +26,179 @@ namespace wvcdm {
class HttpSocketTest : public testing::Test {
public:
HttpSocketTest() {}
~HttpSocketTest() { socket_.CloseSocket(); }
~HttpSocketTest() {}
protected:
bool Connect(const std::string& server_url, bool secure_connection) {
bool Connect(const std::string& server_url) {
socket_.reset(new HttpSocket(server_url));
std::string port = secure_connection ? "443" : "80";
if (socket_.Connect(server_url.c_str(), port, true, secure_connection)) {
LOGD("connected to %s", socket_.domain_name().c_str());
if (socket_->Connect(kTimeout)) {
LOGD("connected to %s", socket_->domain_name().c_str());
return true;
} else {
LOGE("failed to connect to %s", socket_.domain_name().c_str());
LOGE("failed to connect to %s", server_url.c_str());
return false;
}
return true;
}
bool PostRequest(const std::string& data) {
std::string request("POST ");
if (socket_.resource_path().empty())
request.append(socket_.domain_name());
else
request.append(socket_.resource_path());
request.append(socket_->resource_path());
request.append(" HTTP/1.1\r\n");
request.append("Host: ");
request.append(socket_.domain_name());
request.append("\r\nUser-Agent: httpSocketTest/1.0\r\n");
request.append(socket_->domain_name());
request.append("\r\n");
// Important! Otherwise, the HTTP 1.1 default behavior for a server is to
// keep the connection open for a subsequent request.
request.append("Connection: close\r\n");
request.append("User-Agent: httpSocketTest/1.0\r\n");
char buffer[32] = {0};
snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(data.size()));
request.append("Content-Length: ");
memset(gBuffer, 0, kHttpBufferSize);
snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast<int>(data.size()));
request.append(gBuffer);
request.append(buffer);
request.append("\r\n");
request.append("Content-Type: multipart/form-data\r\n");
// newline terminates header
// an extra newline terminates HTTP headers.
request.append("\r\n");
// append data
request.append(data);
socket_.Write(request.c_str(), request.size());
socket_->Write(request.c_str(), request.size(), kTimeout);
LOGD("request: %s", request.c_str());
return true;
}
bool GetResponse() {
int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000);
bool GetResponse(std::string* response) {
char buffer[kHttpBufferSize];
int bytes = socket_->Read(buffer, sizeof(buffer), kTimeout);
if (bytes < 0) {
LOGE("read error = ", errno);
LOGE("read error, errno = %d", errno);
return false;
} else {
LOGD("read %d bytes", bytes);
std::string response(gBuffer, bytes);
LOGD("response: %s", response.c_str());
LOGD("end response dump");
return true;
}
LOGD("read %d bytes", bytes);
response->assign(buffer, bytes);
return true;
}
HttpSocket socket_;
scoped_ptr<HttpSocket> socket_;
std::string domain_name_;
std::string resource_path_;
};
TEST_F(HttpSocketTest, GetDomainNameAndPathFromUrlTest) {
socket_.GetDomainNameAndPathFromUrl(
"https://code.google.com/p/googletest/wiki/Primer", domain_name_,
resource_path_);
EXPECT_STREQ("code.google.com", domain_name_.c_str());
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
struct ParseUrlTests {
const char* url;
const char* scheme;
bool secure_connect;
const char* domain_name;
int port;
const char* path;
};
socket_.GetDomainNameAndPathFromUrl(
"http://code.google.com/p/googletest/wiki/Primer/", domain_name_,
resource_path_);
EXPECT_STREQ("code.google.com", domain_name_.c_str());
EXPECT_STREQ("p/googletest/wiki/Primer/", resource_path_.c_str());
ParseUrlTests parse_url_tests[] = {
{
"https://code.google.com/p/googletest/wiki/Primer", // url
"https", // scheme
true, // secure_connect
"code.google.com", // domain_name
443, // port
"/p/googletest/wiki/Primer", // path
},
{
"http://code.google.com/p/googletest/wiki/Primer/", // url
"http", // scheme
false, // secure_connect
"code.google.com", // domain_name
80, // port
"/p/googletest/wiki/Primer/", // path
},
{
"http://code.google.com/", // url
"http", // scheme
false, // secure_connect
"code.google.com", // domain_name
80, // port
"/", // path
},
{
"http://code.google.com", // url
"http", // scheme
false, // secure_connect
"code.google.com", // domain_name
80, // port
"/", // path
},
{
"http://10.11.12.13:8888/drm", // url
"http", // scheme
false, // secure_connect
"10.11.12.13", // domain_name
8888, // port
"/drm", // path
},
{
"http://10.11.12.13:8888", // url
"http", // scheme
false, // secure_connect
"10.11.12.13", // domain_name
8888, // port
"/", // path
},
{
"https://10.11.12.13:8888", // url
"https", // scheme
true, // secure_connect
"10.11.12.13", // domain_name
8888, // port
"/", // path
},
{ NULL } // list terminator
};
socket_.GetDomainNameAndPathFromUrl("http://code.google.com/", domain_name_,
resource_path_);
EXPECT_STREQ("code.google.com", domain_name_.c_str());
EXPECT_STREQ("", resource_path_.c_str());
TEST_F(HttpSocketTest, ParseUrlTest) {
std::string scheme;
bool secure_connect;
std::string domain_name;
int port;
std::string path;
ParseUrlTests* test = NULL;
socket_.GetDomainNameAndPathFromUrl("http://code.google.com", domain_name_,
resource_path_);
EXPECT_STREQ("code.google.com", domain_name_.c_str());
EXPECT_STREQ("", resource_path_.c_str());
socket_.GetDomainNameAndPathFromUrl(
"code.google.com/p/googletest/wiki/Primer", domain_name_, resource_path_);
EXPECT_STREQ("code.google.com", domain_name_.c_str());
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
socket_.GetDomainNameAndPathFromUrl("code.google.com", domain_name_,
resource_path_);
EXPECT_STREQ("code.google.com", domain_name_.c_str());
EXPECT_STREQ("", resource_path_.c_str());
socket_.GetDomainNameAndPathFromUrl("code.google.com/", domain_name_,
resource_path_);
EXPECT_STREQ("code.google.com", domain_name_.c_str());
EXPECT_STREQ("", resource_path_.c_str());
socket_.GetDomainNameAndPathFromUrl("", domain_name_, resource_path_);
EXPECT_TRUE(domain_name_.empty());
EXPECT_TRUE(resource_path_.empty());
// Test with arbitrary numeric URL
socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888/drm",
domain_name_, resource_path_);
EXPECT_STREQ("10.11.12.13", domain_name_.c_str());
EXPECT_STREQ("drm", resource_path_.c_str());
socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888", domain_name_,
resource_path_);
EXPECT_STREQ("10.11.12.13", domain_name_.c_str());
EXPECT_TRUE(resource_path_.empty());
for (test = &parse_url_tests[0]; test->url != NULL; ++test) {
bool ok = HttpSocket::ParseUrl(test->url, &scheme, &secure_connect,
&domain_name, &port, &path);
EXPECT_TRUE(ok);
if (ok) {
EXPECT_EQ(test->scheme, scheme);
EXPECT_EQ(test->secure_connect, secure_connect);
EXPECT_EQ(test->domain_name, domain_name);
EXPECT_EQ(test->port, port);
EXPECT_EQ(test->path, path);
}
}
}
TEST_F(HttpSocketTest, ConnectTest) {
const bool kUseSecureConnection = true;
if (gTestServer.find("https") != std::string::npos) {
EXPECT_TRUE(Connect(gTestServer, kUseSecureConnection));
socket_.CloseSocket();
// https connection allows insecure connection through port 80 as well
EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection));
socket_.CloseSocket();
} else {
EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection));
socket_.CloseSocket();
// Test for the case that non-https connection must not use port 443
EXPECT_FALSE(Connect(gTestServer, kUseSecureConnection));
socket_.CloseSocket();
}
EXPECT_FALSE(Connect("ww.g.c", kUseSecureConnection));
socket_.CloseSocket();
EXPECT_FALSE(Connect("ww.g.c", !kUseSecureConnection));
socket_.CloseSocket();
EXPECT_TRUE(Connect(kHttpsTestServer));
EXPECT_TRUE(Connect(kHttpTestServer));
EXPECT_FALSE(Connect("ww.g.c"));
EXPECT_FALSE(Connect("http://ww.g.c"));
EXPECT_FALSE(Connect("https://ww.g.c"));
}
TEST_F(HttpSocketTest, RoundTripTest) {
int secure_connection =
(gTestServer.find("https") != std::string::npos) ? true : false;
ASSERT_TRUE(Connect(gTestServer, secure_connection));
ASSERT_TRUE(Connect(gTestServer));
EXPECT_TRUE(PostRequest(gTestData));
GetResponse();
socket_.CloseSocket();
std::string response;
EXPECT_TRUE(GetResponse(&response));
LOGD("response: %s", response.c_str());
}
} // namespace wvcdm

View File

@@ -44,9 +44,6 @@ void LicenseRequest::GetDrmMessage(const std::string& response,
if (drm_msg_pos != std::string::npos) {
drm_msg = response.substr(drm_msg_pos);
} else {
// TODO(edwinwong, rfrias): hack to get HTTP message body out for
// non-Google Play webservers. Need to clean this up. Possibly test
// for GLS and decide which part is the drm message
drm_msg = response.substr(header_end_pos);
}
} else {
@@ -59,7 +56,7 @@ void LicenseRequest::GetDrmMessage(const std::string& response,
void LicenseRequest::GetHeartbeatUrl(const std::string& response,
std::string& heartbeat_url) {
if (response.empty()) {
heartbeat_url.clear(); // TODO: assign default heartbeat url
heartbeat_url.clear();
return;
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,52 +10,16 @@
#include "string_conversions.h"
namespace {
const int kMaxReadAttempts = 4;
const int kSingleReadAttempt = 1;
} // namespace
namespace wvcdm {
UrlRequest::UrlRequest(const std::string& url, const std::string& port,
bool secure_connection, bool chunk_transfer_mode)
: chunk_transfer_mode_(chunk_transfer_mode),
is_connected_(false),
port_("80"),
request_(""),
server_url_(url) {
if (!port.empty()) {
port_.assign(port);
}
if (socket_.Connect((server_url_).c_str(), port_, true, secure_connection)) {
is_connected_ = true;
} else {
LOGE("failed to connect to %s, port=%s", socket_.domain_name().c_str(),
port.c_str());
}
}
UrlRequest::~UrlRequest() { socket_.CloseSocket(); }
void UrlRequest::AppendChunkToUpload(const std::string& data) {
// format of chunk:
// size of chunk in hex\r\n
// data\r\n
// . . .
// 0\r\n
// buffer to store length of chunk
memset(buffer_, 0, kHttpBufferSize);
snprintf(buffer_, kHttpBufferSize, "%zx\r\n", data.size());
request_.append(buffer_); // appends size of chunk
LOGD("...\r\n%s", request_.c_str());
request_.append(data);
request_.append("\r\n"); // marks end of data
}
const int kReadBufferSize = 1024;
const int kConnectTimeoutMs = 5000;
const int kWriteTimeoutMs = 3000;
const int kReadTimeoutMs = 3000;
// Concatenate all chunks into one blob and returns the response with
// header information.
void UrlRequest::ConcatenateChunkedResponse(const std::string http_response,
std::string* modified_response) {
void ConcatenateChunkedResponse(const std::string http_response,
std::string* modified_response) {
if (http_response.empty()) return;
modified_response->clear();
@@ -103,33 +67,52 @@ void UrlRequest::ConcatenateChunkedResponse(const std::string http_response,
}
}
int UrlRequest::GetResponse(std::string* message) {
message->clear();
} // namespace
namespace wvcdm {
UrlRequest::UrlRequest(const std::string& url)
: is_connected_(false),
socket_(url) {
if (socket_.Connect(kConnectTimeoutMs)) {
is_connected_ = true;
} else {
LOGE("failed to connect to %s, port=%d", socket_.domain_name().c_str(),
socket_.port());
}
}
UrlRequest::~UrlRequest() {}
bool UrlRequest::GetResponse(std::string* message) {
std::string response;
const int kTimeoutInMs = 3000;
int bytes = 0;
for (int attempts = kMaxReadAttempts; attempts > 0; --attempts) {
memset(buffer_, 0, kHttpBufferSize);
bytes = socket_.Read(buffer_, kHttpBufferSize, kTimeoutInMs);
// 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];
int bytes = socket_.Read(read_buffer, sizeof(read_buffer), kReadTimeoutMs);
if (bytes > 0) {
response.append(buffer_, bytes);
if (bytes < static_cast<int>(kHttpBufferSize)) {
attempts = kSingleReadAttempt;
}
response.append(read_buffer, bytes);
} else if (bytes < 0) {
LOGE("read error, errno = %d", errno);
return false;
} else {
if (bytes < 0) LOGE("read error = ", errno);
// bytes == 0 indicates nothing to read
// end of stream.
break;
}
}
ConcatenateChunkedResponse(response, message);
LOGD("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str());
return message->size();
return true;
}
// static
int UrlRequest::GetStatusCode(const std::string& response) {
const std::string kHttpVersion("HTTP/1.1");
const std::string kHttpVersion("HTTP/1.1 ");
int status_code = -1;
size_t pos = response.find(kHttpVersion);
@@ -140,79 +123,48 @@ int UrlRequest::GetStatusCode(const std::string& response) {
return status_code;
}
bool UrlRequest::PostRequestChunk(const std::string& data) {
request_.assign("POST /");
request_.append(socket_.resource_path());
request_.append(" HTTP/1.1\r\n");
request_.append("Host: ");
request_.append(socket_.domain_name());
request_.append("\r\nConnection: Keep-Alive\r\n");
request_.append("Transfer-Encoding: chunked\r\n");
request_.append("User-Agent: Widevine CDM v1.0\r\n");
request_.append("Accept-Encoding: gzip,deflate\r\n");
request_.append("Accept-Language: en-us,fr\r\n");
request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n");
request_.append("\r\n"); // empty line to terminate header
bool UrlRequest::PostRequestWithPath(const std::string& path,
const std::string& data) {
std::string request;
// calls AppendChunkToUpload repeatedly for multiple chunks
AppendChunkToUpload(data);
request.append("POST ");
request.append(path);
request.append(" HTTP/1.1\r\n");
// terminates last chunk with 0\r\n, then ends header with an empty line
request_.append("0\r\n\r\n");
request.append("Host: ");
request.append(socket_.domain_name());
request.append("\r\n");
socket_.Write(request_.c_str(), request_.size());
return true;
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), "%zd", 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);
int ret = socket_.Write(request.c_str(), request.size(), kWriteTimeoutMs);
LOGD("HTTP request: (%d): %s", request.size(), b2a_hex(request).c_str());
return ret != -1;
}
bool UrlRequest::PostRequest(const std::string& data) {
if (chunk_transfer_mode_) {
return PostRequestChunk(data);
}
request_.assign("POST /");
request_.append(socket_.resource_path());
request_.append(" HTTP/1.1\r\n");
request_.append("Host: ");
request_.append(socket_.domain_name());
request_.append("\r\nConnection: Keep-Alive\r\n");
request_.append("User-Agent: Widevine CDM v1.0\r\n");
request_.append("Accept-Encoding: gzip,deflate\r\n");
request_.append("Accept-Language: en-us,fr\r\n");
request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n");
std::ostringstream ss;
ss << data.size();
request_.append("Content-Length: ");
request_.append(ss.str());
request_.append("\r\n\r\n");
request_.append(data);
// terminates with \r\n, then ends with an empty line
request_.append("\r\n\r\n");
socket_.Write(request_.c_str(), request_.size());
LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str());
LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str());
return true;
return PostRequestWithPath(socket_.resource_path(), data);
}
bool UrlRequest::PostCertRequestInQueryString(const std::string& data) {
request_.assign("POST /");
request_.append(socket_.resource_path());
request_.append("&signedRequest=");
request_.append(data);
request_.append(" HTTP/1.1\r\n");
request_.append("User-Agent: Widevine CDM v1.0\r\n");
request_.append("Host: ");
request_.append(socket_.domain_name());
request_.append("\r\nAccept: */*");
request_.append("\r\nContent-Type: application/json");
request_.append("\r\nContent-Length: 0");
request_.append("\r\n"); // empty line to terminate header
request_.append("\r\n"); // terminates the request
std::string path = socket_.resource_path();
path.append("&signedRequest=");
path.append(data);
socket_.Write(request_.c_str(), request_.size());
LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str());
LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str());
return true;
return PostRequestWithPath(path, "");
}
} // namespace wvcdm

View File

@@ -13,29 +13,22 @@ namespace wvcdm {
// Only POST request method is implemented.
class UrlRequest {
public:
UrlRequest(const std::string& url, const std::string& port,
bool secure_connect, bool chunk_transfer_mode);
explicit UrlRequest(const std::string& url);
~UrlRequest();
void AppendChunkToUpload(const std::string& data);
void ConcatenateChunkedResponse(const std::string http_response,
std::string* modified_response);
int GetResponse(std::string* message);
int GetStatusCode(const std::string& response);
bool is_connected() const { return is_connected_; }
bool PostRequest(const std::string& data);
bool PostRequestChunk(const std::string& data);
bool PostCertRequestInQueryString(const std::string& data);
bool GetResponse(std::string* message);
static int GetStatusCode(const std::string& response);
private:
static const unsigned int kHttpBufferSize = 4096;
char buffer_[kHttpBufferSize];
bool chunk_transfer_mode_;
bool PostRequestWithPath(const std::string& path, const std::string& data);
bool is_connected_;
std::string port_;
std::string request_;
HttpSocket socket_;
std::string server_url_;
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
};