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/string_number_conversions.h"
     18 #include "base/strings/stringprintf.h"
     19 #include "base/strings/utf_string_conversions.h"
     20 #include "base/win/windows_version.h"
     21 #include "chrome/browser/browser_process.h"
     22 #include "chrome/browser/extensions/api/feedback_private/feedback_private_api.h"
     23 #include "chrome/browser/feedback/feedback_data.h"
     24 #include "chrome/browser/metrics/variations/variations_http_header_provider.h"
     25 #include "chrome/browser/profiles/profile.h"
     26 #include "chrome/browser/profiles/profile_manager.h"
     27 #include "chrome/browser/safe_browsing/safe_browsing_util.h"
     28 #include "chrome/browser/ui/browser_finder.h"
     29 #include "chrome/browser/ui/browser_list.h"
     30 #include "chrome/browser/ui/browser_window.h"
     31 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     32 #include "chrome/common/chrome_switches.h"
     33 #include "chrome/common/chrome_version_info.h"
     34 #include "chrome/common/metrics/metrics_log_manager.h"
     35 #include "content/public/browser/browser_thread.h"
     36 #include "content/public/browser/navigation_controller.h"
     37 #include "content/public/browser/web_contents.h"
     38 #include "content/public/common/content_client.h"
     39 #include "grit/generated_resources.h"
     40 #include "grit/locale_settings.h"
     41 #include "grit/theme_resources.h"
     42 #include "net/base/load_flags.h"
     43 #include "net/http/http_request_headers.h"
     44 #include "net/url_request/url_fetcher.h"
     45 #include "net/url_request/url_fetcher_delegate.h"
     46 #include "net/url_request/url_request_status.h"
     47 #include "third_party/icu/source/common/unicode/locid.h"
     48 #include "third_party/zlib/google/zip.h"
     49 #include "ui/base/l10n/l10n_util.h"
     50 #include "url/gurl.h"
     51 
     52 #if defined(OS_CHROMEOS)
     53 #include "ash/shell.h"
     54 #include "ui/aura/root_window.h"
     55 #include "ui/aura/window.h"
     56 #endif
     57 
     58 namespace {
     59 
     60 void DispatchFeedback(Profile* profile, std::string* post_body, int64 delay);
     61 
     62 GURL GetTargetTabUrl(int session_id, int index) {
     63   Browser* browser = chrome::FindBrowserWithID(session_id);
     64   // Sanity checks.
     65   if (!browser || index >= browser->tab_strip_model()->count())
     66     return GURL();
     67 
     68   if (index >= 0) {
     69     content::WebContents* target_tab =
     70         browser->tab_strip_model()->GetWebContentsAt(index);
     71     if (target_tab)
     72       return target_tab->GetURL();
     73   }
     74 
     75   return GURL();
     76 }
     77 
     78 // URL to post bug reports to.
     79 const char kFeedbackPostUrl[] =
     80     "https://www.google.com/tools/feedback/chrome/__submit";
     81 
     82 const char kProtBufMimeType[] = "application/x-protobuf";
     83 const char kPngMimeType[] = "image/png";
     84 
     85 const int kHttpPostSuccessNoContent = 204;
     86 const int kHttpPostFailNoConnection = -1;
     87 const int kHttpPostFailClientError = 400;
     88 const int kHttpPostFailServerError = 500;
     89 
     90 const int64 kInitialRetryDelay = 900000;  // 15 minutes
     91 const int64 kRetryDelayIncreaseFactor = 2;
     92 const int64 kRetryDelayLimit = 14400000;  // 4 hours
     93 
     94 const char kArbitraryMimeType[] = "application/octet-stream";
     95 const char kHistogramsAttachmentName[] = "histograms.zip";
     96 const char kLogsAttachmentName[] = "system_logs.zip";
     97 
     98 #if defined(OS_CHROMEOS)
     99 const int kChromeOSProductId = 208;
    100 #else
    101 const int kChromeBrowserProductId = 237;
    102 #endif
    103 
    104 // Simple net::URLFetcherDelegate to clean up URLFetcher on completion.
    105 class PostCleanup : public net::URLFetcherDelegate {
    106  public:
    107   PostCleanup(Profile* profile,
    108               std::string* post_body,
    109               int64 previous_delay) : profile_(profile),
    110                                       post_body_(post_body),
    111                                       previous_delay_(previous_delay) { }
    112   // Overridden from net::URLFetcherDelegate.
    113   virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
    114 
    115  protected:
    116   virtual ~PostCleanup() {}
    117 
    118  private:
    119   Profile* profile_;
    120   std::string* post_body_;
    121   int64 previous_delay_;
    122 
    123   DISALLOW_COPY_AND_ASSIGN(PostCleanup);
    124 };
    125 
    126 // Don't use the data parameter, instead use the pointer we pass into every
    127 // post cleanup object - that pointer will be deleted and deleted only on a
    128 // successful post to the feedback server.
    129 void PostCleanup::OnURLFetchComplete(
    130     const net::URLFetcher* source) {
    131   std::stringstream error_stream;
    132   int response_code = source->GetResponseCode();
    133   if (response_code == kHttpPostSuccessNoContent) {
    134     // We've sent our report, delete the report data
    135     delete post_body_;
    136 
    137     error_stream << "Success";
    138   } else {
    139     // Uh oh, feedback failed, send it off to retry
    140     if (previous_delay_) {
    141       if (previous_delay_ < kRetryDelayLimit)
    142         previous_delay_ *= kRetryDelayIncreaseFactor;
    143     } else {
    144       previous_delay_ = kInitialRetryDelay;
    145     }
    146     DispatchFeedback(profile_, post_body_, previous_delay_);
    147 
    148     // Process the error for debug output
    149     if (response_code == kHttpPostFailNoConnection) {
    150       error_stream << "No connection to server.";
    151     } else if ((response_code > kHttpPostFailClientError) &&
    152         (response_code < kHttpPostFailServerError)) {
    153       error_stream << "Client error: HTTP response code " << response_code;
    154     } else if (response_code > kHttpPostFailServerError) {
    155       error_stream << "Server error: HTTP response code " << response_code;
    156     } else {
    157       error_stream << "Unknown error: HTTP response code " << response_code;
    158     }
    159   }
    160 
    161   LOG(WARNING) << "FEEDBACK: Submission to feedback server (" <<
    162                source->GetURL() << ") status: " << error_stream.str();
    163 
    164   // Delete the URLFetcher.
    165   delete source;
    166   // And then delete ourselves.
    167   delete this;
    168 }
    169 
    170 void SendFeedback(Profile* profile,
    171                   std::string* post_body,
    172                   int64 previous_delay) {
    173   DCHECK(post_body);
    174 
    175   GURL post_url;
    176   if (CommandLine::ForCurrentProcess()->
    177       HasSwitch(switches::kFeedbackServer))
    178     post_url = GURL(CommandLine::ForCurrentProcess()->
    179         GetSwitchValueASCII(switches::kFeedbackServer));
    180   else
    181     post_url = GURL(kFeedbackPostUrl);
    182 
    183   net::URLFetcher* fetcher = net::URLFetcher::Create(
    184       post_url, net::URLFetcher::POST,
    185       new PostCleanup(profile, post_body, previous_delay));
    186   fetcher->SetRequestContext(profile->GetRequestContext());
    187   fetcher->SetLoadFlags(
    188       net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES);
    189 
    190   net::HttpRequestHeaders headers;
    191   chrome_variations::VariationsHttpHeaderProvider::GetInstance()->AppendHeaders(
    192       fetcher->GetOriginalURL(), profile->IsOffTheRecord(), false, &headers);
    193   fetcher->SetExtraRequestHeaders(headers.ToString());
    194 
    195   fetcher->SetUploadData(std::string(kProtBufMimeType), *post_body);
    196   fetcher->Start();
    197 }
    198 
    199 void DispatchFeedback(Profile* profile, std::string* post_body, int64 delay) {
    200   DCHECK(post_body);
    201 
    202   base::MessageLoop::current()->PostDelayedTask(
    203       FROM_HERE,
    204       base::Bind(&SendFeedback, profile, post_body, delay),
    205       base::TimeDelta::FromMilliseconds(delay));
    206 }
    207 
    208 
    209 void AddFeedbackData(userfeedback::ExtensionSubmit* feedback_data,
    210                      const std::string& key, const std::string& value) {
    211   // Don't bother with empty keys or values
    212   if (key == "" || value == "") return;
    213   // Create log_value object and add it to the web_data object
    214   userfeedback::ProductSpecificData log_value;
    215   log_value.set_key(key);
    216   log_value.set_value(value);
    217   userfeedback::WebData* web_data = feedback_data->mutable_web_data();
    218   *(web_data->add_product_specific_data()) = log_value;
    219 }
    220 
    221 // Adds data as an attachment to feedback_data if the data is non-empty.
    222 void AddAttachment(userfeedback::ExtensionSubmit* feedback_data,
    223                    const char* name,
    224                    std::string* data) {
    225   if (data == NULL || data->empty())
    226     return;
    227 
    228   userfeedback::ProductSpecificBinaryData* attachment =
    229       feedback_data->add_product_specific_binary_data();
    230   attachment->set_mime_type(kArbitraryMimeType);
    231   attachment->set_name(name);
    232   attachment->set_data(*data);
    233 }
    234 
    235 }  // namespace
    236 
    237 namespace chrome {
    238 
    239 const char kAppLauncherCategoryTag[] = "AppLauncher";
    240 
    241 void ShowFeedbackPage(Browser* browser,
    242                       const std::string& description_template,
    243                       const std::string& category_tag) {
    244   GURL page_url;
    245   if (browser) {
    246     page_url = GetTargetTabUrl(browser->session_id().id(),
    247                                browser->tab_strip_model()->active_index());
    248   }
    249 
    250   Profile* profile = NULL;
    251   if (browser) {
    252     profile = browser->profile();
    253   } else {
    254     profile = ProfileManager::GetLastUsedProfileAllowedByPolicy();
    255   }
    256   if (!profile) {
    257     LOG(ERROR) << "Cannot invoke feedback: No profile found!";
    258     return;
    259   }
    260 
    261   extensions::FeedbackPrivateAPI* api =
    262       extensions::FeedbackPrivateAPI::GetFactoryInstance()->GetForProfile(
    263           profile);
    264 
    265   api->RequestFeedback(description_template,
    266                        category_tag,
    267                        page_url);
    268 }
    269 
    270 }  // namespace chrome
    271 
    272 namespace feedback_util {
    273 
    274 void SendReport(scoped_refptr<FeedbackData> data) {
    275   if (!data.get()) {
    276     LOG(ERROR) << "SendReport called with NULL data!";
    277     NOTREACHED();
    278     return;
    279   }
    280 
    281   userfeedback::ExtensionSubmit feedback_data;
    282   // Unused field, needs to be 0 though.
    283   feedback_data.set_type_id(0);
    284 
    285   userfeedback::CommonData* common_data = feedback_data.mutable_common_data();
    286   // We're not using gaia ids, we're using the e-mail field instead.
    287   common_data->set_gaia_id(0);
    288   common_data->set_user_email(data->user_email());
    289   common_data->set_description(data->description());
    290 
    291   std::string chrome_locale = g_browser_process->GetApplicationLocale();
    292   common_data->set_source_description_language(chrome_locale);
    293 
    294   userfeedback::WebData* web_data = feedback_data.mutable_web_data();
    295   web_data->set_url(data->page_url());
    296   web_data->mutable_navigator()->set_user_agent(content::GetUserAgent(GURL()));
    297 
    298   gfx::Rect screen_size;
    299   if (data->sys_info()) {
    300     for (FeedbackData::SystemLogsMap::const_iterator i =
    301         data->sys_info()->begin(); i != data->sys_info()->end(); ++i) {
    302       if (FeedbackData::BelowCompressionThreshold(i->second))
    303         AddFeedbackData(&feedback_data, i->first, i->second);
    304     }
    305 
    306     AddAttachment(&feedback_data, kLogsAttachmentName, data->compressed_logs());
    307   }
    308 
    309   if (data->histograms()) {
    310     AddAttachment(&feedback_data,
    311                   kHistogramsAttachmentName,
    312                   data->compressed_histograms());
    313   }
    314 
    315   if (!data->attached_filename().empty()) {
    316     // We need to use the UTF8Unsafe methods here to accomodate Windows, which
    317     // uses wide strings to store filepaths.
    318     std::string name = base::FilePath::FromUTF8Unsafe(
    319         data->attached_filename()).BaseName().AsUTF8Unsafe();
    320     AddAttachment(&feedback_data, name.c_str(), data->attached_filedata());
    321   }
    322 
    323   // NOTE: Screenshot needs to be processed after system info since we'll get
    324   // the screenshot dimensions from system info.
    325   if (data->image() && data->image()->size()) {
    326     userfeedback::PostedScreenshot screenshot;
    327     screenshot.set_mime_type(kPngMimeType);
    328 
    329     // Set that we 'have' dimensions of the screenshot. These dimensions are
    330     // ignored by the server but are a 'required' field in the protobuf.
    331     userfeedback::Dimensions dimensions;
    332     dimensions.set_width(0.0);
    333     dimensions.set_height(0.0);
    334 
    335     *(screenshot.mutable_dimensions()) = dimensions;
    336     screenshot.set_binary_content(*data->image());
    337 
    338     *(feedback_data.mutable_screenshot()) = screenshot;
    339   }
    340 
    341   if (data->category_tag().size())
    342     feedback_data.set_bucket(data->category_tag());
    343 
    344   // Set whether we're reporting from ChromeOS or Chrome on another platform.
    345   userfeedback::ChromeData chrome_data;
    346 #if defined(OS_CHROMEOS)
    347   chrome_data.set_chrome_platform(
    348       userfeedback::ChromeData_ChromePlatform_CHROME_OS);
    349   userfeedback::ChromeOsData chrome_os_data;
    350   chrome_os_data.set_category(
    351       userfeedback::ChromeOsData_ChromeOsCategory_OTHER);
    352   *(chrome_data.mutable_chrome_os_data()) = chrome_os_data;
    353   feedback_data.set_product_id(kChromeOSProductId);
    354 #else
    355   chrome_data.set_chrome_platform(
    356       userfeedback::ChromeData_ChromePlatform_CHROME_BROWSER);
    357   userfeedback::ChromeBrowserData chrome_browser_data;
    358   chrome_browser_data.set_category(
    359       userfeedback::ChromeBrowserData_ChromeBrowserCategory_OTHER);
    360   *(chrome_data.mutable_chrome_browser_data()) = chrome_browser_data;
    361   feedback_data.set_product_id(kChromeBrowserProductId);
    362 #endif
    363 
    364   *(feedback_data.mutable_chrome_data()) = chrome_data;
    365 
    366   // This pointer will eventually get deleted by the PostCleanup class, after
    367   // we've either managed to successfully upload the report or died trying.
    368   std::string* post_body = new std::string;
    369   feedback_data.SerializeToString(post_body);
    370 
    371   DispatchFeedback(data->profile(), post_body, 0);
    372 }
    373 
    374 bool ZipString(const base::FilePath& filename,
    375                const std::string& data, std::string* compressed_logs) {
    376   base::FilePath temp_path;
    377   base::FilePath zip_file;
    378 
    379   // Create a temporary directory, put the logs into a file in it. Create
    380   // another temporary file to receive the zip file in.
    381   if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_path))
    382     return false;
    383   if (file_util::WriteFile(temp_path.Append(filename),
    384                            data.c_str(), data.size()) == -1)
    385     return false;
    386 
    387   bool succeed = base::CreateTemporaryFile(&zip_file) &&
    388       zip::Zip(temp_path, zip_file, false) &&
    389       base::ReadFileToString(zip_file, compressed_logs);
    390 
    391   base::DeleteFile(temp_path, true);
    392   base::DeleteFile(zip_file, false);
    393 
    394   return succeed;
    395 }
    396 
    397 }  // namespace feedback_util
    398