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