Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 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/bug_report_util.h"
      6 
      7 #include <sstream>
      8 #include <string>
      9 
     10 #include "base/command_line.h"
     11 #include "base/file_util.h"
     12 #include "base/file_version_info.h"
     13 #include "base/memory/singleton.h"
     14 #include "base/string_util.h"
     15 #include "base/utf_string_conversions.h"
     16 #include "base/win/windows_version.h"
     17 #include "chrome/browser/browser_process_impl.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/browser/safe_browsing/safe_browsing_util.h"
     20 #include "chrome/browser/ui/browser_list.h"
     21 #include "chrome/common/chrome_switches.h"
     22 #include "chrome/common/chrome_version_info.h"
     23 #include "chrome/common/net/url_fetcher.h"
     24 #include "content/browser/tab_contents/tab_contents.h"
     25 #include "googleurl/src/gurl.h"
     26 #include "grit/generated_resources.h"
     27 #include "grit/locale_settings.h"
     28 #include "grit/theme_resources.h"
     29 #include "net/url_request/url_request_status.h"
     30 #include "ui/base/l10n/l10n_util.h"
     31 #include "unicode/locid.h"
     32 
     33 #if defined(OS_CHROMEOS)
     34 #include "chrome/browser/chromeos/notifications/system_notification.h"
     35 #endif
     36 
     37 namespace {
     38 
     39 const int kBugReportVersion = 1;
     40 
     41 const char kReportPhishingUrl[] =
     42     "http://www.google.com/safebrowsing/report_phish/";
     43 
     44 // URL to post bug reports to.
     45 static char const kBugReportPostUrl[] =
     46     "https://www.google.com/tools/feedback/chrome/__submit";
     47 
     48 static char const kProtBufMimeType[] = "application/x-protobuf";
     49 static char const kPngMimeType[] = "image/png";
     50 
     51 // Tags we use in product specific data
     52 static char const kPageTitleTag[] = "PAGE TITLE";
     53 static char const kProblemTypeIdTag[] = "PROBLEM TYPE ID";
     54 static char const kProblemTypeTag[] = "PROBLEM TYPE";
     55 static char const kChromeVersionTag[] = "CHROME VERSION";
     56 static char const kOsVersionTag[] = "OS VERSION";
     57 
     58 static char const kNotificationId[] = "feedback.chromeos";
     59 
     60 static int const kHttpPostSuccessNoContent = 204;
     61 static int const kHttpPostFailNoConnection = -1;
     62 static int const kHttpPostFailClientError = 400;
     63 static int const kHttpPostFailServerError = 500;
     64 
     65 #if defined(OS_CHROMEOS)
     66 static char const kBZip2MimeType[] = "application/x-bzip2";
     67 static char const kLogsAttachmentName[] = "system_logs.bz2";
     68 // Maximum number of lines in system info log chunk to be still included
     69 // in product specific data.
     70 const size_t kMaxLineCount       = 10;
     71 // Maximum number of bytes in system info log chunk to be still included
     72 // in product specific data.
     73 const size_t kMaxSystemLogLength = 1024;
     74 #endif
     75 
     76 const int64 kInitialRetryDelay = 900000; // 15 minutes
     77 const int64 kRetryDelayIncreaseFactor = 2;
     78 const int64 kRetryDelayLimit = 14400000; // 4 hours
     79 
     80 
     81 }  // namespace
     82 
     83 
     84 // Simple URLFetcher::Delegate to clean up URLFetcher on completion.
     85 class BugReportUtil::PostCleanup : public URLFetcher::Delegate {
     86  public:
     87   PostCleanup(Profile* profile, std::string* post_body,
     88               int64 previous_delay) : profile_(profile),
     89                                       post_body_(post_body),
     90                                       previous_delay_(previous_delay) { }
     91   // Overridden from URLFetcher::Delegate.
     92   virtual void OnURLFetchComplete(const URLFetcher* source,
     93                                   const GURL& url,
     94                                   const net::URLRequestStatus& status,
     95                                   int response_code,
     96                                   const ResponseCookies& cookies,
     97                                   const std::string& data);
     98 
     99  protected:
    100   virtual ~PostCleanup() {}
    101 
    102  private:
    103   Profile* profile_;
    104   std::string* post_body_;
    105   int64 previous_delay_;
    106 
    107   DISALLOW_COPY_AND_ASSIGN(PostCleanup);
    108 };
    109 
    110 // Don't use the data parameter, instead use the pointer we pass into every
    111 // post cleanup object - that pointer will be deleted and deleted only on a
    112 // successful post to the feedback server.
    113 void BugReportUtil::PostCleanup::OnURLFetchComplete(
    114     const URLFetcher* source,
    115     const GURL& url,
    116     const net::URLRequestStatus& status,
    117     int response_code,
    118     const ResponseCookies& cookies,
    119     const std::string& data) {
    120 
    121   std::stringstream error_stream;
    122   if (response_code == kHttpPostSuccessNoContent) {
    123     // We've sent our report, delete the report data
    124     delete post_body_;
    125 
    126     error_stream << "Success";
    127   } else {
    128     // Uh oh, feedback failed, send it off to retry
    129     if (previous_delay_) {
    130       if (previous_delay_ < kRetryDelayLimit)
    131         previous_delay_ *= kRetryDelayIncreaseFactor;
    132     } else {
    133       previous_delay_ = kInitialRetryDelay;
    134     }
    135     BugReportUtil::DispatchFeedback(profile_, post_body_, previous_delay_);
    136 
    137     // Process the error for debug output
    138     if (response_code == kHttpPostFailNoConnection) {
    139       error_stream << "No connection to server.";
    140     } else if ((response_code > kHttpPostFailClientError) &&
    141         (response_code < kHttpPostFailServerError)) {
    142       error_stream << "Client error: HTTP response code " << response_code;
    143     } else if (response_code > kHttpPostFailServerError) {
    144       error_stream << "Server error: HTTP response code " << response_code;
    145     } else {
    146       error_stream << "Unknown error: HTTP response code " << response_code;
    147     }
    148   }
    149 
    150   LOG(WARNING) << "FEEDBACK: Submission to feedback server (" << url
    151                << ") status: " << error_stream.str();
    152 
    153   // Delete the URLFetcher.
    154   delete source;
    155   // And then delete ourselves.
    156   delete this;
    157 }
    158 
    159 // static
    160 void BugReportUtil::SetOSVersion(std::string* os_version) {
    161 #if defined(OS_WIN)
    162   base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
    163   base::win::OSInfo::VersionNumber version_number = os_info->version_number();
    164   *os_version = StringPrintf("%d.%d.%d", version_number.major,
    165                              version_number.minor, version_number.build);
    166   int service_pack = os_info->service_pack().major;
    167   if (service_pack > 0)
    168     os_version->append(StringPrintf("Service Pack %d", service_pack));
    169 #elif defined(OS_MACOSX)
    170   int32 major;
    171   int32 minor;
    172   int32 bugFix;
    173   base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugFix);
    174   *os_version = StringPrintf("%d.%d.%d", major, minor, bugFix);
    175 #else
    176   *os_version = "unknown";
    177 #endif
    178 }
    179 
    180 // static
    181 std::string BugReportUtil::feedback_server_("");
    182 
    183 // static
    184 void BugReportUtil::SetFeedbackServer(const std::string& server) {
    185   feedback_server_ = server;
    186 }
    187 
    188 // static
    189 void BugReportUtil::DispatchFeedback(Profile* profile,
    190                                      std::string* post_body,
    191                                      int64 delay) {
    192   DCHECK(post_body);
    193 
    194   MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableFunction(
    195       &BugReportUtil::SendFeedback, profile, post_body, delay), delay);
    196 }
    197 
    198 // static
    199 void BugReportUtil::SendFeedback(Profile* profile,
    200                                  std::string* post_body,
    201                                  int64 previous_delay) {
    202   DCHECK(post_body);
    203 
    204   GURL post_url;
    205   if (CommandLine::ForCurrentProcess()->
    206       HasSwitch(switches::kFeedbackServer))
    207     post_url = GURL(CommandLine::ForCurrentProcess()->
    208         GetSwitchValueASCII(switches::kFeedbackServer));
    209   else
    210     post_url = GURL(kBugReportPostUrl);
    211 
    212   URLFetcher* fetcher = new URLFetcher(post_url, URLFetcher::POST,
    213                             new BugReportUtil::PostCleanup(profile,
    214                                                            post_body,
    215                                                            previous_delay));
    216   fetcher->set_request_context(profile->GetRequestContext());
    217 
    218   fetcher->set_upload_data(std::string(kProtBufMimeType), *post_body);
    219   fetcher->Start();
    220 }
    221 
    222 
    223 // static
    224 void BugReportUtil::AddFeedbackData(
    225     userfeedback::ExternalExtensionSubmit* 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 BugReportUtil::ValidFeedbackSize(const std::string& content) {
    239   if (content.length() > kMaxSystemLogLength)
    240     return false;
    241   size_t line_count = 0;
    242   const char* text = content.c_str();
    243   for (size_t i = 0; i < content.length(); i++) {
    244     if (*(text + i) == '\n') {
    245       line_count++;
    246       if (line_count > kMaxLineCount)
    247         return false;
    248     }
    249   }
    250   return true;
    251 }
    252 #endif
    253 
    254 // static
    255 void BugReportUtil::SendReport(Profile* profile,
    256     int problem_type,
    257     const std::string& page_url_text,
    258     const std::string& description,
    259     const char* png_data,
    260     int png_data_length,
    261     int png_width,
    262 #if defined(OS_CHROMEOS)
    263     int png_height,
    264     const std::string& user_email_text,
    265     const char* zipped_logs_data,
    266     int zipped_logs_length,
    267     const chromeos::LogDictionaryType* const sys_info) {
    268 #else
    269     int png_height) {
    270 #endif
    271   // Create google feedback protocol buffer objects
    272   userfeedback::ExternalExtensionSubmit feedback_data;
    273   // type id set to 0, unused field but needs to be initialized to 0
    274   feedback_data.set_type_id(0);
    275 
    276   userfeedback::CommonData* common_data = feedback_data.mutable_common_data();
    277   userfeedback::WebData* web_data = feedback_data.mutable_web_data();
    278 
    279   // Set GAIA id to 0. We're not using gaia id's for recording
    280   // use feedback - we're using the e-mail field, allows users to
    281   // submit feedback from incognito mode and specify any mail id
    282   // they wish
    283   common_data->set_gaia_id(0);
    284 
    285 #if defined(OS_CHROMEOS)
    286   // Add the user e-mail to the feedback object
    287   common_data->set_user_email(user_email_text);
    288 #endif
    289 
    290   // Add the description to the feedback object
    291   common_data->set_description(description);
    292 
    293   // Add the language
    294   std::string chrome_locale = g_browser_process->GetApplicationLocale();
    295   common_data->set_source_description_language(chrome_locale);
    296 
    297   // Set the url
    298   web_data->set_url(page_url_text);
    299 
    300   // Add the Chrome version
    301   chrome::VersionInfo version_info;
    302   if (version_info.is_valid()) {
    303     std::string chrome_version = version_info.Name() + " - " +
    304         version_info.Version() +
    305         " (" + version_info.LastChange() + ")";
    306     AddFeedbackData(&feedback_data, std::string(kChromeVersionTag),
    307                     chrome_version);
    308   }
    309 
    310   // Add OS version (eg, for WinXP SP2: "5.1.2600 Service Pack 2").
    311   std::string os_version = "";
    312   SetOSVersion(&os_version);
    313   AddFeedbackData(&feedback_data, std::string(kOsVersionTag), os_version);
    314 
    315   // Include the page image if we have one.
    316   if (png_data) {
    317     userfeedback::PostedScreenshot screenshot;
    318     screenshot.set_mime_type(kPngMimeType);
    319     // Set the dimensions of the screenshot
    320     userfeedback::Dimensions dimensions;
    321     dimensions.set_width(static_cast<float>(png_width));
    322     dimensions.set_height(static_cast<float>(png_height));
    323     *(screenshot.mutable_dimensions()) = dimensions;
    324     screenshot.set_binary_content(std::string(png_data, png_data_length));
    325 
    326     // Set the screenshot object in feedback
    327     *(feedback_data.mutable_screenshot()) = screenshot;
    328   }
    329 
    330 #if defined(OS_CHROMEOS)
    331   if (sys_info) {
    332     // Add the product specific data
    333     for (chromeos::LogDictionaryType::const_iterator i = sys_info->begin();
    334          i != sys_info->end(); ++i)
    335       if (!CommandLine::ForCurrentProcess()->HasSwitch(
    336           switches::kCompressSystemFeedback) || ValidFeedbackSize(i->second)) {
    337         AddFeedbackData(&feedback_data, i->first, i->second);
    338       }
    339 
    340     // If we have zipped logs, add them here
    341     if (zipped_logs_data && CommandLine::ForCurrentProcess()->HasSwitch(
    342         switches::kCompressSystemFeedback)) {
    343       userfeedback::ProductSpecificBinaryData attachment;
    344       attachment.set_mime_type(kBZip2MimeType);
    345       attachment.set_name(kLogsAttachmentName);
    346       attachment.set_data(std::string(zipped_logs_data, zipped_logs_length));
    347       *(feedback_data.add_product_specific_binary_data()) = attachment;
    348     }
    349   }
    350 #endif
    351 
    352   // Set our Chrome specific data
    353   userfeedback::ChromeData chrome_data;
    354 #if defined(OS_CHROMEOS)
    355   chrome_data.set_chrome_platform(
    356       userfeedback::ChromeData_ChromePlatform_CHROME_OS);
    357   userfeedback::ChromeOsData chrome_os_data;
    358   chrome_os_data.set_category(
    359       (userfeedback::ChromeOsData_ChromeOsCategory) problem_type);
    360   *(chrome_data.mutable_chrome_os_data()) = chrome_os_data;
    361 #else
    362   chrome_data.set_chrome_platform(
    363       userfeedback::ChromeData_ChromePlatform_CHROME_BROWSER);
    364   userfeedback::ChromeBrowserData chrome_browser_data;
    365   chrome_browser_data.set_category(
    366       (userfeedback::ChromeBrowserData_ChromeBrowserCategory) problem_type);
    367   *(chrome_data.mutable_chrome_browser_data()) = chrome_browser_data;
    368 #endif
    369 
    370   *(feedback_data.mutable_chrome_data()) = chrome_data;
    371 
    372   // Serialize our report to a string pointer we can pass around
    373   std::string* post_body = new std::string;
    374   feedback_data.SerializeToString(post_body);
    375 
    376   // We have the body of our POST, so send it off to the server with 0 delay
    377   DispatchFeedback(profile, post_body, 0);
    378 }
    379 
    380 // static
    381 void BugReportUtil::ReportPhishing(TabContents* currentTab,
    382                                    const std::string& phishing_url) {
    383   currentTab->controller().LoadURL(
    384       safe_browsing_util::GeneratePhishingReportUrl(
    385           kReportPhishingUrl, phishing_url),
    386       GURL(),
    387       PageTransition::LINK);
    388 }
    389