| // 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 |