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 31 namespace { 32 33 const int kLogCountLimit = 5; 34 const uint32 kIntermediateCompressionBufferBytes = 256 * 1024; // 256 KB 35 const int kLogListLimitLines = 50; 36 37 const char kUploadURL[] = "https://clients2.google.com/cr/report"; 38 const char kUploadContentType[] = "multipart/form-data"; 39 const char kMultipartBoundary[] = 40 "----**--yradnuoBgoLtrapitluMklaTelgooG--**----"; 41 42 const int kHttpResponseOk = 200; 43 44 } // namespace 45 46 WebRtcLogUploader::WebRtcLogUploader() 47 : log_count_(0), 48 post_data_(NULL) { 49 file_thread_checker_.DetachFromThread(); 50 } 51 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) { 58 delete it->first; 59 } 60 upload_done_data_.clear(); 61 } 62 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, 76 report_id)); 77 } 78 NotifyUploadDone(response_code, report_id, upload_done_data_[source]); 79 upload_done_data_.erase(source); 80 delete source; 81 } 82 83 void WebRtcLogUploader::OnURLFetchUploadProgress( 84 const net::URLFetcher* source, int64 current, int64 total) { 85 } 86 87 bool WebRtcLogUploader::ApplyForStartLogging() { 88 DCHECK(create_thread_checker_.CalledOnValidThread()); 89 if (log_count_ < kLogCountLimit) { 90 ++log_count_; 91 return true; 92 } 93 return false; 94 } 95 96 void WebRtcLogUploader::LoggingStoppedDontUpload() { 97 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 98 base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this))); 99 } 100 101 void WebRtcLogUploader::LoggingStoppedDoUpload( 102 net::URLRequestContextGetter* request_context, 103 scoped_ptr<unsigned char[]> log_buffer, 104 uint32 length, 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()); 110 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()), 115 length, 116 meta_data); 117 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. 123 if (post_data_) { 124 *post_data_ = *post_data; 125 NotifyUploadDone(kHttpResponseOk, "", upload_done_data); 126 return; 127 } 128 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), 134 upload_done_data, 135 Passed(&post_data))); 136 137 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 138 base::Bind(&WebRtcLogUploader::DecreaseLogCount, base::Unretained(this))); 139 } 140 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) { 144 #if defined(OS_WIN) 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"; 151 #else 152 const char product[] = "Chrome_Linux_ASan"; 153 #endif 154 #elif defined(OS_ANDROID) 155 const char product[] = "Chrome_Android"; 156 #elif defined(OS_CHROMEOS) 157 const char product[] = "Chrome_ChromeOS"; 158 #else 159 // This file should not be compiled for other platforms. 160 COMPILE_ASSERT(false); 161 #endif 162 net::AddMultipartValueForUpload("prod", product, kMultipartBoundary, 163 "", post_data); 164 chrome::VersionInfo version_info; 165 net::AddMultipartValueForUpload("ver", version_info.Version(), 166 kMultipartBoundary, "", post_data); 167 net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary, 168 "", post_data); 169 net::AddMultipartValueForUpload("type", "webrtc_log", kMultipartBoundary, 170 "", post_data); 171 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, 176 "", post_data); 177 } 178 179 AddLogData(post_data, log_buffer, log_buffer_length); 180 net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, post_data); 181 } 182 183 void WebRtcLogUploader::AddLogData(std::string* post_data, 184 uint8* log_buffer, 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"); 192 193 CompressLog(post_data, log_buffer, log_buffer_length); 194 195 post_data->append("\r\n"); 196 } 197 198 void WebRtcLogUploader::CompressLog(std::string* post_data, 199 uint8* input, 200 uint32 input_size) { 201 PartialCircularBuffer read_pcb(input, input_size); 202 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. 207 15 + 16, 208 8, // memLevel = 8 is default. 209 Z_DEFAULT_STRATEGY); 210 DCHECK_EQ(Z_OK, result); 211 212 uint8 intermediate_buffer[kIntermediateCompressionBufferBytes] = {0}; 213 ResizeForNextOutput(post_data, &stream); 214 uint32 read = 0; 215 216 do { 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) 223 break; 224 } 225 result = deflate(&stream, Z_SYNC_FLUSH); 226 DCHECK_EQ(Z_OK, result); 227 if (stream.avail_out == 0) 228 ResizeForNextOutput(post_data, &stream); 229 } while (true); 230 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); 235 236 result = deflate(&stream, Z_FINISH); 237 DCHECK_EQ(Z_STREAM_END, result); 238 result = deflateEnd(&stream); 239 DCHECK_EQ(Z_OK, result); 240 241 post_data->resize(post_data->size() - stream.avail_out); 242 } 243 244 void WebRtcLogUploader::ResizeForNextOutput(std::string* post_data, 245 z_stream* stream) { 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; 250 } 251 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); 260 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; 267 } 268 269 void WebRtcLogUploader::DecreaseLogCount() { 270 DCHECK(create_thread_checker_.CalledOnValidThread()); 271 --log_count_; 272 } 273 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; 279 280 if (base::PathExists(upload_list_path)) { 281 bool read_ok = base::ReadFileToString(upload_list_path, &contents); 282 DPCHECK(read_ok); 283 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). 287 int lf_count = 0; 288 int i = contents.size() - 1; 289 for (; i >= 0 && lf_count < kLogListLimitLines; --i) { 290 if (contents[i] == '\n') 291 ++lf_count; 292 } 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); 297 } 298 } 299 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'; 304 305 int written = file_util::WriteFile(upload_list_path, &contents[0], 306 contents.size()); 307 DPCHECK(written == static_cast<int>(contents.size())); 308 } 309 310 void WebRtcLogUploader::NotifyUploadDone( 311 int response_code, 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; 320 if (!success) { 321 error_message = "Uploading failed, response code: " + 322 base::IntToString(response_code); 323 } 324 content::BrowserThread::PostTask( 325 content::BrowserThread::UI, FROM_HERE, 326 base::Bind(upload_done_data.callback, success, report_id, 327 error_message)); 328 } 329 } 330