9cb5d9335505a838b9049e446803e41429bcb820
[WebKit-https.git] / Source / WebKit / UIProcess / API / glib / WebKitWebViewSessionState.cpp
1 /*
2  * Copyright (C) 2016 Igalia S.L.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21 #include "WebKitWebViewSessionState.h"
22
23 #include "WebKitWebViewSessionStatePrivate.h"
24 #include <wtf/glib/GRefPtr.h>
25 #include <wtf/glib/GUniquePtr.h>
26
27 using namespace WebKit;
28
29 struct _WebKitWebViewSessionState {
30     _WebKitWebViewSessionState(SessionState&& state)
31         : sessionState(WTFMove(state))
32         , referenceCount(1)
33     {
34     }
35
36     SessionState sessionState;
37     int referenceCount;
38 };
39
40 G_DEFINE_BOXED_TYPE(WebKitWebViewSessionState, webkit_web_view_session_state, webkit_web_view_session_state_ref, webkit_web_view_session_state_unref)
41
42 static const guint16 g_sessionStateVersion = 1;
43 #define HTTP_BODY_ELEMENT_TYPE_STRING_V1 "(uaysxmxmds)"
44 #define HTTP_BODY_ELEMENT_FORMAT_STRING_V1 "(uay&sxmxmd&s)"
45 #define HTTP_BODY_TYPE_STRING_V1 "m(sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")"
46 #define HTTP_BODY_FORMAT_STRING_V1 "m(&sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")"
47 #define FRAME_STATE_TYPE_STRING_V1  "(ssssasmayxx(ii)d" HTTP_BODY_TYPE_STRING_V1 "av)"
48 #define FRAME_STATE_FORMAT_STRING_V1  "(&s&s&s&sasmayxx(ii)d@" HTTP_BODY_TYPE_STRING_V1 "av)"
49 #define BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1  "(ts" FRAME_STATE_TYPE_STRING_V1 "u)"
50 #define BACK_FORWARD_LIST_ITEM_FORMAT_STRING_V1  "(t&s@" FRAME_STATE_TYPE_STRING_V1 "u)"
51 #define SESSION_STATE_TYPE_STRING_V1  "(qa" BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1 "mu)"
52
53 // Use our own enum types to ensure the serialized format even if the core enums change.
54 enum ExternalURLsPolicy {
55     Allow,
56     AllowExternalSchemes,
57     NotAllow
58 };
59
60 static inline unsigned toExternalURLsPolicy(WebCore::ShouldOpenExternalURLsPolicy policy)
61 {
62     switch (policy) {
63     case WebCore::ShouldOpenExternalURLsPolicy::ShouldAllow:
64         return ExternalURLsPolicy::Allow;
65     case WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes:
66         return ExternalURLsPolicy::AllowExternalSchemes;
67     case WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow:
68         return ExternalURLsPolicy::NotAllow;
69     }
70
71     return ExternalURLsPolicy::NotAllow;
72 }
73
74 static inline WebCore::ShouldOpenExternalURLsPolicy toWebCoreExternalURLsPolicy(unsigned policy)
75 {
76     switch (policy) {
77     case ExternalURLsPolicy::Allow:
78         return WebCore::ShouldOpenExternalURLsPolicy::ShouldAllow;
79     case ExternalURLsPolicy::AllowExternalSchemes:
80         return WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes;
81     case ExternalURLsPolicy::NotAllow:
82         return WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow;
83     }
84
85     return WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow;
86 }
87
88 enum HTMLBodyElementType {
89     Data,
90     File,
91     Blob
92 };
93
94 static inline unsigned toHTMLBodyElementType(HTTPBody::Element::Type type)
95 {
96     switch (type) {
97     case HTTPBody::Element::Type::Data:
98         return HTMLBodyElementType::Data;
99     case HTTPBody::Element::Type::File:
100         return HTMLBodyElementType::File;
101     case HTTPBody::Element::Type::Blob:
102         return HTMLBodyElementType::Blob;
103     }
104
105     return HTMLBodyElementType::Data;
106 }
107
108 static inline HTTPBody::Element::Type toHTTPBodyElementType(unsigned type)
109 {
110     switch (type) {
111     case HTMLBodyElementType::Data:
112         return HTTPBody::Element::Type::Data;
113     case HTMLBodyElementType::File:
114         return HTTPBody::Element::Type::File;
115     case HTMLBodyElementType::Blob:
116         return HTTPBody::Element::Type::Blob;
117     }
118
119     return HTTPBody::Element::Type::Data;
120 }
121
122 static inline void encodeHTTPBody(GVariantBuilder* sessionBuilder, const HTTPBody& httpBody)
123 {
124     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("(sa" HTTP_BODY_ELEMENT_TYPE_STRING_V1 ")"));
125     g_variant_builder_add(sessionBuilder, "s", httpBody.contentType.utf8().data());
126     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("a" HTTP_BODY_ELEMENT_TYPE_STRING_V1));
127     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(HTTP_BODY_ELEMENT_TYPE_STRING_V1));
128     for (const auto& element : httpBody.elements) {
129         g_variant_builder_add(sessionBuilder, "u", toHTMLBodyElementType(element.type));
130         g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("ay"));
131         for (auto item : element.data)
132             g_variant_builder_add(sessionBuilder, "y", item);
133         g_variant_builder_close(sessionBuilder);
134         g_variant_builder_add(sessionBuilder, "s", element.filePath.utf8().data());
135         g_variant_builder_add(sessionBuilder, "x", element.fileStart);
136         if (element.fileLength)
137             g_variant_builder_add(sessionBuilder, "mx", TRUE, element.fileLength.value());
138         else
139             g_variant_builder_add(sessionBuilder, "mx", FALSE);
140         if (element.expectedFileModificationTime)
141             g_variant_builder_add(sessionBuilder, "md", TRUE, element.expectedFileModificationTime.value());
142         else
143             g_variant_builder_add(sessionBuilder, "md", FALSE);
144         g_variant_builder_add(sessionBuilder, "s", element.blobURLString.utf8().data());
145     }
146     g_variant_builder_close(sessionBuilder);
147     g_variant_builder_close(sessionBuilder);
148     g_variant_builder_close(sessionBuilder);
149 }
150
151 static inline void encodeFrameState(GVariantBuilder* sessionBuilder, const FrameState& frameState)
152 {
153     g_variant_builder_add(sessionBuilder, "s", frameState.urlString.utf8().data());
154     g_variant_builder_add(sessionBuilder, "s", frameState.originalURLString.utf8().data());
155     g_variant_builder_add(sessionBuilder, "s", frameState.referrer.utf8().data());
156     g_variant_builder_add(sessionBuilder, "s", frameState.target.utf8().data());
157     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("as"));
158     for (const auto& state : frameState.documentState)
159         g_variant_builder_add(sessionBuilder, "s", state.utf8().data());
160     g_variant_builder_close(sessionBuilder);
161     if (!frameState.stateObjectData)
162         g_variant_builder_add(sessionBuilder, "may", FALSE);
163     else {
164         g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("may"));
165         g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("ay"));
166         for (auto item : frameState.stateObjectData.value())
167             g_variant_builder_add(sessionBuilder, "y", item);
168         g_variant_builder_close(sessionBuilder);
169         g_variant_builder_close(sessionBuilder);
170     }
171     g_variant_builder_add(sessionBuilder, "x", frameState.documentSequenceNumber);
172     g_variant_builder_add(sessionBuilder, "x", frameState.itemSequenceNumber);
173     g_variant_builder_add(sessionBuilder, "(ii)", frameState.scrollPosition.x(), frameState.scrollPosition.y());
174     g_variant_builder_add(sessionBuilder, "d", static_cast<gdouble>(frameState.pageScaleFactor));
175     if (!frameState.httpBody)
176         g_variant_builder_add(sessionBuilder, HTTP_BODY_TYPE_STRING_V1, FALSE);
177     else {
178         g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(HTTP_BODY_TYPE_STRING_V1));
179         encodeHTTPBody(sessionBuilder, frameState.httpBody.value());
180         g_variant_builder_close(sessionBuilder);
181     }
182     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("av"));
183     for (const auto& child : frameState.children) {
184         GVariantBuilder frameStateBuilder;
185         g_variant_builder_init(&frameStateBuilder, G_VARIANT_TYPE(FRAME_STATE_TYPE_STRING_V1));
186         encodeFrameState(&frameStateBuilder, child);
187         g_variant_builder_add(sessionBuilder, "v", g_variant_builder_end(&frameStateBuilder));
188     }
189     g_variant_builder_close(sessionBuilder);
190 }
191
192 static inline void encodePageState(GVariantBuilder* sessionBuilder, const PageState& pageState)
193 {
194     g_variant_builder_add(sessionBuilder, "s", pageState.title.utf8().data());
195     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(FRAME_STATE_TYPE_STRING_V1));
196     encodeFrameState(sessionBuilder, pageState.mainFrameState);
197     g_variant_builder_close(sessionBuilder);
198     g_variant_builder_add(sessionBuilder, "u", toExternalURLsPolicy(pageState.shouldOpenExternalURLsPolicy));
199 }
200
201 static inline void encodeBackForwardListItemState(GVariantBuilder* sessionBuilder, const BackForwardListItemState& item)
202 {
203     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE(BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1));
204     g_variant_builder_add(sessionBuilder, "t", item.identifier);
205     encodePageState(sessionBuilder, item.pageState);
206     g_variant_builder_close(sessionBuilder);
207 }
208
209 static inline void encodeBackForwardListState(GVariantBuilder* sessionBuilder, const BackForwardListState& backForwardListState)
210 {
211     g_variant_builder_open(sessionBuilder, G_VARIANT_TYPE("a" BACK_FORWARD_LIST_ITEM_TYPE_STRING_V1));
212     for (const auto& item : backForwardListState.items)
213         encodeBackForwardListItemState(sessionBuilder, item);
214     g_variant_builder_close(sessionBuilder);
215
216     if (backForwardListState.currentIndex)
217         g_variant_builder_add(sessionBuilder, "mu", TRUE, backForwardListState.currentIndex.value());
218     else
219         g_variant_builder_add(sessionBuilder, "mu", FALSE);
220 }
221
222 static GBytes* encodeSessionState(const SessionState& sessionState)
223 {
224     GVariantBuilder sessionBuilder;
225     g_variant_builder_init(&sessionBuilder, G_VARIANT_TYPE(SESSION_STATE_TYPE_STRING_V1));
226     g_variant_builder_add(&sessionBuilder, "q", g_sessionStateVersion);
227     encodeBackForwardListState(&sessionBuilder, sessionState.backForwardListState);
228     GRefPtr<GVariant> variant = g_variant_builder_end(&sessionBuilder);
229     return g_variant_get_data_as_bytes(variant.get());
230 }
231
232 static inline bool decodeHTTPBody(GVariant* httpBodyVariant, HTTPBody& httpBody)
233 {
234     gboolean hasHTTPBody;
235     const char* contentType;
236     GUniqueOutPtr<GVariantIter> elementsIter;
237     g_variant_get(httpBodyVariant, HTTP_BODY_FORMAT_STRING_V1, &hasHTTPBody, &contentType, &elementsIter.outPtr());
238     if (!hasHTTPBody)
239         return false;
240     httpBody.contentType = String::fromUTF8(contentType);
241     gsize elementsLength = g_variant_iter_n_children(elementsIter.get());
242     if (!elementsLength)
243         return true;
244     httpBody.elements.reserveInitialCapacity(elementsLength);
245     unsigned type;
246     GVariantIter* dataIter;
247     const char* filePath;
248     gint64 fileStart;
249     gboolean hasFileLength;
250     gint64 fileLength;
251     gboolean hasFileModificationTime;
252     gdouble fileModificationTime;
253     const char* blobURLString;
254     while (g_variant_iter_loop(elementsIter.get(), HTTP_BODY_ELEMENT_FORMAT_STRING_V1, &type, &dataIter, &filePath, &fileStart, &hasFileLength, &fileLength, &hasFileModificationTime, &fileModificationTime, &blobURLString)) {
255         HTTPBody::Element element;
256         element.type = toHTTPBodyElementType(type);
257         if (gsize dataLength = g_variant_iter_n_children(dataIter)) {
258             element.data.reserveInitialCapacity(dataLength);
259             guchar dataValue;
260             while (g_variant_iter_next(dataIter, "y", &dataValue))
261                 element.data.uncheckedAppend(dataValue);
262         }
263         element.filePath = String::fromUTF8(filePath);
264         element.fileStart = fileStart;
265         if (hasFileLength)
266             element.fileLength = fileLength;
267         if (hasFileModificationTime)
268             element.expectedFileModificationTime = fileModificationTime;
269         element.blobURLString = String::fromUTF8(blobURLString);
270
271         httpBody.elements.uncheckedAppend(WTFMove(element));
272     }
273
274     return true;
275 }
276
277 static inline void decodeFrameState(GVariant* frameStateVariant, FrameState& frameState)
278 {
279     const char* urlString;
280     const char* originalURLString;
281     const char* referrer;
282     const char* target;
283     GUniqueOutPtr<GVariantIter> documentStateIter;
284     GUniqueOutPtr<GVariantIter> stateObjectDataIter;
285     gint64 documentSequenceNumber;
286     gint64 itemSequenceNumber;
287     gint32 scrollPositionX, scrollPositionY;
288     gdouble pageScaleFactor;
289     GVariant* httpBodyVariant;
290     GUniqueOutPtr<GVariantIter> childrenIter;
291     g_variant_get(frameStateVariant, FRAME_STATE_FORMAT_STRING_V1, &urlString, &originalURLString, &referrer, &target,
292         &documentStateIter.outPtr(), &stateObjectDataIter.outPtr(), &documentSequenceNumber, &itemSequenceNumber,
293         &scrollPositionX, &scrollPositionY, &pageScaleFactor, &httpBodyVariant, &childrenIter.outPtr());
294     frameState.urlString = String::fromUTF8(urlString);
295     frameState.originalURLString = String::fromUTF8(originalURLString);
296     // frameState.referrer must not be an empty string since we never want to
297     // send an empty Referer header. Bug #159606.
298     if (strlen(referrer))
299         frameState.referrer = String::fromUTF8(referrer);
300     frameState.target = String::fromUTF8(target);
301     if (gsize documentStateLength = g_variant_iter_n_children(documentStateIter.get())) {
302         frameState.documentState.reserveInitialCapacity(documentStateLength);
303         const char* documentStateString;
304         while (g_variant_iter_next(documentStateIter.get(), "&s", &documentStateString))
305             frameState.documentState.uncheckedAppend(String::fromUTF8(documentStateString));
306     }
307     if (stateObjectDataIter) {
308         Vector<uint8_t> stateObjectVector;
309         if (gsize stateObjectDataLength = g_variant_iter_n_children(stateObjectDataIter.get())) {
310             stateObjectVector.reserveInitialCapacity(stateObjectDataLength);
311             guchar stateObjectDataValue;
312             while (g_variant_iter_next(stateObjectDataIter.get(), "y", &stateObjectDataValue))
313                 stateObjectVector.uncheckedAppend(stateObjectDataValue);
314         }
315         frameState.stateObjectData = WTFMove(stateObjectVector);
316     }
317     frameState.documentSequenceNumber = documentSequenceNumber;
318     frameState.itemSequenceNumber = itemSequenceNumber;
319     frameState.scrollPosition.setX(scrollPositionX);
320     frameState.scrollPosition.setY(scrollPositionY);
321     frameState.pageScaleFactor = pageScaleFactor;
322     HTTPBody httpBody;
323     if (decodeHTTPBody(httpBodyVariant, httpBody))
324         frameState.httpBody = WTFMove(httpBody);
325     g_variant_unref(httpBodyVariant);
326     while (GRefPtr<GVariant> child = adoptGRef(g_variant_iter_next_value(childrenIter.get()))) {
327         FrameState childFrameState;
328         GRefPtr<GVariant> childVariant = adoptGRef(g_variant_get_variant(child.get()));
329         decodeFrameState(childVariant.get(), childFrameState);
330         frameState.children.append(WTFMove(childFrameState));
331     }
332 }
333
334 static inline void decodeBackForwardListItemState(GVariantIter* backForwardListStateIter, BackForwardListState& backForwardListState)
335 {
336     gsize backForwardListStateLength = g_variant_iter_n_children(backForwardListStateIter);
337     if (!backForwardListStateLength)
338         return;
339
340     backForwardListState.items.reserveInitialCapacity(backForwardListStateLength);
341     guint64 identifier;
342     const char* title;
343     GVariant* frameStateVariant;
344     unsigned shouldOpenExternalURLsPolicy;
345     while (g_variant_iter_loop(backForwardListStateIter, BACK_FORWARD_LIST_ITEM_FORMAT_STRING_V1, &identifier, &title, &frameStateVariant, &shouldOpenExternalURLsPolicy)) {
346         BackForwardListItemState state;
347         state.identifier = identifier;
348         state.pageState.title = String::fromUTF8(title);
349         decodeFrameState(frameStateVariant, state.pageState.mainFrameState);
350         state.pageState.shouldOpenExternalURLsPolicy = toWebCoreExternalURLsPolicy(shouldOpenExternalURLsPolicy);
351         backForwardListState.items.uncheckedAppend(WTFMove(state));
352     }
353 }
354
355 static bool decodeSessionState(GBytes* data, SessionState& sessionState)
356 {
357     GRefPtr<GVariant> variant = g_variant_new_from_bytes(G_VARIANT_TYPE(SESSION_STATE_TYPE_STRING_V1), data, FALSE);
358     if (!g_variant_is_normal_form(variant.get()))
359         return false;
360
361     guint16 version;
362     GUniqueOutPtr<GVariantIter> backForwardListStateIter;
363     gboolean hasCurrentIndex;
364     guint32 currentIndex;
365     g_variant_get(variant.get(), SESSION_STATE_TYPE_STRING_V1, &version, &backForwardListStateIter.outPtr(), &hasCurrentIndex, &currentIndex);
366     if (!version || version > g_sessionStateVersion)
367         return false;
368
369     decodeBackForwardListItemState(backForwardListStateIter.get(), sessionState.backForwardListState);
370
371     if (hasCurrentIndex)
372         sessionState.backForwardListState.currentIndex = std::min<uint32_t>(currentIndex, sessionState.backForwardListState.items.size() - 1);
373     return true;
374 }
375
376 WebKitWebViewSessionState* webkitWebViewSessionStateCreate(SessionState&& sessionState)
377 {
378     WebKitWebViewSessionState* state = static_cast<WebKitWebViewSessionState*>(fastMalloc(sizeof(WebKitWebViewSessionState)));
379     new (state) WebKitWebViewSessionState(WTFMove(sessionState));
380     return state;
381 }
382
383 const SessionState& webkitWebViewSessionStateGetSessionState(WebKitWebViewSessionState* state)
384 {
385     return state->sessionState;
386 }
387
388 /**
389  * webkit_web_view_session_state_new:
390  * @data: a #GBytes
391  *
392  * Creates a new #WebKitWebViewSessionState from serialized data.
393  *
394  * Returns: (transfer full): a new #WebKitWebViewSessionState, or %NULL if @data doesn't contain a
395  *     valid serialized #WebKitWebViewSessionState.
396  *
397  * Since: 2.12
398  */
399 WebKitWebViewSessionState* webkit_web_view_session_state_new(GBytes* data)
400 {
401     g_return_val_if_fail(data, nullptr);
402
403     SessionState sessionState;
404     if (!decodeSessionState(data, sessionState))
405         return nullptr;
406     return webkitWebViewSessionStateCreate(WTFMove(sessionState));
407 }
408
409 /**
410  * webkit_web_view_session_state_ref:
411  * @state: a #WebKitWebViewSessionState
412  *
413  * Atomically increments the reference count of @state by one. This
414  * function is MT-safe and may be called from any thread.
415  *
416  * Returns: The passed in #WebKitWebViewSessionState
417  *
418  * Since: 2.12
419  */
420 WebKitWebViewSessionState* webkit_web_view_session_state_ref(WebKitWebViewSessionState* state)
421 {
422     g_return_val_if_fail(state, nullptr);
423     g_atomic_int_inc(&state->referenceCount);
424     return state;
425 }
426
427 /**
428  * webkit_web_view_session_state_unref:
429  * @state: a #WebKitWebViewSessionState
430  *
431  * Atomically decrements the reference count of @state by one. If the
432  * reference count drops to 0, all memory allocated by the #WebKitWebViewSessionState is
433  * released. This function is MT-safe and may be called from any thread.
434  *
435  * Since: 2.12
436  */
437 void webkit_web_view_session_state_unref(WebKitWebViewSessionState* state)
438 {
439     g_return_if_fail(state);
440     if (g_atomic_int_dec_and_test(&state->referenceCount)) {
441         state->~WebKitWebViewSessionState();
442         fastFree(state);
443     }
444 }
445
446 /**
447  * webkit_web_view_session_state_serialize:
448  * @state: a #WebKitWebViewSessionState
449  *
450  * Serializes a #WebKitWebViewSessionState.
451  *
452  * Returns: (transfer full): a #GBytes containing the @state serialized.
453  *
454  * Since: 2.12
455  */
456 GBytes* webkit_web_view_session_state_serialize(WebKitWebViewSessionState* state)
457 {
458     g_return_val_if_fail(state, nullptr);
459
460     return encodeSessionState(state->sessionState);
461 }