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/files/file_enumerator.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/logging.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/browser_process.h"
17 #include "chrome/browser/media/webrtc_log_list.h"
18 #include "chrome/browser/media/webrtc_log_util.h"
19 #include "chrome/common/chrome_version_info.h"
20 #include "chrome/common/partial_circular_buffer.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "net/base/mime_util.h"
23 #include "net/url_request/url_fetcher.h"
24 #include "third_party/zlib/zlib.h"
25
26 namespace {
27
28 const int kLogCountLimit = 5;
29 const uint32 kIntermediateCompressionBufferBytes = 256 * 1024; // 256 KB
30 const int kLogListLimitLines = 50;
31
32 const char kUploadURL[] = "https://clients2.google.com/cr/report";
33 const char kUploadContentType[] = "multipart/form-data";
34 const char kMultipartBoundary[] =
35 "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
36
37 const int kHttpResponseOk = 200;
38
39 // Adds the header section for a gzip file to the multipart |post_data|.
AddMultipartFileContentHeader(std::string * post_data,const std::string & content_name)40 void AddMultipartFileContentHeader(std::string* post_data,
41 const std::string& content_name) {
42 post_data->append("--");
43 post_data->append(kMultipartBoundary);
44 post_data->append("\r\nContent-Disposition: form-data; name=\"");
45 post_data->append(content_name);
46 post_data->append("\"; filename=\"");
47 post_data->append(content_name + ".gz");
48 post_data->append("\"\r\nContent-Type: application/gzip\r\n\r\n");
49 }
50
51 // Adds |compressed_log| to |post_data|.
AddLogData(std::string * post_data,const std::vector<uint8> & compressed_log)52 void AddLogData(std::string* post_data,
53 const std::vector<uint8>& compressed_log) {
54 AddMultipartFileContentHeader(post_data, "webrtc_log");
55 post_data->append(reinterpret_cast<const char*>(&compressed_log[0]),
56 compressed_log.size());
57 post_data->append("\r\n");
58 }
59
60 // Adds the RTP dump data to |post_data|.
AddRtpDumpData(std::string * post_data,const std::string & name,const std::string & dump_data)61 void AddRtpDumpData(std::string* post_data,
62 const std::string& name,
63 const std::string& dump_data) {
64 AddMultipartFileContentHeader(post_data, name);
65 post_data->append(dump_data.data(), dump_data.size());
66 post_data->append("\r\n");
67 }
68
69 } // namespace
70
WebRtcLogUploadDoneData()71 WebRtcLogUploadDoneData::WebRtcLogUploadDoneData() {}
72
~WebRtcLogUploadDoneData()73 WebRtcLogUploadDoneData::~WebRtcLogUploadDoneData() {}
74
WebRtcLogUploader()75 WebRtcLogUploader::WebRtcLogUploader()
76 : log_count_(0),
77 post_data_(NULL),
78 shutting_down_(false) {
79 file_thread_checker_.DetachFromThread();
80 }
81
~WebRtcLogUploader()82 WebRtcLogUploader::~WebRtcLogUploader() {
83 DCHECK(create_thread_checker_.CalledOnValidThread());
84 DCHECK(upload_done_data_.empty());
85 DCHECK(shutting_down_);
86 }
87
OnURLFetchComplete(const net::URLFetcher * source)88 void WebRtcLogUploader::OnURLFetchComplete(
89 const net::URLFetcher* source) {
90 DCHECK(create_thread_checker_.CalledOnValidThread());
91 DCHECK(upload_done_data_.find(source) != upload_done_data_.end());
92 DCHECK(!shutting_down_);
93 int response_code = source->GetResponseCode();
94 UploadDoneDataMap::iterator it = upload_done_data_.find(source);
95 if (it != upload_done_data_.end()) {
96 // The log path can be empty here if we failed getting it before. We still
97 // upload the log if that's the case.
98 std::string report_id;
99 if (response_code == kHttpResponseOk &&
100 source->GetResponseAsString(&report_id) &&
101 !it->second.log_path.empty()) {
102 // TODO(jiayl): Add the RTP dump records to chrome://webrtc-logs.
103 base::FilePath log_list_path =
104 WebRtcLogList::GetWebRtcLogListFileForDirectory(it->second.log_path);
105 content::BrowserThread::PostTask(
106 content::BrowserThread::FILE,
107 FROM_HERE,
108 base::Bind(&WebRtcLogUploader::AddUploadedLogInfoToUploadListFile,
109 base::Unretained(this),
110 log_list_path,
111 it->second.local_log_id,
112 report_id));
113 }
114 NotifyUploadDone(response_code, report_id, it->second);
115 upload_done_data_.erase(it);
116 }
117
118 delete source;
119 }
120
OnURLFetchUploadProgress(const net::URLFetcher * source,int64 current,int64 total)121 void WebRtcLogUploader::OnURLFetchUploadProgress(
122 const net::URLFetcher* source, int64 current, int64 total) {
123 }
124
ApplyForStartLogging()125 bool WebRtcLogUploader::ApplyForStartLogging() {
126 DCHECK(create_thread_checker_.CalledOnValidThread());
127 if (log_count_ < kLogCountLimit && !shutting_down_) {
128 ++log_count_;
129 return true;
130 }
131 return false;
132 }
133
LoggingStoppedDontUpload()134 void WebRtcLogUploader::LoggingStoppedDontUpload() {
135 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
136 base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this)));
137 }
138
LoggingStoppedDoUpload(scoped_ptr<unsigned char[]> log_buffer,uint32 length,const std::map<std::string,std::string> & meta_data,const WebRtcLogUploadDoneData & upload_done_data)139 void WebRtcLogUploader::LoggingStoppedDoUpload(
140 scoped_ptr<unsigned char[]> log_buffer,
141 uint32 length,
142 const std::map<std::string, std::string>& meta_data,
143 const WebRtcLogUploadDoneData& upload_done_data) {
144 DCHECK(file_thread_checker_.CalledOnValidThread());
145 DCHECK(log_buffer.get());
146 DCHECK(!upload_done_data.log_path.empty());
147
148 std::vector<uint8> compressed_log;
149 CompressLog(
150 &compressed_log, reinterpret_cast<uint8*>(&log_buffer[0]), length);
151
152 std::string local_log_id;
153
154 if (base::PathExists(upload_done_data.log_path)) {
155 WebRtcLogUtil::DeleteOldWebRtcLogFiles(upload_done_data.log_path);
156
157 local_log_id = base::DoubleToString(base::Time::Now().ToDoubleT());
158 base::FilePath log_file_path =
159 upload_done_data.log_path.AppendASCII(local_log_id)
160 .AddExtension(FILE_PATH_LITERAL(".gz"));
161 WriteCompressedLogToFile(compressed_log, log_file_path);
162
163 base::FilePath log_list_path =
164 WebRtcLogList::GetWebRtcLogListFileForDirectory(
165 upload_done_data.log_path);
166 AddLocallyStoredLogInfoToUploadListFile(log_list_path, local_log_id);
167 }
168
169 WebRtcLogUploadDoneData upload_done_data_with_log_id = upload_done_data;
170 upload_done_data_with_log_id.local_log_id = local_log_id;
171
172 scoped_ptr<std::string> post_data(new std::string());
173 SetupMultipart(post_data.get(),
174 compressed_log,
175 upload_done_data.incoming_rtp_dump,
176 upload_done_data.outgoing_rtp_dump,
177 meta_data);
178
179 // If a test has set the test string pointer, write to it and skip uploading.
180 // Still fire the upload callback so that we can run an extension API test
181 // using the test framework for that without hanging.
182 // TODO(grunell): Remove this when the api test for this feature is fully
183 // implemented according to the test plan. http://crbug.com/257329.
184 if (post_data_) {
185 *post_data_ = *post_data;
186 NotifyUploadDone(kHttpResponseOk, "", upload_done_data_with_log_id);
187 return;
188 }
189
190 content::BrowserThread::PostTask(
191 content::BrowserThread::UI,
192 FROM_HERE,
193 base::Bind(&WebRtcLogUploader::CreateAndStartURLFetcher,
194 base::Unretained(this),
195 upload_done_data_with_log_id,
196 Passed(&post_data)));
197
198 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
199 base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this)));
200 }
201
StartShutdown()202 void WebRtcLogUploader::StartShutdown() {
203 DCHECK(create_thread_checker_.CalledOnValidThread());
204 DCHECK(!shutting_down_);
205
206 // Delete all URLFetchers first and clear the upload done map.
207 for (UploadDoneDataMap::iterator it = upload_done_data_.begin();
208 it != upload_done_data_.end();
209 ++it) {
210 delete it->first;
211 }
212 upload_done_data_.clear();
213 shutting_down_ = true;
214 }
215
SetupMultipart(std::string * post_data,const std::vector<uint8> & compressed_log,const base::FilePath & incoming_rtp_dump,const base::FilePath & outgoing_rtp_dump,const std::map<std::string,std::string> & meta_data)216 void WebRtcLogUploader::SetupMultipart(
217 std::string* post_data,
218 const std::vector<uint8>& compressed_log,
219 const base::FilePath& incoming_rtp_dump,
220 const base::FilePath& outgoing_rtp_dump,
221 const std::map<std::string, std::string>& meta_data) {
222 #if defined(OS_WIN)
223 const char product[] = "Chrome";
224 #elif defined(OS_MACOSX)
225 const char product[] = "Chrome_Mac";
226 #elif defined(OS_LINUX)
227 #if !defined(ADDRESS_SANITIZER)
228 const char product[] = "Chrome_Linux";
229 #else
230 const char product[] = "Chrome_Linux_ASan";
231 #endif
232 #elif defined(OS_ANDROID)
233 const char product[] = "Chrome_Android";
234 #elif defined(OS_CHROMEOS)
235 const char product[] = "Chrome_ChromeOS";
236 #else
237 #error Platform not supported.
238 #endif
239 net::AddMultipartValueForUpload("prod", product, kMultipartBoundary,
240 "", post_data);
241 chrome::VersionInfo version_info;
242 net::AddMultipartValueForUpload("ver", version_info.Version() + "-webrtc",
243 kMultipartBoundary, "", post_data);
244 net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary,
245 "", post_data);
246 net::AddMultipartValueForUpload("type", "webrtc_log", kMultipartBoundary,
247 "", post_data);
248
249 // Add custom meta data.
250 std::map<std::string, std::string>::const_iterator it = meta_data.begin();
251 for (; it != meta_data.end(); ++it) {
252 net::AddMultipartValueForUpload(it->first, it->second, kMultipartBoundary,
253 "", post_data);
254 }
255
256 AddLogData(post_data, compressed_log);
257
258 // Add the rtp dumps if they exist.
259 base::FilePath rtp_dumps[2] = {incoming_rtp_dump, outgoing_rtp_dump};
260 static const char* kRtpDumpNames[2] = {"rtpdump_recv", "rtpdump_send"};
261
262 for (size_t i = 0; i < 2; ++i) {
263 if (!rtp_dumps[i].empty() && base::PathExists(rtp_dumps[i])) {
264 std::string dump_data;
265 if (base::ReadFileToString(rtp_dumps[i], &dump_data))
266 AddRtpDumpData(post_data, kRtpDumpNames[i], dump_data);
267 }
268 }
269
270 net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, post_data);
271 }
272
CompressLog(std::vector<uint8> * compressed_log,uint8 * input,uint32 input_size)273 void WebRtcLogUploader::CompressLog(std::vector<uint8>* compressed_log,
274 uint8* input,
275 uint32 input_size) {
276 PartialCircularBuffer read_pcb(input, input_size);
277
278 z_stream stream = {0};
279 int result = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
280 // windowBits = 15 is default, 16 is added to
281 // produce a gzip header + trailer.
282 15 + 16,
283 8, // memLevel = 8 is default.
284 Z_DEFAULT_STRATEGY);
285 DCHECK_EQ(Z_OK, result);
286
287 uint8 intermediate_buffer[kIntermediateCompressionBufferBytes] = {0};
288 ResizeForNextOutput(compressed_log, &stream);
289 uint32 read = 0;
290
291 do {
292 if (stream.avail_in == 0) {
293 read = read_pcb.Read(&intermediate_buffer[0],
294 kIntermediateCompressionBufferBytes);
295 stream.next_in = &intermediate_buffer[0];
296 stream.avail_in = read;
297 if (read != kIntermediateCompressionBufferBytes)
298 break;
299 }
300 result = deflate(&stream, Z_SYNC_FLUSH);
301 DCHECK_EQ(Z_OK, result);
302 if (stream.avail_out == 0)
303 ResizeForNextOutput(compressed_log, &stream);
304 } while (true);
305
306 // Ensure we have enough room in the output buffer. Easier to always just do a
307 // resize than looping around and resize if needed.
308 if (stream.avail_out < kIntermediateCompressionBufferBytes)
309 ResizeForNextOutput(compressed_log, &stream);
310
311 result = deflate(&stream, Z_FINISH);
312 DCHECK_EQ(Z_STREAM_END, result);
313 result = deflateEnd(&stream);
314 DCHECK_EQ(Z_OK, result);
315
316 compressed_log->resize(compressed_log->size() - stream.avail_out);
317 }
318
ResizeForNextOutput(std::vector<uint8> * compressed_log,z_stream * stream)319 void WebRtcLogUploader::ResizeForNextOutput(std::vector<uint8>* compressed_log,
320 z_stream* stream) {
321 size_t old_size = compressed_log->size() - stream->avail_out;
322 compressed_log->resize(old_size + kIntermediateCompressionBufferBytes);
323 stream->next_out = &(*compressed_log)[old_size];
324 stream->avail_out = kIntermediateCompressionBufferBytes;
325 }
326
CreateAndStartURLFetcher(const WebRtcLogUploadDoneData & upload_done_data,scoped_ptr<std::string> post_data)327 void WebRtcLogUploader::CreateAndStartURLFetcher(
328 const WebRtcLogUploadDoneData& upload_done_data,
329 scoped_ptr<std::string> post_data) {
330 DCHECK(create_thread_checker_.CalledOnValidThread());
331
332 if (shutting_down_)
333 return;
334
335 std::string content_type = kUploadContentType;
336 content_type.append("; boundary=");
337 content_type.append(kMultipartBoundary);
338
339 net::URLFetcher* url_fetcher =
340 net::URLFetcher::Create(GURL(kUploadURL), net::URLFetcher::POST, this);
341 url_fetcher->SetRequestContext(g_browser_process->system_request_context());
342 url_fetcher->SetUploadData(content_type, *post_data);
343 url_fetcher->Start();
344 upload_done_data_[url_fetcher] = upload_done_data;
345 }
346
DecreaseLogCount()347 void WebRtcLogUploader::DecreaseLogCount() {
348 DCHECK(create_thread_checker_.CalledOnValidThread());
349 --log_count_;
350 }
351
WriteCompressedLogToFile(const std::vector<uint8> & compressed_log,const base::FilePath & log_file_path)352 void WebRtcLogUploader::WriteCompressedLogToFile(
353 const std::vector<uint8>& compressed_log,
354 const base::FilePath& log_file_path) {
355 DCHECK(file_thread_checker_.CalledOnValidThread());
356 DCHECK(!compressed_log.empty());
357 base::WriteFile(log_file_path,
358 reinterpret_cast<const char*>(&compressed_log[0]),
359 compressed_log.size());
360 }
361
AddLocallyStoredLogInfoToUploadListFile(const base::FilePath & upload_list_path,const std::string & local_log_id)362 void WebRtcLogUploader::AddLocallyStoredLogInfoToUploadListFile(
363 const base::FilePath& upload_list_path,
364 const std::string& local_log_id) {
365 DCHECK(file_thread_checker_.CalledOnValidThread());
366 DCHECK(!upload_list_path.empty());
367 DCHECK(!local_log_id.empty());
368
369 std::string contents;
370
371 if (base::PathExists(upload_list_path)) {
372 if (!base::ReadFileToString(upload_list_path, &contents)) {
373 DPLOG(WARNING) << "Could not read WebRTC log list file.";
374 return;
375 }
376
377 // Limit the number of log entries to |kLogListLimitLines| - 1, to make room
378 // for the new entry. Each line including the last ends with a '\n', so hit
379 // n will be before line n-1 (from the back).
380 int lf_count = 0;
381 int i = contents.size() - 1;
382 for (; i >= 0 && lf_count < kLogListLimitLines; --i) {
383 if (contents[i] == '\n')
384 ++lf_count;
385 }
386 if (lf_count >= kLogListLimitLines) {
387 // + 1 to compensate for the for loop decrease before the conditional
388 // check and + 1 to get the length.
389 contents.erase(0, i + 2);
390 }
391 }
392
393 // Write the log ID to the log list file. Leave the upload time and report ID
394 // empty.
395 contents += ",," + local_log_id + '\n';
396
397 int written =
398 base::WriteFile(upload_list_path, &contents[0], contents.size());
399 if (written != static_cast<int>(contents.size())) {
400 DPLOG(WARNING) << "Could not write all data to WebRTC log list file: "
401 << written;
402 }
403 }
404
AddUploadedLogInfoToUploadListFile(const base::FilePath & upload_list_path,const std::string & local_log_id,const std::string & report_id)405 void WebRtcLogUploader::AddUploadedLogInfoToUploadListFile(
406 const base::FilePath& upload_list_path,
407 const std::string& local_log_id,
408 const std::string& report_id) {
409 DCHECK(file_thread_checker_.CalledOnValidThread());
410 DCHECK(!upload_list_path.empty());
411 DCHECK(!local_log_id.empty());
412 DCHECK(!report_id.empty());
413
414 std::string contents;
415
416 if (base::PathExists(upload_list_path)) {
417 if (!base::ReadFileToString(upload_list_path, &contents)) {
418 DPLOG(WARNING) << "Could not read WebRTC log list file.";
419 return;
420 }
421 }
422
423 // Write the Unix time and report ID to the log list file. We should be able
424 // to find the local log ID, in that case insert the data into the existing
425 // line. Otherwise add it in the end.
426 base::Time time_now = base::Time::Now();
427 std::string time_now_str = base::DoubleToString(time_now.ToDoubleT());
428 size_t pos = contents.find(",," + local_log_id);
429 if (pos != std::string::npos) {
430 contents.insert(pos, time_now_str);
431 contents.insert(pos + time_now_str.length() + 1, report_id);
432 } else {
433 contents += time_now_str + "," + report_id + ",\n";
434 }
435
436 int written =
437 base::WriteFile(upload_list_path, &contents[0], contents.size());
438 if (written != static_cast<int>(contents.size())) {
439 DPLOG(WARNING) << "Could not write all data to WebRTC log list file: "
440 << written;
441 }
442 }
443
NotifyUploadDone(int response_code,const std::string & report_id,const WebRtcLogUploadDoneData & upload_done_data)444 void WebRtcLogUploader::NotifyUploadDone(
445 int response_code,
446 const std::string& report_id,
447 const WebRtcLogUploadDoneData& upload_done_data) {
448 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
449 base::Bind(&WebRtcLoggingHandlerHost::UploadLogDone,
450 upload_done_data.host));
451 if (!upload_done_data.callback.is_null()) {
452 bool success = response_code == kHttpResponseOk;
453 std::string error_message;
454 if (!success) {
455 error_message = "Uploading failed, response code: " +
456 base::IntToString(response_code);
457 }
458 content::BrowserThread::PostTask(
459 content::BrowserThread::UI, FROM_HERE,
460 base::Bind(upload_done_data.callback, success, report_id,
461 error_message));
462 }
463 }
464