diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-21 14:10:52 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-21 14:10:52 +0000 |
commit | 0a5e34a5386fa254a3b133f0a5f7667f1f790f67 (patch) | |
tree | d1dd0e80264f900004c56ad5ce6b56efea0bec49 /rlz/lib | |
parent | b049c382ff842f546e2bcc261ce23438bc8764bf (diff) | |
download | chromium_src-0a5e34a5386fa254a3b133f0a5f7667f1f790f67.zip chromium_src-0a5e34a5386fa254a3b133f0a5f7667f1f790f67.tar.gz chromium_src-0a5e34a5386fa254a3b133f0a5f7667f1f790f67.tar.bz2 |
Check-in rlz code to src\rlz from http://code.google.com/p/rlz/.
Review URL: https://chromiumcodereview.appspot.com/10597002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@143381 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'rlz/lib')
-rw-r--r-- | rlz/lib/assert.cc | 15 | ||||
-rw-r--r-- | rlz/lib/assert.h | 53 | ||||
-rw-r--r-- | rlz/lib/crc32.h | 17 | ||||
-rw-r--r-- | rlz/lib/crc32_unittest.cc | 52 | ||||
-rw-r--r-- | rlz/lib/crc32_wrapper.cc | 36 | ||||
-rw-r--r-- | rlz/lib/crc8.cc | 90 | ||||
-rw-r--r-- | rlz/lib/crc8.h | 24 | ||||
-rw-r--r-- | rlz/lib/crc8_unittest.cc | 51 | ||||
-rw-r--r-- | rlz/lib/financial_ping.cc | 354 | ||||
-rw-r--r-- | rlz/lib/financial_ping.h | 64 | ||||
-rw-r--r-- | rlz/lib/financial_ping_test.cc | 293 | ||||
-rw-r--r-- | rlz/lib/lib_values.cc | 208 | ||||
-rw-r--r-- | rlz/lib/lib_values.h | 99 | ||||
-rw-r--r-- | rlz/lib/lib_values_unittest.cc | 61 | ||||
-rw-r--r-- | rlz/lib/machine_id.cc | 80 | ||||
-rw-r--r-- | rlz/lib/machine_id.h | 34 | ||||
-rw-r--r-- | rlz/lib/machine_id_unittest.cc | 20 | ||||
-rw-r--r-- | rlz/lib/rlz_enums.h | 132 | ||||
-rw-r--r-- | rlz/lib/rlz_lib.cc | 651 | ||||
-rw-r--r-- | rlz/lib/rlz_lib.h | 335 | ||||
-rw-r--r-- | rlz/lib/rlz_lib_clear.cc | 82 | ||||
-rw-r--r-- | rlz/lib/rlz_lib_test.cc | 824 | ||||
-rw-r--r-- | rlz/lib/rlz_value_store.h | 116 | ||||
-rw-r--r-- | rlz/lib/string_utils.cc | 92 | ||||
-rw-r--r-- | rlz/lib/string_utils.h | 26 | ||||
-rw-r--r-- | rlz/lib/string_utils_unittest.cc | 69 |
26 files changed, 3878 insertions, 0 deletions
diff --git a/rlz/lib/assert.cc b/rlz/lib/assert.cc new file mode 100644 index 0000000..d8ad935 --- /dev/null +++ b/rlz/lib/assert.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Macros specific to the RLZ library. + +#include "rlz/lib/assert.h" + +namespace rlz_lib { + +#ifdef MUTE_EXPECTED_ASSERTS +std::string expected_assertion_; +#endif + +} // namespace rlz_lib diff --git a/rlz/lib/assert.h b/rlz/lib/assert.h new file mode 100644 index 0000000..89e94e0 --- /dev/null +++ b/rlz/lib/assert.h @@ -0,0 +1,53 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Macros specific to the RLZ library. + +#ifndef RLZ_LIB_ASSERT_H_ +#define RLZ_LIB_ASSERT_H_ + +#include <string> +#include "base/logging.h" + +// An assertion macro. +// Can mute expected assertions in debug mode. + +#ifndef ASSERT_STRING + #ifndef MUTE_EXPECTED_ASSERTS + #define ASSERT_STRING(expr) LOG_IF(FATAL, false) << (expr) + #else + #define ASSERT_STRING(expr) \ + do { \ + std::string expr_string(expr); \ + if (rlz_lib::expected_assertion_ != expr_string) { \ + LOG_IF(FATAL, false) << (expr); \ + } \ + } while (0) + #endif +#endif + + +#ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(expr) LOG_IF(FATAL, !(expr)) << #expr + #else + #define VERIFY(expr) (void)(expr) + #endif +#endif + +namespace rlz_lib { + +#ifdef MUTE_EXPECTED_ASSERTS +extern std::string expected_assertion_; +#endif + +inline void SetExpectedAssertion(const char* s) { +#ifdef MUTE_EXPECTED_ASSERTS + expected_assertion_ = s; +#endif +} + +} // rlz_lib + +#endif // RLZ_LIB_ASSERT_H_ diff --git a/rlz/lib/crc32.h b/rlz/lib/crc32.h new file mode 100644 index 0000000..aded355 --- /dev/null +++ b/rlz/lib/crc32.h @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A wrapper around ZLib's CRC function. + +#ifndef RLZ_LIB_CRC32_H_ +#define RLZ_LIB_CRC32_H_ + +namespace rlz_lib { + +int Crc32(const unsigned char* buf, int length); +bool Crc32(const char* text, int* crc); + +} // namespace rlz_lib + +#endif // RLZ_LIB_CRC32_H_ diff --git a/rlz/lib/crc32_unittest.cc b/rlz/lib/crc32_unittest.cc new file mode 100644 index 0000000..9700058 --- /dev/null +++ b/rlz/lib/crc32_unittest.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A test for ZLib's checksum function. + +#include "rlz/lib/crc32.h" + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(Crc32Unittest, ByteTest) { + struct { + const char* data; + int len; + // Externally calculated at http://crc32-checksum.waraxe.us/ + int crc; + } kData[] = { + {"Hello" , 5, 0xF7D18982}, + {"Google" , 6, 0x62B0F067}, + {"" , 0, 0x0}, + {"One more string.", 16, 0x0CA14970}, + {NULL , 0, 0x0}, + }; + + for (int i = 0; kData[i].data; i++) + EXPECT_EQ(kData[i].crc, + rlz_lib::Crc32(reinterpret_cast<const unsigned char*>(kData[i].data), + kData[i].len)); +} + +TEST(Crc32Unittest, CharTest) { + struct { + const char* data; + // Externally calculated at http://crc32-checksum.waraxe.us/ + int crc; + } kData[] = { + {"Hello" , 0xF7D18982}, + {"Google" , 0x62B0F067}, + {"" , 0x0}, + {"One more string.", 0x0CA14970}, + {"Google\r\n" , 0x83A3E860}, + {NULL , 0x0}, + }; + + int crc; + for (int i = 0; kData[i].data; i++) { + EXPECT_TRUE(rlz_lib::Crc32(kData[i].data, &crc)); + EXPECT_EQ(kData[i].crc, crc); + } +} diff --git a/rlz/lib/crc32_wrapper.cc b/rlz/lib/crc32_wrapper.cc new file mode 100644 index 0000000..d763c26 --- /dev/null +++ b/rlz/lib/crc32_wrapper.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A wrapper around ZLib's CRC functions to put them in the rlz_lib namespace +// and use our types. + +#include "rlz/lib/assert.h" +#include "rlz/lib/crc32.h" +#include "rlz/lib/string_utils.h" +#include "third_party/zlib/zlib.h" + +namespace rlz_lib { + +int Crc32(const unsigned char* buf, int length) { + return crc32(0L, buf, length); +} + +bool Crc32(const char* text, int* crc) { + if (!crc) { + ASSERT_STRING("Crc32: crc is NULL."); + return false; + } + + *crc = 0; + for (int i = 0; text[i]; i++) { + if (!IsAscii(text[i])) + return false; + + *crc = crc32(*crc, reinterpret_cast<const unsigned char*>(text + i), 1); + } + + return true; +} + +} // namespace rlz_lib diff --git a/rlz/lib/crc8.cc b/rlz/lib/crc8.cc new file mode 100644 index 0000000..fe02eab --- /dev/null +++ b/rlz/lib/crc8.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "rlz/lib/crc8.h" + +namespace { + +// The CRC lookup table used for ATM HES (Polynomial = 0x07). +// These are 256 unique 8-bit values. +const unsigned char kCrcTable[256] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, + 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, + 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, + 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, + 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, + 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, + 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, + 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, + 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, + 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, + 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, + 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 +}; + +} // namespace anonymous + + +namespace rlz_lib { + +bool Crc8::Generate(const unsigned char *data, int length, + unsigned char* check_sum) { + if (!check_sum) + return false; + + *check_sum = 0; + if (!data) + return false; + + // The inital and final constants are as used in the ATM HEC. + static const unsigned char kInitial = 0x00; + static const unsigned char kFinal = 0x55; + unsigned char crc = kInitial; + for (int i = 0; i < length; ++i) { + crc = kCrcTable[(data[i] ^ crc) & 0xFFU]; + } + + *check_sum = crc ^ kFinal; + return true; +} + +bool Crc8::Verify(const unsigned char* data, int length, + unsigned char check_sum, bool* matches) { + if (!matches) + return false; + + *matches = false; + if (!data) + return false; + + unsigned char calculated_crc; + if (!Generate(data, length, &calculated_crc)) + return false; + + *matches = check_sum == calculated_crc; + + return true; +} + +} // namespace rlz_lib diff --git a/rlz/lib/crc8.h b/rlz/lib/crc8.h new file mode 100644 index 0000000..6c3c848 --- /dev/null +++ b/rlz/lib/crc8.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Crc8 utility functions. + +#ifndef RLZ_LIB_CRC8_H_ +#define RLZ_LIB_CRC8_H_ + +namespace rlz_lib { +// CRC-8 methods: +class Crc8 { + public: + static bool Generate(const unsigned char* data, + int length, + unsigned char* check_sum); + static bool Verify(const unsigned char* data, + int length, + unsigned char checksum, + bool * matches); +}; +}; // namespace rlz_lib + +#endif // RLZ_LIB_CRC8_H_ diff --git a/rlz/lib/crc8_unittest.cc b/rlz/lib/crc8_unittest.cc new file mode 100644 index 0000000..4889242 --- /dev/null +++ b/rlz/lib/crc8_unittest.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Uniitest for data encryption functions. + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "rlz/lib/crc8.h" + +TEST(Crc8Unittest, TestCrc8) { + struct Data { + char string[10]; + // Externally calculated checksums use + // http://www.zorc.breitbandkatze.de/crc.html + // with the ATM HEC paramters: + // CRC-8, Polynomial 0x07, Initial value 0x00, Final XOR value 0x55 + // (direct, don't reverse data byes, don't reverse CRC before final XOR) + unsigned char external_crc; + int random_byte; + unsigned char corrupt_value; + } data[] = { + {"Google", 0x01, 2, 0x53}, + {"GOOGLE", 0xA6, 4, 0x11}, + {"My CRC 8!", 0xDC, 0, 0x50}, + }; + + unsigned char* bytes; + unsigned char crc; + bool matches; + int length; + for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { + bytes = reinterpret_cast<unsigned char*>(data[i].string); + crc = 0; + matches = false; + length = strlen(data[i].string); + + // Calculate CRC and compare against external value. + rlz_lib::Crc8::Generate(bytes, length, &crc); + EXPECT_TRUE(crc == data[i].external_crc); + rlz_lib::Crc8::Verify(bytes, length, crc, &matches); + EXPECT_TRUE(matches); + + // Corrupt string and see if CRC still matches. + data[i].string[data[i].random_byte] = data[i].corrupt_value; + rlz_lib::Crc8::Verify(bytes, length, crc, &matches); + EXPECT_FALSE(matches); + } +} diff --git a/rlz/lib/financial_ping.cc b/rlz/lib/financial_ping.cc new file mode 100644 index 0000000..aad3db3 --- /dev/null +++ b/rlz/lib/financial_ping.cc @@ -0,0 +1,354 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Library functions related to the Financial Server ping. + +#include "rlz/lib/financial_ping.h" + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "rlz/lib/assert.h" +#include "rlz/lib/lib_values.h" +#include "rlz/lib/machine_id.h" +#include "rlz/lib/rlz_lib.h" +#include "rlz/lib/rlz_value_store.h" +#include "rlz/lib/string_utils.h" + +#if !defined(OS_WIN) +#include "base/time.h" +#endif + +#if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET) + +#include <windows.h> +#include <wininet.h> + +namespace { + +class InternetHandle { + public: + InternetHandle(HINTERNET handle) { handle_ = handle; } + ~InternetHandle() { if (handle_) InternetCloseHandle(handle_); } + operator HINTERNET() const { return handle_; } + bool operator!() const { return (handle_ == NULL); } + + private: + HINTERNET handle_; +}; + +} // namespace + +#else + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/time.h" +#include "googleurl/src/gurl.h" +#include "net/base/load_flags.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" + +#endif + +namespace { + +// Returns the time relative to a fixed point in the past in multiples of +// 100 ns stepts. The point in the past is arbitrary but can't change, as the +// result of this value is stored on disk. +int64 GetSystemTimeAsInt64() { +#if defined(OS_WIN) + FILETIME now_as_file_time; + // Relative to Jan 1, 1601 (UTC). + GetSystemTimeAsFileTime(&now_as_file_time); + + LARGE_INTEGER integer; + integer.HighPart = now_as_file_time.dwHighDateTime; + integer.LowPart = now_as_file_time.dwLowDateTime; + return integer.QuadPart; +#else + // Seconds since epoch (Jan 1, 1970). + double now_seconds = base::Time::Now().ToDoubleT(); + return static_cast<int64>(now_seconds * 1000 * 1000 * 10); +#endif +} + +} // namespace + + +namespace rlz_lib { + +bool FinancialPing::FormRequest(Product product, + const AccessPoint* access_points, const char* product_signature, + const char* product_brand, const char* product_id, + const char* product_lang, bool exclude_machine_id, + std::string* request) { + if (!request) { + ASSERT_STRING("FinancialPing::FormRequest: request is NULL"); + return false; + } + + request->clear(); + + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kReadAccess)) + return false; + + if (!access_points) { + ASSERT_STRING("FinancialPing::FormRequest: access_points is NULL"); + return false; + } + + if (!product_signature) { + ASSERT_STRING("FinancialPing::FormRequest: product_signature is NULL"); + return false; + } + + if (!SupplementaryBranding::GetBrand().empty()) { + if (SupplementaryBranding::GetBrand() != product_brand) { + ASSERT_STRING("FinancialPing::FormRequest: supplementary branding bad"); + return false; + } + } + + base::StringAppendF(request, "%s?", kFinancialPingPath); + + // Add the signature, brand, product id and language. + base::StringAppendF(request, "%s=%s", kProductSignatureCgiVariable, + product_signature); + if (product_brand) + base::StringAppendF(request, "&%s=%s", kProductBrandCgiVariable, + product_brand); + + if (product_id) + base::StringAppendF(request, "&%s=%s", kProductIdCgiVariable, product_id); + + if (product_lang) + base::StringAppendF(request, "&%s=%s", kProductLanguageCgiVariable, + product_lang); + + // Add the product events. + char cgi[kMaxCgiLength + 1]; + cgi[0] = 0; + bool has_events = GetProductEventsAsCgi(product, cgi, arraysize(cgi)); + if (has_events) + base::StringAppendF(request, "&%s", cgi); + + // If we don't have any events, we should ping all the AP's on the system + // that we know about and have a current RLZ value, even if they are not + // used by this product. + AccessPoint all_points[LAST_ACCESS_POINT]; + if (!has_events) { + char rlz[kMaxRlzLength + 1]; + int idx = 0; + for (int ap = NO_ACCESS_POINT + 1; ap < LAST_ACCESS_POINT; ap++) { + rlz[0] = 0; + AccessPoint point = static_cast<AccessPoint>(ap); + if (GetAccessPointRlz(point, rlz, arraysize(rlz)) && + rlz[0] != '\0') + all_points[idx++] = point; + } + all_points[idx] = NO_ACCESS_POINT; + } + + // Add the RLZ's and the DCC if needed. This is the same as get PingParams. + // This will also include the RLZ Exchange Protocol CGI Argument. + cgi[0] = 0; + if (GetPingParams(product, has_events ? access_points : all_points, + cgi, arraysize(cgi))) + base::StringAppendF(request, "&%s", cgi); + + if (has_events && !exclude_machine_id) { + std::string machine_id; + if (GetMachineId(&machine_id)) { + base::StringAppendF(request, "&%s=%s", kMachineIdCgiVariable, + machine_id.c_str()); + } + } + + return true; +} + +#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) +// The URLRequestContextGetter used by FinancialPing::PingServer(). +net::URLRequestContextGetter* g_context; + +bool FinancialPing::SetURLRequestContext( + net::URLRequestContextGetter* context) { + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store) + return false; + + g_context = context; + return true; +} + +namespace { + +class FinancialPingUrlFetcherDelegate : public net::URLFetcherDelegate { + public: + FinancialPingUrlFetcherDelegate(MessageLoop* loop) : loop_(loop) { } + virtual void OnURLFetchComplete(const net::URLFetcher* source); + private: + MessageLoop* loop_; +}; + +void FinancialPingUrlFetcherDelegate::OnURLFetchComplete( + const net::URLFetcher* source) { + loop_->Quit(); +} + +} // namespace + +#endif + +bool FinancialPing::PingServer(const char* request, std::string* response) { + if (!response) + return false; + + response->clear(); + +#if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET) + // Initialize WinInet. + InternetHandle inet_handle = InternetOpenA(kFinancialPingUserAgent, + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, NULL, 0); + if (!inet_handle) + return false; + + // Open network connection. + InternetHandle connection_handle = InternetConnectA(inet_handle, + kFinancialServer, kFinancialPort, "", "", INTERNET_SERVICE_HTTP, + INTERNET_FLAG_NO_CACHE_WRITE, 0); + if (!connection_handle) + return false; + + // Prepare the HTTP request. + InternetHandle http_handle = HttpOpenRequestA(connection_handle, + "GET", request, NULL, NULL, kFinancialPingResponseObjects, + INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES, NULL); + if (!http_handle) + return false; + + // Timeouts are probably: + // INTERNET_OPTION_SEND_TIMEOUT, INTERNET_OPTION_RECEIVE_TIMEOUT + + // Send the HTTP request. Note: Fails if user is working in off-line mode. + if (!HttpSendRequest(http_handle, NULL, 0, NULL, 0)) + return false; + + // Check the response status. + DWORD status; + DWORD status_size = sizeof(status); + if (!HttpQueryInfo(http_handle, HTTP_QUERY_STATUS_CODE | + HTTP_QUERY_FLAG_NUMBER, &status, &status_size, NULL) || + 200 != status) + return false; + + // Get the response text. + scoped_array<char> buffer(new char[kMaxPingResponseLength]); + if (buffer.get() == NULL) + return false; + + DWORD bytes_read = 0; + while (InternetReadFile(http_handle, buffer.get(), kMaxPingResponseLength, + &bytes_read) && bytes_read > 0) { + response->append(buffer.get(), bytes_read); + bytes_read = 0; + }; + + return true; +#else + // Run a blocking event loop to match the win inet implementation. + MessageLoop loop; + FinancialPingUrlFetcherDelegate delegate(&loop); + + std::string url = base::StringPrintf("http://%s:%d%s", + kFinancialServer, kFinancialPort, + request); + + scoped_ptr<net::URLFetcher> fetcher(net::URLFetcher::Create( + GURL(url), net::URLFetcher::GET, &delegate)); + + fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE | + net::LOAD_DO_NOT_SEND_AUTH_DATA | + net::LOAD_DO_NOT_PROMPT_FOR_LOGIN | + net::LOAD_DO_NOT_SEND_COOKIES | + net::LOAD_DO_NOT_SAVE_COOKIES); + + // Ensure rlz_lib::SetURLRequestContext() has been called before sending + // pings. + CHECK(g_context); + fetcher->SetRequestContext(g_context); + + const base::TimeDelta kTimeout = base::TimeDelta::FromMinutes(5); + loop.PostTask( + FROM_HERE, + base::Bind(&net::URLFetcher::Start, base::Unretained(fetcher.get()))); + loop.PostNonNestableDelayedTask( + FROM_HERE, MessageLoop::QuitClosure(), kTimeout); + + loop.Run(); + + if (fetcher->GetResponseCode() != 200) + return false; + + return fetcher->GetResponseAsString(response); +#endif +} + +bool FinancialPing::IsPingTime(Product product, bool no_delay) { + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kReadAccess)) + return false; + + int64 last_ping = 0; + if (!store->ReadPingTime(product, &last_ping)) + return true; + + uint64 now = GetSystemTimeAsInt64(); + int64 interval = now - last_ping; + + // If interval is negative, clock was probably reset. So ping. + if (interval < 0) + return true; + + // Check if this product has any unreported events. + char cgi[kMaxCgiLength + 1]; + cgi[0] = 0; + bool has_events = GetProductEventsAsCgi(product, cgi, arraysize(cgi)); + if (no_delay && has_events) + return true; + + return interval >= (has_events ? kEventsPingInterval : kNoEventsPingInterval); +} + + +bool FinancialPing::UpdateLastPingTime(Product product) { + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kWriteAccess)) + return false; + + uint64 now = GetSystemTimeAsInt64(); + return store->WritePingTime(product, now); +} + + +bool FinancialPing::ClearLastPingTime(Product product) { + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kWriteAccess)) + return false; + return store->ClearPingTime(product); +} + +} // namespace diff --git a/rlz/lib/financial_ping.h b/rlz/lib/financial_ping.h new file mode 100644 index 0000000..081ecba --- /dev/null +++ b/rlz/lib/financial_ping.h @@ -0,0 +1,64 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Library functions related to the Financial Server ping. + +#ifndef RLZ_LIB_FINANCIAL_PING_H_ +#define RLZ_LIB_FINANCIAL_PING_H_ + +#include <string> +#include "rlz/lib/rlz_enums.h" + +#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) +namespace net { +class URLRequestContextGetter; +} // namespace net +#endif + +namespace rlz_lib { + +class FinancialPing { + public: + // Form the HTTP request to send to the PSO server. + // Will look something like: + // /pso/ping?as=swg&brand=GGLD&id=124&hl=en& + // events=I7S&rep=1&rlz=I7:val,W1:&dcc=dval + static bool FormRequest(Product product, const AccessPoint* access_points, + const char* product_signature, + const char* product_brand, const char* product_id, + const char* product_lang, bool exclude_machine_id, + std::string* request); + + // Returns whether the time is right to send a ping. + // If no_delay is true, this should always ping if there are events, + // or one week has passed since last_ping when there are no new events. + // If no_delay is false, this should ping if current time < last_ping time + // (case of time reset) or if one day has passed since last_ping and there + // are events, or one week has passed since last_ping when there are + // no new events. + static bool IsPingTime(Product product, bool no_delay); + + // Set the last ping time to be now. Writes to RlzValueStore. + static bool UpdateLastPingTime(Product product); + + // Clear the last ping time - should be called on uninstall. + // Writes to RlzValueStore. + static bool ClearLastPingTime(Product product); + + // Ping the financial server with request. Writes to RlzValueStore. + static bool PingServer(const char* request, std::string* response); + +#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) + static bool SetURLRequestContext(net::URLRequestContextGetter* context); +#endif + + private: + FinancialPing() {} + ~FinancialPing() {} +}; + +} // namespace rlz_lib + + +#endif // RLZ_LIB_FINANCIAL_PING_H_ diff --git a/rlz/lib/financial_ping_test.cc b/rlz/lib/financial_ping_test.cc new file mode 100644 index 0000000..c04cf46 --- /dev/null +++ b/rlz/lib/financial_ping_test.cc @@ -0,0 +1,293 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A test application for the FinancialPing class. +// +// These tests should not be executed on the build server: +// - They modify machine state (registry). +// +// These tests require write access to HKCU and HKLM. +// +// The "GGLA" brand is used to test the normal code flow of the code, and the +// "TEST" brand is used to test the supplementary brand code code flow. In one +// case below, the brand "GOOG" is used because the code wants to use a brand +// that is neither of the two mentioned above. + +#include "rlz/lib/financial_ping.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "rlz/lib/lib_values.h" +#include "rlz/lib/machine_id.h" +#include "rlz/lib/rlz_lib.h" +#include "rlz/lib/rlz_value_store.h" +#include "rlz/test/rlz_test_helpers.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "rlz/win/lib/machine_deal.h" +#else +#include "base/time.h" +#endif + +namespace { + +// Must match the implementation in file_time.cc. +int64 GetSystemTimeAsInt64() { +#if defined(OS_WIN) + FILETIME now_as_file_time; + GetSystemTimeAsFileTime(&now_as_file_time); + LARGE_INTEGER integer; + integer.HighPart = now_as_file_time.dwHighDateTime; + integer.LowPart = now_as_file_time.dwLowDateTime; + return integer.QuadPart; +#else + double now_seconds = base::Time::Now().ToDoubleT(); + return static_cast<int64>(now_seconds * 1000 * 1000 * 10); +#endif +} + +// Ping times in 100-nanosecond intervals. +const int64 k1MinuteInterval = 60LL * 10000000LL; // 1 minute + +} // namespace anonymous + +class FinancialPingTest : public RlzLibTestBase { +}; + +TEST_F(FinancialPingTest, FormRequest) { + std::string brand_string = rlz_lib::SupplementaryBranding::GetBrand(); + const char* brand = brand_string.empty() ? "GGLA" : brand_string.c_str(); + +#if defined(OS_WIN) + EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value")); +#define DCC_PARAM "&dcc=dcc_value" +#else +#define DCC_PARAM "" +#endif + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, + "TbRlzValue")); + + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + + rlz_lib::AccessPoint points[] = + {rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT, + rlz_lib::NO_ACCESS_POINT}; + + std::string machine_id; + bool got_machine_id = rlz_lib::GetMachineId(&machine_id); + + std::string request; + EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER, + points, "swg", brand, NULL, "en", false, &request)); + std::string expected_response; + base::StringAppendF(&expected_response, + "/tools/pso/ping?as=swg&brand=%s&hl=en&" + "events=I7S,W1I&rep=2&rlz=T4:TbRlzValue" DCC_PARAM +, brand); + + if (got_machine_id) + base::StringAppendF(&expected_response, "&id=%s", machine_id.c_str()); + EXPECT_EQ(expected_response, request); + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "")); + EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER, + points, "swg", brand, "IdOk2", NULL, false, &request)); + expected_response.clear(); + base::StringAppendF(&expected_response, + "/tools/pso/ping?as=swg&brand=%s&pid=IdOk2&" + "events=I7S,W1I&rep=2&rlz=T4:" DCC_PARAM, brand); + + if (got_machine_id) + base::StringAppendF(&expected_response, "&id=%s", machine_id.c_str()); + EXPECT_EQ(expected_response, request); + + EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER, + points, "swg", brand, "IdOk", NULL, true, &request)); + expected_response.clear(); + base::StringAppendF(&expected_response, + "/tools/pso/ping?as=swg&brand=%s&pid=IdOk&" + "events=I7S,W1I&rep=2&rlz=T4:" DCC_PARAM, brand); + EXPECT_EQ(expected_response, request); + + EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER, + points, "swg", brand, NULL, NULL, true, &request)); + expected_response.clear(); + base::StringAppendF(&expected_response, + "/tools/pso/ping?as=swg&brand=%s&events=I7S,W1I&rep=2" + "&rlz=T4:" DCC_PARAM, brand); + EXPECT_EQ(expected_response, request); + + + // Clear all events. + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + + // Clear all RLZs. + char rlz[rlz_lib::kMaxRlzLength + 1]; + for (int ap = rlz_lib::NO_ACCESS_POINT + 1; + ap < rlz_lib::LAST_ACCESS_POINT; ap++) { + rlz[0] = 0; + rlz_lib::AccessPoint point = static_cast<rlz_lib::AccessPoint>(ap); + if (rlz_lib::GetAccessPointRlz(point, rlz, arraysize(rlz)) && rlz[0]) { + rlz_lib::SetAccessPointRlz(point, ""); + } + } + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, + "TbRlzValue")); + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::QUICK_SEARCH_BOX, + "QsbRlzValue")); + EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER, + points, "swg", brand, NULL, NULL, false, &request)); + expected_response.clear(); + base::StringAppendF(&expected_response, + "/tools/pso/ping?as=swg&brand=%s&rep=2&rlz=T4:TbRlzValue," + "Q1:QsbRlzValue" DCC_PARAM, brand); + EXPECT_STREQ(expected_response.c_str(), request.c_str()); + + if (!GetAccessPointRlz(rlz_lib::IE_HOME_PAGE, rlz, arraysize(rlz))) { + points[2] = rlz_lib::IE_HOME_PAGE; + EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER, + points, "swg", brand, "MyId", "en-US", true, &request)); + expected_response.clear(); + base::StringAppendF(&expected_response, + "/tools/pso/ping?as=swg&brand=%s&hl=en-US&pid=MyId&rep=2" + "&rlz=T4:TbRlzValue,Q1:QsbRlzValue" DCC_PARAM, brand); + EXPECT_STREQ(expected_response.c_str(), request.c_str()); + } +} + +TEST_F(FinancialPingTest, FormRequestBadBrand) { + rlz_lib::AccessPoint points[] = + {rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT, + rlz_lib::NO_ACCESS_POINT}; + + std::string request; + bool ok = rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER, + points, "swg", "GOOG", NULL, "en", false, &request); + EXPECT_EQ(rlz_lib::SupplementaryBranding::GetBrand().empty(), ok); +} + + +static void SetLastPingTime(int64 time, rlz_lib::Product product) { + rlz_lib::ScopedRlzValueStoreLock lock; + rlz_lib::RlzValueStore* store = lock.GetStore(); + ASSERT_TRUE(store); + ASSERT_TRUE(store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess)); + store->WritePingTime(product, time); +} + +TEST_F(FinancialPingTest, IsPingTime) { + int64 now = GetSystemTimeAsInt64(); + int64 last_ping = now - rlz_lib::kEventsPingInterval - k1MinuteInterval; + SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER); + + // No events, last ping just over a day ago. + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + + // Has events, last ping just over a day ago. + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + + // Has events, last ping just under a day ago. + last_ping = now - rlz_lib::kEventsPingInterval + k1MinuteInterval; + SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER); + EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + + // No events, last ping just under a week ago. + last_ping = now - rlz_lib::kNoEventsPingInterval + k1MinuteInterval; + SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER); + EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + + // No events, last ping just over a week ago. + last_ping = now - rlz_lib::kNoEventsPingInterval - k1MinuteInterval; + SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER); + EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + + // Last ping was in future (invalid). + last_ping = now + k1MinuteInterval; + SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER); + EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); +} + +TEST_F(FinancialPingTest, BrandingIsPingTime) { + // Don't run these tests if a supplementary brand is already in place. That + // way we can control the branding. + if (!rlz_lib::SupplementaryBranding::GetBrand().empty()) + return; + + int64 now = GetSystemTimeAsInt64(); + int64 last_ping = now - rlz_lib::kEventsPingInterval - k1MinuteInterval; + SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER); + + // Has events, last ping just over a day ago. + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + + { + rlz_lib::SupplementaryBranding branding("TEST"); + SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER); + + // Has events, last ping just over a day ago. + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + } + + last_ping = now - k1MinuteInterval; + SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER); + + EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + + { + rlz_lib::SupplementaryBranding branding("TEST"); + EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + } +} + +TEST_F(FinancialPingTest, ClearLastPingTime) { + int64 now = GetSystemTimeAsInt64(); + int64 last_ping = now - rlz_lib::kEventsPingInterval + k1MinuteInterval; + SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER); + + // Has events, last ping just under a day ago. + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); + + EXPECT_TRUE(rlz_lib::FinancialPing::ClearLastPingTime( + rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER, + false)); +} diff --git a/rlz/lib/lib_values.cc b/rlz/lib/lib_values.cc new file mode 100644 index 0000000..467e4568 --- /dev/null +++ b/rlz/lib/lib_values.cc @@ -0,0 +1,208 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Key and value names of the location of the RLZ shared state. + +#include "rlz/lib/lib_values.h" + +#include "base/stringprintf.h" +#include "rlz/lib/assert.h" + +namespace rlz_lib { + +// +// Ping information. +// + +// rep=2: includes the new stateful events. +const char kProtocolCgiArgument[] = "rep=2"; + +const char kEventsCgiVariable[] = "events"; +const char kStatefulEventsCgiVariable[] = "stateful-events"; +const char kEventsCgiSeparator = ','; + +const char kRlzCgiVariable[] = "rlz"; +const char kRlzCgiSeparator[] = ","; +const char kRlzCgiIndicator[] = ":"; + +const char kProductSignatureCgiVariable[] = "as"; +const char kProductBrandCgiVariable[] = "brand"; +const char kProductLanguageCgiVariable[] = "hl"; +const char kProductIdCgiVariable[] = "pid"; + +const char kDccCgiVariable[] = "dcc"; +const char kRlsCgiVariable[] = "rls"; +const char kMachineIdCgiVariable[] = "id"; +const char kSetDccResponseVariable[] = "set_dcc"; + +// +// Financial server information. +// + +const char kFinancialPingPath[] = "/tools/pso/ping"; +const char kFinancialServer[] = "clients1.google.com"; +const int kFinancialPort = 80; + +// Ping times in 100-nanosecond intervals. +const int64 kEventsPingInterval = 24LL * 3600LL * 10000000LL; // 1 day +const int64 kNoEventsPingInterval = kEventsPingInterval * 7LL; // 1 week + +const char kFinancialPingUserAgent[] = "Mozilla/4.0 (compatible; Win32)"; +const char* kFinancialPingResponseObjects[] = { "text/*", NULL }; + +// +// AccessPoint and Event names. +// +// + +const char* GetAccessPointName(AccessPoint point) { + switch (point) { + case NO_ACCESS_POINT: return ""; + case IE_DEFAULT_SEARCH: return "I7"; + case IE_HOME_PAGE: return "W1"; + case IETB_SEARCH_BOX: return "T4"; + case QUICK_SEARCH_BOX: return "Q1"; + case GD_DESKBAND: return "D1"; + case GD_SEARCH_GADGET: return "D2"; + case GD_WEB_SERVER: return "D3"; + case GD_OUTLOOK: return "D4"; + case CHROME_OMNIBOX: return "C1"; + case CHROME_HOME_PAGE: return "C2"; + case FFTB2_BOX: return "B2"; + case FFTB3_BOX: return "B3"; + case PINYIN_IME_BHO: return "N1"; + case IGOOGLE_WEBPAGE: return "G1"; + case MOBILE_IDLE_SCREEN_BLACKBERRY: return "H1"; + case MOBILE_IDLE_SCREEN_WINMOB: return "H2"; + case MOBILE_IDLE_SCREEN_SYMBIAN: return "H3"; + case FF_HOME_PAGE: return "R0"; + case FF_SEARCH_BOX: return "R1"; + case IE_BROWSED_PAGE: return "R2"; + case QSB_WIN_BOX: return "R3"; + case WEBAPPS_CALENDAR: return "R4"; + case WEBAPPS_DOCS: return "R5"; + case WEBAPPS_GMAIL: return "R6"; + case IETB_LINKDOCTOR: return "R7"; + case FFTB_LINKDOCTOR: return "R8"; + case IETB7_SEARCH_BOX: return "T7"; + case TB8_SEARCH_BOX: return "T8"; + case CHROME_FRAME: return "C3"; + case PARTNER_AP_1: return "V1"; + case PARTNER_AP_2: return "V2"; + case PARTNER_AP_3: return "V3"; + case PARTNER_AP_4: return "V4"; + case PARTNER_AP_5: return "V5"; + case UNDEFINED_AP_H: return "RH"; + case UNDEFINED_AP_I: return "RI"; + case UNDEFINED_AP_J: return "RJ"; + case UNDEFINED_AP_K: return "RK"; + case UNDEFINED_AP_L: return "RL"; + case UNDEFINED_AP_M: return "RM"; + case UNDEFINED_AP_N: return "RN"; + case UNDEFINED_AP_O: return "RO"; + case UNDEFINED_AP_P: return "RP"; + case UNDEFINED_AP_Q: return "RQ"; + case UNDEFINED_AP_R: return "RR"; + case UNDEFINED_AP_S: return "RS"; + case UNDEFINED_AP_T: return "RT"; + case UNDEFINED_AP_U: return "RU"; + case UNDEFINED_AP_V: return "RV"; + case UNDEFINED_AP_W: return "RW"; + case UNDEFINED_AP_X: return "RX"; + case UNDEFINED_AP_Y: return "RY"; + case UNDEFINED_AP_Z: return "RZ"; + case PACK_AP0: return "U0"; + case PACK_AP1: return "U1"; + case PACK_AP2: return "U2"; + case PACK_AP3: return "U3"; + case PACK_AP4: return "U4"; + case PACK_AP5: return "U5"; + case PACK_AP6: return "U6"; + case PACK_AP7: return "U7"; + case PACK_AP8: return "U8"; + case PACK_AP9: return "U9"; + case PACK_AP10: return "UA"; + case PACK_AP11: return "UB"; + case PACK_AP12: return "UC"; + case PACK_AP13: return "UD"; + case LAST_ACCESS_POINT: ; // Fall through. + } + + ASSERT_STRING("GetAccessPointName: Unknown Access Point"); + return NULL; +} + + +bool GetAccessPointFromName(const char* name, AccessPoint* point) { + if (!point) { + ASSERT_STRING("GetAccessPointFromName: point is NULL"); + return false; + } + *point = NO_ACCESS_POINT; + if (!name) + return false; + + for (int i = NO_ACCESS_POINT; i < LAST_ACCESS_POINT; i++) + if (strcmp(name, GetAccessPointName(static_cast<AccessPoint>(i))) == 0) { + *point = static_cast<AccessPoint>(i); + return true; + } + + return false; +} + + +const char* GetEventName(Event event) { + switch (event) { + case INVALID_EVENT: return ""; + case INSTALL: return "I"; + case SET_TO_GOOGLE: return "S"; + case FIRST_SEARCH: return "F"; + case REPORT_RLS: return "R"; + case ACTIVATE: return "A"; + case LAST_EVENT: ; // Fall through. + } + + ASSERT_STRING("GetPointName: Unknown Event"); + return NULL; +} + + +bool GetEventFromName(const char* name, Event* event) { + if (!event) { + ASSERT_STRING("GetEventFromName: event is NULL"); + return false; + } + *event = INVALID_EVENT; + if (!name) + return false; + + for (int i = INVALID_EVENT; i < LAST_EVENT; i++) + if (strcmp(name, GetEventName(static_cast<Event>(i))) == 0) { + *event = static_cast<Event>(i); + return true; + } + + return false; +} + +const char* GetProductName(Product product) { + switch (product) { + case IE_TOOLBAR: return "T"; + case TOOLBAR_NOTIFIER: return "P"; + case PACK: return "U"; + case DESKTOP: return "D"; + case CHROME: return "C"; + case FF_TOOLBAR: return "B"; + case QSB_WIN: return "K"; + case WEBAPPS: return "W"; + case PINYIN_IME: return "N"; + case PARTNER: return "V"; + } + + ASSERT_STRING("GetProductName: Unknown Product"); + return ""; +} + +} // namespace rlz_lib diff --git a/rlz/lib/lib_values.h b/rlz/lib/lib_values.h new file mode 100644 index 0000000..05fbc6b --- /dev/null +++ b/rlz/lib/lib_values.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Key and value names of the location of the RLZ shared state. + +#ifndef RLZ_LIB_LIB_VALUES_H_ +#define RLZ_LIB_LIB_VALUES_H_ + +#include "base/basictypes.h" +#include "rlz/lib/rlz_enums.h" + +namespace rlz_lib { + +// +// Ping CGI arguments: +// +// Events are reported as (without spaces): +// kEventsCgiVariable = <AccessPoint1><Event1> kEventsCgiSeparator <P2><E2>... +// +// Event responses from the server look like: +// kEventsCgiVariable : <AccessPoint1><Event1> kEventsCgiSeparator <P2><E2>... +// +// RLZ's are reported as (without spaces): +// kRlzCgiVariable = <AccessPoint> <kRlzCgiIndicator> <RLZ value> +// <kRlzCgiSeparator> <AP2><Indicator><V2><Separator> .... +// +// RLZ responses from the server look like (without spaces): +// kRlzCgiVariable<Access Point> : <RLZ value> +// +// DCC if reported should look like (without spaces): +// kDccCgiVariable = <DCC Value> +// +// RLS if reported should look like (without spaces): +// kRlsCgiVariable = <RLS Value> +// +// Machine ID if reported should look like (without spaces): +// kMachineIdCgiVariable = <Machine ID Value> +// +// A server response setting / confirming the DCC will look like (no spaces): +// kDccCgiVariable : <DCC Value> +// +// Each ping to the server must also contain kProtocolCgiArgument as well. +// +// Pings may also contain (but not necessarily controlled by this Lib): +// - The product signature: kProductSignatureCgiVariable = <signature> +// - The product brand: kProductBrandCgiVariable = <brand> +// - The product installation ID: kProductIdCgiVariable = <id> +extern const char kEventsCgiVariable[]; +extern const char kStatefulEventsCgiVariable[]; +extern const char kEventsCgiSeparator; + +extern const char kDccCgiVariable[]; +extern const char kProtocolCgiArgument[]; + +extern const char kProductSignatureCgiVariable[]; +extern const char kProductBrandCgiVariable[]; +extern const char kProductLanguageCgiVariable[]; +extern const char kProductIdCgiVariable[]; + +extern const char kRlzCgiVariable[]; +extern const char kRlzCgiSeparator[]; +extern const char kRlzCgiIndicator[]; + +extern const char kRlsCgiVariable[]; +extern const char kMachineIdCgiVariable[]; +extern const char kSetDccResponseVariable[]; + +// +// Financial ping server information. +// + +extern const char kFinancialPingPath[]; +extern const char kFinancialServer[]; + +extern const int kFinancialPort; + +extern const int64 kEventsPingInterval; +extern const int64 kNoEventsPingInterval; + +extern const char kFinancialPingUserAgent[]; +extern const char* kFinancialPingResponseObjects[]; + +// +// The names for AccessPoints and Events that we use MUST be the same +// as those used/understood by the server. +// +const char* GetAccessPointName(AccessPoint point); +bool GetAccessPointFromName(const char* name, AccessPoint* point); + +const char* GetEventName(Event event); +bool GetEventFromName(const char* name, Event* event); + +// The names for products are used only client-side. +const char* GetProductName(Product product); + +} // namespace rlz_lib + +#endif // RLZ_LIB_LIB_VALUES_H_ diff --git a/rlz/lib/lib_values_unittest.cc b/rlz/lib/lib_values_unittest.cc new file mode 100644 index 0000000..7e8fa83 --- /dev/null +++ b/rlz/lib/lib_values_unittest.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "rlz/lib/lib_values.h" + +#include "base/logging.h" +#include "rlz/lib/assert.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(LibValuesUnittest, GetAccessPointFromName) { + rlz_lib::SetExpectedAssertion("GetAccessPointFromName: point is NULL"); + EXPECT_FALSE(rlz_lib::GetAccessPointFromName("", NULL)); + rlz_lib::SetExpectedAssertion(""); + + rlz_lib::AccessPoint point; + EXPECT_FALSE(rlz_lib::GetAccessPointFromName(NULL, &point)); + EXPECT_EQ(rlz_lib::NO_ACCESS_POINT, point); + + EXPECT_TRUE(rlz_lib::GetAccessPointFromName("", &point)); + EXPECT_EQ(rlz_lib::NO_ACCESS_POINT, point); + + EXPECT_FALSE(rlz_lib::GetAccessPointFromName("i1", &point)); + EXPECT_EQ(rlz_lib::NO_ACCESS_POINT, point); + + EXPECT_TRUE(rlz_lib::GetAccessPointFromName("I7", &point)); + EXPECT_EQ(rlz_lib::IE_DEFAULT_SEARCH, point); + + EXPECT_TRUE(rlz_lib::GetAccessPointFromName("T4", &point)); + EXPECT_EQ(rlz_lib::IETB_SEARCH_BOX, point); + + EXPECT_FALSE(rlz_lib::GetAccessPointFromName("T4 ", &point)); + EXPECT_EQ(rlz_lib::NO_ACCESS_POINT, point); +} + + +TEST(LibValuesUnittest, GetEventFromName) { + rlz_lib::SetExpectedAssertion("GetEventFromName: event is NULL"); + EXPECT_FALSE(rlz_lib::GetEventFromName("", NULL)); + rlz_lib::SetExpectedAssertion(""); + + rlz_lib::Event event; + EXPECT_FALSE(rlz_lib::GetEventFromName(NULL, &event)); + EXPECT_EQ(rlz_lib::INVALID_EVENT, event); + + EXPECT_TRUE(rlz_lib::GetEventFromName("", &event)); + EXPECT_EQ(rlz_lib::INVALID_EVENT, event); + + EXPECT_FALSE(rlz_lib::GetEventFromName("i1", &event)); + EXPECT_EQ(rlz_lib::INVALID_EVENT, event); + + EXPECT_TRUE(rlz_lib::GetEventFromName("I", &event)); + EXPECT_EQ(rlz_lib::INSTALL, event); + + EXPECT_TRUE(rlz_lib::GetEventFromName("F", &event)); + EXPECT_EQ(rlz_lib::FIRST_SEARCH, event); + + EXPECT_FALSE(rlz_lib::GetEventFromName("F ", &event)); + EXPECT_EQ(rlz_lib::INVALID_EVENT, event); +} diff --git a/rlz/lib/machine_id.cc b/rlz/lib/machine_id.cc new file mode 100644 index 0000000..48d05b9 --- /dev/null +++ b/rlz/lib/machine_id.cc @@ -0,0 +1,80 @@ +#include "rlz/lib/machine_id.h" + +#include "base/sha1.h" +#include "rlz/lib/assert.h" +#include "rlz/lib/crc8.h" +#include "rlz/lib/string_utils.h" + +namespace rlz_lib { + +bool GetMachineId(std::string* machine_id) { + if (!machine_id) + return false; + + static std::string calculated_id; + static bool calculated = false; + if (calculated) { + *machine_id = calculated_id; + return true; + } + + string16 sid_string; + int volume_id; + if (!GetRawMachineId(&sid_string, &volume_id)) + return false; + + if (!testing::GetMachineIdImpl(sid_string, volume_id, machine_id)) + return false; + + calculated = true; + calculated_id = *machine_id; + return true; +} + +namespace testing { + +bool GetMachineIdImpl(const string16& sid_string, + int volume_id, + std::string* machine_id) { + machine_id->clear(); + + // The ID should be the SID hash + the Hard Drive SNo. + checksum byte. + static const int kSizeWithoutChecksum = base::kSHA1Length + sizeof(int); + std::basic_string<unsigned char> id_binary(kSizeWithoutChecksum + 1, 0); + + if (!sid_string.empty()) { + // In order to be compatible with the old version of RLZ, the hash of the + // SID must be done with all the original bytes from the unicode string. + // However, the chromebase SHA1 hash function takes only an std::string as + // input, so the unicode string needs to be converted to std::string + // "as is". + size_t byte_count = sid_string.size() * sizeof(string16::value_type); + const char* buffer = reinterpret_cast<const char*>(sid_string.c_str()); + std::string sid_string_buffer(buffer, byte_count); + + // Note that digest can have embedded nulls. + std::string digest(base::SHA1HashString(sid_string_buffer)); + VERIFY(digest.size() == base::kSHA1Length); + std::copy(digest.begin(), digest.end(), id_binary.begin()); + } + + // Convert from int to binary (makes big-endian). + for (size_t i = 0; i < sizeof(int); i++) { + int shift_bits = 8 * (sizeof(int) - i - 1); + id_binary[base::kSHA1Length + i] = static_cast<unsigned char>( + (volume_id >> shift_bits) & 0xFF); + } + + // Append the checksum byte. + if (!sid_string.empty() || (0 != volume_id)) + rlz_lib::Crc8::Generate(id_binary.c_str(), + kSizeWithoutChecksum, + &id_binary[kSizeWithoutChecksum]); + + return rlz_lib::BytesToString( + id_binary.c_str(), kSizeWithoutChecksum + 1, machine_id); +} + +} // namespace testing + +} // namespace rlz_lib diff --git a/rlz/lib/machine_id.h b/rlz/lib/machine_id.h new file mode 100644 index 0000000..3ec4888 --- /dev/null +++ b/rlz/lib/machine_id.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RLZ_LIB_MACHINE_ID_H_ +#define RLZ_LIB_MACHINE_ID_H_ + +#include "base/string16.h" + +#include <string> + +namespace rlz_lib { + +// Gets the unique ID for the machine used for RLZ tracking purposes. On +// Windows, this ID is derived from the Windows machine SID, and is the string +// representation of a 20 byte hash + 4 bytes volum id + a 1 byte checksum. +// Included in financial pings with events, unless explicitly forbidden by the +// calling application. +bool GetMachineId(std::string* machine_id); + +// Retrieves a raw machine identifier string and a machine-specific +// 4 byte value. GetMachineId() will SHA1 |data|, append |more_data|, compute +// the Crc8 of that, and return a hex-encoded string of that data. +bool GetRawMachineId(string16* data, int* more_data); + +namespace testing { +bool GetMachineIdImpl(const string16& sid_string, + int volume_id, + std::string* machine_id); +} // namespace testing + +} // namespace rlz_lib + +#endif // RLZ_LIB_MACHINE_ID_H_ diff --git a/rlz/lib/machine_id_unittest.cc b/rlz/lib/machine_id_unittest.cc new file mode 100644 index 0000000..cecf529 --- /dev/null +++ b/rlz/lib/machine_id_unittest.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "rlz/lib/machine_id.h" + +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "rlz/test/rlz_test_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +// This test will fail if the behavior of GetMachineId changes. +TEST(MachineDealCodeTestMachineId, MachineId) { + string16 computer_sid(UTF8ToUTF16( + "S-1-5-21-2345599882-2448789067-1921365677")); + std::string id; + rlz_lib::testing::GetMachineIdImpl(computer_sid, 2651229008, &id); + EXPECT_STREQ("A341BA986A7E86840688977FCF20C86E253F00919E068B50F8", + id.c_str()); +} diff --git a/rlz/lib/rlz_enums.h b/rlz/lib/rlz_enums.h new file mode 100644 index 0000000..4fc60ff --- /dev/null +++ b/rlz/lib/rlz_enums.h @@ -0,0 +1,132 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RLZ_LIB_RLZ_ENUMS_H_ +#define RLZ_LIB_RLZ_ENUMS_H_ + +namespace rlz_lib { + +// An Access Point offers a way to search using Google. +enum AccessPoint { + NO_ACCESS_POINT = 0, + + // Access points on Windows PCs. + IE_DEFAULT_SEARCH, // The IE7+ chrome search box next to the address bar. + IE_HOME_PAGE, // Search box on IE 5+ primary home page when Google. + IETB_SEARCH_BOX, // IE Toolbar v4+ search box. + QUICK_SEARCH_BOX, // Search box brought up by ctrl-ctrl key sequence, + // distributed as a part of Google Desktop + GD_DESKBAND, // Search box in deskbar when GD in deskbar mode. + GD_SEARCH_GADGET, // Search gadget when GD in sidebar mode. + GD_WEB_SERVER, // Boxes in web pages shown by local GD web server. + GD_OUTLOOK, // Search box installed within outlook by GD. + CHROME_OMNIBOX, // Chrome searches through the address bar omnibox. + CHROME_HOME_PAGE, // Chrome searches through Google as home page. + FFTB2_BOX, // Firefox Toolbar v2 Search Box. + FFTB3_BOX, // Firefox Toolbar v3+ Search Box. + PINYIN_IME_BHO, // Goopy Input Method Editor BHO (Pinyin). + IGOOGLE_WEBPAGE, // Searches on iGoogle through partner deals. + + // Mobile idle screen search for different platforms. + MOBILE_IDLE_SCREEN_BLACKBERRY, + MOBILE_IDLE_SCREEN_WINMOB, + MOBILE_IDLE_SCREEN_SYMBIAN, + + FF_HOME_PAGE, // Firefox home page when set to Google. + FF_SEARCH_BOX, // Firefox search box when set to Google. + IE_BROWSED_PAGE, // Search made in IE through user action (no product). + QSB_WIN_BOX, // Search box brought up by ctrl+space by default, + // distributed by toolbar and separate from the GD QSB + WEBAPPS_CALENDAR, // Webapps use of calendar. + WEBAPPS_DOCS, // Webapps use of writely. + WEBAPPS_GMAIL, // Webapps use of Gmail. + + IETB_LINKDOCTOR, // Linkdoctor of IE Toolbar + FFTB_LINKDOCTOR, // Linkdoctor of FF Toolbar + IETB7_SEARCH_BOX, // IE Toolbar search box. + TB8_SEARCH_BOX, // IE/FF Toolbar search box. + CHROME_FRAME, // Chrome Frame. + + // Partner access points. + PARTNER_AP_1, + PARTNER_AP_2, + PARTNER_AP_3, + PARTNER_AP_4, + PARTNER_AP_5, + + // Unclaimed access points - should be used first before creating new APs. + // Please also make sure you re-name the enum before using an unclaimed value; + // this acts as a check to ensure we don't have collisions. + UNDEFINED_AP_H, + UNDEFINED_AP_I, + UNDEFINED_AP_J, + UNDEFINED_AP_K, + UNDEFINED_AP_L, + UNDEFINED_AP_M, + UNDEFINED_AP_N, + UNDEFINED_AP_O, + UNDEFINED_AP_P, + UNDEFINED_AP_Q, + UNDEFINED_AP_R, + UNDEFINED_AP_S, + UNDEFINED_AP_T, + UNDEFINED_AP_U, + UNDEFINED_AP_V, + UNDEFINED_AP_W, + UNDEFINED_AP_X, + UNDEFINED_AP_Y, + UNDEFINED_AP_Z, + + PACK_AP0, + PACK_AP1, + PACK_AP2, + PACK_AP3, + PACK_AP4, + PACK_AP5, + PACK_AP6, + PACK_AP7, + PACK_AP8, + PACK_AP9, + PACK_AP10, + PACK_AP11, + PACK_AP12, + PACK_AP13, + + // New Access Points should be added here without changing existing enums, + // (i.e. before LAST_ACCESS_POINT) + LAST_ACCESS_POINT +}; + +// A product is an entity which wants to gets credit for setting +// an Access Point. +enum Product { + IE_TOOLBAR = 1, + TOOLBAR_NOTIFIER, + PACK, + DESKTOP, + CHROME, + FF_TOOLBAR, + QSB_WIN, + WEBAPPS, + PINYIN_IME, + PARTNER + // New Products should be added here without changing existing enums. +}; + +// Events that note Product and Access Point modifications. +enum Event { + INVALID_EVENT = 0, + INSTALL = 1, // Access Point added to the system. + SET_TO_GOOGLE, // Point set from non-Google provider to Google. + FIRST_SEARCH, // First search from point since INSTALL + REPORT_RLS, // Report old system "RLS" financial value for this point. + // New Events should be added here without changing existing enums, + // before LAST_EVENT. + ACTIVATE, // Product being used for a period of time. + LAST_EVENT +}; + +} // namespace rlz_lib + +#endif // RLZ_LIB_RLZ_ENUMS_H_ diff --git a/rlz/lib/rlz_lib.cc b/rlz/lib/rlz_lib.cc new file mode 100644 index 0000000..a613876 --- /dev/null +++ b/rlz/lib/rlz_lib.cc @@ -0,0 +1,651 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A library to manage RLZ information for access-points shared +// across different client applications. + +#include "rlz/lib/rlz_lib.h" + +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "rlz/lib/assert.h" +#include "rlz/lib/crc32.h" +#include "rlz/lib/financial_ping.h" +#include "rlz/lib/lib_values.h" +#include "rlz/lib/rlz_value_store.h" +#include "rlz/lib/string_utils.h" + +namespace { + +// Event information returned from ping response. +struct ReturnedEvent { + rlz_lib::AccessPoint access_point; + rlz_lib::Event event_type; +}; + +// Helper functions + +bool IsAccessPointSupported(rlz_lib::AccessPoint point) { + switch (point) { + case rlz_lib::NO_ACCESS_POINT: + case rlz_lib::LAST_ACCESS_POINT: + + case rlz_lib::MOBILE_IDLE_SCREEN_BLACKBERRY: + case rlz_lib::MOBILE_IDLE_SCREEN_WINMOB: + case rlz_lib::MOBILE_IDLE_SCREEN_SYMBIAN: + // These AP's are never available on Windows PCs. + return false; + + case rlz_lib::IE_DEFAULT_SEARCH: + case rlz_lib::IE_HOME_PAGE: + case rlz_lib::IETB_SEARCH_BOX: + case rlz_lib::QUICK_SEARCH_BOX: + case rlz_lib::GD_DESKBAND: + case rlz_lib::GD_SEARCH_GADGET: + case rlz_lib::GD_WEB_SERVER: + case rlz_lib::GD_OUTLOOK: + case rlz_lib::CHROME_OMNIBOX: + case rlz_lib::CHROME_HOME_PAGE: + // TODO: Figure out when these settings are set to Google. + + default: + return true; + } +} + +// Current RLZ can only use [a-zA-Z0-9_\-] +// We will be more liberal and allow some additional chars, but not url meta +// chars. +bool IsGoodRlzChar(const char ch) { + if (IsAsciiAlpha(ch) || IsAsciiDigit(ch)) + return true; + + switch (ch) { + case '_': + case '-': + case '!': + case '@': + case '$': + case '*': + case '(': + case ')': + case ';': + case '.': + case '<': + case '>': + return true; + } + + return false; +} + +// This function will remove bad rlz chars and also limit the max rlz to some +// reasonable size. It also assumes that normalized_rlz is at least +// kMaxRlzLength+1 long. +void NormalizeRlz(const char* raw_rlz, char* normalized_rlz) { + int index = 0; + for (; raw_rlz[index] != 0 && index < rlz_lib::kMaxRlzLength; ++index) { + char current = raw_rlz[index]; + if (IsGoodRlzChar(current)) { + normalized_rlz[index] = current; + } else { + normalized_rlz[index] = '.'; + } + } + + normalized_rlz[index] = 0; +} + +void GetEventsFromResponseString( + const std::string& response_line, + const std::string& field_header, + std::vector<ReturnedEvent>* event_array) { + // Get the string of events. + std::string events = response_line.substr(field_header.size()); + TrimWhitespaceASCII(events, TRIM_LEADING, &events); + + int events_length = events.find_first_of("\r\n "); + if (events_length < 0) + events_length = events.size(); + events = events.substr(0, events_length); + + // Break this up into individual events + int event_end_index = -1; + do { + int event_begin = event_end_index + 1; + event_end_index = events.find(rlz_lib::kEventsCgiSeparator, event_begin); + int event_end = event_end_index; + if (event_end < 0) + event_end = events_length; + + std::string event_string = events.substr(event_begin, + event_end - event_begin); + if (event_string.size() != 3) // 3 = 2(AP) + 1(E) + continue; + + rlz_lib::AccessPoint point = rlz_lib::NO_ACCESS_POINT; + rlz_lib::Event event = rlz_lib::INVALID_EVENT; + if (!GetAccessPointFromName(event_string.substr(0, 2).c_str(), &point) || + point == rlz_lib::NO_ACCESS_POINT) { + continue; + } + + if (!GetEventFromName(event_string.substr(event_string.size() - 1).c_str(), + &event) || event == rlz_lib::INVALID_EVENT) { + continue; + } + + ReturnedEvent current_event = {point, event}; + event_array->push_back(current_event); + } while (event_end_index >= 0); +} + +// Event storage functions. +bool RecordStatefulEvent(rlz_lib::Product product, rlz_lib::AccessPoint point, + rlz_lib::Event event) { + rlz_lib::ScopedRlzValueStoreLock lock; + rlz_lib::RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess)) + return false; + + // Write the new event to the value store. + const char* point_name = GetAccessPointName(point); + const char* event_name = GetEventName(event); + if (!point_name || !event_name) + return false; + + if (!point_name[0] || !event_name[0]) + return false; + + std::string new_event_value; + base::StringAppendF(&new_event_value, "%s%s", point_name, event_name); + return store->AddStatefulEvent(product, new_event_value.c_str()); +} + +bool GetProductEventsAsCgiHelper(rlz_lib::Product product, char* cgi, + size_t cgi_size, + rlz_lib::RlzValueStore* store) { + // Prepend the CGI param key to the buffer. + std::string cgi_arg; + base::StringAppendF(&cgi_arg, "%s=", rlz_lib::kEventsCgiVariable); + if (cgi_size <= cgi_arg.size()) + return false; + + size_t index; + for (index = 0; index < cgi_arg.size(); ++index) + cgi[index] = cgi_arg[index]; + + // Read stored events. + std::vector<std::string> events; + if (!store->ReadProductEvents(product, &events)) + return false; + + // Append the events to the buffer. + size_t num_values = 0; + + for (num_values = 0; num_values < events.size(); ++num_values) { + cgi[index] = '\0'; + + int divider = num_values > 0 ? 1 : 0; + int size = cgi_size - (index + divider); + if (size <= 0) + return cgi_size >= (rlz_lib::kMaxCgiLength + 1); + + strncpy(cgi + index + divider, events[num_values].c_str(), size); + if (divider) + cgi[index] = rlz_lib::kEventsCgiSeparator; + + index += std::min((int)events[num_values].length(), size) + divider; + } + + cgi[index] = '\0'; + + return num_values > 0; +} + +} // namespace + +namespace rlz_lib { + +#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) +bool SetURLRequestContext(net::URLRequestContextGetter* context) { + return FinancialPing::SetURLRequestContext(context); +} +#endif + +bool GetProductEventsAsCgi(Product product, char* cgi, size_t cgi_size) { + if (!cgi || cgi_size <= 0) { + ASSERT_STRING("GetProductEventsAsCgi: Invalid buffer"); + return false; + } + + cgi[0] = 0; + + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kReadAccess)) + return false; + + size_t size_local = std::min( + static_cast<size_t>(kMaxCgiLength + 1), cgi_size); + bool result = GetProductEventsAsCgiHelper(product, cgi, size_local, store); + + if (!result) { + ASSERT_STRING("GetProductEventsAsCgi: Possibly insufficient buffer size"); + cgi[0] = 0; + return false; + } + + return true; +} + +bool RecordProductEvent(Product product, AccessPoint point, Event event) { + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kWriteAccess)) + return false; + + // Get this event's value. + const char* point_name = GetAccessPointName(point); + const char* event_name = GetEventName(event); + if (!point_name || !event_name) + return false; + + if (!point_name[0] || !event_name[0]) + return false; + + std::string new_event_value; + base::StringAppendF(&new_event_value, "%s%s", point_name, event_name); + + // Check whether this event is a stateful event. If so, don't record it. + if (store->IsStatefulEvent(product, new_event_value.c_str())) { + // For a stateful event we skip recording, this function is also + // considered successful. + return true; + } + + // Write the new event to the value store. + return store->AddProductEvent(product, new_event_value.c_str()); +} + +bool ClearProductEvent(Product product, AccessPoint point, Event event) { + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kWriteAccess)) + return false; + + // Get the event's value store value and delete it. + const char* point_name = GetAccessPointName(point); + const char* event_name = GetEventName(event); + if (!point_name || !event_name) + return false; + + if (!point_name[0] || !event_name[0]) + return false; + + std::string event_value; + base::StringAppendF(&event_value, "%s%s", point_name, event_name); + return store->ClearProductEvent(product, event_value.c_str()); +} + +// RLZ storage functions. + +bool GetAccessPointRlz(AccessPoint point, char* rlz, size_t rlz_size) { + if (!rlz || rlz_size <= 0) { + ASSERT_STRING("GetAccessPointRlz: Invalid buffer"); + return false; + } + + rlz[0] = 0; + + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kReadAccess)) + return false; + + if (!IsAccessPointSupported(point)) + return false; + + return store->ReadAccessPointRlz(point, rlz, rlz_size); +} + +bool SetAccessPointRlz(AccessPoint point, const char* new_rlz) { + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kWriteAccess)) + return false; + + if (!new_rlz) { + ASSERT_STRING("SetAccessPointRlz: Invalid buffer"); + return false; + } + + // Return false if the access point is not set to Google. + if (!IsAccessPointSupported(point)) { + ASSERT_STRING(("SetAccessPointRlz: " + "Cannot set RLZ for unsupported access point.")); + return false; + } + + // Verify the RLZ length. + size_t rlz_length = strlen(new_rlz); + if (rlz_length > kMaxRlzLength) { + ASSERT_STRING("SetAccessPointRlz: RLZ length is exceeds max allowed."); + return false; + } + + char normalized_rlz[kMaxRlzLength + 1]; + NormalizeRlz(new_rlz, normalized_rlz); + VERIFY(strlen(new_rlz) == rlz_length); + + // Setting RLZ to empty == clearing. + if (normalized_rlz[0] == 0) + return store->ClearAccessPointRlz(point); + return store->WriteAccessPointRlz(point, normalized_rlz); +} + +// Financial Server pinging functions. + +bool FormFinancialPingRequest(Product product, const AccessPoint* access_points, + const char* product_signature, + const char* product_brand, + const char* product_id, + const char* product_lang, + bool exclude_machine_id, + char* request, size_t request_buffer_size) { + if (!request || request_buffer_size == 0) + return false; + + request[0] = 0; + + std::string request_string; + if (!FinancialPing::FormRequest(product, access_points, product_signature, + product_brand, product_id, product_lang, + exclude_machine_id, &request_string)) + return false; + + if (request_string.size() >= request_buffer_size) + return false; + + strncpy(request, request_string.c_str(), request_buffer_size); + request[request_buffer_size - 1] = 0; + return true; +} + +bool PingFinancialServer(Product product, const char* request, char* response, + size_t response_buffer_size) { + if (!response || response_buffer_size == 0) + return false; + response[0] = 0; + + // Check if the time is right to ping. + if (!FinancialPing::IsPingTime(product, false)) + return false; + + // Send out the ping. + std::string response_string; + if (!FinancialPing::PingServer(request, &response_string)) + return false; + + if (response_string.size() >= response_buffer_size) + return false; + + strncpy(response, response_string.c_str(), response_buffer_size); + response[response_buffer_size - 1] = 0; + return true; +} + +bool IsPingResponseValid(const char* response, int* checksum_idx) { + if (!response || !response[0]) + return false; + + if (checksum_idx) + *checksum_idx = -1; + + if (strlen(response) > kMaxPingResponseLength) { + ASSERT_STRING("IsPingResponseValid: response is too long to parse."); + return false; + } + + // Find the checksum line. + std::string response_string(response); + + std::string checksum_param("\ncrc32: "); + int calculated_crc; + int checksum_index = response_string.find(checksum_param); + if (checksum_index >= 0) { + // Calculate checksum of message preceeding checksum line. + // (+ 1 to include the \n) + std::string message(response_string.substr(0, checksum_index + 1)); + if (!Crc32(message.c_str(), &calculated_crc)) + return false; + } else { + checksum_param = "crc32: "; // Empty response case. + if (!StartsWithASCII(response_string, checksum_param, true)) + return false; + + checksum_index = 0; + if (!Crc32("", &calculated_crc)) + return false; + } + + // Find the checksum value on the response. + int checksum_end = response_string.find("\n", checksum_index + 1); + if (checksum_end < 0) + checksum_end = response_string.size(); + + int checksum_begin = checksum_index + checksum_param.size(); + std::string checksum = response_string.substr(checksum_begin, + checksum_end - checksum_begin + 1); + TrimWhitespaceASCII(checksum, TRIM_ALL, &checksum); + + if (checksum_idx) + *checksum_idx = checksum_index; + + return calculated_crc == HexStringToInteger(checksum.c_str()); +} + +// Complex helpers built on top of other functions. + +bool ParseFinancialPingResponse(Product product, const char* response) { + // Update the last ping time irrespective of success. + FinancialPing::UpdateLastPingTime(product); + // Parse the ping response - update RLZs, clear events. + return ParsePingResponse(product, response); +} + +bool SendFinancialPing(Product product, const AccessPoint* access_points, + const char* product_signature, + const char* product_brand, + const char* product_id, const char* product_lang, + bool exclude_machine_id) { + return SendFinancialPing(product, access_points, product_signature, + product_brand, product_id, product_lang, + exclude_machine_id, false); +} + + +bool SendFinancialPing(Product product, const AccessPoint* access_points, + const char* product_signature, + const char* product_brand, + const char* product_id, const char* product_lang, + bool exclude_machine_id, + const bool skip_time_check) { + // Create the financial ping request. + std::string request; + if (!FinancialPing::FormRequest(product, access_points, product_signature, + product_brand, product_id, product_lang, + exclude_machine_id, &request)) + return false; + + // Check if the time is right to ping. + if (!FinancialPing::IsPingTime(product, skip_time_check)) + return false; + + // Send out the ping, update the last ping time irrespective of success. + FinancialPing::UpdateLastPingTime(product); + std::string response; + if (!FinancialPing::PingServer(request.c_str(), &response)) + return false; + + // Parse the ping response - update RLZs, clear events. + return ParsePingResponse(product, response.c_str()); +} + +// TODO: Use something like RSA to make sure the response is +// from a Google server. +bool ParsePingResponse(Product product, const char* response) { + rlz_lib::ScopedRlzValueStoreLock lock; + rlz_lib::RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess)) + return false; + + std::string response_string(response); + int response_length = -1; + if (!IsPingResponseValid(response, &response_length)) + return false; + + if (0 == response_length) + return true; // Empty response - no parsing. + + std::string events_variable; + std::string stateful_events_variable; + base::SStringPrintf(&events_variable, "%s: ", kEventsCgiVariable); + base::SStringPrintf(&stateful_events_variable, "%s: ", + kStatefulEventsCgiVariable); + + int rlz_cgi_length = strlen(kRlzCgiVariable); + + // Split response lines. Expected response format is lines of the form: + // rlzW1: 1R1_____en__252 + int line_end_index = -1; + do { + int line_begin = line_end_index + 1; + line_end_index = response_string.find("\n", line_begin); + + int line_end = line_end_index; + if (line_end < 0) + line_end = response_length; + + if (line_end <= line_begin) + continue; // Empty line. + + std::string response_line; + response_line = response_string.substr(line_begin, line_end - line_begin); + + if (StartsWithASCII(response_line, kRlzCgiVariable, true)) { // An RLZ. + int separator_index = -1; + if ((separator_index = response_line.find(": ")) < 0) + continue; // Not a valid key-value pair. + + // Get the access point. + std::string point_name = + response_line.substr(3, separator_index - rlz_cgi_length); + AccessPoint point = NO_ACCESS_POINT; + if (!GetAccessPointFromName(point_name.c_str(), &point) || + point == NO_ACCESS_POINT) + continue; // Not a valid access point. + + // Get the new RLZ. + std::string rlz_value(response_line.substr(separator_index + 2)); + TrimWhitespaceASCII(rlz_value, TRIM_LEADING, &rlz_value); + + int rlz_length = rlz_value.find_first_of("\r\n "); + if (rlz_length < 0) + rlz_length = rlz_value.size(); + + if (rlz_length > kMaxRlzLength) + continue; // Too long. + + if (IsAccessPointSupported(point)) + SetAccessPointRlz(point, rlz_value.substr(0, rlz_length).c_str()); + } else if (StartsWithASCII(response_line, events_variable, true)) { + // Clear events which server parsed. + std::vector<ReturnedEvent> event_array; + GetEventsFromResponseString(response_line, events_variable, &event_array); + for (size_t i = 0; i < event_array.size(); ++i) { + ClearProductEvent(product, event_array[i].access_point, + event_array[i].event_type); + } + } else if (StartsWithASCII(response_line, stateful_events_variable, true)) { + // Record any stateful events the server send over. + std::vector<ReturnedEvent> event_array; + GetEventsFromResponseString(response_line, stateful_events_variable, + &event_array); + for (size_t i = 0; i < event_array.size(); ++i) { + RecordStatefulEvent(product, event_array[i].access_point, + event_array[i].event_type); + } + } + } while (line_end_index >= 0); + +#if defined(OS_WIN) + // Update the DCC in registry if needed. + SetMachineDealCodeFromPingResponse(response); +#endif + + return true; +} + +bool GetPingParams(Product product, const AccessPoint* access_points, + char* cgi, size_t cgi_size) { + if (!cgi || cgi_size <= 0) { + ASSERT_STRING("GetPingParams: Invalid buffer"); + return false; + } + + cgi[0] = 0; + + if (!access_points) { + ASSERT_STRING("GetPingParams: access_points is NULL"); + return false; + } + + // Add the RLZ Exchange Protocol version. + std::string cgi_string(kProtocolCgiArgument); + + // Copy the &rlz= over. + base::StringAppendF(&cgi_string, "&%s=", kRlzCgiVariable); + + { + // Now add each of the RLZ's. Keep the lock during all GetAccessPointRlz() + // calls below. + ScopedRlzValueStoreLock lock; + RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(RlzValueStore::kReadAccess)) + return false; + bool first_rlz = true; // comma before every RLZ but the first. + for (int i = 0; access_points[i] != NO_ACCESS_POINT; i++) { + char rlz[kMaxRlzLength + 1]; + if (GetAccessPointRlz(access_points[i], rlz, arraysize(rlz))) { + const char* access_point = GetAccessPointName(access_points[i]); + if (!access_point) + continue; + + base::StringAppendF(&cgi_string, "%s%s%s%s", + first_rlz ? "" : kRlzCgiSeparator, + access_point, kRlzCgiIndicator, rlz); + first_rlz = false; + } + } + +#if defined(OS_WIN) + // Report the DCC too if not empty. DCCs are windows-only. + char dcc[kMaxDccLength + 1]; + dcc[0] = 0; + if (GetMachineDealCode(dcc, arraysize(dcc)) && dcc[0]) + base::StringAppendF(&cgi_string, "&%s=%s", kDccCgiVariable, dcc); +#endif + } + + if (cgi_string.size() >= cgi_size) + return false; + + strncpy(cgi, cgi_string.c_str(), cgi_size); + cgi[cgi_size - 1] = 0; + + return true; +} + +} // namespace rlz_lib diff --git a/rlz/lib/rlz_lib.h b/rlz/lib/rlz_lib.h new file mode 100644 index 0000000..9d4cf3b --- /dev/null +++ b/rlz/lib/rlz_lib.h @@ -0,0 +1,335 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A library to manage RLZ information for access-points shared +// across different client applications. +// +// All functions return true on success and false on error. +// This implemenation is thread safe. + + +#ifndef RLZ_LIB_RLZ_LIB_H_ +#define RLZ_LIB_RLZ_LIB_H_ + +#include <stdio.h> +#include <string> + +#include "build/build_config.h" + +#include "rlz/lib/rlz_enums.h" + +#if defined(OS_WIN) +#define RLZ_LIB_API __cdecl +#else +#define RLZ_LIB_API +#endif + +// Define one of +// + RLZ_NETWORK_IMPLEMENTATION_WIN_INET: Uses win inet to send financial pings. +// + RLZ_NETWORK_IMPLEMENTATION_CHROME_NET: Uses chrome's network stack to send +// financial pings. rlz_lib::SetURLRequestContext() must be called before +// any calls to SendFinancialPing(). +#if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET) && \ + defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) +#error Exactly one of RLZ_NETWORK_IMPLEMENTATION_WIN_INET and \ + RLZ_NETWORK_IMPLEMENTATION_CHROME_NET should be defined. +#endif +#if !defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET) && \ + !defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) +#if defined(OS_WIN) +#define RLZ_NETWORK_IMPLEMENTATION_WIN_INET +#else +#define RLZ_NETWORK_IMPLEMENTATION_CHROME_NET +#endif +#endif + +#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) +namespace net { +class URLRequestContextGetter; +} // namespace net +#endif + +namespace rlz_lib { + +class ScopedRlzValueStoreLock; + +// The maximum length of an access points RLZ in bytes. +const int kMaxRlzLength = 64; +// The maximum length of an access points RLZ in bytes. +const int kMaxDccLength = 128; +// The maximum length of a CGI string in bytes. +const int kMaxCgiLength = 2048; +// The maximum length of a ping response we will parse in bytes. If the response +// is bigger, please break it up into separate calls. +const int kMaxPingResponseLength = 0x4000; // 16K + +#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) +// Set the URLRequestContextGetter used by SendFinancialPing(). The IO message +// loop returned by this context will be used for the IO done by +// SendFinancialPing(). +bool RLZ_LIB_API SetURLRequestContext(net::URLRequestContextGetter* context); +#endif + +// RLZ storage functions. + +// Get all the events reported by this product as a CGI string to append to +// the daily ping. +// Access: HKCU read. +bool RLZ_LIB_API GetProductEventsAsCgi(Product product, char* unescaped_cgi, + size_t unescaped_cgi_size); + +// Records an RLZ event. +// Some events can be product-independent (e.g: First search from home page), +// and some can be access point independent (e.g. Pack installed). However, +// product independent events must still include the product which cares about +// that information being reported. +// Access: HKCU write. +bool RLZ_LIB_API RecordProductEvent(Product product, AccessPoint point, + Event event_id); + +// Clear an event reported by this product. This should be called after a +// successful ping to the RLZ server. +// Access: HKCU write. +bool RLZ_LIB_API ClearProductEvent(Product product, AccessPoint point, + Event event_id); + +// Clear all reported events and recorded stateful events of this product. +// This should be called on complete uninstallation of the product. +// Access: HKCU write. +bool RLZ_LIB_API ClearAllProductEvents(Product product); + +// Clears all product-specifc state from the RLZ registry. +// Should be called during product uninstallation. +// This removes outstanding product events, product financial ping times, +// the product RLS argument (if any), and any RLZ's for access points being +// uninstalled with the product. +// access_points is an array terminated with NO_ACCESS_POINT. +// IMPORTANT: These are the access_points the product is removing as part +// of the uninstallation, not necessarily all the access points passed to +// SendFinancialPing() and GetPingParams(). +// access_points can be NULL if no points are being uninstalled. +// No return value - this is best effort. Will assert in debug mode on +// failed attempts. +// Access: HKCU write. +void RLZ_LIB_API ClearProductState(Product product, + const AccessPoint* access_points); + +// Get the RLZ value of the access point. If the access point is not Google, the +// RLZ will be the empty string and the function will return false. +// Access: HKCU read. +bool RLZ_LIB_API GetAccessPointRlz(AccessPoint point, char* rlz, + size_t rlz_size); + +// Set the RLZ for the access-point. Fails and asserts if called when the access +// point is not set to Google. +// new_rlz should come from a server-response. Client applications should not +// create their own RLZ values. +// Access: HKCU write. +bool RLZ_LIB_API SetAccessPointRlz(AccessPoint point, const char* new_rlz); + +// Financial Server pinging functions. +// These functions deal with pinging the RLZ financial server and parsing and +// acting upon the response. Clients should SendFinancialPing() to avoid needing +// these functions. However, these functions allow clients to split the various +// parts of the pinging process up as needed (to avoid firewalls, etc). + +// Forms the HTTP request to send to the RLZ financial server. +// +// product : The product to ping for. +// access_points : The access points this product affects. Array must be +// terminated with NO_ACCESS_POINT. +// product_signature : The signature sent with daily pings (e.g. swg, ietb) +// product_brand : The brand of the pinging product, if any. +// product_id : The product-specific installation ID (can be NULL). +// product_lang : The language for the product (used to determine cohort). +// exclude_machine_id : Whether the Machine ID should be explicitly excluded +// based on the products privacy policy. +// request : The buffer where the function returns the HTTP request. +// request_buffer_size: The size of the request buffer in bytes. The buffer +// size (kMaxCgiLength+1) is guaranteed to be enough. +// +// Access: HKCU read. +bool RLZ_LIB_API FormFinancialPingRequest(Product product, + const AccessPoint* access_points, + const char* product_signature, + const char* product_brand, + const char* product_id, + const char* product_lang, + bool exclude_machine_id, + char* request, + size_t request_buffer_size); + +// Pings the financial server and returns the HTTP response. This will fail +// if it is too early to ping the server since the last ping. +// +// If RLZ_NETWORK_IMPLEMENTATION_CHROME_NET is set, SetURLRequestContext() needs +// to be called before calling this function. +// +// product : The product to ping for. +// request : The HTTP request (for example, returned by +// FormFinancialPingRequest). +// response : The buffer in which the HTTP response is returned. +// response_buffer_size : The size of the response buffer in bytes. The buffer +// size (kMaxPingResponseLength+1) is enough for all +// legitimate server responses (any response that is +// bigger should be considered the same way as a general +// network problem). +// +// Access: HKCU read. +bool RLZ_LIB_API PingFinancialServer(Product product, + const char* request, + char* response, + size_t response_buffer_size); + +// Checks if a ping response is valid - ie. it has a checksum line which +// is the CRC-32 checksum of the message uptil the checksum. If +// checksum_idx is not NULL, it will get the index of the checksum, i.e. - +// the effective end of the message. +// Access: No restrictions. +bool RLZ_LIB_API IsPingResponseValid(const char* response, + int* checksum_idx); + + +// Complex helpers built on top of other functions. + +// Parses the responses from the financial server and updates product state +// and access point RLZ's in registry. Like ParsePingResponse(), but also +// updates the last ping time. +// Access: HKCU write. +bool RLZ_LIB_API ParseFinancialPingResponse(Product product, + const char* response); + +// Send the ping with RLZs and events to the PSO server. +// This ping method should be called daily. (More frequent calls will fail). +// Also, if there are no events, the call will succeed only once a week. +// +// If RLZ_NETWORK_IMPLEMENTATION_CHROME_NET is set, SetURLRequestContext() needs +// to be called before calling this function. +// +// product : The product to ping for. +// access_points : The access points this product affects. Array must be +// terminated with NO_ACCESS_POINT. +// product_signature : The signature sent with daily pings (e.g. swg, ietb) +// product_brand : The brand of the pinging product, if any. +// product_id : The product-specific installation ID (can be NULL). +// product_lang : The language for the product (used to determine cohort). +// exclude_machine_id : Whether the Machine ID should be explicitly excluded +// based on the products privacy policy. +// +// Returns true on successful ping and response, false otherwise. +// Access: HKCU write. +bool RLZ_LIB_API SendFinancialPing(Product product, + const AccessPoint* access_points, + const char* product_signature, + const char* product_brand, + const char* product_id, + const char* product_lang, + bool exclude_machine_id); + +// An alternate implementations of SendFinancialPing with the same behavior, +// except the caller can optionally choose to skip the timing check. +bool RLZ_LIB_API SendFinancialPing(Product product, + const AccessPoint* access_points, + const char* product_signature, + const char* product_brand, + const char* product_id, + const char* product_lang, + bool exclude_machine_id, + const bool skip_time_check); + +// Parses RLZ related ping response information from the server. +// Updates stored RLZ values and clears stored events accordingly. +// Access: HKCU write. +bool RLZ_LIB_API ParsePingResponse(Product product, const char* response); + + +// Copies the events associated with the product and the RLZ's for each access +// point in access_points into cgi. This string can be directly appended +// to a ping (will need an & if not first paramter). +// access_points must be an array of AccessPoints terminated with +// NO_ACCESS_POINT. +// Access: HKCU read. +bool RLZ_LIB_API GetPingParams(Product product, + const AccessPoint* access_points, + char* unescaped_cgi, size_t unescaped_cgi_size); + +#if defined(OS_WIN) +// OEM Deal confirmation storage functions. OEM Deals are windows-only. + +// Makes the OEM Deal Confirmation code writable by all users on the machine. +// This should be called before calling SetMachineDealCode from a non-admin +// account. +// Access: HKLM write. +bool RLZ_LIB_API CreateMachineState(void); + +// Set the OEM Deal Confirmation Code (DCC). This information is used for RLZ +// initalization. +// Access: HKLM write, or +// HKCU read if rlz_lib::CreateMachineState() has been sucessfully called. +bool RLZ_LIB_API SetMachineDealCode(const char* dcc); + +// Get the DCC cgi argument string to append to a daily ping. +// Should be used only by OEM deal trackers. Applications should use the +// GetMachineDealCode method which has an AccessPoint paramter. +// Access: HKLM read. +bool RLZ_LIB_API GetMachineDealCodeAsCgi(char* cgi, size_t cgi_size); + +// Get the DCC value stored in registry. +// Should be used only by OEM deal trackers. Applications should use the +// GetMachineDealCode method which has an AccessPoint paramter. +// Access: HKLM read. +bool RLZ_LIB_API GetMachineDealCode(char* dcc, size_t dcc_size); + +// Parses a ping response, checks if it is valid and sets the machine DCC +// from the response. The ping must also contain the current DCC value in +// order to be considered valid. +// Access: HKLM write; +// HKCU write if CreateMachineState() has been successfully called. +bool RLZ_LIB_API SetMachineDealCodeFromPingResponse(const char* response); + +#endif + +// Segment RLZ persistence based on branding information. +// All information for a given product is persisted under keys with the either +// product's name or its access point's name. This assumes that only +// one instance of the product is installed on the machine, and that only one +// product brand is associated with it. +// +// In some cases, a given product may be using supplementary brands. The RLZ +// information must be kept separately for each of these brands. To achieve +// this segmentation, scope all RLZ library calls that deal with supplementary +// brands within the lifetime of an rlz_lib::ProductBranding instance. +// +// For example, to record events for a supplementary brand, do the following: +// +// { +// rlz_lib::SupplementaryBranding branding("AAAA"); +// // This call to RecordProductEvent is scoped to the AAAA brand. +// rlz_lib::RecordProductEvent(rlz_lib::DESKTOP, rlz_lib::GD_DESKBAND, +// rlz_lib::INSTALL); +// } +// +// // This call to RecordProductEvent is not scoped to any supplementary brand. +// rlz_lib::RecordProductEvent(rlz_lib::DESKTOP, rlz_lib::GD_DESKBAND, +// rlz_lib::INSTALL); +// +// In particular, this affects the recording of stateful events and the sending +// of financial pings. In the former case, a stateful event recorded while +// scoped to a supplementary brand will be recorded again when scoped to a +// different supplementary brand (or not scoped at all). In the latter case, +// the time skip check is specific to each supplementary brand. +class SupplementaryBranding { + public: + SupplementaryBranding(const char* brand); + ~SupplementaryBranding(); + + static const std::string& GetBrand(); + + private: + ScopedRlzValueStoreLock* lock_; +}; + +} // namespace rlz_lib + +#endif // RLZ_LIB_RLZ_LIB_H_ diff --git a/rlz/lib/rlz_lib_clear.cc b/rlz/lib/rlz_lib_clear.cc new file mode 100644 index 0000000..0dd13a4 --- /dev/null +++ b/rlz/lib/rlz_lib_clear.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The methods in this file belong conceptually to rlz_lib.cc. However, some +// programs depend on rlz only to call ClearAllProductEvents(), so this file +// contains this in fairly self-contained form to make it easier for linkers +// to strip away most of rlz. In particular, this file should not reference any +// symbols defined in financial_ping.cc. + +#include "rlz/lib/rlz_lib.h" + +#include "base/lazy_instance.h" +#include "rlz/lib/assert.h" +#include "rlz/lib/rlz_value_store.h" + +namespace rlz_lib { + +bool ClearAllProductEvents(Product product) { + rlz_lib::ScopedRlzValueStoreLock lock; + rlz_lib::RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess)) + return false; + + bool result; + result = store->ClearAllProductEvents(product); + result &= store->ClearAllStatefulEvents(product); + return result; +} + +void ClearProductState(Product product, const AccessPoint* access_points) { + rlz_lib::ScopedRlzValueStoreLock lock; + rlz_lib::RlzValueStore* store = lock.GetStore(); + if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess)) + return; + + // Delete all product specific state. + VERIFY(ClearAllProductEvents(product)); + VERIFY(store->ClearPingTime(product)); + + // Delete all RLZ's for access points being uninstalled. + if (access_points) { + for (int i = 0; access_points[i] != NO_ACCESS_POINT; i++) { + VERIFY(store->ClearAccessPointRlz(access_points[i])); + } + } + + store->CollectGarbage(); +} + +static base::LazyInstance<std::string>::Leaky g_supplemental_branding; + +SupplementaryBranding::SupplementaryBranding(const char* brand) + : lock_(new ScopedRlzValueStoreLock) { + if (!lock_->GetStore()) + return; + + if (!g_supplemental_branding.Get().empty()) { + ASSERT_STRING("ProductBranding: existing brand is not empty"); + return; + } + + if (brand == NULL || brand[0] == 0) { + ASSERT_STRING("ProductBranding: new brand is empty"); + return; + } + + g_supplemental_branding.Get() = brand; +} + +SupplementaryBranding::~SupplementaryBranding() { + if (lock_->GetStore()) + g_supplemental_branding.Get().clear(); + delete lock_; +} + +// static +const std::string& SupplementaryBranding::GetBrand() { + return g_supplemental_branding.Get(); +} + +} // namespace rlz_lib diff --git a/rlz/lib/rlz_lib_test.cc b/rlz/lib/rlz_lib_test.cc new file mode 100644 index 0000000..0f8cb4c --- /dev/null +++ b/rlz/lib/rlz_lib_test.cc @@ -0,0 +1,824 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A test application for the RLZ library. +// +// These tests should not be executed on the build server: +// - They assert for the failed cases. +// - They modify machine state (registry). +// +// These tests require write access to HKLM and HKCU. +// +// The "GGLA" brand is used to test the normal code flow of the code, and the +// "TEST" brand is used to test the supplementary brand code code flow. + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "rlz/lib/rlz_lib.h" +#include "rlz/test/rlz_test_helpers.h" + +#if defined(OS_WIN) +#include <Windows.h> +#include "rlz/win/lib/machine_deal.h" +#endif + +#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/threading/thread.h" +#include "net/url_request/url_request_test_util.h" +#endif + + +class MachineDealCodeHelper +#if defined(OS_WIN) + : public rlz_lib::MachineDealCode +#endif + { + public: + static bool Clear() { +#if defined(OS_WIN) + return rlz_lib::MachineDealCode::Clear(); +#else + return true; +#endif + } + + private: + MachineDealCodeHelper() {} + ~MachineDealCodeHelper() {} +}; + +class RlzLibTest : public RlzLibTestBase { +}; + +TEST_F(RlzLibTest, RecordProductEvent) { + char cgi_50[50]; + + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7S", cgi_50); + + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7S,W1I", cgi_50); + + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7S,W1I", cgi_50); +} + +TEST_F(RlzLibTest, ClearProductEvent) { + char cgi_50[50]; + + // Clear 1 of 1 events. + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7S", cgi_50); + EXPECT_TRUE(rlz_lib::ClearProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("", cgi_50); + + // Clear 1 of 2 events. + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7S,W1I", cgi_50); + EXPECT_TRUE(rlz_lib::ClearProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=W1I", cgi_50); + + // Clear a non-recorded event. + EXPECT_TRUE(rlz_lib::ClearProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IETB_SEARCH_BOX, rlz_lib::FIRST_SEARCH)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=W1I", cgi_50); +} + + +TEST_F(RlzLibTest, GetProductEventsAsCgi) { + char cgi_50[50]; + char cgi_1[1]; + + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + + EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_1, 1)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7S,W1I", cgi_50); +} + +TEST_F(RlzLibTest, ClearAllAllProductEvents) { + char cgi_50[50]; + + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7S", cgi_50); + + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("", cgi_50); +} + +TEST_F(RlzLibTest, SetAccessPointRlz) { + char rlz_50[50]; + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "")); + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50)); + EXPECT_STREQ("", rlz_50); + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "IeTbRlz")); + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50)); + EXPECT_STREQ("IeTbRlz", rlz_50); +} + +TEST_F(RlzLibTest, GetAccessPointRlz) { + char rlz_1[1]; + char rlz_50[50]; + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "")); + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_1, 1)); + EXPECT_STREQ("", rlz_1); + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "IeTbRlz")); + EXPECT_FALSE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_1, 1)); + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50)); + EXPECT_STREQ("IeTbRlz", rlz_50); +} + +TEST_F(RlzLibTest, GetPingParams) { + MachineDealCodeHelper::Clear(); + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, + "TbRlzValue")); + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IE_HOME_PAGE, "")); + + char cgi[2048]; + rlz_lib::AccessPoint points[] = + {rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT, + rlz_lib::NO_ACCESS_POINT}; + + EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points, + cgi, 2048)); + EXPECT_STREQ("rep=2&rlz=T4:TbRlzValue", cgi); + +#if defined(OS_WIN) + EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value")); +#define DCC_PARAM "&dcc=dcc_value" +#else +#define DCC_PARAM "" +#endif + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "")); + EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points, + cgi, 2048)); + EXPECT_STREQ("rep=2&rlz=T4:" DCC_PARAM, cgi); + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, + "TbRlzValue")); + EXPECT_FALSE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points, + cgi, 23 + strlen(DCC_PARAM))); + EXPECT_STREQ("", cgi); + EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points, + cgi, 24 + strlen(DCC_PARAM))); + EXPECT_STREQ("rep=2&rlz=T4:TbRlzValue" DCC_PARAM, cgi); + + EXPECT_TRUE(GetAccessPointRlz(rlz_lib::IE_HOME_PAGE, cgi, 2048)); + points[2] = rlz_lib::IE_HOME_PAGE; + EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points, + cgi, 2048)); + EXPECT_STREQ("rep=2&rlz=T4:TbRlzValue" DCC_PARAM, cgi); +} + +TEST_F(RlzLibTest, IsPingResponseValid) { + const char* kBadPingResponses[] = { + // No checksum. + "version: 3.0.914.7250\r\n" + "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n" + "launch-action: custom-action\r\n" + "launch-target: SearchWithGoogleUpdate.exe\r\n" + "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n" + "rlz: 1R1_____en__252\r\n" + "rlzXX: 1R1_____en__250\r\n", + + // Invalid checksum. + "version: 3.0.914.7250\r\n" + "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n" + "launch-action: custom-action\r\n" + "launch-target: SearchWithGoogleUpdate.exe\r\n" + "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n" + "rlz: 1R1_____en__252\r\n" + "rlzXX: 1R1_____en__250\r\n" + "rlzT4 1T4_____en__251\r\n" + "rlzT4: 1T4_____en__252\r\n" + "rlz\r\n" + "crc32: B12CC79A", + + // Misplaced checksum. + "version: 3.0.914.7250\r\n" + "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n" + "launch-action: custom-action\r\n" + "launch-target: SearchWithGoogleUpdate.exe\r\n" + "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n" + "rlz: 1R1_____en__252\r\n" + "rlzXX: 1R1_____en__250\r\n" + "crc32: B12CC79C\r\n" + "rlzT4 1T4_____en__251\r\n" + "rlzT4: 1T4_____en__252\r\n" + "rlz\r\n", + + NULL + }; + + const char* kGoodPingResponses[] = { + "version: 3.0.914.7250\r\n" + "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n" + "launch-action: custom-action\r\n" + "launch-target: SearchWithGoogleUpdate.exe\r\n" + "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n" + "rlz: 1R1_____en__252\r\n" + "rlzXX: 1R1_____en__250\r\n" + "rlzT4 1T4_____en__251\r\n" + "rlzT4: 1T4_____en__252\r\n" + "rlz\r\n" + "crc32: D6FD55A3", + + "version: 3.0.914.7250\r\n" + "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n" + "launch-action: custom-action\r\n" + "launch-target: SearchWithGoogleUpdate.exe\r\n" + "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n" + "rlz: 1R1_____en__252\r\n" + "rlzXX: 1R1_____en__250\r\n" + "rlzT4 1T4_____en__251\r\n" + "rlzT4: 1T4_____en__252\r\n" + "rlz\r\n" + "crc32: D6FD55A3\r\n" + "extradata: not checksummed", + + NULL + }; + + for (int i = 0; kBadPingResponses[i]; i++) + EXPECT_FALSE(rlz_lib::IsPingResponseValid(kBadPingResponses[i], NULL)); + + for (int i = 0; kGoodPingResponses[i]; i++) + EXPECT_TRUE(rlz_lib::IsPingResponseValid(kGoodPingResponses[i], NULL)); +} + +TEST_F(RlzLibTest, ParsePingResponse) { + const char* kPingResponse = + "version: 3.0.914.7250\r\n" + "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n" + "launch-action: custom-action\r\n" + "launch-target: SearchWithGoogleUpdate.exe\r\n" + "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n" + "rlz: 1R1_____en__252\r\n" // Invalid RLZ - no access point. + "rlzXX: 1R1_____en__250\r\n" // Invalid RLZ - bad access point. + "rlzT4 1T4_____en__251\r\n" // Invalid RLZ - missing colon. + "rlzT4: 1T4_____en__252\r\n" // GoodRLZ. + "events: I7S,W1I\r\n" // Clear all events. + "rlz\r\n" + "dcc: dcc_value\r\n" + "crc32: F9070F81"; + +#if defined(OS_WIN) + EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value2")); +#endif + + // Record some product events to check that they get cleared. + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz( + rlz_lib::IETB_SEARCH_BOX, "TbRlzValue")); + + EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER, + kPingResponse)); + +#if defined(OS_WIN) + EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value")); +#endif + EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER, + kPingResponse)); + + char value[50]; + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, value, 50)); + EXPECT_STREQ("1T4_____en__252", value); + EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + value, 50)); + EXPECT_STREQ("", value); + + const char* kPingResponse2 = + "rlzT4: 1T4_____de__253 \r\n" // Good with extra spaces. + "crc32: 321334F5\r\n"; + EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER, + kPingResponse2)); + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, value, 50)); + EXPECT_STREQ("1T4_____de__253", value); + + const char* kPingResponse3 = + "crc32: 0\r\n"; // Good RLZ - empty response. + EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER, + kPingResponse3)); + EXPECT_STREQ("1T4_____de__253", value); +} + +// Test whether a stateful event will only be sent in financial pings once. +TEST_F(RlzLibTest, ParsePingResponseWithStatefulEvents) { + const char* kPingResponse = + "version: 3.0.914.7250\r\n" + "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n" + "launch-action: custom-action\r\n" + "launch-target: SearchWithGoogleUpdate.exe\r\n" + "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n" + "rlzT4: 1T4_____en__252\r\n" // GoodRLZ. + "events: I7S,W1I\r\n" // Clear all events. + "stateful-events: W1I\r\n" // W1I as an stateful event. + "rlz\r\n" + "dcc: dcc_value\r\n" + "crc32: 55191759"; + + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + + // Record some product events to check that they get cleared. + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz( + rlz_lib::IETB_SEARCH_BOX, "TbRlzValue")); + + EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER, + kPingResponse)); + + // Check all the events sent earlier are cleared. + char value[50]; + EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + value, 50)); + EXPECT_STREQ("", value); + + // Record both events (one is stateless and the other is stateful) again. + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + + // Check the stateful event won't be sent again while the stateless one will. + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + value, 50)); + EXPECT_STREQ("events=I7S", value); + + // Test that stateful events are cleared by ClearAllProductEvents(). After + // calling it, trying to record a stateful again should result in it being + // recorded again. + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + value, 50)); + EXPECT_STREQ("events=W1I", value); +} + +TEST_F(RlzLibTest, SendFinancialPing) { + // We don't really check a value or result in this test. All this does is + // attempt to ping the financial server, which you can verify in Fiddler. + // TODO: Make this a measurable test. + +#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) +#if defined(OS_MACOSX) + base::mac::ScopedNSAutoreleasePool pool; +#endif + + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + + base::Thread io_thread("rlz_unittest_io_thread"); + ASSERT_TRUE(io_thread.StartWithOptions(options)); + + scoped_refptr<TestURLRequestContextGetter> context = + new TestURLRequestContextGetter( + io_thread.message_loop()->message_loop_proxy()); + rlz_lib::SetURLRequestContext(context.get()); + + class URLRequestRAII { + public: + URLRequestRAII(net::URLRequestContextGetter* context) { + rlz_lib::SetURLRequestContext(context); + } + ~URLRequestRAII() { + rlz_lib::SetURLRequestContext(NULL); + } + }; + + URLRequestRAII set_context(context.get()); +#endif + + MachineDealCodeHelper::Clear(); +#if defined(OS_WIN) + EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value")); +#endif + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, + "TbRlzValue")); + + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + + rlz_lib::AccessPoint points[] = + {rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT, + rlz_lib::NO_ACCESS_POINT}; + + std::string request; + rlz_lib::SendFinancialPing(rlz_lib::TOOLBAR_NOTIFIER, points, + "swg", "GGLA", "SwgProductId1234", "en-UK", false, + /*skip_time_check=*/true); +} + +TEST_F(RlzLibTest, ClearProductState) { + MachineDealCodeHelper::Clear(); + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, + "TbRlzValue")); + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::GD_DESKBAND, + "GdbRlzValue")); + + rlz_lib::AccessPoint points[] = + { rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT }; + + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IETB_SEARCH_BOX, rlz_lib::INSTALL)); + + rlz_lib::AccessPoint points2[] = + { rlz_lib::IETB_SEARCH_BOX, + rlz_lib::GD_DESKBAND, + rlz_lib::NO_ACCESS_POINT }; + + char cgi[2048]; + EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points2, + cgi, 2048)); + EXPECT_STREQ("rep=2&rlz=T4:TbRlzValue,D1:GdbRlzValue", cgi); + + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi, 2048)); + std::string events(cgi); + EXPECT_LT(0u, events.find("I7S")); + EXPECT_LT(0u, events.find("T4I")); + EXPECT_LT(0u, events.find("T4R")); + + rlz_lib::ClearProductState(rlz_lib::TOOLBAR_NOTIFIER, points); + + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, + cgi, 2048)); + EXPECT_STREQ("", cgi); + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::GD_DESKBAND, + cgi, 2048)); + EXPECT_STREQ("GdbRlzValue", cgi); + + EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi, 2048)); + EXPECT_STREQ("", cgi); +} + +#if defined(OS_WIN) +template<class T> +class typed_buffer_ptr { + scoped_array<char> buffer_; + + public: + typed_buffer_ptr() { + } + + explicit typed_buffer_ptr(size_t size) : buffer_(new char[size]) { + } + + void reset(size_t size) { + buffer_.reset(new char[size]); + } + + operator T*() { + return reinterpret_cast<T*>(buffer_.get()); + } +}; + +namespace rlz_lib { +bool HasAccess(PSID sid, ACCESS_MASK access_mask, ACL* dacl); +} + +bool EmptyAcl(ACL* acl) { + ACL_SIZE_INFORMATION info; + bool ret = GetAclInformation(acl, &info, sizeof(info), AclSizeInformation); + EXPECT_TRUE(ret); + + for (DWORD i = 0; i < info.AceCount && ret; ++i) { + ret = DeleteAce(acl, 0); + EXPECT_TRUE(ret); + } + + return ret; +} + +TEST_F(RlzLibTest, HasAccess) { + // Create a SID that represents ALL USERS. + DWORD users_sid_size = SECURITY_MAX_SID_SIZE; + typed_buffer_ptr<SID> users_sid(users_sid_size); + CreateWellKnownSid(WinBuiltinUsersSid, NULL, users_sid, &users_sid_size); + + // RLZ always asks for KEY_ALL_ACCESS access to the key. This is what we + // test here. + + // No ACL mean no access. + EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, NULL)); + + // Create an ACL for these tests. + const DWORD kMaxAclSize = 1024; + typed_buffer_ptr<ACL> dacl(kMaxAclSize); + InitializeAcl(dacl, kMaxAclSize, ACL_REVISION); + + // Empty DACL mean no access. + EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); + + // ACE without all needed privileges should mean no access. + EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_READ, users_sid)); + EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); + + // ACE without all needed privileges should mean no access. + EXPECT_TRUE(EmptyAcl(dacl)); + EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_WRITE, users_sid)); + EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); + + // A deny ACE before an allow ACE should not give access. + EXPECT_TRUE(EmptyAcl(dacl)); + EXPECT_TRUE(AddAccessDeniedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS, + users_sid)); + EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS, + users_sid)); + EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); + + // A deny ACE before an allow ACE should not give access. + EXPECT_TRUE(EmptyAcl(dacl)); + EXPECT_TRUE(AddAccessDeniedAce(dacl, ACL_REVISION, KEY_READ, users_sid)); + EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS, + users_sid)); + EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); + + + // An allow ACE without all required bits should not give access. + EXPECT_TRUE(EmptyAcl(dacl)); + EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_WRITE, users_sid)); + EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); + + // An allow ACE with all required bits should give access. + EXPECT_TRUE(EmptyAcl(dacl)); + EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS, + users_sid)); + EXPECT_TRUE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); + + // A deny ACE after an allow ACE should not give access. + EXPECT_TRUE(EmptyAcl(dacl)); + EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS, + users_sid)); + EXPECT_TRUE(AddAccessDeniedAce(dacl, ACL_REVISION, KEY_READ, users_sid)); + EXPECT_TRUE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); + + // An inherit-only allow ACE should not give access. + EXPECT_TRUE(EmptyAcl(dacl)); + EXPECT_TRUE(AddAccessAllowedAceEx(dacl, ACL_REVISION, INHERIT_ONLY_ACE, + KEY_ALL_ACCESS, users_sid)); + EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); + + // An inherit-only deny ACE should not apply. + EXPECT_TRUE(EmptyAcl(dacl)); + EXPECT_TRUE(AddAccessDeniedAceEx(dacl, ACL_REVISION, INHERIT_ONLY_ACE, + KEY_ALL_ACCESS, users_sid)); + EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS, + users_sid)); + EXPECT_TRUE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl)); +} +#endif + +TEST_F(RlzLibTest, BrandingRecordProductEvent) { + // Don't run these tests if a supplementary brand is already in place. That + // way we can control the branding. + if (!rlz_lib::SupplementaryBranding::GetBrand().empty()) + return; + + char cgi_50[50]; + + // Record different events for the same product with diffrent branding, and + // make sure that the information remains separate. + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + { + rlz_lib::SupplementaryBranding branding("TEST"); + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + } + + // Test that recording events with the default brand and a supplementary + // brand don't overwrite each other. + + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7S", cgi_50); + + { + rlz_lib::SupplementaryBranding branding("TEST"); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::INSTALL)); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7I", cgi_50); + } + + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + cgi_50, 50)); + EXPECT_STREQ("events=I7S", cgi_50); +} + +TEST_F(RlzLibTest, BrandingSetAccessPointRlz) { + // Don't run these tests if a supplementary brand is already in place. That + // way we can control the branding. + if (!rlz_lib::SupplementaryBranding::GetBrand().empty()) + return; + + char rlz_50[50]; + + // Test that setting RLZ strings with the default brand and a supplementary + // brand don't overwrite each other. + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "IeTbRlz")); + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50)); + EXPECT_STREQ("IeTbRlz", rlz_50); + + { + rlz_lib::SupplementaryBranding branding("TEST"); + + EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "SuppRlz")); + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, + 50)); + EXPECT_STREQ("SuppRlz", rlz_50); + } + + EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50)); + EXPECT_STREQ("IeTbRlz", rlz_50); + +} + +TEST_F(RlzLibTest, BrandingWithStatefulEvents) { + // Don't run these tests if a supplementary brand is already in place. That + // way we can control the branding. + if (!rlz_lib::SupplementaryBranding::GetBrand().empty()) + return; + + const char* kPingResponse = + "version: 3.0.914.7250\r\n" + "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n" + "launch-action: custom-action\r\n" + "launch-target: SearchWithGoogleUpdate.exe\r\n" + "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n" + "rlzT4: 1T4_____en__252\r\n" // GoodRLZ. + "events: I7S,W1I\r\n" // Clear all events. + "stateful-events: W1I\r\n" // W1I as an stateful event. + "rlz\r\n" + "dcc: dcc_value\r\n" + "crc32: 55191759"; + + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + { + rlz_lib::SupplementaryBranding branding("TEST"); + EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER)); + } + + // Record some product events for the default and supplementary brand. + // Check that they get cleared only for the default brand. + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + + { + rlz_lib::SupplementaryBranding branding("TEST"); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + } + + EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER, + kPingResponse)); + + // Check all the events sent earlier are cleared only for default brand. + char value[50]; + EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + value, 50)); + EXPECT_STREQ("", value); + + { + rlz_lib::SupplementaryBranding branding("TEST"); + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + value, 50)); + EXPECT_STREQ("events=I7S,W1I", value); + } + + // Record both events (one is stateless and the other is stateful) again. + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); + EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL)); + + // Check the stateful event won't be sent again while the stateless one will. + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + value, 50)); + EXPECT_STREQ("events=I7S", value); + + { + rlz_lib::SupplementaryBranding branding("TEST"); + EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER, + kPingResponse)); + + EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + value, 50)); + EXPECT_STREQ("", value); + } + + EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER, + value, 50)); + EXPECT_STREQ("events=I7S", value); +} + +#if defined(OS_MACOSX) +class ReadonlyRlzDirectoryTest : public RlzLibTestNoMachineState { + protected: + virtual void SetUp() OVERRIDE; +}; + +void ReadonlyRlzDirectoryTest::SetUp() { + RlzLibTestNoMachineState::SetUp(); + // Make the rlz directory non-writeable. + chmod(temp_dir_.path().value().c_str(), 0500); +} + +TEST_F(ReadonlyRlzDirectoryTest, WriteFails) { + // The rlz test runner runs every test twice: Once normally, and once with + // a SupplementaryBranding on the stack. In the latter case, the rlz lock + // has already been acquired before the rlz directory got changed to + // read-only, which makes this test pointless. So run it only in the first + // pass. + if (!rlz_lib::SupplementaryBranding::GetBrand().empty()) + return; + + EXPECT_FALSE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE)); +} + +// Regression test for http://crbug.com/121255 +TEST_F(ReadonlyRlzDirectoryTest, SupplementaryBrandingDoesNotCrash) { + // See the comment at the top of WriteFails. + if (!rlz_lib::SupplementaryBranding::GetBrand().empty()) + return; + + rlz_lib::SupplementaryBranding branding("TEST"); + EXPECT_FALSE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER, + rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::INSTALL)); +} +#endif diff --git a/rlz/lib/rlz_value_store.h b/rlz/lib/rlz_value_store.h new file mode 100644 index 0000000..d41421b --- /dev/null +++ b/rlz/lib/rlz_value_store.h @@ -0,0 +1,116 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RLZ_VALUE_STORE_H_ +#define RLZ_VALUE_STORE_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "rlz/lib/rlz_enums.h" + +#if defined(OS_WIN) +#include "rlz/win/lib/lib_mutex.h" +#endif + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif + + +#include <string> +#include <vector> + +class FilePath; + +namespace rlz_lib { + +// Abstracts away rlz's key value store. On windows, this usually writes to +// the registry. On mac, it writes to an NSDefaults object. +class RlzValueStore { + public: + virtual ~RlzValueStore() {} + + enum AccessType { kReadAccess, kWriteAccess }; + virtual bool HasAccess(AccessType type) = 0; + + // Ping times. + virtual bool WritePingTime(Product product, int64 time) = 0; + virtual bool ReadPingTime(Product product, int64* time) = 0; + virtual bool ClearPingTime(Product product) = 0; + + // Access point RLZs. + virtual bool WriteAccessPointRlz(AccessPoint access_point, + const char* new_rlz) = 0; + virtual bool ReadAccessPointRlz(AccessPoint access_point, + char* rlz, // At most kMaxRlzLength + 1 bytes + size_t rlz_size) = 0; + virtual bool ClearAccessPointRlz(AccessPoint access_point) = 0; + + // Product events. + // Stores |event_rlz| for product |product| as product event. + virtual bool AddProductEvent(Product product, const char* event_rlz) = 0; + // Appends all events for |product| to |events|, in arbirtrary order. + virtual bool ReadProductEvents(Product product, + std::vector<std::string>* events) = 0; + // Removes the stored event |event_rlz| for |product| if it exists. + virtual bool ClearProductEvent(Product product, const char* event_rlz) = 0; + // Removes all stored product events for |product|. + virtual bool ClearAllProductEvents(Product product) = 0; + + // Stateful events. + // Stores |event_rlz| for product |product| as stateful event. + virtual bool AddStatefulEvent(Product product, const char* event_rlz) = 0; + // Checks if |event_rlz| has been stored as stateful event for |product|. + virtual bool IsStatefulEvent(Product product, const char* event_rlz) = 0; + // Removes all stored stateful events for |product|. + virtual bool ClearAllStatefulEvents(Product product) = 0; + + // Tells the value store to clean up unimportant internal data structures, for + // example empty registry folders, that might remain after clearing other + // data. Best-effort. + virtual void CollectGarbage() = 0; +}; + +// All methods of RlzValueStore must stays consistent even when accessed from +// multiple threads in multiple processes. To enforce this through the type +// system, the only way to access the RlzValueStore is through a +// ScopedRlzValueStoreLock, which is a cross-process lock. It is active while +// it is in scope. If the class fails to acquire a lock, its GetStore() method +// returns NULL. If the lock fails to be acquired, it must not be taken +// recursively. That is, all user code should look like this: +// ScopedRlzValueStoreLock lock; +// RlzValueStore* store = lock.GetStore(); +// if (!store) +// return some_error_code; +// ... +class ScopedRlzValueStoreLock { + public: + ScopedRlzValueStoreLock(); + ~ScopedRlzValueStoreLock(); + + // Returns a RlzValueStore protected by a cross-process lock, or NULL if the + // lock can't be obtained. The lifetime of the returned object is limited to + // the lifetime of this ScopedRlzValueStoreLock object. + RlzValueStore* GetStore(); + + private: + scoped_ptr<RlzValueStore> store_; +#if defined(OS_WIN) + LibMutex lock_; +#else + base::mac::ScopedNSAutoreleasePool autorelease_pool_; +#endif +}; + +#if defined(OS_MACOSX) +namespace testing { +// Prefix |directory| to the path where the RLZ data file lives, for tests. +void SetRlzStoreDirectory(const FilePath& directory); +} // namespace testing +#endif // defined(OS_MACOSX) + + +} // namespace rlz_lib + +#endif // RLZ_VALUE_STORE_H_ diff --git a/rlz/lib/string_utils.cc b/rlz/lib/string_utils.cc new file mode 100644 index 0000000..50206a8 --- /dev/null +++ b/rlz/lib/string_utils.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// String manipulation functions used in the RLZ library. + +#include "rlz/lib/string_utils.h" + +#include "rlz/lib/assert.h" + +namespace rlz_lib { + +bool IsAscii(char letter) { + return (letter >= 0x0 && letter < 0x80); +} + +bool GetHexValue(char letter, int* value) { + if (!value) { + ASSERT_STRING("GetHexValue: Invalid output paramter"); + return false; + } + *value = 0; + + if (letter >= '0' && letter <= '9') + *value = letter - '0'; + else if (letter >= 'a' && letter <= 'f') + *value = (letter - 'a') + 0xA; + else if (letter >= 'A' && letter <= 'F') + *value = (letter - 'A') + 0xA; + else + return false; + + return true; +} + +int HexStringToInteger(const char* text) { + if (!text) { + ASSERT_STRING("HexStringToInteger: text is NULL."); + return 0; + } + + int idx = 0; + // Ignore leading whitespace. + while (text[idx] == '\t' || text[idx] == ' ') + idx++; + + if ((text[idx] == '0') && + (text[idx + 1] == 'X' || text[idx + 1] == 'x')) + idx +=2; // String is of the form 0x... + + int number = 0; + int digit = 0; + for (; text[idx] != '\0'; idx++) { + if (!GetHexValue(text[idx], &digit)) { + // Ignore trailing whitespaces, but assert on other trailing characters. + bool only_whitespaces = true; + while (only_whitespaces && text[idx]) + only_whitespaces = (text[idx++] == ' '); + if (!only_whitespaces) + ASSERT_STRING("HexStringToInteger: text contains non-hex characters."); + return number; + } + number = (number << 4) | digit; + } + + return number; +} + +bool BytesToString(const unsigned char* data, + int data_len, + std::string* string) { + if (!string) + return false; + + string->clear(); + if (data_len < 1 || !data) + return false; + + static const char kHex[] = "0123456789ABCDEF"; + + // Fix the buffer size to begin with to avoid repeated re-allocation. + string->resize(data_len * 2); + int index = data_len; + while (index--) { + string->at(2 * index) = kHex[data[index] >> 4]; // high digit + string->at(2 * index + 1) = kHex[data[index] & 0x0F]; // low digit + } + + return true; +} + +} // namespace rlz_lib diff --git a/rlz/lib/string_utils.h b/rlz/lib/string_utils.h new file mode 100644 index 0000000..6f64e58 --- /dev/null +++ b/rlz/lib/string_utils.h @@ -0,0 +1,26 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// String manipulation functions used in the RLZ library. + +#ifndef RLZ_LIB_STRING_UTILS_H_ +#define RLZ_LIB_STRING_UTILS_H_ + +#include <string> + +namespace rlz_lib { + +bool IsAscii(char letter); + +bool BytesToString(const unsigned char* data, + int data_len, + std::string* string); + +bool GetHexValue(char letter, int* value); + +int HexStringToInteger(const char* text); + +}; // namespace + +#endif // RLZ_LIB_STRING_UTILS_H_ diff --git a/rlz/lib/string_utils_unittest.cc b/rlz/lib/string_utils_unittest.cc new file mode 100644 index 0000000..82a6edb --- /dev/null +++ b/rlz/lib/string_utils_unittest.cc @@ -0,0 +1,69 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Unit test for string manipulation functions used in the RLZ library. + +#include "rlz/lib/string_utils.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "rlz/lib/assert.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(StringUtilsUnittest, IsAscii) { + rlz_lib::SetExpectedAssertion(""); + + char bad_letters[] = {'\x80', '\xA0', '\xFF'}; + for (size_t i = 0; i < arraysize(bad_letters); ++i) + EXPECT_FALSE(rlz_lib::IsAscii(bad_letters[i])); + + char good_letters[] = {'A', '~', '\n', 0x7F, 0x00}; + for (size_t i = 0; i < arraysize(good_letters); ++i) + EXPECT_TRUE(rlz_lib::IsAscii(good_letters[i])); +} + +TEST(StringUtilsUnittest, HexStringToInteger) { + rlz_lib::SetExpectedAssertion("HexStringToInteger: text is NULL."); + EXPECT_EQ(0, rlz_lib::HexStringToInteger(NULL)); + + rlz_lib::SetExpectedAssertion(""); + EXPECT_EQ(0, rlz_lib::HexStringToInteger("")); + EXPECT_EQ(0, rlz_lib::HexStringToInteger(" ")); + EXPECT_EQ(0, rlz_lib::HexStringToInteger(" 0x ")); + EXPECT_EQ(0, rlz_lib::HexStringToInteger(" 0x0 ")); + EXPECT_EQ(0x12345, rlz_lib::HexStringToInteger("12345")); + EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger("a34Ed0")); + EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger("0xa34Ed0")); + EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger(" 0xa34Ed0")); + EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger("0xa34Ed0 ")); + EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger(" 0xa34Ed0 ")); + EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger(" 0x000a34Ed0 ")); + EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger(" 000a34Ed0 ")); + + rlz_lib::SetExpectedAssertion( + "HexStringToInteger: text contains non-hex characters."); + EXPECT_EQ(0x12ff, rlz_lib::HexStringToInteger("12ffg")); + EXPECT_EQ(0x12f, rlz_lib::HexStringToInteger("12f 121")); + EXPECT_EQ(0x12f, rlz_lib::HexStringToInteger("12f 121")); + EXPECT_EQ(0, rlz_lib::HexStringToInteger("g12f")); + EXPECT_EQ(0, rlz_lib::HexStringToInteger(" 0x0 \n")); + + rlz_lib::SetExpectedAssertion(""); +} + +TEST(StringUtilsUnittest, TestBytesToString) { + unsigned char data[] = {0x1E, 0x00, 0x21, 0x67, 0xFF}; + std::string result; + + EXPECT_FALSE(rlz_lib::BytesToString(NULL, 5, &result)); + EXPECT_FALSE(rlz_lib::BytesToString(data, 5, NULL)); + EXPECT_FALSE(rlz_lib::BytesToString(NULL, 5, NULL)); + + EXPECT_TRUE(rlz_lib::BytesToString(data, 5, &result)); + EXPECT_EQ(std::string("1E002167FF"), result); + EXPECT_TRUE(rlz_lib::BytesToString(data, 4, &result)); + EXPECT_EQ(std::string("1E002167"), result); +} |