From 0f228ada91a9460d1042b1a854fb1a0a32ed3f10 Mon Sep 17 00:00:00 2001 From: John Mark Bell Date: Mon, 6 Feb 2006 00:10:09 +0000 Subject: [project @ 2006-02-06 00:10:09 by jmb] Implement HTTP caching algorithm; this should avoid stale cache entries being used. svn path=/import/netsurf/; revision=2059 --- content/content.c | 49 ++++++++++ content/content.h | 3 + content/fetch.c | 172 ++++++++++++++++++++++++++++---- content/fetch.h | 27 ++--- content/fetchcache.c | 272 ++++++++++++++++++++++++++++++++++++++++++++++----- desktop/browser.c | 1 + riscos/plugin.c | 14 +-- utils/utils.c | 23 +++++ utils/utils.h | 1 + 9 files changed, 501 insertions(+), 61 deletions(-) diff --git a/content/content.c b/content/content.c index 11effac45..9b5d3c8f2 100644 --- a/content/content.c +++ b/content/content.c @@ -288,6 +288,11 @@ struct content * content_create(const char *url) talloc_free(c); return 0; } + c->cache_data = talloc(c, struct cache_data); + if (!c->cache_data) { + talloc_free(c); + return 0; + } talloc_set_name_const(c, c->url); c->type = CONTENT_UNKNOWN; c->mime_type = 0; @@ -315,6 +320,14 @@ struct content * content_create(const char *url) c->no_error_pages = false; c->download = false; c->error_count = 0; + c->cache_data->req_time = 0; + c->cache_data->res_time = 0; + c->cache_data->date = 0; + c->cache_data->expires = 0; + c->cache_data->age = INVALID_AGE; + c->cache_data->max_age = INVALID_AGE; + c->cache_data->no_cache = false; + c->cache_data->etag = 0; c->prev = 0; c->next = content_list; @@ -361,6 +374,42 @@ struct content * content_get(const char *url) } +/** + * Get a READY or DONE content from the memory cache. + * + * \param url URL of content + * \return content if found, or 0 + * + * Searches the list of contents for one corresponding to the given url, and + * which is fresh, shareable and either READY or DONE. + */ + +struct content * content_get_ready(const char *url) +{ + struct content *c; + + for (c = content_list; c; c = c->next) { + if (!c->fresh) + /* not fresh */ + continue; + if (c->status != CONTENT_STATUS_READY && + c->status != CONTENT_STATUS_DONE) + /* not ready or done */ + continue; + if (c->type != CONTENT_UNKNOWN && + handler_map[c->type].no_share && + c->user_list->next) + /* not shareable, and has a user already */ + continue; + if (strcmp(c->url, url)) + continue; + return c; + } + + return 0; +} + + /** * Initialise the content for the specified type. * diff --git a/content/content.h b/content/content.h index 3e24ccbc0..7dbcdcb08 100644 --- a/content/content.h +++ b/content/content.h @@ -134,6 +134,7 @@ struct bitmap; struct box; struct browser_window; +struct cache_data; struct content; struct fetch; struct object_params; @@ -245,6 +246,7 @@ struct content { * was fetched using a simple GET, has not expired, and may be * shared between users. */ bool fresh; + struct cache_data *cache_data; /**< Cache control data */ unsigned int size; /**< Estimated size of all data associated with this content. */ @@ -285,6 +287,7 @@ extern const char *content_status_name[]; content_type content_lookup(const char *mime_type); struct content * content_create(const char *url); struct content * content_get(const char *url); +struct content * content_get_ready(const char *url); bool content_set_type(struct content *c, content_type type, const char *mime_type, const char *params[]); void content_set_status(struct content *c, const char *status_message, ...); diff --git a/content/fetch.c b/content/fetch.c index 9fa792f2c..11f23feee 100644 --- a/content/fetch.c +++ b/content/fetch.c @@ -25,6 +25,7 @@ #include #include #include +#include #ifdef riscos #include #endif @@ -35,9 +36,7 @@ #ifdef WITH_AUTH #include "netsurf/desktop/401login.h" #endif -#ifdef WITH_POST #include "netsurf/render/form.h" -#endif #define NDEBUG #include "netsurf/utils/log.h" #include "netsurf/utils/messages.h" @@ -68,6 +67,8 @@ struct fetch { char *realm; /**< HTTP Auth Realm */ char *post_urlenc; /**< Url encoded POST string, or 0. */ struct curl_httppost *post_multipart; /**< Multipart post data, or 0. */ + struct cache_data cachedata; /**< Cache control data */ + time_t last_modified; /**< If-Modified-Since time */ struct fetch *queue_prev; /**< Previous fetch for this host. */ struct fetch *queue_next; /**< Next fetch for this host. */ struct fetch *prev; /**< Previous active fetch in ::fetch_list. */ @@ -94,9 +95,8 @@ static size_t fetch_curl_data(void *data, size_t size, size_t nmemb, static size_t fetch_curl_header(char *data, size_t size, size_t nmemb, struct fetch *f); static bool fetch_process_headers(struct fetch *f); -#ifdef WITH_POST -static struct curl_httppost *fetch_post_convert(struct form_successful_control *control); -#endif +static struct curl_httppost *fetch_post_convert( + struct form_successful_control *control); /** @@ -148,10 +148,10 @@ void fetch_init(void) SETOPT(CURLOPT_CAINFO, option_ca_bundle); if (!option_ssl_verify_certificates) { - /* disable verification of SSL certificates. - * security? we've heard of it... - */ - SETOPT(CURLOPT_SSL_VERIFYPEER, 0L); + /* disable verification of SSL certificates. + * security? we've heard of it... + */ + SETOPT(CURLOPT_SSL_VERIFYPEER, 0L); SETOPT(CURLOPT_SSL_VERIFYHOST, 0L); } @@ -209,7 +209,8 @@ struct fetch * fetch_start(char *url, char *referer, void (*callback)(fetch_msg msg, void *p, const char *data, unsigned long size), void *p, bool only_2xx, char *post_urlenc, - struct form_successful_control *post_multipart, bool cookies) + struct form_successful_control *post_multipart, bool cookies, + char *headers[]) { char *host; struct fetch *fetch; @@ -219,6 +220,7 @@ struct fetch * fetch_start(char *url, char *referer, struct curl_slist *slist; url_func_result res; char *ref1 = 0, *ref2 = 0; + int i; fetch = malloc(sizeof (*fetch)); if (!fetch) @@ -274,6 +276,15 @@ struct fetch * fetch_start(char *url, char *referer, fetch->post_urlenc = strdup(post_urlenc); else if (post_multipart) fetch->post_multipart = fetch_post_convert(post_multipart); + fetch->cachedata.req_time = time(0); + fetch->cachedata.res_time = 0; + fetch->cachedata.date = 0; + fetch->cachedata.expires = 0; + fetch->cachedata.age = INVALID_AGE; + fetch->cachedata.max_age = INVALID_AGE; + fetch->cachedata.no_cache = false; + fetch->cachedata.etag = 0; + fetch->last_modified = 0; fetch->queue_prev = 0; fetch->queue_next = 0; fetch->prev = 0; @@ -319,6 +330,16 @@ struct fetch * fetch_start(char *url, char *referer, s[sizeof s - 1] = 0; APPEND(fetch->headers, s); } + /* And add any headers specified by the caller */ + for (i = 0; headers[i]; i++) { + if (strncasecmp(headers[i], "If-Modified-Since:", 18) == 0) { + char *d = headers[i] + 18; + for (; *d && (*d == ' ' || *d == '\t'); d++) + /* do nothing */; + fetch->last_modified = curl_getdate(d, NULL); + } + APPEND(fetch->headers, headers[i]); + } /* look for a fetch from the same host */ for (host_fetch = fetch_list; @@ -499,6 +520,7 @@ void fetch_stop(struct fetch *f) fetch->curl_handle = f->curl_handle; f->curl_handle = 0; + fetch->cachedata.req_time = time(0); code = fetch_set_options(fetch); if (code == CURLE_OK) /* add to the global curl multi handle */ @@ -554,6 +576,7 @@ void fetch_free(struct fetch *f) free(f->post_urlenc); if (f->post_multipart) curl_formfree(f->post_multipart); + free(f->cachedata.etag); free(f); } @@ -611,6 +634,7 @@ void fetch_done(CURL *curl_handle, CURLcode result) void (*callback)(fetch_msg msg, void *p, const char *data, unsigned long size); CURLcode code; + struct cache_data cachedata; /* find the structure associated with this fetch */ code = curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &f); @@ -635,14 +659,22 @@ void fetch_done(CURL *curl_handle, CURLcode result) else error = true; + /* If finished, acquire cache info to pass to callback */ + if (finished) { + memcpy(&cachedata, &f->cachedata, sizeof(struct cache_data)); + f->cachedata.etag = 0; + } + /* clean up fetch and start any queued fetch for this host */ fetch_stop(f); /* postponed until after stop so that queue fetches are started */ if (abort) ; /* fetch was aborted: no callback */ - else if (finished) - callback(FETCH_FINISHED, p, 0, 0); + else if (finished) { + callback(FETCH_FINISHED, p, (const char *)&cachedata, 0); + free(cachedata.etag); + } else if (error) callback(FETCH_ERROR, p, fetch_error_buffer, 0); } @@ -716,6 +748,13 @@ size_t fetch_curl_header(char *data, size_t size, size_t nmemb, { int i; size *= nmemb; + +#define SKIP_ST(o) for (i = (o); i < (int) size && (data[i] == ' ' || data[i] == '\t'); i++) + + /* Set fetch response time if not already set */ + if (f->cachedata.res_time == 0) + f->cachedata.res_time = time(0); + if (12 < size && strncasecmp(data, "Location:", 9) == 0) { /* extract Location header */ free(f->location); @@ -724,8 +763,7 @@ size_t fetch_curl_header(char *data, size_t size, size_t nmemb, LOG(("malloc failed")); return size; } - for (i = 9; i < (int)size && (data[i] == ' ' || data[i] == '\t'); i++) - /* */; + SKIP_ST(9); strncpy(f->location, data + i, size - i); f->location[size - i] = '\0'; for (i = size - i - 1; i >= 0 && @@ -736,12 +774,11 @@ size_t fetch_curl_header(char *data, size_t size, size_t nmemb, f->location[i] = '\0'; } else if (15 < size && strncasecmp(data, "Content-Length:", 15) == 0) { /* extract Content-Length header */ - for (i = 15; i < (int)size && (data[i] == ' ' || data[i] == '\t'); i++) - /* */; + SKIP_ST(15); if (i < (int)size && '0' <= data[i] && data[i] <= '9') f->content_length = atol(data + i); #ifdef WITH_AUTH - } else if (16 < size && strncasecmp(data, "WWW-Authenticate", 16) == 0) { + } else if (17 < size && strncasecmp(data, "WWW-Authenticate:", 17) == 0) { /* extract the first Realm from WWW-Authenticate header */ free(f->realm); f->realm = malloc(size); @@ -749,8 +786,7 @@ size_t fetch_curl_header(char *data, size_t size, size_t nmemb, LOG(("malloc failed")); return size; } - for (i = 16; i < (int)size && data[i] != '='; i++) - /* */; + SKIP_ST(17); while (i < (int)size && data[++i] == '"') /* */; strncpy(f->realm, data + i, size - i); @@ -763,8 +799,70 @@ size_t fetch_curl_header(char *data, size_t size, size_t nmemb, f->realm[i] == '\n'); --i) f->realm[i] = '\0'; #endif + } else if (5 < size && strncasecmp(data, "Date:", 5) == 0) { + /* extract Date header */ + SKIP_ST(5); + if (i < (int) size) + f->cachedata.date = curl_getdate(&data[i], NULL); + } else if (4 < size && strncasecmp(data, "Age:", 4) == 0) { + /* extract Age header */ + SKIP_ST(4); + if (i < (int) size && '0' <= data[i] && data[i] <= '9') + f->cachedata.age = atoi(data + i); + } else if (8 < size && strncasecmp(data, "Expires:", 8) == 0) { + /* extract Expires header */ + SKIP_ST(8); + if (i < (int) size) + f->cachedata.expires = curl_getdate(&data[i], NULL); + } else if (14 < size && strncasecmp(data, "Cache-Control:", 14) == 0) { + /* extract and parse Cache-Control header */ + int comma; + SKIP_ST(14); + + while (i < (int) size) { + for (comma = i; comma < (int) size; comma++) + if (data[comma] == ',') + break; + + SKIP_ST(i); + + if (8 < comma - i && (strncasecmp(data + i, "no-cache", 8) == 0 || strncasecmp(data + i, "no-store", 8) == 0)) + /* When we get a disk cache we should + * distinguish between these two */ + f->cachedata.no_cache = true; + else if (7 < comma - i && strncasecmp(data + i, "max-age", 7) == 0) { + for (; i < comma; i++) + if (data[i] == '=') + break; + SKIP_ST(i+1); + if (i < comma) + f->cachedata.max_age = + atoi(data + i); + } + + i = comma + 1; + } + } else if (5 < size && strncasecmp(data, "ETag:", 5) == 0) { + /* extract ETag header */ + free(f->cachedata.etag); + f->cachedata.etag = malloc(size); + if (!f->cachedata.etag) { + LOG(("malloc failed")); + return size; + } + SKIP_ST(5); + strncpy(f->cachedata.etag, data + i, size - i); + f->cachedata.etag[size - i] = '\0'; + for (i = size - i - 1; i >= 0 && + (f->cachedata.etag[i] == ' ' || + f->cachedata.etag[i] == '\t' || + f->cachedata.etag[i] == '\r' || + f->cachedata.etag[i] == '\n'); --i) + f->cachedata.etag[i] = '\0'; } + return size; +#undef SKIP_ST } @@ -779,13 +877,47 @@ bool fetch_process_headers(struct fetch *f) long http_code; const char *type; CURLcode code; + struct stat s; f->had_headers = true; + /* Set fetch response time if not already set */ + if (f->cachedata.res_time == 0) + f->cachedata.res_time = time(0); + code = curl_easy_getinfo(f->curl_handle, CURLINFO_HTTP_CODE, &http_code); assert(code == CURLE_OK); LOG(("HTTP status code %li", http_code)); + if (f->last_modified) { + /* Fake up HTTP cache control for local files */ + char *url_path = 0; + + if (strncmp(f->url, "file:///", 8) == 0) { + url_path = curl_unescape(f->url + 7, + (int) strlen(f->url) - 7); + } + else if (strncmp(f->url, "file:/", 6) == 0) { + url_path = curl_unescape(f->url + 5, + (int) strlen(f->url) - 5); + } + + if (url_path && stat(url_path, &s) == 0) { + if (f->last_modified > s.st_mtime) { + f->callback(FETCH_NOTMODIFIED, f->p, + (const char *)&f->cachedata, 0); + return true; + } + } + } + + if (http_code == 304 && !f->post_urlenc && !f->post_multipart) { + /* Not Modified && GET request */ + f->callback(FETCH_NOTMODIFIED, f->p, + (const char *)&f->cachedata, 0); + return true; + } + /* handle HTTP redirects (3xx response codes) */ if (300 <= http_code && http_code < 400 && f->location != 0) { LOG(("FETCH_REDIRECT, '%s'", f->location)); @@ -840,7 +972,6 @@ bool fetch_process_headers(struct fetch *f) * Convert a list of struct ::form_successful_control to a list of * struct curl_httppost for libcurl. */ -#ifdef WITH_POST struct curl_httppost *fetch_post_convert(struct form_successful_control *control) { struct curl_httppost *post = 0, *last = 0; @@ -901,7 +1032,6 @@ struct curl_httppost *fetch_post_convert(struct form_successful_control *control return post; } -#endif /** diff --git a/content/fetch.h b/content/fetch.h index 38de5d090..b65e150ff 100644 --- a/content/fetch.h +++ b/content/fetch.h @@ -23,6 +23,7 @@ typedef enum { FETCH_FINISHED, FETCH_ERROR, FETCH_REDIRECT, + FETCH_NOTMODIFIED, #ifdef WITH_AUTH FETCH_AUTH #endif @@ -30,9 +31,19 @@ typedef enum { struct content; struct fetch; -#ifdef WITH_POST struct form_successful_control; -#endif + +struct cache_data { + time_t req_time; /**< Time of request */ + time_t res_time; /**< Time of response */ + time_t date; /**< Date: response header */ + time_t expires; /**< Expires: response header */ +#define INVALID_AGE -1 + int age; /**< Age: response header */ + int max_age; /**< Max-age Cache-control parameter */ + bool no_cache; /**< no-cache Cache-control parameter */ + char *etag; /**< Etag: response header */ +}; extern bool fetch_active; extern CURLM *fetch_curl_multi; @@ -41,15 +52,9 @@ void fetch_init(void); struct fetch * fetch_start(char *url, char *referer, void (*callback)(fetch_msg msg, void *p, const char *data, unsigned long size), - void *p, bool only_2xx -#ifdef WITH_POST - , char *post_urlenc, - struct form_successful_control *post_multipart -#endif -#ifdef WITH_COOKIES - ,bool cookies -#endif - ); + void *p, bool only_2xx, char *post_urlenc, + struct form_successful_control *post_multipart, + bool cookies, char *headers[]); void fetch_abort(struct fetch *f); void fetch_poll(void); void fetch_quit(void); diff --git a/content/fetchcache.c b/content/fetchcache.c index a7e68ee97..6e3f359f0 100644 --- a/content/fetchcache.c +++ b/content/fetchcache.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "netsurf/utils/config.h" #include "netsurf/content/content.h" #include "netsurf/content/fetchcache.h" @@ -25,6 +26,7 @@ #include "netsurf/content/url_store.h" #include "netsurf/utils/log.h" #include "netsurf/utils/messages.h" +#include "netsurf/utils/talloc.h" #include "netsurf/utils/url.h" #include "netsurf/utils/utils.h" @@ -35,6 +37,9 @@ static void fetchcache_callback(fetch_msg msg, void *p, const char *data, unsigned long size); static char *fetchcache_parse_type(const char *s, char **params[]); static void fetchcache_error_page(struct content *c, const char *error); +static void fetchcache_cache_update(struct content *c, + const struct cache_data *data); +static void fetchcache_notmodified(struct content *c, const char *data); /** @@ -74,7 +79,9 @@ struct content * fetchcache(const char *url, { struct content *c; char *url1; - char *hash; + char *hash, *query; + char *etag = 0; + time_t date = 0; if ((url1 = strdup(url)) == NULL) return NULL; @@ -83,15 +90,48 @@ struct content * fetchcache(const char *url, if ((hash = strchr(url1, '#')) != NULL) *hash = 0; + /* look for query; we don't cache URLs with a query segment */ + query = strchr(url1, '?'); + LOG(("url %s", url1)); - if (!post_urlenc && !post_multipart && !download) { + if (!post_urlenc && !post_multipart && !download && !query) { if ((c = content_get(url1)) != NULL) { - free(url1); - if (!content_add_user(c, callback, p1, p2)) - return NULL; - else - return c; + struct cache_data *cd = c->cache_data; + int current_age, freshness_lifetime; + + /* Calculate staleness of cached content as per + * RFC 2616 13.2.3/13.2.4 */ + current_age = max(0, (cd->res_time - cd->date)); + current_age = max(current_age, + (cd->age == INVALID_AGE) ? 0 + : cd->age); + current_age += cd->res_time - cd->req_time + + time(0) - cd->res_time; + freshness_lifetime = + (cd->max_age != INVALID_AGE) ? cd->max_age : + (cd->expires != 0) ? cd->expires - cd->date : + 0; + + if (freshness_lifetime > current_age || + cd->date == 0) { + /* Ok, either a fresh content or we're + * currently fetching the selected content + * (therefore it must be fresh) */ + free(url1); + if (!content_add_user(c, callback, p1, p2)) + return NULL; + else + return c; + } + + /* Ok. We have a cache entry, but it appears stale. + * Therefore, validate it. */ + /** \todo perhaps it'd be better to use the + * contents of the Last-Modified header here + * instead */ + date = c->cache_data->date; + etag = c->cache_data->etag; } } @@ -99,11 +139,21 @@ struct content * fetchcache(const char *url, free(url1); if (!c) return NULL; + + /* Fill in cache validation fields (if present) */ + if (date) + c->cache_data->date = date; + if (etag) { + c->cache_data->etag = talloc_strdup(c, etag); + if (!c->cache_data->etag) + return NULL; + } + if (!content_add_user(c, callback, p1, p2)) { return NULL; } - if (!post_urlenc && !post_multipart && !download) + if (!post_urlenc && !post_multipart && !download && !query) c->fresh = true; c->width = width; @@ -152,12 +202,59 @@ void fetchcache_go(struct content *content, char *referer, /* fetching, but not yet received any response: * no action required */ - } else if (content->status == CONTENT_STATUS_TYPE_UNKNOWN) { - /* brand new content: start fetch */ + } else if (content->status == CONTENT_STATUS_TYPE_UNKNOWN) { + /* brand new content: start fetch */ + char **headers; + int i = 0; + char *etag = content->cache_data->etag; + time_t date = content->cache_data->date; + content->cache_data->etag = 0; + content->cache_data->date = 0; + headers = malloc(3 * sizeof(char *)); + if (!headers) { + talloc_free(etag); + msg_data.error = messages_get("NoMemory"); + callback(CONTENT_MSG_ERROR, content, p1, p2, + msg_data); + return; + } + if (etag) { + headers[i] = malloc(15 + strlen(etag) + 1); + if (!headers[i]) { + free(headers); + talloc_free(etag); + msg_data.error = messages_get("NoMemory"); + callback(CONTENT_MSG_ERROR, content, p1, p2, + msg_data); + return; + } + sprintf(headers[i++], "If-None-Match: %s", etag); + talloc_free(etag); + } + if (date) { + headers[i] = malloc(19 + 29 + 1); + if (!headers[i]) { + while (--i >= 0) { + free(headers[i]); + } + free(headers); + msg_data.error = messages_get("NoMemory"); + callback(CONTENT_MSG_ERROR, content, p1, p2, + msg_data); + return; + } + sprintf(headers[i++], "If-Modified-Since: %s", + rfc1123_date(date)); + } + headers[i] = 0; content->fetch = fetch_start(content->url, referer, fetchcache_callback, content, content->no_error_pages, - post_urlenc, post_multipart, cookies); + post_urlenc, post_multipart, cookies, + headers); + for (i = 0; headers[i]; i++) + free(headers[i]); + free(headers); if (!content->fetch) { LOG(("warning: fetch_start failed")); snprintf(error_message, sizeof error_message, @@ -259,24 +356,15 @@ void fetchcache_callback(fetch_msg msg, void *p, const char *data, break; case FETCH_DATA: -/* if (c->total_size) - content_set_status(c, - messages_get("RecPercent"), - human_friendly_bytesize(c->source_size + size), - human_friendly_bytesize(c->total_size), - (unsigned int) ((c->source_size + size) * 100 / c->total_size)); - else - content_set_status(c, - messages_get("Received"), - human_friendly_bytesize(c->source_size + size)); - content_broadcast(c, CONTENT_MSG_STATUS, msg_data); -*/ if (!content_process_data(c, data, size)) { + if (!content_process_data(c, data, size)) { fetch_abort(c->fetch); c->fetch = 0; } break; case FETCH_FINISHED: + fetchcache_cache_update(c, + (const struct cache_data *)data); c->fetch = 0; content_set_status(c, messages_get("Converting"), c->source_size); @@ -327,6 +415,11 @@ void fetchcache_callback(fetch_msg msg, void *p, const char *data, content_broadcast(c, CONTENT_MSG_ERROR, msg_data); } break; + + case FETCH_NOTMODIFIED: + fetchcache_notmodified(c, data); + break; + #ifdef WITH_AUTH case FETCH_AUTH: /* data -> string containing the Realm */ @@ -456,6 +549,139 @@ void fetchcache_error_page(struct content *c, const char *error) } +/** + * Update a content's cache info + * + * \param The content + * \param Cache data + */ + +void fetchcache_cache_update(struct content *c, + const struct cache_data *data) +{ + assert(c && data); + + c->cache_data->req_time = data->req_time; + c->cache_data->res_time = data->res_time; + + if (data->date != 0) + c->cache_data->date = data->date; + else + c->cache_data->date = time(0); + + if (data->expires != 0) + c->cache_data->expires = data->expires; + + if (data->age != INVALID_AGE) + c->cache_data->age = data->age; + + if (data->max_age != INVALID_AGE) + c->cache_data->max_age = data->max_age; + + if (data->no_cache) + c->fresh = false; + + if (data->etag) { + talloc_free(c->cache_data->etag); + c->cache_data->etag = talloc_strdup(c, data->etag); + } +} + + +/** + * Not modified callback handler + */ + +void fetchcache_notmodified(struct content *c, const char *data) +{ + struct content *fb; + union content_msg_data msg_data; + + assert(c && data); + assert(c->status == CONTENT_STATUS_TYPE_UNKNOWN); + + /* Look for cached content */ + fb = content_get_ready(c->url); + + if (fb) { + /* Found it */ + intptr_t p1, p2; + void (*callback)(content_msg msg, + struct content *c, intptr_t p1, + intptr_t p2, + union content_msg_data data); + + /* Now notify all users that we're changing content */ + while (c->user_list->next) { + p1 = c->user_list->next->p1; + p2 = c->user_list->next->p2; + callback = c->user_list->next->callback; + + if (!content_add_user(fb, callback, p1, p2)) { + c->type = CONTENT_UNKNOWN; + c->status = CONTENT_STATUS_ERROR; + msg_data.error = messages_get("NoMemory"); + content_broadcast(c, CONTENT_MSG_ERROR, + msg_data); + return; + } + + content_remove_user(c, callback, p1, p2); + callback(CONTENT_MSG_NEWPTR, fb, p1, p2, msg_data); + + /* and catch user up with fallback's state */ + if (fb->status == CONTENT_STATUS_LOADING) { + callback(CONTENT_MSG_LOADING, + fb, p1, p2, msg_data); + } else if (fb->status == CONTENT_STATUS_READY) { + callback(CONTENT_MSG_LOADING, + fb, p1, p2, msg_data); + if (content_find_user(fb, callback, p1, p2)) + callback(CONTENT_MSG_READY, + fb, p1, p2, msg_data); + } else if (fb->status == CONTENT_STATUS_DONE) { + callback(CONTENT_MSG_LOADING, + fb, p1, p2, msg_data); + if (content_find_user(fb, callback, p1, p2)) + callback(CONTENT_MSG_READY, + fb, p1, p2, msg_data); + if (content_find_user(fb, callback, p1, p2)) + callback(CONTENT_MSG_DONE, + fb, p1, p2, msg_data); + } else if (fb->status == CONTENT_STATUS_ERROR) { + /* shouldn't usually occur */ + msg_data.error = messages_get("MiscError"); + callback(CONTENT_MSG_ERROR, fb, p1, p2, + msg_data); + } + } + + /* mark content invalid */ + c->fetch = 0; + c->status = CONTENT_STATUS_ERROR; + + /* and update fallback's cache control data */ + fetchcache_cache_update(fb, + (const struct cache_data *)data); + } + else { + /* No cached content, so unconditionally refetch */ + struct content_user *u; + + fetch_abort(c->fetch); + c->fetch = 0; + + c->cache_data->date = 0; + talloc_free(c->cache_data->etag); + c->cache_data->etag = 0; + + for (u = c->user_list->next; u; u = u->next) { + fetchcache_go(c, 0, u->callback, u->p1, u->p2, + c->width, c->height, 0, 0, false); + } + } +} + #ifdef TEST #include diff --git a/desktop/browser.c b/desktop/browser.c index c585b14d0..41d510c29 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -697,6 +697,7 @@ void download_window_callback(fetch_msg msg, void *p, const char *data, case FETCH_TYPE: case FETCH_REDIRECT: + case FETCH_NOTMODIFIED: case FETCH_AUTH: default: /* not possible */ diff --git a/riscos/plugin.c b/riscos/plugin.c index 8a8b0c921..b1c26df2c 100644 --- a/riscos/plugin.c +++ b/riscos/plugin.c @@ -163,7 +163,7 @@ static bool plugin_active(struct content *c); static void plugin_stream_free(struct plugin_stream *p); static bool plugin_start_fetch(struct plugin_stream *p, const char *url); static void plugin_stream_callback(content_msg msg, struct content *c, - void *p1, void *p2, union content_msg_data data); + intptr_t p1, intptr_t p2, union content_msg_data data); static void plugin_fetch_callback(fetch_msg msg, void *p, const char *data, unsigned long size); @@ -1602,7 +1602,8 @@ void plugin_stream_free(struct plugin_stream *p) p->c->fetch = 0; p->c->status = CONTENT_STATUS_DONE; } - content_remove_user(p->c, plugin_stream_callback, p, 0); + content_remove_user(p->c, plugin_stream_callback, + (intptr_t)p, 0); } /* free normal stream context. file streams get freed later */ @@ -1649,7 +1650,7 @@ bool plugin_start_fetch(struct plugin_stream *p, const char *url) return false; } - c = fetchcache(url2, plugin_stream_callback, p, 0, + c = fetchcache(url2, plugin_stream_callback, (intptr_t)p, 0, 100, 100, true, 0, 0, true, true); free(url2); if (!c) { @@ -1657,7 +1658,7 @@ bool plugin_start_fetch(struct plugin_stream *p, const char *url) } p->c = c; - fetchcache_go(c, 0, plugin_stream_callback, p, 0, + fetchcache_go(c, 0, plugin_stream_callback, (intptr_t)p, 0, 100, 100, 0, 0, true); return true; @@ -1667,9 +1668,9 @@ bool plugin_start_fetch(struct plugin_stream *p, const char *url) * Callback for fetchcache() for plugin stream fetches. */ void plugin_stream_callback(content_msg msg, struct content *c, - void *p1, void *p2, union content_msg_data data) + intptr_t p1, intptr_t p2, union content_msg_data data) { - struct plugin_stream *p = p1; + struct plugin_stream *p = (struct plugin_stream *)p1; switch (msg) { case CONTENT_MSG_LOADING: @@ -1751,6 +1752,7 @@ void plugin_fetch_callback(fetch_msg msg, void *p, const char *data, case FETCH_TYPE: case FETCH_REDIRECT: + case FETCH_NOTMODIFIED: case FETCH_AUTH: default: /* not possible */ diff --git a/utils/utils.c b/utils/utils.c index 0632b2318..d2cab2aa5 100644 --- a/utils/utils.c +++ b/utils/utils.c @@ -268,3 +268,26 @@ char *human_friendly_bytesize(unsigned long bsize) { return curbuffer; } + +/** + * Create an RFC 1123 compliant date string from a Unix timestamp + * + * \param t The timestamp to consider + * \return Pointer to buffer containing string - invalidated by next call. + */ +const char *rfc1123_date(time_t t) +{ + static char ret[30]; + + struct tm *tm = gmtime(&t); + const char *days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }, + *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + + snprintf(ret, sizeof ret, "%s, %02d %s %d %02d:%02d:%02d GMT", + days[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], + tm->tm_year + 1900, tm->tm_hour, tm->tm_min, + tm->tm_sec); + + return ret; +} diff --git a/utils/utils.h b/utils/utils.h index 50623670e..4eaf02666 100644 --- a/utils/utils.h +++ b/utils/utils.h @@ -56,6 +56,7 @@ void regcomp_wrapper(regex_t *preg, const char *regex, int cflags); void clean_cookiejar(void); void unicode_transliterate(unsigned int c, char **r); char *human_friendly_bytesize(unsigned long bytesize); +const char *rfc1123_date(time_t t); /* Platform specific functions */ void die(const char * const error); -- cgit v1.2.3