// 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/strings/string_util.h" #include "base/strings/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 (base::IsAsciiAlpha(ch) || base::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) { size_t 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* event_array) { // Get the string of events. std::string events = response_line.substr(field_header.size()); base::TrimWhitespaceASCII(events, base::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 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(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 (!base::StartsWith(response_string, checksum_param, base::CompareCase::SENSITIVE)) 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); base::TrimWhitespaceASCII(checksum, base::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 (base::StartsWith(response_line, kRlzCgiVariable, base::CompareCase::SENSITIVE)) { // 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)); base::TrimWhitespaceASCII(rlz_value, base::TRIM_LEADING, &rlz_value); size_t rlz_length = rlz_value.find_first_of("\r\n "); if (rlz_length == std::string::npos) 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 (base::StartsWith(response_line, events_variable, base::CompareCase::SENSITIVE)) { // Clear events which server parsed. std::vector 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 (base::StartsWith(response_line, stateful_events_variable, base::CompareCase::SENSITIVE)) { // Record any stateful events the server send over. std::vector 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