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 "content/browser/tracing/trace_uploader.h"
6
7#include "base/files/file_path.h"
8#include "base/files/file_util.h"
9#include "base/memory/shared_memory.h"
10#include "base/path_service.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/stringprintf.h"
13#include "base/time/time.h"
14#include "content/public/browser/browser_thread.h"
15#include "net/base/mime_util.h"
16#include "net/base/network_delegate.h"
17#include "net/proxy/proxy_config.h"
18#include "net/proxy/proxy_config_service.h"
19#include "net/url_request/url_fetcher.h"
20#include "net/url_request/url_request_context.h"
21#include "net/url_request/url_request_context_builder.h"
22#include "net/url_request/url_request_context_getter.h"
23#include "third_party/zlib/zlib.h"
24#include "url/gurl.h"
25
26namespace content {
27namespace {
28const char kUploadContentType[] = "multipart/form-data";
29const char kMultipartBoundary[] =
30    "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
31
32const int kHttpResponseOk = 200;
33
34}  // namespace
35
36TraceUploader::TraceUploader(const std::string& product,
37                             const std::string& version,
38                             const std::string& upload_url,
39                             net::URLRequestContextGetter* request_context)
40    : product_(product),
41      version_(version),
42      upload_url_(upload_url),
43      request_context_(request_context) {
44  DCHECK(!product_.empty());
45  DCHECK(!version_.empty());
46  DCHECK(!upload_url_.empty());
47}
48
49TraceUploader::~TraceUploader() {
50  DCHECK_CURRENTLY_ON(BrowserThread::UI);
51}
52
53void TraceUploader::OnURLFetchComplete(const net::URLFetcher* source) {
54  DCHECK_CURRENTLY_ON(BrowserThread::UI);
55  DCHECK_EQ(source, url_fetcher_.get());
56  int response_code = source->GetResponseCode();
57  string report_id;
58  string error_message;
59  bool success = (response_code == kHttpResponseOk);
60  if (success) {
61    source->GetResponseAsString(&report_id);
62  } else {
63    error_message = "Uploading failed, response code: " +
64                    base::IntToString(response_code);
65  }
66
67  BrowserThread::PostTask(
68      content::BrowserThread::UI,
69      FROM_HERE,
70      base::Bind(done_callback_, success, report_id, error_message));
71  url_fetcher_.reset();
72}
73
74void TraceUploader::OnURLFetchUploadProgress(
75    const net::URLFetcher* source, int64 current, int64 total) {
76  DCHECK(url_fetcher_.get());
77
78  LOG(WARNING) << "Upload progress: " << current << " of " << total;
79  BrowserThread::PostTask(
80      content::BrowserThread::UI,
81      FROM_HERE,
82      base::Bind(progress_callback_, current, total));
83}
84
85void TraceUploader::DoUpload(
86    const std::string& file_contents,
87    UploadProgressCallback progress_callback,
88    UploadDoneCallback done_callback) {
89  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
90  DCHECK(!url_fetcher_.get());
91
92  progress_callback_ = progress_callback;
93  done_callback_ = done_callback;
94
95  if (url_fetcher_.get()) {
96    OnUploadError("Already uploading.");
97  }
98
99  scoped_ptr<char[]> compressed_contents(new char[kMaxUploadBytes]);
100  int compressed_bytes;
101  if (!Compress(file_contents, kMaxUploadBytes, compressed_contents.get(),
102                &compressed_bytes)) {
103    OnUploadError("Compressing file failed.");
104    return;
105  }
106
107  std::string post_data;
108  SetupMultipart("trace.json.gz",
109                 std::string(compressed_contents.get(), compressed_bytes),
110                 &post_data);
111
112  content::BrowserThread::PostTask(
113      content::BrowserThread::UI, FROM_HERE,
114      base::Bind(&TraceUploader::CreateAndStartURLFetcher,
115                 base::Unretained(this),
116                 post_data));
117}
118
119void TraceUploader::OnUploadError(std::string error_message) {
120  LOG(ERROR) << error_message;
121  content::BrowserThread::PostTask(
122      content::BrowserThread::UI,
123      FROM_HERE,
124      base::Bind(done_callback_, false, "", error_message));
125}
126
127void TraceUploader::SetupMultipart(const std::string& trace_filename,
128                                   const std::string& trace_contents,
129                                   std::string* post_data) {
130  net::AddMultipartValueForUpload("prod", product_, kMultipartBoundary, "",
131                                  post_data);
132  net::AddMultipartValueForUpload("ver", version_ + "-trace",
133                                  kMultipartBoundary, "", post_data);
134  net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary,
135                                  "", post_data);
136  net::AddMultipartValueForUpload("type", "trace", kMultipartBoundary,
137                                  "", post_data);
138  // No minidump means no need for crash to process the report.
139  net::AddMultipartValueForUpload("should_process", "false", kMultipartBoundary,
140                                  "", post_data);
141
142  AddTraceFile(trace_filename, trace_contents, post_data);
143
144  net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, post_data);
145}
146
147void TraceUploader::AddTraceFile(const std::string& trace_filename,
148                                 const std::string& trace_contents,
149                                 std::string* post_data) {
150  post_data->append("--");
151  post_data->append(kMultipartBoundary);
152  post_data->append("\r\n");
153  post_data->append("Content-Disposition: form-data; name=\"trace\"");
154  post_data->append("; filename=\"");
155  post_data->append(trace_filename);
156  post_data->append("\"\r\n");
157  post_data->append("Content-Type: application/gzip\r\n\r\n");
158  post_data->append(trace_contents);
159  post_data->append("\r\n");
160}
161
162bool TraceUploader::Compress(std::string input,
163                             int max_compressed_bytes,
164                             char* compressed,
165                             int* compressed_bytes) {
166  DCHECK(compressed);
167  DCHECK(compressed_bytes);
168  z_stream stream = {0};
169  int result = deflateInit2(&stream,
170                            Z_DEFAULT_COMPRESSION,
171                            Z_DEFLATED,
172                            // 16 is added to produce a gzip header + trailer.
173                            MAX_WBITS + 16,
174                            8,  // memLevel = 8 is default.
175                            Z_DEFAULT_STRATEGY);
176  DCHECK_EQ(Z_OK, result);
177  stream.next_in = reinterpret_cast<uint8*>(&input[0]);
178  stream.avail_in = input.size();
179  stream.next_out = reinterpret_cast<uint8*>(compressed);
180  stream.avail_out = max_compressed_bytes;
181  // Do a one-shot compression. This will return Z_STREAM_END only if |output|
182  // is large enough to hold all compressed data.
183  result = deflate(&stream, Z_FINISH);
184  bool success = (result == Z_STREAM_END);
185  result = deflateEnd(&stream);
186  DCHECK(result == Z_OK || result == Z_DATA_ERROR);
187
188  if (success)
189    *compressed_bytes = max_compressed_bytes - stream.avail_out;
190
191  LOG(WARNING) << "input size: " << input.size()
192               << ", output size: " << *compressed_bytes;
193  return success;
194}
195
196void TraceUploader::CreateAndStartURLFetcher(const std::string& post_data) {
197  DCHECK_CURRENTLY_ON(BrowserThread::UI);
198  DCHECK(!url_fetcher_.get());
199
200  std::string content_type = kUploadContentType;
201  content_type.append("; boundary=");
202  content_type.append(kMultipartBoundary);
203
204  url_fetcher_.reset(
205      net::URLFetcher::Create(GURL(upload_url_), net::URLFetcher::POST, this));
206  url_fetcher_->SetRequestContext(request_context_);
207  url_fetcher_->SetUploadData(content_type, post_data);
208  url_fetcher_->Start();
209}
210
211}  // namespace content
212