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