Home | History | Annotate | Download | only in lib
      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:
     36   InternetHandle(HINTERNET handle) { handle_ = handle; }
     37   ~InternetHandle() { if (handle_) InternetCloseHandle(handle_); }
     38   operator HINTERNET() const { return handle_; }
     39   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.
     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 
     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 
    189 bool FinancialPing::SetURLRequestContext(
    190     net::URLRequestContextGetter* context) {
    191   base::subtle::NoBarrier_Store(
    192       &g_context, reinterpret_cast<AtomicWord>(context));
    193   return true;
    194 }
    195 
    196 namespace {
    197 
    198 class FinancialPingUrlFetcherDelegate : public net::URLFetcherDelegate {
    199  public:
    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 
    209 void FinancialPingUrlFetcherDelegate::OnURLFetchComplete(
    210     const net::URLFetcher* source) {
    211   callback_.Run();
    212 }
    213 
    214 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
    215 bool send_financial_ping_interrupted_for_test = false;
    216 #endif
    217 
    218 }  // namespace
    219 
    220 #endif
    221 
    222 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
    223 void ShutdownCheck(base::WeakPtr<base::RunLoop> weak) {
    224   if (!weak.get())
    225     return;
    226   if (!base::subtle::NoBarrier_Load(&g_context)) {
    227     send_financial_ping_interrupted_for_test = true;
    228     weak->QuitClosure().Run();
    229     return;
    230   }
    231   // How frequently the financial ping thread should check
    232   // the shutdown condition?
    233   const base::TimeDelta kInterval = base::TimeDelta::FromMilliseconds(500);
    234   base::MessageLoop::current()->PostDelayedTask(
    235       FROM_HERE,
    236       base::Bind(&ShutdownCheck, weak),
    237       kInterval);
    238 }
    239 #endif
    240 
    241 bool FinancialPing::PingServer(const char* request, std::string* response) {
    242   if (!response)
    243     return false;
    244 
    245   response->clear();
    246 
    247 #if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET)
    248   // Initialize WinInet.
    249   InternetHandle inet_handle = InternetOpenA(kFinancialPingUserAgent,
    250                                              INTERNET_OPEN_TYPE_PRECONFIG,
    251                                              NULL, NULL, 0);
    252   if (!inet_handle)
    253     return false;
    254 
    255   // Open network connection.
    256   InternetHandle connection_handle = InternetConnectA(inet_handle,
    257       kFinancialServer, kFinancialPort, "", "", INTERNET_SERVICE_HTTP,
    258       INTERNET_FLAG_NO_CACHE_WRITE, 0);
    259   if (!connection_handle)
    260     return false;
    261 
    262   // Prepare the HTTP request.
    263   InternetHandle http_handle = HttpOpenRequestA(connection_handle,
    264       "GET", request, NULL, NULL, kFinancialPingResponseObjects,
    265       INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES, NULL);
    266   if (!http_handle)
    267     return false;
    268 
    269   // Timeouts are probably:
    270   // INTERNET_OPTION_SEND_TIMEOUT, INTERNET_OPTION_RECEIVE_TIMEOUT
    271 
    272   // Send the HTTP request. Note: Fails if user is working in off-line mode.
    273   if (!HttpSendRequest(http_handle, NULL, 0, NULL, 0))
    274     return false;
    275 
    276   // Check the response status.
    277   DWORD status;
    278   DWORD status_size = sizeof(status);
    279   if (!HttpQueryInfo(http_handle, HTTP_QUERY_STATUS_CODE |
    280                      HTTP_QUERY_FLAG_NUMBER, &status, &status_size, NULL) ||
    281       200 != status)
    282     return false;
    283 
    284   // Get the response text.
    285   scoped_ptr<char[]> buffer(new char[kMaxPingResponseLength]);
    286   if (buffer.get() == NULL)
    287     return false;
    288 
    289   DWORD bytes_read = 0;
    290   while (InternetReadFile(http_handle, buffer.get(), kMaxPingResponseLength,
    291                           &bytes_read) && bytes_read > 0) {
    292     response->append(buffer.get(), bytes_read);
    293     bytes_read = 0;
    294   };
    295 
    296   return true;
    297 #else
    298   // Copy the pointer to stack because g_context may be set to NULL
    299   // in different thread. The instance is guaranteed to exist while
    300   // the method is running.
    301   net::URLRequestContextGetter* context =
    302       reinterpret_cast<net::URLRequestContextGetter*>(
    303           base::subtle::NoBarrier_Load(&g_context));
    304 
    305   // Browser shutdown will cause the context to be reset to NULL.
    306   if (!context)
    307     return false;
    308 
    309   // Run a blocking event loop to match the win inet implementation.
    310   scoped_ptr<base::MessageLoop> message_loop;
    311   // Ensure that we have a MessageLoop.
    312   if (!base::MessageLoop::current())
    313     message_loop.reset(new base::MessageLoop);
    314   base::RunLoop loop;
    315   FinancialPingUrlFetcherDelegate delegate(loop.QuitClosure());
    316 
    317   std::string url = base::StringPrintf("http://%s:%d%s",
    318                                        kFinancialServer, kFinancialPort,
    319                                        request);
    320 
    321   scoped_ptr<net::URLFetcher> fetcher(net::URLFetcher::Create(
    322       GURL(url), net::URLFetcher::GET, &delegate));
    323 
    324   fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE |
    325                         net::LOAD_DO_NOT_SEND_AUTH_DATA |
    326                         net::LOAD_DO_NOT_PROMPT_FOR_LOGIN |
    327                         net::LOAD_DO_NOT_SEND_COOKIES |
    328                         net::LOAD_DO_NOT_SAVE_COOKIES);
    329 
    330   // Ensure rlz_lib::SetURLRequestContext() has been called before sending
    331   // pings.
    332   fetcher->SetRequestContext(context);
    333 
    334   base::WeakPtrFactory<base::RunLoop> weak(&loop);
    335 
    336   const base::TimeDelta kTimeout = base::TimeDelta::FromMinutes(5);
    337   base::MessageLoop::ScopedNestableTaskAllower allow_nested(
    338       base::MessageLoop::current());
    339   base::MessageLoop::current()->PostTask(
    340       FROM_HERE,
    341       base::Bind(&ShutdownCheck, weak.GetWeakPtr()));
    342   base::MessageLoop::current()->PostTask(
    343       FROM_HERE,
    344       base::Bind(&net::URLFetcher::Start, base::Unretained(fetcher.get())));
    345   base::MessageLoop::current()->PostDelayedTask(
    346       FROM_HERE, loop.QuitClosure(), kTimeout);
    347 
    348   loop.Run();
    349 
    350   if (fetcher->GetResponseCode() != 200)
    351     return false;
    352 
    353   return fetcher->GetResponseAsString(response);
    354 #endif
    355 }
    356 
    357 bool FinancialPing::IsPingTime(Product product, bool no_delay) {
    358   ScopedRlzValueStoreLock lock;
    359   RlzValueStore* store = lock.GetStore();
    360   if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
    361     return false;
    362 
    363   int64 last_ping = 0;
    364   if (!store->ReadPingTime(product, &last_ping))
    365     return true;
    366 
    367   uint64 now = GetSystemTimeAsInt64();
    368   int64 interval = now - last_ping;
    369 
    370   // If interval is negative, clock was probably reset. So ping.
    371   if (interval < 0)
    372     return true;
    373 
    374   // Check if this product has any unreported events.
    375   char cgi[kMaxCgiLength + 1];
    376   cgi[0] = 0;
    377   bool has_events = GetProductEventsAsCgi(product, cgi, arraysize(cgi));
    378   if (no_delay && has_events)
    379     return true;
    380 
    381   return interval >= (has_events ? kEventsPingInterval : kNoEventsPingInterval);
    382 }
    383 
    384 
    385 bool FinancialPing::UpdateLastPingTime(Product product) {
    386   ScopedRlzValueStoreLock lock;
    387   RlzValueStore* store = lock.GetStore();
    388   if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
    389     return false;
    390 
    391   uint64 now = GetSystemTimeAsInt64();
    392   return store->WritePingTime(product, now);
    393 }
    394 
    395 
    396 bool FinancialPing::ClearLastPingTime(Product product) {
    397   ScopedRlzValueStoreLock lock;
    398   RlzValueStore* store = lock.GetStore();
    399   if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
    400     return false;
    401   return store->ClearPingTime(product);
    402 }
    403 
    404 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
    405 namespace test {
    406 
    407 void ResetSendFinancialPingInterrupted() {
    408   send_financial_ping_interrupted_for_test = false;
    409 }
    410 
    411 bool WasSendFinancialPingInterrupted() {
    412   return send_financial_ping_interrupted_for_test;
    413 }
    414 
    415 }  // namespace test
    416 #endif
    417 
    418 }  // namespace rlz_lib
    419