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.
5 #include "chrome/browser/media/webrtc_log_uploader.h"
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"
33 const int kLogCountLimit
= 5;
34 const uint32 kIntermediateCompressionBufferBytes
= 256 * 1024; // 256 KB
35 const int kLogListLimitLines
= 50;
37 const char kUploadURL
[] = "https://clients2.google.com/cr/report";
38 const char kUploadContentType
[] = "multipart/form-data";
39 const char kMultipartBoundary
[] =
40 "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
42 const int kHttpResponseOk
= 200;
46 WebRtcLogUploader::WebRtcLogUploader()
49 file_thread_checker_
.DetachFromThread();
52 WebRtcLogUploader::~WebRtcLogUploader() {
53 DCHECK(create_thread_checker_
.CalledOnValidThread());
54 // Delete all pending URLFetcher and release all references to
55 // WebRtcLoggingHandlerHost.
56 for (UploadDoneDataMap::iterator it
= upload_done_data_
.begin();
57 it
!= upload_done_data_
.end(); ++it
) {
60 upload_done_data_
.clear();
63 void WebRtcLogUploader::OnURLFetchComplete(
64 const net::URLFetcher
* source
) {
65 DCHECK(create_thread_checker_
.CalledOnValidThread());
66 DCHECK(upload_done_data_
.find(source
) != upload_done_data_
.end());
67 int response_code
= source
->GetResponseCode();
68 std::string report_id
;
69 if (response_code
== kHttpResponseOk
&&
70 source
->GetResponseAsString(&report_id
)) {
71 content::BrowserThread::PostTask(
72 content::BrowserThread::FILE, FROM_HERE
,
73 base::Bind(&WebRtcLogUploader::AddUploadedLogInfoToUploadListFile
,
74 base::Unretained(this),
75 upload_done_data_
[source
].upload_list_path
,
78 NotifyUploadDone(response_code
, report_id
, upload_done_data_
[source
]);
79 upload_done_data_
.erase(source
);
83 void WebRtcLogUploader::OnURLFetchUploadProgress(
84 const net::URLFetcher
* source
, int64 current
, int64 total
) {
87 bool WebRtcLogUploader::ApplyForStartLogging() {
88 DCHECK(create_thread_checker_
.CalledOnValidThread());
89 if (log_count_
< kLogCountLimit
) {
96 void WebRtcLogUploader::LoggingStoppedDontUpload() {
97 content::BrowserThread::PostTask(content::BrowserThread::UI
, FROM_HERE
,
98 base::Bind(&WebRtcLogUploader::DecreaseLogCount
, base::Unretained(this)));
101 void WebRtcLogUploader::LoggingStoppedDoUpload(
102 net::URLRequestContextGetter
* request_context
,
103 scoped_ptr
<unsigned char[]> log_buffer
,
105 const std::map
<std::string
, std::string
>& meta_data
,
106 const WebRtcLogUploadDoneData
& upload_done_data
) {
107 DCHECK(file_thread_checker_
.CalledOnValidThread());
108 DCHECK(log_buffer
.get());
109 DCHECK(!upload_done_data
.upload_list_path
.empty());
111 scoped_ptr
<std::string
> post_data
;
112 post_data
.reset(new std::string
);
113 SetupMultipart(post_data
.get(),
114 reinterpret_cast<uint8
*>(log_buffer
.get()),
118 // If a test has set the test string pointer, write to it and skip uploading.
119 // Still fire the upload callback so that we can run an extension API test
120 // using the test framework for that without hanging.
121 // TODO(grunell): Remove this when the api test for this feature is fully
122 // implemented according to the test plan. http://crbug.com/257329.
124 *post_data_
= *post_data
;
125 NotifyUploadDone(kHttpResponseOk
, "", upload_done_data
);
129 content::BrowserThread::PostTask(
130 content::BrowserThread::UI
, FROM_HERE
,
131 base::Bind(&WebRtcLogUploader::CreateAndStartURLFetcher
,
132 base::Unretained(this),
133 make_scoped_refptr(request_context
),
135 Passed(&post_data
)));
137 content::BrowserThread::PostTask(content::BrowserThread::UI
, FROM_HERE
,
138 base::Bind(&WebRtcLogUploader::DecreaseLogCount
, base::Unretained(this)));
141 void WebRtcLogUploader::SetupMultipart(
142 std::string
* post_data
, uint8
* log_buffer
, uint32 log_buffer_length
,
143 const std::map
<std::string
, std::string
>& meta_data
) {
145 const char product
[] = "Chrome";
146 #elif defined(OS_MACOSX)
147 const char product
[] = "Chrome_Mac";
148 #elif defined(OS_LINUX)
149 #if !defined(ADDRESS_SANITIZER)
150 const char product
[] = "Chrome_Linux";
152 const char product
[] = "Chrome_Linux_ASan";
154 #elif defined(OS_ANDROID)
155 const char product
[] = "Chrome_Android";
156 #elif defined(OS_CHROMEOS)
157 const char product
[] = "Chrome_ChromeOS";
159 // This file should not be compiled for other platforms.
160 COMPILE_ASSERT(false);
162 net::AddMultipartValueForUpload("prod", product
, kMultipartBoundary
,
164 chrome::VersionInfo version_info
;
165 net::AddMultipartValueForUpload("ver", version_info
.Version(),
166 kMultipartBoundary
, "", post_data
);
167 net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary
,
169 net::AddMultipartValueForUpload("type", "webrtc_log", kMultipartBoundary
,
172 // Add custom meta data.
173 std::map
<std::string
, std::string
>::const_iterator it
= meta_data
.begin();
174 for (; it
!= meta_data
.end(); ++it
) {
175 net::AddMultipartValueForUpload(it
->first
, it
->second
, kMultipartBoundary
,
179 AddLogData(post_data
, log_buffer
, log_buffer_length
);
180 net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary
, post_data
);
183 void WebRtcLogUploader::AddLogData(std::string
* post_data
,
185 uint32 log_buffer_length
) {
186 post_data
->append("--");
187 post_data
->append(kMultipartBoundary
);
188 post_data
->append("\r\n");
189 post_data
->append("Content-Disposition: form-data; name=\"webrtc_log\"");
190 post_data
->append("; filename=\"webrtc_log.gz\"\r\n");
191 post_data
->append("Content-Type: application/gzip\r\n\r\n");
193 CompressLog(post_data
, log_buffer
, log_buffer_length
);
195 post_data
->append("\r\n");
198 void WebRtcLogUploader::CompressLog(std::string
* post_data
,
201 PartialCircularBuffer
read_pcb(input
, input_size
);
203 z_stream stream
= {0};
204 int result
= deflateInit2(&stream
, Z_DEFAULT_COMPRESSION
, Z_DEFLATED
,
205 // windowBits = 15 is default, 16 is added to
206 // produce a gzip header + trailer.
208 8, // memLevel = 8 is default.
210 DCHECK_EQ(Z_OK
, result
);
212 uint8 intermediate_buffer
[kIntermediateCompressionBufferBytes
] = {0};
213 ResizeForNextOutput(post_data
, &stream
);
217 if (stream
.avail_in
== 0) {
218 read
= read_pcb
.Read(&intermediate_buffer
[0],
219 kIntermediateCompressionBufferBytes
);
220 stream
.next_in
= &intermediate_buffer
[0];
221 stream
.avail_in
= read
;
222 if (read
!= kIntermediateCompressionBufferBytes
)
225 result
= deflate(&stream
, Z_SYNC_FLUSH
);
226 DCHECK_EQ(Z_OK
, result
);
227 if (stream
.avail_out
== 0)
228 ResizeForNextOutput(post_data
, &stream
);
231 // Ensure we have enough room in the output buffer. Easier to always just do a
232 // resize than looping around and resize if needed.
233 if (stream
.avail_out
< kIntermediateCompressionBufferBytes
)
234 ResizeForNextOutput(post_data
, &stream
);
236 result
= deflate(&stream
, Z_FINISH
);
237 DCHECK_EQ(Z_STREAM_END
, result
);
238 result
= deflateEnd(&stream
);
239 DCHECK_EQ(Z_OK
, result
);
241 post_data
->resize(post_data
->size() - stream
.avail_out
);
244 void WebRtcLogUploader::ResizeForNextOutput(std::string
* post_data
,
246 size_t old_size
= post_data
->size() - stream
->avail_out
;
247 post_data
->resize(old_size
+ kIntermediateCompressionBufferBytes
);
248 stream
->next_out
= reinterpret_cast<uint8
*>(&(*post_data
)[old_size
]);
249 stream
->avail_out
= kIntermediateCompressionBufferBytes
;
252 void WebRtcLogUploader::CreateAndStartURLFetcher(
253 scoped_refptr
<net::URLRequestContextGetter
> request_context
,
254 const WebRtcLogUploadDoneData
& upload_done_data
,
255 scoped_ptr
<std::string
> post_data
) {
256 DCHECK(create_thread_checker_
.CalledOnValidThread());
257 std::string content_type
= kUploadContentType
;
258 content_type
.append("; boundary=");
259 content_type
.append(kMultipartBoundary
);
261 net::URLFetcher
* url_fetcher
=
262 net::URLFetcher::Create(GURL(kUploadURL
), net::URLFetcher::POST
, this);
263 url_fetcher
->SetRequestContext(request_context
);
264 url_fetcher
->SetUploadData(content_type
, *post_data
);
265 url_fetcher
->Start();
266 upload_done_data_
[url_fetcher
] = upload_done_data
;
269 void WebRtcLogUploader::DecreaseLogCount() {
270 DCHECK(create_thread_checker_
.CalledOnValidThread());
274 void WebRtcLogUploader::AddUploadedLogInfoToUploadListFile(
275 const base::FilePath
& upload_list_path
,
276 const std::string
& report_id
) {
277 DCHECK(file_thread_checker_
.CalledOnValidThread());
278 std::string contents
;
280 if (base::PathExists(upload_list_path
)) {
281 bool read_ok
= base::ReadFileToString(upload_list_path
, &contents
);
284 // Limit the number of log entries to |kLogListLimitLines| - 1, to make room
285 // for the new entry. Each line including the last ends with a '\n', so hit
286 // n will be before line n-1 (from the back).
288 int i
= contents
.size() - 1;
289 for (; i
>= 0 && lf_count
< kLogListLimitLines
; --i
) {
290 if (contents
[i
] == '\n')
293 if (lf_count
>= kLogListLimitLines
) {
294 // + 1 to compensate for the for loop decrease before the conditional
295 // check and + 1 to get the length.
296 contents
.erase(0, i
+ 2);
300 // Write the Unix time and report ID to the log list file.
301 base::Time time_now
= base::Time::Now();
302 contents
+= base::DoubleToString(time_now
.ToDoubleT()) +
303 "," + report_id
+ '\n';
305 int written
= file_util::WriteFile(upload_list_path
, &contents
[0],
307 DPCHECK(written
== static_cast<int>(contents
.size()));
310 void WebRtcLogUploader::NotifyUploadDone(
312 const std::string
& report_id
,
313 const WebRtcLogUploadDoneData
& upload_done_data
) {
314 content::BrowserThread::PostTask(content::BrowserThread::IO
, FROM_HERE
,
315 base::Bind(&WebRtcLoggingHandlerHost::UploadLogDone
,
316 upload_done_data
.host
));
317 if (!upload_done_data
.callback
.is_null()) {
318 bool success
= response_code
== kHttpResponseOk
;
319 std::string error_message
;
321 error_message
= "Uploading failed, response code: " +
322 base::IntToString(response_code
);
324 content::BrowserThread::PostTask(
325 content::BrowserThread::UI
, FROM_HERE
,
326 base::Bind(upload_done_data
.callback
, success
, report_id
,