1 // Copyright 2018 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/webrtc_event_log_manager_common.h"
6 
7 #include <cctype>
8 #include <limits>
9 
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/memory/scoped_refptr.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/stl_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_piece.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/threading/sequenced_task_runner_handle.h"
19 #include "base/unguessable_token.h"
20 #include "chrome/browser/policy/profile_policy_connector.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "components/policy/core/common/policy_service.h"
23 #include "content/public/browser/browser_context.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/render_process_host.h"
26 #include "third_party/zlib/zlib.h"
27 
28 #if defined OS_CHROMEOS
29 #include "chrome/browser/chromeos/profiles/profile_helper.h"
30 #endif
31 
32 namespace webrtc_event_logging {
33 
34 using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
35 
36 const size_t kWebRtcEventLogManagerUnlimitedFileSize = 0;
37 
38 const size_t kWebRtcEventLogIdLength = 32;
39 
40 // Be careful not to change these without updating the number of characters
41 // reserved in the filename. See kWebAppIdLength.
42 const size_t kMinWebRtcEventLogWebAppId = 1;
43 const size_t kMaxWebRtcEventLogWebAppId = 99;
44 
45 // Sentinel value for an invalid web-app ID.
46 const size_t kInvalidWebRtcEventLogWebAppId = 0;
47 static_assert(kInvalidWebRtcEventLogWebAppId < kMinWebRtcEventLogWebAppId ||
48                   kInvalidWebRtcEventLogWebAppId > kMaxWebRtcEventLogWebAppId,
49               "Sentinel value must be distinct from legal values.");
50 
51 const char kRemoteBoundWebRtcEventLogFileNamePrefix[] = "webrtc_event_log";
52 
53 // Important! These values may be relied on by web-apps. Do not change.
54 const char kStartRemoteLoggingFailureAlreadyLogging[] = "Already logging.";
55 const char kStartRemoteLoggingFailureDeadRenderProcessHost[] =
56     "RPH already dead.";
57 const char kStartRemoteLoggingFailureFeatureDisabled[] = "Feature disabled.";
58 const char kStartRemoteLoggingFailureFileCreationError[] =
59     "Could not create file.";
60 const char kStartRemoteLoggingFailureFilePathUsedHistory[] =
61     "Used history file path.";
62 const char kStartRemoteLoggingFailureFilePathUsedLog[] = "Used log file path.";
63 const char kStartRemoteLoggingFailureIllegalWebAppId[] = "Illegal web-app ID.";
64 const char kStartRemoteLoggingFailureLoggingDisabledBrowserContext[] =
65     "Disabled for browser context.";
66 const char kStartRemoteLoggingFailureMaxSizeTooLarge[] =
67     "Excessively large max log size.";
68 const char kStartRemoteLoggingFailureMaxSizeTooSmall[] = "Max size too small.";
69 const char kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed[] =
70     "No additional active logs allowed.";
71 const char kStartRemoteLoggingFailureOutputPeriodMsTooLarge[] =
72     "Excessively large output period (ms).";
73 const char kStartRemoteLoggingFailureUnknownOrInactivePeerConnection[] =
74     "Unknown or inactive peer connection.";
75 const char kStartRemoteLoggingFailureUnlimitedSizeDisallowed[] =
76     "Unlimited size disallowed.";
77 
78 const BrowserContextId kNullBrowserContextId =
79     reinterpret_cast<BrowserContextId>(nullptr);
80 
UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result)81 void UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result) {
82   base::UmaHistogramEnumeration("WebRtcEventLogging.Api", result);
83 }
84 
UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result)85 void UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result) {
86   base::UmaHistogramEnumeration("WebRtcEventLogging.Upload", result);
87 }
88 
UmaRecordWebRtcEventLoggingNetErrorType(int net_error)89 void UmaRecordWebRtcEventLoggingNetErrorType(int net_error) {
90   base::UmaHistogramSparse("WebRtcEventLogging.NetError", net_error);
91 }
92 
93 namespace {
94 
95 constexpr int kDefaultMemLevel = 8;
96 
97 constexpr size_t kGzipHeaderBytes = 15;
98 constexpr size_t kGzipFooterBytes = 10;
99 
100 constexpr size_t kWebAppIdLength = 2;
101 
102 // Tracks budget over a resource (such as bytes allowed in a file, etc.).
103 // Allows an unlimited budget.
104 class Budget {
105  public:
106   // If !max.has_value(), the budget is unlimited.
Budget(base::Optional<size_t> max)107   explicit Budget(base::Optional<size_t> max) : max_(max), current_(0) {}
108 
109   // Check whether the budget allows consuming an additional |consumed| of
110   // the resource.
ConsumeAllowed(size_t consumed) const111   bool ConsumeAllowed(size_t consumed) const {
112     if (!max_.has_value()) {
113       return true;
114     }
115 
116     DCHECK_LE(current_, max_.value());
117 
118     const size_t after_consumption = current_ + consumed;
119 
120     if (after_consumption < current_) {
121       return false;  // Wrap-around.
122     } else if (after_consumption > max_.value()) {
123       return false;  // Budget exceeded.
124     } else {
125       return true;
126     }
127   }
128 
129   // Checks whether the budget has been completely used up.
Exhausted() const130   bool Exhausted() const { return !ConsumeAllowed(0); }
131 
132   // Consume an additional |consumed| of the resource.
Consume(size_t consumed)133   void Consume(size_t consumed) {
134     DCHECK(ConsumeAllowed(consumed));
135     current_ += consumed;
136   }
137 
138  private:
139   const base::Optional<size_t> max_;
140   size_t current_;
141 };
142 
143 // Writes a log to a file while observing a maximum size.
144 class BaseLogFileWriter : public LogFileWriter {
145  public:
146   // If !max_file_size_bytes.has_value(), an unlimited writer is created.
147   // If it has a value, it must be at least MinFileSizeBytes().
148   BaseLogFileWriter(const base::FilePath& path,
149                     base::Optional<size_t> max_file_size_bytes);
150 
151   ~BaseLogFileWriter() override;
152 
153   bool Init() override;
154 
155   const base::FilePath& path() const override;
156 
157   bool MaxSizeReached() const override;
158 
159   bool Write(const std::string& input) override;
160 
161   bool Close() override;
162 
163   void Delete() override;
164 
165  protected:
166   // * Logs are created PRE_INIT.
167   // * If Init() is successful (potentially writing some header to the log),
168   //   the log becomes ACTIVE.
169   // * Any error puts the log into an unrecoverable ERRORED state. When an
170   //   errored file is Close()-ed, it is deleted.
171   // * If Write() is ever denied because of budget constraintss, the file
172   //   becomes FULL. Only metadata is then allowed (subject to its own budget).
173   // * Closing an ACTIVE or FULL file puts it into CLOSED, at which point the
174   //   file may be used. (Note that closing itself might also yield an error,
175   //   which would put the file into ERRORED, then deleted.)
176   // * Closed files may be DELETED.
177   enum class State { PRE_INIT, ACTIVE, FULL, CLOSED, ERRORED, DELETED };
178 
179   // Setter/getter for |state_|.
180   void SetState(State state);
state() const181   State state() const { return state_; }
182 
183   // Checks whether the budget allows writing an additional |bytes|.
184   bool WithinBudget(size_t bytes) const;
185 
186   // Writes |input| to the file.
187   // May only be called on ACTIVE or FULL files (for FULL files, only metadata
188   // such as compression footers, etc., may be written; the budget must still
189   // be respected).
190   // It's up to the caller to respect the budget; this will DCHECK on it.
191   // Returns |true| if writing was successful. |false| indicates an
192   // unrecoverable error; the file must be discarded.
193   bool WriteInternal(const std::string& input, bool metadata);
194 
195   // Finalizes the file (writes metadata such as compression footer, if any).
196   // Reports whether the file was successfully finalized. Those which weren't
197   // should be discarded.
198   virtual bool Finalize();
199 
200  private:
201   scoped_refptr<base::SequencedTaskRunner> task_runner_;
202   const base::FilePath path_;
203   base::File file_;  // Populated by Init().
204   State state_;
205   Budget budget_;
206 };
207 
BaseLogFileWriter(const base::FilePath & path,base::Optional<size_t> max_file_size_bytes)208 BaseLogFileWriter::BaseLogFileWriter(const base::FilePath& path,
209                                      base::Optional<size_t> max_file_size_bytes)
210     : task_runner_(base::SequencedTaskRunnerHandle::Get()),
211       path_(path),
212       state_(State::PRE_INIT),
213       budget_(max_file_size_bytes) {}
214 
~BaseLogFileWriter()215 BaseLogFileWriter::~BaseLogFileWriter() {
216   if (!task_runner_->RunsTasksInCurrentSequence()) {
217     // Chrome shut-down. The original task_runner_ is no longer running, so
218     // no risk of concurrent access or races.
219     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
220     task_runner_ = base::SequencedTaskRunnerHandle::Get();
221   }
222 
223   if (state() != State::CLOSED && state() != State::DELETED) {
224     Close();
225   }
226 }
227 
Init()228 bool BaseLogFileWriter::Init() {
229   DCHECK(task_runner_->RunsTasksInCurrentSequence());
230   DCHECK_EQ(state(), State::PRE_INIT);
231 
232   // TODO(crbug.com/775415): Use a temporary filename which will indicate
233   // incompletion, and rename to something that is eligible for upload only
234   // on an orderly and successful Close().
235 
236   // Attempt to create the file.
237   constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
238                              base::File::FLAG_EXCLUSIVE_WRITE;
239   file_.Initialize(path_, file_flags);
240   if (!file_.IsValid() || !file_.created()) {
241     LOG(WARNING) << "Couldn't create remote-bound WebRTC event log file.";
242     if (!base::DeleteFile(path_)) {
243       LOG(ERROR) << "Failed to delete " << path_ << ".";
244     }
245     SetState(State::ERRORED);
246     return false;
247   }
248 
249   SetState(State::ACTIVE);
250 
251   return true;
252 }
253 
path() const254 const base::FilePath& BaseLogFileWriter::path() const {
255   DCHECK(task_runner_->RunsTasksInCurrentSequence());
256   return path_;
257 }
258 
MaxSizeReached() const259 bool BaseLogFileWriter::MaxSizeReached() const {
260   DCHECK(task_runner_->RunsTasksInCurrentSequence());
261   DCHECK_EQ(state(), State::ACTIVE);
262   return !WithinBudget(1);
263 }
264 
Write(const std::string & input)265 bool BaseLogFileWriter::Write(const std::string& input) {
266   DCHECK(task_runner_->RunsTasksInCurrentSequence());
267   DCHECK_EQ(state(), State::ACTIVE);
268   DCHECK(!MaxSizeReached());
269 
270   if (input.empty()) {
271     return true;
272   }
273 
274   if (!WithinBudget(input.length())) {
275     SetState(State::FULL);
276     return false;
277   }
278 
279   const bool did_write = WriteInternal(input, /*metadata=*/false);
280   if (!did_write) {
281     SetState(State::ERRORED);
282   }
283   return did_write;
284 }
285 
Close()286 bool BaseLogFileWriter::Close() {
287   DCHECK(task_runner_->RunsTasksInCurrentSequence());
288   DCHECK_NE(state(), State::CLOSED);
289   DCHECK_NE(state(), State::DELETED);
290 
291   const bool result = ((state() != State::ERRORED) && Finalize());
292 
293   if (result) {
294     file_.Flush();
295     file_.Close();
296     SetState(State::CLOSED);
297   } else {
298     Delete();  // Changes the state to DELETED.
299   }
300 
301   return result;
302 }
303 
Delete()304 void BaseLogFileWriter::Delete() {
305   DCHECK(task_runner_->RunsTasksInCurrentSequence());
306   DCHECK_NE(state(), State::DELETED);
307 
308   // The file should be closed before deletion. However, we do not want to go
309   // through Finalize() and any potential production of a compression footer,
310   // etc., since we'll be discarding the file anyway.
311   if (state() != State::CLOSED) {
312     file_.Close();
313   }
314 
315   if (!base::DeleteFile(path_)) {
316     LOG(ERROR) << "Failed to delete " << path_ << ".";
317   }
318 
319   SetState(State::DELETED);
320 }
321 
SetState(State state)322 void BaseLogFileWriter::SetState(State state) {
323   DCHECK(task_runner_->RunsTasksInCurrentSequence());
324   state_ = state;
325 }
326 
WithinBudget(size_t bytes) const327 bool BaseLogFileWriter::WithinBudget(size_t bytes) const {
328   DCHECK(task_runner_->RunsTasksInCurrentSequence());
329   return budget_.ConsumeAllowed(bytes);
330 }
331 
WriteInternal(const std::string & input,bool metadata)332 bool BaseLogFileWriter::WriteInternal(const std::string& input, bool metadata) {
333   DCHECK(task_runner_->RunsTasksInCurrentSequence());
334   DCHECK(state() == State::ACTIVE || (state() == State::FULL && metadata));
335   DCHECK(WithinBudget(input.length()));
336 
337   // base::File's interface does not allow writing more than
338   // numeric_limits<int>::max() bytes at a time.
339   DCHECK_LE(input.length(),
340             static_cast<size_t>(std::numeric_limits<int>::max()));
341   const int input_len = static_cast<int>(input.length());
342 
343   int written = file_.WriteAtCurrentPos(input.c_str(), input_len);
344   if (written != input_len) {
345     LOG(WARNING) << "WebRTC event log couldn't be written to the "
346                     "locally stored file in its entirety.";
347     return false;
348   }
349 
350   budget_.Consume(static_cast<size_t>(written));
351 
352   return true;
353 }
354 
Finalize()355 bool BaseLogFileWriter::Finalize() {
356   DCHECK(task_runner_->RunsTasksInCurrentSequence());
357   DCHECK_NE(state(), State::CLOSED);
358   DCHECK_NE(state(), State::DELETED);
359   DCHECK_NE(state(), State::ERRORED);
360   return true;
361 }
362 
363 // Writes a GZIP-compressed log to a file while observing a maximum size.
364 class GzippedLogFileWriter : public BaseLogFileWriter {
365  public:
366   GzippedLogFileWriter(const base::FilePath& path,
367                        base::Optional<size_t> max_file_size_bytes,
368                        std::unique_ptr<LogCompressor> compressor);
369 
370   ~GzippedLogFileWriter() override = default;
371 
372   bool Init() override;
373 
374   bool MaxSizeReached() const override;
375 
376   bool Write(const std::string& input) override;
377 
378  protected:
379   bool Finalize() override;
380 
381  private:
382   std::unique_ptr<LogCompressor> compressor_;
383 };
384 
GzippedLogFileWriter(const base::FilePath & path,base::Optional<size_t> max_file_size_bytes,std::unique_ptr<LogCompressor> compressor)385 GzippedLogFileWriter::GzippedLogFileWriter(
386     const base::FilePath& path,
387     base::Optional<size_t> max_file_size_bytes,
388     std::unique_ptr<LogCompressor> compressor)
389     : BaseLogFileWriter(path, max_file_size_bytes),
390       compressor_(std::move(compressor)) {
391   // Factory validates size before instantiation.
392   DCHECK(!max_file_size_bytes.has_value() ||
393          max_file_size_bytes.value() >= kGzipOverheadBytes);
394 }
395 
Init()396 bool GzippedLogFileWriter::Init() {
397   if (!BaseLogFileWriter::Init()) {
398     // Super-class should SetState on its own.
399     return false;
400   }
401 
402   std::string header;
403   compressor_->CreateHeader(&header);
404 
405   const bool result = WriteInternal(header, /*metadata=*/true);
406   if (!result) {
407     SetState(State::ERRORED);
408   }
409 
410   return result;
411 }
412 
MaxSizeReached() const413 bool GzippedLogFileWriter::MaxSizeReached() const {
414   DCHECK_EQ(state(), State::ACTIVE);
415 
416   // Note that the overhead used (footer only) assumes state() is State::ACTIVE,
417   // as DCHECKed above.
418   return !WithinBudget(1 + kGzipFooterBytes);
419 }
420 
Write(const std::string & input)421 bool GzippedLogFileWriter::Write(const std::string& input) {
422   DCHECK_EQ(state(), State::ACTIVE);
423   DCHECK(!MaxSizeReached());
424 
425   if (input.empty()) {
426     return true;
427   }
428 
429   std::string compressed_input;
430   const auto result = compressor_->Compress(input, &compressed_input);
431 
432   switch (result) {
433     case LogCompressor::Result::OK: {
434       // |compressor_| guarantees |compressed_input| is within-budget.
435       bool did_write = WriteInternal(compressed_input, /*metadata=*/false);
436       if (!did_write) {
437         SetState(State::ERRORED);
438       }
439       return did_write;
440     }
441     case LogCompressor::Result::DISALLOWED: {
442       SetState(State::FULL);
443       return false;
444     }
445     case LogCompressor::Result::ERROR_ENCOUNTERED: {
446       SetState(State::ERRORED);
447       return false;
448     }
449   }
450 
451   NOTREACHED();
452   return false;  // Appease compiler.
453 }
454 
Finalize()455 bool GzippedLogFileWriter::Finalize() {
456   DCHECK_NE(state(), State::CLOSED);
457   DCHECK_NE(state(), State::DELETED);
458   DCHECK_NE(state(), State::ERRORED);
459 
460   std::string footer;
461   if (!compressor_->CreateFooter(&footer)) {
462     LOG(WARNING) << "Compression footer could not be produced.";
463     SetState(State::ERRORED);
464     return false;
465   }
466 
467   // |compressor_| guarantees |footer| is within-budget.
468   if (!WriteInternal(footer, /*metadata=*/true)) {
469     LOG(WARNING) << "Footer could not be written.";
470     SetState(State::ERRORED);
471     return false;
472   }
473 
474   return true;
475 }
476 
477 // Concrete implementation of LogCompressor using GZIP.
478 class GzipLogCompressor : public LogCompressor {
479  public:
480   GzipLogCompressor(
481       base::Optional<size_t> max_size_bytes,
482       std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator);
483 
484   ~GzipLogCompressor() override;
485 
486   void CreateHeader(std::string* output) override;
487 
488   Result Compress(const std::string& input, std::string* output) override;
489 
490   bool CreateFooter(std::string* output) override;
491 
492  private:
493   // * A compressed log starts out empty (PRE_HEADER).
494   // * Once the header is produced, the stream is ACTIVE.
495   // * If it is ever detected that compressing the next input would exceed the
496   //   budget, that input is NOT compressed, and the state becomes FULL, from
497   //   which only writing the footer or discarding the file are allowed.
498   // * Writing the footer is allowed on an ACTIVE or FULL stream. Then, the
499   //   stream is effectively closed.
500   // * Any error puts the stream into ERRORED. An errored stream can only
501   //   be discarded.
502   enum class State { PRE_HEADER, ACTIVE, FULL, POST_FOOTER, ERRORED };
503 
504   // Returns the budget left after reserving the GZIP overhead.
505   // Optionals without a value, both in the parameters as well as in the
506   // return value of the function, signal an unlimited amount.
507   static base::Optional<size_t> SizeAfterOverheadReservation(
508       base::Optional<size_t> max_size_bytes);
509 
510   // Compresses |input| into |output|, while observing the budget (unless
511   // !budgeted). If |last|, also closes the stream.
512   Result CompressInternal(const std::string& input,
513                           std::string* output,
514                           bool budgeted,
515                           bool last);
516 
517   // Compresses the input data already in |stream_| into |output|.
518   bool Deflate(int flush, std::string* output);
519 
520   State state_;
521   Budget budget_;
522   std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator_;
523   z_stream stream_;
524 };
525 
GzipLogCompressor(base::Optional<size_t> max_size_bytes,std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator)526 GzipLogCompressor::GzipLogCompressor(
527     base::Optional<size_t> max_size_bytes,
528     std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator)
529     : state_(State::PRE_HEADER),
530       budget_(SizeAfterOverheadReservation(max_size_bytes)),
531       compressed_size_estimator_(std::move(compressed_size_estimator)) {
532   memset(&stream_, 0, sizeof(z_stream));
533   // Using (MAX_WBITS + 16) triggers the creation of a GZIP header.
534   const int result =
535       deflateInit2(&stream_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16,
536                    kDefaultMemLevel, Z_DEFAULT_STRATEGY);
537   DCHECK_EQ(result, Z_OK);
538 }
539 
~GzipLogCompressor()540 GzipLogCompressor::~GzipLogCompressor() {
541   const int result = deflateEnd(&stream_);
542   // Z_DATA_ERROR reports that the stream was not properly terminated,
543   // but nevertheless correctly released. That happens when we don't
544   // write the footer.
545   DCHECK(result == Z_OK ||
546          (result == Z_DATA_ERROR && state_ != State::POST_FOOTER));
547 }
548 
CreateHeader(std::string * output)549 void GzipLogCompressor::CreateHeader(std::string* output) {
550   DCHECK(output);
551   DCHECK(output->empty());
552   DCHECK_EQ(state_, State::PRE_HEADER);
553 
554   const Result result = CompressInternal(std::string(), output,
555                                          /*budgeted=*/false, /*last=*/false);
556   DCHECK_EQ(result, Result::OK);
557   DCHECK_EQ(output->size(), kGzipHeaderBytes);
558 
559   state_ = State::ACTIVE;
560 }
561 
Compress(const std::string & input,std::string * output)562 LogCompressor::Result GzipLogCompressor::Compress(const std::string& input,
563                                                   std::string* output) {
564   DCHECK_EQ(state_, State::ACTIVE);
565 
566   if (input.empty()) {
567     return Result::OK;
568   }
569 
570   const auto result =
571       CompressInternal(input, output, /*budgeted=*/true, /*last=*/false);
572 
573   switch (result) {
574     case Result::OK:
575       return result;
576     case Result::DISALLOWED:
577       state_ = State::FULL;
578       return result;
579     case Result::ERROR_ENCOUNTERED:
580       state_ = State::ERRORED;
581       return result;
582   }
583 
584   NOTREACHED();
585   return Result::ERROR_ENCOUNTERED;  // Appease compiler.
586 }
587 
CreateFooter(std::string * output)588 bool GzipLogCompressor::CreateFooter(std::string* output) {
589   DCHECK(output);
590   DCHECK(output->empty());
591   DCHECK(state_ == State::ACTIVE || state_ == State::FULL);
592 
593   const Result result = CompressInternal(std::string(), output,
594                                          /*budgeted=*/false, /*last=*/true);
595   if (result != Result::OK) {  // !budgeted -> Result::DISALLOWED impossible.
596     DCHECK_EQ(result, Result::ERROR_ENCOUNTERED);
597     // An error message was logged by CompressInternal().
598     state_ = State::ERRORED;
599     return false;
600   }
601 
602   if (output->length() != kGzipFooterBytes) {
603     LOG(ERROR) << "Incorrect footer size (" << output->length() << ").";
604     state_ = State::ERRORED;
605     return false;
606   }
607 
608   state_ = State::POST_FOOTER;
609 
610   return true;
611 }
612 
SizeAfterOverheadReservation(base::Optional<size_t> max_size_bytes)613 base::Optional<size_t> GzipLogCompressor::SizeAfterOverheadReservation(
614     base::Optional<size_t> max_size_bytes) {
615   if (!max_size_bytes.has_value()) {
616     return base::Optional<size_t>();
617   } else {
618     DCHECK_GE(max_size_bytes.value(), kGzipHeaderBytes + kGzipFooterBytes);
619     return max_size_bytes.value() - (kGzipHeaderBytes + kGzipFooterBytes);
620   }
621 }
622 
CompressInternal(const std::string & input,std::string * output,bool budgeted,bool last)623 LogCompressor::Result GzipLogCompressor::CompressInternal(
624     const std::string& input,
625     std::string* output,
626     bool budgeted,
627     bool last) {
628   DCHECK(output);
629   DCHECK(output->empty());
630   DCHECK(state_ == State::PRE_HEADER || state_ == State::ACTIVE ||
631          (!budgeted && state_ == State::FULL));
632 
633   // Avoid writing to |output| unless the return value is OK.
634   std::string temp_output;
635 
636   if (budgeted) {
637     const size_t estimated_compressed_size =
638         compressed_size_estimator_->EstimateCompressedSize(input);
639     if (!budget_.ConsumeAllowed(estimated_compressed_size)) {
640       return Result::DISALLOWED;
641     }
642   }
643 
644   if (last) {
645     DCHECK(input.empty());
646     stream_.next_in = nullptr;
647   } else {
648     stream_.next_in = reinterpret_cast<z_const Bytef*>(input.c_str());
649   }
650 
651   DCHECK_LE(input.length(),
652             static_cast<size_t>(std::numeric_limits<uInt>::max()));
653   stream_.avail_in = static_cast<uInt>(input.length());
654 
655   const bool result = Deflate(last ? Z_FINISH : Z_SYNC_FLUSH, &temp_output);
656 
657   stream_.next_in = nullptr;  // Avoid dangling pointers.
658 
659   if (!result) {
660     // An error message was logged by Deflate().
661     return Result::ERROR_ENCOUNTERED;
662   }
663 
664   if (budgeted) {
665     if (!budget_.ConsumeAllowed(temp_output.length())) {
666       LOG(WARNING) << "Compressed size was above estimate and unexpectedly "
667                       "exceeded the budget.";
668       return Result::ERROR_ENCOUNTERED;
669     }
670     budget_.Consume(temp_output.length());
671   }
672 
673   std::swap(*output, temp_output);
674   return Result::OK;
675 }
676 
Deflate(int flush,std::string * output)677 bool GzipLogCompressor::Deflate(int flush, std::string* output) {
678   DCHECK((flush != Z_FINISH && stream_.next_in != nullptr) ||
679          (flush == Z_FINISH && stream_.next_in == nullptr));
680   DCHECK(output->empty());
681 
682   bool success = true;  // Result of this method.
683   int z_result;         // Result of the zlib function.
684 
685   size_t total_compressed_size = 0;
686 
687   do {
688     // Allocate some additional buffer.
689     constexpr uInt kCompressionBuffer = 4 * 1024;
690     output->resize(total_compressed_size + kCompressionBuffer);
691 
692     // This iteration should write directly beyond previous iterations' last
693     // written byte.
694     stream_.next_out =
695         reinterpret_cast<uint8_t*>(&((*output)[total_compressed_size]));
696     stream_.avail_out = kCompressionBuffer;
697 
698     z_result = deflate(&stream_, flush);
699 
700     DCHECK_GE(kCompressionBuffer, stream_.avail_out);
701     const size_t compressed_size = kCompressionBuffer - stream_.avail_out;
702 
703     if (flush != Z_FINISH) {
704       if (z_result != Z_OK) {
705         LOG(ERROR) << "Compression failed (" << z_result << ").";
706         success = false;
707         break;
708       }
709     } else {  // flush == Z_FINISH
710       // End of the stream; we expect the footer to be exactly the size which
711       // we've set aside for it.
712       if (z_result != Z_STREAM_END || compressed_size != kGzipFooterBytes) {
713         LOG(ERROR) << "Compression failed (" << z_result << ", "
714                    << compressed_size << ").";
715         success = false;
716         break;
717       }
718     }
719 
720     total_compressed_size += compressed_size;
721   } while (stream_.avail_out == 0 && z_result != Z_STREAM_END);
722 
723   stream_.next_out = nullptr;  // Avoid dangling pointers.
724 
725   if (success) {
726     output->resize(total_compressed_size);
727   } else {
728     output->clear();
729   }
730 
731   return success;
732 }
733 
734 // Given a string with a textual representation of a web-app ID, return the
735 // ID in integer form. If the textual representation does not name a valid
736 // web-app ID, return kInvalidWebRtcEventLogWebAppId.
ExtractWebAppId(base::StringPiece str)737 size_t ExtractWebAppId(base::StringPiece str) {
738   DCHECK_EQ(str.length(), kWebAppIdLength);
739 
740   // Avoid leading '+', etc.
741   for (size_t i = 0; i < str.length(); i++) {
742     if (!std::isdigit(str[i])) {
743       return kInvalidWebRtcEventLogWebAppId;
744     }
745   }
746 
747   size_t result;
748   if (!base::StringToSizeT(str, &result) ||
749       result < kMinWebRtcEventLogWebAppId ||
750       result > kMaxWebRtcEventLogWebAppId) {
751     return kInvalidWebRtcEventLogWebAppId;
752   }
753   return result;
754 }
755 
756 }  // namespace
757 
758 const size_t kGzipOverheadBytes = kGzipHeaderBytes + kGzipFooterBytes;
759 
760 const base::FilePath::CharType kWebRtcEventLogUncompressedExtension[] =
761     FILE_PATH_LITERAL("log");
762 const base::FilePath::CharType kWebRtcEventLogGzippedExtension[] =
763     FILE_PATH_LITERAL("log.gz");
764 const base::FilePath::CharType kWebRtcEventLogHistoryExtension[] =
765     FILE_PATH_LITERAL("hist");
766 
MinFileSizeBytes() const767 size_t BaseLogFileWriterFactory::MinFileSizeBytes() const {
768   // No overhead incurred; data written straight to the file without metadata.
769   return 0;
770 }
771 
Extension() const772 base::FilePath::StringPieceType BaseLogFileWriterFactory::Extension() const {
773   return kWebRtcEventLogUncompressedExtension;
774 }
775 
Create(const base::FilePath & path,base::Optional<size_t> max_file_size_bytes) const776 std::unique_ptr<LogFileWriter> BaseLogFileWriterFactory::Create(
777     const base::FilePath& path,
778     base::Optional<size_t> max_file_size_bytes) const {
779   if (max_file_size_bytes.has_value() &&
780       max_file_size_bytes.value() < MinFileSizeBytes()) {
781     LOG(WARNING) << "Max size (" << max_file_size_bytes.value()
782                  << ") below minimum size (" << MinFileSizeBytes() << ").";
783     return nullptr;
784   }
785 
786   auto result = std::make_unique<BaseLogFileWriter>(path, max_file_size_bytes);
787 
788   if (!result->Init()) {
789     // Error logged by Init.
790     result.reset();  // Destructor deletes errored files.
791   }
792 
793   return result;
794 }
795 
796 std::unique_ptr<CompressedSizeEstimator>
Create() const797 DefaultGzippedSizeEstimator::Factory::Create() const {
798   return std::make_unique<DefaultGzippedSizeEstimator>();
799 }
800 
EstimateCompressedSize(const std::string & input) const801 size_t DefaultGzippedSizeEstimator::EstimateCompressedSize(
802     const std::string& input) const {
803   // This estimation is not tight. Since we expect to produce logs of
804   // several MBs, overshooting the estimation by one KB should be
805   // very safe and still relatively efficient.
806   constexpr size_t kOverheadOverUncompressedSizeBytes = 1000;
807   return input.length() + kOverheadOverUncompressedSizeBytes;
808 }
809 
GzipLogCompressorFactory(std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory)810 GzipLogCompressorFactory::GzipLogCompressorFactory(
811     std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory)
812     : estimator_factory_(std::move(estimator_factory)) {}
813 
814 GzipLogCompressorFactory::~GzipLogCompressorFactory() = default;
815 
MinSizeBytes() const816 size_t GzipLogCompressorFactory::MinSizeBytes() const {
817   return kGzipOverheadBytes;
818 }
819 
Create(base::Optional<size_t> max_size_bytes) const820 std::unique_ptr<LogCompressor> GzipLogCompressorFactory::Create(
821     base::Optional<size_t> max_size_bytes) const {
822   if (max_size_bytes.has_value() && max_size_bytes.value() < MinSizeBytes()) {
823     LOG(WARNING) << "Max size (" << max_size_bytes.value()
824                  << ") below minimum size (" << MinSizeBytes() << ").";
825     return nullptr;
826   }
827   return std::make_unique<GzipLogCompressor>(max_size_bytes,
828                                              estimator_factory_->Create());
829 }
830 
GzippedLogFileWriterFactory(std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory)831 GzippedLogFileWriterFactory::GzippedLogFileWriterFactory(
832     std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory)
833     : gzip_compressor_factory_(std::move(gzip_compressor_factory)) {}
834 
835 GzippedLogFileWriterFactory::~GzippedLogFileWriterFactory() = default;
836 
MinFileSizeBytes() const837 size_t GzippedLogFileWriterFactory::MinFileSizeBytes() const {
838   // Only the compression's own overhead is incurred.
839   return gzip_compressor_factory_->MinSizeBytes();
840 }
841 
Extension() const842 base::FilePath::StringPieceType GzippedLogFileWriterFactory::Extension() const {
843   return kWebRtcEventLogGzippedExtension;
844 }
845 
Create(const base::FilePath & path,base::Optional<size_t> max_file_size_bytes) const846 std::unique_ptr<LogFileWriter> GzippedLogFileWriterFactory::Create(
847     const base::FilePath& path,
848     base::Optional<size_t> max_file_size_bytes) const {
849   if (max_file_size_bytes.has_value() &&
850       max_file_size_bytes.value() < MinFileSizeBytes()) {
851     LOG(WARNING) << "Size below allowed minimum.";
852     return nullptr;
853   }
854 
855   auto gzip_compressor = gzip_compressor_factory_->Create(max_file_size_bytes);
856   if (!gzip_compressor) {
857     // The factory itself will have logged an error.
858     return nullptr;
859   }
860 
861   auto result = std::make_unique<GzippedLogFileWriter>(
862       path, max_file_size_bytes, std::move(gzip_compressor));
863 
864   if (!result->Init()) {
865     // Error logged by Init.
866     result.reset();  // Destructor deletes errored files.
867   }
868 
869   return result;
870 }
871 
872 // Create a random identifier of 32 hexadecimal (uppercase) characters.
CreateWebRtcEventLogId()873 std::string CreateWebRtcEventLogId() {
874   // UnguessableToken's interface makes no promisses over case. We therefore
875   // convert, even if the current implementation does not require it.
876   std::string log_id =
877       base::ToUpperASCII(base::UnguessableToken::Create().ToString());
878   DCHECK_EQ(log_id.size(), kWebRtcEventLogIdLength);
879   DCHECK_EQ(log_id.find_first_not_of("0123456789ABCDEF"), std::string::npos);
880   return log_id;
881 }
882 
GetBrowserContextId(const content::BrowserContext * browser_context)883 BrowserContextId GetBrowserContextId(
884     const content::BrowserContext* browser_context) {
885   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
886   return reinterpret_cast<BrowserContextId>(browser_context);
887 }
888 
GetBrowserContextId(int render_process_id)889 BrowserContextId GetBrowserContextId(int render_process_id) {
890   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
891 
892   content::RenderProcessHost* const host =
893       content::RenderProcessHost::FromID(render_process_id);
894 
895   content::BrowserContext* const browser_context =
896       host ? host->GetBrowserContext() : nullptr;
897 
898   return GetBrowserContextId(browser_context);
899 }
900 
GetRemoteBoundWebRtcEventLogsDir(const base::FilePath & browser_context_dir)901 base::FilePath GetRemoteBoundWebRtcEventLogsDir(
902     const base::FilePath& browser_context_dir) {
903   const base::FilePath::CharType kRemoteBoundLogSubDirectory[] =
904       FILE_PATH_LITERAL("webrtc_event_logs");
905   return browser_context_dir.Append(kRemoteBoundLogSubDirectory);
906 }
907 
WebRtcEventLogPath(const base::FilePath & remote_logs_dir,const std::string & log_id,size_t web_app_id,const base::FilePath::StringPieceType & extension)908 base::FilePath WebRtcEventLogPath(
909     const base::FilePath& remote_logs_dir,
910     const std::string& log_id,
911     size_t web_app_id,
912     const base::FilePath::StringPieceType& extension) {
913   DCHECK_GE(web_app_id, kMinWebRtcEventLogWebAppId);
914   DCHECK_LE(web_app_id, kMaxWebRtcEventLogWebAppId);
915 
916   static_assert(kWebAppIdLength == 2u, "Fix the code below.");
917   const std::string web_app_id_str = base::StringPrintf("%02zu", web_app_id);
918   DCHECK_EQ(web_app_id_str.length(), kWebAppIdLength);
919 
920   const std::string filename =
921       std::string(kRemoteBoundWebRtcEventLogFileNamePrefix) + "_" +
922       web_app_id_str + "_" + log_id;
923 
924   return remote_logs_dir.AppendASCII(filename).AddExtension(extension);
925 }
926 
IsValidRemoteBoundLogFilename(const std::string & filename)927 bool IsValidRemoteBoundLogFilename(const std::string& filename) {
928   // The -1 is because of the implict \0.
929   const size_t kPrefixLength =
930       base::size(kRemoteBoundWebRtcEventLogFileNamePrefix) - 1;
931 
932   // [prefix]_[web_app_id]_[log_id]
933   const size_t expected_length =
934       kPrefixLength + 1 + kWebAppIdLength + 1 + kWebRtcEventLogIdLength;
935   if (filename.length() != expected_length) {
936     return false;
937   }
938 
939   size_t index = 0;
940 
941   // Expect prefix.
942   if (filename.find(kRemoteBoundWebRtcEventLogFileNamePrefix) != index) {
943     return false;
944   }
945   index += kPrefixLength;
946 
947   // Expect underscore between prefix and web-app ID.
948   if (filename[index] != '_') {
949     return false;
950   }
951   index += 1;
952 
953   // Expect web-app-ID.
954   const size_t web_app_id =
955       ExtractWebAppId(base::StringPiece(&filename[index], kWebAppIdLength));
956   if (web_app_id == kInvalidWebRtcEventLogWebAppId) {
957     return false;
958   }
959   index += kWebAppIdLength;
960 
961   // Expect underscore between web-app ID and log ID.
962   if (filename[index] != '_') {
963     return false;
964   }
965   index += 1;
966 
967   // Expect log ID.
968   const std::string log_id = filename.substr(index);
969   DCHECK_EQ(log_id.length(), kWebRtcEventLogIdLength);
970   const char* const log_id_chars = "0123456789ABCDEF";
971   if (filename.find_first_not_of(log_id_chars, index) != std::string::npos) {
972     return false;
973   }
974 
975   return true;
976 }
977 
IsValidRemoteBoundLogFilePath(const base::FilePath & path)978 bool IsValidRemoteBoundLogFilePath(const base::FilePath& path) {
979   const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
980   return IsValidRemoteBoundLogFilename(filename);
981 }
982 
GetWebRtcEventLogHistoryFilePath(const base::FilePath & path)983 base::FilePath GetWebRtcEventLogHistoryFilePath(const base::FilePath& path) {
984   // TODO(crbug.com/775415): Check for validity (after fixing unit tests).
985   return path.RemoveExtension().AddExtension(kWebRtcEventLogHistoryExtension);
986 }
987 
ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(const base::FilePath & path)988 std::string ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(
989     const base::FilePath& path) {
990   const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
991   if (!IsValidRemoteBoundLogFilename(filename)) {
992     LOG(WARNING) << "Invalid remote-bound WebRTC event log filename.";
993     return std::string();
994   }
995 
996   DCHECK_GE(filename.length(), kWebRtcEventLogIdLength);
997   return filename.substr(filename.length() - kWebRtcEventLogIdLength);
998 }
999 
ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(const base::FilePath & path)1000 size_t ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(
1001     const base::FilePath& path) {
1002   const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
1003   if (!IsValidRemoteBoundLogFilename(filename)) {
1004     LOG(WARNING) << "Invalid remote-bound WebRTC event log filename.";
1005     return kInvalidWebRtcEventLogWebAppId;
1006   }
1007 
1008   // The -1 is because of the implict \0.
1009   const size_t kPrefixLength =
1010       base::size(kRemoteBoundWebRtcEventLogFileNamePrefix) - 1;
1011 
1012   // The +1 is for the underscore between the prefix and the web-app ID.
1013   // Length verified by above call to IsValidRemoteBoundLogFilename().
1014   DCHECK_GE(filename.length(), kPrefixLength + 1 + kWebAppIdLength);
1015   base::StringPiece id_str(&filename[kPrefixLength + 1], kWebAppIdLength);
1016 
1017   return ExtractWebAppId(id_str);
1018 }
1019 
DoesProfileDefaultToLoggingEnabled(const Profile * const profile)1020 bool DoesProfileDefaultToLoggingEnabled(const Profile* const profile) {
1021 // For Chrome OS, exclude special profiles and users.
1022 #if defined(OS_CHROMEOS)
1023   const user_manager::User* user =
1024       chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
1025   // We do not log an error here since this can happen in several cases,
1026   // e.g. for signin profiles or lock screen app profiles.
1027   if (!user) {
1028     return false;
1029   }
1030   const user_manager::UserType user_type = user->GetType();
1031   if (user_type != user_manager::USER_TYPE_REGULAR) {
1032     return false;
1033   }
1034   if (chromeos::ProfileHelper::IsEphemeralUserProfile(profile)) {
1035     return false;
1036   }
1037 #endif
1038 
1039   // We only want a default of true for regular (i.e. logged-in) profiles
1040   // receiving cloud-based user-level enterprise policies. Supervised (child)
1041   // profiles are considered regular and can also receive cloud policies in some
1042   // cases (e.g. on Chrome OS). Although currently this should be covered by the
1043   // other checks, let's explicitly check to anticipate edge cases and make the
1044   // requirement explicit.
1045   if (!profile->IsRegularProfile() || profile->IsSupervised()) {
1046     return false;
1047   }
1048 
1049   const policy::ProfilePolicyConnector* const policy_connector =
1050       profile->GetProfilePolicyConnector();
1051 
1052   return policy_connector->policy_service()->IsInitializationComplete(
1053              policy::POLICY_DOMAIN_CHROME) &&
1054          policy_connector->IsManaged();
1055 }
1056 
1057 }  // namespace webrtc_event_logging
1058