diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-26 00:39:45 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-26 00:39:45 +0000 |
commit | 6dba1dd2efa3ed67bd41312bc682fc12c3c69544 (patch) | |
tree | bfb0bca756ad3e274227d085b82dedda508424fe | |
parent | 4a44f6610eb14a728a22a9dea00d6707d2e616e1 (diff) | |
download | chromium_src-6dba1dd2efa3ed67bd41312bc682fc12c3c69544.zip chromium_src-6dba1dd2efa3ed67bd41312bc682fc12c3c69544.tar.gz chromium_src-6dba1dd2efa3ed67bd41312bc682fc12c3c69544.tar.bz2 |
Revert 144071 - Add a regenerate button to regenerate the password in Windows.
BUG=120480
TEST=Not tested.
Review URL: https://chromiumcodereview.appspot.com/10642009
TBR=zysxqn@google.com
Review URL: https://chromiumcodereview.appspot.com/10659022
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@144074 0039d316-1c4b-4281-b951-d872f2087c98
55 files changed, 6986 insertions, 34 deletions
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc index 09f410f..615f495 100644 --- a/chrome/browser/ui/views/frame/browser_view.cc +++ b/chrome/browser/ui/views/frame/browser_view.cc @@ -2487,8 +2487,6 @@ void BrowserView::ShowPasswordGenerationBubble( const gfx::Rect& rect, autofill::PasswordGenerator* password_generator, const webkit::forms::PasswordForm& form) { - ui::ThemeProvider* theme_provider = GetWidget()->GetThemeProvider(); - // Create a rect in the content bounds that the bubble will point to. gfx::Point origin(rect.origin()); views::View::ConvertPointToScreen(GetTabContentsContainerView(), &origin); @@ -2507,8 +2505,7 @@ void BrowserView::ShowPasswordGenerationBubble( tab_contents->web_contents()->GetRenderViewHost(), password_generator, browser_.get(), - tab_contents->password_manager(), - theme_provider); + tab_contents->password_manager()); views::BubbleDelegateView::CreateBubble(bubble); bubble->SetAlignment(views::BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR); diff --git a/chrome/browser/ui/views/password_generation_bubble_view.cc b/chrome/browser/ui/views/password_generation_bubble_view.cc index 6800a99..1e82e3a 100644 --- a/chrome/browser/ui/views/password_generation_bubble_view.cc +++ b/chrome/browser/ui/views/password_generation_bubble_view.cc @@ -15,10 +15,7 @@ #include "content/public/browser/render_view_host.h" #include "googleurl/src/gurl.h" #include "grit/generated_resources.h" -#include "grit/theme_resources_standard.h" -#include "ui/base/theme_provider.h" #include "ui/base/l10n/l10n_util.h" -#include "ui/views/controls/button/image_button.h" #include "ui/views/controls/button/text_button.h" #include "ui/views/controls/label.h" #include "ui/views/controls/link.h" @@ -36,19 +33,16 @@ PasswordGenerationBubbleView::PasswordGenerationBubbleView( content::RenderViewHost* render_view_host, autofill::PasswordGenerator* password_generator, content::PageNavigator* navigator, - PasswordManager* password_manager, - ui::ThemeProvider* theme_provider) + PasswordManager* password_manager) : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_LEFT), accept_button_(NULL), - regenerate_button_(NULL), text_field_(NULL), anchor_rect_(anchor_rect), form_(form), render_view_host_(render_view_host), password_generator_(password_generator), navigator_(navigator), - password_manager_(password_manager), - theme_provider_(theme_provider) {} + password_manager_(password_manager) {} PasswordGenerationBubbleView::~PasswordGenerationBubbleView() {} @@ -58,15 +52,6 @@ void PasswordGenerationBubbleView::Init() { accept_button_ = new views::NativeTextButton(this, ASCIIToUTF16("Try It")); - regenerate_button_ = new views::ImageButton(this); - regenerate_button_->SetImage(views::CustomButton::BS_NORMAL, - theme_provider_->GetImageSkiaNamed(IDR_RELOAD)); - regenerate_button_->SetImage(views::CustomButton::BS_HOT, - theme_provider_->GetImageSkiaNamed(IDR_RELOAD_H)); - regenerate_button_->SetImage(views::CustomButton::BS_PUSHED, - theme_provider_->GetImageSkiaNamed(IDR_RELOAD_P)); - regenerate_button_->SetTooltipText(ASCIIToUTF16("Regenerate")); - text_field_ = new views::Textfield(); text_field_->SetText( ASCIIToUTF16(password_generator_->Generate())); @@ -94,8 +79,6 @@ void PasswordGenerationBubbleView::Init() { cs = layout->AddColumnSet(1); cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 100); - cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0, GridLayout::FIXED, - regenerate_button_->GetPreferredSize().width(), 100); cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing); cs->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0); @@ -106,7 +89,6 @@ void PasswordGenerationBubbleView::Init() { layout->StartRow(0, 1); layout->AddView(text_field_); - layout->AddView(regenerate_button_); layout->AddView(accept_button_); } @@ -121,9 +103,6 @@ void PasswordGenerationBubbleView::ButtonPressed(views::Button* sender, render_view_host_->GetRoutingID(), text_field_->text())); password_manager_->SetFormHasGeneratedPassword(form_); StartFade(false); - } else if (sender == regenerate_button_) { - text_field_->SetText( - ASCIIToUTF16(password_generator_->Generate())); } } diff --git a/chrome/browser/ui/views/password_generation_bubble_view.h b/chrome/browser/ui/views/password_generation_bubble_view.h index 7fbf214..9fe5c7a6 100644 --- a/chrome/browser/ui/views/password_generation_bubble_view.h +++ b/chrome/browser/ui/views/password_generation_bubble_view.h @@ -24,7 +24,6 @@ class RenderViewHost; } namespace views { -class ImageButton; class TextButton; class Textfield; } @@ -45,8 +44,7 @@ class PasswordGenerationBubbleView : public views::BubbleDelegateView, content::RenderViewHost* render_view_host, autofill::PasswordGenerator* password_generator, content::PageNavigator* navigator, - PasswordManager* password_manager, - ui::ThemeProvider* theme_provider); + PasswordManager* password_manager); virtual ~PasswordGenerationBubbleView(); private: @@ -66,7 +64,6 @@ class PasswordGenerationBubbleView : public views::BubbleDelegateView, // Subviews views::TextButton* accept_button_; - views::ImageButton* regenerate_button_; views::Textfield* text_field_; // Location that the bubble points to @@ -88,9 +85,6 @@ class PasswordGenerationBubbleView : public views::BubbleDelegateView, // PasswordManager associated with this tab. PasswordManager* password_manager_; - // Theme provider used to draw the regenerate button. - ui::ThemeProvider* theme_provider_; - DISALLOW_COPY_AND_ASSIGN(PasswordGenerationBubbleView); }; diff --git a/rlz/DEPS b/rlz/DEPS new file mode 100644 index 0000000..aba5be2 --- /dev/null +++ b/rlz/DEPS @@ -0,0 +1,70 @@ +# 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. +# +# This DEPS file exists so that it can capture the dependenciez of RLZ. This +# allows external projects to check it out and build it independently from all +# of chrome. + +vars = { + "chrev": "@119173" +} + +deps = { + "src/base": + "http://src.chromium.org/svn/trunk/src/base" + Var("chrev"), + + "src/build": + "http://src.chromium.org/svn/trunk/src/build" + Var("chrev"), + + "src/third_party/icu": + "http://src.chromium.org/svn/trunk/deps/third_party/icu42" + Var("chrev"), + + "src/third_party/modp_b64": + "http://src.chromium.org/svn/trunk/src/third_party/modp_b64" + Var("chrev"), + + "src/third_party/nss": + "http://src.chromium.org/svn/trunk/deps/third_party/nss" + Var("chrev"), + + "src/third_party/sqlite": + "http://src.chromium.org/svn/trunk/src/third_party/sqlite" + Var("chrev"), + + "src/third_party/wtl": + "http://src.chromium.org/svn/trunk/src/third_party/wtl" + Var("chrev"), + + "src/third_party/zlib": + "http://src.chromium.org/svn/trunk/src/third_party/zlib" + Var("chrev"), + + "src/testing": + "http://src.chromium.org/svn/trunk/src/testing" + Var("chrev"), + + "src/testing/gmock": + "http://googlemock.googlecode.com/svn/trunk@374", + + "src/testing/gtest": + "http://googletest.googlecode.com/svn/trunk@492", + + "src/tools/gyp": + "http://gyp.googlecode.com/svn/trunk@1233", + + "src/tools/win": + "http://src.chromium.org/svn/trunk/src/tools/win" + Var("chrev"), + + # If using rlz with chrome's networking library, add it and its dependencies + # here. +} + +include_rules = [ + "+build", + "+net", # This is only used when force_rlz_use_chrome_net=1 is passed to gyp. + "+third_party/zlib", +] + +hooks = [ + { + # A change to a .gyp, .gypi, or to GYP itself should run the generator. + "pattern": ".", + "action": ["python", "src/build/gyp_chromium", "src/rlz/rlz.gyp"], + } +] + diff --git a/rlz/OWNERS b/rlz/OWNERS new file mode 100644 index 0000000..339fb11 --- /dev/null +++ b/rlz/OWNERS @@ -0,0 +1,4 @@ +set noparent +rogerta@chromium.org +gwilson@chromium.org +thakis@chromium.org 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..b8c10f0 --- /dev/null +++ b/rlz/lib/machine_id.cc @@ -0,0 +1,84 @@ +// 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/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); +} diff --git a/rlz/mac/lib/machine_id_mac.cc b/rlz/mac/lib/machine_id_mac.cc new file mode 100644 index 0000000..2198978 --- /dev/null +++ b/rlz/mac/lib/machine_id_mac.cc @@ -0,0 +1,147 @@ +// 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 <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/network/IOEthernetInterface.h> +#include <IOKit/network/IONetworkInterface.h> +#include <IOKit/network/IOEthernetController.h> + +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_ioobject.h" +#include "base/string16.h" +#include "base/stringprintf.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" + +namespace rlz_lib { + +namespace { + +// See http://developer.apple.com/library/mac/#technotes/tn1103/_index.html + +// The caller is responsible for freeing |matching_services|. +bool FindEthernetInterfaces(io_iterator_t* matching_services) { + base::mac::ScopedCFTypeRef<CFMutableDictionaryRef> matching_dict( + IOServiceMatching(kIOEthernetInterfaceClass)); + if (!matching_dict) + return false; + + base::mac::ScopedCFTypeRef<CFMutableDictionaryRef> primary_interface( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + if (!primary_interface) + return false; + + CFDictionarySetValue( + primary_interface, CFSTR(kIOPrimaryInterface), kCFBooleanTrue); + CFDictionarySetValue( + matching_dict, CFSTR(kIOPropertyMatchKey), primary_interface); + + kern_return_t kern_result = IOServiceGetMatchingServices( + kIOMasterPortDefault, matching_dict.release(), matching_services); + + return kern_result == KERN_SUCCESS; +} + +bool GetMACAddressFromIterator(io_iterator_t primary_interface_iterator, + uint8_t* buffer, size_t buffer_size) { + if (buffer_size < kIOEthernetAddressSize) + return false; + + bool success = false; + + bzero(buffer, buffer_size); + base::mac::ScopedIOObject<io_object_t> primary_interface; + while (primary_interface.reset(IOIteratorNext(primary_interface_iterator)), + primary_interface) { + io_object_t primary_interface_parent; + kern_return_t kern_result = IORegistryEntryGetParentEntry( + primary_interface, kIOServicePlane, &primary_interface_parent); + base::mac::ScopedIOObject<io_object_t> primary_interface_parent_deleter( + primary_interface_parent); + success = kern_result == KERN_SUCCESS; + + if (!success) + continue; + + base::mac::ScopedCFTypeRef<CFTypeRef> mac_data( + IORegistryEntryCreateCFProperty(primary_interface_parent, + CFSTR(kIOMACAddress), + kCFAllocatorDefault, + 0)); + CFDataRef mac_data_data = base::mac::CFCast<CFDataRef>(mac_data); + if (mac_data_data) { + CFDataGetBytes( + mac_data_data, CFRangeMake(0, kIOEthernetAddressSize), buffer); + } + } + + return success; +} + +bool GetMacAddress(unsigned char* buffer, size_t size) { + io_iterator_t primary_interface_iterator; + if (!FindEthernetInterfaces(&primary_interface_iterator)) + return false; + bool result = GetMACAddressFromIterator( + primary_interface_iterator, buffer, size); + IOObjectRelease(primary_interface_iterator); + return result; +} + +CFStringRef CopySerialNumber() { + base::mac::ScopedIOObject<io_service_t> expert_device( + IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching("IOPlatformExpertDevice"))); + if (!expert_device) + return NULL; + + base::mac::ScopedCFTypeRef<CFTypeRef> serial_number( + IORegistryEntryCreateCFProperty(expert_device, + CFSTR(kIOPlatformSerialNumberKey), + kCFAllocatorDefault, + 0)); + CFStringRef serial_number_cfstring = + base::mac::CFCast<CFStringRef>(serial_number); + if (!serial_number_cfstring) + return NULL; + + ignore_result(serial_number.release()); + return serial_number_cfstring; +} + +} // namespace + +bool GetRawMachineId(string16* data, int* more_data) { + uint8_t mac_address[kIOEthernetAddressSize]; + + data->clear(); + if (GetMacAddress(mac_address, sizeof(mac_address))) { + *data += ASCIIToUTF16(StringPrintf("mac:%02x%02x%02x%02x%02x%02x", + mac_address[0], mac_address[1], mac_address[2], + mac_address[3], mac_address[4], mac_address[5])); + } + + // A MAC address is enough to uniquely identify a machine, but it's only 6 + // bytes, 3 of which are manufacturer-determined. To make brute-forcing the + // SHA1 of this harder, also append the system's serial number. + CFStringRef serial = CopySerialNumber(); + if (serial) { + if (!data->empty()) + *data += UTF8ToUTF16(" "); + *data += UTF8ToUTF16("serial:") + base::SysCFStringRefToUTF16(serial); + CFRelease(serial); + } + + // On windows, this is set to the volume id. Since it's not scrambled before + // being sent, just set it to 1. + *more_data = 1; + return true; +} + +} // namespace rlz_lib diff --git a/rlz/mac/lib/rlz_value_store_mac.h b/rlz/mac/lib/rlz_value_store_mac.h new file mode 100644 index 0000000..b7ffb4e --- /dev/null +++ b/rlz/mac/lib/rlz_value_store_mac.h @@ -0,0 +1,80 @@ +// 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_MAC_LIB_RLZ_VALUE_STORE_MAC_H_ +#define RLZ_MAC_LIB_RLZ_VALUE_STORE_MAC_H_ + +#include "rlz/lib/rlz_value_store.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_nsobject.h" + +@class NSDictionary; +@class NSMutableDictionary; + +namespace rlz_lib { + +// An implementation of RlzValueStore for mac. It stores information in a +// plist file in the user's Application Support folder. +class RlzValueStoreMac : public RlzValueStore { + public: + virtual bool HasAccess(AccessType type) OVERRIDE; + + virtual bool WritePingTime(Product product, int64 time) OVERRIDE; + virtual bool ReadPingTime(Product product, int64* time) OVERRIDE; + virtual bool ClearPingTime(Product product) OVERRIDE; + + virtual bool WriteAccessPointRlz(AccessPoint access_point, + const char* new_rlz) OVERRIDE; + virtual bool ReadAccessPointRlz(AccessPoint access_point, + char* rlz, + size_t rlz_size) OVERRIDE; + virtual bool ClearAccessPointRlz(AccessPoint access_point) OVERRIDE; + + virtual bool AddProductEvent(Product product, const char* event_rlz) OVERRIDE; + virtual bool ReadProductEvents(Product product, + std::vector<std::string>* events) OVERRIDE; + virtual bool ClearProductEvent(Product product, + const char* event_rlz) OVERRIDE; + virtual bool ClearAllProductEvents(Product product) OVERRIDE; + + virtual bool AddStatefulEvent(Product product, + const char* event_rlz) OVERRIDE; + virtual bool IsStatefulEvent(Product product, + const char* event_rlz) OVERRIDE; + virtual bool ClearAllStatefulEvents(Product product) OVERRIDE; + + virtual void CollectGarbage() OVERRIDE; + + private: + // |dict| is the dictionary that backs all data. plist_path is the name of the + // plist file, used solely for implementing HasAccess(). + RlzValueStoreMac(NSMutableDictionary* dict, NSString* plist_path); + virtual ~RlzValueStoreMac(); + friend class ScopedRlzValueStoreLock; + + // Returns the backing dictionary that should be written to disk. + NSDictionary* dictionary(); + + // Returns the dictionary to which all data should be written. Usually, this + // is just |dictionary()|, but if supplementary branding is used, it's a + // subdirectory at key "brand_<supplementary branding code>". + // Note that windows stores data at + // rlz/name (e.g. "pingtime")/supplementalbranding/productcode + // Mac on the other hand does + // supplementalbranding/productcode/pingtime. + NSMutableDictionary* WorkingDict(); + + // Returns the subdirectory of |WorkingDict()| used to store data for + // product p. + NSMutableDictionary* ProductDict(Product p); + + scoped_nsobject<NSMutableDictionary> dict_; + scoped_nsobject<NSString> plist_path_; + + DISALLOW_COPY_AND_ASSIGN(RlzValueStoreMac); +}; + +} // namespace rlz_lib + +#endif // RLZ_MAC_LIB_RLZ_VALUE_STORE_MAC_H_ diff --git a/rlz/mac/lib/rlz_value_store_mac.mm b/rlz/mac/lib/rlz_value_store_mac.mm new file mode 100644 index 0000000..5313a30 --- /dev/null +++ b/rlz/mac/lib/rlz_value_store_mac.mm @@ -0,0 +1,447 @@ +// 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/mac/lib/rlz_value_store_mac.h" + +#include "base/mac/foundation_util.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/sys_string_conversions.h" +#include "rlz/lib/assert.h" +#include "rlz/lib/lib_values.h" +#include "rlz/lib/rlz_lib.h" + +#import <Foundation/Foundation.h> +#include <pthread.h> + +using base::mac::ObjCCast; + +namespace rlz_lib { + +// These are written to disk and should not be changed. +NSString* const kPingTimeKey = @"pingTime"; +NSString* const kAccessPointKey = @"accessPoints"; +NSString* const kProductEventKey = @"productEvents"; +NSString* const kStatefulEventKey = @"statefulEvents"; + +namespace { + +NSString* GetNSProductName(Product product) { + return base::SysUTF8ToNSString(GetProductName(product)); +} + +NSString* GetNSAccessPointName(AccessPoint p) { + return base::SysUTF8ToNSString(GetAccessPointName(p)); +} + +// Retrieves a subdictionary in |p| for key |k|, creating it if necessary. +// If the dictionary contains an object for |k| that is not a mutable +// dictionary, that object is replaced with an empty mutable dictinary. +NSMutableDictionary* GetOrCreateDict( + NSMutableDictionary* p, NSString* k) { + NSMutableDictionary* d = ObjCCast<NSMutableDictionary>([p objectForKey:k]); + if (!d) { + d = [NSMutableDictionary dictionaryWithCapacity:0]; + [p setObject:d forKey:k]; + } + return d; +} + +} // namespace + +RlzValueStoreMac::RlzValueStoreMac(NSMutableDictionary* dict, + NSString* plist_path) + : dict_([dict retain]), plist_path_([plist_path retain]) { +} + +RlzValueStoreMac::~RlzValueStoreMac() { +} + +bool RlzValueStoreMac::HasAccess(AccessType type) { + NSFileManager* manager = [NSFileManager defaultManager]; + switch (type) { + case kReadAccess: return [manager isReadableFileAtPath:plist_path_]; + case kWriteAccess: return [manager isWritableFileAtPath:plist_path_]; + } +} + +bool RlzValueStoreMac::WritePingTime(Product product, int64 time) { + NSNumber* n = [NSNumber numberWithLongLong:time]; + [ProductDict(product) setObject:n forKey:kPingTimeKey]; + return true; +} + +bool RlzValueStoreMac::ReadPingTime(Product product, int64* time) { + if (NSNumber* n = + ObjCCast<NSNumber>([ProductDict(product) objectForKey:kPingTimeKey])) { + *time = [n longLongValue]; + return true; + } + return false; +} + +bool RlzValueStoreMac::ClearPingTime(Product product) { + [ProductDict(product) removeObjectForKey:kPingTimeKey]; + return true; +} + + +bool RlzValueStoreMac::WriteAccessPointRlz(AccessPoint access_point, + const char* new_rlz) { + NSMutableDictionary* d = GetOrCreateDict(WorkingDict(), kAccessPointKey); + [d setObject:base::SysUTF8ToNSString(new_rlz) + forKey:GetNSAccessPointName(access_point)]; + return true; +} + +bool RlzValueStoreMac::ReadAccessPointRlz(AccessPoint access_point, + char* rlz, + size_t rlz_size) { + // Reading a non-existent access point counts as success. + if (NSDictionary* d = ObjCCast<NSDictionary>( + [WorkingDict() objectForKey:kAccessPointKey])) { + NSString* val = ObjCCast<NSString>( + [d objectForKey:GetNSAccessPointName(access_point)]); + if (!val) { + if (rlz_size > 0) + rlz[0] = '\0'; + return true; + } + + std::string s = base::SysNSStringToUTF8(val); + if (s.size() >= rlz_size) { + rlz[0] = 0; + ASSERT_STRING("GetAccessPointRlz: Insufficient buffer size"); + return false; + } + strncpy(rlz, s.c_str(), rlz_size); + return true; + } + if (rlz_size > 0) + rlz[0] = '\0'; + return true; +} + +bool RlzValueStoreMac::ClearAccessPointRlz(AccessPoint access_point) { + if (NSMutableDictionary* d = ObjCCast<NSMutableDictionary>( + [WorkingDict() objectForKey:kAccessPointKey])) { + [d removeObjectForKey:GetNSAccessPointName(access_point)]; + } + return true; +} + + +bool RlzValueStoreMac::AddProductEvent(Product product, + const char* event_rlz) { + [GetOrCreateDict(ProductDict(product), kProductEventKey) + setObject:[NSNumber numberWithBool:YES] + forKey:base::SysUTF8ToNSString(event_rlz)]; + return true; +} + +bool RlzValueStoreMac::ReadProductEvents(Product product, + std::vector<std::string>* events) { + if (NSDictionary* d = ObjCCast<NSDictionary>( + [ProductDict(product) objectForKey:kProductEventKey])) { + for (NSString* s in d) + events->push_back(base::SysNSStringToUTF8(s)); + return true; + } + return true; +} + +bool RlzValueStoreMac::ClearProductEvent(Product product, + const char* event_rlz) { + if (NSMutableDictionary* d = ObjCCast<NSMutableDictionary>( + [ProductDict(product) objectForKey:kProductEventKey])) { + [d removeObjectForKey:base::SysUTF8ToNSString(event_rlz)]; + return true; + } + return false; +} + +bool RlzValueStoreMac::ClearAllProductEvents(Product product) { + [ProductDict(product) removeObjectForKey:kProductEventKey]; + return true; +} + + +bool RlzValueStoreMac::AddStatefulEvent(Product product, + const char* event_rlz) { + [GetOrCreateDict(ProductDict(product), kStatefulEventKey) + setObject:[NSNumber numberWithBool:YES] + forKey:base::SysUTF8ToNSString(event_rlz)]; + return true; +} + +bool RlzValueStoreMac::IsStatefulEvent(Product product, + const char* event_rlz) { + if (NSDictionary* d = ObjCCast<NSDictionary>( + [ProductDict(product) objectForKey:kStatefulEventKey])) { + return [d objectForKey:base::SysUTF8ToNSString(event_rlz)] != nil; + } + return false; +} + +bool RlzValueStoreMac::ClearAllStatefulEvents(Product product) { + [ProductDict(product) removeObjectForKey:kStatefulEventKey]; + return true; +} + + +void RlzValueStoreMac::CollectGarbage() { + NOTIMPLEMENTED(); +} + +NSDictionary* RlzValueStoreMac::dictionary() { + return dict_.get(); +} + +NSMutableDictionary* RlzValueStoreMac::WorkingDict() { + std::string brand(SupplementaryBranding::GetBrand()); + if (brand.empty()) + return dict_; + + NSString* brand_ns = + [@"brand_" stringByAppendingString:base::SysUTF8ToNSString(brand)]; + + return GetOrCreateDict(dict_.get(), brand_ns); +} + +NSMutableDictionary* RlzValueStoreMac::ProductDict(Product p) { + return GetOrCreateDict(WorkingDict(), GetNSProductName(p)); +} + + +namespace { + +// Creating a recursive cross-process mutex on windows is one line. On mac, +// there's no primitve for that, so this lock is emulated by an in-process +// mutex to get the recursive part, followed by a cross-process lock for the +// cross-process part. + +// This is a struct so that it doesn't need a static initializer. +struct RecursiveCrossProcessLock { + // Tries to acquire a recursive cross-process lock. Note that this _always_ + // acquires the in-process lock (if it wasn't already acquired). The parent + // directory of |lock_file| must exist. + bool TryGetCrossProcessLock(NSString* lock_filename); + + // Releases the lock. Should always be called, even if + // TryGetCrossProcessLock() returns false. + void ReleaseLock(); + + pthread_mutex_t recursive_lock_; + pthread_t locking_thread_; + + NSDistributedLock* file_lock_; +} g_recursive_lock = { + // PTHREAD_RECURSIVE_MUTEX_INITIALIZER doesn't exist before 10.7 and is buggy + // on 10.7 (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51906#c34), so emulate + // recursive locking with a normal non-recursive mutex. + PTHREAD_MUTEX_INITIALIZER +}; + +bool RecursiveCrossProcessLock::TryGetCrossProcessLock( + NSString* lock_filename) { + bool just_got_lock = false; + + // Emulate a recursive mutex with a non-recursive one. + if (pthread_mutex_trylock(&recursive_lock_) == EBUSY) { + if (pthread_equal(pthread_self(), locking_thread_) == 0) { + // Some other thread has the lock, wait for it. + pthread_mutex_lock(&recursive_lock_); + CHECK(locking_thread_ == 0); + just_got_lock = true; + } + } else { + just_got_lock = true; + } + + locking_thread_ = pthread_self(); + + // Try to acquire file lock. + if (just_got_lock) { + const int kMaxTimeoutMS = 5000; // Matches windows. + const int kSleepPerTryMS = 200; + + CHECK(!file_lock_); + file_lock_ = [[NSDistributedLock alloc] initWithPath:lock_filename]; + + BOOL got_file_lock = NO; + int elapsedMS = 0; + while (!(got_file_lock = [file_lock_ tryLock]) && + elapsedMS < kMaxTimeoutMS) { + usleep(kSleepPerTryMS * 1000); + elapsedMS += kSleepPerTryMS; + } + + if (!got_file_lock) { + [file_lock_ release]; + file_lock_ = nil; + return false; + } + return true; + } else { + return file_lock_ != nil; + } +} + +void RecursiveCrossProcessLock::ReleaseLock() { + if (file_lock_) { + [file_lock_ unlock]; + [file_lock_ release]; + file_lock_ = nil; + } + + locking_thread_ = 0; + pthread_mutex_unlock(&recursive_lock_); +} + + +// This is set during test execution, to write RLZ files into a temporary +// directory instead of the user's Application Support folder. +NSString* g_test_folder; + +// RlzValueStoreMac keeps its data in memory and only writes it to disk when +// ScopedRlzValueStoreLock goes out of scope. Hence, if several +// ScopedRlzValueStoreLocks are nested, they all need to use the same store +// object. + +// This counts the nesting depth. +int g_lock_depth = 0; + +// This is the store object that might be shared. Only set if g_lock_depth > 0. +RlzValueStoreMac* g_store_object = NULL; + + +NSString* CreateRlzDirectory() { + NSFileManager* manager = [NSFileManager defaultManager]; + NSArray* paths = NSSearchPathForDirectoriesInDomains( + NSApplicationSupportDirectory, NSUserDomainMask, /*expandTilde=*/YES); + NSString* folder = nil; + if ([paths count] > 0) + folder = ObjCCast<NSString>([paths objectAtIndex:0]); + if (!folder) + folder = [@"~/Library/Application Support" stringByStandardizingPath]; + folder = [folder stringByAppendingPathComponent:@"Google/RLZ"]; + + if (g_test_folder) + folder = [g_test_folder stringByAppendingPathComponent:folder]; + + [manager createDirectoryAtPath:folder + withIntermediateDirectories:YES + attributes:nil + error:nil]; + return folder; +} + +// Returns the path of the rlz plist store, also creates the parent directory +// path if it doesn't exist. +NSString* RlzPlistFilename() { + NSString* const kRlzFile = @"RlzStore.plist"; + return [CreateRlzDirectory() stringByAppendingPathComponent:kRlzFile]; +} + +// Returns the path of the rlz lock file, also creates the parent directory +// path if it doesn't exist. +NSString* RlzLockFilename() { + NSString* const kRlzFile = @"lockfile"; + return [CreateRlzDirectory() stringByAppendingPathComponent:kRlzFile]; +} + +} // namespace + +ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() { + bool got_distributed_lock = + g_recursive_lock.TryGetCrossProcessLock(RlzLockFilename()); + // At this point, we hold the in-process lock, no matter the value of + // |got_distributed_lock|. + + ++g_lock_depth; + + if (!got_distributed_lock) { + // Give up. |store_| isn't set, which signals to callers that acquiring + // the lock failed. |g_recursive_lock| will be released by the + // destructor. + CHECK(!g_store_object); + return; + } + + if (g_lock_depth > 1) { + // Reuse the already existing store object. + CHECK(g_store_object); + store_.reset(g_store_object); + return; + } + + CHECK(!g_store_object); + + NSString* plist = RlzPlistFilename(); + + // Create an empty file if none exists yet. + NSFileManager* manager = [NSFileManager defaultManager]; + if (![manager fileExistsAtPath:plist isDirectory:NULL]) + [[NSDictionary dictionary] writeToFile:plist atomically:YES]; + + NSMutableDictionary* dict = + [NSMutableDictionary dictionaryWithContentsOfFile:plist]; + VERIFY(dict); + + if (dict) { + store_.reset(new RlzValueStoreMac(dict, plist)); + g_store_object = (RlzValueStoreMac*)store_.get(); + } +} + +ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() { + --g_lock_depth; + CHECK(g_lock_depth >= 0); + + if (g_lock_depth > 0) { + // Other locks are still using store_, don't free it yet. + ignore_result(store_.release()); + return; + } + + if (store_.get()) { + g_store_object = NULL; + + NSDictionary* dict = + static_cast<RlzValueStoreMac*>(store_.get())->dictionary(); + VERIFY([dict writeToFile:RlzPlistFilename() atomically:YES]); + } + + // Check that "store_ set" => "file_lock acquired". The converse isn't true, + // for example if the rlz data file can't be read. + if (store_.get()) + CHECK(g_recursive_lock.file_lock_); + if (!g_recursive_lock.file_lock_) + CHECK(!store_.get()); + + g_recursive_lock.ReleaseLock(); +} + +RlzValueStore* ScopedRlzValueStoreLock::GetStore() { + return store_.get(); +} + +namespace testing { + +void SetRlzStoreDirectory(const FilePath& directory) { + base::mac::ScopedNSAutoreleasePool pool; + + [g_test_folder release]; + if (directory.empty()) { + g_test_folder = nil; + } else { + // Not Unsafe on OS X. + g_test_folder = + [[NSString alloc] initWithUTF8String:directory.AsUTF8Unsafe().c_str()]; + } +} + +} // namespace testing + +} // namespace rlz_lib diff --git a/rlz/rlz.gyp b/rlz/rlz.gyp new file mode 100644 index 0000000..5b0db4b7 --- /dev/null +++ b/rlz/rlz.gyp @@ -0,0 +1,151 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + 'variables': { + # Set force_rlz_use_chrome_net to 1 to use chrome's network stack instead + # of win inet. + 'force_rlz_use_chrome_net%': 0, + }, + 'conditions': [ + ['force_rlz_use_chrome_net or OS=="mac"', { + 'rlz_use_chrome_net%': 1, + }, { + 'rlz_use_chrome_net%': 0, + }], + ], + }, + 'target_defaults': { + 'include_dirs': [ + '..', + ], + }, + 'targets': [ + { + 'target_name': 'rlz_lib', + 'type': 'static_library', + 'include_dirs': [], + 'dependencies': [ + '../base/base.gyp:base', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + ], + 'sources': [ + 'lib/assert.cc', + 'lib/assert.h', + 'lib/crc32.h', + 'lib/crc32_wrapper.cc', + 'lib/crc8.h', + 'lib/crc8.cc', + 'lib/financial_ping.cc', + 'lib/financial_ping.h', + 'lib/lib_values.cc', + 'lib/machine_id.cc', + 'lib/machine_id.h', + 'lib/rlz_enums.h', + 'lib/rlz_lib.cc', + 'lib/rlz_lib.h', + 'lib/rlz_lib_clear.cc', + 'lib/lib_values.h', + 'lib/rlz_value_store.h', + 'lib/string_utils.cc', + 'lib/string_utils.h', + 'mac/lib/machine_id_mac.cc', + 'mac/lib/rlz_value_store_mac.mm', + 'mac/lib/rlz_value_store_mac.h', + 'win/lib/lib_mutex.cc', + 'win/lib/lib_mutex.h', + 'win/lib/machine_deal.cc', + 'win/lib/machine_deal.h', + 'win/lib/machine_id_win.cc', + 'win/lib/process_info.cc', + 'win/lib/process_info.h', + 'win/lib/registry_util.cc', + 'win/lib/registry_util.h', + 'win/lib/rlz_lib.h', + 'win/lib/rlz_lib_win.cc', + 'win/lib/rlz_value_store_registry.cc', + 'win/lib/rlz_value_store_registry.h', + 'win/lib/vista_winnt.h', + ], + 'conditions': [ + ['rlz_use_chrome_net==1', { + 'defines': [ + 'RLZ_NETWORK_IMPLEMENTATION_CHROME_NET', + ], + 'direct_dependent_settings': { + 'defines': [ + 'RLZ_NETWORK_IMPLEMENTATION_CHROME_NET', + ], + }, + 'dependencies': [ + '../build/temp_gyp/googleurl.gyp:googleurl', + '../net/net.gyp:net', + ], + }, { + 'defines': [ + 'RLZ_NETWORK_IMPLEMENTATION_WIN_INET', + ], + 'direct_dependent_settings': { + 'defines': [ + 'RLZ_NETWORK_IMPLEMENTATION_WIN_INET', + ], + }, + }], + ], + }, + { + 'target_name': 'rlz_unittests', + 'type': 'executable', + 'include_dirs': [], + 'dependencies': [ + ':rlz_lib', + '../base/base.gyp:base', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + '../third_party/zlib/zlib.gyp:zlib', + ], + 'sources': [ + 'lib/crc32_unittest.cc', + 'lib/crc8_unittest.cc', + 'lib/financial_ping_test.cc', + 'lib/lib_values_unittest.cc', + 'lib/machine_id_unittest.cc', + 'lib/rlz_lib_test.cc', + 'lib/string_utils_unittest.cc', + 'test/rlz_test_helpers.cc', + 'test/rlz_test_helpers.h', + 'test/rlz_unittest_main.cc', + 'win/lib/machine_deal_test.cc', + ], + 'conditions': [ + ['rlz_use_chrome_net==1', { + 'dependencies': [ + '../net/net.gyp:net_test_support', + ], + }] + ], + }, + ], + 'conditions': [ + ['OS=="win"', { + 'targets': [ + { + 'target_name': 'rlz', + 'type': 'shared_library', + 'include_dirs': [], + 'sources': [ + 'win/dll/dll_main.cc', + 'win/dll/exports.cc', + ], + 'dependencies': [ + ':rlz_lib', + '../third_party/zlib/zlib.gyp:zlib', + ], + }, + ], + }], + ], +} diff --git a/rlz/test/rlz_test_helpers.cc b/rlz/test/rlz_test_helpers.cc new file mode 100644 index 0000000..1a1870a --- /dev/null +++ b/rlz/test/rlz_test_helpers.cc @@ -0,0 +1,86 @@ +// 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. +// +// Main entry point for all unit tests. + +#include "rlz_test_helpers.h" + +#include "rlz/lib/rlz_lib.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include <shlwapi.h> +#include "base/win/registry.h" +#include "rlz/win/lib/rlz_lib.h" +#elif defined(OS_MACOSX) +#include "base/file_path.h" +#include "rlz/lib/rlz_value_store.h" +#endif + +#if defined(OS_WIN) +namespace { + +const wchar_t* kHKCUReplacement = L"Software\\Google\\RlzUtilUnittest\\HKCU"; +const wchar_t* kHKLMReplacement = L"Software\\Google\\RlzUtilUnittest\\HKLM"; + +void OverrideRegistryHives() { + // Wipe the keys we redirect to. + // This gives us a stable run, even in the presence of previous + // crashes or failures. + LSTATUS err = SHDeleteKey(HKEY_CURRENT_USER, kHKCUReplacement); + EXPECT_TRUE(err == ERROR_SUCCESS || err == ERROR_FILE_NOT_FOUND); + err = SHDeleteKey(HKEY_CURRENT_USER, kHKLMReplacement); + EXPECT_TRUE(err == ERROR_SUCCESS || err == ERROR_FILE_NOT_FOUND); + + // Create the keys we're redirecting HKCU and HKLM to. + base::win::RegKey hkcu; + base::win::RegKey hklm; + ASSERT_EQ(ERROR_SUCCESS, + hkcu.Create(HKEY_CURRENT_USER, kHKCUReplacement, KEY_READ)); + ASSERT_EQ(ERROR_SUCCESS, + hklm.Create(HKEY_CURRENT_USER, kHKLMReplacement, KEY_READ)); + + rlz_lib::InitializeTempHivesForTesting(hklm, hkcu); + + // And do the switcharoo. + ASSERT_EQ(ERROR_SUCCESS, + ::RegOverridePredefKey(HKEY_CURRENT_USER, hkcu.Handle())); + ASSERT_EQ(ERROR_SUCCESS, + ::RegOverridePredefKey(HKEY_LOCAL_MACHINE, hklm.Handle())); +} + +void UndoOverrideRegistryHives() { + // Undo the redirection. + EXPECT_EQ(ERROR_SUCCESS, ::RegOverridePredefKey(HKEY_CURRENT_USER, NULL)); + EXPECT_EQ(ERROR_SUCCESS, ::RegOverridePredefKey(HKEY_LOCAL_MACHINE, NULL)); +} + +} // namespace +#endif // defined(OS_WIN) + + +void RlzLibTestNoMachineState::SetUp() { +#if defined(OS_WIN) + OverrideRegistryHives(); +#elif defined(OS_MACOSX) + base::mac::ScopedNSAutoreleasePool pool; + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + rlz_lib::testing::SetRlzStoreDirectory(temp_dir_.path()); +#endif // defined(OS_WIN) +} + +void RlzLibTestNoMachineState::TearDown() { +#if defined(OS_WIN) + UndoOverrideRegistryHives(); +#elif defined(OS_MACOSX) + rlz_lib::testing::SetRlzStoreDirectory(FilePath()); +#endif // defined(OS_WIN) +} + +void RlzLibTestBase::SetUp() { + RlzLibTestNoMachineState::SetUp(); +#if defined(OS_WIN) + rlz_lib::CreateMachineState(); +#endif // defined(OS_WIN) +} diff --git a/rlz/test/rlz_test_helpers.h b/rlz/test/rlz_test_helpers.h new file mode 100644 index 0000000..66bb525 --- /dev/null +++ b/rlz/test/rlz_test_helpers.h @@ -0,0 +1,33 @@ +// 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. +// +// Helper functions used by the tests. + +#ifndef RLZ_TEST_RLZ_TEST_HELPERS_H +#define RLZ_TEST_RLZ_TEST_HELPERS_H + +#include "base/compiler_specific.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_MACOSX) +#include "base/scoped_temp_dir.h" +#endif + +class RlzLibTestNoMachineState : public ::testing::Test { + protected: + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + +#if defined(OS_MACOSX) + ScopedTempDir temp_dir_; +#endif +}; + +class RlzLibTestBase : public RlzLibTestNoMachineState { + virtual void SetUp() OVERRIDE; +}; + + +#endif // RLZ_TEST_RLZ_TEST_HELPERS_H diff --git a/rlz/test/rlz_unittest_main.cc b/rlz/test/rlz_unittest_main.cc new file mode 100644 index 0000000..8ebbaf8 --- /dev/null +++ b/rlz/test/rlz_unittest_main.cc @@ -0,0 +1,29 @@ +// 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. +// +// Main entry point for all unit tests. + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "rlz/lib/rlz_lib.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +int main(int argc, char **argv) { + base::AtExitManager at_exit; + CommandLine::Init(argc, argv); + + testing::InitGoogleMock(&argc, argv); + testing::InitGoogleTest(&argc, argv); + + int ret = RUN_ALL_TESTS(); + if (ret == 0) { + // Now re-run all the tests using a supplementary brand code. This brand + // code will remain in effect for the lifetime of the branding object. + rlz_lib::SupplementaryBranding branding("TEST"); + ret = RUN_ALL_TESTS(); + } + + return ret; +} diff --git a/rlz/win/dll/dll_main.cc b/rlz/win/dll/dll_main.cc new file mode 100644 index 0000000..943f517 --- /dev/null +++ b/rlz/win/dll/dll_main.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. +// +// Main function for the RLZ DLL. + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif +#include <windows.h> + +BOOL APIENTRY DllMain(HANDLE module, DWORD reason, LPVOID reserved) { + return TRUE; +} + diff --git a/rlz/win/dll/exports.cc b/rlz/win/dll/exports.cc new file mode 100644 index 0000000..efe5cd98 --- /dev/null +++ b/rlz/win/dll/exports.cc @@ -0,0 +1,108 @@ +// 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. +// +// Functions exported by the RLZ DLL. + +#include "rlz/win/lib/rlz_lib.h" + +#define RLZ_DLL_EXPORT extern "C" __declspec(dllexport) + +RLZ_DLL_EXPORT bool RecordProductEvent(rlz_lib::Product product, + rlz_lib::AccessPoint point, + rlz_lib::Event event_id) { + return rlz_lib::RecordProductEvent(product, point, event_id); +} + +RLZ_DLL_EXPORT bool GetProductEventsAsCgi(rlz_lib::Product product, + char* unescaped_cgi, + size_t unescaped_cgi_size) { + return rlz_lib::GetProductEventsAsCgi(product, unescaped_cgi, + unescaped_cgi_size); +} +RLZ_DLL_EXPORT bool ClearAllProductEvents(rlz_lib::Product product) { + return rlz_lib::ClearAllProductEvents(product); +} + +RLZ_DLL_EXPORT bool ClearProductEvent(rlz_lib::Product product, + rlz_lib::AccessPoint point, + rlz_lib::Event event_id) { + return rlz_lib::ClearProductEvent(product, point, event_id); +} + +RLZ_DLL_EXPORT bool GetAccessPointRlz(rlz_lib::AccessPoint point, + char* rlz, + size_t rlz_size) { + return rlz_lib::GetAccessPointRlz(point, rlz, rlz_size); +} + +RLZ_DLL_EXPORT bool SetAccessPointRlz(rlz_lib::AccessPoint point, + const char* new_rlz) { + return rlz_lib::SetAccessPointRlz(point, new_rlz); +} + +RLZ_DLL_EXPORT bool CreateMachineState() { + return rlz_lib::CreateMachineState(); +} + +RLZ_DLL_EXPORT bool SetMachineDealCode(const char* dcc) { + return rlz_lib::SetMachineDealCode(dcc); +} + +RLZ_DLL_EXPORT bool GetMachineDealCodeAsCgi(char* cgi, size_t cgi_size) { + return rlz_lib::GetMachineDealCodeAsCgi(cgi, cgi_size); +} + +RLZ_DLL_EXPORT bool GetMachineDealCode2(char* dcc, size_t dcc_size) { + return rlz_lib::GetMachineDealCode(dcc, dcc_size); +} + +RLZ_DLL_EXPORT bool GetPingParams(rlz_lib::Product product, + const rlz_lib::AccessPoint* access_points, + char* unescaped_cgi, + size_t unescaped_cgi_size) { + return rlz_lib::GetPingParams(product, access_points, unescaped_cgi, + unescaped_cgi_size); +} + +RLZ_DLL_EXPORT bool ParsePingResponse(rlz_lib::Product product, + const char* response) { + return rlz_lib::ParsePingResponse(product, response); +} + +RLZ_DLL_EXPORT bool IsPingResponseValid(const char* response, + int* checksum_idx) { + return rlz_lib::IsPingResponseValid(response, checksum_idx); +} + +RLZ_DLL_EXPORT bool SetMachineDealCodeFromPingResponse(const char* response) { + return rlz_lib::SetMachineDealCodeFromPingResponse(response); +} + +RLZ_DLL_EXPORT bool SendFinancialPing(rlz_lib::Product product, + const rlz_lib::AccessPoint* access_points, + const char* product_signature, + const char* product_brand, + const char* product_id, + const char* product_lang, + bool exclude_machine_id) { + return rlz_lib::SendFinancialPing(product, access_points, product_signature, + product_brand, product_id, product_lang, exclude_machine_id); +} + +RLZ_DLL_EXPORT bool SendFinancialPingNoDelay( + rlz_lib::Product product, + const rlz_lib::AccessPoint* access_points, + const char* product_signature, + const char* product_brand, + const char* product_id, + const char* product_lang, + bool exclude_machine_id) { + return rlz_lib::SendFinancialPing(product, access_points, product_signature, + product_brand, product_id, product_lang, exclude_machine_id, true); +} + +RLZ_DLL_EXPORT void ClearProductState( + rlz_lib::Product product, const rlz_lib::AccessPoint* access_points) { + return rlz_lib::ClearProductState(product, access_points); +} diff --git a/rlz/win/lib/lib_mutex.cc b/rlz/win/lib/lib_mutex.cc new file mode 100644 index 0000000..73dfade --- /dev/null +++ b/rlz/win/lib/lib_mutex.cc @@ -0,0 +1,67 @@ +// 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. +// +// Mutex to guarantee serialization of RLZ key accesses. + +#include "rlz/win/lib/lib_mutex.h" + +#include <windows.h> +#include <Sddl.h> // For SDDL_REVISION_1, ConvertStringSecurityDescript.. +#include <Aclapi.h> // For SetSecurityInfo + +#include "base/logging.h" +#include "base/win/windows_version.h" + +namespace { + +const wchar_t kMutexName[] = L"{A946A6A9-917E-4949-B9BC-6BADA8C7FD63}"; + +} // namespace anonymous + +namespace rlz_lib { + +// Needed to allow synchronization across integrity levels. +static bool SetObjectToLowIntegrity(HANDLE object, + SE_OBJECT_TYPE type = SE_KERNEL_OBJECT) { + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return true; // Not needed on XP. + + // The LABEL_SECURITY_INFORMATION SDDL SACL to be set for low integrity. + static const wchar_t kLowIntegritySddlSacl[] = L"S:(ML;;NW;;;LW)"; + + bool result = false; + DWORD error = ERROR_SUCCESS; + PSECURITY_DESCRIPTOR security_descriptor = NULL; + PACL sacl = NULL; + BOOL sacl_present = FALSE; + BOOL sacl_defaulted = FALSE; + + if (ConvertStringSecurityDescriptorToSecurityDescriptorW( + kLowIntegritySddlSacl, SDDL_REVISION_1, &security_descriptor, NULL)) { + if (GetSecurityDescriptorSacl(security_descriptor, &sacl_present, + &sacl, &sacl_defaulted)) { + error = SetSecurityInfo(object, type, LABEL_SECURITY_INFORMATION, + NULL, NULL, NULL, sacl); + result = (ERROR_SUCCESS == error); + } + LocalFree(security_descriptor); + } + + return result; +} + +LibMutex::LibMutex() : acquired_(false), mutex_(NULL) { + mutex_ = CreateMutex(NULL, false, kMutexName); + bool result = SetObjectToLowIntegrity(mutex_); + if (result) { + acquired_ = (WAIT_OBJECT_0 == WaitForSingleObject(mutex_, 5000L)); + } +} + +LibMutex::~LibMutex() { + if (acquired_) ReleaseMutex(mutex_); + CloseHandle(mutex_); +} + +} // namespace rlz_lib diff --git a/rlz/win/lib/lib_mutex.h b/rlz/win/lib/lib_mutex.h new file mode 100644 index 0000000..6992bea --- /dev/null +++ b/rlz/win/lib/lib_mutex.h @@ -0,0 +1,28 @@ +// 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. +// +// Mutex to guarantee serialization of RLZ key accesses. + +#ifndef RLZ_WIN_LIB_LIB_MUTEX_H_ +#define RLZ_WIN_LIB_LIB_MUTEX_H_ + +#include <windows.h> + +namespace rlz_lib { + +class LibMutex { + public: + LibMutex(); + ~LibMutex(); + + bool failed(void) { return !acquired_; } + + private: + bool acquired_; + HANDLE mutex_; +}; + +} // namespace rlz_lib + +#endif // RLZ_WIN_LIB_LIB_MUTEX_H_ diff --git a/rlz/win/lib/machine_deal.cc b/rlz/win/lib/machine_deal.cc new file mode 100644 index 0000000..3216260 --- /dev/null +++ b/rlz/win/lib/machine_deal.cc @@ -0,0 +1,300 @@ +// 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 OEM Deal Confirmation Code. + +#include "rlz/win/lib/machine_deal.h" + +#include <windows.h> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/win/registry.h" +#include "rlz/lib/assert.h" +#include "rlz/lib/lib_values.h" +#include "rlz/win/lib/lib_mutex.h" +#include "rlz/win/lib/registry_util.h" +#include "rlz/win/lib/rlz_value_store_registry.h" + +const wchar_t kDccValueName[] = L"DCC"; + +namespace { + +// Current DCC can only uses [a-zA-Z0-9_-!@$*();.<>,:] +// We will be more liberal and allow some additional chars, but not url meta +// chars. +bool IsGoodDccChar(char ch) { + if (IsAsciiAlpha(ch) || IsAsciiDigit(ch)) + return true; + + switch (ch) { + case '_': + case '-': + 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_dcc is at least +// kMaxDccLength+1 long. +void NormalizeDcc(const char* raw_dcc, char* normalized_dcc) { + int index = 0; + for (; raw_dcc[index] != 0 && index < rlz_lib::kMaxDccLength; ++index) { + char current = raw_dcc[index]; + if (IsGoodDccChar(current)) { + normalized_dcc[index] = current; + } else { + normalized_dcc[index] = '.'; + } + } + + normalized_dcc[index] = 0; +} + +bool GetResponseLine(const char* response_text, int response_length, + int* search_index, std::string* response_line) { + if (!response_line || !search_index || *search_index > response_length) + return false; + + response_line->clear(); + + if (*search_index < 0) + return false; + + int line_begin = *search_index; + const char* line_end = strchr(response_text + line_begin, '\n'); + + if (line_end == NULL || line_end - response_text > response_length) { + line_end = response_text + response_length; + *search_index = -1; + } else { + *search_index = line_end - response_text + 1; + } + + response_line->assign(response_text + line_begin, + line_end - response_text - line_begin); + return true; +} + +bool GetResponseValue(const std::string& response_line, + const std::string& response_key, + std::string* value) { + if (!value) + return false; + + value->clear(); + + if (!StartsWithASCII(response_line, response_key, true)) + return false; + + std::vector<std::string> tokens; + base::SplitString(response_line, ':', &tokens); + if (tokens.size() != 2) + return false; + + // The first token is the key, the second is the value. The value is already + // trimmed for whitespace. + *value = tokens[1]; + return true; +} + +} // namespace anonymous + +namespace rlz_lib { + +bool MachineDealCode::Set(const char* dcc) { + LibMutex lock; + if (lock.failed()) + return false; + + // TODO: if (!ProcessInfo::CanWriteMachineKey()) return false; + + // Validate the new dcc value. + size_t length = strlen(dcc); + if (length > kMaxDccLength) { + ASSERT_STRING("MachineDealCode::Set: DCC length is exceeds max allowed."); + return false; + } + + base::win::RegKey hklm_key(HKEY_LOCAL_MACHINE, + RlzValueStoreRegistry::GetWideLibKeyName().c_str(), + KEY_READ | KEY_WRITE | KEY_WOW64_32KEY); + if (!hklm_key.Valid()) { + ASSERT_STRING("MachineDealCode::Set: Unable to create / open machine key." + " Did you call rlz_lib::CreateMachineState()?"); + return false; + } + + char normalized_dcc[kMaxDccLength + 1]; + NormalizeDcc(dcc, normalized_dcc); + VERIFY(length == strlen(normalized_dcc)); + + // Write the DCC to HKLM. Note that we need to include the null character + // when writing the string. + if (!RegKeyWriteValue(hklm_key, kDccValueName, normalized_dcc)) { + ASSERT_STRING("MachineDealCode::Set: Could not write the DCC value"); + return false; + } + + return true; +} + +bool MachineDealCode::GetNewCodeFromPingResponse(const char* response, + bool* has_new_dcc, char* new_dcc, int new_dcc_size) { + if (!has_new_dcc || !new_dcc || !new_dcc_size) + return false; + + *has_new_dcc = false; + new_dcc[0] = 0; + + int response_length = -1; + if (!IsPingResponseValid(response, &response_length)) + return false; + + // Get the current DCC value to compare to later) + char stored_dcc[kMaxDccLength + 1]; + if (!Get(stored_dcc, arraysize(stored_dcc))) + stored_dcc[0] = 0; + + int search_index = 0; + std::string response_line; + std::string new_dcc_value; + bool old_dcc_confirmed = false; + const std::string dcc_cgi(kDccCgiVariable); + const std::string dcc_cgi_response(kSetDccResponseVariable); + while (GetResponseLine(response, response_length, &search_index, + &response_line)) { + std::string value; + + if (!old_dcc_confirmed && + GetResponseValue(response_line, dcc_cgi, &value)) { + // This is the old DCC confirmation - should match value in registry. + if (value != stored_dcc) + return false; // Corrupted DCC - ignore this response. + else + old_dcc_confirmed = true; + continue; + } + + if (!(*has_new_dcc) && + GetResponseValue(response_line, dcc_cgi_response, &value)) { + // This is the new DCC. + if (value.size() > kMaxDccLength) continue; // Too long + *has_new_dcc = true; + new_dcc_value = value; + } + } + + old_dcc_confirmed |= (NULL == stored_dcc[0]); + + base::strlcpy(new_dcc, new_dcc_value.c_str(), new_dcc_size); + return old_dcc_confirmed; +} + +bool MachineDealCode::SetFromPingResponse(const char* response) { + bool has_new_dcc = false; + char new_dcc[kMaxDccLength + 1]; + + bool response_valid = GetNewCodeFromPingResponse( + response, &has_new_dcc, new_dcc, arraysize(new_dcc)); + + if (response_valid && has_new_dcc) + return Set(new_dcc); + + return response_valid; +} + +bool MachineDealCode::GetAsCgi(char* cgi, int cgi_size) { + if (!cgi || cgi_size <= 0) { + ASSERT_STRING("MachineDealCode::GetAsCgi: Invalid buffer"); + return false; + } + + cgi[0] = 0; + + std::string cgi_arg; + base::StringAppendF(&cgi_arg, "%s=", kDccCgiVariable); + int cgi_arg_length = cgi_arg.size(); + + if (cgi_arg_length >= cgi_size) { + ASSERT_STRING("MachineDealCode::GetAsCgi: Insufficient buffer size"); + return false; + } + + base::strlcpy(cgi, cgi_arg.c_str(), cgi_size); + + if (!Get(cgi + cgi_arg_length, cgi_size - cgi_arg_length)) { + cgi[0] = 0; + return false; + } + return true; +} + +bool MachineDealCode::Get(char* dcc, int dcc_size) { + LibMutex lock; + if (lock.failed()) + return false; + + if (!dcc || dcc_size <= 0) { + ASSERT_STRING("MachineDealCode::Get: Invalid buffer"); + return false; + } + + dcc[0] = 0; + + base::win::RegKey dcc_key(HKEY_LOCAL_MACHINE, + RlzValueStoreRegistry::GetWideLibKeyName().c_str(), + KEY_READ | KEY_WOW64_32KEY); + if (!dcc_key.Valid()) + return false; // no DCC key. + + size_t size = dcc_size; + if (!RegKeyReadValue(dcc_key, kDccValueName, dcc, &size)) { + ASSERT_STRING("MachineDealCode::Get: Insufficient buffer size"); + dcc[0] = 0; + return false; + } + + return true; +} + +bool MachineDealCode::Clear() { + base::win::RegKey dcc_key(HKEY_LOCAL_MACHINE, + RlzValueStoreRegistry::GetWideLibKeyName().c_str(), + KEY_READ | KEY_WRITE | KEY_WOW64_32KEY); + if (!dcc_key.Valid()) + return false; // no DCC key. + + dcc_key.DeleteValue(kDccValueName); + + // Verify deletion. + wchar_t dcc[kMaxDccLength + 1]; + DWORD dcc_size = arraysize(dcc); + if (dcc_key.ReadValue(kDccValueName, dcc, &dcc_size, NULL) == ERROR_SUCCESS) { + ASSERT_STRING("MachineDealCode::Clear: Could not delete the DCC value."); + return false; + } + + return true; +} + +} // namespace rlz_lib diff --git a/rlz/win/lib/machine_deal.h b/rlz/win/lib/machine_deal.h new file mode 100644 index 0000000..5b8b84a --- /dev/null +++ b/rlz/win/lib/machine_deal.h @@ -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. +// +// Library functions related to the OEM Deal Confirmation Code. + +#ifndef RLZ_WIN_LIB_MACHINE_DEAL_H_ +#define RLZ_WIN_LIB_MACHINE_DEAL_H_ + +#include <string> +#include "rlz/win/lib/rlz_lib.h" + +namespace rlz_lib { + +class MachineDealCode { + public: + // Set the OEM Deal Confirmation Code (DCC). This information is used for RLZ + // initalization. Must have write access to HKLM - SYSTEM or admin, unless + // rlz_lib::CreateMachineState() has been successfully called. + static bool Set(const char* dcc); + + // Get the OEM Deal Confirmation Code from the registry. Used to ping + // the server. + static bool Get(AccessPoint point, + char* dcc, + int dcc_size, + const wchar_t* sid = NULL); + + // Parses a ping response, checks if it is valid and sets the machine DCC + // from the response. The response should also contain the current value of + // the DCC to be considered valid. + // Write access to HKLM (system / admin) needed, unless + // rlz_lib::CreateMachineState() has been successfully called. + static bool SetFromPingResponse(const char* response); + + // Gets the new DCC to set from a ping response. Returns true if the ping + // response is valid. Sets has_new_dcc true if there is a new DCC value. + static bool GetNewCodeFromPingResponse(const char* response, + bool* has_new_dcc, + char* new_dcc, + int new_dcc_size); + + // Get the DCC cgi argument string to append to a daily or financial ping. + static bool GetAsCgi(char* cgi, int cgi_size); + + // Get the machine code. + static bool Get(char* dcc, int dcc_size); + + protected: + // Clear the DCC value. Only for testing purposes. + // Requires write access to HKLM, unless rlz_lib::CreateMachineState() has + // been successfully called. + static bool Clear(); + + MachineDealCode() {} + ~MachineDealCode() {} +}; + +} // namespace rlz_lib + +#endif // RLZ_WIN_LIB_MACHINE_DEAL_H_ diff --git a/rlz/win/lib/machine_deal_test.cc b/rlz/win/lib/machine_deal_test.cc new file mode 100644 index 0000000..47b7265 --- /dev/null +++ b/rlz/win/lib/machine_deal_test.cc @@ -0,0 +1,156 @@ +// 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 MachineDealCode class. +// +// 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, unless +// rlz_lib::CreateMachineState() has been successfully called. + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "rlz/test/rlz_test_helpers.h" +#include "rlz/win/lib/machine_deal.h" + +class MachineDealCodeHelper : public rlz_lib::MachineDealCode { + public: + static bool Clear() { return rlz_lib::MachineDealCode::Clear(); } + + private: + MachineDealCodeHelper() {} + ~MachineDealCodeHelper() {} +}; + +class MachineDealCodeTest : public RlzLibTestBase { +}; + +TEST_F(MachineDealCodeTest, CreateMachineState) { + EXPECT_TRUE(rlz_lib::CreateMachineState()); +} + +TEST_F(MachineDealCodeTest, Set) { + MachineDealCodeHelper::Clear(); + char dcc_50[50]; + dcc_50[0] = 0; + + EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value")); + + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("dcc_value", dcc_50); + + EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value_2")); + + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("dcc_value_2", dcc_50); +} + +TEST_F(MachineDealCodeTest, Get) { + MachineDealCodeHelper::Clear(); + char dcc_50[50], dcc_2[2]; + dcc_50[0] = 0; + dcc_2[0] = 0; + + EXPECT_FALSE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + + EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value")); + + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("dcc_value", dcc_50); + + EXPECT_FALSE(rlz_lib::MachineDealCode::Get(dcc_2, 2)); +} + +TEST_F(MachineDealCodeTest, SetFromPingResponse) { + rlz_lib::MachineDealCode::Set("MyDCCode"); + char dcc_50[50]; + + // Bad responses + + char* kBadDccResponse = + "dcc: NotMyDCCode \r\n" + "set_dcc: NewDCCode\r\n" + "crc32: 1B4D6BB3"; + EXPECT_FALSE(rlz_lib::MachineDealCode::SetFromPingResponse( + kBadDccResponse)); + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("MyDCCode", dcc_50); + + char* kBadCrcResponse = + "dcc: MyDCCode \r\n" + "set_dcc: NewDCCode\r\n" + "crc32: 90707106"; + EXPECT_FALSE(rlz_lib::MachineDealCode::SetFromPingResponse( + kBadCrcResponse)); + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("MyDCCode", dcc_50); + + // Good responses + + char* kMissingSetResponse = + "dcc: MyDCCode \r\n" + "crc32: 35F2E717"; + EXPECT_TRUE(rlz_lib::MachineDealCode::SetFromPingResponse( + kMissingSetResponse)); + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("MyDCCode", dcc_50); + + char* kGoodResponse = + "dcc: MyDCCode \r\n" + "set_dcc: NewDCCode\r\n" + "crc32: C8540E02"; + EXPECT_TRUE(rlz_lib::MachineDealCode::SetFromPingResponse( + kGoodResponse)); + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("NewDCCode", dcc_50); + + char* kGoodResponse2 = + "set_dcc: NewDCCode2 \r\n" + "dcc: NewDCCode \r\n" + "crc32: 60B6409A"; + EXPECT_TRUE(rlz_lib::MachineDealCode::SetFromPingResponse( + kGoodResponse2)); + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("NewDCCode2", dcc_50); + + MachineDealCodeHelper::Clear(); + char* kGoodResponse3 = + "set_dcc: NewDCCode \r\n" + "crc32: 374C1C47"; + EXPECT_TRUE(rlz_lib::MachineDealCode::SetFromPingResponse( + kGoodResponse3)); + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("NewDCCode", dcc_50); + + MachineDealCodeHelper::Clear(); + char* kGoodResponse4 = + "dcc: \r\n" + "set_dcc: NewDCCode \r\n" + "crc32: 0AB1FB39"; + EXPECT_TRUE(rlz_lib::MachineDealCode::SetFromPingResponse( + kGoodResponse4)); + EXPECT_TRUE(rlz_lib::MachineDealCode::Get(dcc_50, 50)); + EXPECT_STREQ("NewDCCode", dcc_50); +} + +TEST_F(MachineDealCodeTest, GetAsCgi) { + MachineDealCodeHelper::Clear(); + char cgi_50[50], cgi_2[2]; + cgi_50[0] = 0; + cgi_2[0] = 0; + + EXPECT_FALSE(rlz_lib::MachineDealCode::GetAsCgi(cgi_50, 50)); + EXPECT_STREQ("", cgi_50); + + EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value")); + + EXPECT_TRUE(rlz_lib::MachineDealCode::GetAsCgi(cgi_50, 50)); + EXPECT_STREQ("dcc=dcc_value", cgi_50); + + EXPECT_FALSE(rlz_lib::MachineDealCode::GetAsCgi(cgi_2, 2)); +} diff --git a/rlz/win/lib/machine_id_win.cc b/rlz/win/lib/machine_id_win.cc new file mode 100644 index 0000000..8eb138a --- /dev/null +++ b/rlz/win/lib/machine_id_win.cc @@ -0,0 +1,130 @@ +// 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 <windows.h> +#include <Sddl.h> // For ConvertSidToStringSidW. +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "rlz/lib/assert.h" + +namespace rlz_lib { + +namespace { + +bool GetSystemVolumeSerialNumber(int* number) { + if (!number) + return false; + + *number = 0; + + // Find the system root path (e.g: C:\). + wchar_t system_path[MAX_PATH + 1]; + if (!GetSystemDirectoryW(system_path, MAX_PATH)) + return false; + + wchar_t* first_slash = wcspbrk(system_path, L"\\/"); + if (first_slash != NULL) + *(first_slash + 1) = 0; + + DWORD number_local = 0; + if (!GetVolumeInformationW(system_path, NULL, 0, &number_local, NULL, NULL, + NULL, 0)) + return false; + + *number = number_local; + return true; +} + +bool GetComputerSid(const wchar_t* account_name, SID* sid, DWORD sid_size) { + static const DWORD kStartDomainLength = 128; // reasonable to start with + + scoped_array<wchar_t> domain_buffer(new wchar_t[kStartDomainLength]); + DWORD domain_size = kStartDomainLength; + DWORD sid_dword_size = sid_size; + SID_NAME_USE sid_name_use; + + BOOL success = ::LookupAccountNameW(NULL, account_name, sid, + &sid_dword_size, domain_buffer.get(), + &domain_size, &sid_name_use); + if (!success && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // We could have gotten the insufficient buffer error because + // one or both of sid and szDomain was too small. Check for that + // here. + if (sid_dword_size > sid_size) + return false; + + if (domain_size > kStartDomainLength) + domain_buffer.reset(new wchar_t[domain_size]); + + success = ::LookupAccountNameW(NULL, account_name, sid, &sid_dword_size, + domain_buffer.get(), &domain_size, + &sid_name_use); + } + + return success != FALSE; +} + +std::wstring ConvertSidToString(SID* sid) { + std::wstring sid_string; +#if _WIN32_WINNT >= 0x500 + wchar_t* sid_buffer = NULL; + if (ConvertSidToStringSidW(sid, &sid_buffer)) { + sid_string = sid_buffer; + LocalFree(sid_buffer); + } +#else + SID_IDENTIFIER_AUTHORITY* sia = ::GetSidIdentifierAuthority(sid); + + if(sia->Value[0] || sia->Value[1]) { + base::SStringPrintf( + &sid_string, L"S-%d-0x%02hx%02hx%02hx%02hx%02hx%02hx", + SID_REVISION, (USHORT)sia->Value[0], (USHORT)sia->Value[1], + (USHORT)sia->Value[2], (USHORT)sia->Value[3], (USHORT)sia->Value[4], + (USHORT)sia->Value[5]); + } else { + ULONG authority = 0; + for (int i = 2; i < 6; ++i) { + authority <<= 8; + authority |= sia->Value[i]; + } + base::SStringPrintf(&sid_string, L"S-%d-%lu", SID_REVISION, authority); + } + + int sub_auth_count = *::GetSidSubAuthorityCount(sid); + for(int i = 0; i < sub_auth_count; ++i) + base::StringAppendF(&sid_string, L"-%lu", *::GetSidSubAuthority(sid, i)); +#endif + + return sid_string; +} + +} // namespace + +bool GetRawMachineId(string16* sid_string, int* volume_id) { + // Calculate the Windows SID. + + wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {0}; + DWORD size = arraysize(computer_name); + + if (GetComputerNameW(computer_name, &size)) { + char sid_buffer[SECURITY_MAX_SID_SIZE]; + SID* sid = reinterpret_cast<SID*>(sid_buffer); + if (GetComputerSid(computer_name, sid, SECURITY_MAX_SID_SIZE)) { + *sid_string = ConvertSidToString(sid); + } + } + + // Get the system drive volume serial number. + *volume_id = 0; + if (!GetSystemVolumeSerialNumber(volume_id)) { + ASSERT_STRING("GetMachineId: Failed to retrieve volume serial number"); + *volume_id = 0; + } + + return true; +} + +} // namespace rlz_lib diff --git a/rlz/win/lib/process_info.cc b/rlz/win/lib/process_info.cc new file mode 100644 index 0000000..8d4a02f --- /dev/null +++ b/rlz/win/lib/process_info.cc @@ -0,0 +1,196 @@ +// 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. +// +// Information about the current process. + +#include "rlz/win/lib/process_info.h" + +#include <windows.h> +#include <Sddl.h> // For ConvertSidToStringSid. +#include <LMCons.h> // For UNLEN + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/process_util.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "rlz/lib/assert.h" +#include "rlz/win/lib/vista_winnt.h" + +namespace { + +HRESULT GetCurrentUser(std::wstring* name, + std::wstring* domain, + std::wstring* sid) { + DWORD err; + + // Get the current username & domain the hard way. (GetUserNameEx would be + // nice, but unfortunately requires connectivity to a domain controller. + // Useless.) + + // (Following call doesn't work if running as a Service - because a Service + // runs under special accounts like LOCAL_SYSTEM, not as the logged in user. + // In which case, search for and use the process handle of a running + // Explorer.exe.) + HANDLE token; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) + return E_FAIL; + + base::win::ScopedHandle scoped_process_token(token); + + // (Following call will fail with ERROR_INSUFFICIENT_BUFFER and give us the + // required size.) + scoped_array<char> token_user_bytes; + DWORD token_user_size; + DWORD token_user_size2; + BOOL result = ::GetTokenInformation(token, TokenUser, NULL, 0, + &token_user_size); + err = ::GetLastError(); + CHECK(!result && err == ERROR_INSUFFICIENT_BUFFER); + + token_user_bytes.reset(new char[token_user_size]); + if (!token_user_bytes.get()) + return E_OUTOFMEMORY; + + if (!::GetTokenInformation(token, TokenUser, token_user_bytes.get(), + token_user_size, &token_user_size2)) { + return E_FAIL; + } + + WCHAR user_name[UNLEN + 1]; // max username length + WCHAR domain_name[UNLEN + 1]; + DWORD user_name_size = UNLEN + 1; + DWORD domain_name_size = UNLEN + 1; + SID_NAME_USE sid_type; + TOKEN_USER* token_user = + reinterpret_cast<TOKEN_USER*>(token_user_bytes.get()); + if (!token_user) + return E_FAIL; + PSID user_sid = token_user->User.Sid; + if (!::LookupAccountSidW(NULL, user_sid, user_name, &user_name_size, + domain_name, &domain_name_size, &sid_type)) { + return E_FAIL; + } + + if (name != NULL) { + *name = user_name; + } + if (domain != NULL) { + *domain = domain_name; + } + if (sid != NULL) { + LPWSTR string_sid; + ConvertSidToStringSidW(user_sid, &string_sid); + *sid = string_sid; // copy out to cstring + // free memory, as documented for ConvertSidToStringSid + LocalFree(string_sid); + } + + return S_OK; +} + +HRESULT GetElevationType(PTOKEN_ELEVATION_TYPE elevation) { + if (!elevation) + return E_POINTER; + + *elevation = TokenElevationTypeDefault; + + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return E_FAIL; + + HANDLE process_token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token)) + return HRESULT_FROM_WIN32(GetLastError()); + + base::win::ScopedHandle scoped_process_token(process_token); + + DWORD size; + TOKEN_ELEVATION_TYPE elevation_type; + if (!GetTokenInformation(process_token, TokenElevationType, &elevation_type, + sizeof(elevation_type), &size)) { + return HRESULT_FROM_WIN32(GetLastError()); + } + + *elevation = elevation_type; + return S_OK; +} + +// based on http://msdn2.microsoft.com/en-us/library/aa376389.aspx +bool GetUserGroup(long* group) { + if (!group) + return false; + + *group = 0; + + // groups are listed in DECREASING order of importance + // (eg. If a user is a member of both the admin group and + // the power user group, it is more useful to list the user + // as an admin) + DWORD user_groups[] = {DOMAIN_ALIAS_RID_ADMINS, + DOMAIN_ALIAS_RID_POWER_USERS}; + SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY; + + for (int i = 0; i < arraysize(user_groups) && *group == 0; ++i) { + PSID current_group; + if (AllocateAndInitializeSid(&nt_authority, 2, + SECURITY_BUILTIN_DOMAIN_RID, + user_groups[i], 0, 0, 0, 0, + 0, 0, ¤t_group)) { + BOOL current_level; + if (CheckTokenMembership(NULL, current_group, ¤t_level) && + current_level) { + *group = user_groups[i]; + } + + FreeSid(current_group); + } + } + + return group != 0; +} +} //anonymous + + +namespace rlz_lib { + +bool ProcessInfo::IsRunningAsSystem() { + static std::wstring name; + static std::wstring domain; + static std::wstring sid; + if (name.empty()) + CHECK(SUCCEEDED(GetCurrentUser(&name, &domain, &sid))); + + return (name == L"SYSTEM"); +} + +bool ProcessInfo::HasAdminRights() { + static bool evaluated = false; + static bool has_rights = false; + + if (!evaluated) { + if (IsRunningAsSystem()) { + has_rights = true; + } else if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + TOKEN_ELEVATION_TYPE elevation; + base::IntegrityLevel level; + + if (SUCCEEDED(GetElevationType(&elevation)) && + base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(), &level)) + has_rights = (elevation == TokenElevationTypeFull) || + (level == HIGH_INTEGRITY); + } else { + long group = 0; + if (GetUserGroup(&group)) + has_rights = (group == DOMAIN_ALIAS_RID_ADMINS); + } + } + + evaluated = true; + if (!has_rights) + ASSERT_STRING("ProcessInfo::HasAdminRights: Does not have admin rights."); + + return has_rights; +} + +}; // namespace diff --git a/rlz/win/lib/process_info.h b/rlz/win/lib/process_info.h new file mode 100644 index 0000000..84bd604 --- /dev/null +++ b/rlz/win/lib/process_info.h @@ -0,0 +1,32 @@ +// 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. +// +// Information about the current process. + +#ifndef RLZ_WIN_LIB_PROCESS_INFO_H_ +#define RLZ_WIN_LIB_PROCESS_INFO_H_ + +#include "base/basictypes.h" + +namespace rlz_lib { + +class ProcessInfo { + public: + enum IntegrityLevel { + INTEGRITY_UNKNOWN, + LOW_INTEGRITY, + MEDIUM_INTEGRITY, + HIGH_INTEGRITY, + }; + + // All these functions cache the result after first run. + static bool IsRunningAsSystem(); + static bool HasAdminRights(); // System / Admin / High Elevation on Vista + + private: + DISALLOW_COPY_AND_ASSIGN(ProcessInfo); +}; // class +}; // namespace + +#endif // RLZ_WIN_LIB_PROCESS_INFO_H_ diff --git a/rlz/win/lib/registry_util.cc b/rlz/win/lib/registry_util.cc new file mode 100644 index 0000000..6a324d5 --- /dev/null +++ b/rlz/win/lib/registry_util.cc @@ -0,0 +1,77 @@ +// 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 helper library to keep track of a user's key by SID. +// Used by RLZ libary. Also to be used by SearchWithGoogle library. + +#include "rlz/win/lib/registry_util.h" + +#include "base/process_util.h" +#include "base/utf_string_conversions.h" +#include "base/win/registry.h" +#include "base/win/windows_version.h" +#include "rlz/lib/assert.h" +#include "rlz/win/lib/process_info.h" + +namespace rlz_lib { + +bool RegKeyReadValue(base::win::RegKey& key, const wchar_t* name, + char* value, size_t* value_size) { + value[0] = 0; + + std::wstring value_string; + if (key.ReadValue(name, &value_string) != ERROR_SUCCESS) { + return false; + } + + if (value_string.length() > *value_size) { + *value_size = value_string.length(); + return false; + } + + // Note that RLZ string are always ASCII by design. + strncpy(value, WideToUTF8(value_string).c_str(), *value_size); + value[*value_size - 1] = 0; + return true; +} + +bool RegKeyWriteValue(base::win::RegKey& key, const wchar_t* name, + const char* value) { + std::wstring value_string(ASCIIToWide(value)); + return key.WriteValue(name, value_string.c_str()) == ERROR_SUCCESS; +} + +bool HasUserKeyAccess(bool write_access) { + // The caller is trying to access HKEY_CURRENT_USER. Test to see if we can + // read from there. Don't try HKEY_CURRENT_USER because this will cause + // problems in the unit tests: if we open HKEY_CURRENT_USER directly here, + // the overriding done for unit tests will no longer work. So we try subkey + // "Software" which is known to always exist. + base::win::RegKey key; + if (key.Open(HKEY_CURRENT_USER, L"Software", KEY_READ) != ERROR_SUCCESS) + ASSERT_STRING("Could not open HKEY_CURRENT_USER"); + + if (ProcessInfo::IsRunningAsSystem()) { + ASSERT_STRING("UserKey::HasAccess: No access as SYSTEM without SID set."); + return false; + } + + if (write_access) { + if (base::win::GetVersion() < base::win::VERSION_VISTA) return true; + base::ProcessHandle process_handle = base::GetCurrentProcessHandle(); + base::IntegrityLevel level = base::INTEGRITY_UNKNOWN; + + if (!base::GetProcessIntegrityLevel(process_handle, &level)) { + ASSERT_STRING("UserKey::HasAccess: Cannot determine Integrity Level."); + return false; + } + if (level <= base::LOW_INTEGRITY) { + ASSERT_STRING("UserKey::HasAccess: Cannot write from Low Integrity."); + return false; + } + } + return true; +} + +} // namespace rlz_lib diff --git a/rlz/win/lib/registry_util.h b/rlz/win/lib/registry_util.h new file mode 100644 index 0000000..60b8be6 --- /dev/null +++ b/rlz/win/lib/registry_util.h @@ -0,0 +1,29 @@ +// 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_WIN_LIB_REGISTRY_UTIL_H_ +#define RLZ_WIN_LIB_REGISTRY_UTIL_H_ + +namespace base { +namespace win { +class RegKey; +} // namespace win +} // namespace base + +namespace rlz_lib { + +bool RegKeyReadValue(base::win::RegKey& key, + const wchar_t* name, + char* value, + size_t* value_size); + +bool RegKeyWriteValue(base::win::RegKey& key, + const wchar_t* name, + const char* value); + +bool HasUserKeyAccess(bool write_access); + +} // namespace rlz_lib + +#endif // RLZ_WIN_LIB_REGISTRY_UTIL_H_ diff --git a/rlz/win/lib/rlz_lib.h b/rlz/win/lib/rlz_lib.h new file mode 100644 index 0000000..3adbf97 --- /dev/null +++ b/rlz/win/lib/rlz_lib.h @@ -0,0 +1,56 @@ +// 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. +// +// Each prototype mentions the registry access requirements: +// +// HKLM read: Will work from any process and at any privilege level on Vista. +// HKCU read: Calls made from the SYSTEM account must pass the current user's +// SID as the optional 'sid' param. Can be called from low integrity +// process on Vista. +// HKCU write: Calls made from the SYSTEM account must pass the current user's +// SID as the optional 'sid' param. Calls require at least medium +// integrity on Vista (e.g. Toolbar will need to use their broker) +// HKLM write: Calls must be made from an account with admin rights. No SID +// need be passed when running as SYSTEM. +// Functions which do not access registry will be marked with "no restrictions". + +#ifndef RLZ_WIN_LIB_RLZ_LIB_H_ +#define RLZ_WIN_LIB_RLZ_LIB_H_ + +// Clients can get away by just including rlz/lib/rlz_lib.h. This file only +// contains function definitions for files used by tests. It's mostly kept +// around for backwards-compatibility. + +#include "rlz/lib/rlz_lib.h" + +#include "base/win/registry.h" + +namespace rlz_lib { + +#if defined(OS_WIN) + +// Initialize temporary HKLM/HKCU registry hives used for testing. +// Testing RLZ requires reading and writing to the Windows registry. To keep +// the tests isolated from the machine's state, as well as to prevent the tests +// from causing side effects in the registry, HKCU and HKLM are overridden for +// the duration of the tests. RLZ tests don't expect the HKCU and KHLM hives to +// be empty though, and this function initializes the minimum value needed so +// that the test will run successfully. +// +// The two arguments to this function should be the keys that will represent +// the HKLM and HKCU registry hives during the tests. This function should be +// called *before* the hives are overridden. +void InitializeTempHivesForTesting(const base::win::RegKey& temp_hklm_key, + const base::win::RegKey& temp_hkcu_key); +#endif // defined(OS_WIN) + +} // namespace rlz_lib + +#endif // RLZ_WIN_LIB_RLZ_LIB_H_ diff --git a/rlz/win/lib/rlz_lib_win.cc b/rlz/win/lib/rlz_lib_win.cc new file mode 100644 index 0000000..e7b145e --- /dev/null +++ b/rlz/win/lib/rlz_lib_win.cc @@ -0,0 +1,260 @@ +// 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/win/lib/rlz_lib.h" + +#include <windows.h> +#include <aclapi.h> +#include <winerror.h> + +#include "base/basictypes.h" +#include "base/win/registry.h" +#include "base/win/windows_version.h" +#include "rlz/lib/assert.h" +#include "rlz/lib/rlz_value_store.h" +#include "rlz/win/lib/machine_deal.h" +#include "rlz/win/lib/rlz_value_store_registry.h" + +namespace { + +// Path to recursively copy into the replacemment hives. These are needed +// to make sure certain win32 APIs continue to run correctly once the real +// hives are replaced. +const wchar_t* kHKLMAccessProviders = + L"System\\CurrentControlSet\\Control\\Lsa\\AccessProviders"; + +// Helper functions + +void CopyRegistryTree(const base::win::RegKey& src, base::win::RegKey* dest) { + // First copy values. + for (base::win::RegistryValueIterator i(src.Handle(), L""); + i.Valid(); ++i) { + dest->WriteValue(i.Name(), reinterpret_cast<const void*>(i.Value()), + i.ValueSize(), i.Type()); + } + + // Next copy subkeys recursively. + for (base::win::RegistryKeyIterator i(src.Handle(), L""); + i.Valid(); ++i) { + base::win::RegKey subkey(dest->Handle(), i.Name(), KEY_ALL_ACCESS); + CopyRegistryTree(base::win::RegKey(src.Handle(), i.Name(), KEY_READ), + &subkey); + } +} + +} // namespace anonymous + + +namespace rlz_lib { + +// OEM Deal confirmation storage functions. + +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()); + } +}; + +// Check if this SID has the desired access by scanning the ACEs in the DACL. +// This function is part of the rlz_lib namespace so that it can be called from +// unit tests. Non-unit test code should not call this function. +bool HasAccess(PSID sid, ACCESS_MASK access_mask, ACL* dacl) { + if (dacl == NULL) + return false; + + ACL_SIZE_INFORMATION info; + if (!GetAclInformation(dacl, &info, sizeof(info), AclSizeInformation)) + return false; + + GENERIC_MAPPING generic_mapping = {KEY_READ, KEY_WRITE, KEY_EXECUTE, + KEY_ALL_ACCESS}; + MapGenericMask(&access_mask, &generic_mapping); + + for (DWORD i = 0; i < info.AceCount; ++i) { + ACCESS_ALLOWED_ACE* ace; + if (GetAce(dacl, i, reinterpret_cast<void**>(&ace))) { + if ((ace->Header.AceFlags & INHERIT_ONLY_ACE) == INHERIT_ONLY_ACE) + continue; + + PSID existing_sid = reinterpret_cast<PSID>(&ace->SidStart); + DWORD mask = ace->Mask; + MapGenericMask(&mask, &generic_mapping); + + if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE && + (mask & access_mask) == access_mask && EqualSid(existing_sid, sid)) + return true; + + if (ace->Header.AceType == ACCESS_DENIED_ACE_TYPE && + (mask & access_mask) != 0 && EqualSid(existing_sid, sid)) + return false; + } + } + + return false; +} + +bool CreateMachineState() { + LibMutex lock; + if (lock.failed()) + return false; + + base::win::RegKey hklm_key; + if (hklm_key.Create(HKEY_LOCAL_MACHINE, + RlzValueStoreRegistry::GetWideLibKeyName().c_str(), + KEY_ALL_ACCESS | KEY_WOW64_32KEY) != ERROR_SUCCESS) { + ASSERT_STRING("rlz_lib::CreateMachineState: " + "Unable to create / open machine key."); + return false; + } + + // 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); + + // Get the security descriptor for the registry key. + DWORD original_sd_size = 0; + ::RegGetKeySecurity(hklm_key.Handle(), DACL_SECURITY_INFORMATION, NULL, + &original_sd_size); + typed_buffer_ptr<SECURITY_DESCRIPTOR> original_sd(original_sd_size); + + LONG result = ::RegGetKeySecurity(hklm_key.Handle(), + DACL_SECURITY_INFORMATION, original_sd, &original_sd_size); + if (result != ERROR_SUCCESS) { + ASSERT_STRING("rlz_lib::CreateMachineState: " + "Unable to create / open machine key."); + return false; + } + + // Make a copy of the security descriptor so we can modify it. The one + // returned by RegGetKeySecurity() is self-relative, so we need to make it + // absolute. + DWORD new_sd_size = 0; + DWORD dacl_size = 0; + DWORD sacl_size = 0; + DWORD owner_size = 0; + DWORD group_size = 0; + ::MakeAbsoluteSD(original_sd, NULL, &new_sd_size, NULL, &dacl_size, + NULL, &sacl_size, NULL, &owner_size, + NULL, &group_size); + + typed_buffer_ptr<SECURITY_DESCRIPTOR> new_sd(new_sd_size); + // Make sure the DACL is big enough to add one more ACE. + typed_buffer_ptr<ACL> dacl(dacl_size + SECURITY_MAX_SID_SIZE); + typed_buffer_ptr<ACL> sacl(sacl_size); + typed_buffer_ptr<SID> owner(owner_size); + typed_buffer_ptr<SID> group(group_size); + + if (!::MakeAbsoluteSD(original_sd, new_sd, &new_sd_size, dacl, &dacl_size, + sacl, &sacl_size, owner, &owner_size, + group, &group_size)) { + ASSERT_STRING("rlz_lib::CreateMachineState: MakeAbsoluteSD failed"); + return false; + } + + // If all users already have read/write access to the registry key, then + // nothing to do. Otherwise change the security descriptor of the key to + // give everyone access. + if (HasAccess(users_sid, KEY_ALL_ACCESS, dacl)) { + return false; + } + + // Add ALL-USERS ALL-ACCESS ACL. + EXPLICIT_ACCESS ea; + ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS)); + ea.grfAccessPermissions = GENERIC_ALL | KEY_ALL_ACCESS; + ea.grfAccessMode = GRANT_ACCESS; + ea.grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME; + ea.Trustee.ptstrName = L"Everyone"; + + ACL* new_dacl = NULL; + result = SetEntriesInAcl(1, &ea, dacl, &new_dacl); + if (result != ERROR_SUCCESS) { + ASSERT_STRING("rlz_lib::CreateMachineState: SetEntriesInAcl failed"); + return false; + } + + BOOL ok = SetSecurityDescriptorDacl(new_sd, TRUE, new_dacl, FALSE); + if (!ok) { + ASSERT_STRING("rlz_lib::CreateMachineState: " + "SetSecurityDescriptorOwner failed"); + LocalFree(new_dacl); + return false; + } + + result = ::RegSetKeySecurity(hklm_key.Handle(), + DACL_SECURITY_INFORMATION, + new_sd); + // Note that the new DACL cannot be freed until after the call to + // RegSetKeySecurity(). + LocalFree(new_dacl); + + bool success = true; + if (result != ERROR_SUCCESS) { + ASSERT_STRING("rlz_lib::CreateMachineState: " + "Unable to create / open machine key."); + success = false; + } + + + return success; +} + +bool SetMachineDealCode(const char* dcc) { + return MachineDealCode::Set(dcc); +} + +bool GetMachineDealCodeAsCgi(char* cgi, size_t cgi_size) { + return MachineDealCode::GetAsCgi(cgi, cgi_size); +} + +bool GetMachineDealCode(char* dcc, size_t dcc_size) { + return MachineDealCode::Get(dcc, dcc_size); +} + +// Combined functions. + +bool SetMachineDealCodeFromPingResponse(const char* response) { + return MachineDealCode::SetFromPingResponse(response); +} + +void InitializeTempHivesForTesting(const base::win::RegKey& temp_hklm_key, + const base::win::RegKey& temp_hkcu_key) { + // For the moment, the HKCU hive requires no initialization. + + if (base::win::GetVersion() >= base::win::VERSION_WIN7) { + // Copy the following HKLM subtrees to the temporary location so that the + // win32 APIs used by the tests continue to work: + // + // HKLM\System\CurrentControlSet\Control\Lsa\AccessProviders + // + // This seems to be required since Win7. + base::win::RegKey dest(temp_hklm_key.Handle(), kHKLMAccessProviders, + KEY_ALL_ACCESS); + CopyRegistryTree(base::win::RegKey(HKEY_LOCAL_MACHINE, + kHKLMAccessProviders, + KEY_READ), + &dest); + } +} + +} // namespace rlz_lib diff --git a/rlz/win/lib/rlz_value_store_registry.cc b/rlz/win/lib/rlz_value_store_registry.cc new file mode 100644 index 0000000..36c53d5 --- /dev/null +++ b/rlz/win/lib/rlz_value_store_registry.cc @@ -0,0 +1,384 @@ +// 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/win/lib/rlz_value_store_registry.h" + +#include "base/win/registry.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/rlz_lib.h" +#include "rlz/lib/string_utils.h" +#include "rlz/win/lib/registry_util.h" + +namespace rlz_lib { + +namespace { + +// +// Registry keys: +// +// RLZ's are stored as: +// <AccessPointName> = <RLZ value> @ kRootKey\kLibKeyName\kRlzsSubkeyName. +// +// Events are stored as: +// <AccessPointName><EventName> = 1 @ +// HKCU\kLibKeyName\kEventsSubkeyName\GetProductName(product). +// +// The OEM Deal Confirmation Code (DCC) is stored as +// kDccValueName = <DCC value> @ HKLM\kLibKeyName +// +// The last ping time, per product is stored as: +// GetProductName(product) = <last ping time> @ +// HKCU\kLibKeyName\kPingTimesSubkeyName. +// +// The server does not care about any of these constants. +// +const char kLibKeyName[] = "Software\\Google\\Common\\Rlz"; +const wchar_t kGoogleKeyName[] = L"Software\\Google"; +const wchar_t kGoogleCommonKeyName[] = L"Software\\Google\\Common"; +const char kRlzsSubkeyName[] = "RLZs"; +const char kEventsSubkeyName[] = "Events"; +const char kStatefulEventsSubkeyName[] = "StatefulEvents"; +const char kPingTimesSubkeyName[] = "PTimes"; + +std::wstring GetWideProductName(Product product) { + return ASCIIToWide(GetProductName(product)); +} + +void AppendBrandToString(std::string* str) { + std::string brand(SupplementaryBranding::GetBrand()); + if (!brand.empty()) + base::StringAppendF(str, "\\_%s", brand.c_str()); +} + +// Function to get the specific registry keys. +bool GetRegKey(const char* name, REGSAM access, base::win::RegKey* key) { + std::string key_location; + base::StringAppendF(&key_location, "%s\\%s", kLibKeyName, name); + AppendBrandToString(&key_location); + + LONG ret = ERROR_SUCCESS; + if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK)) { + ret = key->Create(HKEY_CURRENT_USER, ASCIIToWide(key_location).c_str(), + access); + } else { + ret = key->Open(HKEY_CURRENT_USER, ASCIIToWide(key_location).c_str(), + access); + } + + return ret == ERROR_SUCCESS; +} + +bool GetPingTimesRegKey(REGSAM access, base::win::RegKey* key) { + return GetRegKey(kPingTimesSubkeyName, access, key); +} + + +bool GetEventsRegKey(const char* event_type, + const rlz_lib::Product* product, + REGSAM access, base::win::RegKey* key) { + std::string key_location; + base::StringAppendF(&key_location, "%s\\%s", kLibKeyName, + event_type); + AppendBrandToString(&key_location); + + if (product != NULL) { + std::string product_name = GetProductName(*product); + if (product_name.empty()) + return false; + + base::StringAppendF(&key_location, "\\%s", product_name.c_str()); + } + + LONG ret = ERROR_SUCCESS; + if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK)) { + ret = key->Create(HKEY_CURRENT_USER, ASCIIToWide(key_location).c_str(), + access); + } else { + ret = key->Open(HKEY_CURRENT_USER, ASCIIToWide(key_location).c_str(), + access); + } + + return ret == ERROR_SUCCESS; +} + +bool GetAccessPointRlzsRegKey(REGSAM access, base::win::RegKey* key) { + return GetRegKey(kRlzsSubkeyName, access, key); +} + +bool ClearAllProductEventValues(rlz_lib::Product product, const char* key) { + std::wstring product_name = GetWideProductName(product); + if (product_name.empty()) + return false; + + base::win::RegKey reg_key; + GetEventsRegKey(key, NULL, KEY_WRITE, ®_key); + reg_key.DeleteKey(product_name.c_str()); + + // Verify that the value no longer exists. + base::win::RegKey product_events( + reg_key.Handle(), product_name.c_str(), KEY_READ); + if (product_events.Valid()) { + ASSERT_STRING("ClearAllProductEvents: Key deletion failed"); + return false; + } + + return true; +} + +// Deletes a registry key if it exists and has no subkeys or values. +// TODO: Move this to a registry_utils file and add unittest. +bool DeleteKeyIfEmpty(HKEY root_key, const wchar_t* key_name) { + if (!key_name) { + ASSERT_STRING("DeleteKeyIfEmpty: key_name is NULL"); + return false; + } else { // Scope needed for RegKey + base::win::RegKey key(root_key, key_name, KEY_READ); + if (!key.Valid()) + return true; // Key does not exist - nothing to do. + + base::win::RegistryKeyIterator key_iter(root_key, key_name); + if (key_iter.SubkeyCount() > 0) + return true; // Not empty, so nothing to do + + base::win::RegistryValueIterator value_iter(root_key, key_name); + if (value_iter.ValueCount() > 0) + return true; // Not empty, so nothing to do + } + + // The key is empty - delete it now. + base::win::RegKey key(root_key, L"", KEY_WRITE); + return key.DeleteKey(key_name) == ERROR_SUCCESS; +} + +} // namespace + +// static +std::wstring RlzValueStoreRegistry::GetWideLibKeyName() { + return ASCIIToWide(kLibKeyName); +} + +bool RlzValueStoreRegistry::HasAccess(AccessType type) { + return HasUserKeyAccess(type == kWriteAccess); +} + +bool RlzValueStoreRegistry::WritePingTime(Product product, int64 time) { + base::win::RegKey key; + std::wstring product_name = GetWideProductName(product); + return GetPingTimesRegKey(KEY_WRITE, &key) && + key.WriteValue(product_name.c_str(), &time, sizeof(time), + REG_QWORD) == ERROR_SUCCESS; +} + +bool RlzValueStoreRegistry::ReadPingTime(Product product, int64* time) { + base::win::RegKey key; + std::wstring product_name = GetWideProductName(product); + return GetPingTimesRegKey(KEY_READ, &key) && + key.ReadInt64(product_name.c_str(), time) == ERROR_SUCCESS; +} + +bool RlzValueStoreRegistry::ClearPingTime(Product product) { + base::win::RegKey key; + GetPingTimesRegKey(KEY_WRITE, &key); + + std::wstring product_name = GetWideProductName(product); + key.DeleteValue(product_name.c_str()); + + // Verify deletion. + uint64 value; + DWORD size = sizeof(value); + if (key.ReadValue( + product_name.c_str(), &value, &size, NULL) == ERROR_SUCCESS) { + ASSERT_STRING("RlzValueStoreRegistry::ClearPingTime: Failed to delete."); + return false; + } + + return true; +} + +bool RlzValueStoreRegistry::WriteAccessPointRlz(AccessPoint access_point, + const char* new_rlz) { + const char* access_point_name = GetAccessPointName(access_point); + if (!access_point_name) + return false; + + std::wstring access_point_name_wide(ASCIIToWide(access_point_name)); + base::win::RegKey key; + GetAccessPointRlzsRegKey(KEY_WRITE, &key); + + if (!RegKeyWriteValue(key, access_point_name_wide.c_str(), new_rlz)) { + ASSERT_STRING("SetAccessPointRlz: Could not write the new RLZ value"); + return false; + } + return true; +} + +bool RlzValueStoreRegistry::ReadAccessPointRlz(AccessPoint access_point, + char* rlz, + size_t rlz_size) { + const char* access_point_name = GetAccessPointName(access_point); + if (!access_point_name) + return false; + + size_t size = rlz_size; + base::win::RegKey key; + GetAccessPointRlzsRegKey(KEY_READ, &key); + if (!RegKeyReadValue(key, ASCIIToWide(access_point_name).c_str(), + rlz, &size)) { + rlz[0] = 0; + if (size > rlz_size) { + ASSERT_STRING("GetAccessPointRlz: Insufficient buffer size"); + return false; + } + } + return true; +} + +bool RlzValueStoreRegistry::ClearAccessPointRlz(AccessPoint access_point) { + const char* access_point_name = GetAccessPointName(access_point); + if (!access_point_name) + return false; + + std::wstring access_point_name_wide(ASCIIToWide(access_point_name)); + base::win::RegKey key; + GetAccessPointRlzsRegKey(KEY_WRITE, &key); + + key.DeleteValue(access_point_name_wide.c_str()); + + // Verify deletion. + DWORD value; + if (key.ReadValueDW(access_point_name_wide.c_str(), &value) == + ERROR_SUCCESS) { + ASSERT_STRING("SetAccessPointRlz: Could not clear the RLZ value."); + return false; + } + return true; +} + +bool RlzValueStoreRegistry::AddProductEvent(Product product, + const char* event_rlz) { + std::wstring event_rlz_wide(ASCIIToWide(event_rlz)); + base::win::RegKey reg_key; + GetEventsRegKey(kEventsSubkeyName, &product, KEY_WRITE, ®_key); + if (reg_key.WriteValue(event_rlz_wide.c_str(), 1) != ERROR_SUCCESS) { + ASSERT_STRING("AddProductEvent: Could not write the new event value"); + return false; + } + + return true; +} + +bool RlzValueStoreRegistry::ReadProductEvents(Product product, + std::vector<std::string>* events) { + // Open the events key. + base::win::RegKey events_key; + GetEventsRegKey(kEventsSubkeyName, &product, KEY_READ, &events_key); + if (!events_key.Valid()) + return false; + + // Append the events to the buffer. + int num_values = 0; + LONG result = ERROR_SUCCESS; + for (num_values = 0; result == ERROR_SUCCESS; ++num_values) { + // Max 32767 bytes according to MSDN, but we never use that much. + const size_t kMaxValueNameLength = 2048; + char buffer[kMaxValueNameLength]; + DWORD size = arraysize(buffer); + + result = RegEnumValueA(events_key.Handle(), num_values, buffer, &size, + NULL, NULL, NULL, NULL); + if (result == ERROR_SUCCESS) + events->push_back(std::string(buffer)); + } + + return result == ERROR_NO_MORE_ITEMS; +} + +bool RlzValueStoreRegistry::ClearProductEvent(Product product, + const char* event_rlz) { + std::wstring event_rlz_wide(ASCIIToWide(event_rlz)); + base::win::RegKey key; + GetEventsRegKey(kEventsSubkeyName, &product, KEY_WRITE, &key); + key.DeleteValue(event_rlz_wide.c_str()); + + // Verify deletion. + DWORD value; + if (key.ReadValueDW(event_rlz_wide.c_str(), &value) == ERROR_SUCCESS) { + ASSERT_STRING("ClearProductEvent: Could not delete the event value."); + return false; + } + + return true; +} + +bool RlzValueStoreRegistry::ClearAllProductEvents(Product product) { + return ClearAllProductEventValues(product, kEventsSubkeyName); +} + +bool RlzValueStoreRegistry::AddStatefulEvent(Product product, + const char* event_rlz) { + base::win::RegKey key; + std::wstring event_rlz_wide(ASCIIToWide(event_rlz)); + if (!GetEventsRegKey(kStatefulEventsSubkeyName, &product, KEY_WRITE, &key) || + key.WriteValue(event_rlz_wide.c_str(), 1) != ERROR_SUCCESS) { + ASSERT_STRING( + "AddStatefulEvent: Could not write the new stateful event"); + return false; + } + + return true; +} + +bool RlzValueStoreRegistry::IsStatefulEvent(Product product, + const char* event_rlz) { + DWORD value; + base::win::RegKey key; + GetEventsRegKey(kStatefulEventsSubkeyName, &product, KEY_READ, &key); + std::wstring event_rlz_wide(ASCIIToWide(event_rlz)); + return key.ReadValueDW(event_rlz_wide.c_str(), &value) == ERROR_SUCCESS; +} + +bool RlzValueStoreRegistry::ClearAllStatefulEvents(Product product) { + return ClearAllProductEventValues(product, kStatefulEventsSubkeyName); +} + +void RlzValueStoreRegistry::CollectGarbage() { + // Delete each of the known subkeys if empty. + const char* subkeys[] = { + kRlzsSubkeyName, + kEventsSubkeyName, + kStatefulEventsSubkeyName, + kPingTimesSubkeyName + }; + + for (int i = 0; i < arraysize(subkeys); i++) { + std::string subkey_name; + base::StringAppendF(&subkey_name, "%s\\%s", kLibKeyName, subkeys[i]); + AppendBrandToString(&subkey_name); + + VERIFY(DeleteKeyIfEmpty(HKEY_CURRENT_USER, + ASCIIToWide(subkey_name).c_str())); + } + + // Delete the library key and its parents too now if empty. + VERIFY(DeleteKeyIfEmpty(HKEY_CURRENT_USER, GetWideLibKeyName().c_str())); + VERIFY(DeleteKeyIfEmpty(HKEY_CURRENT_USER, kGoogleCommonKeyName)); + VERIFY(DeleteKeyIfEmpty(HKEY_CURRENT_USER, kGoogleKeyName)); +} + +ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() { + if (!lock_.failed()) + store_.reset(new RlzValueStoreRegistry); +} + +ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() { +} + +RlzValueStore* ScopedRlzValueStoreLock::GetStore() { + return store_.get(); +} + +} // namespace rlz_lib diff --git a/rlz/win/lib/rlz_value_store_registry.h b/rlz/win/lib/rlz_value_store_registry.h new file mode 100644 index 0000000..c7fae46 --- /dev/null +++ b/rlz/win/lib/rlz_value_store_registry.h @@ -0,0 +1,55 @@ +// 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_WIN_LIB_RLZ_VALUE_STORE_REGISTRY_H_ +#define RLZ_WIN_LIB_RLZ_VALUE_STORE_REGISTRY_H_ + +#include "base/compiler_specific.h" +#include "rlz/lib/rlz_value_store.h" + +namespace rlz_lib { + +// Implements RlzValueStore by storing values in the windows registry. +class RlzValueStoreRegistry : public RlzValueStore { + public: + static std::wstring GetWideLibKeyName(); + + virtual bool HasAccess(AccessType type) OVERRIDE; + + virtual bool WritePingTime(Product product, int64 time) OVERRIDE; + virtual bool ReadPingTime(Product product, int64* time) OVERRIDE; + virtual bool ClearPingTime(Product product) OVERRIDE; + + virtual bool WriteAccessPointRlz(AccessPoint access_point, + const char* new_rlz) OVERRIDE; + virtual bool ReadAccessPointRlz(AccessPoint access_point, + char* rlz, + size_t rlz_size) OVERRIDE; + virtual bool ClearAccessPointRlz(AccessPoint access_point) OVERRIDE; + + virtual bool AddProductEvent(Product product, const char* event_rlz) OVERRIDE; + virtual bool ReadProductEvents(Product product, + std::vector<std::string>* events) OVERRIDE; + virtual bool ClearProductEvent(Product product, + const char* event_rlz) OVERRIDE; + virtual bool ClearAllProductEvents(Product product) OVERRIDE; + + virtual bool AddStatefulEvent(Product product, + const char* event_rlz) OVERRIDE; + virtual bool IsStatefulEvent(Product product, + const char* event_rlz) OVERRIDE; + virtual bool ClearAllStatefulEvents(Product product) OVERRIDE; + + virtual void CollectGarbage() OVERRIDE; + + private: + RlzValueStoreRegistry() {} + DISALLOW_COPY_AND_ASSIGN(RlzValueStoreRegistry); + friend class ScopedRlzValueStoreLock; +}; + +} // namespace rlz_lib + +#endif // RLZ_WIN_LIB_RLZ_VALUE_STORE_REGISTRY_H_ + diff --git a/rlz/win/lib/vista_winnt.h b/rlz/win/lib/vista_winnt.h new file mode 100644 index 0000000..073e66f --- /dev/null +++ b/rlz/win/lib/vista_winnt.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. +// +// This file contains snippets borrowed from the Vista SDK version of +// WinNT.h, (c) Microsoft (2006) + +#ifndef RLZ_WIN_LIB_VISTA_WINNT_H_ +#define RLZ_WIN_LIB_VISTA_WINNT_H_ + +#include <windows.h> + +// If no Vista SDK yet, borrow these from Vista's version of WinNT.h +#ifndef SE_GROUP_INTEGRITY + +// TOKEN_MANDATORY_LABEL.Label.Attributes = SE_GROUP_INTEGRITY +#define SE_GROUP_INTEGRITY (0x00000020L) +#define SE_GROUP_INTEGRITY_ENABLED (0x00000040L) + +typedef struct _TOKEN_MANDATORY_LABEL { + SID_AND_ATTRIBUTES Label; +} TOKEN_MANDATORY_LABEL, *PTOKEN_MANDATORY_LABEL; + +// These are a few new enums for TOKEN_INFORMATION_CLASS +#define TokenElevationType static_cast<TOKEN_INFORMATION_CLASS>(18) +#define TokenLinkedToken static_cast<TOKEN_INFORMATION_CLASS>(19) +#define TokenElevation static_cast<TOKEN_INFORMATION_CLASS>(20) +#define TokenHasRestrictions static_cast<TOKEN_INFORMATION_CLASS>(21) +#define TokenAccessInformation static_cast<TOKEN_INFORMATION_CLASS>(22) +#define TokenVirtualizationAllowed static_cast<TOKEN_INFORMATION_CLASS>(23) +#define TokenVirtualizationEnabled static_cast<TOKEN_INFORMATION_CLASS>(24) +// TokenIntegrityLevel is the proces's privilege level, low, med, or high +#define TokenIntegrityLevel static_cast<TOKEN_INFORMATION_CLASS>(25) +// TokenIntegrityLevelDeasktop is an alternate level used for access apis +// (screen readers, imes) +#define TokenIntegrityLevelDesktop static_cast<TOKEN_INFORMATION_CLASS>(26) + +// This is a new flag to pass to GetNamedSecurityInfo or SetNamedSecurityInfo +// that puts the mandatory level label info in an access control list (ACL) +// structure in the parameter normally used for system acls (SACL) +#define LABEL_SECURITY_INFORMATION (0x00000010L) + +// The new Access Control Entry type identifier for mandatory labels +#define SYSTEM_MANDATORY_LABEL_ACE_TYPE (0x11) + +// The structure of mandatory label acess control binary entry +typedef struct _SYSTEM_MANDATORY_LABEL_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD SidStart; +} SYSTEM_MANDATORY_LABEL_ACE, *PSYSTEM_MANDATORY_LABEL_ACE; + +// Masks for ACCESS_MASK above +#define SYSTEM_MANDATORY_LABEL_NO_WRITE_UP 0x1 +#define SYSTEM_MANDATORY_LABEL_NO_READ_UP 0x2 +#define SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP 0x4 +#define SYSTEM_MANDATORY_LABEL_VALID_MASK \ + (SYSTEM_MANDATORY_LABEL_NO_WRITE_UP | \ + SYSTEM_MANDATORY_LABEL_NO_READ_UP | \ + SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP) + +// The SID authority for mandatory labels +#define SECURITY_MANDATORY_LABEL_AUTHORITY {0, 0, 0, 0, 0, 16} + +// the RID values (sub authorities) that define mandatory label levels +#define SECURITY_MANDATORY_UNTRUSTED_RID (0x00000000L) +#define SECURITY_MANDATORY_LOW_RID (0x00001000L) +#define SECURITY_MANDATORY_MEDIUM_RID (0x00002000L) +#define SECURITY_MANDATORY_HIGH_RID (0x00003000L) +#define SECURITY_MANDATORY_SYSTEM_RID (0x00004000L) +#define SECURITY_MANDATORY_UI_ACCESS_RID (0x00004100L) +#define SECURITY_MANDATORY_PROTECTED_PROCESS_RID (0x00005000L) + +// Vista's mandatory labels, enumerated +typedef enum _MANDATORY_LEVEL { + MandatoryLevelUntrusted = 0, + MandatoryLevelLow, + MandatoryLevelMedium, + MandatoryLevelHigh, + MandatoryLevelSystem, + MandatoryLevelSecureProcess, + MandatoryLevelCount +} MANDATORY_LEVEL, *PMANDATORY_LEVEL; + + +// Token elevation values describe the relative strength of a given token. +// A full token is a token with all groups and privileges to which the +// principal is authorized. A limited token is one with some groups or +// privileges removed. + +typedef enum _TOKEN_ELEVATION_TYPE { + TokenElevationTypeDefault = 1, + TokenElevationTypeFull, + TokenElevationTypeLimited, +} TOKEN_ELEVATION_TYPE, *PTOKEN_ELEVATION_TYPE; + +#endif // #ifndef SE_GROUP_INTEGRITY + +#endif // RLZ_WIN_LIB_VISTA_WINNT_H_ |