| Index: net/filter/sdch_stream_source.cc
 | 
| diff --git a/net/filter/sdch_stream_source.cc b/net/filter/sdch_stream_source.cc
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..77cb8d8278478f4fee2989071ca139a8e639d774
 | 
| --- /dev/null
 | 
| +++ b/net/filter/sdch_stream_source.cc
 | 
| @@ -0,0 +1,239 @@
 | 
| +// Copyright 2016 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +#include "net/filter/sdch_stream_source.h"
 | 
| +
 | 
| +#include "base/auto_reset.h"
 | 
| +#include "base/bind.h"
 | 
| +#include "base/numerics/safe_conversions.h"
 | 
| +#include "sdch/open-vcdiff/src/google/vcdecoder.h"
 | 
| +
 | 
| +namespace net {
 | 
| +
 | 
| +namespace {
 | 
| +
 | 
| +const size_t kServerIdLength = 9;
 | 
| +const char kSDCH[] = "SDCH";
 | 
| +
 | 
| +}  // namespace
 | 
| +
 | 
| +SdchStreamSource::SdchStreamSource(std::unique_ptr<StreamSource> previous,
 | 
| +                                   Delegate* delegate)
 | 
| +    : FilterStreamSource(StreamSource::TYPE_SDCH, std::move(previous)),
 | 
| +      delegate_(delegate),
 | 
| +      output_replaced_(false),
 | 
| +      passthrough_(false),
 | 
| +      dictionary_tried_load_(false) {
 | 
| +  CHECK(delegate);
 | 
| +}
 | 
| +
 | 
| +SdchStreamSource::~SdchStreamSource() {}
 | 
| +
 | 
| +void SdchStreamSource::StopDecoding() {
 | 
| +  DCHECK(in_delegate_handler_);
 | 
| +  passthrough_ = true;
 | 
| +}
 | 
| +
 | 
| +void SdchStreamSource::ReplaceOutput(const char* data, size_t size) {
 | 
| +  DCHECK(in_delegate_handler_);
 | 
| +  output_replaced_ = true;
 | 
| +  buffered_output_.assign(data, size);
 | 
| +}
 | 
| +
 | 
| +std::string SdchStreamSource::GetTypeAsString() const {
 | 
| +  return kSDCH;
 | 
| +}
 | 
| +
 | 
| +int SdchStreamSource::FilterData(IOBuffer* output_buffer,
 | 
| +                                 size_t output_buffer_size,
 | 
| +                                 DrainableIOBuffer* input_buffer) {
 | 
| +  size_t bytes_read = 0;
 | 
| +  // Run the synchronous decompressor.
 | 
| +  SdchStreamState state =
 | 
| +      Decompress(output_buffer, output_buffer_size, input_buffer, &bytes_read);
 | 
| +
 | 
| +  // If decompression failed, call OnDecodingError on the delegate, then
 | 
| +  // rerun the decompressor. The decompressor shouldn't actually decompress
 | 
| +  // this time, since the delegate should have called either PassThrough or
 | 
| +  // ReplaceOutput.
 | 
| +  if (state == SDCH_STREAM_ERROR) {
 | 
| +    input_buffer->DidConsume(input_buffer->BytesRemaining());
 | 
| +    bool handled = AskDelegateToHandleDecodingError();
 | 
| +    if (!handled)
 | 
| +      return ERR_FAILED;
 | 
| +    state = Decompress(output_buffer, output_buffer_size, input_buffer,
 | 
| +                       &bytes_read);
 | 
| +  }
 | 
| +
 | 
| +  DCHECK_EQ(0, input_buffer->BytesRemaining());
 | 
| +  return bytes_read;
 | 
| +}
 | 
| +
 | 
| +// Synchronous decompressor core.
 | 
| +SdchStreamSource::SdchStreamState SdchStreamSource::Decompress(
 | 
| +    IOBuffer* output_buffer,
 | 
| +    size_t output_buffer_size,
 | 
| +    DrainableIOBuffer* input_buffer,
 | 
| +    size_t* bytes_read) {
 | 
| +  // If there is no loaded dictionary, try loading a dictionary ID from
 | 
| +  // |input_buffer| and then loading a dictionary. If anything goes wrong in
 | 
| +  // LoadDictionary, the delegate's OnDictionaryError() call is expected
 | 
| +  // to recover, either by calling StopDecoding() or ReplaceOutput(). Both of
 | 
| +  // these are handled by Decompress (as is having no dictionary loaded). If
 | 
| +  // the delegate's OnDictionaryError cannot recover, this function
 | 
| +  // returns a read error.
 | 
| +  if (!dictionary_tried_load_ && !LoadDictionary(input_buffer)) {
 | 
| +    return SDCH_STREAM_ERROR;
 | 
| +  }
 | 
| +
 | 
| +  // If there's any data left in |buffered_output_|, try flushing some of it.
 | 
| +  if (Flush(output_buffer, output_buffer_size, bytes_read) !=
 | 
| +      SDCH_STREAM_MORE_INPUT)
 | 
| +    return SDCH_STREAM_MORE_OUTPUT_SPACE;
 | 
| +
 | 
| +  // If LoadDictionary() consume all |input_buffer|, return early here.
 | 
| +  if (input_buffer->BytesRemaining() == 0)
 | 
| +    return SDCH_STREAM_MORE_INPUT;
 | 
| +
 | 
| +  DCHECK(dictionary_tried_load_);
 | 
| +
 | 
| +  // If output has been replaced via ReplaceOutput, short-circuit any other
 | 
| +  // processing, and emit as much of the buffered output as possible. If there's
 | 
| +  // no buffered output left, return EOF.
 | 
| +  if (output_replaced_)
 | 
| +    return Flush(output_buffer, output_buffer_size, bytes_read);
 | 
| +
 | 
| +  // If this stream is no longer decoding, pass any input through.
 | 
| +  if (passthrough_)
 | 
| +    return PassThrough(output_buffer, output_buffer_size, input_buffer,
 | 
| +                       bytes_read);
 | 
| +
 | 
| +  // There is no data left in |buffered_output_| and there is data in
 | 
| +  // |input_buffer|, so run the vcdiff decoder.
 | 
| +  DCHECK(buffered_output_.empty());
 | 
| +  DCHECK_LT(0, input_buffer->BytesRemaining());
 | 
| +
 | 
| +  // Calls to DecodeChunk always consume all their input, so this always drains
 | 
| +  // the entire buffer.
 | 
| +  bool ok = decoder_->DecodeChunk(
 | 
| +      input_buffer->data(), input_buffer->BytesRemaining(), &buffered_output_);
 | 
| +
 | 
| +  input_buffer->DidConsume(input_buffer->BytesRemaining());
 | 
| +
 | 
| +  if (!ok) {
 | 
| +    // Don't call it again.
 | 
| +    decoder_.reset();
 | 
| +    return SDCH_STREAM_ERROR;
 | 
| +  }
 | 
| +  return Flush(output_buffer, output_buffer_size, bytes_read);
 | 
| +}
 | 
| +
 | 
| +SdchStreamSource::SdchStreamState SdchStreamSource::PassThrough(
 | 
| +    IOBuffer* output_buffer,
 | 
| +    size_t output_buffer_size,
 | 
| +    DrainableIOBuffer* input_buffer,
 | 
| +    size_t* bytes_read) {
 | 
| +  *bytes_read = 0;
 | 
| +  SdchStreamState state = Flush(output_buffer, output_buffer_size, bytes_read);
 | 
| +  if (state == SDCH_STREAM_MORE_OUTPUT_SPACE)
 | 
| +    return state;
 | 
| +
 | 
| +  // No buffered data to flush. See if there's any input.
 | 
| +  DCHECK(buffered_output_.empty());
 | 
| +  if (input_buffer->BytesRemaining() == 0)
 | 
| +    return SDCH_STREAM_MORE_INPUT;
 | 
| +
 | 
| +  size_t to_copy = output_buffer_size;
 | 
| +  if (to_copy > base::checked_cast<size_t>(input_buffer->BytesRemaining()))
 | 
| +    to_copy = input_buffer->BytesRemaining();
 | 
| +  memcpy(output_buffer->data(), input_buffer->data(), to_copy);
 | 
| +  input_buffer->DidConsume(to_copy);
 | 
| +  *bytes_read = *bytes_read + to_copy;
 | 
| +  if (input_buffer->BytesRemaining() > 0)
 | 
| +    return SDCH_STREAM_MORE_OUTPUT_SPACE;
 | 
| +  else
 | 
| +    return SDCH_STREAM_MORE_INPUT;
 | 
| +}
 | 
| +
 | 
| +SdchStreamSource::SdchStreamState SdchStreamSource::Flush(
 | 
| +    IOBuffer* output_buffer,
 | 
| +    size_t output_buffer_size,
 | 
| +    size_t* bytes_read) {
 | 
| +  size_t to_copy = buffered_output_.length();
 | 
| +  if (to_copy > output_buffer_size)
 | 
| +    to_copy = output_buffer_size;
 | 
| +  memcpy(output_buffer->data(), buffered_output_.data(), to_copy);
 | 
| +  buffered_output_.erase(0, to_copy);
 | 
| +  *bytes_read = to_copy;
 | 
| +  if (!buffered_output_.empty()) {
 | 
| +    return SDCH_STREAM_MORE_OUTPUT_SPACE;
 | 
| +  }
 | 
| +  return SDCH_STREAM_MORE_INPUT;
 | 
| +}
 | 
| +
 | 
| +// Attempts to find a dictionary ID at the start of the input stream and load
 | 
| +// the dictionary with that identifier. Returns true if no error happened or an
 | 
| +// error happened but the delegate repaired it, and false if an error happened
 | 
| +// and the delegate did not repair it.
 | 
| +bool SdchStreamSource::LoadDictionary(DrainableIOBuffer* input_buffer) {
 | 
| +  // Copy at most |kServerIdLength| from |input_buffer|.
 | 
| +  size_t to_copy = kServerIdLength - dictionary_id_.length();
 | 
| +  if (to_copy > base::checked_cast<size_t>(input_buffer->BytesRemaining()))
 | 
| +    to_copy = input_buffer->BytesRemaining();
 | 
| +  dictionary_id_.append(input_buffer->data(), to_copy);
 | 
| +  input_buffer->DidConsume(to_copy);
 | 
| +
 | 
| +  // Not enough bytes for a dictionary ID accumulated yet.
 | 
| +  if (dictionary_id_.length() != kServerIdLength)
 | 
| +    return true;
 | 
| +
 | 
| +  dictionary_tried_load_ = true;
 | 
| +
 | 
| +  // If the dictionary ID looks bogus, or the delegate can't find a dictionary
 | 
| +  // with that identifier, then call OnDictionaryError and bail.
 | 
| +  // Also, stick the dictionary ID into the output buffer here; LoadDictionary
 | 
| +  // will consume it assuming it's a dictionary ID, so if it's really not, the
 | 
| +  // "dictionary ID" should appear in the output stream.
 | 
| +  //
 | 
| +  // To avoid passing a std::string with a null terminator into GetDictionary(),
 | 
| +  // |server_hash| here removes the last byte blindly, and this method only
 | 
| +  // calls
 | 
| +  // GetDictionary with it if CouldBeDictionaryId returns true.
 | 
| +  std::string server_hash = dictionary_id_.substr(0, kServerIdLength - 1);
 | 
| +  const std::string* dictionary_text = nullptr;
 | 
| +  if (!CouldBeDictionaryId(dictionary_id_) ||
 | 
| +      !delegate_->OnGetDictionary(server_hash, &dictionary_text)) {
 | 
| +    input_buffer->DidConsume(input_buffer->BytesRemaining());
 | 
| +    buffered_output_.append(dictionary_id_);
 | 
| +    return AskDelegateToHandleDictionaryError();
 | 
| +  }
 | 
| +
 | 
| +  decoder_.reset(new open_vcdiff::VCDiffStreamingDecoder);
 | 
| +  decoder_->SetAllowVcdTarget(false);
 | 
| +  decoder_->StartDecoding(dictionary_text->data(), dictionary_text->length());
 | 
| +  return true;
 | 
| +}
 | 
| +
 | 
| +bool SdchStreamSource::CouldBeDictionaryId(const std::string& id) const {
 | 
| +  for (size_t i = 0; i < kServerIdLength - 1; i++) {
 | 
| +    char base64_char = id[i];
 | 
| +    if (!isalnum(base64_char) && '-' != base64_char && '_' != base64_char)
 | 
| +      return false;
 | 
| +  }
 | 
| +  if (id[kServerIdLength - 1] != '\0')
 | 
| +    return false;
 | 
| +  return true;
 | 
| +}
 | 
| +
 | 
| +bool SdchStreamSource::AskDelegateToHandleDictionaryError() {
 | 
| +  base::AutoReset<bool> resetter(&in_delegate_handler_, true);
 | 
| +  return delegate_->OnDictionaryError(this);
 | 
| +}
 | 
| +
 | 
| +bool SdchStreamSource::AskDelegateToHandleDecodingError() {
 | 
| +  base::AutoReset<bool> resetter(&in_delegate_handler_, true);
 | 
| +  return delegate_->OnDecodingError(this);
 | 
| +}
 | 
| +
 | 
| +}  // namespace net
 | 
| 
 |