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