Coverage Report

Created: 2026-02-14 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libsoup/libsoup/cache/soup-cache.c
Line
Count
Source
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2
/*
3
 * soup-cache.c
4
 *
5
 * Copyright (C) 2009, 2010 Igalia S.L.
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Library General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Library General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Library General Public License
18
 * along with this library; see the file COPYING.LIB.  If not, write to
19
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20
 * Boston, MA 02110-1301, USA.
21
 */
22
23
/* TODO:
24
 * - Need to hook the feature in the sync SoupSession.
25
 * - Need more tests.
26
 */
27
28
#ifdef HAVE_CONFIG_H
29
#include <config.h>
30
#endif
31
32
#include <string.h>
33
#include <glib/gstdio.h>
34
35
#include "soup-cache.h"
36
#include "soup-body-input-stream.h"
37
#include "soup-cache-client-input-stream.h"
38
#include "soup-cache-input-stream.h"
39
#include "soup-cache-private.h"
40
#include "soup-content-processor.h"
41
#include "soup-message-private.h"
42
#include "soup-message-headers-private.h"
43
#include "soup.h"
44
#include "soup-message-metrics-private.h"
45
#include "soup-misc.h"
46
#include "soup-session-private.h"
47
#include "soup-session-feature-private.h"
48
49
/**
50
 * SoupCache:
51
 *
52
 * File-based cache for HTTP resources.
53
 */
54
55
/**
56
 * SoupCacheability:
57
 * @SOUP_CACHE_CACHEABLE: The message should be cached
58
 * @SOUP_CACHE_UNCACHEABLE: The message shouldn't be cached
59
 * @SOUP_CACHE_INVALIDATES: The messages cache should be invalidated
60
 * @SOUP_CACHE_VALIDATES: The messages cache should be updated
61
 *
62
 * Indicates if a message should or shouldn't be cached.
63
 */
64
65
static void soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
66
67
static SoupContentProcessorInterface *soup_cache_default_content_processor_interface;
68
static void soup_cache_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
69
70
0
#define DEFAULT_MAX_SIZE (50 * 1024 * 1024)
71
0
#define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size
72
                                  of the cache that can be
73
                                  filled by a single entry */
74
75
/*
76
 * Version 2: cache is now saved in soup.cache2. Added the version
77
 * number to the beginning of the file.
78
 *
79
 * Version 3: added HTTP status code to the cache entries.
80
 *
81
 * Version 4: replaced several types.
82
 *   - freshness_lifetime,corrected_initial_age,response_time: time_t -> guint32
83
 *   - status_code: guint -> guint16
84
 *   - hits: guint -> guint32
85
 *
86
 * Version 5: key is no longer stored on disk as it can be easily
87
 * built from the URI. Apart from that some fields in the
88
 * SoupCacheEntry have changed:
89
 *   - entry key is now a uint32 instead of a (char *).
90
 *   - added uri, used to check for collisions
91
 *   - removed filename, it's built from the entry key.
92
 */
93
0
#define SOUP_CACHE_CURRENT_VERSION 5
94
95
#define OLD_SOUP_CACHE_FILE "soup.cache"
96
0
#define SOUP_CACHE_FILE "soup.cache2"
97
98
0
#define SOUP_CACHE_HEADERS_FORMAT "{ss}"
99
0
#define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqa" SOUP_CACHE_HEADERS_FORMAT ")"
100
0
#define SOUP_CACHE_ENTRIES_FORMAT "(qa" SOUP_CACHE_PHEADERS_FORMAT ")"
101
102
/* Basically the same format than above except that some strings are
103
   prepended with &. This way the GVariant returns a pointer to the
104
   data instead of duplicating the string */
105
#define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
106
107
108
typedef struct _SoupCacheEntry {
109
  guint32 key;
110
  char *uri;
111
  guint32 freshness_lifetime;
112
  gboolean must_revalidate;
113
  gsize length;
114
  guint32 corrected_initial_age;
115
  guint32 response_time;
116
  gboolean dirty;
117
  gboolean being_validated;
118
  SoupMessageHeaders *headers;
119
  guint32 hits;
120
  GCancellable *cancellable;
121
  guint16 status_code;
122
} SoupCacheEntry;
123
124
typedef struct {
125
  char *cache_dir;
126
        GMutex mutex;
127
  GHashTable *cache;
128
  guint n_pending;
129
  SoupSession *session;
130
  SoupCacheType cache_type;
131
  guint size;
132
  guint max_size;
133
  guint max_entry_data_size; /* Computed value. Here for performance reasons */
134
  GList *lru_start;
135
} SoupCachePrivate;
136
137
enum {
138
  PROP_0,
139
  PROP_CACHE_DIR,
140
  PROP_CACHE_TYPE,
141
142
        LAST_PROPERTY
143
};
144
145
static GParamSpec *properties[LAST_PROPERTY] = { NULL, };
146
147
0
G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE_OBJECT,
148
0
                         G_ADD_PRIVATE (SoupCache)
149
0
       G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
150
0
            soup_cache_session_feature_init)
151
0
       G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR,
152
0
            soup_cache_content_processor_init))
153
0
154
0
static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge);
155
0
static void make_room_for_new_entry (SoupCache *cache, guint length_to_add);
156
0
static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add);
157
0
158
0
static GFile *
159
0
get_file_from_entry (SoupCache *cache, SoupCacheEntry *entry)
160
0
{
161
0
        SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
162
0
  char *filename = g_strdup_printf ("%s%s%u", priv->cache_dir,
163
0
            G_DIR_SEPARATOR_S, (guint) entry->key);
164
0
  GFile *file = g_file_new_for_path (filename);
165
0
  g_free (filename);
166
167
0
  return file;
168
0
}
169
170
static SoupCacheability
171
get_cacheability (SoupCache *cache, SoupMessage *msg)
172
0
{
173
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
174
0
  SoupCacheability cacheability;
175
0
  const char *cache_control, *content_type;
176
0
  gboolean has_max_age = FALSE;
177
178
  /* 1. The request method must be cacheable */
179
0
  if (soup_message_get_method (msg) == SOUP_METHOD_GET)
180
0
    cacheability = SOUP_CACHE_CACHEABLE;
181
0
  else if (soup_message_get_method (msg) == SOUP_METHOD_HEAD ||
182
0
     soup_message_get_method (msg) == SOUP_METHOD_TRACE ||
183
0
     soup_message_get_method (msg) == SOUP_METHOD_CONNECT)
184
0
    return SOUP_CACHE_UNCACHEABLE;
185
0
  else
186
0
    return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
187
188
0
  content_type = soup_message_headers_get_content_type (soup_message_get_response_headers (msg), NULL);
189
0
  if (content_type && !g_ascii_strcasecmp (content_type, "multipart/x-mixed-replace"))
190
0
    return SOUP_CACHE_UNCACHEABLE;
191
192
0
  cache_control = soup_message_headers_get_list_common (soup_message_get_response_headers (msg), SOUP_HEADER_CACHE_CONTROL);
193
0
  if (cache_control && *cache_control) {
194
0
    GHashTable *hash;
195
196
0
    hash = soup_header_parse_param_list (cache_control);
197
198
    /* Shared caches MUST NOT store private resources */
199
0
    if (priv->cache_type == SOUP_CACHE_SHARED) {
200
0
      if (g_hash_table_lookup_extended (hash, "private", NULL, NULL)) {
201
0
        soup_header_free_param_list (hash);
202
0
        return SOUP_CACHE_UNCACHEABLE;
203
0
      }
204
0
    }
205
206
    /* 2. The 'no-store' cache directive does not appear in the
207
     * headers
208
     */
209
0
    if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
210
0
      soup_header_free_param_list (hash);
211
0
      return SOUP_CACHE_UNCACHEABLE;
212
0
    }
213
214
0
    if (g_hash_table_lookup_extended (hash, "max-age", NULL, NULL))
215
0
      has_max_age = TRUE;
216
217
    /* This does not appear in section 2.1, but I think it makes
218
     * sense to check it too?
219
     */
220
0
    if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
221
0
      soup_header_free_param_list (hash);
222
0
      return SOUP_CACHE_UNCACHEABLE;
223
0
    }
224
225
0
    soup_header_free_param_list (hash);
226
0
  }
227
228
  /* Section 13.9 */
229
0
  if ((g_uri_get_query (soup_message_get_uri (msg))) &&
230
0
      !soup_message_headers_get_one_common (soup_message_get_response_headers (msg), SOUP_HEADER_EXPIRES) &&
231
0
      !has_max_age)
232
0
    return SOUP_CACHE_UNCACHEABLE;
233
234
0
  switch (soup_message_get_status (msg)) {
235
0
  case SOUP_STATUS_PARTIAL_CONTENT:
236
    /* We don't cache partial responses, but they only
237
     * invalidate cached full responses if the headers
238
     * don't match.
239
     */
240
0
    cacheability = SOUP_CACHE_UNCACHEABLE;
241
0
    break;
242
243
0
  case SOUP_STATUS_NOT_MODIFIED:
244
    /* A 304 response validates an existing cache entry */
245
0
    cacheability = SOUP_CACHE_VALIDATES;
246
0
    break;
247
248
0
  case SOUP_STATUS_MULTIPLE_CHOICES:
249
0
  case SOUP_STATUS_MOVED_PERMANENTLY:
250
0
  case SOUP_STATUS_GONE:
251
    /* FIXME: cacheable unless indicated otherwise */
252
0
    cacheability = SOUP_CACHE_UNCACHEABLE;
253
0
    break;
254
255
0
  case SOUP_STATUS_FOUND:
256
0
  case SOUP_STATUS_TEMPORARY_REDIRECT:
257
    /* FIXME: cacheable if explicitly indicated */
258
0
    cacheability = SOUP_CACHE_UNCACHEABLE;
259
0
    break;
260
261
0
  case SOUP_STATUS_SEE_OTHER:
262
0
  case SOUP_STATUS_FORBIDDEN:
263
0
  case SOUP_STATUS_NOT_FOUND:
264
0
  case SOUP_STATUS_METHOD_NOT_ALLOWED:
265
0
    return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
266
267
0
  default:
268
    /* Any 5xx status or any 4xx status not handled above
269
     * is uncacheable but doesn't break the cache.
270
     */
271
0
    if ((soup_message_get_status (msg) >= SOUP_STATUS_BAD_REQUEST &&
272
0
         soup_message_get_status (msg) <= SOUP_STATUS_FAILED_DEPENDENCY) ||
273
0
        soup_message_get_status (msg) >= SOUP_STATUS_INTERNAL_SERVER_ERROR)
274
0
      return SOUP_CACHE_UNCACHEABLE;
275
276
    /* An unrecognized 2xx, 3xx, or 4xx response breaks
277
     * the cache.
278
     */
279
0
    if ((soup_message_get_status (msg) > SOUP_STATUS_PARTIAL_CONTENT &&
280
0
         soup_message_get_status (msg) < SOUP_STATUS_MULTIPLE_CHOICES) ||
281
0
        (soup_message_get_status (msg) > SOUP_STATUS_TEMPORARY_REDIRECT &&
282
0
         soup_message_get_status (msg) < SOUP_STATUS_INTERNAL_SERVER_ERROR))
283
0
      return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
284
0
    break;
285
0
  }
286
287
0
  return cacheability;
288
0
}
289
290
/* NOTE: this function deletes the file pointed by the file argument
291
 * and also unref's the GFile object representing it.
292
 */
293
static void
294
soup_cache_entry_free (SoupCacheEntry *entry)
295
0
{
296
0
  g_free (entry->uri);
297
0
  g_clear_pointer (&entry->headers, soup_message_headers_unref);
298
0
  g_clear_object (&entry->cancellable);
299
300
0
  g_slice_free (SoupCacheEntry, entry);
301
0
}
302
303
static void
304
copy_headers (const char *name, const char *value, SoupMessageHeaders *headers)
305
0
{
306
0
  soup_message_headers_append (headers, name, value);
307
0
}
308
309
static void
310
remove_headers (const char *name, const char *value, SoupMessageHeaders *headers)
311
0
{
312
0
  soup_message_headers_remove (headers, name);
313
0
}
314
315
static SoupHeaderName hop_by_hop_headers[] = {
316
        SOUP_HEADER_CONNECTION,
317
        SOUP_HEADER_KEEP_ALIVE,
318
        SOUP_HEADER_PROXY_AUTHENTICATE,
319
        SOUP_HEADER_PROXY_AUTHORIZATION,
320
        SOUP_HEADER_TE,
321
        SOUP_HEADER_TRAILER,
322
        SOUP_HEADER_TRANSFER_ENCODING,
323
        SOUP_HEADER_UPGRADE
324
};
325
326
static void
327
copy_end_to_end_headers (SoupMessageHeaders *source, SoupMessageHeaders *destination)
328
0
{
329
0
  int i;
330
331
0
  soup_message_headers_foreach (source, (SoupMessageHeadersForeachFunc) copy_headers, destination);
332
0
  for (i = 0; i < G_N_ELEMENTS (hop_by_hop_headers); i++)
333
0
    soup_message_headers_remove_common (destination, hop_by_hop_headers[i]);
334
0
  soup_message_headers_clean_connection_headers (destination);
335
0
}
336
337
static guint
338
soup_cache_entry_get_current_age (SoupCacheEntry *entry)
339
0
{
340
0
  time_t now = time (NULL);
341
0
  time_t resident_time;
342
343
0
  resident_time = now - entry->response_time;
344
0
  return entry->corrected_initial_age + resident_time;
345
0
}
346
347
static gboolean
348
soup_cache_entry_is_fresh_enough (SoupCacheEntry *entry, gint min_fresh)
349
0
{
350
0
  guint limit = (min_fresh == -1) ? soup_cache_entry_get_current_age (entry) : (guint) min_fresh;
351
0
  return entry->freshness_lifetime > limit;
352
0
}
353
354
static inline guint32
355
get_cache_key_from_uri (const char *uri)
356
0
{
357
0
  return (guint32) g_str_hash (uri);
358
0
}
359
360
static void
361
soup_cache_entry_set_freshness (SoupCacheEntry *entry, SoupMessage *msg, SoupCache *cache)
362
0
{
363
0
  const char *cache_control;
364
0
  const char *expires, *date, *last_modified;
365
366
  /* Reset these values. We have to do this to ensure that
367
   * revalidations overwrite previous values for the headers.
368
   */
369
0
  entry->must_revalidate = FALSE;
370
0
  entry->freshness_lifetime = 0;
371
372
0
  cache_control = soup_message_headers_get_list_common (entry->headers, SOUP_HEADER_CACHE_CONTROL);
373
0
  if (cache_control && *cache_control) {
374
0
    const char *max_age, *s_maxage;
375
0
    gint64 freshness_lifetime = 0;
376
0
    GHashTable *hash;
377
0
    SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
378
379
0
    hash = soup_header_parse_param_list (cache_control);
380
381
    /* Should we re-validate the entry when it goes stale */
382
0
    entry->must_revalidate = g_hash_table_lookup_extended (hash, "must-revalidate", NULL, NULL);
383
384
    /* Section 2.3.1 */
385
0
    if (priv->cache_type == SOUP_CACHE_SHARED) {
386
0
      s_maxage = g_hash_table_lookup (hash, "s-maxage");
387
0
      if (s_maxage) {
388
0
        freshness_lifetime = g_ascii_strtoll (s_maxage, NULL, 10);
389
0
        if (freshness_lifetime) {
390
          /* Implies proxy-revalidate. TODO: is it true? */
391
0
          entry->must_revalidate = TRUE;
392
0
          soup_header_free_param_list (hash);
393
0
          return;
394
0
        }
395
0
      }
396
0
    }
397
398
    /* If 'max-age' cache directive is present, use that */
399
0
    max_age = g_hash_table_lookup (hash, "max-age");
400
0
    if (max_age)
401
0
      freshness_lifetime = g_ascii_strtoll (max_age, NULL, 10);
402
403
0
    if (freshness_lifetime) {
404
0
      entry->freshness_lifetime = (guint32) MIN (freshness_lifetime, G_MAXUINT32);
405
0
      soup_header_free_param_list (hash);
406
0
      return;
407
0
    }
408
409
0
    soup_header_free_param_list (hash);
410
0
  }
411
412
  /* If the 'Expires' response header is present, use its value
413
   * minus the value of the 'Date' response header
414
   */
415
0
  expires = soup_message_headers_get_one_common (entry->headers, SOUP_HEADER_EXPIRES);
416
0
  date = soup_message_headers_get_one_common (entry->headers, SOUP_HEADER_DATE);
417
0
  if (expires && date) {
418
0
    GDateTime *expires_d, *date_d;
419
0
    gint64 expires_t, date_t;
420
421
0
    expires_d = soup_date_time_new_from_http_string (expires);
422
0
    date_d = soup_date_time_new_from_http_string (date);
423
424
0
    if (expires_d && date_d) {
425
0
      expires_t = g_date_time_to_unix (expires_d);
426
0
      date_t = g_date_time_to_unix (date_d);
427
428
0
      g_date_time_unref (expires_d);
429
0
      g_date_time_unref (date_d);
430
431
0
      if (expires_t && date_t) {
432
0
        entry->freshness_lifetime = (guint32) MAX (expires_t - date_t, 0);
433
0
        return;
434
0
      }
435
0
    } else {
436
      /* If Expires is not a valid date we should
437
         treat it as already expired, see section
438
         3.3 */
439
0
      entry->freshness_lifetime = 0;
440
0
      return;
441
0
    }
442
0
  }
443
444
  /* Otherwise an heuristic may be used */
445
446
  /* Heuristics MUST NOT be used with stored responses with
447
     these status codes (section 2.3.1.1) */
448
0
  if (entry->status_code != SOUP_STATUS_OK &&
449
0
      entry->status_code != SOUP_STATUS_NON_AUTHORITATIVE &&
450
0
      entry->status_code != SOUP_STATUS_PARTIAL_CONTENT &&
451
0
      entry->status_code != SOUP_STATUS_MULTIPLE_CHOICES &&
452
0
      entry->status_code != SOUP_STATUS_MOVED_PERMANENTLY &&
453
0
      entry->status_code != SOUP_STATUS_GONE)
454
0
    goto expire;
455
456
  /* TODO: attach warning 113 if response's current_age is more
457
     than 24h (section 2.3.1.1) when using heuristics */
458
459
  /* Last-Modified based heuristic */
460
0
  last_modified = soup_message_headers_get_one_common (entry->headers, SOUP_HEADER_LAST_MODIFIED);
461
0
  if (last_modified) {
462
0
    GDateTime *soup_date;
463
0
    gint64 now, last_modified_t;
464
465
0
    soup_date = soup_date_time_new_from_http_string (last_modified);
466
0
    if (soup_date) {
467
0
      last_modified_t = g_date_time_to_unix (soup_date);
468
0
      now = time (NULL);
469
470
0
#define HEURISTIC_FACTOR 0.1 /* From Section 2.3.1.1 */
471
472
0
      entry->freshness_lifetime = MAX (0, (now - last_modified_t) * HEURISTIC_FACTOR);
473
0
      g_date_time_unref (soup_date);
474
0
    }
475
0
  }
476
477
0
  return;
478
479
0
 expire:
480
  /* If all else fails, make the entry expire immediately */
481
0
  entry->freshness_lifetime = 0;
482
0
}
483
484
static SoupCacheEntry *
485
soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, time_t response_time)
486
0
{
487
0
  SoupCacheEntry *entry;
488
0
  const char *date;
489
0
  GDateTime *soup_date = NULL;
490
491
0
  entry = g_slice_new0 (SoupCacheEntry);
492
0
  entry->dirty = FALSE;
493
0
  entry->being_validated = FALSE;
494
0
  entry->status_code = soup_message_get_status (msg);
495
0
  entry->response_time = response_time;
496
0
  entry->uri = g_uri_to_string_partial (soup_message_get_uri (msg), G_URI_HIDE_PASSWORD);
497
498
  /* Headers */
499
0
  entry->headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
500
0
  copy_end_to_end_headers (soup_message_get_response_headers (msg), entry->headers);
501
502
  /* LRU list */
503
0
  entry->hits = 0;
504
505
  /* Section 2.3.1, Freshness Lifetime */
506
0
  soup_cache_entry_set_freshness (entry, msg, cache);
507
508
  /* Section 2.3.2, Calculating Age */
509
0
  date = soup_message_headers_get_one_common (entry->headers, SOUP_HEADER_DATE);
510
0
  if (date)
511
0
    soup_date = soup_date_time_new_from_http_string (date);
512
513
0
  if (soup_date) {
514
0
    const char *age;
515
0
    gint64 date_value, apparent_age, corrected_received_age, response_delay, age_value = 0;
516
517
0
    date_value = g_date_time_to_unix (soup_date);
518
0
    g_date_time_unref (soup_date);
519
520
0
    age = soup_message_headers_get_one_common (entry->headers, SOUP_HEADER_AGE);
521
0
    if (age)
522
0
      age_value = g_ascii_strtoll (age, NULL, 10);
523
524
0
    apparent_age = MAX (0, entry->response_time - date_value);
525
0
    corrected_received_age = MAX (apparent_age, age_value);
526
0
    response_delay = entry->response_time - request_time;
527
0
    entry->corrected_initial_age = corrected_received_age + response_delay;
528
0
  } else {
529
    /* Is this correct ? */
530
0
    entry->corrected_initial_age = time (NULL);
531
0
  }
532
533
0
  return entry;
534
0
}
535
536
static gboolean
537
soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge)
538
0
{
539
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
540
0
  GList *lru_item;
541
542
0
  if (entry->dirty) {
543
0
    g_cancellable_cancel (entry->cancellable);
544
0
    return FALSE;
545
0
  }
546
547
0
  g_assert (!entry->dirty);
548
0
  g_assert (g_list_length (priv->lru_start) == g_hash_table_size (priv->cache));
549
550
0
  if (!g_hash_table_remove (priv->cache, GUINT_TO_POINTER (entry->key))) {
551
0
                g_mutex_unlock (&priv->mutex);
552
0
    return FALSE;
553
0
        }
554
555
  /* Remove from LRU */
556
0
  lru_item = g_list_find (priv->lru_start, entry);
557
0
  priv->lru_start = g_list_delete_link (priv->lru_start, lru_item);
558
559
  /* Adjust cache size */
560
0
  priv->size -= entry->length;
561
562
0
  g_assert (g_list_length (priv->lru_start) == g_hash_table_size (priv->cache));
563
564
  /* Free resources */
565
0
  if (purge) {
566
0
    GFile *file = get_file_from_entry (cache, entry);
567
0
    g_file_delete (file, NULL, NULL);
568
0
    g_object_unref (file);
569
0
  }
570
0
  soup_cache_entry_free (entry);
571
572
0
  return TRUE;
573
0
}
574
575
static gint
576
lru_compare_func (gconstpointer a, gconstpointer b)
577
0
{
578
0
  SoupCacheEntry *entry_a = (SoupCacheEntry *)a;
579
0
  SoupCacheEntry *entry_b = (SoupCacheEntry *)b;
580
581
  /* The rationale of this sorting func is
582
   *
583
   * 1. sort by hits -> LRU algorithm, then
584
   *
585
   * 2. sort by freshness lifetime, we better discard first
586
   * entries that are close to expire
587
   *
588
   * 3. sort by size, replace first small size resources as they
589
   * are cheaper to download
590
   */
591
592
  /* Sort by hits */
593
0
  if (entry_a->hits != entry_b->hits)
594
0
    return entry_a->hits - entry_b->hits;
595
596
  /* Sort by freshness_lifetime */
597
0
  if (entry_a->freshness_lifetime != entry_b->freshness_lifetime)
598
0
    return entry_a->freshness_lifetime - entry_b->freshness_lifetime;
599
600
  /* Sort by size */
601
0
  return entry_a->length - entry_b->length;
602
0
}
603
604
static gboolean
605
cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add)
606
0
{
607
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
608
  /* We could add here some more heuristics. TODO: review how
609
     this is done by other HTTP caches */
610
611
0
  return length_to_add <= priv->max_entry_data_size;
612
0
}
613
614
static void
615
make_room_for_new_entry (SoupCache *cache, guint length_to_add)
616
0
{
617
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
618
0
  GList *lru_entry = priv->lru_start;
619
620
  /* Check that there is enough room for the new entry. This is
621
     an approximation as we're not working out the size of the
622
     cache file or the size of the headers for performance
623
     reasons. TODO: check if that would be really that expensive */
624
625
0
  while (lru_entry &&
626
0
         (length_to_add + priv->size > priv->max_size)) {
627
0
    SoupCacheEntry *old_entry = (SoupCacheEntry *)lru_entry->data;
628
629
    /* Discard entries. Once cancelled resources will be
630
     * freed in close_ready_cb
631
     */
632
0
    if (soup_cache_entry_remove (cache, old_entry, TRUE))
633
0
      lru_entry = priv->lru_start;
634
0
    else
635
0
      lru_entry = g_list_next (lru_entry);
636
0
  }
637
0
}
638
639
static gboolean
640
soup_cache_entry_insert (SoupCache *cache,
641
       SoupCacheEntry *entry,
642
       gboolean sort)
643
0
{
644
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
645
0
  guint length_to_add = 0;
646
0
  SoupCacheEntry *old_entry;
647
648
  /* Fill the key */
649
0
  entry->key = get_cache_key_from_uri ((const char *) entry->uri);
650
651
0
  if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
652
0
    length_to_add = soup_message_headers_get_content_length (entry->headers);
653
654
  /* Check if we are going to store the resource depending on its size */
655
0
  if (length_to_add) {
656
0
    if (!cache_accepts_entries_of_size (cache, length_to_add))
657
0
      return FALSE;
658
659
    /* Make room for new entry if needed */
660
0
    make_room_for_new_entry (cache, length_to_add);
661
0
  }
662
663
  /* Remove any previous entry */
664
0
  if ((old_entry = g_hash_table_lookup (priv->cache, GUINT_TO_POINTER (entry->key))) != NULL) {
665
0
    if (!soup_cache_entry_remove (cache, old_entry, TRUE))
666
0
      return FALSE;
667
0
  }
668
669
  /* Add to hash table */
670
0
  g_hash_table_insert (priv->cache, GUINT_TO_POINTER (entry->key), entry);
671
672
  /* Compute new cache size */
673
0
  priv->size += length_to_add;
674
675
  /* Update LRU */
676
0
  if (sort)
677
0
    priv->lru_start = g_list_insert_sorted (priv->lru_start, entry, lru_compare_func);
678
0
  else
679
0
    priv->lru_start = g_list_prepend (priv->lru_start, entry);
680
681
0
  g_assert (g_list_length (priv->lru_start) == g_hash_table_size (priv->cache));
682
683
0
  return TRUE;
684
0
}
685
686
static SoupCacheEntry*
687
soup_cache_entry_lookup (SoupCache *cache,
688
       SoupMessage *msg)
689
0
{
690
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
691
0
  SoupCacheEntry *entry;
692
0
  guint32 key;
693
0
  char *uri = NULL;
694
695
0
  uri = g_uri_to_string_partial (soup_message_get_uri (msg), G_URI_HIDE_PASSWORD);
696
0
  key = get_cache_key_from_uri ((const char *) uri);
697
698
0
  entry = g_hash_table_lookup (priv->cache, GUINT_TO_POINTER (key));
699
700
0
  if (entry != NULL && (strcmp (entry->uri, uri) != 0))
701
0
    entry = NULL;
702
703
0
  g_free (uri);
704
0
  return entry;
705
0
}
706
707
GInputStream *
708
soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
709
0
{
710
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
711
0
  SoupCacheEntry *entry;
712
0
  GInputStream *file_stream, *body_stream, *cache_stream, *client_stream;
713
0
  GFile *file;
714
0
        SoupMessageMetrics *metrics;
715
716
0
  g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
717
0
  g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
718
719
0
        soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_REQUEST_START);
720
721
0
        g_mutex_lock (&priv->mutex);
722
0
  entry = soup_cache_entry_lookup (cache, msg);
723
0
        g_mutex_unlock (&priv->mutex);
724
0
  g_return_val_if_fail (entry, NULL);
725
726
0
  file = get_file_from_entry (cache, entry);
727
0
  file_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
728
0
  g_object_unref (file);
729
730
  /* Do not change the original message if there is no resource */
731
0
  if (!file_stream)
732
0
    return NULL;
733
734
0
  body_stream = soup_body_input_stream_new (file_stream, SOUP_ENCODING_CONTENT_LENGTH, entry->length);
735
0
  g_object_unref (file_stream);
736
737
0
  if (!body_stream)
738
0
    return NULL;
739
740
0
        metrics = soup_message_get_metrics (msg);
741
0
        if (metrics)
742
0
                metrics->response_body_size = entry->length;
743
744
  /* If we are told to send a response from cache any validation
745
     in course is over by now */
746
0
  entry->being_validated = FALSE;
747
748
  /* Message starting */
749
0
  soup_message_starting (msg);
750
751
0
        soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_START);
752
753
  /* Status */
754
0
  soup_message_set_status (msg, entry->status_code, NULL);
755
756
  /* Headers */
757
0
  copy_end_to_end_headers (entry->headers, soup_message_get_response_headers (msg));
758
759
  /* Create the cache stream. */
760
0
  soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
761
0
  cache_stream = soup_session_setup_message_body_input_stream (priv->session,
762
0
                                                                     msg, body_stream,
763
0
                                                                     SOUP_STAGE_ENTITY_BODY);
764
0
  g_object_unref (body_stream);
765
766
0
  client_stream = soup_cache_client_input_stream_new (cache_stream);
767
0
  g_object_unref (cache_stream);
768
769
0
  return client_stream;
770
0
}
771
772
static void
773
msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
774
0
{
775
0
  g_object_set_data (G_OBJECT (msg), "response-time", GINT_TO_POINTER (time (NULL)));
776
0
  g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
777
0
}
778
779
static void
780
msg_starting_cb (SoupMessage *msg, gpointer user_data)
781
0
{
782
0
  g_object_set_data (G_OBJECT (msg), "request-time", GINT_TO_POINTER (time (NULL)));
783
0
  g_signal_connect (msg, "got-headers", G_CALLBACK (msg_got_headers_cb), user_data);
784
0
  g_signal_handlers_disconnect_by_func (msg, msg_starting_cb, user_data);
785
0
}
786
787
static void
788
request_queued (SoupSessionFeature *feature,
789
    SoupMessage        *msg)
790
0
{
791
0
  g_signal_connect (msg, "starting", G_CALLBACK (msg_starting_cb), feature);
792
0
}
793
794
static void
795
attach (SoupSessionFeature *feature, SoupSession *session)
796
0
{
797
0
  SoupCache *cache = SOUP_CACHE (feature);
798
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
799
0
  priv->session = session;
800
0
}
801
802
static void
803
soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
804
          gpointer interface_data)
805
0
{
806
0
  feature_interface->attach = attach;
807
0
  feature_interface->request_queued = request_queued;
808
0
}
809
810
typedef struct {
811
  SoupCache *cache;
812
  SoupCacheEntry *entry;
813
} StreamHelper;
814
815
static void
816
istream_caching_finished (SoupCacheInputStream *istream,
817
        gsize                 bytes_written,
818
        GError               *error,
819
        gpointer              user_data)
820
0
{
821
0
  StreamHelper *helper = (StreamHelper *) user_data;
822
0
  SoupCache *cache = helper->cache;
823
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
824
0
  SoupCacheEntry *entry = helper->entry;
825
826
0
        g_mutex_lock (&priv->mutex);
827
828
0
  --priv->n_pending;
829
830
0
  entry->dirty = FALSE;
831
0
  entry->length = bytes_written;
832
0
  g_clear_object (&entry->cancellable);
833
834
0
  if (error) {
835
    /* Update cache size */
836
0
    if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
837
0
      priv->size -= soup_message_headers_get_content_length (entry->headers);
838
839
0
    soup_cache_entry_remove (cache, entry, TRUE);
840
0
    helper->entry = entry = NULL;
841
0
    goto cleanup;
842
0
  }
843
844
0
  if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CONTENT_LENGTH) {
845
846
0
    if (cache_accepts_entries_of_size (cache, entry->length)) {
847
0
      make_room_for_new_entry (cache, entry->length);
848
0
      priv->size += entry->length;
849
0
    } else {
850
0
      soup_cache_entry_remove (cache, entry, TRUE);
851
0
      helper->entry = entry = NULL;
852
0
    }
853
0
  }
854
855
0
 cleanup:
856
0
        g_mutex_unlock (&priv->mutex);
857
0
  g_object_unref (helper->cache);
858
0
  g_slice_free (StreamHelper, helper);
859
0
}
860
861
static GInputStream*
862
soup_cache_content_processor_wrap_input (SoupContentProcessor *processor,
863
           GInputStream *base_stream,
864
           SoupMessage *msg,
865
           GError **error)
866
0
{
867
0
  SoupCache *cache = (SoupCache*) processor;
868
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
869
0
  SoupCacheEntry *entry;
870
0
  SoupCacheability cacheability;
871
0
  GInputStream *istream;
872
0
  GFile *file;
873
0
  StreamHelper *helper;
874
0
  time_t request_time, response_time;
875
876
0
        g_mutex_lock (&priv->mutex);
877
878
  /* First of all, check if we should cache the resource. */
879
0
  cacheability = soup_cache_get_cacheability (cache, msg);
880
0
  entry = soup_cache_entry_lookup (cache, msg);
881
882
0
  if (cacheability & SOUP_CACHE_INVALIDATES) {
883
0
    if (entry)
884
0
      soup_cache_entry_remove (cache, entry, TRUE);
885
0
                g_mutex_unlock (&priv->mutex);
886
0
    return NULL;
887
0
  }
888
889
0
  if (cacheability & SOUP_CACHE_VALIDATES) {
890
    /* It's possible to get a CACHE_VALIDATES with no
891
     * entry in the hash table. This could happen if for
892
     * example the soup client is the one creating the
893
     * conditional request.
894
     */
895
0
    if (entry)
896
0
      soup_cache_update_from_conditional_request (cache, msg);
897
0
                g_mutex_unlock (&priv->mutex);
898
0
    return NULL;
899
0
  }
900
901
0
  if (!(cacheability & SOUP_CACHE_CACHEABLE)) {
902
0
                g_mutex_unlock (&priv->mutex);
903
0
    return NULL;
904
0
        }
905
906
  /* Check if we are already caching this resource */
907
0
  if (entry && (entry->dirty || entry->being_validated)) {
908
0
                g_mutex_unlock (&priv->mutex);
909
0
    return NULL;
910
0
        }
911
912
  /* Create a new entry, deleting any old one if present */
913
0
  if (entry)
914
0
    soup_cache_entry_remove (cache, entry, TRUE);
915
916
0
  request_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time"));
917
0
  response_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "response-time"));
918
0
  entry = soup_cache_entry_new (cache, msg, request_time, response_time);
919
0
  entry->hits = 1;
920
0
  entry->dirty = TRUE;
921
922
  /* Do not continue if it can not be stored */
923
0
  if (!soup_cache_entry_insert (cache, entry, TRUE)) {
924
0
    soup_cache_entry_free (entry);
925
0
                g_mutex_unlock (&priv->mutex);
926
0
    return NULL;
927
0
  }
928
929
0
  entry->cancellable = g_cancellable_new ();
930
0
  ++priv->n_pending;
931
932
0
        g_mutex_unlock (&priv->mutex);
933
934
0
  helper = g_slice_new (StreamHelper);
935
0
  helper->cache = g_object_ref (cache);
936
0
  helper->entry = entry;
937
938
0
  file = get_file_from_entry (cache, entry);
939
0
  istream = soup_cache_input_stream_new (base_stream, file);
940
0
  g_object_unref (file);
941
942
0
  g_signal_connect (istream, "caching-finished", G_CALLBACK (istream_caching_finished), helper);
943
944
0
  return istream;
945
0
}
946
947
static void
948
soup_cache_content_processor_init (SoupContentProcessorInterface *processor_interface,
949
           gpointer interface_data)
950
0
{
951
0
  soup_cache_default_content_processor_interface =
952
0
    g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
953
954
0
  processor_interface->processing_stage = SOUP_STAGE_ENTITY_BODY;
955
0
  processor_interface->wrap_input = soup_cache_content_processor_wrap_input;
956
0
}
957
958
static void
959
soup_cache_init (SoupCache *cache)
960
0
{
961
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
962
963
0
  priv->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
964
  /* LRU */
965
0
  priv->lru_start = NULL;
966
967
  /* */
968
0
  priv->n_pending = 0;
969
970
  /* Cache size */
971
0
  priv->max_size = DEFAULT_MAX_SIZE;
972
0
  priv->max_entry_data_size = priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
973
0
  priv->size = 0;
974
975
0
        g_mutex_init (&priv->mutex);
976
0
}
977
978
static void
979
remove_cache_item (gpointer data,
980
       gpointer user_data)
981
0
{
982
0
  soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, FALSE);
983
0
}
984
985
static void
986
soup_cache_finalize (GObject *object)
987
0
{
988
0
  SoupCachePrivate *priv = soup_cache_get_instance_private ((SoupCache*)object);
989
0
  GList *entries;
990
991
  /* Cannot use g_hash_table_foreach as callbacks must not modify the hash table */
992
0
  entries = g_hash_table_get_values (priv->cache);
993
0
  g_list_foreach (entries, remove_cache_item, object);
994
0
  g_list_free (entries);
995
996
0
  g_hash_table_destroy (priv->cache);
997
0
  g_free (priv->cache_dir);
998
999
0
  g_list_free (priv->lru_start);
1000
1001
0
        g_mutex_clear (&priv->mutex);
1002
1003
0
  G_OBJECT_CLASS (soup_cache_parent_class)->finalize (object);
1004
0
}
1005
1006
static void
1007
soup_cache_set_property (GObject *object, guint prop_id,
1008
        const GValue *value, GParamSpec *pspec)
1009
0
{
1010
0
  SoupCachePrivate *priv = soup_cache_get_instance_private ((SoupCache*)object);
1011
1012
0
  switch (prop_id) {
1013
0
  case PROP_CACHE_DIR:
1014
0
    g_assert (!priv->cache_dir);
1015
1016
0
    priv->cache_dir = g_value_dup_string (value);
1017
1018
0
    if (!priv->cache_dir)
1019
      /* Set a default cache dir, different for each user */
1020
0
      priv->cache_dir = g_build_filename (g_get_user_cache_dir (),
1021
0
                  "httpcache",
1022
0
                  NULL);
1023
1024
    /* Create directory if it does not exist */
1025
0
    if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
1026
0
      g_mkdir_with_parents (priv->cache_dir, 0700);
1027
0
    break;
1028
0
  case PROP_CACHE_TYPE:
1029
0
    priv->cache_type = g_value_get_enum (value);
1030
    /* TODO: clear private entries and issue a warning if moving to shared? */
1031
0
    break;
1032
0
  default:
1033
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1034
0
    break;
1035
0
  }
1036
0
}
1037
1038
static void
1039
soup_cache_get_property (GObject *object, guint prop_id,
1040
       GValue *value, GParamSpec *pspec)
1041
0
{
1042
0
  SoupCachePrivate *priv = soup_cache_get_instance_private ((SoupCache*)object);
1043
1044
0
  switch (prop_id) {
1045
0
  case PROP_CACHE_DIR:
1046
0
    g_value_set_string (value, priv->cache_dir);
1047
0
    break;
1048
0
  case PROP_CACHE_TYPE:
1049
0
    g_value_set_enum (value, priv->cache_type);
1050
0
    break;
1051
0
  default:
1052
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1053
0
    break;
1054
0
  }
1055
0
}
1056
1057
static void
1058
soup_cache_class_init (SoupCacheClass *cache_class)
1059
0
{
1060
0
  GObjectClass *gobject_class = (GObjectClass *)cache_class;
1061
1062
0
  gobject_class->finalize = soup_cache_finalize;
1063
0
  gobject_class->set_property = soup_cache_set_property;
1064
0
  gobject_class->get_property = soup_cache_get_property;
1065
1066
0
  cache_class->get_cacheability = get_cacheability;
1067
1068
        /**
1069
         * SoupCache:cache-dir:
1070
         * The directory to store the cache files.
1071
         */
1072
0
        properties[PROP_CACHE_DIR] =
1073
0
                g_param_spec_string ("cache-dir",
1074
0
                                     "Cache directory",
1075
0
                                     "The directory to store the cache files",
1076
0
                                     NULL,
1077
0
                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
1078
0
                                     G_PARAM_STATIC_STRINGS);
1079
1080
        /**
1081
         * SoupCache:cache-type:
1082
         * Whether the cache is private or shared.
1083
         */
1084
0
        properties[PROP_CACHE_TYPE] =
1085
0
                g_param_spec_enum ("cache-type",
1086
0
                                   "Cache type",
1087
0
                                   "Whether the cache is private or shared",
1088
0
                                   SOUP_TYPE_CACHE_TYPE,
1089
0
                                   SOUP_CACHE_SINGLE_USER,
1090
0
                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
1091
0
                                   G_PARAM_STATIC_STRINGS);
1092
1093
0
        g_object_class_install_properties (gobject_class, LAST_PROPERTY, properties);
1094
0
}
1095
1096
/**
1097
 * SoupCacheType:
1098
 * @SOUP_CACHE_SINGLE_USER: a single-user cache
1099
 * @SOUP_CACHE_SHARED: a shared cache
1100
 *
1101
 * The type of cache; this affects what kinds of responses will be
1102
 * saved.
1103
 *
1104
 */
1105
1106
/**
1107
 * soup_cache_new:
1108
 * @cache_dir: (nullable): the directory to store the cached data, or %NULL
1109
 *   to use the default one. Note that since the cache isn't safe to access for
1110
 *   multiple processes at once, and the default directory isn't namespaced by
1111
 *   process, clients are strongly discouraged from passing %NULL.
1112
 * @cache_type: the #SoupCacheType of the cache
1113
 *
1114
 * Creates a new #SoupCache.
1115
 *
1116
 * Returns: a new #SoupCache
1117
 *
1118
 */
1119
SoupCache *
1120
soup_cache_new (const char *cache_dir, SoupCacheType cache_type)
1121
0
{
1122
0
  return g_object_new (SOUP_TYPE_CACHE,
1123
0
           "cache-dir", cache_dir,
1124
0
           "cache-type", cache_type,
1125
0
           NULL);
1126
0
}
1127
1128
/**
1129
 * soup_cache_has_response:
1130
 * @cache: a #SoupCache
1131
 * @msg: a #SoupMessage
1132
 *
1133
 * This function calculates whether the @cache object has a proper
1134
 * response for the request @msg given the flags both in the request
1135
 * and the cached reply and the time ellapsed since it was cached.
1136
 *
1137
 * Returns: whether or not the @cache has a valid response for @msg
1138
 *
1139
 */
1140
SoupCacheResponse
1141
soup_cache_has_response (SoupCache *cache, SoupMessage *msg)
1142
0
{
1143
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1144
0
  SoupCacheEntry *entry;
1145
0
  const char *cache_control;
1146
0
  gpointer value;
1147
0
  int max_age, max_stale, min_fresh;
1148
0
  GList *lru_item, *item;
1149
1150
0
        g_mutex_lock (&priv->mutex);
1151
1152
0
  entry = soup_cache_entry_lookup (cache, msg);
1153
1154
  /* 1. The presented Request-URI and that of stored response
1155
   * match
1156
   */
1157
0
  if (!entry) {
1158
0
                g_mutex_unlock (&priv->mutex);
1159
0
    return SOUP_CACHE_RESPONSE_STALE;
1160
0
        }
1161
1162
  /* Increase hit count. Take sorting into account */
1163
0
  entry->hits++;
1164
0
  lru_item = g_list_find (priv->lru_start, entry);
1165
0
  item = lru_item;
1166
0
  while (item->next && lru_compare_func (item->data, item->next->data) > 0)
1167
0
    item = g_list_next (item);
1168
1169
0
  if (item != lru_item) {
1170
0
    priv->lru_start = g_list_remove_link (priv->lru_start, lru_item);
1171
0
    item = g_list_insert_sorted (item, lru_item->data, lru_compare_func);
1172
0
                (void)item; // Silence scan-build warning
1173
0
    g_list_free (lru_item);
1174
0
  }
1175
1176
0
        g_mutex_unlock (&priv->mutex);
1177
1178
0
  if (entry->dirty || entry->being_validated)
1179
0
    return SOUP_CACHE_RESPONSE_STALE;
1180
1181
  /* 2. The request method associated with the stored response
1182
   *  allows it to be used for the presented request
1183
   */
1184
1185
  /* In practice this means we only return our resource for GET,
1186
   * cacheability for other methods is a TODO in the RFC
1187
   * (TODO: although we could return the headers for HEAD
1188
   * probably).
1189
   */
1190
0
  if (soup_message_get_method (msg) != SOUP_METHOD_GET)
1191
0
    return SOUP_CACHE_RESPONSE_STALE;
1192
1193
  /* 3. Selecting request-headers nominated by the stored
1194
   * response (if any) match those presented.
1195
   */
1196
1197
  /* TODO */
1198
1199
  /* 4. The request is a conditional request issued by the client.
1200
   */
1201
0
  if (soup_message_headers_get_one_common (soup_message_get_request_headers (msg), SOUP_HEADER_IF_MODIFIED_SINCE) ||
1202
0
      soup_message_headers_get_list_common (soup_message_get_request_headers (msg), SOUP_HEADER_IF_NONE_MATCH))
1203
0
    return SOUP_CACHE_RESPONSE_STALE;
1204
1205
  /* 5. The presented request and stored response are free from
1206
   * directives that would prevent its use.
1207
   */
1208
1209
0
  max_age = max_stale = min_fresh = -1;
1210
1211
  /* For HTTP 1.0 compatibility. RFC2616 section 14.9.4
1212
   */
1213
0
  if (soup_message_headers_header_contains_common (soup_message_get_request_headers (msg), SOUP_HEADER_PRAGMA, "no-cache"))
1214
0
    return SOUP_CACHE_RESPONSE_STALE;
1215
1216
0
  cache_control = soup_message_headers_get_list_common (soup_message_get_request_headers (msg), SOUP_HEADER_CACHE_CONTROL);
1217
0
  if (cache_control && *cache_control) {
1218
0
    GHashTable *hash = soup_header_parse_param_list (cache_control);
1219
1220
0
    if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
1221
0
      soup_header_free_param_list (hash);
1222
0
      return SOUP_CACHE_RESPONSE_STALE;
1223
0
    }
1224
1225
0
    if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
1226
0
      soup_header_free_param_list (hash);
1227
0
      return SOUP_CACHE_RESPONSE_STALE;
1228
0
    }
1229
1230
0
    if (g_hash_table_lookup_extended (hash, "max-age", NULL, &value) && value) {
1231
0
      max_age = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1232
      /* Forcing cache revalidaton
1233
       */
1234
0
      if (!max_age) {
1235
0
        soup_header_free_param_list (hash);
1236
0
        return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1237
0
      }
1238
0
    }
1239
1240
    /* max-stale can have no value set, we need to use _extended */
1241
0
    if (g_hash_table_lookup_extended (hash, "max-stale", NULL, &value)) {
1242
0
      if (value)
1243
0
        max_stale = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1244
0
      else
1245
0
        max_stale = G_MAXINT32;
1246
0
    }
1247
1248
0
    value = g_hash_table_lookup (hash, "min-fresh");
1249
0
    if (value)
1250
0
      min_fresh = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1251
1252
0
    soup_header_free_param_list (hash);
1253
1254
0
    if (max_age > 0) {
1255
0
      guint current_age = soup_cache_entry_get_current_age (entry);
1256
1257
      /* If we are over max-age and max-stale is not
1258
         set, do not use the value from the cache
1259
         without validation */
1260
0
      if ((guint) max_age <= current_age && max_stale == -1)
1261
0
        return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1262
0
    }
1263
0
  }
1264
1265
  /* 6. The stored response is either: fresh, allowed to be
1266
   * served stale or succesfully validated
1267
   */
1268
0
  if (!soup_cache_entry_is_fresh_enough (entry, min_fresh)) {
1269
    /* Not fresh, can it be served stale? */
1270
1271
    /* When the must-revalidate directive is present in a
1272
     * response received by a cache, that cache MUST NOT
1273
     * use the entry after it becomes stale
1274
     */
1275
    /* TODO consider also proxy-revalidate & s-maxage */
1276
0
    if (entry->must_revalidate)
1277
0
      return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1278
1279
0
    if (max_stale != -1) {
1280
      /* G_MAXINT32 means we accept any staleness */
1281
0
      if (max_stale == G_MAXINT32)
1282
0
        return SOUP_CACHE_RESPONSE_FRESH;
1283
1284
0
      if ((soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= (guint) max_stale)
1285
0
        return SOUP_CACHE_RESPONSE_FRESH;
1286
0
    }
1287
1288
0
    return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1289
0
  }
1290
1291
0
  return SOUP_CACHE_RESPONSE_FRESH;
1292
0
}
1293
1294
/**
1295
 * soup_cache_get_cacheability:
1296
 * @cache: a #SoupCache
1297
 * @msg: a #SoupMessage
1298
 *
1299
 * Calculates whether the @msg can be cached or not.
1300
 *
1301
 * Returns: a [flags@Cacheability] value indicating whether the @msg can be cached
1302
 *   or not.
1303
 */
1304
SoupCacheability
1305
soup_cache_get_cacheability (SoupCache *cache, SoupMessage *msg)
1306
0
{
1307
0
  g_return_val_if_fail (SOUP_IS_CACHE (cache), SOUP_CACHE_UNCACHEABLE);
1308
0
  g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_CACHE_UNCACHEABLE);
1309
1310
0
  return SOUP_CACHE_GET_CLASS (cache)->get_cacheability (cache, msg);
1311
0
}
1312
1313
static gboolean
1314
force_flush_timeout (gpointer data)
1315
0
{
1316
0
  gboolean *forced = (gboolean *)data;
1317
0
  *forced = TRUE;
1318
1319
0
  return FALSE;
1320
0
}
1321
1322
/**
1323
 * soup_cache_flush:
1324
 * @cache: a #SoupCache
1325
 *
1326
 * Forces all pending writes in the @cache to be
1327
 * committed to disk.
1328
 *
1329
 * For doing so it will iterate the [struct@GLib.MainContext] associated with
1330
 * @cache's session as long as needed.
1331
 *
1332
 * Contrast with [method@Cache.dump], which writes out the cache index file.
1333
 */
1334
void
1335
soup_cache_flush (SoupCache *cache)
1336
0
{
1337
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1338
0
  GMainContext *async_context;
1339
0
  SoupSession *session;
1340
0
  GSource *timeout;
1341
0
  gboolean forced = FALSE;
1342
1343
0
  g_return_if_fail (SOUP_IS_CACHE (cache));
1344
1345
0
  session = priv->session;
1346
0
  g_return_if_fail (SOUP_IS_SESSION (session));
1347
0
  async_context = g_main_context_get_thread_default ();
1348
1349
  /* We give cache 10 secs to finish */
1350
0
  timeout = soup_add_timeout (async_context, 10000, force_flush_timeout, &forced);
1351
1352
0
  while (!forced && priv->n_pending > 0)
1353
0
    g_main_context_iteration (async_context, FALSE);
1354
1355
0
  if (!forced)
1356
0
    g_source_destroy (timeout);
1357
0
  else
1358
0
    g_warning ("Cache flush finished despite %d pending requests", priv->n_pending);
1359
1360
0
        g_source_unref (timeout);
1361
0
}
1362
1363
typedef void (* SoupCacheForeachFileFunc) (SoupCache *cache, const char *name, gpointer user_data);
1364
1365
static void
1366
soup_cache_foreach_file (SoupCache *cache, SoupCacheForeachFileFunc func, gpointer user_data)
1367
0
{
1368
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1369
0
  GDir *dir;
1370
0
  const char *name;
1371
1372
0
  dir = g_dir_open (priv->cache_dir, 0, NULL);
1373
0
  while ((name = g_dir_read_name (dir))) {
1374
0
    if (g_str_has_prefix (name, "soup."))
1375
0
        continue;
1376
1377
0
    func (cache, name, user_data);
1378
0
  }
1379
0
  g_dir_close (dir);
1380
0
}
1381
1382
static void
1383
clear_cache_item (gpointer data,
1384
      gpointer user_data)
1385
0
{
1386
0
  soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, TRUE);
1387
0
}
1388
1389
static void
1390
delete_cache_file (SoupCache *cache, const char *name, gpointer user_data)
1391
0
{
1392
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1393
0
  gchar *path;
1394
1395
0
  path = g_build_filename (priv->cache_dir, name, NULL);
1396
0
  g_unlink (path);
1397
0
  g_free (path);
1398
0
}
1399
1400
static void
1401
clear_cache_files (SoupCache *cache)
1402
0
{
1403
0
  soup_cache_foreach_file (cache, delete_cache_file, NULL);
1404
0
}
1405
1406
/**
1407
 * soup_cache_clear:
1408
 * @cache: a #SoupCache
1409
 *
1410
 * Will remove all entries in the @cache plus all the cache files.
1411
 *
1412
 * This is not thread safe and must be called only from the thread that created the [class@Cache]
1413
 */
1414
void
1415
soup_cache_clear (SoupCache *cache)
1416
0
{
1417
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1418
0
  GList *entries;
1419
1420
0
  g_return_if_fail (SOUP_IS_CACHE (cache));
1421
0
  g_return_if_fail (priv->cache);
1422
1423
  /* Cannot use g_hash_table_foreach as callbacks must not modify the hash table */
1424
0
  entries = g_hash_table_get_values (priv->cache);
1425
0
  g_list_foreach (entries, clear_cache_item, cache);
1426
0
  g_list_free (entries);
1427
1428
  /* Remove also any file not associated with a cache entry. */
1429
0
  clear_cache_files (cache);
1430
0
}
1431
1432
SoupMessage *
1433
soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original)
1434
0
{
1435
0
        SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1436
0
  SoupMessage *msg;
1437
0
  GUri *uri;
1438
0
  SoupCacheEntry *entry;
1439
0
  const char *last_modified, *etag;
1440
0
  GList *disabled_features, *f;
1441
1442
0
  g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
1443
0
  g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL);
1444
1445
  /* Add the validator entries in the header from the cached data */
1446
0
        g_mutex_lock (&priv->mutex);
1447
0
  entry = soup_cache_entry_lookup (cache, original);
1448
0
        g_mutex_unlock (&priv->mutex);
1449
0
  g_return_val_if_fail (entry, NULL);
1450
1451
0
  last_modified = soup_message_headers_get_one_common (entry->headers, SOUP_HEADER_LAST_MODIFIED);
1452
0
  etag = soup_message_headers_get_one_common (entry->headers, SOUP_HEADER_ETAG);
1453
1454
0
  if (!last_modified && !etag)
1455
0
    return NULL;
1456
1457
0
  entry->being_validated = TRUE;
1458
1459
  /* Copy the data we need from the original message */
1460
0
  uri = soup_message_get_uri (original);
1461
0
  msg = soup_message_new_from_uri (soup_message_get_method (original), uri);
1462
0
  soup_message_set_flags (msg, soup_message_get_flags (original));
1463
0
  soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
1464
1465
0
  soup_message_headers_foreach (soup_message_get_request_headers (original),
1466
0
              (SoupMessageHeadersForeachFunc)copy_headers,
1467
0
              soup_message_get_request_headers (msg));
1468
1469
0
  disabled_features = soup_message_get_disabled_features (original);
1470
0
  for (f = disabled_features; f; f = f->next)
1471
0
    soup_message_disable_feature (msg, (GType) GPOINTER_TO_SIZE (f->data));
1472
0
  g_list_free (disabled_features);
1473
1474
0
  if (last_modified)
1475
0
    soup_message_headers_append_common (soup_message_get_request_headers (msg),
1476
0
                                                    SOUP_HEADER_IF_MODIFIED_SINCE,
1477
0
                                                    last_modified, SOUP_HEADER_VALUE_TRUSTED);
1478
0
  if (etag)
1479
0
    soup_message_headers_append_common (soup_message_get_request_headers (msg),
1480
0
                                                    SOUP_HEADER_IF_NONE_MATCH,
1481
0
                                                    etag, SOUP_HEADER_VALUE_TRUSTED);
1482
1483
0
  return msg;
1484
0
}
1485
1486
void
1487
soup_cache_cancel_conditional_request (SoupCache   *cache,
1488
               SoupMessage *msg)
1489
0
{
1490
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1491
0
  SoupCacheEntry *entry;
1492
1493
0
        g_mutex_lock (&priv->mutex);
1494
0
  entry = soup_cache_entry_lookup (cache, msg);
1495
0
        g_mutex_unlock (&priv->mutex);
1496
0
  if (entry)
1497
0
    entry->being_validated = FALSE;
1498
1499
0
  soup_session_cancel_message (priv->session, msg);
1500
0
}
1501
1502
void
1503
soup_cache_update_from_conditional_request (SoupCache   *cache,
1504
              SoupMessage *msg)
1505
0
{
1506
0
        SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1507
0
  SoupCacheEntry *entry;
1508
1509
0
        g_mutex_lock (&priv->mutex);
1510
0
        entry = soup_cache_entry_lookup (cache, msg);
1511
0
        g_mutex_unlock (&priv->mutex);
1512
0
  if (!entry)
1513
0
    return;
1514
1515
0
  entry->being_validated = FALSE;
1516
1517
0
  if (soup_message_get_status (msg) == SOUP_STATUS_NOT_MODIFIED) {
1518
0
    soup_message_headers_foreach (soup_message_get_response_headers (msg),
1519
0
                (SoupMessageHeadersForeachFunc) remove_headers,
1520
0
                entry->headers);
1521
0
    copy_end_to_end_headers (soup_message_get_response_headers (msg), entry->headers);
1522
1523
0
    soup_cache_entry_set_freshness (entry, msg, cache);
1524
0
  }
1525
0
}
1526
1527
static void
1528
pack_entry (gpointer data,
1529
      gpointer user_data)
1530
0
{
1531
0
  SoupCacheEntry *entry = (SoupCacheEntry *) data;
1532
0
  SoupMessageHeadersIter iter;
1533
0
  const char *header_key, *header_value;
1534
0
  GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
1535
1536
  /* Do not store non-consolidated entries */
1537
0
  if (entry->dirty || !entry->key)
1538
0
    return;
1539
1540
0
  g_variant_builder_open (entries_builder, G_VARIANT_TYPE (SOUP_CACHE_PHEADERS_FORMAT));
1541
0
  g_variant_builder_add (entries_builder, "s", entry->uri);
1542
0
  g_variant_builder_add (entries_builder, "b", entry->must_revalidate);
1543
0
  g_variant_builder_add (entries_builder, "u", entry->freshness_lifetime);
1544
0
  g_variant_builder_add (entries_builder, "u", entry->corrected_initial_age);
1545
0
  g_variant_builder_add (entries_builder, "u", entry->response_time);
1546
0
  g_variant_builder_add (entries_builder, "u", entry->hits);
1547
0
  g_variant_builder_add (entries_builder, "u", entry->length);
1548
0
  g_variant_builder_add (entries_builder, "q", entry->status_code);
1549
1550
  /* Pack headers */
1551
0
  g_variant_builder_open (entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_HEADERS_FORMAT));
1552
0
  soup_message_headers_iter_init (&iter, entry->headers);
1553
0
  while (soup_message_headers_iter_next (&iter, &header_key, &header_value)) {
1554
0
    if (g_utf8_validate (header_value, -1, NULL))
1555
0
      g_variant_builder_add (entries_builder, SOUP_CACHE_HEADERS_FORMAT,
1556
0
                 header_key, header_value);
1557
0
  }
1558
0
  g_variant_builder_close (entries_builder); /* "a" SOUP_CACHE_HEADERS_FORMAT */
1559
0
  g_variant_builder_close (entries_builder); /* SOUP_CACHE_PHEADERS_FORMAT */
1560
0
}
1561
1562
/**
1563
 * soup_cache_dump:
1564
 * @cache: a #SoupCache
1565
 *
1566
 * Synchronously writes the cache index out to disk.
1567
 *
1568
 * Contrast with [method@Cache.flush], which writes pending cache *entries* to
1569
 * disk.
1570
 *
1571
 * You must call this before exiting if you want your cache data to
1572
 * persist between sessions.
1573
 *
1574
 * This is not thread safe and must be called only from the thread that created the [class@Cache]
1575
 */
1576
void
1577
soup_cache_dump (SoupCache *cache)
1578
0
{
1579
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1580
0
  char *filename;
1581
0
  GVariantBuilder entries_builder;
1582
0
  GVariant *cache_variant;
1583
1584
0
  if (!g_list_length (priv->lru_start))
1585
0
    return;
1586
1587
  /* Create the builder and iterate over all entries */
1588
0
  g_variant_builder_init (&entries_builder, G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT));
1589
0
  g_variant_builder_add (&entries_builder, "q", SOUP_CACHE_CURRENT_VERSION);
1590
0
  g_variant_builder_open (&entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_PHEADERS_FORMAT));
1591
0
  g_list_foreach (priv->lru_start, pack_entry, &entries_builder);
1592
0
  g_variant_builder_close (&entries_builder);
1593
1594
  /* Serialize and dump */
1595
0
  cache_variant = g_variant_builder_end (&entries_builder);
1596
0
  g_variant_ref_sink (cache_variant);
1597
0
  filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1598
0
  g_file_set_contents (filename, (const char *) g_variant_get_data (cache_variant),
1599
0
           g_variant_get_size (cache_variant), NULL);
1600
0
  g_free (filename);
1601
0
  g_variant_unref (cache_variant);
1602
0
}
1603
1604
static inline guint32
1605
get_key_from_cache_filename (const char *name)
1606
0
{
1607
0
  guint64 key;
1608
1609
0
  key = g_ascii_strtoull (name, NULL, 10);
1610
0
  return key ? (guint32)key : 0;
1611
0
}
1612
1613
static void
1614
insert_cache_file (SoupCache *cache, const char *name, GHashTable *leaked_entries)
1615
0
{
1616
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1617
0
  gchar *path;
1618
1619
0
  path = g_build_filename (priv->cache_dir, name, NULL);
1620
0
  if (g_file_test (path, G_FILE_TEST_IS_REGULAR)) {
1621
0
    guint32 key = get_key_from_cache_filename (name);
1622
1623
0
    if (key) {
1624
0
      g_hash_table_insert (leaked_entries, GUINT_TO_POINTER (key), path);
1625
0
      return;
1626
0
    }
1627
0
  }
1628
0
  g_free (path);
1629
0
}
1630
1631
/**
1632
 * soup_cache_load:
1633
 * @cache: a #SoupCache
1634
 *
1635
 * Loads the contents of @cache's index into memory.
1636
 *
1637
 * This is not thread safe and must be called only from the thread that created the [class@Cache]
1638
 */
1639
void
1640
soup_cache_load (SoupCache *cache)
1641
0
{
1642
0
  gboolean must_revalidate;
1643
0
  guint32 freshness_lifetime, hits;
1644
0
  guint32 corrected_initial_age, response_time;
1645
0
  char *url, *filename = NULL, *contents = NULL;
1646
0
  GVariant *cache_variant;
1647
0
  GVariantIter *entries_iter = NULL, *headers_iter = NULL;
1648
0
  gsize length;
1649
0
  SoupCacheEntry *entry;
1650
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1651
0
  guint16 version, status_code;
1652
0
  GHashTable *leaked_entries = NULL;
1653
0
  GHashTableIter iter;
1654
0
  gpointer value;
1655
1656
0
  filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1657
0
  if (!g_file_get_contents (filename, &contents, &length, NULL)) {
1658
0
    g_free (filename);
1659
0
    g_free (contents);
1660
0
    clear_cache_files (cache);
1661
0
    return;
1662
0
  }
1663
0
  g_free (filename);
1664
1665
0
  cache_variant = g_variant_new_from_data (G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT),
1666
0
             (const char *) contents, length, FALSE, g_free, contents);
1667
0
  g_variant_get (cache_variant, SOUP_CACHE_ENTRIES_FORMAT, &version, &entries_iter);
1668
0
  if (version != SOUP_CACHE_CURRENT_VERSION) {
1669
0
    g_variant_iter_free (entries_iter);
1670
0
    g_variant_unref (cache_variant);
1671
0
    clear_cache_files (cache);
1672
0
    return;
1673
0
  }
1674
1675
0
  leaked_entries = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
1676
0
  soup_cache_foreach_file (cache, (SoupCacheForeachFileFunc)insert_cache_file, leaked_entries);
1677
1678
0
  while (g_variant_iter_loop (entries_iter, SOUP_CACHE_PHEADERS_FORMAT,
1679
0
            &url, &must_revalidate, &freshness_lifetime, &corrected_initial_age,
1680
0
            &response_time, &hits, &length, &status_code,
1681
0
            &headers_iter)) {
1682
0
    const char *header_key, *header_value;
1683
0
    SoupMessageHeaders *headers;
1684
0
    SoupMessageHeadersIter soup_headers_iter;
1685
1686
    /* SoupMessage Headers */
1687
0
    headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
1688
0
    while (g_variant_iter_loop (headers_iter, SOUP_CACHE_HEADERS_FORMAT, &header_key, &header_value))
1689
0
      if (*header_key && *header_value)
1690
0
        soup_message_headers_append (headers, header_key, header_value);
1691
1692
    /* Check that we have headers */
1693
0
    soup_message_headers_iter_init (&soup_headers_iter, headers);
1694
0
    if (!soup_message_headers_iter_next (&soup_headers_iter, &header_key, &header_value)) {
1695
0
      soup_message_headers_unref (headers);
1696
0
      continue;
1697
0
    }
1698
1699
    /* Insert in cache */
1700
0
    entry = g_slice_new0 (SoupCacheEntry);
1701
0
    entry->uri = g_strdup (url);
1702
0
    entry->must_revalidate = must_revalidate;
1703
0
    entry->freshness_lifetime = freshness_lifetime;
1704
0
    entry->corrected_initial_age = corrected_initial_age;
1705
0
    entry->response_time = response_time;
1706
0
    entry->hits = hits;
1707
0
    entry->length = length;
1708
0
    entry->headers = headers;
1709
0
    entry->status_code = status_code;
1710
1711
0
    if (!soup_cache_entry_insert (cache, entry, FALSE))
1712
0
      soup_cache_entry_free (entry);
1713
0
    else
1714
0
      g_hash_table_remove (leaked_entries, GUINT_TO_POINTER (entry->key));
1715
0
  }
1716
1717
  /* Remove the leaked files */
1718
0
  g_hash_table_iter_init (&iter, leaked_entries);
1719
0
  while (g_hash_table_iter_next (&iter, NULL, &value))
1720
0
    g_unlink ((char *)value);
1721
0
  g_hash_table_destroy (leaked_entries);
1722
1723
0
  priv->lru_start = g_list_reverse (priv->lru_start);
1724
1725
  /* frees */
1726
0
  g_variant_iter_free (entries_iter);
1727
0
  g_variant_unref (cache_variant);
1728
0
}
1729
1730
/**
1731
 * soup_cache_set_max_size:
1732
 * @cache: a #SoupCache
1733
 * @max_size: the maximum size of the cache, in bytes
1734
 *
1735
 * Sets the maximum size of the cache.
1736
 */
1737
void
1738
soup_cache_set_max_size (SoupCache *cache,
1739
       guint      max_size)
1740
0
{
1741
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1742
0
  priv->max_size = max_size;
1743
0
  priv->max_entry_data_size = priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
1744
0
}
1745
1746
/**
1747
 * soup_cache_get_max_size:
1748
 * @cache: a #SoupCache
1749
 *
1750
 * Gets the maximum size of the cache.
1751
 *
1752
 * Returns: the maximum size of the cache, in bytes.
1753
 */
1754
guint
1755
soup_cache_get_max_size (SoupCache *cache)
1756
0
{
1757
0
  SoupCachePrivate *priv = soup_cache_get_instance_private (cache);
1758
0
  return priv->max_size;
1759
0
}