• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "base/atomicops.h"
10 #include "base/basictypes.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/memory/weak_ptr.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "rlz/lib/assert.h"
17 #include "rlz/lib/lib_values.h"
18 #include "rlz/lib/machine_id.h"
19 #include "rlz/lib/rlz_lib.h"
20 #include "rlz/lib/rlz_value_store.h"
21 #include "rlz/lib/string_utils.h"
22 
23 #if !defined(OS_WIN)
24 #include "base/time/time.h"
25 #endif
26 
27 #if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET)
28 
29 #include <windows.h>
30 #include <wininet.h>
31 
32 namespace {
33 
34 class InternetHandle {
35  public:
InternetHandle(HINTERNET handle)36   InternetHandle(HINTERNET handle) { handle_ = handle; }
~InternetHandle()37   ~InternetHandle() { if (handle_) InternetCloseHandle(handle_); }
operator HINTERNET() const38   operator HINTERNET() const { return handle_; }
operator !() const39   bool operator!() const { return (handle_ == NULL); }
40 
41  private:
42   HINTERNET handle_;
43 };
44 
45 }  // namespace
46 
47 #else
48 
49 #include "base/bind.h"
50 #include "base/message_loop/message_loop.h"
51 #include "base/run_loop.h"
52 #include "base/time/time.h"
53 #include "net/base/load_flags.h"
54 #include "net/url_request/url_fetcher.h"
55 #include "net/url_request/url_fetcher_delegate.h"
56 #include "net/url_request/url_request_context.h"
57 #include "net/url_request/url_request_context_getter.h"
58 #include "url/gurl.h"
59 
60 #endif
61 
62 namespace {
63 
64 // Returns the time relative to a fixed point in the past in multiples of
65 // 100 ns stepts. The point in the past is arbitrary but can't change, as the
66 // result of this value is stored on disk.
GetSystemTimeAsInt64()67 int64 GetSystemTimeAsInt64() {
68 #if defined(OS_WIN)
69   FILETIME now_as_file_time;
70   // Relative to Jan 1, 1601 (UTC).
71   GetSystemTimeAsFileTime(&now_as_file_time);
72 
73   LARGE_INTEGER integer;
74   integer.HighPart = now_as_file_time.dwHighDateTime;
75   integer.LowPart = now_as_file_time.dwLowDateTime;
76   return integer.QuadPart;
77 #else
78   // Seconds since epoch (Jan 1, 1970).
79   double now_seconds = base::Time::Now().ToDoubleT();
80   return static_cast<int64>(now_seconds * 1000 * 1000 * 10);
81 #endif
82 }
83 
84 }  // namespace
85 
86 
87 namespace rlz_lib {
88 
89 using base::subtle::AtomicWord;
90 
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)91 bool FinancialPing::FormRequest(Product product,
92     const AccessPoint* access_points, const char* product_signature,
93     const char* product_brand, const char* product_id,
94     const char* product_lang, bool exclude_machine_id,
95     std::string* request) {
96   if (!request) {
97     ASSERT_STRING("FinancialPing::FormRequest: request is NULL");
98     return false;
99   }
100 
101   request->clear();
102 
103   ScopedRlzValueStoreLock lock;
104   RlzValueStore* store = lock.GetStore();
105   if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
106     return false;
107 
108   if (!access_points) {
109     ASSERT_STRING("FinancialPing::FormRequest: access_points is NULL");
110     return false;
111   }
112 
113   if (!product_signature) {
114     ASSERT_STRING("FinancialPing::FormRequest: product_signature is NULL");
115     return false;
116   }
117 
118   if (!SupplementaryBranding::GetBrand().empty()) {
119     if (SupplementaryBranding::GetBrand() != product_brand) {
120       ASSERT_STRING("FinancialPing::FormRequest: supplementary branding bad");
121       return false;
122     }
123   }
124 
125   base::StringAppendF(request, "%s?", kFinancialPingPath);
126 
127   // Add the signature, brand, product id and language.
128   base::StringAppendF(request, "%s=%s", kProductSignatureCgiVariable,
129                       product_signature);
130   if (product_brand)
131     base::StringAppendF(request, "&%s=%s", kProductBrandCgiVariable,
132                         product_brand);
133 
134   if (product_id)
135     base::StringAppendF(request, "&%s=%s", kProductIdCgiVariable, product_id);
136 
137   if (product_lang)
138     base::StringAppendF(request, "&%s=%s", kProductLanguageCgiVariable,
139                         product_lang);
140 
141   // Add the product events.
142   char cgi[kMaxCgiLength + 1];
143   cgi[0] = 0;
144   bool has_events = GetProductEventsAsCgi(product, cgi, arraysize(cgi));
145   if (has_events)
146     base::StringAppendF(request, "&%s", cgi);
147 
148   // If we don't have any events, we should ping all the AP's on the system
149   // that we know about and have a current RLZ value, even if they are not
150   // used by this product.
151   AccessPoint all_points[LAST_ACCESS_POINT];
152   if (!has_events) {
153     char rlz[kMaxRlzLength + 1];
154     int idx = 0;
155     for (int ap = NO_ACCESS_POINT + 1; ap < LAST_ACCESS_POINT; ap++) {
156       rlz[0] = 0;
157       AccessPoint point = static_cast<AccessPoint>(ap);
158       if (GetAccessPointRlz(point, rlz, arraysize(rlz)) &&
159           rlz[0] != '\0')
160         all_points[idx++] = point;
161     }
162     all_points[idx] = NO_ACCESS_POINT;
163   }
164 
165   // Add the RLZ's and the DCC if needed. This is the same as get PingParams.
166   // This will also include the RLZ Exchange Protocol CGI Argument.
167   cgi[0] = 0;
168   if (GetPingParams(product, has_events ? access_points : all_points,
169                     cgi, arraysize(cgi)))
170     base::StringAppendF(request, "&%s", cgi);
171 
172   if (has_events && !exclude_machine_id) {
173     std::string machine_id;
174     if (GetMachineId(&machine_id)) {
175       base::StringAppendF(request, "&%s=%s", kMachineIdCgiVariable,
176                           machine_id.c_str());
177     }
178   }
179 
180   return true;
181 }
182 
183 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
184 // The pointer to URLRequestContextGetter used by FinancialPing::PingServer().
185 // It is atomic pointer because it can be accessed and modified by multiple
186 // threads.
187 AtomicWord g_context;
188 
SetURLRequestContext(net::URLRequestContextGetter * context)189 bool FinancialPing::SetURLRequestContext(
190     net::URLRequestContextGetter* context) {
191   base::subtle::Release_Store(
192       &g_context, reinterpret_cast<AtomicWord>(context));
193   return true;
194 }
195 
196 namespace {
197 
198 class FinancialPingUrlFetcherDelegate : public net::URLFetcherDelegate {
199  public:
FinancialPingUrlFetcherDelegate(const base::Closure & callback)200   FinancialPingUrlFetcherDelegate(const base::Closure& callback)
201       : callback_(callback) {
202   }
203   virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
204 
205  private:
206   base::Closure callback_;
207 };
208 
OnURLFetchComplete(const net::URLFetcher * source)209 void FinancialPingUrlFetcherDelegate::OnURLFetchComplete(
210     const net::URLFetcher* source) {
211   callback_.Run();
212 }
213 
214 bool send_financial_ping_interrupted_for_test = false;
215 
216 }  // namespace
217 
ShutdownCheck(base::WeakPtr<base::RunLoop> weak)218 void ShutdownCheck(base::WeakPtr<base::RunLoop> weak) {
219   if (!weak.get())
220     return;
221   if (!base::subtle::Acquire_Load(&g_context)) {
222     send_financial_ping_interrupted_for_test = true;
223     weak->QuitClosure().Run();
224     return;
225   }
226   // How frequently the financial ping thread should check
227   // the shutdown condition?
228   const base::TimeDelta kInterval = base::TimeDelta::FromMilliseconds(500);
229   base::MessageLoop::current()->PostDelayedTask(
230       FROM_HERE,
231       base::Bind(&ShutdownCheck, weak),
232       kInterval);
233 }
234 #endif
235 
PingServer(const char * request,std::string * response)236 bool FinancialPing::PingServer(const char* request, std::string* response) {
237   if (!response)
238     return false;
239 
240   response->clear();
241 
242 #if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET)
243   // Initialize WinInet.
244   InternetHandle inet_handle = InternetOpenA(kFinancialPingUserAgent,
245                                              INTERNET_OPEN_TYPE_PRECONFIG,
246                                              NULL, NULL, 0);
247   if (!inet_handle)
248     return false;
249 
250   // Open network connection.
251   InternetHandle connection_handle = InternetConnectA(inet_handle,
252       kFinancialServer, kFinancialPort, "", "", INTERNET_SERVICE_HTTP,
253       INTERNET_FLAG_NO_CACHE_WRITE, 0);
254   if (!connection_handle)
255     return false;
256 
257   // Prepare the HTTP request.
258   InternetHandle http_handle = HttpOpenRequestA(connection_handle,
259       "GET", request, NULL, NULL, kFinancialPingResponseObjects,
260       INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES, NULL);
261   if (!http_handle)
262     return false;
263 
264   // Timeouts are probably:
265   // INTERNET_OPTION_SEND_TIMEOUT, INTERNET_OPTION_RECEIVE_TIMEOUT
266 
267   // Send the HTTP request. Note: Fails if user is working in off-line mode.
268   if (!HttpSendRequest(http_handle, NULL, 0, NULL, 0))
269     return false;
270 
271   // Check the response status.
272   DWORD status;
273   DWORD status_size = sizeof(status);
274   if (!HttpQueryInfo(http_handle, HTTP_QUERY_STATUS_CODE |
275                      HTTP_QUERY_FLAG_NUMBER, &status, &status_size, NULL) ||
276       200 != status)
277     return false;
278 
279   // Get the response text.
280   scoped_ptr<char[]> buffer(new char[kMaxPingResponseLength]);
281   if (buffer.get() == NULL)
282     return false;
283 
284   DWORD bytes_read = 0;
285   while (InternetReadFile(http_handle, buffer.get(), kMaxPingResponseLength,
286                           &bytes_read) && bytes_read > 0) {
287     response->append(buffer.get(), bytes_read);
288     bytes_read = 0;
289   };
290 
291   return true;
292 #else
293   // Copy the pointer to stack because g_context may be set to NULL
294   // in different thread. The instance is guaranteed to exist while
295   // the method is running.
296   net::URLRequestContextGetter* context =
297       reinterpret_cast<net::URLRequestContextGetter*>(
298           base::subtle::Acquire_Load(&g_context));
299 
300   // Browser shutdown will cause the context to be reset to NULL.
301   if (!context)
302     return false;
303 
304   // Run a blocking event loop to match the win inet implementation.
305   scoped_ptr<base::MessageLoop> message_loop;
306   // Ensure that we have a MessageLoop.
307   if (!base::MessageLoop::current())
308     message_loop.reset(new base::MessageLoop);
309   base::RunLoop loop;
310   FinancialPingUrlFetcherDelegate delegate(loop.QuitClosure());
311 
312   std::string url = base::StringPrintf("http://%s:%d%s",
313                                        kFinancialServer, kFinancialPort,
314                                        request);
315 
316   scoped_ptr<net::URLFetcher> fetcher(net::URLFetcher::Create(
317       GURL(url), net::URLFetcher::GET, &delegate));
318 
319   fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE |
320                         net::LOAD_DO_NOT_SEND_AUTH_DATA |
321                         net::LOAD_DO_NOT_PROMPT_FOR_LOGIN |
322                         net::LOAD_DO_NOT_SEND_COOKIES |
323                         net::LOAD_DO_NOT_SAVE_COOKIES);
324 
325   // Ensure rlz_lib::SetURLRequestContext() has been called before sending
326   // pings.
327   fetcher->SetRequestContext(context);
328 
329   base::WeakPtrFactory<base::RunLoop> weak(&loop);
330 
331   const base::TimeDelta kTimeout = base::TimeDelta::FromMinutes(5);
332   base::MessageLoop::ScopedNestableTaskAllower allow_nested(
333       base::MessageLoop::current());
334   base::MessageLoop::current()->PostTask(
335       FROM_HERE,
336       base::Bind(&ShutdownCheck, weak.GetWeakPtr()));
337   base::MessageLoop::current()->PostTask(
338       FROM_HERE,
339       base::Bind(&net::URLFetcher::Start, base::Unretained(fetcher.get())));
340   base::MessageLoop::current()->PostDelayedTask(
341       FROM_HERE, loop.QuitClosure(), kTimeout);
342 
343   loop.Run();
344 
345   if (fetcher->GetResponseCode() != 200)
346     return false;
347 
348   return fetcher->GetResponseAsString(response);
349 #endif
350 }
351 
IsPingTime(Product product,bool no_delay)352 bool FinancialPing::IsPingTime(Product product, bool no_delay) {
353   ScopedRlzValueStoreLock lock;
354   RlzValueStore* store = lock.GetStore();
355   if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
356     return false;
357 
358   int64 last_ping = 0;
359   if (!store->ReadPingTime(product, &last_ping))
360     return true;
361 
362   uint64 now = GetSystemTimeAsInt64();
363   int64 interval = now - last_ping;
364 
365   // If interval is negative, clock was probably reset. So ping.
366   if (interval < 0)
367     return true;
368 
369   // Check if this product has any unreported events.
370   char cgi[kMaxCgiLength + 1];
371   cgi[0] = 0;
372   bool has_events = GetProductEventsAsCgi(product, cgi, arraysize(cgi));
373   if (no_delay && has_events)
374     return true;
375 
376   return interval >= (has_events ? kEventsPingInterval : kNoEventsPingInterval);
377 }
378 
379 
UpdateLastPingTime(Product product)380 bool FinancialPing::UpdateLastPingTime(Product product) {
381   ScopedRlzValueStoreLock lock;
382   RlzValueStore* store = lock.GetStore();
383   if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
384     return false;
385 
386   uint64 now = GetSystemTimeAsInt64();
387   return store->WritePingTime(product, now);
388 }
389 
390 
ClearLastPingTime(Product product)391 bool FinancialPing::ClearLastPingTime(Product product) {
392   ScopedRlzValueStoreLock lock;
393   RlzValueStore* store = lock.GetStore();
394   if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
395     return false;
396   return store->ClearPingTime(product);
397 }
398 
399 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
400 namespace test {
401 
ResetSendFinancialPingInterrupted()402 void ResetSendFinancialPingInterrupted() {
403   send_financial_ping_interrupted_for_test = false;
404 }
405 
WasSendFinancialPingInterrupted()406 bool WasSendFinancialPingInterrupted() {
407   return send_financial_ping_interrupted_for_test;
408 }
409 
410 }  // namespace test
411 #endif
412 
413 }  // namespace rlz_lib
414