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