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
37namespace {
38
39const int kBugReportVersion = 1;
40
41const char kReportPhishingUrl[] =
42    "http://www.google.com/safebrowsing/report_phish/";
43
44// URL to post bug reports to.
45static char const kBugReportPostUrl[] =
46    "https://www.google.com/tools/feedback/chrome/__submit";
47
48static char const kProtBufMimeType[] = "application/x-protobuf";
49static char const kPngMimeType[] = "image/png";
50
51// Tags we use in product specific data
52static char const kPageTitleTag[] = "PAGE TITLE";
53static char const kProblemTypeIdTag[] = "PROBLEM TYPE ID";
54static char const kProblemTypeTag[] = "PROBLEM TYPE";
55static char const kChromeVersionTag[] = "CHROME VERSION";
56static char const kOsVersionTag[] = "OS VERSION";
57
58static char const kNotificationId[] = "feedback.chromeos";
59
60static int const kHttpPostSuccessNoContent = 204;
61static int const kHttpPostFailNoConnection = -1;
62static int const kHttpPostFailClientError = 400;
63static int const kHttpPostFailServerError = 500;
64
65#if defined(OS_CHROMEOS)
66static char const kBZip2MimeType[] = "application/x-bzip2";
67static 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.
70const size_t kMaxLineCount       = 10;
71// Maximum number of bytes in system info log chunk to be still included
72// in product specific data.
73const size_t kMaxSystemLogLength = 1024;
74#endif
75
76const int64 kInitialRetryDelay = 900000; // 15 minutes
77const int64 kRetryDelayIncreaseFactor = 2;
78const int64 kRetryDelayLimit = 14400000; // 4 hours
79
80
81}  // namespace
82
83
84// Simple URLFetcher::Delegate to clean up URLFetcher on completion.
85class 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.
113void 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
160void 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
181std::string BugReportUtil::feedback_server_("");
182
183// static
184void BugReportUtil::SetFeedbackServer(const std::string& server) {
185  feedback_server_ = server;
186}
187
188// static
189void 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
199void 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
224void 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)
238bool 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
255void 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
381void 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