// Copyright (c) 2011 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 "webkit/glue/glue_serialize.h"
#include <string>
#include "base/pickle.h"
#include "base/utf_string_conversions.h"
#include "googleurl/src/gurl.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebData.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebHistoryItem.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebHTTPBody.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebPoint.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebSerializedScriptValue.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebVector.h"
#include "webkit/glue/webkit_glue.h"
using WebKit::WebData;
using WebKit::WebHistoryItem;
using WebKit::WebHTTPBody;
using WebKit::WebPoint;
using WebKit::WebSerializedScriptValue;
using WebKit::WebString;
using WebKit::WebUChar;
using WebKit::WebVector;
namespace webkit_glue {
struct SerializeObject {
SerializeObject() : iter(NULL) {}
SerializeObject(const char* data, int len) : pickle(data, len), iter(NULL) {}
std::string GetAsString() {
return std::string(static_cast<const char*>(pickle.data()), pickle.size());
}
Pickle pickle;
mutable void* iter;
mutable int version;
};
// TODO(mpcomplete): obsolete versions 1 and 2 after 1/1/2008.
// Version ID used in reading/writing history items.
// 1: Initial revision.
// 2: Added case for NULL string versus "". Version 2 code can read Version 1
// data, but not vice versa.
// 3: Version 2 was broken, it stored number of WebUChars, not number of bytes.
// This version checks and reads v1 and v2 correctly.
// 4: Adds support for storing FormData::identifier().
// 5: Adds support for empty FormData
// 6: Adds support for documentSequenceNumbers
// 7: Adds support for stateObject
// 8: Adds support for file range and modification time
// 9: Adds support for itemSequenceNumbers
// 10: Adds support for blob
// Should be const, but unit tests may modify it.
//
// NOTE: If the version is -1, then the pickle contains only a URL string.
// See CreateHistoryStateForURL.
//
int kVersion = 10;
// A bunch of convenience functions to read/write to SerializeObjects.
// The serializers assume the input data is in the correct format and so does
// no error checking.
inline void WriteData(const void* data, int length, SerializeObject* obj) {
obj->pickle.WriteData(static_cast<const char*>(data), length);
}
inline void ReadData(const SerializeObject* obj, const void** data,
int* length) {
const char* tmp = NULL;
obj->pickle.ReadData(&obj->iter, &tmp, length);
*data = tmp;
}
inline bool ReadBytes(const SerializeObject* obj, const void** data,
int length) {
const char *tmp;
if (!obj->pickle.ReadBytes(&obj->iter, &tmp, length))
return false;
*data = tmp;
return true;
}
inline void WriteInteger(int data, SerializeObject* obj) {
obj->pickle.WriteInt(data);
}
inline int ReadInteger(const SerializeObject* obj) {
int tmp = 0;
obj->pickle.ReadInt(&obj->iter, &tmp);
return tmp;
}
inline void WriteInteger64(int64 data, SerializeObject* obj) {
obj->pickle.WriteInt64(data);
}
inline int64 ReadInteger64(const SerializeObject* obj) {
int64 tmp = 0;
obj->pickle.ReadInt64(&obj->iter, &tmp);
return tmp;
}
inline void WriteReal(double data, SerializeObject* obj) {
WriteData(&data, sizeof(double), obj);
}
inline double ReadReal(const SerializeObject* obj) {
const void* tmp = NULL;
int length = 0;
ReadData(obj, &tmp, &length);
if (tmp && length > 0 && length >= static_cast<int>(sizeof(0.0)))
return *static_cast<const double*>(tmp);
else
return 0.0;
}
inline void WriteBoolean(bool data, SerializeObject* obj) {
obj->pickle.WriteInt(data ? 1 : 0);
}
inline bool ReadBoolean(const SerializeObject* obj) {
bool tmp = false;
obj->pickle.ReadBool(&obj->iter, &tmp);
return tmp;
}
inline void WriteGURL(const GURL& url, SerializeObject* obj) {
obj->pickle.WriteString(url.possibly_invalid_spec());
}
inline GURL ReadGURL(const SerializeObject* obj) {
std::string spec;
obj->pickle.ReadString(&obj->iter, &spec);
return GURL(spec);
}
// Read/WriteString pickle the WebString as <int length><WebUChar* data>.
// If length == -1, then the WebString itself is NULL (WebString()).
// Otherwise the length is the number of WebUChars (not bytes) in the WebString.
inline void WriteString(const WebString& str, SerializeObject* obj) {
switch (kVersion) {
case 1:
// Version 1 writes <length in bytes><string data>.
// It saves WebString() and "" as "".
obj->pickle.WriteInt(str.length() * sizeof(WebUChar));
obj->pickle.WriteBytes(str.data(), str.length() * sizeof(WebUChar));
break;
case 2:
// Version 2 writes <length in WebUChar><string data>.
// It uses -1 in the length field to mean WebString().
if (str.isNull()) {
obj->pickle.WriteInt(-1);
} else {
obj->pickle.WriteInt(str.length());
obj->pickle.WriteBytes(str.data(),
str.length() * sizeof(WebUChar));
}
break;
default:
// Version 3+ writes <length in bytes><string data>.
// It uses -1 in the length field to mean WebString().
if (str.isNull()) {
obj->pickle.WriteInt(-1);
} else {
obj->pickle.WriteInt(str.length() * sizeof(WebUChar));
obj->pickle.WriteBytes(str.data(),
str.length() * sizeof(WebUChar));
}
break;
}
}
// This reads a serialized WebString from obj. If a string can't be read,
// WebString() is returned.
inline WebString ReadString(const SerializeObject* obj) {
int length;
// Versions 1, 2, and 3 all start with an integer.
if (!obj->pickle.ReadInt(&obj->iter, &length))
return WebString();
// Starting with version 2, -1 means WebString().
if (length == -1)
return WebString();
// In version 2, the length field was the length in WebUChars.
// In version 1 and 3 it is the length in bytes.
int bytes = length;
if (obj->version == 2)
bytes *= sizeof(WebUChar);
const void* data;
if (!ReadBytes(obj, &data, bytes))
return WebString();
return WebString(static_cast<const WebUChar*>(data),
bytes / sizeof(WebUChar));
}
// Writes a Vector of Strings into a SerializeObject for serialization.
static void WriteStringVector(
const WebVector<WebString>& data, SerializeObject* obj) {
WriteInteger(static_cast<int>(data.size()), obj);
for (size_t i = 0, c = data.size(); i < c; ++i) {
unsigned ui = static_cast<unsigned>(i); // sigh
WriteString(data[ui], obj);
}
}
static WebVector<WebString> ReadStringVector(const SerializeObject* obj) {
int num_elements = ReadInteger(obj);
WebVector<WebString> result(static_cast<size_t>(num_elements));
for (int i = 0; i < num_elements; ++i)
result[i] = ReadString(obj);
return result;
}
// Writes a FormData object into a SerializeObject for serialization.
static void WriteFormData(const WebHTTPBody& http_body, SerializeObject* obj) {
WriteBoolean(!http_body.isNull(), obj);
if (http_body.isNull())
return;
WriteInteger(static_cast<int>(http_body.elementCount()), obj);
WebHTTPBody::Element element;
for (size_t i = 0; http_body.elementAt(i, element); ++i) {
WriteInteger(element.type, obj);
if (element.type == WebHTTPBody::Element::TypeData) {
WriteData(element.data.data(), static_cast<int>(element.data.size()),
obj);
} else if (element.type == WebHTTPBody::Element::TypeFile) {
WriteString(element.filePath, obj);
WriteInteger64(element.fileStart, obj);
WriteInteger64(element.fileLength, obj);
WriteReal(element.modificationTime, obj);
} else {
WriteGURL(element.blobURL, obj);
}
}
WriteInteger64(http_body.identifier(), obj);
}
static WebHTTPBody ReadFormData(const SerializeObject* obj) {
// In newer versions, an initial boolean indicates if we have form data.
if (obj->version >= 5 && !ReadBoolean(obj))
return WebHTTPBody();
// In older versions, 0 elements implied no form data.
int num_elements = ReadInteger(obj);
if (num_elements == 0 && obj->version < 5)
return WebHTTPBody();
WebHTTPBody http_body;
http_body.initialize();
for (int i = 0; i < num_elements; ++i) {
int type = ReadInteger(obj);
if (type == WebHTTPBody::Element::TypeData) {
const void* data;
int length = -1;
ReadData(obj, &data, &length);
if (length >= 0)
http_body.appendData(WebData(static_cast<const char*>(data), length));
} else if (type == WebHTTPBody::Element::TypeFile) {
WebString file_path = ReadString(obj);
long long file_start = 0;
long long file_length = -1;
double modification_time = 0.0;
if (obj->version >= 8) {
file_start = ReadInteger64(obj);
file_length = ReadInteger64(obj);
modification_time = ReadReal(obj);
}
http_body.appendFileRange(file_path, file_start, file_length,
modification_time);
} else if (obj->version >= 10) {
GURL blob_url = ReadGURL(obj);
http_body.appendBlob(blob_url);
}
}
if (obj->version >= 4)
http_body.setIdentifier(ReadInteger64(obj));
return http_body;
}
// Writes the HistoryItem data into the SerializeObject object for
// serialization.
static void WriteHistoryItem(
const WebHistoryItem& item, SerializeObject* obj) {
// WARNING: This data may be persisted for later use. As such, care must be
// taken when changing the serialized format. If a new field needs to be
// written, only adding at the end will make it easier to deal with loading
// older versions. Similarly, this should NOT save fields with sensitive
// data, such as password fields.
WriteInteger(kVersion, obj);
WriteString(item.urlString(), obj);
WriteString(item.originalURLString(), obj);
WriteString(item.target(), obj);
WriteString(item.parent(), obj);
WriteString(item.title(), obj);
WriteString(item.alternateTitle(), obj);
WriteReal(item.lastVisitedTime(), obj);
WriteInteger(item.scrollOffset().x, obj);
WriteInteger(item.scrollOffset().y, obj);
WriteBoolean(item.isTargetItem(), obj);
WriteInteger(item.visitCount(), obj);
WriteString(item.referrer(), obj);
WriteStringVector(item.documentState(), obj);
if (kVersion >= 9)
WriteInteger64(item.itemSequenceNumber(), obj);
if (kVersion >= 6)
WriteInteger64(item.documentSequenceNumber(), obj);
if (kVersion >= 7) {
bool has_state_object = !item.stateObject().isNull();
WriteBoolean(has_state_object, obj);
if (has_state_object)
WriteString(item.stateObject().toString(), obj);
}
// Yes, the referrer is written twice. This is for backwards
// compatibility with the format.
WriteFormData(item.httpBody(), obj);
WriteString(item.httpContentType(), obj);
WriteString(item.referrer(), obj);
// Subitems
const WebVector<WebHistoryItem>& children = item.children();
WriteInteger(static_cast<int>(children.size()), obj);
for (size_t i = 0, c = children.size(); i < c; ++i)
WriteHistoryItem(children[i], obj);
}
// Creates a new HistoryItem tree based on the serialized string.
// Assumes the data is in the format returned by WriteHistoryItem.
static WebHistoryItem ReadHistoryItem(
const SerializeObject* obj,
bool include_form_data,
bool include_scroll_offset) {
// See note in WriteHistoryItem. on this.
obj->version = ReadInteger(obj);
if (obj->version == -1) {
GURL url = ReadGURL(obj);
WebHistoryItem item;
item.initialize();
item.setURLString(WebString::fromUTF8(url.possibly_invalid_spec()));
return item;
}
if (obj->version > kVersion || obj->version < 1)
return WebHistoryItem();
WebHistoryItem item;
item.initialize();
item.setURLString(ReadString(obj));
item.setOriginalURLString(ReadString(obj));
item.setTarget(ReadString(obj));
item.setParent(ReadString(obj));
item.setTitle(ReadString(obj));
item.setAlternateTitle(ReadString(obj));
item.setLastVisitedTime(ReadReal(obj));
int x = ReadInteger(obj);
int y = ReadInteger(obj);
if (include_scroll_offset)
item.setScrollOffset(WebPoint(x, y));
item.setIsTargetItem(ReadBoolean(obj));
item.setVisitCount(ReadInteger(obj));
item.setReferrer(ReadString(obj));
item.setDocumentState(ReadStringVector(obj));
if (obj->version >= 9)
item.setItemSequenceNumber(ReadInteger64(obj));
if (obj->version >= 6)
item.setDocumentSequenceNumber(ReadInteger64(obj));
if (obj->version >= 7) {
bool has_state_object = ReadBoolean(obj);
if (has_state_object) {
item.setStateObject(
WebSerializedScriptValue::fromString(ReadString(obj)));
}
}
// The extra referrer string is read for backwards compat.
const WebHTTPBody& http_body = ReadFormData(obj);
const WebString& http_content_type = ReadString(obj);
ALLOW_UNUSED const WebString& unused_referrer = ReadString(obj);
if (include_form_data) {
item.setHTTPBody(http_body);
item.setHTTPContentType(http_content_type);
}
// Subitems
int num_children = ReadInteger(obj);
for (int i = 0; i < num_children; ++i)
item.appendToChildren(ReadHistoryItem(obj,
include_form_data,
include_scroll_offset));
return item;
}
// Serialize a HistoryItem to a string, using our JSON Value serializer.
std::string HistoryItemToString(const WebHistoryItem& item) {
if (item.isNull())
return std::string();
SerializeObject obj;
WriteHistoryItem(item, &obj);
return obj.GetAsString();
}
// Reconstruct a HistoryItem from a string, using our JSON Value deserializer.
// This assumes that the given serialized string has all the required key,value
// pairs, and does minimal error checking. If |include_form_data| is true,
// the form data from a post is restored, otherwise the form data is empty.
// If |include_scroll_offset| is true, the scroll offset is restored.
static WebHistoryItem HistoryItemFromString(
const std::string& serialized_item,
bool include_form_data,
bool include_scroll_offset) {
if (serialized_item.empty())
return WebHistoryItem();
SerializeObject obj(serialized_item.data(),
static_cast<int>(serialized_item.length()));
return ReadHistoryItem(&obj, include_form_data, include_scroll_offset);
}
WebHistoryItem HistoryItemFromString(
const std::string& serialized_item) {
return HistoryItemFromString(serialized_item, true, true);
}
// For testing purposes only.
void HistoryItemToVersionedString(const WebHistoryItem& item, int version,
std::string* serialized_item) {
if (item.isNull()) {
serialized_item->clear();
return;
}
// Temporarily change the version.
int real_version = kVersion;
kVersion = version;
SerializeObject obj;
WriteHistoryItem(item, &obj);
*serialized_item = obj.GetAsString();
kVersion = real_version;
}
std::string CreateHistoryStateForURL(const GURL& url) {
// We avoid using the WebKit API here, so that we do not need to have WebKit
// initialized before calling this method. Instead, we write a simple
// serialization of the given URL with a dummy version number of -1. This
// will be interpreted by ReadHistoryItem as a request to create a default
// WebHistoryItem.
SerializeObject obj;
WriteInteger(-1, &obj);
WriteGURL(url, &obj);
return obj.GetAsString();
}
std::string RemoveFormDataFromHistoryState(const std::string& content_state) {
// TODO(darin): We should avoid using the WebKit API here, so that we do not
// need to have WebKit initialized before calling this method.
const WebHistoryItem& item =
HistoryItemFromString(content_state, false, true);
if (item.isNull()) {
// Couldn't parse the string, return an empty string.
return std::string();
}
return HistoryItemToString(item);
}
std::string RemoveScrollOffsetFromHistoryState(
const std::string& content_state) {
// TODO(darin): We should avoid using the WebKit API here, so that we do not
// need to have WebKit initialized before calling this method.
const WebHistoryItem& item =
HistoryItemFromString(content_state, true, false);
if (item.isNull()) {
// Couldn't parse the string, return an empty string.
return std::string();
}
return HistoryItemToString(item);
}
} // namespace webkit_glue