summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/platform/network/soup/cache/webkit/soup-cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/platform/network/soup/cache/webkit/soup-cache.c')
-rw-r--r--Source/WebCore/platform/network/soup/cache/webkit/soup-cache.c1677
1 files changed, 0 insertions, 1677 deletions
diff --git a/Source/WebCore/platform/network/soup/cache/webkit/soup-cache.c b/Source/WebCore/platform/network/soup/cache/webkit/soup-cache.c
deleted file mode 100644
index b96428d..0000000
--- a/Source/WebCore/platform/network/soup/cache/webkit/soup-cache.c
+++ /dev/null
@@ -1,1677 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/*
- * soup-cache.c
- *
- * Copyright (C) 2009, 2010 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.
- */
-
-/* TODO:
- * - Need to hook the feature in the sync SoupSession.
- * - Need more tests.
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include "soup-cache.h"
-#include "soup-cache-private.h"
-#include <libsoup/soup.h>
-#include <gio/gio.h>
-#include <stdlib.h>
-
-static SoupSessionFeatureInterface *webkit_soup_cache_default_feature_interface;
-static void webkit_soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
-
-#define DEFAULT_MAX_SIZE 50 * 1024 * 1024
-#define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size
- of the cache that can be
- filled by a single entry */
-
-typedef struct _WebKitSoupCacheEntry {
- char *key;
- char *filename;
- guint freshness_lifetime;
- gboolean must_revalidate;
- GString *data;
- gsize pos;
- gsize length;
- time_t corrected_initial_age;
- time_t response_time;
- gboolean writing;
- gboolean dirty;
- gboolean got_body;
- gboolean being_validated;
- SoupMessageHeaders *headers;
- GOutputStream *stream;
- GError *error;
- guint hits;
- GCancellable *cancellable;
-} WebKitSoupCacheEntry;
-
-struct _WebKitSoupCachePrivate {
- char *cache_dir;
- GHashTable *cache;
- guint n_pending;
- SoupSession *session;
- WebKitSoupCacheType cache_type;
- guint size;
- guint max_size;
- guint max_entry_data_size; /* Computed value. Here for performance reasons */
- GList *lru_start;
-};
-
-typedef struct {
- WebKitSoupCache *cache;
- WebKitSoupCacheEntry *entry;
- SoupMessage *msg;
- gulong got_chunk_handler;
- gulong got_body_handler;
- gulong restarted_handler;
-} WebKitSoupCacheWritingFixture;
-
-enum {
- PROP_0,
- PROP_CACHE_DIR,
- PROP_CACHE_TYPE
-};
-
-#define WEBKIT_SOUP_CACHE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), WEBKIT_TYPE_SOUP_CACHE, WebKitSoupCachePrivate))
-
-G_DEFINE_TYPE_WITH_CODE (WebKitSoupCache, webkit_soup_cache, G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
- webkit_soup_cache_session_feature_init))
-
-static gboolean webkit_soup_cache_entry_remove (WebKitSoupCache *cache, WebKitSoupCacheEntry *entry);
-static void make_room_for_new_entry (WebKitSoupCache *cache, guint length_to_add);
-static gboolean cache_accepts_entries_of_size (WebKitSoupCache *cache, guint length_to_add);
-
-static WebKitSoupCacheability
-get_cacheability (WebKitSoupCache *cache, SoupMessage *msg)
-{
- WebKitSoupCacheability cacheability;
- const char *cache_control;
-
- /* 1. The request method must be cacheable */
- if (msg->method == SOUP_METHOD_GET)
- cacheability = WEBKIT_SOUP_CACHE_CACHEABLE;
- else if (msg->method == SOUP_METHOD_HEAD ||
- msg->method == SOUP_METHOD_TRACE ||
- msg->method == SOUP_METHOD_CONNECT)
- return WEBKIT_SOUP_CACHE_UNCACHEABLE;
- else
- return (WEBKIT_SOUP_CACHE_UNCACHEABLE | WEBKIT_SOUP_CACHE_INVALIDATES);
-
- cache_control = soup_message_headers_get (msg->response_headers, "Cache-Control");
- if (cache_control) {
- GHashTable *hash;
- WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE_GET_PRIVATE (cache);
-
- hash = soup_header_parse_param_list (cache_control);
-
- /* Shared caches MUST NOT store private resources */
- if (priv->cache_type == WEBKIT_SOUP_CACHE_SHARED) {
- if (g_hash_table_lookup_extended (hash, "private", NULL, NULL)) {
- soup_header_free_param_list (hash);
- return WEBKIT_SOUP_CACHE_UNCACHEABLE;
- }
- }
-
- /* 2. The 'no-store' cache directive does not appear in the
- * headers
- */
- if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
- soup_header_free_param_list (hash);
- return WEBKIT_SOUP_CACHE_UNCACHEABLE;
- }
-
- /* This does not appear in section 2.1, but I think it makes
- * sense to check it too?
- */
- if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
- soup_header_free_param_list (hash);
- return WEBKIT_SOUP_CACHE_UNCACHEABLE;
- }
-
- soup_header_free_param_list (hash);
- }
-
- switch (msg->status_code) {
- case SOUP_STATUS_PARTIAL_CONTENT:
- /* We don't cache partial responses, but they only
- * invalidate cached full responses if the headers
- * don't match.
- */
- cacheability = WEBKIT_SOUP_CACHE_UNCACHEABLE;
- break;
-
- case SOUP_STATUS_NOT_MODIFIED:
- /* A 304 response validates an existing cache entry */
- cacheability = WEBKIT_SOUP_CACHE_VALIDATES;
- break;
-
- case SOUP_STATUS_MULTIPLE_CHOICES:
- case SOUP_STATUS_MOVED_PERMANENTLY:
- case SOUP_STATUS_GONE:
- /* FIXME: cacheable unless indicated otherwise */
- cacheability = WEBKIT_SOUP_CACHE_UNCACHEABLE;
- break;
-
- case SOUP_STATUS_FOUND:
- case SOUP_STATUS_TEMPORARY_REDIRECT:
- /* FIXME: cacheable if explicitly indicated */
- cacheability = WEBKIT_SOUP_CACHE_UNCACHEABLE;
- break;
-
- case SOUP_STATUS_SEE_OTHER:
- case SOUP_STATUS_FORBIDDEN:
- case SOUP_STATUS_NOT_FOUND:
- case SOUP_STATUS_METHOD_NOT_ALLOWED:
- return (WEBKIT_SOUP_CACHE_UNCACHEABLE | WEBKIT_SOUP_CACHE_INVALIDATES);
-
- default:
- /* Any 5xx status or any 4xx status not handled above
- * is uncacheable but doesn't break the cache.
- */
- if ((msg->status_code >= SOUP_STATUS_BAD_REQUEST &&
- msg->status_code <= SOUP_STATUS_FAILED_DEPENDENCY) ||
- msg->status_code >= SOUP_STATUS_INTERNAL_SERVER_ERROR)
- return WEBKIT_SOUP_CACHE_UNCACHEABLE;
-
- /* An unrecognized 2xx, 3xx, or 4xx response breaks
- * the cache.
- */
- if ((msg->status_code > SOUP_STATUS_PARTIAL_CONTENT &&
- msg->status_code < SOUP_STATUS_MULTIPLE_CHOICES) ||
- (msg->status_code > SOUP_STATUS_TEMPORARY_REDIRECT &&
- msg->status_code < SOUP_STATUS_INTERNAL_SERVER_ERROR))
- return (WEBKIT_SOUP_CACHE_UNCACHEABLE | WEBKIT_SOUP_CACHE_INVALIDATES);
- break;
- }
-
- return cacheability;
-}
-
-static void
-webkit_soup_cache_entry_free (WebKitSoupCacheEntry *entry, gboolean purge)
-{
- if (purge) {
- GFile *file = g_file_new_for_path (entry->filename);
- g_file_delete (file, NULL, NULL);
- g_object_unref (file);
- }
-
- g_free (entry->filename);
- entry->filename = NULL;
- g_free (entry->key);
- entry->key = NULL;
-
- if (entry->headers) {
- soup_message_headers_free (entry->headers);
- entry->headers = NULL;
- }
-
- if (entry->data) {
- g_string_free (entry->data, TRUE);
- entry->data = NULL;
- }
- if (entry->error) {
- g_error_free (entry->error);
- entry->error = NULL;
- }
- if (entry->cancellable) {
- g_object_unref (entry->cancellable);
- entry->cancellable = NULL;
- }
-
- g_slice_free (WebKitSoupCacheEntry, entry);
-}
-
-static void
-copy_headers (const char *name, const char *value, SoupMessageHeaders *headers)
-{
- soup_message_headers_append (headers, name, value);
-}
-
-static void
-update_headers (const char *name, const char *value, SoupMessageHeaders *headers)
-{
- if (soup_message_headers_get (headers, name))
- soup_message_headers_replace (headers, name, value);
- else
- soup_message_headers_append (headers, name, value);
-}
-
-static guint
-webkit_soup_cache_entry_get_current_age (WebKitSoupCacheEntry *entry)
-{
- time_t now = time (NULL);
- time_t resident_time;
-
- resident_time = now - entry->response_time;
- return entry->corrected_initial_age + resident_time;
-}
-
-static gboolean
-webkit_soup_cache_entry_is_fresh_enough (WebKitSoupCacheEntry *entry, gint min_fresh)
-{
- guint limit = (min_fresh == -1) ? webkit_soup_cache_entry_get_current_age (entry) : (guint) min_fresh;
- return entry->freshness_lifetime > limit;
-}
-
-static char *
-soup_message_get_cache_key (SoupMessage *msg)
-{
- SoupURI *uri = soup_message_get_uri (msg);
- return soup_uri_to_string (uri, FALSE);
-}
-
-static void
-webkit_soup_cache_entry_set_freshness (WebKitSoupCacheEntry *entry, SoupMessage *msg, WebKitSoupCache *cache)
-{
- const char *cache_control;
- const char *expires, *date, *last_modified;
-
- cache_control = soup_message_headers_get (entry->headers, "Cache-Control");
- if (cache_control) {
- const char *max_age, *s_maxage;
- gint64 freshness_lifetime = 0;
- GHashTable *hash;
- WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE_GET_PRIVATE (cache);
-
- hash = soup_header_parse_param_list (cache_control);
-
- /* Should we re-validate the entry when it goes stale */
- entry->must_revalidate = g_hash_table_lookup_extended (hash, "must-revalidate", NULL, NULL);
-
- /* Section 2.3.1 */
- if (priv->cache_type == WEBKIT_SOUP_CACHE_SHARED) {
- s_maxage = g_hash_table_lookup (hash, "s-maxage");
- if (s_maxage) {
- freshness_lifetime = g_ascii_strtoll (s_maxage, NULL, 10);
- if (freshness_lifetime) {
- /* Implies proxy-revalidate. TODO: is it true? */
- entry->must_revalidate = TRUE;
- soup_header_free_param_list (hash);
- return;
- }
- }
- }
-
- /* If 'max-age' cache directive is present, use that */
- max_age = g_hash_table_lookup (hash, "max-age");
- if (max_age)
- freshness_lifetime = g_ascii_strtoll (max_age, NULL, 10);
-
- if (freshness_lifetime) {
- entry->freshness_lifetime = (guint)MIN (freshness_lifetime, G_MAXUINT32);
- soup_header_free_param_list (hash);
- return;
- }
-
- soup_header_free_param_list (hash);
- }
-
- /* If the 'Expires' response header is present, use its value
- * minus the value of the 'Date' response header
- */
- expires = soup_message_headers_get (entry->headers, "Expires");
- date = soup_message_headers_get (entry->headers, "Date");
- if (expires && date) {
- SoupDate *expires_d, *date_d;
- time_t expires_t, date_t;
-
- expires_d = soup_date_new_from_string (expires);
- if (expires_d) {
- date_d = soup_date_new_from_string (date);
-
- expires_t = soup_date_to_time_t (expires_d);
- date_t = soup_date_to_time_t (date_d);
-
- soup_date_free (expires_d);
- soup_date_free (date_d);
-
- if (expires_t && date_t) {
- entry->freshness_lifetime = (guint)MAX (expires_t - date_t, 0);
- return;
- }
- } else {
- /* If Expires is not a valid date we should
- treat it as already expired, see section
- 3.3 */
- entry->freshness_lifetime = 0;
- return;
- }
- }
-
- /* Otherwise an heuristic may be used */
-
- /* Heuristics MUST NOT be used with these status codes
- (section 2.3.1.1) */
- if (msg->status_code != SOUP_STATUS_OK &&
- msg->status_code != SOUP_STATUS_NON_AUTHORITATIVE &&
- msg->status_code != SOUP_STATUS_PARTIAL_CONTENT &&
- msg->status_code != SOUP_STATUS_MULTIPLE_CHOICES &&
- msg->status_code != SOUP_STATUS_MOVED_PERMANENTLY &&
- msg->status_code != SOUP_STATUS_GONE)
- goto expire;
-
- /* TODO: attach warning 113 if response's current_age is more
- than 24h (section 2.3.1.1) when using heuristics */
-
- /* Last-Modified based heuristic */
- last_modified = soup_message_headers_get (entry->headers, "Last-Modified");
- if (last_modified) {
- SoupDate *soup_date;
- time_t now, last_modified_t;
-
- soup_date = soup_date_new_from_string (last_modified);
- last_modified_t = soup_date_to_time_t (soup_date);
- now = time (NULL);
-
-#define HEURISTIC_FACTOR 0.1 /* From Section 2.3.1.1 */
-
- entry->freshness_lifetime = MAX (0, (now - last_modified_t) * HEURISTIC_FACTOR);
- soup_date_free (soup_date);
- }
-
- return;
-
- expire:
- /* If all else fails, make the entry expire immediately */
- entry->freshness_lifetime = 0;
-}
-
-static WebKitSoupCacheEntry *
-webkit_soup_cache_entry_new (WebKitSoupCache *cache, SoupMessage *msg, time_t request_time, time_t response_time)
-{
- WebKitSoupCacheEntry *entry;
- SoupMessageHeaders *headers;
- const char *date;
- char *md5;
-
- entry = g_slice_new0 (WebKitSoupCacheEntry);
- entry->dirty = FALSE;
- entry->writing = FALSE;
- entry->got_body = FALSE;
- entry->being_validated = FALSE;
- entry->data = g_string_new (NULL);
- entry->pos = 0;
- entry->error = NULL;
-
- /* key & filename */
- entry->key = soup_message_get_cache_key (msg);
- md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, entry->key, -1);
- entry->filename = g_build_filename (cache->priv->cache_dir, md5, NULL);
- g_free (md5);
-
- /* Headers */
- headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
- soup_message_headers_foreach (msg->response_headers,
- (SoupMessageHeadersForeachFunc)copy_headers,
- headers);
- entry->headers = headers;
-
- /* LRU list */
- entry->hits = 0;
-
- /* Section 2.3.1, Freshness Lifetime */
- webkit_soup_cache_entry_set_freshness (entry, msg, cache);
-
- /* Section 2.3.2, Calculating Age */
- date = soup_message_headers_get (entry->headers, "Date");
-
- if (date) {
- SoupDate *soup_date;
- const char *age;
- time_t date_value, apparent_age, corrected_received_age, response_delay, age_value = 0;
-
- soup_date = soup_date_new_from_string (date);
- date_value = soup_date_to_time_t (soup_date);
- soup_date_free (soup_date);
-
- age = soup_message_headers_get (entry->headers, "Age");
- if (age)
- age_value = g_ascii_strtoll (age, NULL, 10);
-
- entry->response_time = response_time;
- apparent_age = MAX (0, entry->response_time - date_value);
- corrected_received_age = MAX (apparent_age, age_value);
- response_delay = entry->response_time - request_time;
- entry->corrected_initial_age = corrected_received_age + response_delay;
- } else {
- /* Is this correct ? */
- entry->corrected_initial_age = time (NULL);
- }
-
- return entry;
-}
-
-static void
-webkit_soup_cache_writing_fixture_free (WebKitSoupCacheWritingFixture *fixture)
-{
- /* Free fixture. And disconnect signals, we don't want to
- listen to more SoupMessage events as we're finished with
- this resource */
- if (g_signal_handler_is_connected (fixture->msg, fixture->got_chunk_handler))
- g_signal_handler_disconnect (fixture->msg, fixture->got_chunk_handler);
- if (g_signal_handler_is_connected (fixture->msg, fixture->got_body_handler))
- g_signal_handler_disconnect (fixture->msg, fixture->got_body_handler);
- if (g_signal_handler_is_connected (fixture->msg, fixture->restarted_handler))
- g_signal_handler_disconnect (fixture->msg, fixture->restarted_handler);
- g_object_unref (fixture->msg);
- g_object_unref (fixture->cache);
- g_slice_free (WebKitSoupCacheWritingFixture, fixture);
-}
-
-static void
-close_ready_cb (GObject *source, GAsyncResult *result, WebKitSoupCacheWritingFixture *fixture)
-{
- WebKitSoupCacheEntry *entry = fixture->entry;
- WebKitSoupCache *cache = fixture->cache;
- GOutputStream *stream = G_OUTPUT_STREAM (source);
- goffset content_length;
-
- g_warn_if_fail (entry->error == NULL);
-
- /* FIXME: what do we do on error ? */
-
- if (stream) {
- g_output_stream_close_finish (stream, result, NULL);
- g_object_unref (stream);
- }
- entry->stream = NULL;
-
- content_length = soup_message_headers_get_content_length (entry->headers);
-
- /* If the process was cancelled, then delete the entry from
- the cache. Do it also if the size of a chunked resource is
- too much for the cache */
- if (g_cancellable_is_cancelled (entry->cancellable)) {
- entry->dirty = FALSE;
- webkit_soup_cache_entry_remove (cache, entry);
- webkit_soup_cache_entry_free (entry, TRUE);
- entry = NULL;
- } else if ((soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CHUNKED) ||
- entry->length != (gsize) content_length) {
- /** Two options here:
- *
- * 1. "chunked" data, entry was temporarily added to
- * cache (as content-length is 0) and now that we have
- * the actual size we have to evaluate if we want it
- * in the cache or not
- *
- * 2. Content-Length has a different value than actual
- * length, means that the content was encoded for
- * transmission (typically compressed) and thus we
- * have to substract the content-length value that was
- * added to the cache and add the unencoded length
- **/
- gint length_to_add = entry->length - content_length;
-
- /* Make room in cache if needed */
- if (cache_accepts_entries_of_size (cache, length_to_add)) {
- make_room_for_new_entry (cache, length_to_add);
-
- cache->priv->size += length_to_add;
- } else {
- entry->dirty = FALSE;
- webkit_soup_cache_entry_remove (cache, entry);
- webkit_soup_cache_entry_free (entry, TRUE);
- entry = NULL;
- }
- }
-
- if (entry) {
- /* Get rid of the GString in memory for the resource now */
- if (entry->data) {
- g_string_free (entry->data, TRUE);
- entry->data = NULL;
- }
-
- entry->dirty = FALSE;
- entry->writing = FALSE;
- entry->got_body = FALSE;
- entry->pos = 0;
-
- g_object_unref (entry->cancellable);
- entry->cancellable = NULL;
- }
-
- cache->priv->n_pending--;
-
- /* Frees */
- webkit_soup_cache_writing_fixture_free (fixture);
-}
-
-static void
-write_ready_cb (GObject *source, GAsyncResult *result, WebKitSoupCacheWritingFixture *fixture)
-{
- GOutputStream *stream = G_OUTPUT_STREAM (source);
- GError *error = NULL;
- gssize write_size;
- WebKitSoupCacheEntry *entry = fixture->entry;
-
- if (g_cancellable_is_cancelled (entry->cancellable)) {
- g_output_stream_close_async (stream,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)close_ready_cb,
- fixture);
- return;
- }
-
- write_size = g_output_stream_write_finish (stream, result, &error);
- if (write_size <= 0 || error) {
- if (error)
- entry->error = error;
- g_output_stream_close_async (stream,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)close_ready_cb,
- fixture);
- /* FIXME: We should completely stop caching the
- resource at this point */
- } else {
- entry->pos += write_size;
-
- /* Are we still writing and is there new data to write
- already ? */
- if (entry->data && entry->pos < entry->data->len) {
- g_output_stream_write_async (entry->stream,
- entry->data->str + entry->pos,
- entry->data->len - entry->pos,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)write_ready_cb,
- fixture);
- } else {
- entry->writing = FALSE;
-
- if (entry->got_body) {
- /* If we already received 'got-body'
- and we have written all the data,
- we can close the stream */
- g_output_stream_close_async (entry->stream,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)close_ready_cb,
- fixture);
- }
- }
- }
-}
-
-static void
-msg_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, WebKitSoupCacheWritingFixture *fixture)
-{
- WebKitSoupCacheEntry *entry = fixture->entry;
-
- g_return_if_fail (chunk->data && chunk->length);
- g_return_if_fail (entry);
-
- /* Ignore this if the writing or appending was cancelled */
- if (!g_cancellable_is_cancelled (entry->cancellable)) {
- g_string_append_len (entry->data, chunk->data, chunk->length);
- entry->length = entry->data->len;
-
- if (!cache_accepts_entries_of_size (fixture->cache, entry->length)) {
- /* Quickly cancel the caching of the resource */
- g_cancellable_cancel (entry->cancellable);
- }
- }
-
- /* FIXME: remove the error check when we cancel the caching at
- the first write error */
- /* Only write if the entry stream is ready */
- if (entry->writing == FALSE && entry->error == NULL && entry->stream) {
- GString *data = entry->data;
- entry->writing = TRUE;
- g_output_stream_write_async (entry->stream,
- data->str + entry->pos,
- data->len - entry->pos,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)write_ready_cb,
- fixture);
- }
-}
-
-static void
-msg_got_body_cb (SoupMessage *msg, WebKitSoupCacheWritingFixture *fixture)
-{
- WebKitSoupCacheEntry *entry = fixture->entry;
- g_return_if_fail (entry);
-
- entry->got_body = TRUE;
-
- if (!entry->stream && entry->pos != entry->length)
- /* The stream is not ready to be written but we still
- have data to write, we'll write it when the stream
- is opened for writing */
- return;
-
-
- if (entry->pos != entry->length) {
- /* If we still have data to write, write it,
- write_ready_cb will close the stream */
- if (entry->writing == FALSE && entry->error == NULL && entry->stream) {
- g_output_stream_write_async (entry->stream,
- entry->data->str + entry->pos,
- entry->data->len - entry->pos,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)write_ready_cb,
- fixture);
- }
- return;
- }
-
- if (entry->stream && !entry->writing)
- g_output_stream_close_async (entry->stream,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)close_ready_cb,
- fixture);
-}
-
-static gboolean
-webkit_soup_cache_entry_remove (WebKitSoupCache *cache, WebKitSoupCacheEntry *entry)
-{
- GList *lru_item;
-
- /* if (entry->dirty && !g_cancellable_is_cancelled (entry->cancellable)) { */
- if (entry->dirty) {
- g_cancellable_cancel (entry->cancellable);
- return FALSE;
- }
-
- g_assert (!entry->dirty);
- g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
-
- /* Remove from cache */
- if (!g_hash_table_remove (cache->priv->cache, entry->key))
- return FALSE;
-
- /* Remove from LRU */
- lru_item = g_list_find (cache->priv->lru_start, entry);
- cache->priv->lru_start = g_list_delete_link (cache->priv->lru_start, lru_item);
-
- /* Adjust cache size */
- cache->priv->size -= entry->length;
-
- g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
-
- return TRUE;
-}
-
-static gint
-lru_compare_func (gconstpointer a, gconstpointer b)
-{
- WebKitSoupCacheEntry *entry_a = (WebKitSoupCacheEntry *)a;
- WebKitSoupCacheEntry *entry_b = (WebKitSoupCacheEntry *)b;
-
- /** The rationale of this sorting func is
- *
- * 1. sort by hits -> LRU algorithm, then
- *
- * 2. sort by freshness lifetime, we better discard first
- * entries that are close to expire
- *
- * 3. sort by size, replace first small size resources as they
- * are cheaper to download
- **/
-
- /* Sort by hits */
- if (entry_a->hits != entry_b->hits)
- return entry_a->hits - entry_b->hits;
-
- /* Sort by freshness_lifetime */
- if (entry_a->freshness_lifetime != entry_b->freshness_lifetime)
- return entry_a->freshness_lifetime - entry_b->freshness_lifetime;
-
- /* Sort by size */
- return entry_a->length - entry_b->length;
-}
-
-static gboolean
-cache_accepts_entries_of_size (WebKitSoupCache *cache, guint length_to_add)
-{
- /* We could add here some more heuristics. TODO: review how
- this is done by other HTTP caches */
-
- return length_to_add <= cache->priv->max_entry_data_size;
-}
-
-static void
-make_room_for_new_entry (WebKitSoupCache *cache, guint length_to_add)
-{
- GList *lru_entry = cache->priv->lru_start;
-
- /* Check that there is enough room for the new entry. This is
- an approximation as we're not working out the size of the
- cache file or the size of the headers for performance
- reasons. TODO: check if that would be really that expensive */
-
- while (lru_entry &&
- (length_to_add + cache->priv->size > cache->priv->max_size)) {
- WebKitSoupCacheEntry *old_entry = (WebKitSoupCacheEntry *)lru_entry->data;
-
- /* Discard entries. Once cancelled resources will be
- * freed in close_ready_cb
- */
- if (webkit_soup_cache_entry_remove (cache, old_entry)) {
- webkit_soup_cache_entry_free (old_entry, TRUE);
- lru_entry = cache->priv->lru_start;
- } else
- lru_entry = g_list_next (lru_entry);
- }
-}
-
-static gboolean
-webkit_soup_cache_entry_insert_by_key (WebKitSoupCache *cache,
- const char *key,
- WebKitSoupCacheEntry *entry,
- gboolean sort)
-{
- guint length_to_add = 0;
-
- if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CHUNKED)
- length_to_add = soup_message_headers_get_content_length (entry->headers);
-
- /* Check if we are going to store the resource depending on its size */
- if (length_to_add) {
- if (!cache_accepts_entries_of_size (cache, length_to_add))
- return FALSE;
-
- /* Make room for new entry if needed */
- make_room_for_new_entry (cache, length_to_add);
- }
-
- g_hash_table_insert (cache->priv->cache, g_strdup (key), entry);
-
- /* Compute new cache size */
- cache->priv->size += length_to_add;
-
- /* Update LRU */
- if (sort)
- cache->priv->lru_start = g_list_insert_sorted (cache->priv->lru_start, entry, lru_compare_func);
- else
- cache->priv->lru_start = g_list_prepend (cache->priv->lru_start, entry);
-
- g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
-
- return TRUE;
-}
-
-static void
-msg_restarted_cb (SoupMessage *msg, WebKitSoupCacheEntry *entry)
-{
- /* FIXME: What should we do here exactly? */
-}
-
-static void
-append_to_ready_cb (GObject *source, GAsyncResult *result, WebKitSoupCacheWritingFixture *fixture)
-{
- GFile *file = (GFile *)source;
- GOutputStream *stream;
- WebKitSoupCacheEntry *entry = fixture->entry;
-
- stream = (GOutputStream *)g_file_append_to_finish (file, result, &entry->error);
-
- if (g_cancellable_is_cancelled (entry->cancellable) || entry->error) {
- fixture->cache->priv->n_pending--;
- entry->dirty = FALSE;
- webkit_soup_cache_entry_remove (fixture->cache, entry);
- webkit_soup_cache_entry_free (entry, TRUE);
- webkit_soup_cache_writing_fixture_free (fixture);
- return;
- }
-
- entry->stream = g_object_ref (stream);
- g_object_unref (file);
-
- /* If we already got all the data we have to initiate the
- writing here, since we won't get more 'got-chunk'
- signals */
- if (entry->got_body) {
- GString *data = entry->data;
-
- /* It could happen that reading the data from server
- was completed before this happens. In that case
- there is no data */
- if (data) {
- entry->writing = TRUE;
- g_output_stream_write_async (entry->stream,
- data->str + entry->pos,
- data->len - entry->pos,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)write_ready_cb,
- fixture);
- }
- }
-}
-
-typedef struct {
- time_t request_time;
- SoupSessionFeature *feature;
- gulong got_headers_handler;
-} RequestHelper;
-
-static void
-msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
-{
- WebKitSoupCache *cache;
- WebKitSoupCacheability cacheable;
- RequestHelper *helper;
- time_t request_time, response_time;
-
- response_time = time (NULL);
-
- helper = (RequestHelper *)user_data;
- cache = WEBKIT_SOUP_CACHE (helper->feature);
- request_time = helper->request_time;
- g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
- g_slice_free (RequestHelper, helper);
-
- cacheable = webkit_soup_cache_get_cacheability (cache, msg);
-
- if (cacheable & WEBKIT_SOUP_CACHE_CACHEABLE) {
- WebKitSoupCacheEntry *entry;
- char *key;
- GFile *file;
- WebKitSoupCacheWritingFixture *fixture;
-
- /* Check if we are already caching this resource */
- key = soup_message_get_cache_key (msg);
- entry = g_hash_table_lookup (cache->priv->cache, key);
- g_free (key);
-
- if (entry && entry->dirty)
- return;
-
- /* Create a new entry, deleting any old one if present */
- if (entry) {
- webkit_soup_cache_entry_remove (cache, entry);
- webkit_soup_cache_entry_free (entry, TRUE);
- }
-
- entry = webkit_soup_cache_entry_new (cache, msg, request_time, response_time);
- entry->hits = 1;
-
- /* Do not continue if it can not be stored */
- if (!webkit_soup_cache_entry_insert_by_key (cache, (const gchar *)entry->key, entry, TRUE)) {
- webkit_soup_cache_entry_free (entry, TRUE);
- return;
- }
-
- fixture = g_slice_new0 (WebKitSoupCacheWritingFixture);
- fixture->cache = g_object_ref (cache);
- fixture->entry = entry;
- fixture->msg = g_object_ref (msg);
-
- /* We connect now to these signals and buffer the data
- if it comes before the file is ready for writing */
- fixture->got_chunk_handler =
- g_signal_connect (msg, "got-chunk", G_CALLBACK (msg_got_chunk_cb), fixture);
- fixture->got_body_handler =
- g_signal_connect (msg, "got-body", G_CALLBACK (msg_got_body_cb), fixture);
- fixture->restarted_handler =
- g_signal_connect (msg, "restarted", G_CALLBACK (msg_restarted_cb), entry);
-
- /* Prepare entry */
- file = g_file_new_for_path (entry->filename);
- cache->priv->n_pending++;
-
- entry->dirty = TRUE;
- entry->cancellable = g_cancellable_new ();
- g_file_append_to_async (file, 0,
- G_PRIORITY_LOW, entry->cancellable,
- (GAsyncReadyCallback)append_to_ready_cb,
- fixture);
- } else if (cacheable & WEBKIT_SOUP_CACHE_INVALIDATES) {
- char *key;
- WebKitSoupCacheEntry *entry;
-
- key = soup_message_get_cache_key (msg);
- entry = g_hash_table_lookup (cache->priv->cache, key);
- g_free (key);
-
- if (entry) {
- if (webkit_soup_cache_entry_remove (cache, entry))
- webkit_soup_cache_entry_free (entry, TRUE);
- }
- } else if (cacheable & WEBKIT_SOUP_CACHE_VALIDATES) {
- char *key;
- WebKitSoupCacheEntry *entry;
-
- key = soup_message_get_cache_key (msg);
- entry = g_hash_table_lookup (cache->priv->cache, key);
- g_free (key);
-
- /* It's possible to get a CACHE_VALIDATES with no
- * entry in the hash table. This could happen if for
- * example the soup client is the one creating the
- * conditional request.
- */
- if (entry) {
- entry->being_validated = FALSE;
-
- /* We update the headers of the existing cache item,
- plus its age */
- soup_message_headers_foreach (msg->response_headers,
- (SoupMessageHeadersForeachFunc)update_headers,
- entry->headers);
- webkit_soup_cache_entry_set_freshness (entry, msg, cache);
- }
- }
-}
-
-GInputStream *
-webkit_soup_cache_send_response (WebKitSoupCache *cache, SoupMessage *msg)
-{
- char *key;
- WebKitSoupCacheEntry *entry;
- char *current_age;
- GInputStream *stream = NULL;
- GFile *file;
-
- g_return_val_if_fail (WEBKIT_IS_SOUP_CACHE (cache), NULL);
- g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
-
- key = soup_message_get_cache_key (msg);
- entry = g_hash_table_lookup (cache->priv->cache, key);
- g_free (key);
- g_return_val_if_fail (entry, NULL);
-
- /* If we are told to send a response from cache any validation
- in course is over by now */
- entry->being_validated = FALSE;
-
- /* Headers */
- soup_message_headers_foreach (entry->headers,
- (SoupMessageHeadersForeachFunc)update_headers,
- msg->response_headers);
-
- /* Add 'Age' header with the current age */
- current_age = g_strdup_printf ("%d", webkit_soup_cache_entry_get_current_age (entry));
- soup_message_headers_replace (msg->response_headers,
- "Age",
- current_age);
- g_free (current_age);
-
- /* TODO: the original idea was to save reads, but current code
- assumes that a stream is always returned. Need to reach
- some agreement here. Also we have to handle the situation
- were the file was no longer there (for example files
- removed without notifying the cache */
- file = g_file_new_for_path (entry->filename);
- stream = (GInputStream *)g_file_read (file, NULL, NULL);
-
- return stream;
-}
-
-static void
-request_started (SoupSessionFeature *feature, SoupSession *session,
- SoupMessage *msg, SoupSocket *socket)
-{
- RequestHelper *helper = g_slice_new0 (RequestHelper);
- helper->request_time = time (NULL);
- helper->feature = feature;
- helper->got_headers_handler = g_signal_connect (msg, "got-headers",
- G_CALLBACK (msg_got_headers_cb),
- helper);
-}
-
-static void
-attach (SoupSessionFeature *feature, SoupSession *session)
-{
- WebKitSoupCache *cache = WEBKIT_SOUP_CACHE (feature);
- cache->priv->session = session;
-
- webkit_soup_cache_default_feature_interface->attach (feature, session);
-}
-
-static void
-webkit_soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
- gpointer interface_data)
-{
- webkit_soup_cache_default_feature_interface =
- g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
-
- feature_interface->attach = attach;
- feature_interface->request_started = request_started;
-}
-
-static void
-webkit_soup_cache_init (WebKitSoupCache *cache)
-{
- WebKitSoupCachePrivate *priv;
-
- priv = cache->priv = WEBKIT_SOUP_CACHE_GET_PRIVATE (cache);
-
- priv->cache = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- (GDestroyNotify)g_free,
- NULL);
-
- /* LRU */
- priv->lru_start = NULL;
-
- /* */
- priv->n_pending = 0;
-
- /* Cache size */
- priv->max_size = DEFAULT_MAX_SIZE;
- priv->max_entry_data_size = priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
- priv->size = 0;
-}
-
-static void
-remove_cache_item (gpointer data,
- gpointer user_data)
-{
- WebKitSoupCache *cache = (WebKitSoupCache *) user_data;
- WebKitSoupCacheEntry *entry = (WebKitSoupCacheEntry *) data;
-
- if (webkit_soup_cache_entry_remove (cache, entry))
- webkit_soup_cache_entry_free (entry, FALSE);
-}
-
-static void
-webkit_soup_cache_finalize (GObject *object)
-{
- WebKitSoupCachePrivate *priv;
- GList *entries;
-
- priv = WEBKIT_SOUP_CACHE (object)->priv;
-
- // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
- entries = g_hash_table_get_values (priv->cache);
- g_list_foreach (entries, remove_cache_item, object);
- g_list_free (entries);
-
- g_hash_table_destroy (priv->cache);
- g_free (priv->cache_dir);
-
- g_list_free (priv->lru_start);
- priv->lru_start = NULL;
-
- G_OBJECT_CLASS (webkit_soup_cache_parent_class)->finalize (object);
-}
-
-static void
-webkit_soup_cache_set_property (GObject *object, guint prop_id,
- const GValue *value, GParamSpec *pspec)
-{
- WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE (object)->priv;
-
- switch (prop_id) {
- case PROP_CACHE_DIR:
- priv->cache_dir = g_value_dup_string (value);
- /* Create directory if it does not exist (FIXME: should we?) */
- if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
- g_mkdir_with_parents (priv->cache_dir, 0700);
- break;
- case PROP_CACHE_TYPE:
- priv->cache_type = g_value_get_enum (value);
- /* TODO: clear private entries and issue a warning if moving to shared? */
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static void
-webkit_soup_cache_get_property (GObject *object, guint prop_id,
- GValue *value, GParamSpec *pspec)
-{
- WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE (object)->priv;
-
- switch (prop_id) {
- case PROP_CACHE_DIR:
- g_value_set_string (value, priv->cache_dir);
- break;
- case PROP_CACHE_TYPE:
- g_value_set_enum (value, priv->cache_type);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static void
-webkit_soup_cache_constructed (GObject *object)
-{
- WebKitSoupCachePrivate *priv;
-
- priv = WEBKIT_SOUP_CACHE (object)->priv;
-
- if (!priv->cache_dir) {
- /* Set a default cache dir, different for each user */
- priv->cache_dir = g_build_filename (g_get_user_cache_dir (),
- "httpcache",
- NULL);
- if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
- g_mkdir_with_parents (priv->cache_dir, 0700);
- }
-
- if (G_OBJECT_CLASS (webkit_soup_cache_parent_class)->constructed)
- G_OBJECT_CLASS (webkit_soup_cache_parent_class)->constructed (object);
-}
-
-#define WEBKIT_SOUP_CACHE_TYPE_TYPE (webkit_soup_cache_type_get_type ())
-static GType
-webkit_soup_cache_type_get_type (void)
-{
- static GType cache_type = 0;
-
- static const GEnumValue cache_types[] = {
- { WEBKIT_SOUP_CACHE_SINGLE_USER, "Single user cache", "user" },
- { WEBKIT_SOUP_CACHE_SHARED, "Shared cache", "shared" },
- { 0, NULL, NULL }
- };
-
- if (!cache_type) {
- cache_type = g_enum_register_static ("WebKitSoupCacheTypeType", cache_types);
- }
- return cache_type;
-}
-
-static void
-webkit_soup_cache_class_init (WebKitSoupCacheClass *cache_class)
-{
- GObjectClass *gobject_class = (GObjectClass *)cache_class;
-
- gobject_class->finalize = webkit_soup_cache_finalize;
- gobject_class->constructed = webkit_soup_cache_constructed;
- gobject_class->set_property = webkit_soup_cache_set_property;
- gobject_class->get_property = webkit_soup_cache_get_property;
-
- cache_class->get_cacheability = get_cacheability;
-
- g_object_class_install_property (gobject_class, PROP_CACHE_DIR,
- g_param_spec_string ("cache-dir",
- "Cache directory",
- "The directory to store the cache files",
- NULL,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
- g_object_class_install_property (gobject_class, PROP_CACHE_TYPE,
- g_param_spec_enum ("cache-type",
- "Cache type",
- "Whether the cache is private or shared",
- WEBKIT_SOUP_CACHE_TYPE_TYPE,
- WEBKIT_SOUP_CACHE_SINGLE_USER,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
- g_type_class_add_private (cache_class, sizeof (WebKitSoupCachePrivate));
-}
-
-/**
- * webkit_soup_cache_new:
- * @cache_dir: the directory to store the cached data, or %NULL to use the default one
- * @cache_type: the #WebKitSoupCacheType of the cache
- *
- * Creates a new #WebKitSoupCache.
- *
- * Returns: a new #WebKitSoupCache
- *
- * Since: 2.28
- **/
-WebKitSoupCache *
-webkit_soup_cache_new (const char *cache_dir, WebKitSoupCacheType cache_type)
-{
- return g_object_new (WEBKIT_TYPE_SOUP_CACHE,
- "cache-dir", cache_dir,
- "cache-type", cache_type,
- NULL);
-}
-
-/**
- * webkit_soup_cache_has_response:
- * @cache: a #WebKitSoupCache
- * @msg: a #SoupMessage
- *
- * This function calculates whether the @cache object has a proper
- * response for the request @msg given the flags both in the request
- * and the cached reply and the time ellapsed since it was cached.
- *
- * Returns: whether or not the @cache has a valid response for @msg
- **/
-WebKitSoupCacheResponse
-webkit_soup_cache_has_response (WebKitSoupCache *cache, SoupMessage *msg)
-{
- char *key;
- WebKitSoupCacheEntry *entry;
- const char *cache_control;
- gpointer value;
- gboolean must_revalidate;
- int max_age, max_stale, min_fresh;
- GList *lru_item, *item;
-
- key = soup_message_get_cache_key (msg);
- entry = g_hash_table_lookup (cache->priv->cache, key);
- g_free (key);
-
- /* 1. The presented Request-URI and that of stored response
- * match
- */
- if (!entry)
- return WEBKIT_SOUP_CACHE_RESPONSE_STALE;
-
- /* Increase hit count. Take sorting into account */
- entry->hits++;
- lru_item = g_list_find (cache->priv->lru_start, entry);
- item = lru_item;
- while (item->next && lru_compare_func (item->data, item->next->data) > 0)
- item = g_list_next (item);
-
- if (item != lru_item) {
- cache->priv->lru_start = g_list_remove_link (cache->priv->lru_start, lru_item);
- item = g_list_insert_sorted (item, lru_item->data, lru_compare_func);
- g_list_free (lru_item);
- }
-
- if (entry->dirty || entry->being_validated)
- return WEBKIT_SOUP_CACHE_RESPONSE_STALE;
-
- /* 2. The request method associated with the stored response
- * allows it to be used for the presented request
- */
-
- /* In practice this means we only return our resource for GET,
- * cacheability for other methods is a TODO in the RFC
- * (TODO: although we could return the headers for HEAD
- * probably).
- */
- if (msg->method != SOUP_METHOD_GET)
- return WEBKIT_SOUP_CACHE_RESPONSE_STALE;
-
- /* 3. Selecting request-headers nominated by the stored
- * response (if any) match those presented.
- */
-
- /* TODO */
-
- /* 4. The request is a conditional request issued by the client.
- */
- if (soup_message_headers_get (msg->request_headers, "If-Modified-Since") ||
- soup_message_headers_get (msg->request_headers, "If-None-Match"))
- return WEBKIT_SOUP_CACHE_RESPONSE_STALE;
-
- /* 5. The presented request and stored response are free from
- * directives that would prevent its use.
- */
-
- must_revalidate = FALSE;
- max_age = max_stale = min_fresh = -1;
-
- cache_control = soup_message_headers_get (msg->request_headers, "Cache-Control");
- if (cache_control) {
- GHashTable *hash = soup_header_parse_param_list (cache_control);
-
- if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
- soup_header_free_param_list (hash);
- return WEBKIT_SOUP_CACHE_RESPONSE_STALE;
- }
-
- if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
- entry->must_revalidate = TRUE;
- }
-
- if (g_hash_table_lookup_extended (hash, "max-age", NULL, &value)) {
- max_age = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
- }
-
- /* max-stale can have no value set, we need to use _extended */
- if (g_hash_table_lookup_extended (hash, "max-stale", NULL, &value)) {
- if (value)
- max_stale = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
- else
- max_stale = G_MAXINT32;
- }
-
- value = g_hash_table_lookup (hash, "min-fresh");
- if (value)
- min_fresh = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
-
- soup_header_free_param_list (hash);
-
- if (max_age != -1) {
- guint current_age = webkit_soup_cache_entry_get_current_age (entry);
-
- /* If we are over max-age and max-stale is not
- set, do not use the value from the cache
- without validation */
- if ((guint) max_age <= current_age && max_stale == -1)
- return WEBKIT_SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
- }
- }
-
- /* 6. The stored response is either: fresh, allowed to be
- * served stale or succesfully validated
- */
- /* TODO consider also proxy-revalidate & s-maxage */
- if (entry->must_revalidate)
- return WEBKIT_SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
-
- if (!webkit_soup_cache_entry_is_fresh_enough (entry, min_fresh)) {
- /* Not fresh, can it be served stale? */
- if (max_stale != -1) {
- /* G_MAXINT32 means we accept any staleness */
- if (max_stale == G_MAXINT32)
- return WEBKIT_SOUP_CACHE_RESPONSE_FRESH;
-
- if ((webkit_soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= (guint) max_stale)
- return WEBKIT_SOUP_CACHE_RESPONSE_FRESH;
- }
-
- return WEBKIT_SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
- }
-
- return WEBKIT_SOUP_CACHE_RESPONSE_FRESH;
-}
-
-/**
- * webkit_soup_cache_get_cacheability:
- * @cache: a #WebKitSoupCache
- * @msg: a #SoupMessage
- *
- * Calculates whether the @msg can be cached or not.
- *
- * Returns: a #WebKitSoupCacheability value indicating whether the @msg can be cached or not.
- **/
-WebKitSoupCacheability
-webkit_soup_cache_get_cacheability (WebKitSoupCache *cache, SoupMessage *msg)
-{
- g_return_val_if_fail (WEBKIT_IS_SOUP_CACHE (cache), WEBKIT_SOUP_CACHE_UNCACHEABLE);
- g_return_val_if_fail (SOUP_IS_MESSAGE (msg), WEBKIT_SOUP_CACHE_UNCACHEABLE);
-
- return WEBKIT_SOUP_CACHE_GET_CLASS (cache)->get_cacheability (cache, msg);
-}
-
-static gboolean
-force_flush_timeout (gpointer data)
-{
- gboolean *forced = (gboolean *)data;
- *forced = TRUE;
-
- return FALSE;
-}
-
-/**
- * webkit_soup_cache_flush:
- * @cache: a #WebKitSoupCache
- * @session: the #SoupSession associated with the @cache
- *
- * This function will force all pending writes in the @cache to be
- * committed to disk. For doing so it will iterate the #GMainContext
- * associated with the @session (which can be the default one) as long
- * as needed.
- **/
-void
-webkit_soup_cache_flush (WebKitSoupCache *cache)
-{
- GMainContext *async_context;
- SoupSession *session;
- guint timeout_id;
- gboolean forced = FALSE;
-
- g_return_if_fail (WEBKIT_IS_SOUP_CACHE (cache));
-
- session = cache->priv->session;
- g_return_if_fail (SOUP_IS_SESSION (session));
- async_context = soup_session_get_async_context (session);
-
- /* We give cache 10 secs to finish */
- timeout_id = g_timeout_add (10000, force_flush_timeout, &forced);
-
- while (!forced && cache->priv->n_pending > 0)
- g_main_context_iteration (async_context, FALSE);
-
- if (!forced)
- g_source_remove (timeout_id);
- else
- g_warning ("Cache flush finished despite %d pending requests", cache->priv->n_pending);
-}
-
-static void
-clear_cache_item (gpointer data,
- gpointer user_data)
-{
- WebKitSoupCache *cache = (WebKitSoupCache *) user_data;
- WebKitSoupCacheEntry *entry = (WebKitSoupCacheEntry *) data;
-
- if (webkit_soup_cache_entry_remove (cache, entry))
- webkit_soup_cache_entry_free (entry, TRUE);
-}
-
-/**
- * webkit_soup_cache_clear:
- * @cache: a #WebKitSoupCache
- *
- * Will remove all entries in the @cache plus all the cache files
- * associated with them.
- **/
-void
-webkit_soup_cache_clear (WebKitSoupCache *cache)
-{
- GHashTable *hash;
- GList *entries;
-
- g_return_if_fail (WEBKIT_IS_SOUP_CACHE (cache));
-
- hash = cache->priv->cache;
- g_return_if_fail (hash);
-
- // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
- entries = g_hash_table_get_values (hash);
- g_list_foreach (entries, clear_cache_item, cache);
- g_list_free (entries);
-}
-
-SoupMessage *
-webkit_soup_cache_generate_conditional_request (WebKitSoupCache *cache, SoupMessage *original)
-{
- SoupMessage *msg;
- SoupURI *uri;
- WebKitSoupCacheEntry *entry;
- char *key;
- const char *value;
-
- g_return_val_if_fail (WEBKIT_IS_SOUP_CACHE (cache), NULL);
- g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL);
-
- /* First copy the data we need from the original message */
- uri = soup_message_get_uri (original);
- msg = soup_message_new_from_uri (original->method, uri);
-
- soup_message_headers_foreach (original->request_headers,
- (SoupMessageHeadersForeachFunc)copy_headers,
- msg->request_headers);
-
- /* Now add the validator entries in the header from the cached
- data */
- key = soup_message_get_cache_key (original);
- entry = g_hash_table_lookup (cache->priv->cache, key);
- g_free (key);
-
- g_return_val_if_fail (entry, NULL);
-
- entry->being_validated = TRUE;
-
- value = soup_message_headers_get (entry->headers, "Last-Modified");
- if (value)
- soup_message_headers_append (msg->request_headers,
- "If-Modified-Since",
- value);
- value = soup_message_headers_get (entry->headers, "ETag");
- if (value)
- soup_message_headers_append (msg->request_headers,
- "If-None-Match",
- value);
- return msg;
-}
-
-#define WEBKIT_SOUP_CACHE_FILE "soup.cache"
-
-#define WEBKIT_SOUP_CACHE_HEADERS_FORMAT "{ss}"
-#define WEBKIT_SOUP_CACHE_PHEADERS_FORMAT "(ssbuuuuua" WEBKIT_SOUP_CACHE_HEADERS_FORMAT ")"
-#define WEBKIT_SOUP_CACHE_ENTRIES_FORMAT "a" WEBKIT_SOUP_CACHE_PHEADERS_FORMAT
-
-/* Basically the same format than above except that some strings are
- prepended with &. This way the GVariant returns a pointer to the
- data instead of duplicating the string */
-#define WEBKIT_SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
-
-static void
-pack_entry (gpointer data,
- gpointer user_data)
-{
- WebKitSoupCacheEntry *entry = (WebKitSoupCacheEntry *) data;
- SoupMessageHeadersIter iter;
- const gchar *header_key, *header_value;
- GVariantBuilder *headers_builder;
- GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
-
- /* Do not store non-consolidated entries */
- if (entry->dirty || entry->writing || !entry->key)
- return;
-
- /* Pack headers */
- headers_builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
- soup_message_headers_iter_init (&iter, entry->headers);
- while (soup_message_headers_iter_next (&iter, &header_key, &header_value)) {
- if (g_utf8_validate (header_value, -1, NULL))
- g_variant_builder_add (headers_builder, WEBKIT_SOUP_CACHE_HEADERS_FORMAT,
- header_key, header_value);
- }
-
- /* Entry data */
- g_variant_builder_add (entries_builder, WEBKIT_SOUP_CACHE_PHEADERS_FORMAT,
- entry->key, entry->filename, entry->must_revalidate,
- entry->freshness_lifetime, entry->corrected_initial_age,
- entry->response_time, entry->hits, entry->length, headers_builder);
-
- g_variant_builder_unref (headers_builder);
-}
-
-void
-webkit_soup_cache_dump (WebKitSoupCache *cache)
-{
- WebKitSoupCachePrivate *priv = WEBKIT_SOUP_CACHE_GET_PRIVATE (cache);
- gchar *filename;
- GVariantBuilder *entries_builder;
- GVariant *cache_variant;
-
- if (!g_list_length (cache->priv->lru_start))
- return;
-
- /* Create the builder and iterate over all entries */
- entries_builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
- g_list_foreach (cache->priv->lru_start, pack_entry, entries_builder);
-
- /* Serialize and dump */
- cache_variant = g_variant_new (WEBKIT_SOUP_CACHE_ENTRIES_FORMAT, entries_builder);
- g_variant_builder_unref (entries_builder);
-
- filename = g_build_filename (priv->cache_dir, WEBKIT_SOUP_CACHE_FILE, NULL);
- g_file_set_contents (filename, (const gchar *)g_variant_get_data (cache_variant),
- g_variant_get_size (cache_variant), NULL);
- g_free (filename);
- g_variant_unref (cache_variant);
-}
-
-void
-webkit_soup_cache_load (WebKitSoupCache *cache)
-{
- gchar *filename = NULL, *contents = NULL;
- GVariant *cache_variant;
- GVariantIter *entries_iter, *headers_iter;
- GVariantType *variant_format;
- gsize length;
- WebKitSoupCacheEntry *entry;
- WebKitSoupCachePrivate *priv = cache->priv;
-
- filename = g_build_filename (priv->cache_dir, WEBKIT_SOUP_CACHE_FILE, NULL);
- if (!g_file_get_contents (filename, &contents, &length, NULL)) {
- g_free (filename);
- g_free (contents);
- return;
- }
- g_free (filename);
-
- variant_format = g_variant_type_new (WEBKIT_SOUP_CACHE_ENTRIES_FORMAT);
- cache_variant = g_variant_new_from_data (variant_format, (const gchar *)contents, length, FALSE, g_free, contents);
- g_variant_type_free (variant_format);
-
- g_variant_get (cache_variant, WEBKIT_SOUP_CACHE_ENTRIES_FORMAT, &entries_iter);
- entry = g_slice_new0 (WebKitSoupCacheEntry);
-
- while (g_variant_iter_loop (entries_iter, WEBKIT_SOUP_CACHE_PHEADERS_FORMAT,
- &entry->key, &entry->filename, &entry->must_revalidate,
- &entry->freshness_lifetime, &entry->corrected_initial_age,
- &entry->response_time, &entry->hits, &entry->length,
- &headers_iter)) {
- const gchar *header_key, *header_value;
-
- /* SoupMessage Headers */
- entry->headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
- while (g_variant_iter_loop (headers_iter, WEBKIT_SOUP_CACHE_DECODE_HEADERS_FORMAT, &header_key, &header_value))
- soup_message_headers_append (entry->headers, header_key, header_value);
-
- /* Insert in cache */
- if (!webkit_soup_cache_entry_insert_by_key (cache, (const gchar *)entry->key, entry, FALSE))
- webkit_soup_cache_entry_free (entry, TRUE);
-
- /* New entry for the next iteration. This creates an
- extra object the last iteration but it's worth it
- as we save several if's */
- entry = g_slice_new0 (WebKitSoupCacheEntry);
- }
- /* Remove last created entry */
- g_slice_free (WebKitSoupCacheEntry, entry);
-
- /* Sort LRU (shouldn't be needed). First reverse as elements
- * are always prepended when inserting
- */
- cache->priv->lru_start = g_list_reverse (cache->priv->lru_start);
- cache->priv->lru_start = g_list_sort (cache->priv->lru_start, lru_compare_func);
-
- /* frees */
- g_variant_iter_free (entries_iter);
- g_variant_unref (cache_variant);
-}
-
-void
-webkit_soup_cache_set_max_size (WebKitSoupCache *cache,
- guint max_size)
-{
- cache->priv->max_size = max_size;
- cache->priv->max_entry_data_size = cache->priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
-}
-
-guint
-webkit_soup_cache_get_max_size (WebKitSoupCache *cache)
-{
- return cache->priv->max_size;
-}