| // Copyright 2012 The Chromium Authors |
| // 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 <stdint.h> |
| |
| #include <memory> |
| |
| #include "base/atomicops.h" |
| #include "base/location.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.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" |
| #include "rlz/lib/time_util.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| |
| #if !BUILDFLAG(IS_WIN) |
| #include "base/time/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/functional/bind.h" |
| #include "base/run_loop.h" |
| #include "base/time/time.h" |
| #include "net/base/load_flags.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "url/gurl.h" |
| |
| #endif |
| |
| namespace rlz_lib { |
| |
| using base::subtle::AtomicWord; |
| |
| 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, std::size(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, std::size(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, |
| std::size(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) |
| namespace { |
| |
| // A waitable event used to detect when either: |
| // |
| // 1/ the RLZ ping request completes |
| // 2/ the RLZ ping request times out |
| // 3/ browser shutdown begins |
| class RefCountedWaitableEvent |
| : public base::RefCountedThreadSafe<RefCountedWaitableEvent> { |
| public: |
| RefCountedWaitableEvent() |
| : event_(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED) {} |
| |
| void SignalShutdown() { event_.Signal(); } |
| |
| void SignalFetchComplete(int response_code, std::string response) { |
| base::AutoLock autolock(lock_); |
| response_code_ = response_code; |
| response_ = std::move(response); |
| event_.Signal(); |
| } |
| |
| bool TimedWait(base::TimeDelta timeout) { return event_.TimedWait(timeout); } |
| |
| int GetResponseCode() { |
| base::AutoLock autolock(lock_); |
| return response_code_; |
| } |
| |
| std::string TakeResponse() { |
| base::AutoLock autolock(lock_); |
| std::string temp = std::move(response_); |
| response_.clear(); |
| return temp; |
| } |
| |
| private: |
| ~RefCountedWaitableEvent() = default; |
| friend class base::RefCountedThreadSafe<RefCountedWaitableEvent>; |
| |
| base::WaitableEvent event_; |
| base::Lock lock_; |
| std::string response_; |
| int response_code_ = -1; |
| }; |
| |
| // The URL load complete callback signals an instance of |
| // RefCountedWaitableEvent when the load completes. |
| void OnURLLoadComplete(std::unique_ptr<network::SimpleURLLoader> url_loader, |
| scoped_refptr<RefCountedWaitableEvent> event, |
| std::unique_ptr<std::string> response_body) { |
| int response_code = -1; |
| if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) { |
| response_code = url_loader->ResponseInfo()->headers->response_code(); |
| } |
| |
| std::string response; |
| if (response_body) { |
| response = std::move(*response_body); |
| } |
| |
| event->SignalFetchComplete(response_code, std::move(response)); |
| } |
| |
| bool send_financial_ping_interrupted_for_test = false; |
| |
| } // namespace |
| |
| #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) |
| |
| // The signal for the current ping request. It can be used to cancel the request |
| // in case of a shutdown. |
| scoped_refptr<RefCountedWaitableEvent>& GetPingResultEvent() { |
| static base::NoDestructor<scoped_refptr<RefCountedWaitableEvent>> |
| g_pingResultEvent; |
| return *g_pingResultEvent; |
| } |
| |
| // The pointer to URLRequestContextGetter used by FinancialPing::PingServer(). |
| // It is atomic pointer because it can be accessed and modified by multiple |
| // threads. |
| AtomicWord g_URLLoaderFactory; |
| |
| bool FinancialPing::SetURLLoaderFactory( |
| network::mojom::URLLoaderFactory* factory) { |
| base::subtle::Release_Store(&g_URLLoaderFactory, |
| reinterpret_cast<AtomicWord>(factory)); |
| scoped_refptr<RefCountedWaitableEvent> event = GetPingResultEvent(); |
| if (!factory && event) { |
| send_financial_ping_interrupted_for_test = true; |
| event->SignalShutdown(); |
| } |
| return true; |
| } |
| |
| #endif |
| |
| void PingRlzServer(std::string url, |
| scoped_refptr<RefCountedWaitableEvent> event) { |
| // Copy the pointer to stack because g_URLLoaderFactory may be set to NULL |
| // in different thread. The instance is guaranteed to exist while |
| // the method is running. |
| network::mojom::URLLoaderFactory* url_loader_factory = |
| reinterpret_cast<network::mojom::URLLoaderFactory*>( |
| base::subtle::Acquire_Load(&g_URLLoaderFactory)); |
| |
| // Browser shutdown will cause the factory to be reset to NULL. |
| // ShutdownCheck will catch this. |
| if (!url_loader_factory) |
| return; |
| |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("rlz_ping", R"( |
| semantics { |
| sender: "RLZ Ping" |
| description: |
| "Used for measuring the effectiveness of a promotion. See the " |
| "Chrome Privacy Whitepaper for complete details." |
| trigger: |
| "1- At Chromium first run.\n" |
| "2- When Chromium is re-activated by a new promotion.\n" |
| "3- Once a week thereafter as long as Chromium is used.\n" |
| data: |
| "1- Non-unique cohort tag of when Chromium was installed.\n" |
| "2- Unique machine id on desktop platforms.\n" |
| "3- Whether Google is the default omnibox search.\n" |
| "4- Whether google.com is the default home page." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: "This feature cannot be disabled in settings." |
| policy_exception_justification: "Not implemented." |
| })"); |
| auto resource_request = std::make_unique<network::ResourceRequest>(); |
| resource_request->url = GURL(url); |
| resource_request->load_flags = net::LOAD_DISABLE_CACHE; |
| resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit; |
| |
| auto url_loader = network::SimpleURLLoader::Create( |
| std::move(resource_request), traffic_annotation); |
| |
| // Pass ownership of the loader to the bound function. Otherwise the load will |
| // be canceled when the SimpleURLLoader object is destroyed. |
| auto* url_loader_ptr = url_loader.get(); |
| url_loader_ptr->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| url_loader_factory, |
| base::BindOnce(&OnURLLoadComplete, std::move(url_loader), |
| std::move(event))); |
| } |
| #endif |
| |
| FinancialPing::PingResponse FinancialPing::PingServer(const char* request, |
| std::string* response) { |
| if (!response) |
| return PING_FAILURE; |
| |
| 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 PING_FAILURE; |
| |
| // Open network connection. |
| InternetHandle connection_handle = InternetConnectA(inet_handle, |
| kFinancialServer, kFinancialPort, "", "", INTERNET_SERVICE_HTTP, |
| INTERNET_FLAG_NO_CACHE_WRITE, 0); |
| if (!connection_handle) |
| return PING_FAILURE; |
| |
| // Prepare the HTTP request. |
| const DWORD kFlags = INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES | |
| INTERNET_FLAG_SECURE; |
| InternetHandle http_handle = |
| HttpOpenRequestA(connection_handle, "GET", request, NULL, NULL, |
| kFinancialPingResponseObjects, kFlags, NULL); |
| if (!http_handle) |
| return PING_FAILURE; |
| |
| // 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 PING_FAILURE; |
| |
| // 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 PING_FAILURE; |
| |
| // Get the response text. |
| std::unique_ptr<char[]> buffer(new char[kMaxPingResponseLength]); |
| if (buffer.get() == NULL) |
| return PING_FAILURE; |
| |
| 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 PING_SUCCESSFUL; |
| #else |
| std::string url = |
| base::StringPrintf("https://%s%s", kFinancialServer, request); |
| |
| // Use a waitable event to cause this function to block, to match the |
| // wininet implementation. |
| auto event = base::MakeRefCounted<RefCountedWaitableEvent>(); |
| scoped_refptr<RefCountedWaitableEvent>& event_ref = GetPingResultEvent(); |
| event_ref = event; |
| |
| // PingRlzServer must be run in a separate sequence so that the TimedWait() |
| // call below does not block the URL fetch response from being handled by |
| // the URL delegate. |
| scoped_refptr<base::SequencedTaskRunner> background_runner( |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, |
| base::TaskPriority::BEST_EFFORT})); |
| background_runner->PostTask(FROM_HERE, |
| base::BindOnce(&PingRlzServer, url, event)); |
| |
| bool is_signaled; |
| { |
| base::ScopedAllowBaseSyncPrimitives allow_base_sync_primitives; |
| is_signaled = event->TimedWait(base::Minutes(5)); |
| } |
| |
| event_ref.reset(); |
| if (!is_signaled) |
| return PING_FAILURE; |
| |
| if (event->GetResponseCode() == -1) { |
| return PING_SHUTDOWN; |
| } else if (event->GetResponseCode() != 200) { |
| return PING_FAILURE; |
| } |
| |
| *response = event->TakeResponse(); |
| return PING_SUCCESSFUL; |
| #endif |
| } |
| |
| bool FinancialPing::IsPingTime(Product product, bool no_delay) { |
| ScopedRlzValueStoreLock lock; |
| RlzValueStore* store = lock.GetStore(); |
| if (!store || !store->HasAccess(RlzValueStore::kReadAccess)) |
| return false; |
| |
| int64_t last_ping = 0; |
| if (!store->ReadPingTime(product, &last_ping)) |
| return true; |
| |
| uint64_t now = GetSystemTimeAsInt64(); |
| int64_t 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, std::size(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_t 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); |
| } |
| |
| #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET) |
| namespace test { |
| |
| void ResetSendFinancialPingInterrupted() { |
| send_financial_ping_interrupted_for_test = false; |
| } |
| |
| bool WasSendFinancialPingInterrupted() { |
| return send_financial_ping_interrupted_for_test; |
| } |
| |
| } // namespace test |
| #endif |
| |
| } // namespace rlz_lib |