// 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 #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(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(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(sizeof(0.0))) return *static_cast(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 . // 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 . // 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 . // 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 . // 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(data), bytes / sizeof(WebUChar)); } // Writes a Vector of Strings into a SerializeObject for serialization. static void WriteStringVector( const WebVector& data, SerializeObject* obj) { WriteInteger(static_cast(data.size()), obj); for (size_t i = 0, c = data.size(); i < c; ++i) { unsigned ui = static_cast(i); // sigh WriteString(data[ui], obj); } } static WebVector ReadStringVector(const SerializeObject* obj) { int num_elements = ReadInteger(obj); WebVector result(static_cast(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(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(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(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& children = item.children(); WriteInteger(static_cast(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(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