Home | History | Annotate | Download | only in feedback
      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 #include "chrome/browser/feedback/feedback_util.h"
      6 
      7 #include <sstream>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/bind.h"
     12 #include "base/command_line.h"
     13 #include "base/file_util.h"
     14 #include "base/file_version_info.h"
     15 #include "base/memory/singleton.h"
     16 #include "base/message_loop/message_loop.h"
     17 #include "base/strings/stringprintf.h"
     18 #include "base/strings/utf_string_conversions.h"
     19 #include "base/win/windows_version.h"
     20 #include "chrome/browser/browser_process.h"
     21 #include "chrome/browser/metrics/variations/variations_http_header_provider.h"
     22 #include "chrome/browser/profiles/profile.h"
     23 #include "chrome/browser/safe_browsing/safe_browsing_util.h"
     24 #include "chrome/browser/ui/browser_list.h"
     25 #include "chrome/common/chrome_switches.h"
     26 #include "chrome/common/chrome_version_info.h"
     27 #include "chrome/common/metrics/metrics_log_manager.h"
     28 #include "content/public/browser/browser_thread.h"
     29 #include "content/public/browser/navigation_controller.h"
     30 #include "content/public/browser/web_contents.h"
     31 #include "content/public/common/content_client.h"
     32 #include "grit/generated_resources.h"
     33 #include "grit/locale_settings.h"
     34 #include "grit/theme_resources.h"
     35 #include "net/base/load_flags.h"
     36 #include "net/http/http_request_headers.h"
     37 #include "net/url_request/url_fetcher.h"
     38 #include "net/url_request/url_fetcher_delegate.h"
     39 #include "net/url_request/url_request_status.h"
     40 #include "third_party/icu/source/common/unicode/locid.h"
     41 #include "third_party/zlib/google/zip.h"
     42 #include "ui/base/l10n/l10n_util.h"
     43 #include "url/gurl.h"
     44 
     45 using content::WebContents;
     46 
     47 namespace {
     48 const base::FilePath::CharType kLogsFilename[] =
     49     FILE_PATH_LITERAL("system_logs.txt");
     50 }
     51 
     52 namespace chrome {
     53 const char kAppLauncherCategoryTag[] = "AppLauncher";
     54 }  // namespace chrome
     55 
     56 const int kFeedbackVersion = 1;
     57 
     58 const char kReportPhishingUrl[] =
     59     "http://www.google.com/safebrowsing/report_phish/";
     60 
     61 // URL to post bug reports to.
     62 const char kFeedbackPostUrl[] =
     63     "https://www.google.com/tools/feedback/chrome/__submit";
     64 
     65 const char kProtBufMimeType[] = "application/x-protobuf";
     66 const char kPngMimeType[] = "image/png";
     67 
     68 // Tags we use in product specific data
     69 const char kChromeVersionTag[] = "CHROME VERSION";
     70 const char kOsVersionTag[] = "OS VERSION";
     71 
     72 const int kHttpPostSuccessNoContent = 204;
     73 const int kHttpPostFailNoConnection = -1;
     74 const int kHttpPostFailClientError = 400;
     75 const int kHttpPostFailServerError = 500;
     76 
     77 const int64 kInitialRetryDelay = 900000;  // 15 minutes
     78 const int64 kRetryDelayIncreaseFactor = 2;
     79 const int64 kRetryDelayLimit = 14400000;  // 4 hours
     80 
     81 #if defined(OS_CHROMEOS)
     82 const size_t kFeedbackMaxLength = 4 * 1024;
     83 const size_t kFeedbackMaxLineCount = 40;
     84 
     85 const char kArbitraryMimeType[] = "application/octet-stream";
     86 const char kLogsAttachmentName[] = "system_logs.zip";
     87 
     88 const char kTimestampTag[] = "TIMESTAMP";
     89 
     90 const int kChromeOSProductId = 208;
     91 #endif
     92 
     93 const int kChromeBrowserProductId = 237;
     94 
     95 // Simple net::URLFetcherDelegate to clean up URLFetcher on completion.
     96 class FeedbackUtil::PostCleanup : public net::URLFetcherDelegate {
     97  public:
     98   PostCleanup(Profile* profile,
     99               std::string* post_body,
    100               int64 previous_delay) : profile_(profile),
    101                                       post_body_(post_body),
    102                                       previous_delay_(previous_delay) { }
    103   // Overridden from net::URLFetcherDelegate.
    104   virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
    105 
    106  protected:
    107   virtual ~PostCleanup() {}
    108 
    109  private:
    110   Profile* profile_;
    111   std::string* post_body_;
    112   int64 previous_delay_;
    113 
    114   DISALLOW_COPY_AND_ASSIGN(PostCleanup);
    115 };
    116 
    117 // Don't use the data parameter, instead use the pointer we pass into every
    118 // post cleanup object - that pointer will be deleted and deleted only on a
    119 // successful post to the feedback server.
    120 void FeedbackUtil::PostCleanup::OnURLFetchComplete(
    121     const net::URLFetcher* source) {
    122   std::stringstream error_stream;
    123   int response_code = source->GetResponseCode();
    124   if (response_code == kHttpPostSuccessNoContent) {
    125     // We've sent our report, delete the report data
    126     delete post_body_;
    127 
    128     error_stream << "Success";
    129   } else {
    130     // Uh oh, feedback failed, send it off to retry
    131     if (previous_delay_) {
    132       if (previous_delay_ < kRetryDelayLimit)
    133         previous_delay_ *= kRetryDelayIncreaseFactor;
    134     } else {
    135       previous_delay_ = kInitialRetryDelay;
    136     }
    137     FeedbackUtil::DispatchFeedback(profile_, post_body_, previous_delay_);
    138 
    139     // Process the error for debug output
    140     if (response_code == kHttpPostFailNoConnection) {
    141       error_stream << "No connection to server.";
    142     } else if ((response_code > kHttpPostFailClientError) &&
    143         (response_code < kHttpPostFailServerError)) {
    144       error_stream << "Client error: HTTP response code " << response_code;
    145     } else if (response_code > kHttpPostFailServerError) {
    146       error_stream << "Server error: HTTP response code " << response_code;
    147     } else {
    148       error_stream << "Unknown error: HTTP response code " << response_code;
    149     }
    150   }
    151 
    152   LOG(WARNING) << "FEEDBACK: Submission to feedback server (" <<
    153                source->GetURL() << ") status: " << error_stream.str();
    154 
    155   // Delete the URLFetcher.
    156   delete source;
    157   // And then delete ourselves.
    158   delete this;
    159 }
    160 
    161 // static
    162 void FeedbackUtil::SetOSVersion(std::string* os_version) {
    163 #if defined(OS_WIN)
    164   base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
    165   base::win::OSInfo::VersionNumber version_number = os_info->version_number();
    166   *os_version = base::StringPrintf("%d.%d.%d",
    167                                    version_number.major,
    168                                    version_number.minor,
    169                                    version_number.build);
    170   int service_pack = os_info->service_pack().major;
    171   if (service_pack > 0)
    172     os_version->append(base::StringPrintf("Service Pack %d", service_pack));
    173 #elif defined(OS_MACOSX)
    174   *os_version = base::SysInfo::OperatingSystemVersion();
    175 #else
    176   *os_version = "unknown";
    177 #endif
    178 }
    179 
    180 // static
    181 void FeedbackUtil::DispatchFeedback(Profile* profile,
    182                                     std::string* post_body,
    183                                     int64 delay) {
    184   DCHECK(post_body);
    185 
    186   base::MessageLoop::current()->PostDelayedTask(
    187       FROM_HERE,
    188       base::Bind(&FeedbackUtil::SendFeedback, profile, post_body, delay),
    189       base::TimeDelta::FromMilliseconds(delay));
    190 }
    191 
    192 // static
    193 void FeedbackUtil::SendFeedback(Profile* profile,
    194                                 std::string* post_body,
    195                                 int64 previous_delay) {
    196   DCHECK(post_body);
    197 
    198   GURL post_url;
    199   if (CommandLine::ForCurrentProcess()->
    200       HasSwitch(switches::kFeedbackServer))
    201     post_url = GURL(CommandLine::ForCurrentProcess()->
    202         GetSwitchValueASCII(switches::kFeedbackServer));
    203   else
    204     post_url = GURL(kFeedbackPostUrl);
    205 
    206   net::URLFetcher* fetcher = net::URLFetcher::Create(
    207       post_url, net::URLFetcher::POST,
    208       new FeedbackUtil::PostCleanup(profile, post_body, previous_delay));
    209   fetcher->SetRequestContext(profile->GetRequestContext());
    210   fetcher->SetLoadFlags(
    211       net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES);
    212 
    213   net::HttpRequestHeaders headers;
    214   chrome_variations::VariationsHttpHeaderProvider::GetInstance()->AppendHeaders(
    215       fetcher->GetOriginalURL(), profile->IsOffTheRecord(), false, &headers);
    216   fetcher->SetExtraRequestHeaders(headers.ToString());
    217 
    218   fetcher->SetUploadData(std::string(kProtBufMimeType), *post_body);
    219   fetcher->Start();
    220 }
    221 
    222 
    223 // static
    224 void FeedbackUtil::AddFeedbackData(
    225     userfeedback::ExtensionSubmit* feedback_data,
    226     const std::string& key, const std::string& value) {
    227   // Don't bother with empty keys or values
    228   if (key == "" || value == "") return;
    229   // Create log_value object and add it to the web_data object
    230   userfeedback::ProductSpecificData log_value;
    231   log_value.set_key(key);
    232   log_value.set_value(value);
    233   userfeedback::WebData* web_data = feedback_data->mutable_web_data();
    234   *(web_data->add_product_specific_data()) = log_value;
    235 }
    236 
    237 #if defined(OS_CHROMEOS)
    238 bool FeedbackUtil::ValidFeedbackSize(const std::string& content) {
    239   if (content.length() > kFeedbackMaxLength)
    240     return false;
    241   const size_t line_count = std::count(content.begin(), content.end(), '\n');
    242   if (line_count > kFeedbackMaxLineCount)
    243     return false;
    244   return true;
    245 }
    246 #endif
    247 
    248 // static
    249 void FeedbackUtil::SendReport(scoped_refptr<FeedbackData> data) {
    250   if (!data.get()) {
    251     LOG(ERROR) << "FeedbackUtil::SendReport called with NULL data!";
    252     NOTREACHED();
    253     return;
    254   }
    255 
    256   // Create google feedback protocol buffer objects
    257   userfeedback::ExtensionSubmit feedback_data;
    258   // type id set to 0, unused field but needs to be initialized to 0
    259   feedback_data.set_type_id(0);
    260 
    261   userfeedback::CommonData* common_data = feedback_data.mutable_common_data();
    262   userfeedback::WebData* web_data = feedback_data.mutable_web_data();
    263 
    264   // Set our user agent.
    265   userfeedback::Navigator* navigator = web_data->mutable_navigator();
    266   navigator->set_user_agent(content::GetUserAgent(GURL()));
    267 
    268   // Set GAIA id to 0. We're not using gaia id's for recording
    269   // use feedback - we're using the e-mail field, allows users to
    270   // submit feedback from incognito mode and specify any mail id
    271   // they wish
    272   common_data->set_gaia_id(0);
    273 
    274   // Add the user e-mail to the feedback object
    275   common_data->set_user_email(data->user_email());
    276 
    277   // Add the description to the feedback object
    278   common_data->set_description(data->description());
    279 
    280   // Add the language
    281   std::string chrome_locale = g_browser_process->GetApplicationLocale();
    282   common_data->set_source_description_language(chrome_locale);
    283 
    284   // Set the url
    285   web_data->set_url(data->page_url());
    286 
    287   // Add the Chrome version
    288   chrome::VersionInfo version_info;
    289   if (version_info.is_valid()) {
    290     std::string chrome_version = version_info.Name() + " - " +
    291         version_info.Version() +
    292         " (" + version_info.LastChange() + ")";
    293     AddFeedbackData(&feedback_data, std::string(kChromeVersionTag),
    294                     chrome_version);
    295   }
    296 
    297   // We don't need the OS version for ChromeOS since we get it in
    298   // CHROMEOS_RELEASE_VERSION from /etc/lsb-release
    299 #if !defined(OS_CHROMEOS)
    300   // Add OS version (eg, for WinXP SP2: "5.1.2600 Service Pack 2").
    301   std::string os_version;
    302   SetOSVersion(&os_version);
    303   AddFeedbackData(&feedback_data, std::string(kOsVersionTag), os_version);
    304 #endif
    305 
    306   // Include the page image if we have one.
    307   if (data->image().get() && data->image()->size()) {
    308     userfeedback::PostedScreenshot screenshot;
    309     screenshot.set_mime_type(kPngMimeType);
    310     // Set the dimensions of the screenshot
    311     userfeedback::Dimensions dimensions;
    312     gfx::Rect& screen_size = GetScreenshotSize();
    313     dimensions.set_width(static_cast<float>(screen_size.width()));
    314     dimensions.set_height(static_cast<float>(screen_size.height()));
    315     *(screenshot.mutable_dimensions()) = dimensions;
    316 
    317     int image_data_size = data->image()->size();
    318     char* image_data = reinterpret_cast<char*>(&(data->image()->front()));
    319     screenshot.set_binary_content(std::string(image_data, image_data_size));
    320 
    321     // Set the screenshot object in feedback
    322     *(feedback_data.mutable_screenshot()) = screenshot;
    323   }
    324 
    325 #if defined(OS_CHROMEOS)
    326   if (data->sys_info()) {
    327     // Add the product specific data
    328     for (chromeos::SystemLogsResponse::const_iterator i =
    329         data->sys_info()->begin(); i != data->sys_info()->end(); ++i) {
    330       if (ValidFeedbackSize(i->second)) {
    331         AddFeedbackData(&feedback_data, i->first, i->second);
    332       }
    333     }
    334 
    335     if (data->compressed_logs() && data->compressed_logs()->size()) {
    336       userfeedback::ProductSpecificBinaryData attachment;
    337       attachment.set_mime_type(kArbitraryMimeType);
    338       attachment.set_name(kLogsAttachmentName);
    339       attachment.set_data(*(data->compressed_logs()));
    340       *(feedback_data.add_product_specific_binary_data()) = attachment;
    341     }
    342   }
    343 
    344   if (data->timestamp() != "")
    345     AddFeedbackData(&feedback_data, std::string(kTimestampTag),
    346                     data->timestamp());
    347 
    348   if (data->attached_filename() != "" &&
    349       data->attached_filedata() &&
    350       data->attached_filedata()->size()) {
    351     userfeedback::ProductSpecificBinaryData attached_file;
    352     attached_file.set_mime_type(kArbitraryMimeType);
    353     attached_file.set_name(data->attached_filename());
    354     attached_file.set_data(*data->attached_filedata());
    355     *(feedback_data.add_product_specific_binary_data()) = attached_file;
    356   }
    357 #endif
    358 
    359   // Set our category tag if we have one
    360   if (data->category_tag().size())
    361     feedback_data.set_bucket(data->category_tag());
    362 
    363   // Set our Chrome specific data
    364   userfeedback::ChromeData chrome_data;
    365   chrome_data.set_chrome_platform(
    366 #if defined(OS_CHROMEOS)
    367       userfeedback::ChromeData_ChromePlatform_CHROME_OS);
    368   userfeedback::ChromeOsData chrome_os_data;
    369   chrome_os_data.set_category(
    370       userfeedback::ChromeOsData_ChromeOsCategory_OTHER);
    371   *(chrome_data.mutable_chrome_os_data()) = chrome_os_data;
    372   feedback_data.set_product_id(kChromeOSProductId);
    373 #else
    374       userfeedback::ChromeData_ChromePlatform_CHROME_BROWSER);
    375   userfeedback::ChromeBrowserData chrome_browser_data;
    376   chrome_browser_data.set_category(
    377       userfeedback::ChromeBrowserData_ChromeBrowserCategory_OTHER);
    378   *(chrome_data.mutable_chrome_browser_data()) = chrome_browser_data;
    379   feedback_data.set_product_id(kChromeBrowserProductId);
    380 #endif
    381 
    382   *(feedback_data.mutable_chrome_data()) = chrome_data;
    383 
    384   // Serialize our report to a string pointer we can pass around
    385   std::string* post_body = new std::string;
    386   feedback_data.SerializeToString(post_body);
    387 
    388   // We have the body of our POST, so send it off to the server with 0 delay
    389   DispatchFeedback(data->profile(), post_body, 0);
    390 }
    391 
    392 #if defined(FULL_SAFE_BROWSING)
    393 // static
    394 void FeedbackUtil::ReportPhishing(WebContents* current_tab,
    395                                   const std::string& phishing_url) {
    396   current_tab->GetController().LoadURL(
    397       safe_browsing_util::GeneratePhishingReportUrl(
    398           kReportPhishingUrl, phishing_url,
    399           false /* not client-side detection */),
    400       content::Referrer(),
    401       content::PAGE_TRANSITION_LINK,
    402       std::string());
    403 }
    404 #endif
    405 
    406 static std::vector<unsigned char>* screenshot_png = NULL;
    407 static gfx::Rect* screenshot_size = NULL;
    408 
    409 // static
    410 std::vector<unsigned char>* FeedbackUtil::GetScreenshotPng() {
    411   if (screenshot_png == NULL)
    412     screenshot_png = new std::vector<unsigned char>;
    413   return screenshot_png;
    414 }
    415 
    416 // static
    417 void FeedbackUtil::ClearScreenshotPng() {
    418   if (screenshot_png)
    419     screenshot_png->clear();
    420 }
    421 
    422 // static
    423 gfx::Rect& FeedbackUtil::GetScreenshotSize() {
    424   if (screenshot_size == NULL)
    425     screenshot_size = new gfx::Rect();
    426   return *screenshot_size;
    427 }
    428 
    429 // static
    430 void FeedbackUtil::SetScreenshotSize(const gfx::Rect& rect) {
    431   gfx::Rect& screen_size = GetScreenshotSize();
    432   screen_size = rect;
    433 }
    434 
    435 // static
    436 bool FeedbackUtil::ZipString(const std::string& logs,
    437                              std::string* compressed_logs) {
    438   base::FilePath temp_path;
    439   base::FilePath zip_file;
    440 
    441   // Create a temporary directory, put the logs into a file in it. Create
    442   // another temporary file to receive the zip file in.
    443   if (!file_util::CreateNewTempDirectory(FILE_PATH_LITERAL(""), &temp_path))
    444     return false;
    445   if (file_util::WriteFile(temp_path.Append(kLogsFilename),
    446                            logs.c_str(), logs.size()) == -1)
    447     return false;
    448   if (!file_util::CreateTemporaryFile(&zip_file))
    449     return false;
    450 
    451   if (!zip::Zip(temp_path, zip_file, false))
    452     return false;
    453 
    454   if (!file_util::ReadFileToString(zip_file, compressed_logs))
    455     return false;
    456 
    457   return true;
    458 }
    459