Home | History | Annotate | Download | only in feedback
      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.
     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 
     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.
     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 
     90 FeedbackCommon::AttachedFile::AttachedFile(const std::string& filename,
     91                                            scoped_ptr<std::string> data)
     92     : name(filename), data(data.Pass()) {}
     93 
     94 FeedbackCommon::AttachedFile::~AttachedFile() {}
     95 
     96 
     97 FeedbackCommon::FeedbackCommon() : product_id_(0) {}
     98 
     99 FeedbackCommon::~FeedbackCommon() {}
    100 
    101 // static
    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 
    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 
    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 
    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 
    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 
    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 
    160 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 
    177 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