This source file includes following definitions.
- possible_pass_through_
- InitDecoding
- ReadFilteredData
- InitializeDictionary
- OutputBufferExcess
#include "net/filter/sdch_filter.h"
#include <ctype.h>
#include <limits.h>
#include <algorithm>
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "net/base/sdch_manager.h"
#include "sdch/open-vcdiff/src/google/vcdecoder.h"
namespace net {
SdchFilter::SdchFilter(const FilterContext& filter_context)
: filter_context_(filter_context),
decoding_status_(DECODING_UNINITIALIZED),
dictionary_hash_(),
dictionary_hash_is_plausible_(false),
dictionary_(NULL),
dest_buffer_excess_(),
dest_buffer_excess_index_(0),
source_bytes_(0),
output_bytes_(0),
possible_pass_through_(false) {
bool success = filter_context.GetMimeType(&mime_type_);
DCHECK(success);
success = filter_context.GetURL(&url_);
DCHECK(success);
}
SdchFilter::~SdchFilter() {
static int filter_use_count = 0;
++filter_use_count;
if (META_REFRESH_RECOVERY == decoding_status_) {
UMA_HISTOGRAM_COUNTS("Sdch3.FilterUseBeforeDisabling", filter_use_count);
}
if (vcdiff_streaming_decoder_.get()) {
if (!vcdiff_streaming_decoder_->FinishDecoding()) {
decoding_status_ = DECODING_ERROR;
SdchManager::SdchErrorRecovery(SdchManager::INCOMPLETE_SDCH_CONTENT);
SdchManager::BlacklistDomain(url_);
UMA_HISTOGRAM_COUNTS("Sdch3.PartialBytesIn",
static_cast<int>(filter_context_.GetByteReadCount()));
UMA_HISTOGRAM_COUNTS("Sdch3.PartialVcdiffIn", source_bytes_);
UMA_HISTOGRAM_COUNTS("Sdch3.PartialVcdiffOut", output_bytes_);
}
}
if (!dest_buffer_excess_.empty()) {
SdchManager::SdchErrorRecovery(SdchManager::UNFLUSHED_CONTENT);
UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedBytesIn",
static_cast<int>(filter_context_.GetByteReadCount()));
UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedBufferSize",
dest_buffer_excess_.size());
UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedVcdiffIn", source_bytes_);
UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedVcdiffOut", output_bytes_);
}
if (filter_context_.IsCachedContent()) {
SdchManager::SdchErrorRecovery(SdchManager::CACHE_DECODED);
return;
}
switch (decoding_status_) {
case DECODING_IN_PROGRESS: {
if (output_bytes_)
UMA_HISTOGRAM_PERCENTAGE("Sdch3.Network_Decode_Ratio_a",
static_cast<int>(
(filter_context_.GetByteReadCount() * 100) / output_bytes_));
UMA_HISTOGRAM_COUNTS("Sdch3.Network_Decode_Bytes_VcdiffOut_a",
output_bytes_);
filter_context_.RecordPacketStats(FilterContext::SDCH_DECODE);
SdchManager::Global()->SetAllowLatencyExperiment(url_, true);
return;
}
case PASS_THROUGH: {
filter_context_.RecordPacketStats(FilterContext::SDCH_PASSTHROUGH);
return;
}
case DECODING_UNINITIALIZED: {
SdchManager::SdchErrorRecovery(SdchManager::UNINITIALIZED);
return;
}
case WAITING_FOR_DICTIONARY_SELECTION: {
SdchManager::SdchErrorRecovery(SdchManager::PRIOR_TO_DICTIONARY);
return;
}
case DECODING_ERROR: {
SdchManager::SdchErrorRecovery(SdchManager::DECODE_ERROR);
return;
}
case META_REFRESH_RECOVERY: {
return;
}
}
}
bool SdchFilter::InitDecoding(Filter::FilterType filter_type) {
if (decoding_status_ != DECODING_UNINITIALIZED)
return false;
if (FILTER_TYPE_SDCH_POSSIBLE == filter_type)
possible_pass_through_ = true;
decoding_status_ = WAITING_FOR_DICTIONARY_SELECTION;
return true;
}
#ifndef NDEBUG
static const char* kDecompressionErrorHtml =
"<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>"
"<div style=\"position:fixed;top:0;left:0;width:100%;border-width:thin;"
"border-color:black;border-style:solid;text-align:left;font-family:arial;"
"font-size:10pt;foreground-color:black;background-color:white\">"
"An error occurred. This page will be reloaded shortly. "
"Or press the \"reload\" button now to reload it immediately."
"</div>";
#else
static const char* kDecompressionErrorHtml =
"<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>";
#endif
Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer,
int* dest_len) {
int available_space = *dest_len;
*dest_len = 0;
if (!dest_buffer || available_space <= 0)
return FILTER_ERROR;
if (WAITING_FOR_DICTIONARY_SELECTION == decoding_status_) {
FilterStatus status = InitializeDictionary();
if (FILTER_NEED_MORE_DATA == status)
return FILTER_NEED_MORE_DATA;
if (FILTER_ERROR == status) {
DCHECK_EQ(DECODING_ERROR, decoding_status_);
DCHECK_EQ(0u, dest_buffer_excess_index_);
DCHECK(dest_buffer_excess_.empty());
if (filter_context_.GetResponseCode() == 404) {
SdchManager::SdchErrorRecovery(SdchManager::PASS_THROUGH_404_CODE);
decoding_status_ = PASS_THROUGH;
} else if (filter_context_.GetResponseCode() != 200) {
} else if (filter_context_.IsCachedContent()
&& !dictionary_hash_is_plausible_) {
SdchManager::SdchErrorRecovery(SdchManager::PASS_THROUGH_OLD_CACHED);
decoding_status_ = PASS_THROUGH;
} else if (possible_pass_through_) {
SdchManager::SdchErrorRecovery(SdchManager::DISCARD_TENTATIVE_SDCH);
} else if (dictionary_hash_is_plausible_) {
} else if (filter_context_.IsSdchResponse()) {
} else {
SdchManager::SdchErrorRecovery(SdchManager::PASSING_THROUGH_NON_SDCH);
decoding_status_ = PASS_THROUGH;
SdchManager::BlacklistDomain(url_);
}
if (decoding_status_ == PASS_THROUGH) {
dest_buffer_excess_ = dictionary_hash_;
} else {
if (std::string::npos == mime_type_.find("text/html")) {
SdchManager::BlacklistDomainForever(url_);
if (filter_context_.IsCachedContent())
SdchManager::SdchErrorRecovery(
SdchManager::CACHED_META_REFRESH_UNSUPPORTED);
else
SdchManager::SdchErrorRecovery(
SdchManager::META_REFRESH_UNSUPPORTED);
return FILTER_ERROR;
}
if (filter_context_.IsCachedContent()) {
SdchManager::SdchErrorRecovery(
SdchManager::META_REFRESH_CACHED_RECOVERY);
} else {
SdchManager::BlacklistDomain(url_);
SdchManager::SdchErrorRecovery(SdchManager::META_REFRESH_RECOVERY);
}
decoding_status_ = META_REFRESH_RECOVERY;
dest_buffer_excess_ = kDecompressionErrorHtml;
}
} else {
DCHECK_EQ(DECODING_IN_PROGRESS, decoding_status_);
}
}
int amount = OutputBufferExcess(dest_buffer, available_space);
*dest_len += amount;
dest_buffer += amount;
available_space -= amount;
DCHECK_GE(available_space, 0);
if (available_space <= 0)
return FILTER_OK;
DCHECK(dest_buffer_excess_.empty());
DCHECK_EQ(0u, dest_buffer_excess_index_);
if (decoding_status_ != DECODING_IN_PROGRESS) {
if (META_REFRESH_RECOVERY == decoding_status_) {
next_stream_data_ = NULL;
stream_data_len_ = 0;
return FILTER_NEED_MORE_DATA;
}
if (PASS_THROUGH == decoding_status_) {
FilterStatus result = CopyOut(dest_buffer, &available_space);
*dest_len += available_space;
return result;
}
DCHECK(false);
decoding_status_ = DECODING_ERROR;
return FILTER_ERROR;
}
if (!next_stream_data_ || stream_data_len_ <= 0)
return FILTER_NEED_MORE_DATA;
bool ret = vcdiff_streaming_decoder_->DecodeChunk(
next_stream_data_, stream_data_len_, &dest_buffer_excess_);
next_stream_data_ = NULL;
source_bytes_ += stream_data_len_;
stream_data_len_ = 0;
output_bytes_ += dest_buffer_excess_.size();
if (!ret) {
vcdiff_streaming_decoder_.reset(NULL);
decoding_status_ = DECODING_ERROR;
SdchManager::SdchErrorRecovery(SdchManager::DECODE_BODY_ERROR);
return FILTER_ERROR;
}
amount = OutputBufferExcess(dest_buffer, available_space);
*dest_len += amount;
dest_buffer += amount;
available_space -= amount;
if (0 == available_space && !dest_buffer_excess_.empty())
return FILTER_OK;
return FILTER_NEED_MORE_DATA;
}
Filter::FilterStatus SdchFilter::InitializeDictionary() {
const size_t kServerIdLength = 9;
size_t bytes_needed = kServerIdLength - dictionary_hash_.size();
DCHECK_GT(bytes_needed, 0u);
if (!next_stream_data_)
return FILTER_NEED_MORE_DATA;
if (static_cast<size_t>(stream_data_len_) < bytes_needed) {
dictionary_hash_.append(next_stream_data_, stream_data_len_);
next_stream_data_ = NULL;
stream_data_len_ = 0;
return FILTER_NEED_MORE_DATA;
}
dictionary_hash_.append(next_stream_data_, bytes_needed);
DCHECK(kServerIdLength == dictionary_hash_.size());
stream_data_len_ -= bytes_needed;
DCHECK_LE(0, stream_data_len_);
if (stream_data_len_ > 0)
next_stream_data_ += bytes_needed;
else
next_stream_data_ = NULL;
DCHECK(!dictionary_.get());
dictionary_hash_is_plausible_ = true;
SdchManager::Dictionary* dictionary = NULL;
if ('\0' == dictionary_hash_[kServerIdLength - 1])
SdchManager::Global()->GetVcdiffDictionary(std::string(dictionary_hash_, 0,
kServerIdLength - 1),
url_, &dictionary);
else
dictionary_hash_is_plausible_ = false;
if (!dictionary) {
DCHECK(dictionary_hash_.size() == kServerIdLength);
for (size_t i = 0; i < kServerIdLength - 1; ++i) {
char base64_char = dictionary_hash_[i];
if (!isalnum(base64_char) && '-' != base64_char && '_' != base64_char) {
dictionary_hash_is_plausible_ = false;
break;
}
}
if (dictionary_hash_is_plausible_)
SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_NOT_FOUND);
else
SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_MALFORMED);
decoding_status_ = DECODING_ERROR;
return FILTER_ERROR;
}
dictionary_ = dictionary;
vcdiff_streaming_decoder_.reset(new open_vcdiff::VCDiffStreamingDecoder);
vcdiff_streaming_decoder_->SetAllowVcdTarget(false);
vcdiff_streaming_decoder_->StartDecoding(dictionary_->text().data(),
dictionary_->text().size());
decoding_status_ = DECODING_IN_PROGRESS;
return FILTER_OK;
}
int SdchFilter::OutputBufferExcess(char* const dest_buffer,
size_t available_space) {
if (dest_buffer_excess_.empty())
return 0;
DCHECK(dest_buffer_excess_.size() > dest_buffer_excess_index_);
size_t amount = std::min(available_space,
dest_buffer_excess_.size() - dest_buffer_excess_index_);
memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_,
amount);
dest_buffer_excess_index_ += amount;
if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) {
DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_);
dest_buffer_excess_.clear();
dest_buffer_excess_index_ = 0;
}
return amount;
}
}