1// Copyright 2014 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 "feedback_common.h"
6
7#include "base/strings/string_util.h"
8#include "components/feedback/proto/common.pb.h"
9#include "components/feedback/proto/dom.pb.h"
10#include "components/feedback/proto/extension.pb.h"
11#include "components/feedback/proto/math.pb.h"
12
13namespace {
14
15const char kMultilineIndicatorString[] = "<multiline>\n";
16const char kMultilineStartString[] = "---------- START ----------\n";
17const char kMultilineEndString[] = "---------- END ----------\n\n";
18
19const size_t kFeedbackMaxLength = 4 * 1024;
20const size_t kFeedbackMaxLineCount = 40;
21
22const base::FilePath::CharType kLogsFilename[] =
23    FILE_PATH_LITERAL("system_logs.txt");
24const char kLogsAttachmentName[] = "system_logs.zip";
25
26const char kZipExt[] = ".zip";
27
28const char kPngMimeType[] = "image/png";
29const char kArbitraryMimeType[] = "application/octet-stream";
30
31// Converts the system logs into a string that we can compress and send
32// with the report. This method only converts those logs that we want in
33// the compressed zip file sent with the report, hence it ignores any logs
34// below the size threshold of what we want compressed.
35std::string* LogsToString(const FeedbackCommon::SystemLogsMap& sys_info) {
36  std::string* syslogs_string = new std::string;
37  for (FeedbackCommon::SystemLogsMap::const_iterator it = sys_info.begin();
38       it != sys_info.end();
39       ++it) {
40    std::string key = it->first;
41    std::string value = it->second;
42
43    if (FeedbackCommon::BelowCompressionThreshold(value))
44      continue;
45
46    base::TrimString(key, "\n ", &key);
47    base::TrimString(value, "\n ", &value);
48
49    if (value.find("\n") != std::string::npos) {
50      syslogs_string->append(key + "=" + kMultilineIndicatorString +
51                             kMultilineStartString + value + "\n" +
52                             kMultilineEndString);
53    } else {
54      syslogs_string->append(key + "=" + value + "\n");
55    }
56  }
57  return syslogs_string;
58}
59
60void AddFeedbackData(userfeedback::ExtensionSubmit* feedback_data,
61                     const std::string& key,
62                     const std::string& value) {
63  // Don't bother with empty keys or values.
64  if (key.empty() || value.empty())
65    return;
66  // Create log_value object and add it to the web_data object.
67  userfeedback::ProductSpecificData log_value;
68  log_value.set_key(key);
69  log_value.set_value(value);
70  userfeedback::WebData* web_data = feedback_data->mutable_web_data();
71  *(web_data->add_product_specific_data()) = log_value;
72}
73
74// Adds data as an attachment to feedback_data if the data is non-empty.
75void AddAttachment(userfeedback::ExtensionSubmit* feedback_data,
76                   const char* name,
77                   const std::string& data) {
78  if (data.empty())
79    return;
80
81  userfeedback::ProductSpecificBinaryData* attachment =
82      feedback_data->add_product_specific_binary_data();
83  attachment->set_mime_type(kArbitraryMimeType);
84  attachment->set_name(name);
85  attachment->set_data(data);
86}
87
88}  // namespace
89
90FeedbackCommon::AttachedFile::AttachedFile(const std::string& filename,
91                                           scoped_ptr<std::string> data)
92    : name(filename), data(data.Pass()) {}
93
94FeedbackCommon::AttachedFile::~AttachedFile() {}
95
96
97FeedbackCommon::FeedbackCommon() : product_id_(0) {}
98
99FeedbackCommon::~FeedbackCommon() {}
100
101// static
102bool FeedbackCommon::BelowCompressionThreshold(const std::string& content) {
103  if (content.length() > kFeedbackMaxLength)
104    return false;
105  const size_t line_count = std::count(content.begin(), content.end(), '\n');
106  if (line_count > kFeedbackMaxLineCount)
107    return false;
108  return true;
109}
110
111void FeedbackCommon::CompressFile(const base::FilePath& filename,
112                                  const std::string& zipname,
113                                  scoped_ptr<std::string> data) {
114  AttachedFile* file = new AttachedFile(
115      zipname, scoped_ptr<std::string>(new std::string()));
116  if (file->name.empty()) {
117    // We need to use the UTF8Unsafe methods here to accomodate Windows, which
118    // uses wide strings to store filepaths.
119    file->name = filename.BaseName().AsUTF8Unsafe();
120    file->name.append(kZipExt);
121  }
122  if (feedback_util::ZipString(filename, *(data.get()), file->data.get())) {
123    base::AutoLock lock(attachments_lock_);
124    attachments_.push_back(file);
125  } else {
126    delete file;
127  }
128}
129
130void FeedbackCommon::AddFile(const std::string& filename,
131                             scoped_ptr<std::string> data) {
132  base::AutoLock lock(attachments_lock_);
133  attachments_.push_back(new AttachedFile(filename, data.Pass()));
134}
135
136void FeedbackCommon::AddLog(const std::string& name, const std::string& value) {
137  if (!logs_.get())
138    logs_ = scoped_ptr<SystemLogsMap>(new SystemLogsMap);
139  (*logs_.get())[name] = value;
140}
141
142void FeedbackCommon::AddLogs(scoped_ptr<SystemLogsMap> logs) {
143  if (logs_) {
144    logs_->insert(logs->begin(), logs->end());
145  } else {
146    logs_ = logs.Pass();
147  }
148}
149
150void FeedbackCommon::CompressLogs() {
151  if (!logs_)
152    return;
153  std::string* logs = LogsToString(*logs_.get());
154  if (!logs->empty())
155    CompressFile(
156        base::FilePath(kLogsFilename), kLogsAttachmentName,
157        scoped_ptr<std::string>(logs));
158}
159
160void FeedbackCommon::AddFilesAndLogsToReport(
161    userfeedback::ExtensionSubmit* feedback_data) const {
162  if (sys_info()) {
163    for (FeedbackCommon::SystemLogsMap::const_iterator i = sys_info()->begin();
164         i != sys_info()->end();
165         ++i) {
166      if (BelowCompressionThreshold(i->second))
167        AddFeedbackData(feedback_data, i->first, i->second);
168    }
169  }
170
171  for (size_t i = 0; i < attachments(); i++) {
172    const AttachedFile* file = attachment(i);
173    AddAttachment(feedback_data, file->name.c_str(), *file->data.get());
174  }
175}
176
177void FeedbackCommon::PrepareReport(
178    userfeedback::ExtensionSubmit* feedback_data) const {
179  // Unused field, needs to be 0 though.
180  feedback_data->set_type_id(0);
181  feedback_data->set_product_id(product_id_);
182
183  userfeedback::CommonData* common_data = feedback_data->mutable_common_data();
184  // We're not using gaia ids, we're using the e-mail field instead.
185  common_data->set_gaia_id(0);
186  common_data->set_user_email(user_email());
187  common_data->set_description(description());
188  common_data->set_source_description_language(locale());
189
190  userfeedback::WebData* web_data = feedback_data->mutable_web_data();
191  web_data->set_url(page_url());
192  web_data->mutable_navigator()->set_user_agent(user_agent());
193
194  AddFilesAndLogsToReport(feedback_data);
195
196  if (image() && image()->size()) {
197    userfeedback::PostedScreenshot screenshot;
198    screenshot.set_mime_type(kPngMimeType);
199
200    // Set that we 'have' dimensions of the screenshot. These dimensions are
201    // ignored by the server but are a 'required' field in the protobuf.
202    userfeedback::Dimensions dimensions;
203    dimensions.set_width(0.0);
204    dimensions.set_height(0.0);
205
206    *(screenshot.mutable_dimensions()) = dimensions;
207    screenshot.set_binary_content(*image());
208
209    *(feedback_data->mutable_screenshot()) = screenshot;
210  }
211
212  if (category_tag().size())
213    feedback_data->set_bucket(category_tag());
214}
215