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