/* * Copyright (C) 2016 Igalia S.L. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "WebKitWebViewSessionState.h" #include "WebKitWebViewSessionStatePrivate.h" #include #include using namespace WebKit; struct _WebKitWebViewSessionState { _WebKitWebViewSessionState(SessionState&& state) : sessionState(WTFMove(state)) , referenceCount(1) { } SessionState sessionState; int referenceCount; }; G_DEFINE_BOXED_TYPE(WebKitWebViewSessionState, webkit_web_view_session_state, webkit_web_view_session_state_ref, webkit_web_view_session_state_unref) static const guint16 g_sessionStateVersion = 1; #define HTTP_BODY_ELEMENT_TYPE_STRING_V1 "(uaysxmxmds)" #define HTTP_BODY_ELEMENT_FORMAT_STRING_V1 "(uay&sxmxmd&s)" #define HTTP_BODY_TYPE_STRING_V1 "m(sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")" #define HTTP_BODY_FORMAT_STRING_V1 "m(&sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")" #define FRAME_STATE_TYPE_STRING_V1 "(ssssasmayxx(ii)d" HTTP_BODY_TYPE_STRING_V1 "av)" #define FRAME_STATE_FORMAT_STRING_V1 "(&s&s&s&sasmayxx(ii)d@" HTTP_BODY_TYPE_STRING_V1 "av)" #define BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1 "(ts" FRAME_STATE_TYPE_STRING_V1 "u)" #define BACK_FORWARD_LIST_ITEM_FORMAT_STRING_V1 "(t&s@" FRAME_STATE_TYPE_STRING_V1 "u)" #define SESSION_STATE_TYPE_STRING_V1 "(qa" BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1 "mu)" // Use our own enum types to ensure the serialized format even if the core enums change. enum ExternalURLsPolicy { Allow, AllowExternalSchemes, NotAllow }; static inline unsigned toExternalURLsPolicy(WebCore::ShouldOpenExternalURLsPolicy policy) { switch (policy) { case WebCore::ShouldOpenExternalURLsPolicy::ShouldAllow: return ExternalURLsPolicy::Allow; case WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes: return ExternalURLsPolicy::AllowExternalSchemes; case WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow: return ExternalURLsPolicy::NotAllow; } return ExternalURLsPolicy::NotAllow; } static inline WebCore::ShouldOpenExternalURLsPolicy toWebCoreExternalURLsPolicy(unsigned policy) { switch (policy) { case ExternalURLsPolicy::Allow: return WebCore::ShouldOpenExternalURLsPolicy::ShouldAllow; case ExternalURLsPolicy::AllowExternalSchemes: return WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes; case ExternalURLsPolicy::NotAllow: return WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow; } return WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow; } enum HTMLBodyElementType { Data, File, Blob }; static inline unsigned toHTMLBodyElementType(HTTPBody::Element::Type type) { switch (type) { case HTTPBody::Element::Type::Data: return HTMLBodyElementType::Data; case HTTPBody::Element::Type::File: return HTMLBodyElementType::File; case HTTPBody::Element::Type::Blob: return HTMLBodyElementType::Blob; } return HTMLBodyElementType::Data; } static inline HTTPBody::Element::Type toHTTPBodyElementType(unsigned type) { switch (type) { case HTMLBodyElementType::Data: return HTTPBody::Element::Type::Data; case HTMLBodyElementType::File: return HTTPBody::Element::Type::File; case HTMLBodyElementType::Blob: return HTTPBody::Element::Type::Blob; } return HTTPBody::Element::Type::Data; } static inline void encodeHTTPBody(GVariantBuilder* sessionBuilder, const HTTPBody& httpBody) { g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("(sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")")); g_variant_builder_add(sessionBuilder, "s", httpBody.contentType.utf8().data()); g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("a" HTTP_BODY_ELEMENT_TYPE_STRING_V1)); g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(HTTP_BODY_ELEMENT_TYPE_STRING_V1)); for (const auto& element : httpBody.elements) { g_variant_builder_add(sessionBuilder, "u", toHTMLBodyElementType(element.type)); g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("ay")); for (auto item : element.data) g_variant_builder_add(sessionBuilder, "y", item); g_variant_builder_close(sessionBuilder); g_variant_builder_add(sessionBuilder, "s", element.filePath.utf8().data()); g_variant_builder_add(sessionBuilder, "x", element.fileStart); if (element.fileLength) g_variant_builder_add(sessionBuilder, "mx", TRUE, element.fileLength.value()); else g_variant_builder_add(sessionBuilder, "mx", FALSE); if (element.expectedFileModificationTime) g_variant_builder_add(sessionBuilder, "md", TRUE, element.expectedFileModificationTime.value()); else g_variant_builder_add(sessionBuilder, "md", FALSE); g_variant_builder_add(sessionBuilder, "s", element.blobURLString.utf8().data()); } g_variant_builder_close(sessionBuilder); g_variant_builder_close(sessionBuilder); g_variant_builder_close(sessionBuilder); } static inline void encodeFrameState(GVariantBuilder* sessionBuilder, const FrameState& frameState) { g_variant_builder_add(sessionBuilder, "s", frameState.urlString.utf8().data()); g_variant_builder_add(sessionBuilder, "s", frameState.originalURLString.utf8().data()); g_variant_builder_add(sessionBuilder, "s", frameState.referrer.utf8().data()); g_variant_builder_add(sessionBuilder, "s", frameState.target.utf8().data()); g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("as")); for (const auto& state : frameState.documentState) g_variant_builder_add(sessionBuilder, "s", state.utf8().data()); g_variant_builder_close(sessionBuilder); if (!frameState.stateObjectData) g_variant_builder_add(sessionBuilder, "may", FALSE); else { g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("may")); g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("ay")); for (auto item : frameState.stateObjectData.value()) g_variant_builder_add(sessionBuilder, "y", item); g_variant_builder_close(sessionBuilder); g_variant_builder_close(sessionBuilder); } g_variant_builder_add(sessionBuilder, "x", frameState.documentSequenceNumber); g_variant_builder_add(sessionBuilder, "x", frameState.itemSequenceNumber); g_variant_builder_add(sessionBuilder, "(ii)", frameState.scrollPosition.x(), frameState.scrollPosition.y()); g_variant_builder_add(sessionBuilder, "d", frameState.pageScaleFactor); if (!frameState.httpBody) g_variant_builder_add(sessionBuilder, HTTP_BODY_TYPE_STRING_V1, FALSE); else { g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(HTTP_BODY_TYPE_STRING_V1)); encodeHTTPBody(sessionBuilder, frameState.httpBody.value()); g_variant_builder_close(sessionBuilder); } g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("av")); for (const auto& child : frameState.children) { GVariantBuilder frameStateBuilder; g_variant_builder_init(&frameStateBuilder, G_VARIANT_TYPE(FRAME_STATE_TYPE_STRING_V1)); encodeFrameState(&frameStateBuilder, child); g_variant_builder_add(sessionBuilder, "v", g_variant_builder_end(&frameStateBuilder)); } g_variant_builder_close(sessionBuilder); } static inline void encodePageState(GVariantBuilder* sessionBuilder, const PageState& pageState) { g_variant_builder_add(sessionBuilder, "s", pageState.title.utf8().data()); g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(FRAME_STATE_TYPE_STRING_V1)); encodeFrameState(sessionBuilder, pageState.mainFrameState); g_variant_builder_close(sessionBuilder); g_variant_builder_add(sessionBuilder, "u", toExternalURLsPolicy(pageState.shouldOpenExternalURLsPolicy)); } static inline void encodeBackForwardListItemState(GVariantBuilder* sessionBuilder, const BackForwardListItemState& item) { g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1)); g_variant_builder_add(sessionBuilder, "t", item.identifier); encodePageState(sessionBuilder, item.pageState); g_variant_builder_close(sessionBuilder); } static inline void encodeBackForwardListState(GVariantBuilder* sessionBuilder, const BackForwardListState& backForwardListState) { g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("a" BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1)); for (const auto& item : backForwardListState.items) encodeBackForwardListItemState(sessionBuilder, item); g_variant_builder_close(sessionBuilder); if (backForwardListState.currentIndex) g_variant_builder_add(sessionBuilder, "mu", TRUE, backForwardListState.currentIndex.value()); else g_variant_builder_add(sessionBuilder, "mu", FALSE); } static GBytes* encodeSessionState(const SessionState& sessionState) { GVariantBuilder sessionBuilder; g_variant_builder_init(&sessionBuilder, G_VARIANT_TYPE(SESSION_STATE_TYPE_STRING_V1)); g_variant_builder_add(&sessionBuilder, "q", g_sessionStateVersion); encodeBackForwardListState(&sessionBuilder, sessionState.backForwardListState); GRefPtr variant = g_variant_builder_end(&sessionBuilder); return g_variant_get_data_as_bytes(variant.get()); } static inline bool decodeHTTPBody(GVariant* httpBodyVariant, HTTPBody& httpBody) { gboolean hasHTTPBody; const char* contentType; GUniqueOutPtr elementsIter; g_variant_get(httpBodyVariant, HTTP_BODY_FORMAT_STRING_V1, &hasHTTPBody, &contentType, &elementsIter.outPtr()); if (!hasHTTPBody) return false; httpBody.contentType = String::fromUTF8(contentType); gsize elementsLength = g_variant_iter_n_children(elementsIter.get()); if (!elementsLength) return true; httpBody.elements.reserveInitialCapacity(elementsLength); unsigned type; GVariantIter* dataIter; const char* filePath; gint64 fileStart; gboolean hasFileLength; gint64 fileLength; gboolean hasFileModificationTime; gdouble fileModificationTime; const char* blobURLString; while (g_variant_iter_loop(elementsIter.get(), HTTP_BODY_ELEMENT_FORMAT_STRING_V1, &type, &dataIter, &filePath, &fileStart, &hasFileLength, &fileLength, &hasFileModificationTime, &fileModificationTime, &blobURLString)) { HTTPBody::Element element; element.type = toHTTPBodyElementType(type); if (gsize dataLength = g_variant_iter_n_children(dataIter)) { element.data.reserveInitialCapacity(dataLength); guchar dataValue; while (g_variant_iter_next(dataIter, "y", &dataValue)) element.data.uncheckedAppend(dataValue); } element.filePath = String::fromUTF8(filePath); element.fileStart = fileStart; if (hasFileLength) element.fileLength = fileLength; if (hasFileModificationTime) element.expectedFileModificationTime = fileModificationTime; element.blobURLString = String::fromUTF8(blobURLString); httpBody.elements.uncheckedAppend(WTFMove(element)); } return true; } static inline void decodeFrameState(GVariant* frameStateVariant, FrameState& frameState) { const char* urlString; const char* originalURLString; const char* referrer; const char* target; GUniqueOutPtr documentStateIter; GUniqueOutPtr stateObjectDataIter; gint64 documentSequenceNumber; gint64 itemSequenceNumber; gint32 scrollPositionX, scrollPositionY; gdouble pageScaleFactor; GVariant* httpBodyVariant; GUniqueOutPtr childrenIter; g_variant_get(frameStateVariant, FRAME_STATE_FORMAT_STRING_V1, &urlString, &originalURLString, &referrer, &target, &documentStateIter.outPtr(), &stateObjectDataIter.outPtr(), &documentSequenceNumber, &itemSequenceNumber, &scrollPositionX, &scrollPositionY, &pageScaleFactor, &httpBodyVariant, &childrenIter.outPtr()); frameState.urlString = String::fromUTF8(urlString); frameState.originalURLString = String::fromUTF8(originalURLString); // frameState.referrer must not be an empty string since we never want to // send an empty Referer header. Bug #159606. if (strlen(referrer)) frameState.referrer = String::fromUTF8(referrer); frameState.target = String::fromUTF8(target); if (gsize documentStateLength = g_variant_iter_n_children(documentStateIter.get())) { frameState.documentState.reserveInitialCapacity(documentStateLength); const char* documentStateString; while (g_variant_iter_next(documentStateIter.get(), "&s", &documentStateString)) frameState.documentState.uncheckedAppend(String::fromUTF8(documentStateString)); } if (stateObjectDataIter) { Vector stateObjectVector; if (gsize stateObjectDataLength = g_variant_iter_n_children(stateObjectDataIter.get())) { stateObjectVector.reserveInitialCapacity(stateObjectDataLength); guchar stateObjectDataValue; while (g_variant_iter_next(stateObjectDataIter.get(), "y", &stateObjectDataValue)) stateObjectVector.uncheckedAppend(stateObjectDataValue); } frameState.stateObjectData = WTFMove(stateObjectVector); } frameState.documentSequenceNumber = documentSequenceNumber; frameState.itemSequenceNumber = itemSequenceNumber; frameState.scrollPosition.setX(scrollPositionX); frameState.scrollPosition.setY(scrollPositionY); frameState.pageScaleFactor = pageScaleFactor; HTTPBody httpBody; if (decodeHTTPBody(httpBodyVariant, httpBody)) frameState.httpBody = WTFMove(httpBody); g_variant_unref(httpBodyVariant); while (GRefPtr child = adoptGRef(g_variant_iter_next_value(childrenIter.get()))) { FrameState childFrameState; GRefPtr childVariant = adoptGRef(g_variant_get_variant(child.get())); decodeFrameState(childVariant.get(), childFrameState); frameState.children.append(WTFMove(childFrameState)); } } static inline void decodeBackForwardListItemState(GVariantIter* backForwardListStateIter, BackForwardListState& backForwardListState) { gsize backForwardListStateLength = g_variant_iter_n_children(backForwardListStateIter); if (!backForwardListStateLength) return; backForwardListState.items.reserveInitialCapacity(backForwardListStateLength); guint64 identifier; const char* title; GVariant* frameStateVariant; unsigned shouldOpenExternalURLsPolicy; while (g_variant_iter_loop(backForwardListStateIter, BACK_FORWARD_LIST_ITEM_FORMAT_STRING_V1, &identifier, &title, &frameStateVariant, &shouldOpenExternalURLsPolicy)) { BackForwardListItemState state; state.identifier = identifier; state.pageState.title = String::fromUTF8(title); decodeFrameState(frameStateVariant, state.pageState.mainFrameState); state.pageState.shouldOpenExternalURLsPolicy = toWebCoreExternalURLsPolicy(shouldOpenExternalURLsPolicy); backForwardListState.items.uncheckedAppend(WTFMove(state)); } } static bool decodeSessionState(GBytes* data, SessionState& sessionState) { GRefPtr variant = g_variant_new_from_bytes(G_VARIANT_TYPE(SESSION_STATE_TYPE_STRING_V1), data, FALSE); if (!g_variant_is_normal_form(variant.get())) return false; guint16 version; GUniqueOutPtr backForwardListStateIter; gboolean hasCurrentIndex; guint32 currentIndex; g_variant_get(variant.get(), SESSION_STATE_TYPE_STRING_V1, &version, &backForwardListStateIter.outPtr(), &hasCurrentIndex, ¤tIndex); if (!version || version > g_sessionStateVersion) return false; decodeBackForwardListItemState(backForwardListStateIter.get(), sessionState.backForwardListState); if (hasCurrentIndex) sessionState.backForwardListState.currentIndex = currentIndex; return true; } WebKitWebViewSessionState* webkitWebViewSessionStateCreate(SessionState&& sessionState) { WebKitWebViewSessionState* state = static_cast(fastMalloc(sizeof(WebKitWebViewSessionState))); new (state) WebKitWebViewSessionState(WTFMove(sessionState)); return state; } const SessionState& webkitWebViewSessionStateGetSessionState(WebKitWebViewSessionState* state) { return state->sessionState; } /** * webkit_web_view_session_state_new: * @data: a #GBytes * * Creates a new #WebKitWebViewSessionState from serialized data. * * Returns: (transfer full): a new #WebKitWebViewSessionState, or %NULL if @data doesn't contain a * valid serialized #WebKitWebViewSessionState. * * Since: 2.12 */ WebKitWebViewSessionState* webkit_web_view_session_state_new(GBytes* data) { g_return_val_if_fail(data, nullptr); SessionState sessionState; if (!decodeSessionState(data, sessionState)) return nullptr; return webkitWebViewSessionStateCreate(WTFMove(sessionState)); } /** * webkit_web_view_session_state_ref: * @state: a #WebKitWebViewSessionState * * Atomically increments the reference count of @state by one. This * function is MT-safe and may be called from any thread. * * Returns: The passed in #WebKitWebViewSessionState * * Since: 2.12 */ WebKitWebViewSessionState* webkit_web_view_session_state_ref(WebKitWebViewSessionState* state) { g_return_val_if_fail(state, nullptr); g_atomic_int_inc(&state->referenceCount); return state; } /** * webkit_web_view_session_state_unref: * @state: a #WebKitWebViewSessionState * * Atomically decrements the reference count of @state by one. If the * reference count drops to 0, all memory allocated by the #WebKitWebViewSessionState is * released. This function is MT-safe and may be called from any thread. * * Since: 2.12 */ void webkit_web_view_session_state_unref(WebKitWebViewSessionState* state) { g_return_if_fail(state); if (g_atomic_int_dec_and_test(&state->referenceCount)) { state->~WebKitWebViewSessionState(); fastFree(state); } } /** * webkit_web_view_session_state_serialize: * @state: a #WebKitWebViewSessionState * * Serializes a #WebKitWebViewSessionState. * * Returns: (transfer full): a #GBytes containing the @state serialized. * * Since: 2.12 */ GBytes* webkit_web_view_session_state_serialize(WebKitWebViewSessionState* state) { g_return_val_if_fail(state, nullptr); return encodeSessionState(state->sessionState); }