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