// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/bug_report_util.h" #include #include #include "base/command_line.h" #include "base/file_util.h" #include "base/file_version_info.h" #include "base/memory/singleton.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/win/windows_version.h" #include "chrome/browser/browser_process_impl.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/safe_browsing/safe_browsing_util.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/net/url_fetcher.h" #include "content/browser/tab_contents/tab_contents.h" #include "googleurl/src/gurl.h" #include "grit/generated_resources.h" #include "grit/locale_settings.h" #include "grit/theme_resources.h" #include "net/url_request/url_request_status.h" #include "ui/base/l10n/l10n_util.h" #include "unicode/locid.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/notifications/system_notification.h" #endif namespace { const int kBugReportVersion = 1; const char kReportPhishingUrl[] = "http://www.google.com/safebrowsing/report_phish/"; // URL to post bug reports to. static char const kBugReportPostUrl[] = "https://www.google.com/tools/feedback/chrome/__submit"; static char const kProtBufMimeType[] = "application/x-protobuf"; static char const kPngMimeType[] = "image/png"; // Tags we use in product specific data static char const kPageTitleTag[] = "PAGE TITLE"; static char const kProblemTypeIdTag[] = "PROBLEM TYPE ID"; static char const kProblemTypeTag[] = "PROBLEM TYPE"; static char const kChromeVersionTag[] = "CHROME VERSION"; static char const kOsVersionTag[] = "OS VERSION"; static char const kNotificationId[] = "feedback.chromeos"; static int const kHttpPostSuccessNoContent = 204; static int const kHttpPostFailNoConnection = -1; static int const kHttpPostFailClientError = 400; static int const kHttpPostFailServerError = 500; #if defined(OS_CHROMEOS) static char const kBZip2MimeType[] = "application/x-bzip2"; static char const kLogsAttachmentName[] = "system_logs.bz2"; // Maximum number of lines in system info log chunk to be still included // in product specific data. const size_t kMaxLineCount = 10; // Maximum number of bytes in system info log chunk to be still included // in product specific data. const size_t kMaxSystemLogLength = 1024; #endif const int64 kInitialRetryDelay = 900000; // 15 minutes const int64 kRetryDelayIncreaseFactor = 2; const int64 kRetryDelayLimit = 14400000; // 4 hours } // namespace // Simple URLFetcher::Delegate to clean up URLFetcher on completion. class BugReportUtil::PostCleanup : public URLFetcher::Delegate { public: PostCleanup(Profile* profile, std::string* post_body, int64 previous_delay) : profile_(profile), post_body_(post_body), previous_delay_(previous_delay) { } // Overridden from URLFetcher::Delegate. virtual void OnURLFetchComplete(const URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const ResponseCookies& cookies, const std::string& data); protected: virtual ~PostCleanup() {} private: Profile* profile_; std::string* post_body_; int64 previous_delay_; DISALLOW_COPY_AND_ASSIGN(PostCleanup); }; // Don't use the data parameter, instead use the pointer we pass into every // post cleanup object - that pointer will be deleted and deleted only on a // successful post to the feedback server. void BugReportUtil::PostCleanup::OnURLFetchComplete( const URLFetcher* source, const GURL& url, const net::URLRequestStatus& status, int response_code, const ResponseCookies& cookies, const std::string& data) { std::stringstream error_stream; if (response_code == kHttpPostSuccessNoContent) { // We've sent our report, delete the report data delete post_body_; error_stream << "Success"; } else { // Uh oh, feedback failed, send it off to retry if (previous_delay_) { if (previous_delay_ < kRetryDelayLimit) previous_delay_ *= kRetryDelayIncreaseFactor; } else { previous_delay_ = kInitialRetryDelay; } BugReportUtil::DispatchFeedback(profile_, post_body_, previous_delay_); // Process the error for debug output if (response_code == kHttpPostFailNoConnection) { error_stream << "No connection to server."; } else if ((response_code > kHttpPostFailClientError) && (response_code < kHttpPostFailServerError)) { error_stream << "Client error: HTTP response code " << response_code; } else if (response_code > kHttpPostFailServerError) { error_stream << "Server error: HTTP response code " << response_code; } else { error_stream << "Unknown error: HTTP response code " << response_code; } } LOG(WARNING) << "FEEDBACK: Submission to feedback server (" << url << ") status: " << error_stream.str(); // Delete the URLFetcher. delete source; // And then delete ourselves. delete this; } // static void BugReportUtil::SetOSVersion(std::string* os_version) { #if defined(OS_WIN) base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); base::win::OSInfo::VersionNumber version_number = os_info->version_number(); *os_version = StringPrintf("%d.%d.%d", version_number.major, version_number.minor, version_number.build); int service_pack = os_info->service_pack().major; if (service_pack > 0) os_version->append(StringPrintf("Service Pack %d", service_pack)); #elif defined(OS_MACOSX) int32 major; int32 minor; int32 bugFix; base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugFix); *os_version = StringPrintf("%d.%d.%d", major, minor, bugFix); #else *os_version = "unknown"; #endif } // static std::string BugReportUtil::feedback_server_(""); // static void BugReportUtil::SetFeedbackServer(const std::string& server) { feedback_server_ = server; } // static void BugReportUtil::DispatchFeedback(Profile* profile, std::string* post_body, int64 delay) { DCHECK(post_body); MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableFunction( &BugReportUtil::SendFeedback, profile, post_body, delay), delay); } // static void BugReportUtil::SendFeedback(Profile* profile, std::string* post_body, int64 previous_delay) { DCHECK(post_body); GURL post_url; if (CommandLine::ForCurrentProcess()-> HasSwitch(switches::kFeedbackServer)) post_url = GURL(CommandLine::ForCurrentProcess()-> GetSwitchValueASCII(switches::kFeedbackServer)); else post_url = GURL(kBugReportPostUrl); URLFetcher* fetcher = new URLFetcher(post_url, URLFetcher::POST, new BugReportUtil::PostCleanup(profile, post_body, previous_delay)); fetcher->set_request_context(profile->GetRequestContext()); fetcher->set_upload_data(std::string(kProtBufMimeType), *post_body); fetcher->Start(); } // static void BugReportUtil::AddFeedbackData( userfeedback::ExternalExtensionSubmit* feedback_data, const std::string& key, const std::string& value) { // Don't bother with empty keys or values if (key=="" || value == "") return; // Create log_value object and add it to the web_data object userfeedback::ProductSpecificData log_value; log_value.set_key(key); log_value.set_value(value); userfeedback::WebData* web_data = feedback_data->mutable_web_data(); *(web_data->add_product_specific_data()) = log_value; } #if defined(OS_CHROMEOS) bool BugReportUtil::ValidFeedbackSize(const std::string& content) { if (content.length() > kMaxSystemLogLength) return false; size_t line_count = 0; const char* text = content.c_str(); for (size_t i = 0; i < content.length(); i++) { if (*(text + i) == '\n') { line_count++; if (line_count > kMaxLineCount) return false; } } return true; } #endif // static void BugReportUtil::SendReport(Profile* profile, int problem_type, const std::string& page_url_text, const std::string& description, const char* png_data, int png_data_length, int png_width, #if defined(OS_CHROMEOS) int png_height, const std::string& user_email_text, const char* zipped_logs_data, int zipped_logs_length, const chromeos::LogDictionaryType* const sys_info) { #else int png_height) { #endif // Create google feedback protocol buffer objects userfeedback::ExternalExtensionSubmit feedback_data; // type id set to 0, unused field but needs to be initialized to 0 feedback_data.set_type_id(0); userfeedback::CommonData* common_data = feedback_data.mutable_common_data(); userfeedback::WebData* web_data = feedback_data.mutable_web_data(); // Set GAIA id to 0. We're not using gaia id's for recording // use feedback - we're using the e-mail field, allows users to // submit feedback from incognito mode and specify any mail id // they wish common_data->set_gaia_id(0); #if defined(OS_CHROMEOS) // Add the user e-mail to the feedback object common_data->set_user_email(user_email_text); #endif // Add the description to the feedback object common_data->set_description(description); // Add the language std::string chrome_locale = g_browser_process->GetApplicationLocale(); common_data->set_source_description_language(chrome_locale); // Set the url web_data->set_url(page_url_text); // Add the Chrome version chrome::VersionInfo version_info; if (version_info.is_valid()) { std::string chrome_version = version_info.Name() + " - " + version_info.Version() + " (" + version_info.LastChange() + ")"; AddFeedbackData(&feedback_data, std::string(kChromeVersionTag), chrome_version); } // Add OS version (eg, for WinXP SP2: "5.1.2600 Service Pack 2"). std::string os_version = ""; SetOSVersion(&os_version); AddFeedbackData(&feedback_data, std::string(kOsVersionTag), os_version); // Include the page image if we have one. if (png_data) { userfeedback::PostedScreenshot screenshot; screenshot.set_mime_type(kPngMimeType); // Set the dimensions of the screenshot userfeedback::Dimensions dimensions; dimensions.set_width(static_cast(png_width)); dimensions.set_height(static_cast(png_height)); *(screenshot.mutable_dimensions()) = dimensions; screenshot.set_binary_content(std::string(png_data, png_data_length)); // Set the screenshot object in feedback *(feedback_data.mutable_screenshot()) = screenshot; } #if defined(OS_CHROMEOS) if (sys_info) { // Add the product specific data for (chromeos::LogDictionaryType::const_iterator i = sys_info->begin(); i != sys_info->end(); ++i) if (!CommandLine::ForCurrentProcess()->HasSwitch( switches::kCompressSystemFeedback) || ValidFeedbackSize(i->second)) { AddFeedbackData(&feedback_data, i->first, i->second); } // If we have zipped logs, add them here if (zipped_logs_data && CommandLine::ForCurrentProcess()->HasSwitch( switches::kCompressSystemFeedback)) { userfeedback::ProductSpecificBinaryData attachment; attachment.set_mime_type(kBZip2MimeType); attachment.set_name(kLogsAttachmentName); attachment.set_data(std::string(zipped_logs_data, zipped_logs_length)); *(feedback_data.add_product_specific_binary_data()) = attachment; } } #endif // Set our Chrome specific data userfeedback::ChromeData chrome_data; #if defined(OS_CHROMEOS) chrome_data.set_chrome_platform( userfeedback::ChromeData_ChromePlatform_CHROME_OS); userfeedback::ChromeOsData chrome_os_data; chrome_os_data.set_category( (userfeedback::ChromeOsData_ChromeOsCategory) problem_type); *(chrome_data.mutable_chrome_os_data()) = chrome_os_data; #else chrome_data.set_chrome_platform( userfeedback::ChromeData_ChromePlatform_CHROME_BROWSER); userfeedback::ChromeBrowserData chrome_browser_data; chrome_browser_data.set_category( (userfeedback::ChromeBrowserData_ChromeBrowserCategory) problem_type); *(chrome_data.mutable_chrome_browser_data()) = chrome_browser_data; #endif *(feedback_data.mutable_chrome_data()) = chrome_data; // Serialize our report to a string pointer we can pass around std::string* post_body = new std::string; feedback_data.SerializeToString(post_body); // We have the body of our POST, so send it off to the server with 0 delay DispatchFeedback(profile, post_body, 0); } // static void BugReportUtil::ReportPhishing(TabContents* currentTab, const std::string& phishing_url) { currentTab->controller().LoadURL( safe_browsing_util::GeneratePhishingReportUrl( kReportPhishingUrl, phishing_url), GURL(), PageTransition::LINK); }