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