webrtc_log_uploader.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
1// Copyright 2013 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/media/webrtc_log_uploader.h"
6
7#include "base/file_util.h"
8#include "base/files/file_path.h"
9#include "base/logging.h"
10#include "base/memory/shared_memory.h"
11#include "base/path_service.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/string_split.h"
14#include "base/strings/stringprintf.h"
15#include "base/time/time.h"
16#include "chrome/browser/media/webrtc_log_upload_list.h"
17#include "chrome/common/chrome_paths.h"
18#include "chrome/common/chrome_version_info.h"
19#include "chrome/common/partial_circular_buffer.h"
20#include "content/public/browser/browser_thread.h"
21#include "net/base/mime_util.h"
22#include "net/base/network_delegate.h"
23#include "net/proxy/proxy_config.h"
24#include "net/proxy/proxy_config_service.h"
25#include "net/url_request/url_fetcher.h"
26#include "net/url_request/url_request_context.h"
27#include "net/url_request/url_request_context_builder.h"
28#include "net/url_request/url_request_context_getter.h"
29#include "third_party/zlib/zlib.h"
30
31namespace {
32
33const int kLogCountLimit = 5;
34const uint32 kIntermediateCompressionBufferBytes = 256 * 1024;  // 256 KB
35const int kLogListLimitLines = 50;
36
37const char kUploadURL[] = "https://clients2.google.com/cr/report";
38const char kUploadContentType[] = "multipart/form-data";
39const char kMultipartBoundary[] =
40    "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
41
42}  // namespace
43
44WebRtcLogUploader::WebRtcLogUploader()
45    : log_count_(0),
46      post_data_(NULL) {
47  base::FilePath log_dir_path;
48  PathService::Get(chrome::DIR_USER_DATA, &log_dir_path);
49  upload_list_path_ =
50      log_dir_path.AppendASCII(WebRtcLogUploadList::kWebRtcLogListFilename);
51}
52
53WebRtcLogUploader::~WebRtcLogUploader() {}
54
55void WebRtcLogUploader::OnURLFetchComplete(
56    const net::URLFetcher* source) {
57  int response_code = source->GetResponseCode();
58  std::string report_id;
59  if (response_code == 200 && source->GetResponseAsString(&report_id))
60    AddUploadedLogInfoToUploadListFile(report_id);
61  DCHECK(upload_done_data_.find(source) != upload_done_data_.end());
62  content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
63      base::Bind(&WebRtcLoggingHandlerHost::UploadLogDone,
64                 upload_done_data_[source].host));
65  if (!upload_done_data_[source].callback.is_null()) {
66    bool success = response_code == 200;
67    std::string error_message;
68    if (!success) {
69      error_message = "Uploading failed, response code: " +
70                      base::IntToString(response_code);
71    }
72    content::BrowserThread::PostTask(
73        content::BrowserThread::UI, FROM_HERE,
74        base::Bind(upload_done_data_[source].callback, success, report_id,
75                   error_message));
76  }
77  upload_done_data_.erase(source);
78}
79
80void WebRtcLogUploader::OnURLFetchUploadProgress(
81    const net::URLFetcher* source, int64 current, int64 total) {
82}
83
84bool WebRtcLogUploader::ApplyForStartLogging() {
85  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
86  if (log_count_ < kLogCountLimit) {
87    ++log_count_;
88    return true;
89  }
90  return false;
91}
92
93void WebRtcLogUploader::LoggingStoppedDontUpload() {
94  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
95      base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this)));
96}
97
98void WebRtcLogUploader::LoggingStoppedDoUpload(
99    net::URLRequestContextGetter* request_context,
100    scoped_ptr<base::SharedMemory> shared_memory,
101    uint32 length,
102    const std::map<std::string, std::string>& meta_data,
103    const WebRtcLogUploadDoneData& upload_done_data) {
104  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
105  DCHECK(shared_memory);
106  DCHECK(shared_memory->memory());
107
108  std::string post_data;
109  SetupMultipart(&post_data, reinterpret_cast<uint8*>(shared_memory->memory()),
110                 length, meta_data);
111
112  // If a test has set the test string pointer, write to it and skip uploading.
113  // This will be removed when the browser test for this feature is fully done
114  // according to the test plan. See http://crbug.com/257329.
115  if (post_data_) {
116    *post_data_ = post_data;
117    return;
118  }
119
120  std::string content_type = kUploadContentType;
121  content_type.append("; boundary=");
122  content_type.append(kMultipartBoundary);
123
124  net::URLFetcher* url_fetcher =
125      net::URLFetcher::Create(GURL(kUploadURL), net::URLFetcher::POST, this);
126  url_fetcher->SetRequestContext(request_context);
127  url_fetcher->SetUploadData(content_type, post_data);
128  url_fetcher->Start();
129  upload_done_data_[url_fetcher] = upload_done_data;
130
131  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
132      base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this)));
133}
134
135void WebRtcLogUploader::SetupMultipart(
136    std::string* post_data, uint8* log_buffer, uint32 log_buffer_length,
137    const std::map<std::string, std::string>& meta_data) {
138#if defined(OS_WIN)
139  const char product[] = "Chrome";
140#elif defined(OS_MACOSX)
141  const char product[] = "Chrome_Mac";
142#elif defined(OS_LINUX)
143#if !defined(ADDRESS_SANITIZER)
144  const char product[] = "Chrome_Linux";
145#else
146  const char product[] = "Chrome_Linux_ASan";
147#endif
148#elif defined(OS_ANDROID)
149  const char product[] = "Chrome_Android";
150#elif defined(OS_CHROMEOS)
151  const char product[] = "Chrome_ChromeOS";
152#else
153  // This file should not be compiled for other platforms.
154  COMPILE_ASSERT(false);
155#endif
156  net::AddMultipartValueForUpload("prod", product, kMultipartBoundary,
157                                  "", post_data);
158  chrome::VersionInfo version_info;
159  net::AddMultipartValueForUpload("ver", version_info.Version(),
160                                  kMultipartBoundary, "", post_data);
161  net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary,
162                                  "", post_data);
163  net::AddMultipartValueForUpload("type", "webrtc_log", kMultipartBoundary,
164                                  "", post_data);
165
166  // Add custom meta data.
167  std::map<std::string, std::string>::const_iterator it = meta_data.begin();
168  for (; it != meta_data.end(); ++it) {
169    net::AddMultipartValueForUpload(it->first, it->second, kMultipartBoundary,
170                                    "", post_data);
171  }
172
173  AddLogData(post_data, log_buffer, log_buffer_length);
174  net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, post_data);
175}
176
177void WebRtcLogUploader::AddLogData(std::string* post_data,
178                                   uint8* log_buffer,
179                                   uint32 log_buffer_length) {
180  post_data->append("--");
181  post_data->append(kMultipartBoundary);
182  post_data->append("\r\n");
183  post_data->append("Content-Disposition: form-data; name=\"webrtc_log\"");
184  post_data->append("; filename=\"webrtc_log.gz\"\r\n");
185  post_data->append("Content-Type: application/gzip\r\n\r\n");
186
187  CompressLog(post_data, log_buffer, log_buffer_length);
188
189  post_data->append("\r\n");
190}
191
192void WebRtcLogUploader::CompressLog(std::string* post_data,
193                                    uint8* input,
194                                    uint32 input_size) {
195  PartialCircularBuffer read_pcb(input, input_size);
196
197  z_stream stream = {0};
198  int result = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
199                            // windowBits = 15 is default, 16 is added to
200                            // produce a gzip header + trailer.
201                            15 + 16,
202                            8,  // memLevel = 8 is default.
203                            Z_DEFAULT_STRATEGY);
204  DCHECK_EQ(Z_OK, result);
205
206  uint8 intermediate_buffer[kIntermediateCompressionBufferBytes] = {0};
207  ResizeForNextOutput(post_data, &stream);
208  uint32 read = 0;
209
210  do {
211    if (stream.avail_in == 0) {
212      read = read_pcb.Read(&intermediate_buffer[0],
213                           kIntermediateCompressionBufferBytes);
214      stream.next_in = &intermediate_buffer[0];
215      stream.avail_in = read;
216      if (read != kIntermediateCompressionBufferBytes)
217        break;
218    }
219    result = deflate(&stream, Z_SYNC_FLUSH);
220    DCHECK_EQ(Z_OK, result);
221    if (stream.avail_out == 0)
222      ResizeForNextOutput(post_data, &stream);
223  } while (true);
224
225  // Ensure we have enough room in the output buffer. Easier to always just do a
226  // resize than looping around and resize if needed.
227  if (stream.avail_out < kIntermediateCompressionBufferBytes)
228    ResizeForNextOutput(post_data, &stream);
229
230  result = deflate(&stream, Z_FINISH);
231  DCHECK_EQ(Z_STREAM_END, result);
232  result = deflateEnd(&stream);
233  DCHECK_EQ(Z_OK, result);
234
235  post_data->resize(post_data->size() - stream.avail_out);
236}
237
238void WebRtcLogUploader::ResizeForNextOutput(std::string* post_data,
239                                            z_stream* stream) {
240  size_t old_size = post_data->size() - stream->avail_out;
241  post_data->resize(old_size + kIntermediateCompressionBufferBytes);
242  stream->next_out = reinterpret_cast<uint8*>(&(*post_data)[old_size]);
243  stream->avail_out = kIntermediateCompressionBufferBytes;
244}
245
246void WebRtcLogUploader::DecreaseLogCount() {
247  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
248  --log_count_;
249}
250
251void WebRtcLogUploader::AddUploadedLogInfoToUploadListFile(
252    const std::string& report_id) {
253  std::string contents;
254
255  if (base::PathExists(upload_list_path_)) {
256    bool read_ok = base::ReadFileToString(upload_list_path_, &contents);
257    DPCHECK(read_ok);
258
259    // Limit the number of log entries to |kLogListLimitLines| - 1, to make room
260    // for the new entry. Each line including the last ends with a '\n', so hit
261    // n will be before line n-1 (from the back).
262    int lf_count = 0;
263    int i = contents.size() - 1;
264    for (; i >= 0 && lf_count < kLogListLimitLines; --i) {
265      if (contents[i] == '\n')
266        ++lf_count;
267    }
268    if (lf_count >= kLogListLimitLines) {
269      // + 1 to compensate for the for loop decrease before the conditional
270      // check and + 1 to get the length.
271      contents.erase(0, i + 2);
272    }
273  }
274
275  // Write the Unix time and report ID to the log list file.
276  base::Time time_now = base::Time::Now();
277  contents += base::DoubleToString(time_now.ToDoubleT()) +
278              "," + report_id + '\n';
279
280  int written = file_util::WriteFile(upload_list_path_, &contents[0],
281                                     contents.size());
282  DPCHECK(written == static_cast<int>(contents.size()));
283}
284