1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // Library functions related to the Financial Server ping.
6
7 #include "rlz/lib/financial_ping.h"
8
9 #include <stdint.h>
10
11 #include <memory>
12
13 #include "base/atomicops.h"
14 #include "base/location.h"
15 #include "base/memory/ref_counted.h"
16 #include "base/no_destructor.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/synchronization/lock.h"
22 #include "base/synchronization/waitable_event.h"
23 #include "base/task/post_task.h"
24 #include "base/task/thread_pool.h"
25 #include "base/threading/thread_restrictions.h"
26 #include "base/threading/thread_task_runner_handle.h"
27 #include "build/build_config.h"
28 #include "rlz/lib/assert.h"
29 #include "rlz/lib/lib_values.h"
30 #include "rlz/lib/machine_id.h"
31 #include "rlz/lib/rlz_lib.h"
32 #include "rlz/lib/rlz_value_store.h"
33 #include "rlz/lib/string_utils.h"
34 #include "rlz/lib/time_util.h"
35 #include "services/network/public/cpp/shared_url_loader_factory.h"
36 #include "services/network/public/cpp/simple_url_loader.h"
37
38 #if !defined(OS_WIN)
39 #include "base/time/time.h"
40 #endif
41
42 #if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET)
43
44 #include <windows.h>
45 #include <wininet.h>
46
47 namespace {
48
49 class InternetHandle {
50 public:
InternetHandle(HINTERNET handle)51 InternetHandle(HINTERNET handle) { handle_ = handle; }
~InternetHandle()52 ~InternetHandle() { if (handle_) InternetCloseHandle(handle_); }
operator HINTERNET() const53 operator HINTERNET() const { return handle_; }
operator !() const54 bool operator!() const { return (handle_ == NULL); }
55
56 private:
57 HINTERNET handle_;
58 };
59
60 } // namespace
61
62 #else
63
64 #include "base/bind.h"
65 #include "base/run_loop.h"
66 #include "base/time/time.h"
67 #include "net/base/load_flags.h"
68 #include "net/traffic_annotation/network_traffic_annotation.h"
69 #include "url/gurl.h"
70
71 #endif
72
73 namespace rlz_lib {
74
75 using base::subtle::AtomicWord;
76
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)77 bool FinancialPing::FormRequest(Product product,
78 const AccessPoint* access_points, const char* product_signature,
79 const char* product_brand, const char* product_id,
80 const char* product_lang, bool exclude_machine_id,
81 std::string* request) {
82 if (!request) {
83 ASSERT_STRING("FinancialPing::FormRequest: request is NULL");
84 return false;
85 }
86
87 request->clear();
88
89 ScopedRlzValueStoreLock lock;
90 RlzValueStore* store = lock.GetStore();
91 if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
92 return false;
93
94 if (!access_points) {
95 ASSERT_STRING("FinancialPing::FormRequest: access_points is NULL");
96 return false;
97 }
98
99 if (!product_signature) {
100 ASSERT_STRING("FinancialPing::FormRequest: product_signature is NULL");
101 return false;
102 }
103
104 if (!SupplementaryBranding::GetBrand().empty()) {
105 if (SupplementaryBranding::GetBrand() != product_brand) {
106 ASSERT_STRING("FinancialPing::FormRequest: supplementary branding bad");
107 return false;
108 }
109 }
110
111 base::StringAppendF(request, "%s?", kFinancialPingPath);
112
113 // Add the signature, brand, product id and language.
114 base::StringAppendF(request, "%s=%s", kProductSignatureCgiVariable,
115 product_signature);
116 if (product_brand)
117 base::StringAppendF(request, "&%s=%s", kProductBrandCgiVariable,
118 product_brand);
119
120 if (product_id)
121 base::StringAppendF(request, "&%s=%s", kProductIdCgiVariable, product_id);
122
123 if (product_lang)
124 base::StringAppendF(request, "&%s=%s", kProductLanguageCgiVariable,
125 product_lang);
126
127 // Add the product events.
128 char cgi[kMaxCgiLength + 1];
129 cgi[0] = 0;
130 bool has_events = GetProductEventsAsCgi(product, cgi, base::size(cgi));
131 if (has_events)
132 base::StringAppendF(request, "&%s", cgi);
133
134 // If we don't have any events, we should ping all the AP's on the system
135 // that we know about and have a current RLZ value, even if they are not
136 // used by this product.
137 AccessPoint all_points[LAST_ACCESS_POINT];
138 if (!has_events) {
139 char rlz[kMaxRlzLength + 1];
140 int idx = 0;
141 for (int ap = NO_ACCESS_POINT + 1; ap < LAST_ACCESS_POINT; ap++) {
142 rlz[0] = 0;
143 AccessPoint point = static_cast<AccessPoint>(ap);
144 if (GetAccessPointRlz(point, rlz, base::size(rlz)) && rlz[0] != '\0')
145 all_points[idx++] = point;
146 }
147 all_points[idx] = NO_ACCESS_POINT;
148 }
149
150 // Add the RLZ's and the DCC if needed. This is the same as get PingParams.
151 // This will also include the RLZ Exchange Protocol CGI Argument.
152 cgi[0] = 0;
153 if (GetPingParams(product, has_events ? access_points : all_points, cgi,
154 base::size(cgi)))
155 base::StringAppendF(request, "&%s", cgi);
156
157 if (has_events && !exclude_machine_id) {
158 std::string machine_id;
159 if (GetMachineId(&machine_id)) {
160 base::StringAppendF(request, "&%s=%s", kMachineIdCgiVariable,
161 machine_id.c_str());
162 }
163 }
164
165 return true;
166 }
167
168 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
169 namespace {
170
171 // A waitable event used to detect when either:
172 //
173 // 1/ the RLZ ping request completes
174 // 2/ the RLZ ping request times out
175 // 3/ browser shutdown begins
176 class RefCountedWaitableEvent
177 : public base::RefCountedThreadSafe<RefCountedWaitableEvent> {
178 public:
RefCountedWaitableEvent()179 RefCountedWaitableEvent()
180 : event_(base::WaitableEvent::ResetPolicy::MANUAL,
181 base::WaitableEvent::InitialState::NOT_SIGNALED) {}
182
SignalShutdown()183 void SignalShutdown() { event_.Signal(); }
184
SignalFetchComplete(int response_code,std::string response)185 void SignalFetchComplete(int response_code, std::string response) {
186 base::AutoLock autolock(lock_);
187 response_code_ = response_code;
188 response_ = std::move(response);
189 event_.Signal();
190 }
191
TimedWait(base::TimeDelta timeout)192 bool TimedWait(base::TimeDelta timeout) { return event_.TimedWait(timeout); }
193
GetResponseCode()194 int GetResponseCode() {
195 base::AutoLock autolock(lock_);
196 return response_code_;
197 }
198
TakeResponse()199 std::string TakeResponse() {
200 base::AutoLock autolock(lock_);
201 std::string temp = std::move(response_);
202 response_.clear();
203 return temp;
204 }
205
206 private:
207 ~RefCountedWaitableEvent() = default;
208 friend class base::RefCountedThreadSafe<RefCountedWaitableEvent>;
209
210 base::WaitableEvent event_;
211 base::Lock lock_;
212 std::string response_;
213 int response_code_ = -1;
214 };
215
216 // The URL load complete callback signals an instance of
217 // RefCountedWaitableEvent when the load completes.
OnURLLoadComplete(std::unique_ptr<network::SimpleURLLoader> url_loader,scoped_refptr<RefCountedWaitableEvent> event,std::unique_ptr<std::string> response_body)218 void OnURLLoadComplete(std::unique_ptr<network::SimpleURLLoader> url_loader,
219 scoped_refptr<RefCountedWaitableEvent> event,
220 std::unique_ptr<std::string> response_body) {
221 int response_code = -1;
222 if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) {
223 response_code = url_loader->ResponseInfo()->headers->response_code();
224 }
225
226 std::string response;
227 if (response_body) {
228 response = std::move(*response_body);
229 }
230
231 event->SignalFetchComplete(response_code, std::move(response));
232 }
233
234 bool send_financial_ping_interrupted_for_test = false;
235
236 } // namespace
237
238 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
239
240 // The signal for the current ping request. It can be used to cancel the request
241 // in case of a shutdown.
GetPingResultEvent()242 scoped_refptr<RefCountedWaitableEvent>& GetPingResultEvent() {
243 static base::NoDestructor<scoped_refptr<RefCountedWaitableEvent>>
244 g_pingResultEvent;
245 return *g_pingResultEvent;
246 }
247
248 // The pointer to URLRequestContextGetter used by FinancialPing::PingServer().
249 // It is atomic pointer because it can be accessed and modified by multiple
250 // threads.
251 AtomicWord g_URLLoaderFactory;
252
SetURLLoaderFactory(network::mojom::URLLoaderFactory * factory)253 bool FinancialPing::SetURLLoaderFactory(
254 network::mojom::URLLoaderFactory* factory) {
255 base::subtle::Release_Store(&g_URLLoaderFactory,
256 reinterpret_cast<AtomicWord>(factory));
257 scoped_refptr<RefCountedWaitableEvent> event = GetPingResultEvent();
258 if (!factory && event) {
259 send_financial_ping_interrupted_for_test = true;
260 event->SignalShutdown();
261 }
262 return true;
263 }
264
265 #endif
266
PingRlzServer(std::string url,scoped_refptr<RefCountedWaitableEvent> event)267 void PingRlzServer(std::string url,
268 scoped_refptr<RefCountedWaitableEvent> event) {
269 // Copy the pointer to stack because g_URLLoaderFactory may be set to NULL
270 // in different thread. The instance is guaranteed to exist while
271 // the method is running.
272 network::mojom::URLLoaderFactory* url_loader_factory =
273 reinterpret_cast<network::mojom::URLLoaderFactory*>(
274 base::subtle::Acquire_Load(&g_URLLoaderFactory));
275
276 // Browser shutdown will cause the factory to be reset to NULL.
277 // ShutdownCheck will catch this.
278 if (!url_loader_factory)
279 return;
280
281 net::NetworkTrafficAnnotationTag traffic_annotation =
282 net::DefineNetworkTrafficAnnotation("rlz_ping", R"(
283 semantics {
284 sender: "RLZ Ping"
285 description:
286 "Used for measuring the effectiveness of a promotion. See the "
287 "Chrome Privacy Whitepaper for complete details."
288 trigger:
289 "1- At Chromium first run.\n"
290 "2- When Chromium is re-activated by a new promotion.\n"
291 "3- Once a week thereafter as long as Chromium is used.\n"
292 data:
293 "1- Non-unique cohort tag of when Chromium was installed.\n"
294 "2- Unique machine id on desktop platforms.\n"
295 "3- Whether Google is the default omnibox search.\n"
296 "4- Whether google.com is the default home page."
297 destination: GOOGLE_OWNED_SERVICE
298 }
299 policy {
300 cookies_allowed: NO
301 setting: "This feature cannot be disabled in settings."
302 policy_exception_justification: "Not implemented."
303 })");
304 auto resource_request = std::make_unique<network::ResourceRequest>();
305 resource_request->url = GURL(url);
306 resource_request->load_flags = net::LOAD_DISABLE_CACHE;
307 resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
308
309 auto url_loader = network::SimpleURLLoader::Create(
310 std::move(resource_request), traffic_annotation);
311
312 // Pass ownership of the loader to the bound function. Otherwise the load will
313 // be canceled when the SimpleURLLoader object is destroyed.
314 auto* url_loader_ptr = url_loader.get();
315 url_loader_ptr->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
316 url_loader_factory,
317 base::BindOnce(&OnURLLoadComplete, std::move(url_loader),
318 std::move(event)));
319 }
320 #endif
321
PingServer(const char * request,std::string * response)322 FinancialPing::PingResponse FinancialPing::PingServer(const char* request,
323 std::string* response) {
324 if (!response)
325 return PING_FAILURE;
326
327 response->clear();
328
329 #if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET)
330 // Initialize WinInet.
331 InternetHandle inet_handle = InternetOpenA(kFinancialPingUserAgent,
332 INTERNET_OPEN_TYPE_PRECONFIG,
333 NULL, NULL, 0);
334 if (!inet_handle)
335 return PING_FAILURE;
336
337 // Open network connection.
338 InternetHandle connection_handle = InternetConnectA(inet_handle,
339 kFinancialServer, kFinancialPort, "", "", INTERNET_SERVICE_HTTP,
340 INTERNET_FLAG_NO_CACHE_WRITE, 0);
341 if (!connection_handle)
342 return PING_FAILURE;
343
344 // Prepare the HTTP request.
345 const DWORD kFlags = INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES |
346 INTERNET_FLAG_SECURE;
347 InternetHandle http_handle =
348 HttpOpenRequestA(connection_handle, "GET", request, NULL, NULL,
349 kFinancialPingResponseObjects, kFlags, NULL);
350 if (!http_handle)
351 return PING_FAILURE;
352
353 // Timeouts are probably:
354 // INTERNET_OPTION_SEND_TIMEOUT, INTERNET_OPTION_RECEIVE_TIMEOUT
355
356 // Send the HTTP request. Note: Fails if user is working in off-line mode.
357 if (!HttpSendRequest(http_handle, NULL, 0, NULL, 0))
358 return PING_FAILURE;
359
360 // Check the response status.
361 DWORD status;
362 DWORD status_size = sizeof(status);
363 if (!HttpQueryInfo(http_handle, HTTP_QUERY_STATUS_CODE |
364 HTTP_QUERY_FLAG_NUMBER, &status, &status_size, NULL) ||
365 200 != status)
366 return PING_FAILURE;
367
368 // Get the response text.
369 std::unique_ptr<char[]> buffer(new char[kMaxPingResponseLength]);
370 if (buffer.get() == NULL)
371 return PING_FAILURE;
372
373 DWORD bytes_read = 0;
374 while (InternetReadFile(http_handle, buffer.get(), kMaxPingResponseLength,
375 &bytes_read) && bytes_read > 0) {
376 response->append(buffer.get(), bytes_read);
377 bytes_read = 0;
378 };
379
380 return PING_SUCCESSFUL;
381 #else
382 std::string url =
383 base::StringPrintf("https://%s%s", kFinancialServer, request);
384
385 // Use a waitable event to cause this function to block, to match the
386 // wininet implementation.
387 auto event = base::MakeRefCounted<RefCountedWaitableEvent>();
388 scoped_refptr<RefCountedWaitableEvent>& event_ref = GetPingResultEvent();
389 event_ref = event;
390
391 // PingRlzServer must be run in a separate sequence so that the TimedWait()
392 // call below does not block the URL fetch response from being handled by
393 // the URL delegate.
394 scoped_refptr<base::SequencedTaskRunner> background_runner(
395 base::ThreadPool::CreateSequencedTaskRunner(
396 {base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
397 base::TaskPriority::BEST_EFFORT}));
398 background_runner->PostTask(FROM_HERE,
399 base::BindOnce(&PingRlzServer, url, event));
400
401 bool is_signaled;
402 {
403 base::ScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
404 is_signaled = event->TimedWait(base::TimeDelta::FromMinutes(5));
405 }
406
407 event_ref.reset();
408 if (!is_signaled)
409 return PING_FAILURE;
410
411 if (event->GetResponseCode() == -1) {
412 return PING_SHUTDOWN;
413 } else if (event->GetResponseCode() != 200) {
414 return PING_FAILURE;
415 }
416
417 *response = event->TakeResponse();
418 return PING_SUCCESSFUL;
419 #endif
420 }
421
IsPingTime(Product product,bool no_delay)422 bool FinancialPing::IsPingTime(Product product, bool no_delay) {
423 ScopedRlzValueStoreLock lock;
424 RlzValueStore* store = lock.GetStore();
425 if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
426 return false;
427
428 int64_t last_ping = 0;
429 if (!store->ReadPingTime(product, &last_ping))
430 return true;
431
432 uint64_t now = GetSystemTimeAsInt64();
433 int64_t interval = now - last_ping;
434
435 // If interval is negative, clock was probably reset. So ping.
436 if (interval < 0)
437 return true;
438
439 // Check if this product has any unreported events.
440 char cgi[kMaxCgiLength + 1];
441 cgi[0] = 0;
442 bool has_events = GetProductEventsAsCgi(product, cgi, base::size(cgi));
443 if (no_delay && has_events)
444 return true;
445
446 return interval >= (has_events ? kEventsPingInterval : kNoEventsPingInterval);
447 }
448
449
UpdateLastPingTime(Product product)450 bool FinancialPing::UpdateLastPingTime(Product product) {
451 ScopedRlzValueStoreLock lock;
452 RlzValueStore* store = lock.GetStore();
453 if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
454 return false;
455
456 uint64_t now = GetSystemTimeAsInt64();
457 return store->WritePingTime(product, now);
458 }
459
460
ClearLastPingTime(Product product)461 bool FinancialPing::ClearLastPingTime(Product product) {
462 ScopedRlzValueStoreLock lock;
463 RlzValueStore* store = lock.GetStore();
464 if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
465 return false;
466 return store->ClearPingTime(product);
467 }
468
469 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
470 namespace test {
471
ResetSendFinancialPingInterrupted()472 void ResetSendFinancialPingInterrupted() {
473 send_financial_ping_interrupted_for_test = false;
474 }
475
WasSendFinancialPingInterrupted()476 bool WasSendFinancialPingInterrupted() {
477 return send_financial_ping_interrupted_for_test;
478 }
479
480 } // namespace test
481 #endif
482
483 } // namespace rlz_lib
484