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
13 namespace {
14
15 const char kMultilineIndicatorString[] = "<multiline>\n";
16 const char kMultilineStartString[] = "---------- START ----------\n";
17 const char kMultilineEndString[] = "---------- END ----------\n\n";
18
19 const size_t kFeedbackMaxLength = 4 * 1024;
20 const size_t kFeedbackMaxLineCount = 40;
21
22 const base::FilePath::CharType kLogsFilename[] =
23 FILE_PATH_LITERAL("system_logs.txt");
24 const char kLogsAttachmentName[] = "system_logs.zip";
25
26 const char kZipExt[] = ".zip";
27
28 const char kPngMimeType[] = "image/png";
29 const 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.
LogsToString(const FeedbackCommon::SystemLogsMap & sys_info)35 std::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
AddFeedbackData(userfeedback::ExtensionSubmit * feedback_data,const std::string & key,const std::string & value)60 void 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.
AddAttachment(userfeedback::ExtensionSubmit * feedback_data,const char * name,const std::string & data)75 void 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
AttachedFile(const std::string & filename,scoped_ptr<std::string> data)90 FeedbackCommon::AttachedFile::AttachedFile(const std::string& filename,
91 scoped_ptr<std::string> data)
92 : name(filename), data(data.Pass()) {}
93
~AttachedFile()94 FeedbackCommon::AttachedFile::~AttachedFile() {}
95
96
FeedbackCommon()97 FeedbackCommon::FeedbackCommon() : product_id_(0) {}
98
~FeedbackCommon()99 FeedbackCommon::~FeedbackCommon() {}
100
101 // static
BelowCompressionThreshold(const std::string & content)102 bool 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
CompressFile(const base::FilePath & filename,const std::string & zipname,scoped_ptr<std::string> data)111 void 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
AddFile(const std::string & filename,scoped_ptr<std::string> data)130 void 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
AddLog(const std::string & name,const std::string & value)136 void 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
AddLogs(scoped_ptr<SystemLogsMap> logs)142 void 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
CompressLogs()150 void 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
AddFilesAndLogsToReport(userfeedback::ExtensionSubmit * feedback_data) const160 void 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
PrepareReport(userfeedback::ExtensionSubmit * feedback_data) const177 void 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