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:
PostCleanup(Profile * profile,std::string * post_body,int64 previous_delay)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:
~PostCleanup()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.
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)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
SetOSVersion(std::string * os_version)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
SetFeedbackServer(const std::string & server)184 void BugReportUtil::SetFeedbackServer(const std::string& server) {
185 feedback_server_ = server;
186 }
187
188 // static
DispatchFeedback(Profile * profile,std::string * post_body,int64 delay)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
SendFeedback(Profile * profile,std::string * post_body,int64 previous_delay)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
AddFeedbackData(userfeedback::ExternalExtensionSubmit * feedback_data,const std::string & key,const std::string & value)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)
ValidFeedbackSize(const std::string & content)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
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,int png_height,const std::string & user_email_text,const char * zipped_logs_data,int zipped_logs_length,const chromeos::LogDictionaryType * const sys_info)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