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