blob: 5d778c35b59a6d8457633cc1dfd96672e2d701a6 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: jmarantz@google.com (Joshua Marantz)
#include "net/instaweb/http/public/http_value.h"
#include "base/logging.h"
#include "pagespeed/kernel/base/shared_string.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/string_writer.h"
#include "pagespeed/kernel/http/response_headers.h"
#include "pagespeed/kernel/http/response_headers_parser.h"
namespace {
// The headers and body are both encoded into one Shared String, which can then
// be efficiently held in an in-memory cache, or passed around as an HTTPValue
// object. The class supports both setting the headers first and then the body,
// and vice versa. Both the headers and body are variable length, and to avoid
// having to re-shuffle memory, we encode which is first in the buffer as the
// first byte. The next four bytes encode the size.
const char kHeadersFirst = 'h';
const char kBodyFirst = 'b';
const int kStorageTypeOverhead = 1;
const int kStorageSizeOverhead = 4;
const int kStorageOverhead = kStorageTypeOverhead + kStorageSizeOverhead;
} // namespace
namespace net_instaweb {
class MessageHandler;
void HTTPValue::CopyOnWrite() {
storage_.DetachRetainingContent();
}
void HTTPValue::Clear() {
storage_.DetachAndClear();
contents_size_ = 0;
}
void HTTPValue::SetHeaders(ResponseHeaders* headers) {
CopyOnWrite();
GoogleString headers_string;
StringWriter writer(&headers_string);
headers->WriteAsBinary(&writer, NULL);
if (storage_.empty()) {
storage_.Append(&kHeadersFirst, 1);
SetSizeOfFirstChunk(headers_string.size());
} else {
CHECK(type_identifier() == kBodyFirst);
// Using 'unsigned int' to facilitate bit-shifting in
// SizeOfFirstChunk and SetSizeOfFirstChunk, and I don't
// want to worry about sign extension.
int size = SizeOfFirstChunk();
CHECK_EQ(storage_.size(), (kStorageOverhead + size));
}
storage_.Append(headers_string);
}
bool HTTPValue::Write(const StringPiece& str, MessageHandler* handler) {
CopyOnWrite();
if (storage_.empty()) {
// We have received data prior to receiving response headers.
storage_.Append(&kBodyFirst, 1);
SetSizeOfFirstChunk(str.size());
} else if (type_identifier() == kBodyFirst) {
CHECK(storage_.size() >= kStorageOverhead);
int string_size = SizeOfFirstChunk();
CHECK(string_size == storage_.size() - kStorageOverhead);
SetSizeOfFirstChunk(str.size() + string_size);
} else {
CHECK(type_identifier() == kHeadersFirst);
}
storage_.Append(str.data(), str.size());
contents_size_ += str.size();
return true;
}
bool HTTPValue::Flush(MessageHandler* handler) {
return true;
}
// Encode the size of the first chunk, which is either the headers or body,
// depending on the order they are called. Rather than trying to assume any
// particular alignment for casting between char* and int*, we just manually
// encode one byte at a time.
void HTTPValue::SetSizeOfFirstChunk(unsigned int size) {
CHECK(!storage_.empty()) << "type encoding should already be in first byte";
char size_buffer[4];
size_buffer[0] = size & 0xff;
size_buffer[1] = (size >> 8) & 0xff;
size_buffer[2] = (size >> 16) & 0xff;
size_buffer[3] = (size >> 24) & 0xff;
storage_.Extend(1 + sizeof(size_buffer));
storage_.WriteAt(1, size_buffer, sizeof(size_buffer));
}
// Decodes the size of the first chunk, which is either the headers or body,
// depending on the order they are called. Rather than trying to assume any
// particular alignment for casting between char* and int*, we just manually
// decode one byte at a time.
unsigned int HTTPValue::SizeOfFirstChunk() const {
CHECK(storage_.size() >= kStorageOverhead);
const unsigned char* size_buffer =
reinterpret_cast<const unsigned char*>(storage_.data() + 1);
unsigned int size = size_buffer[0];
size |= size_buffer[1] << 8;
size |= size_buffer[2] << 16;
size |= size_buffer[3] << 24;
return size;
}
// Note that we avoid CHECK, and instead return false on error. So if
// our cache gets corrupted (say) on disk, we just consider it an
// invalid entry rather than aborting the server.
bool HTTPValue::ExtractHeaders(ResponseHeaders* headers,
MessageHandler* handler) const {
bool ret = false;
headers->Clear();
if (storage_.size() >= kStorageOverhead) {
char type_id = type_identifier();
const char* start = storage_.data() + kStorageOverhead;
int size = SizeOfFirstChunk();
if (size <= storage_.size() - kStorageOverhead) {
if (type_id == kBodyFirst) {
start += size;
size = storage_.size() - size - kStorageOverhead;
ret = true;
} else {
ret = (type_id == kHeadersFirst);
}
if (ret) {
ret = headers->ReadFromBinary(StringPiece(start, size), handler);
}
}
}
return ret;
}
// Note that we avoid CHECK, and instead return false on error. So if
// our cache gets corrupted (say) on disk, we just consider it an
// invalid entry rather than aborting the server.
bool HTTPValue::ExtractContents(StringPiece* val) const {
bool ret = false;
if (storage_.size() >= kStorageOverhead) {
char type_id = type_identifier();
const char* start = storage_.data() + kStorageOverhead;
int size = SizeOfFirstChunk();
if (size <= storage_.size() - kStorageOverhead) {
if (type_id == kHeadersFirst) {
start += size;
size = storage_.size() - size - kStorageOverhead;
ret = true;
} else {
ret = (type_id == kBodyFirst);
}
*val = StringPiece(start, size);
}
}
return ret;
}
int64 HTTPValue::ComputeContentsSize() const {
// Return size as 0 if the cache is corrupted.
int64 size = 0;
if (storage_.size() >= kStorageOverhead) {
// Get the type id which is stored first (head or body).
char type_id = type_identifier();
// Get the size of the type which is stored first.
size = SizeOfFirstChunk();
// If the headers are stored first then update the size with storage size -
// first chunk size.
if ((size <= static_cast<int64>(storage_.size() - kStorageOverhead)) &&
(type_id == kHeadersFirst)) {
size = storage_.size() - size - kStorageOverhead;
}
}
return size;
}
bool HTTPValue::Link(SharedString* src, ResponseHeaders* headers,
MessageHandler* handler) {
bool ok = false;
if (src->size() >= kStorageOverhead) {
// The simplest way to ensure that src is well formed is to save the
// existing storage_ in a temp, assign the storage, and make sure
// Headers and Contents return true. The drawback is that the headers
// parsing is arguably a little heavyweight. We could consider encoding
// the headers in an easier-to-extract form, so we don't have to give up
// the integrity checks.
SharedString temp(storage_);
storage_ = *src;
contents_size_ = ComputeContentsSize();
// TODO(jmarantz): this could be a lot lighter weight, but we are going
// to be sure at this point that both the headers and the contents are
// valid. It would be nice to have an HTML headers parser that didn't
// actually create new temp copies of all the names/values.
ok = ExtractHeaders(headers, handler);
if (!ok) {
storage_ = temp;
}
}
return ok;
}
bool HTTPValue::Decode(StringPiece encoded_value, GoogleString* http_string,
MessageHandler* handler) {
ResponseHeaders headers;
// Load encoded value into an HTTPValue and extract headers.
SharedString buffer(encoded_value);
HTTPValue value;
if (!value.Link(&buffer, &headers, handler)) return false;
// Extract decoded contents.
StringPiece contents;
if (!value.ExtractContents(&contents)) return false;
// Return result as normal HTTP stream.
*http_string = StrCat(headers.ToString(), contents);
return true;
}
bool HTTPValue::Encode(StringPiece http_string, GoogleString* encoded_value,
MessageHandler* handler) {
// Parse headers.
ResponseHeaders headers;
ResponseHeadersParser headers_parser(&headers);
int bytes_parsed = headers_parser.ParseChunk(http_string, handler);
if (!headers.headers_complete()) return false;
// Rest is contents.
StringPiece contents = http_string.substr(bytes_parsed);
// Encode into HTTPValue.
HTTPValue value;
value.SetHeaders(&headers);
value.Write(contents, handler);
// Return SharedString buffer.
*encoded_value = value.share()->Value().as_string();
return true;
}
} // namespace net_instaweb