summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/content.c999
-rw-r--r--content/content.h295
-rw-r--r--content/content_protected.h221
-rw-r--r--content/fetch.c79
-rw-r--r--content/fetch.h18
-rw-r--r--content/fetchcache.c4
-rw-r--r--content/fetchcache.h6
-rw-r--r--content/fetchers/fetch_curl.c110
-rw-r--r--content/fetchers/fetch_data.c32
-rw-r--r--content/hlcache.c362
-rw-r--r--content/hlcache.h110
-rw-r--r--content/llcache.c1815
-rw-r--r--content/llcache.h238
13 files changed, 3465 insertions, 824 deletions
diff --git a/content/content.c b/content/content.c
index 8b6cfd47d..1d7345207 100644
--- a/content/content.c
+++ b/content/content.c
@@ -32,9 +32,9 @@
#include <strings.h>
#include <time.h>
#include "utils/config.h"
-#include "content/content.h"
-#include "content/fetch.h"
+#include "content/content_protected.h"
#include "content/fetchcache.h"
+#include "content/hlcache.h"
#include "css/css.h"
#include "image/bitmap.h"
#include "desktop/options.h"
@@ -78,16 +78,13 @@
#ifdef WITH_PNG
#include "image/png.h"
#endif
+#include "utils/http.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/talloc.h"
#include "utils/utils.h"
-/** Linked list of all content structures. May include more than one content
- * per URL. Doubly-linked. */
-struct content *content_list = 0;
-
/** An entry in mime_map. */
struct mime_entry {
char mime_type[40];
@@ -250,8 +247,7 @@ const char * const content_status_name[] = {
/** An entry in handler_map. */
struct handler_entry {
- bool (*create)(struct content *c, struct content *parent,
- const char *params[]);
+ bool (*create)(struct content *c, const http_parameter *params);
bool (*process_data)(struct content *c, char *data, unsigned int size);
bool (*convert)(struct content *c, int width, int height);
void (*reformat)(struct content *c, int width, int height);
@@ -359,16 +355,16 @@ static const struct handler_entry handler_map[] = {
};
#define HANDLER_MAP_COUNT (sizeof(handler_map) / sizeof(handler_map[0]))
-
+static nserror content_llcache_callback(llcache_handle *llcache,
+ const llcache_event *event, void *pw);
+static void content_convert(struct content *c, int width, int height);
static void content_update_status(struct content *c);
-static void content_destroy(struct content *c);
-static void content_stop_check(struct content *c);
/**
* Convert a MIME type to a content_type.
*
- * The returned ::content_type will always be suitable for content_set_type().
+ * The returned ::content_type will always be suitable for content_create().
*/
content_type content_lookup(const char *mime_type)
@@ -397,289 +393,188 @@ content_type content_lookup(const char *mime_type)
* CONTENT_STATUS_TYPE_UNKNOWN.
*/
-struct content * content_create(const char *url)
+struct content * content_create(llcache_handle *llcache,
+ const char *fallback_charset, bool quirks)
{
struct content *c;
struct content_user *user_sentinel;
+ const char *content_type_header;
+ content_type type;
+ char *mime_type;
+ http_parameter *params;
+ nserror error;
+
+ content_type_header =
+ llcache_handle_get_header(llcache, "Content-Type");
+ if (content_type_header == NULL)
+ content_type_header = "text/plain";
+
+ error = http_parse_content_type(content_type_header, &mime_type,
+ &params);
+ if (error != NSERROR_OK)
+ return NULL;
+
+ type = content_lookup(mime_type);
+ if (type == CONTENT_OTHER) {
+ http_parameter_list_destroy(params);
+ free(mime_type);
+ return NULL;
+ }
c = talloc_zero(0, struct content);
- if (!c)
- return 0;
+ if (c == NULL) {
+ http_parameter_list_destroy(params);
+ free(mime_type);
+ return NULL;
+ }
- LOG(("url %s -> %p", url, c));
+ LOG(("url %s -> %p", llcache_handle_get_url(llcache), c));
user_sentinel = talloc(c, struct content_user);
- if (!user_sentinel) {
+ if (user_sentinel == NULL) {
talloc_free(c);
- return 0;
+ http_parameter_list_destroy(params);
+ free(mime_type);
+ return NULL;
}
- c->url = talloc_strdup(c, url);
- if (!c->url) {
+
+ c->fallback_charset = talloc_strdup(c, fallback_charset);
+ if (fallback_charset != NULL && c->fallback_charset == NULL) {
talloc_free(c);
- return 0;
+ http_parameter_list_destroy(params);
+ free(mime_type);
+ return NULL;
}
- talloc_set_name_const(c, c->url);
- c->type = CONTENT_UNKNOWN;
- c->mime_type = 0;
- c->status = CONTENT_STATUS_TYPE_UNKNOWN;
+
+ c->mime_type = talloc_strdup(c, mime_type);
+ if (c->mime_type == NULL) {
+ talloc_free(c);
+ http_parameter_list_destroy(params);
+ free(mime_type);
+ return NULL;
+ }
+
+ /* No longer require mime_type */
+ free(mime_type);
+
+ c->llcache = llcache;
+ c->type = type;
+ c->status = CONTENT_STATUS_LOADING;
c->width = 0;
c->height = 0;
c->available_width = 0;
+ c->quirks = quirks;
c->refresh = 0;
- c->bitmap = 0;
+ c->bitmap = NULL;
c->fresh = false;
c->time = wallclock();
c->size = 0;
- c->title = 0;
+ c->title = NULL;
c->active = 0;
- user_sentinel->callback = 0;
- user_sentinel->p1 = user_sentinel->p2 = 0;
- user_sentinel->next = 0;
+ user_sentinel->callback = NULL;
+ user_sentinel->pw = NULL;
+ user_sentinel->next = NULL;
c->user_list = user_sentinel;
c->sub_status[0] = 0;
c->locked = false;
- c->fetch = 0;
- c->source_data = 0;
- c->source_size = 0;
- c->source_allocated = 0;
c->total_size = 0;
c->http_code = 0;
- c->no_error_pages = false;
- c->download = false;
- c->tried_with_auth = false;
- c->redirect_count = 0;
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->cache_data.last_modified = 0;
content_set_status(c, messages_get("Loading"));
- c->prev = 0;
- c->next = content_list;
- if (content_list)
- content_list->prev = c;
- content_list = c;
-
- return c;
-}
-
-
-/**
- * Get a 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 and shareable.
- */
+ if (handler_map[type].create) {
+ if (handler_map[type].create(c, params) == false) {
+ talloc_free(c);
+ http_parameter_list_destroy(params);
+ return NULL;
+ }
+ }
-struct content * content_get(const char *url)
-{
- struct content *c;
+ http_parameter_list_destroy(params);
- for (c = content_list; c; c = c->next) {
- if (!c->fresh)
- /* not fresh */
- continue;
- if (c->status == CONTENT_STATUS_ERROR)
- /* error state */
- continue;
- /** \todo We need to reconsider the entire caching strategy in
- * the light of data being shared between specific contents.
- *
- * For example, string dictionaries are owned by the document,
- * and all stylesheets used by the document share the same
- * dictionary.
- *
- * The CSS content handler retrieves the dictionary from its
- * parent content. This relies upon there being a 1:1 mapping
- * between documents and stylesheets.
- *
- * The type of a content is only known once we've received the
- * headers from the fetch layer (and potentially some of the
- * content data, too, if we ever sniff for the type). There
- * is thus a problem with returning contents of unknown type
- * here -- when we subsequently discover that they must only
- * have one user, we clone them. By that point, however, we've
- * no idea what the parent content is, which means that they
- * end up with the wrong parent (and thus wrong dictionary).
- *
- * Of course, the problem with ignoring unknown content types
- * here is that, for all the content types which may be shared,
- * we end up duplicating them and wasting memory. Hence the
- * need to reconsider everything.
- */
- if (c->type == CONTENT_UNKNOWN)
- 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;
+ /* Finally, claim low-level cache events */
+ if (llcache_handle_change_callback(llcache,
+ content_llcache_callback, c) != NSERROR_OK) {
+ talloc_free(c);
+ return NULL;
}
- return 0;
+ return c;
}
-
/**
- * Get a READY or DONE content from the memory cache.
- *
- * \param url URL of content
- * \return content if found, or 0
+ * Handler for low-level cache events
*
- * Searches the list of contents for one corresponding to the given url, and
- * which is fresh, shareable and either READY or DONE.
+ * \param llcache Low-level cache handle
+ * \param event Event details
+ * \param pw Pointer to our context
+ * \return NSERROR_OK on success, appropriate error otherwise
*/
-
-struct content * content_get_ready(const char *url)
+nserror content_llcache_callback(llcache_handle *llcache,
+ const llcache_event *event, void *pw)
{
- struct content *c;
+ struct content *c = pw;
+ union content_msg_data msg_data;
+ nserror error = NSERROR_OK;
+
+ switch (event->type) {
+ case LLCACHE_EVENT_HAD_HEADERS:
+ /* Will never happen: handled in hlcache */
+ break;
+ case LLCACHE_EVENT_HAD_DATA:
+ if (handler_map[c->type].process_data) {
+ if (handler_map[c->type].process_data(c,
+ (char *) event->data.data.buf,
+ event->data.data.len) == false) {
+ c->status = CONTENT_STATUS_ERROR;
+ /** \todo It's not clear what error this is */
+ error = NSERROR_NOMEM;
+ }
+ }
+ break;
+ case LLCACHE_EVENT_DONE:
+ {
+ const uint8_t *source;
+ size_t source_size;
+
+ source = llcache_handle_get_source_data(llcache, &source_size);
- 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;
+ content_set_status(c, messages_get("Converting"), source_size);
+ content_broadcast(c, CONTENT_MSG_STATUS, msg_data);
+
+ content_convert(c, c->width, c->height);
+ }
+ break;
+ case LLCACHE_EVENT_ERROR:
+ /** \todo Error page? */
+ c->status = CONTENT_STATUS_ERROR;
+ msg_data.error = event->data.msg;
+ content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
+ break;
+ case LLCACHE_EVENT_PROGRESS:
+ content_set_status(c, "%s", event->data.msg);
+ content_broadcast(c, CONTENT_MSG_STATUS, msg_data);
+ break;
}
- return 0;
+ return error;
}
-
/**
* Get whether a content can reformat
*
- * \param c content to check
+ * \param h content to check
* \return whether the content can reformat
*/
-bool content_can_reformat(struct content *c)
+bool content_can_reformat(hlcache_handle *h)
{
- return (handler_map[c->type].reformat != NULL);
-}
-
+ struct content *c = hlcache_handle_get_content(h);
-/**
- * Initialise the content for the specified type.
- *
- * \param c content structure
- * \param type content_type to initialise to
- * \param mime_type MIME-type string for this content
- * \param params array of strings, ordered attribute, value, attribute, ..., 0
- * \return true on success, false on error and error broadcast to users and
- * possibly reported
- *
- * The type is updated to the given type, and a copy of mime_type is taken. The
- * status is changed to CONTENT_STATUS_LOADING. CONTENT_MSG_LOADING is sent to
- * all users. The create function for the type is called to initialise the type
- * specific parts of the content structure.
- */
-
-bool content_set_type(struct content *c, content_type type,
- const char *mime_type, const char *params[],
- struct content *parent)
-{
- union content_msg_data msg_data;
- struct content *clone;
- void (*callback)(content_msg msg, struct content *c, intptr_t p1,
- intptr_t p2, union content_msg_data data);
- intptr_t p1, p2;
-
- assert(c != 0);
- assert(c->status == CONTENT_STATUS_TYPE_UNKNOWN);
- assert(type < CONTENT_UNKNOWN);
-
- LOG(("content %s (%p), type %i", c->url, c, type));
-
- c->mime_type = talloc_strdup(c, mime_type);
- if (!c->mime_type) {
- c->status = CONTENT_STATUS_ERROR;
- msg_data.error = messages_get("NoMemory");
- content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
+ if (c == NULL)
return false;
- }
-
- c->type = type;
- c->status = CONTENT_STATUS_LOADING;
- if (handler_map[type].no_share && c->user_list->next &&
- c->user_list->next->next) {
- /* type not shareable, and more than one user: split into
- * a content per user */
- const char *referer =
- c->fetch ? fetch_get_referer(c->fetch) : NULL;
- struct content *parent =
- c->fetch ? fetch_get_parent(c->fetch) : NULL;
-
- while (c->user_list->next->next) {
- clone = content_create(c->url);
- if (!clone) {
- c->type = CONTENT_UNKNOWN;
- c->status = CONTENT_STATUS_ERROR;
- msg_data.error = messages_get("NoMemory");
- content_broadcast(c, CONTENT_MSG_ERROR,
- msg_data);
- return false;
- }
-
- clone->width = c->width;
- clone->height = c->height;
- clone->fresh = c->fresh;
-
- callback = c->user_list->next->next->callback;
- p1 = c->user_list->next->next->p1;
- p2 = c->user_list->next->next->p2;
- if (!content_add_user(clone, callback, p1, p2)) {
- c->type = CONTENT_UNKNOWN;
- c->status = CONTENT_STATUS_ERROR;
- content_destroy(clone);
- msg_data.error = messages_get("NoMemory");
- content_broadcast(c, CONTENT_MSG_ERROR,
- msg_data);
- return false;
- }
- content_remove_user(c, callback, p1, p2);
- msg_data.new_url = NULL;
- content_broadcast(clone, CONTENT_MSG_NEWPTR, msg_data);
- fetchcache_go(clone, referer,
- callback, p1, p2,
- clone->width, clone->height,
- 0, 0, false, parent);
- }
- }
-
- if (handler_map[type].create) {
- if (!handler_map[type].create(c, parent, params)) {
- c->type = CONTENT_UNKNOWN;
- c->status = CONTENT_STATUS_ERROR;
- return false;
- }
- }
-
- content_broadcast(c, CONTENT_MSG_LOADING, msg_data);
- return true;
+ return (handler_map[c->type].reformat != NULL);
}
@@ -733,58 +628,6 @@ void content_update_status(struct content *c)
/**
- * Process a block of source data.
- *
- * Calls the process_data function for the content.
- *
- * \param c content structure
- * \param data new data to process
- * \param size size of data
- * \return true on success, false on error and error broadcast to users and
- * possibly reported
- */
-
-bool content_process_data(struct content *c, const char *data,
- unsigned int size)
-{
- char *source_data;
- union content_msg_data msg_data;
- unsigned int extra_space;
-
- assert(c);
- assert(c->type < HANDLER_MAP_COUNT);
- assert(c->status == CONTENT_STATUS_LOADING);
-
- if ((c->source_size + size) > c->source_allocated) {
- extra_space = (c->source_size + size) / 4;
- if (extra_space < 65536)
- extra_space = 65536;
- source_data = talloc_realloc(c, c->source_data, char,
- c->source_size + size + extra_space);
- if (!source_data) {
- c->status = CONTENT_STATUS_ERROR;
- msg_data.error = messages_get("NoMemory");
- content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
- return false;
- }
- c->source_data = source_data;
- c->source_allocated = c->source_size + size + extra_space;
- }
- memcpy(c->source_data + c->source_size, data, size);
- c->source_size += size;
-
- if (handler_map[c->type].process_data) {
- if (!handler_map[c->type].process_data(c,
- c->source_data + c->source_size - size, size)) {
- c->status = CONTENT_STATUS_ERROR;
- return false;
- }
- }
- return true;
-}
-
-
-/**
* All data has arrived, convert for display.
*
* Calls the convert function for the content.
@@ -801,22 +644,12 @@ bool content_process_data(struct content *c, const char *data,
void content_convert(struct content *c, int width, int height)
{
union content_msg_data msg_data;
- char *source_data;
assert(c);
assert(c->type < HANDLER_MAP_COUNT);
assert(c->status == CONTENT_STATUS_LOADING);
assert(!c->locked);
- LOG(("content %s (%p)", c->url, c));
-
- if (c->source_allocated != c->source_size) {
- source_data = talloc_realloc(c, c->source_data, char,
- c->source_size);
- if (source_data) {
- c->source_data = source_data;
- c->source_allocated = c->source_size;
- }
- }
+ LOG(("content %s (%p)", llcache_handle_get_url(c->llcache), c));
c->locked = true;
c->available_width = width;
@@ -860,14 +693,19 @@ void content_set_done(struct content *c)
* Calls the reformat function for the content.
*/
-void content_reformat(struct content *c, int width, int height)
+void content_reformat(hlcache_handle *h, int width, int height)
+{
+ content__reformat(hlcache_handle_get_content(h), width, height);
+}
+
+void content__reformat(struct content *c, int width, int height)
{
union content_msg_data data;
assert(c != 0);
assert(c->status == CONTENT_STATUS_READY ||
c->status == CONTENT_STATUS_DONE);
assert(!c->locked);
- LOG(("%p %s", c, c->url));
+ LOG(("%p %s", c, llcache_handle_get_url(c->llcache)));
c->locked = true;
c->available_width = width;
if (handler_map[c->type].reformat) {
@@ -879,65 +717,6 @@ void content_reformat(struct content *c, int width, int height)
/**
- * Clean unused contents from the content_list.
- *
- * Destroys any contents in the content_list with no users or in
- * CONTENT_STATUS_ERROR. Fresh contents in CONTENT_STATUS_DONE may be kept even
- * with no users.
- *
- * Each content is also checked for stop requests.
- */
-
-void content_clean(void)
-{
- unsigned int size;
- struct content *c, *next, *prev;
-
- /* destroy unused stale contents and contents with errors */
- for (c = content_list; c; c = next) {
- next = c->next;
-
- /* this function must not be called from a content function */
- assert(!c->locked);
-
- if (c->user_list->next && c->status != CONTENT_STATUS_ERROR)
- /* content has users */
- continue;
-
- if (c->fresh && c->status == CONTENT_STATUS_DONE)
- /* content is fresh */
- continue;
-
- /* content can be destroyed */
- content_destroy(c);
- }
-
- /* check for pending stops */
- for (c = content_list; c; c = c->next) {
- if (c->status == CONTENT_STATUS_READY)
- content_stop_check(c);
- }
-
- /* attempt to shrink the memory cache (unused fresh contents) */
- size = 0;
- next = 0;
- for (c = content_list; c; c = c->next) {
- next = c;
- c->talloc_size = talloc_total_size(c);
- size += c->size + c->talloc_size;
- }
- for (c = next; c && (unsigned int) option_memory_cache_size < size;
- c = prev) {
- prev = c->prev;
- if (c->user_list->next)
- continue;
- size -= c->size + c->talloc_size;
- content_destroy(c);
- }
-}
-
-
-/**
* Destroy and free a content.
*
* Calls the destroy function for the content, and frees the structure.
@@ -946,92 +725,54 @@ void content_clean(void)
void content_destroy(struct content *c)
{
assert(c);
- LOG(("content %p %s", c, c->url));
+ LOG(("content %p %s", c, llcache_handle_get_url(c->llcache)));
assert(!c->locked);
- if (c->fetch)
- fetch_abort(c->fetch);
-
- if (c->next)
- c->next->prev = c->prev;
- if (c->prev)
- c->prev->next = c->next;
- else
- content_list = c->next;
-
if (c->type < HANDLER_MAP_COUNT && handler_map[c->type].destroy)
handler_map[c->type].destroy(c);
talloc_free(c);
}
-
/**
- * Reset a content.
+ * Request a redraw of an area of a content
*
- * Calls the destroy function for the content, but does not free
- * the structure.
+ * \param h Content handle
+ * \param x x co-ord of left edge
+ * \param y y co-ord of top edge
+ * \param width Width of rectangle
+ * \param height Height of rectangle
*/
-
-void content_reset(struct content *c)
+void content_request_redraw(struct hlcache_handle *h,
+ int x, int y, int width, int height)
{
- assert(c != 0);
- LOG(("content %p %s", c, c->url));
- assert(!c->locked);
- if (c->type < HANDLER_MAP_COUNT && handler_map[c->type].destroy)
- handler_map[c->type].destroy(c);
- c->type = CONTENT_UNKNOWN;
- c->status = CONTENT_STATUS_TYPE_UNKNOWN;
- c->size = 0;
- talloc_free(c->mime_type);
- c->mime_type = 0;
- talloc_free(c->refresh);
- c->refresh = 0;
- talloc_free(c->title);
- c->title = 0;
- talloc_free(c->source_data);
- c->source_data = 0;
- c->source_size = c->source_allocated = 0;
-}
+ struct content *c = hlcache_handle_get_content(h);
+ union content_msg_data data;
+ if (c == NULL)
+ return;
-/**
- * Free all contents in the content_list.
- */
+ data.redraw.x = x;
+ data.redraw.y = y;
+ data.redraw.width = width;
+ data.redraw.height = height;
-void content_quit(void)
-{
- bool progress = true;
- struct content *c, *next;
-
- while (content_list && progress) {
- progress = false;
- for (c = content_list; c; c = next) {
- assert(c->next != c);
- next = c->next;
-
- if (c->user_list->next &&
- c->status != CONTENT_STATUS_ERROR)
- /* content has users */
- continue;
-
- /* content can be destroyed */
- content_destroy(c);
- progress = true;
- }
- }
+ data.redraw.full_redraw = true;
- if (content_list) {
- LOG(("bug: some contents could not be destroyed"));
- }
-}
+ data.redraw.object = c;
+ data.redraw.object_x = 0;
+ data.redraw.object_y = 0;
+ data.redraw.object_width = c->width;
+ data.redraw.object_height = c->height;
+ content_broadcast(c, CONTENT_MSG_REDRAW, data);
+}
/**
* Display content on screen.
*
* Calls the redraw function for the content, if it exists.
*
- * \param c content
+ * \param h content
* \param x coordinate for top-left of redraw
* \param y coordinate for top-left of redraw
* \param width available width (not used for HTML redraw)
@@ -1051,11 +792,12 @@ void content_quit(void)
* Units for x, y and clip_* are pixels.
*/
-bool content_redraw(struct content *c, int x, int y,
+bool content_redraw(hlcache_handle *h, int x, int y,
int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour background_colour)
{
+ struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
// LOG(("%p %s", c, c->url));
if (c->locked)
@@ -1076,12 +818,13 @@ bool content_redraw(struct content *c, int x, int y,
* redraw function if it doesn't exist.
*/
-bool content_redraw_tiled(struct content *c, int x, int y,
+bool content_redraw_tiled(hlcache_handle *h, int x, int y,
int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour background_colour,
bool repeat_x, bool repeat_y)
{
+ struct content *c = hlcache_handle_get_content(h);
int x0, y0, x1, y1;
assert(c != 0);
@@ -1139,29 +882,27 @@ bool content_redraw_tiled(struct content *c, int x, int y,
*
* \param c the content to register
* \param callback the callback function
- * \param p1, p2 callback private data
+ * \param pw callback private data
* \return true on success, false otherwise on memory exhaustion
*
- * The callback will be called with p1 and p2 when content_broadcast() is
+ * The callback will be called when content_broadcast() is
* called with the content.
*/
bool content_add_user(struct content *c,
- void (*callback)(content_msg msg, struct content *c,
- intptr_t p1, intptr_t p2, union content_msg_data data),
- intptr_t p1, intptr_t p2)
+ void (*callback)(struct content *c, content_msg msg,
+ union content_msg_data data, void *pw),
+ void *pw)
{
struct content_user *user;
- LOG(("content %s (%p), user %p 0x%" PRIxPTR " 0x%" PRIxPTR,
- c->url, c, callback, p1, p2));
+ LOG(("content %s (%p), user %p %p",
+ llcache_handle_get_url(c->llcache), c, callback, pw));
user = talloc(c, struct content_user);
if (!user)
return false;
user->callback = callback;
- user->p1 = p1;
- user->p2 = p2;
- user->stop = false;
+ user->pw = pw;
user->next = c->user_list->next;
c->user_list->next = user;
@@ -1170,49 +911,25 @@ bool content_add_user(struct content *c,
/**
- * Search the users of a content for the specified user.
- *
- * \return a content_user struct for the user, or 0 if not found
- */
-
-struct content_user * content_find_user(struct content *c,
- void (*callback)(content_msg msg, struct content *c,
- intptr_t p1, intptr_t p2, union content_msg_data data),
- intptr_t p1, intptr_t p2)
-{
- struct content_user *user;
-
- /* user_list starts with a sentinel */
- for (user = c->user_list; user->next &&
- !(user->next->callback == callback &&
- user->next->p1 == p1 &&
- user->next->p2 == p2); user = user->next)
- ;
- return user->next;
-}
-
-
-/**
* Remove a callback user.
*
- * The callback function, p1, and p2 must be identical to those passed to
+ * The callback function and pw must be identical to those passed to
* content_add_user().
*/
void content_remove_user(struct content *c,
- void (*callback)(content_msg msg, struct content *c,
- intptr_t p1, intptr_t p2, union content_msg_data data),
- intptr_t p1, intptr_t p2)
+ void (*callback)(struct content *c, content_msg msg,
+ union content_msg_data data, void *pw),
+ void *pw)
{
struct content_user *user, *next;
- LOG(("content %s (%p), user %p 0x%" PRIxPTR " 0x%" PRIxPTR,
- c->url, c, callback, p1, p2));
+ LOG(("content %s (%p), user %p %p",
+ llcache_handle_get_url(c->llcache), c, callback, pw));
/* user_list starts with a sentinel */
for (user = c->user_list; user->next != 0 &&
!(user->next->callback == callback &&
- user->next->p1 == p1 &&
- user->next->p2 == p2); user = user->next)
+ user->next->pw == pw); user = user->next)
;
if (user->next == 0) {
LOG(("user not found in list"));
@@ -1238,7 +955,7 @@ void content_broadcast(struct content *c, content_msg msg,
for (user = c->user_list->next; user != 0; user = next) {
next = user->next; /* user may be destroyed during callback */
if (user->callback != 0)
- user->callback(msg, c, user->p1, user->p2, data);
+ user->callback(c, msg, data, user->pw);
}
}
@@ -1250,11 +967,13 @@ void content_broadcast(struct content *c, content_msg msg,
* stop, the loading is stopped and the content placed in CONTENT_STATUS_DONE.
*/
-void content_stop(struct content *c,
- void (*callback)(content_msg msg, struct content *c,
- intptr_t p1, intptr_t p2, union content_msg_data data),
- intptr_t p1, intptr_t p2)
+void content_stop(hlcache_handle *h,
+ void (*callback)(struct content *c, content_msg msg,
+ union content_msg_data data, void *pw),
+ void *pw)
{
+//newcache
+#if 0
struct content_user *user;
assert(c->status == CONTENT_STATUS_READY);
@@ -1267,36 +986,10 @@ void content_stop(struct content *c,
}
LOG(("%p %s: stop user %p 0x%" PRIxPTR " 0x%" PRIxPTR,
- c, c->url, callback, p1, p2));
+ c, llcache_handle_get_url(c->llcache),
+ callback, p1, p2));
user->stop = true;
-}
-
-
-/**
- * Check if all users have requested a stop, and do it if so.
- */
-
-void content_stop_check(struct content *c)
-{
- struct content_user *user;
- union content_msg_data data;
-
- assert(c->status == CONTENT_STATUS_READY);
-
- /* user_list starts with a sentinel */
- for (user = c->user_list->next; user; user = user->next)
- if (!user->stop)
- return;
-
- LOG(("%p %s", c, c->url));
-
- /* all users have requested stop */
- assert(handler_map[c->type].stop);
- handler_map[c->type].stop(c);
- assert(c->status == CONTENT_STATUS_DONE);
-
- content_set_status(c, messages_get("Stopped"));
- content_broadcast(c, CONTENT_MSG_DONE, data);
+#endif
}
@@ -1314,13 +1007,14 @@ void content_stop_check(struct content *c)
* Calls the open function for the content.
*/
-void content_open(struct content *c, struct browser_window *bw,
+void content_open(hlcache_handle *h, struct browser_window *bw,
struct content *page, unsigned int index, struct box *box,
struct object_params *params)
{
+ struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
assert(c->type < CONTENT_UNKNOWN);
- LOG(("content %p %s", c, c->url));
+ LOG(("content %p %s", c, llcache_handle_get_url(c->llcache)));
if (handler_map[c->type].open)
handler_map[c->type].open(c, bw, page, index, box, params);
}
@@ -1332,11 +1026,12 @@ void content_open(struct content *c, struct browser_window *bw,
* Calls the close function for the content.
*/
-void content_close(struct content *c)
+void content_close(hlcache_handle *h)
{
+ struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
assert(c->type < CONTENT_UNKNOWN);
- LOG(("content %p %s", c, c->url));
+ LOG(("content %p %s", c, llcache_handle_get_url(c->llcache)));
if (handler_map[c->type].close)
handler_map[c->type].close(c);
}
@@ -1346,3 +1041,285 @@ void content_add_error(struct content *c, const char *token,
unsigned int line)
{
}
+
+/**
+ * Retrieve type of content
+ *
+ * \param c Content to retrieve type of
+ * \return Content type
+ */
+content_type content_get_type(hlcache_handle *h)
+{
+ return content__get_type(hlcache_handle_get_content(h));
+}
+
+content_type content__get_type(struct content *c)
+{
+ if (c == NULL)
+ return CONTENT_UNKNOWN;
+
+ return c->type;
+}
+
+/**
+ * Retrieve URL associated with content
+ *
+ * \param c Content to retrieve URL from
+ * \return Pointer to URL, or NULL if not found.
+ */
+const char *content_get_url(hlcache_handle *h)
+{
+ return content__get_url(hlcache_handle_get_content(h));
+}
+
+const char *content__get_url(struct content *c)
+{
+ if (c == NULL)
+ return NULL;
+
+ return llcache_handle_get_url(c->llcache);
+}
+
+/**
+ * Retrieve title associated with content
+ *
+ * \param c Content to retrieve title from
+ * \return Pointer to title, or NULL if not found.
+ */
+const char *content_get_title(hlcache_handle *h)
+{
+ return content__get_title(hlcache_handle_get_content(h));
+}
+
+const char *content__get_title(struct content *c)
+{
+ if (c == NULL)
+ return NULL;
+
+ return c->title != NULL ? c->title : llcache_handle_get_url(c->llcache);
+}
+
+/**
+ * Retrieve status of content
+ *
+ * \param c Content to retrieve status of
+ * \return Content status
+ */
+content_status content_get_status(hlcache_handle *h)
+{
+ return content__get_status(hlcache_handle_get_content(h));
+}
+
+content_status content__get_status(struct content *c)
+{
+ if (c == NULL)
+ return CONTENT_STATUS_TYPE_UNKNOWN;
+
+ return c->status;
+}
+
+/**
+ * Retrieve status message associated with content
+ *
+ * \param c Content to retrieve status message from
+ * \return Pointer to status message, or NULL if not found.
+ */
+const char *content_get_status_message(hlcache_handle *h)
+{
+ return content__get_status_message(hlcache_handle_get_content(h));
+}
+
+const char *content__get_status_message(struct content *c)
+{
+ if (c == NULL)
+ return NULL;
+
+ return c->status_message;
+}
+
+/**
+ * Retrieve width of content
+ *
+ * \param c Content to retrieve width of
+ * \return Content width
+ */
+int content_get_width(hlcache_handle *h)
+{
+ return content__get_width(hlcache_handle_get_content(h));
+}
+
+int content__get_width(struct content *c)
+{
+ if (c == NULL)
+ return 0;
+
+ return c->width;
+}
+
+/**
+ * Retrieve height of content
+ *
+ * \param c Content to retrieve height of
+ * \return Content height
+ */
+int content_get_height(hlcache_handle *h)
+{
+ return content__get_height(hlcache_handle_get_content(h));
+}
+
+int content__get_height(struct content *c)
+{
+ if (c == NULL)
+ return 0;
+
+ return c->height;
+}
+
+/**
+ * Retrieve available width of content
+ *
+ * \param c Content to retrieve available width of
+ * \return Available width of content
+ */
+int content_get_available_width(hlcache_handle *h)
+{
+ return content__get_available_width(hlcache_handle_get_content(h));
+}
+
+int content__get_available_width(struct content *c)
+{
+ if (c == NULL)
+ return 0;
+
+ return c->available_width;
+}
+
+
+/**
+ * Retrieve source of content
+ *
+ * \param c Content to retrieve source of
+ * \param size Pointer to location to receive byte size of source
+ * \return Pointer to source data
+ */
+const char *content_get_source_data(hlcache_handle *h, unsigned long *size)
+{
+ return content__get_source_data(hlcache_handle_get_content(h), size);
+}
+
+const char *content__get_source_data(struct content *c, unsigned long *size)
+{
+ const uint8_t *data;
+ size_t len;
+
+ assert(size != NULL);
+
+ if (c == NULL)
+ return NULL;
+
+ data = llcache_handle_get_source_data(c->llcache, &len);
+
+ *size = (unsigned long) len;
+
+ return (const char *) data;
+}
+
+/**
+ * Invalidate content reuse data: causes subsequent requests for content URL
+ * to query server to determine if content can be reused. This is required
+ * behaviour for forced reloads etc.
+ *
+ * \param c Content to invalidate
+ */
+void content_invalidate_reuse_data(hlcache_handle *h)
+{
+ content__invalidate_reuse_data(hlcache_handle_get_content(h));
+}
+
+void content__invalidate_reuse_data(struct content *c)
+{
+ if (c == NULL)
+ return;
+
+ /* For now, just cause the content to be completely ignored */
+ c->fresh = false;
+}
+
+/**
+ * Retrieve the refresh URL for a content
+ *
+ * \param c Content to retrieve refresh URL from
+ * \return Pointer to URL, or NULL if none
+ */
+const char *content_get_refresh_url(hlcache_handle *h)
+{
+ return content__get_refresh_url(hlcache_handle_get_content(h));
+}
+
+const char *content__get_refresh_url(struct content *c)
+{
+ if (c == NULL)
+ return NULL;
+
+ return c->refresh;
+}
+
+/**
+ * Retrieve the bitmap contained in an image content
+ *
+ * \param c Content to retrieve bitmap from
+ * \return Pointer to bitmap, or NULL if none.
+ */
+struct bitmap *content_get_bitmap(hlcache_handle *h)
+{
+ return content__get_bitmap(hlcache_handle_get_content(h));
+}
+
+struct bitmap *content__get_bitmap(struct content *c)
+{
+ if (c == NULL)
+ return NULL;
+
+ return c->bitmap;
+}
+
+/**
+ * Retrieve the low-level cache handle for a content
+ *
+ * \param h Content to retrieve from
+ * \return Low-level cache handle
+ */
+const llcache_handle *content_get_llcache_handle(struct content *c)
+{
+ if (c == NULL)
+ return NULL;
+
+ return c->llcache;
+}
+
+
+/**
+ * Convert a content into a download
+ *
+ * \param h Content to convert
+ * \return Pointer to low-level cache handle
+ */
+llcache_handle *content_convert_to_download(hlcache_handle *h)
+{
+ struct content *c = hlcache_handle_get_content(h);
+ llcache_handle *stream = c->llcache;
+
+ assert(c != NULL);
+ assert(c->status == CONTENT_STATUS_LOADING);
+
+ /** \todo Is this safe? */
+ c->llcache = NULL;
+
+ /** \todo Tell the llcache to stream the data without caching it */
+
+ /** \todo Invalidate the content object so it's flushed from the
+ * cache at the earliest opportunity */
+
+ return stream;
+}
+
diff --git a/content/content.h b/content/content.h
index db35b39a2..1dd7f83cd 100644
--- a/content/content.h
+++ b/content/content.h
@@ -26,64 +26,31 @@
#ifndef _NETSURF_CONTENT_CONTENT_H_
#define _NETSURF_CONTENT_CONTENT_H_
-/* Irritatingly this must come first, or odd include errors
- * will occur to do with setjmp.h.
- */
-#ifdef WITH_PNG
-#include "image/png.h"
-#endif
+#include <stdbool.h>
-#include <stdint.h>
-#include <time.h>
#include "utils/config.h"
#include "content/content_type.h"
-#include "css/css.h"
-#include "render/html.h"
-#include "render/textplain.h"
-#ifdef WITH_JPEG
-#include "image/jpeg.h"
-#endif
-#ifdef WITH_GIF
-#include "image/gif.h"
-#endif
-#ifdef WITH_BMP
-#include "image/bmp.h"
-#include "image/ico.h"
-#endif
-#ifdef WITH_PLUGIN
-#include "riscos/plugin.h"
-#endif
-#ifdef WITH_MNG
-#include "image/mng.h"
-#endif
-#ifdef WITH_SPRITE
-#include "riscos/sprite.h"
-#endif
-#ifdef WITH_NSSPRITE
-#include "image/nssprite.h"
-#endif
-#ifdef WITH_DRAW
-#include "riscos/draw.h"
-#endif
-#ifdef WITH_ARTWORKS
-#include "riscos/artworks.h"
-#endif
-#ifdef WITH_NS_SVG
-#include "image/svg.h"
-#endif
-#ifdef WITH_RSVG
-#include "image/rsvg.h"
-#endif
+#include "desktop/plot_style.h"
+struct llcache_handle;
-struct bitmap;
struct box;
struct browser_window;
struct content;
-struct fetch;
+struct hlcache_handle;
struct object_params;
-struct ssl_cert_info;
+/** Status of a content */
+typedef enum {
+ CONTENT_STATUS_TYPE_UNKNOWN, /**< Type not yet known. */
+ CONTENT_STATUS_LOADING, /**< Content is being fetched or
+ converted and is not safe to display. */
+ CONTENT_STATUS_READY, /**< Some parts of content still being
+ loaded, but can be displayed. */
+ CONTENT_STATUS_DONE, /**< All finished. */
+ CONTENT_STATUS_ERROR /**< Error occurred, content will be
+ destroyed imminently. */
+} content_status;
/** Used in callbacks to indicate what has occurred. */
typedef enum {
@@ -129,204 +96,64 @@ union content_msg_data {
} ssl;
};
-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 */
- time_t last_modified; /**< Last-Modified: response header */
-};
-
-/** Linked list of users of a content. */
-struct content_user
-{
- void (*callback)(content_msg msg, struct content *c, intptr_t p1,
- intptr_t p2, union content_msg_data data);
- intptr_t p1;
- intptr_t p2;
- bool stop;
- struct content_user *next;
-};
-
-/** Corresponds to a single URL. */
-struct content {
- char *url; /**< URL, in standard form as from url_join. */
- content_type type; /**< Type of content. */
- char *mime_type; /**< Original MIME type of data, or 0. */
- enum {
- CONTENT_STATUS_TYPE_UNKNOWN, /**< Type not yet known. */
- CONTENT_STATUS_LOADING, /**< Content is being fetched or
- converted and is not safe to display. */
- CONTENT_STATUS_READY, /**< Some parts of content still being
- loaded, but can be displayed. */
- CONTENT_STATUS_DONE, /**< All finished. */
- CONTENT_STATUS_ERROR /**< Error occurred, content will be
- destroyed imminently. */
- } status; /**< Current status. */
-
- int width, height; /**< Dimensions, if applicable. */
- int available_width; /**< Available width (eg window width). */
-
- /** Data dependent on type. */
- union {
- struct content_html_data html;
- struct content_textplain_data textplain;
- struct content_css_data css;
-#ifdef WITH_JPEG
- struct content_jpeg_data jpeg;
-#endif
-#ifdef WITH_GIF
- struct content_gif_data gif;
-#endif
-#ifdef WITH_BMP
- struct content_bmp_data bmp;
- struct content_ico_data ico;
-#endif
-#ifdef WITH_MNG
- struct content_mng_data mng;
-#endif
-#ifdef WITH_SPRITE
- struct content_sprite_data sprite;
-#endif
-#ifdef WITH_NSSPRITE
- struct content_nssprite_data nssprite;
-#endif
-#ifdef WITH_DRAW
- struct content_draw_data draw;
-#endif
-#ifdef WITH_PLUGIN
- struct content_plugin_data plugin;
-#endif
-#ifdef WITH_ARTWORKS
- struct content_artworks_data artworks;
-#endif
-#ifdef WITH_NS_SVG
- struct content_svg_data svg;
-#endif
-#ifdef WITH_RSVG
- struct content_rsvg_data rsvg;
-#endif
-#ifdef WITH_PNG
- struct content_png_data png;
-#endif
- } data;
-
- /**< URL for refresh request, in standard form as from url_join. */
- char *refresh;
-
- /** Bitmap, for various image contents. */
- struct bitmap *bitmap;
-
- /** This content may be given to new users. Indicates that the 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 time; /**< Creation time, if TYPE_UNKNOWN,
- LOADING or READY,
- otherwise total time. */
-
- unsigned int reformat_time; /**< Earliest time to attempt a
- period reflow while fetching a
- page's objects. */
-
- unsigned int size; /**< Estimated size of all data
- associated with this content, except
- alloced as talloc children of this. */
- off_t talloc_size; /**< Used by content_clean() */
- char *title; /**< Title for browser window. */
- unsigned int active; /**< Number of child fetches or
- conversions currently in progress. */
- struct content_user *user_list; /**< List of users. */
- char status_message[120]; /**< Full text for status bar. */
- char sub_status[80]; /**< Status of content. */
- /** Content is being processed: data structures may be inconsistent
- * and content must not be redrawn or modified. */
- bool locked;
-
- struct fetch *fetch; /**< Associated fetch, or 0. */
- char *source_data; /**< Source data, as received. */
- unsigned long source_size; /**< Amount of data fetched so far. */
- unsigned long source_allocated; /**< Amount of space allocated so far. */
- unsigned long total_size; /**< Total data size, 0 if unknown. */
- long http_code; /**< HTTP status code, 0 if not HTTP. */
-
- bool no_error_pages; /**< Used by fetchcache(). */
- bool download; /**< Used by fetchcache(). */
- bool tried_with_auth; /**< Used by fetchcache(). */
- unsigned int redirect_count; /**< Used by fetchcache(). */
-
- /** Array of first n rendering errors or warnings. */
- struct {
- const char *token;
- unsigned int line; /**< Line no, 0 if not applicable. */
- } error_list[40];
- unsigned int error_count; /**< Number of valid error entries. */
-
- struct content *prev; /**< Previous in global content list. */
- struct content *next; /**< Next in global content list. */
-};
+/* The following are for hlcache */
+content_type content_lookup(const char *mime_type);
+struct content *content_create(struct llcache_handle *llcache,
+ const char *fallback_charset, bool quirks);
+void content_destroy(struct content *c);
+
+bool content_add_user(struct content *h,
+ void (*callback)(struct content *c, content_msg msg,
+ union content_msg_data data, void *pw),
+ void *pw);
+void content_remove_user(struct content *c,
+ void (*callback)(struct content *c, content_msg msg,
+ union content_msg_data data, void *pw),
+ void *pw);
-extern struct content *content_list;
-extern const char * const content_type_name[];
-extern const char * const content_status_name[];
+const struct llcache_handle *content_get_llcache_handle(struct content *c);
-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_can_reformat(struct content *c);
-bool content_set_type(struct content *c, content_type type,
- const char *mime_type, const char *params[],
- struct content *parent);
-void content_set_status(struct content *c, const char *status_message, ...);
-bool content_process_data(struct content *c, const char *data,
- unsigned int size);
-void content_convert(struct content *c, int width, int height);
-void content_set_done(struct content *c);
-void content_reformat(struct content *c, int width, int height);
-void content_clean(void);
-void content_reset(struct content *c);
-void content_quit(void);
-bool content_redraw(struct content *c, int x, int y,
+/* Client functions */
+bool content_can_reformat(struct hlcache_handle *h);
+void content_reformat(struct hlcache_handle *h, int width, int height);
+void content_request_redraw(struct hlcache_handle *h,
+ int x, int y, int width, int height);
+bool content_redraw(struct hlcache_handle *h, int x, int y,
int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour background_colour);
-bool content_redraw_tiled(struct content *c, int x, int y,
+bool content_redraw_tiled(struct hlcache_handle *h, int x, int y,
int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour background_colour,
bool repeat_x, bool repeat_y);
-bool content_add_user(struct content *c,
- void (*callback)(content_msg msg, struct content *c,
- intptr_t p1, intptr_t p2, union content_msg_data data),
- intptr_t p1, intptr_t p2);
-struct content_user * content_find_user(struct content *c,
- void (*callback)(content_msg msg, struct content *c,
- intptr_t p1, intptr_t p2, union content_msg_data data),
- intptr_t p1, intptr_t p2);
-void content_remove_user(struct content *c,
- void (*callback)(content_msg msg, struct content *c,
- intptr_t p1, intptr_t p2, union content_msg_data data),
- intptr_t p1, intptr_t p2);
-void content_broadcast(struct content *c, content_msg msg,
- union content_msg_data data);
-void content_stop(struct content *c,
- void (*callback)(content_msg msg, struct content *c,
- intptr_t p1, intptr_t p2, union content_msg_data data),
- intptr_t p1, intptr_t p2);
-void content_open(struct content *c, struct browser_window *bw,
+void content_stop(struct hlcache_handle *h,
+ void (*callback)(struct content *c, content_msg msg,
+ union content_msg_data data, void *pw),
+ void *pw);
+void content_open(struct hlcache_handle *h, struct browser_window *bw,
struct content *page, unsigned int index, struct box *box,
struct object_params *params);
-void content_close(struct content *c);
-void content_add_error(struct content *c, const char *token,
- unsigned int line);
+void content_close(struct hlcache_handle *h);
+
+/* Member accessors */
+content_type content_get_type(struct hlcache_handle *c);
+const char *content_get_url(struct hlcache_handle *c);
+const char *content_get_title(struct hlcache_handle *c);
+content_status content_get_status(struct hlcache_handle *c);
+const char *content_get_status_message(struct hlcache_handle *c);
+int content_get_width(struct hlcache_handle *c);
+int content_get_height(struct hlcache_handle *c);
+int content_get_available_width(struct hlcache_handle *c);
+const char *content_get_source_data(struct hlcache_handle *c,
+ unsigned long *size);
+void content_invalidate_reuse_data(struct hlcache_handle *c);
+const char *content_get_refresh_url(struct hlcache_handle *c);
+struct bitmap *content_get_bitmap(struct hlcache_handle *c);
+
+/* Download support */
+struct llcache_handle *content_convert_to_download(struct hlcache_handle *c);
#endif
diff --git a/content/content_protected.h b/content/content_protected.h
new file mode 100644
index 000000000..261ee7bcb
--- /dev/null
+++ b/content/content_protected.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2005-2007 James Bursa <bursa@users.sourceforge.net>
+ * Copyright 2003 Philip Pemberton <philpem@users.sourceforge.net>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * Content handling (interface).
+ *
+ * The content functions manipulate struct contents, which correspond to URLs.
+ */
+
+#ifndef _NETSURF_CONTENT_CONTENT_PROTECTED_H_
+#define _NETSURF_CONTENT_CONTENT_PROTECTED_H_
+
+/* Irritatingly this must come first, or odd include errors
+ * will occur to do with setjmp.h.
+ */
+#ifdef WITH_PNG
+#include "image/png.h"
+#endif
+
+#include <stdint.h>
+#include <time.h>
+#include "utils/config.h"
+#include "content/content.h"
+#include "content/llcache.h"
+#include "css/css.h"
+#include "render/html.h"
+#include "render/textplain.h"
+#ifdef WITH_JPEG
+#include "image/jpeg.h"
+#endif
+#ifdef WITH_GIF
+#include "image/gif.h"
+#endif
+#ifdef WITH_BMP
+#include "image/bmp.h"
+#include "image/ico.h"
+#endif
+#ifdef WITH_PLUGIN
+#include "riscos/plugin.h"
+#endif
+#ifdef WITH_MNG
+#include "image/mng.h"
+#endif
+#ifdef WITH_SPRITE
+#include "riscos/sprite.h"
+#endif
+#ifdef WITH_NSSPRITE
+#include "image/nssprite.h"
+#endif
+#ifdef WITH_DRAW
+#include "riscos/draw.h"
+#endif
+#ifdef WITH_ARTWORKS
+#include "riscos/artworks.h"
+#endif
+#ifdef WITH_NS_SVG
+#include "image/svg.h"
+#endif
+#ifdef WITH_RSVG
+#include "image/rsvg.h"
+#endif
+
+
+struct bitmap;
+struct content;
+
+/** Linked list of users of a content. */
+struct content_user
+{
+ void (*callback)(struct content *c, content_msg msg,
+ union content_msg_data data, void *pw);
+ void *pw;
+
+ struct content_user *next;
+};
+
+/** Corresponds to a single URL. */
+struct content {
+ llcache_handle *llcache; /**< Low-level cache object */
+
+ content_type type; /**< Type of content. */
+ char *mime_type; /**< Original MIME type of data, or 0. */
+
+ content_status status; /**< Current status. */
+
+ int width, height; /**< Dimensions, if applicable. */
+ int available_width; /**< Available width (eg window width). */
+
+ bool quirks; /**< Content is in quirks mode */
+ char *fallback_charset; /**< Fallback charset, or NULL */
+
+ /** Data dependent on type. */
+ union {
+ struct content_html_data html;
+ struct content_textplain_data textplain;
+ struct content_css_data css;
+#ifdef WITH_JPEG
+ struct content_jpeg_data jpeg;
+#endif
+#ifdef WITH_GIF
+ struct content_gif_data gif;
+#endif
+#ifdef WITH_BMP
+ struct content_bmp_data bmp;
+ struct content_ico_data ico;
+#endif
+#ifdef WITH_MNG
+ struct content_mng_data mng;
+#endif
+#ifdef WITH_SPRITE
+ struct content_sprite_data sprite;
+#endif
+#ifdef WITH_NSSPRITE
+ struct content_nssprite_data nssprite;
+#endif
+#ifdef WITH_DRAW
+ struct content_draw_data draw;
+#endif
+#ifdef WITH_PLUGIN
+ struct content_plugin_data plugin;
+#endif
+#ifdef WITH_ARTWORKS
+ struct content_artworks_data artworks;
+#endif
+#ifdef WITH_NS_SVG
+ struct content_svg_data svg;
+#endif
+#ifdef WITH_RSVG
+ struct content_rsvg_data rsvg;
+#endif
+#ifdef WITH_PNG
+ struct content_png_data png;
+#endif
+ } data;
+
+ /**< URL for refresh request, in standard form as from url_join. */
+ char *refresh;
+
+ /** Bitmap, for various image contents. */
+ struct bitmap *bitmap;
+
+ /** This content may be given to new users. Indicates that the content
+ * was fetched using a simple GET, has not expired, and may be
+ * shared between users. */
+ bool fresh;
+ unsigned int time; /**< Creation time, if TYPE_UNKNOWN,
+ LOADING or READY,
+ otherwise total time. */
+
+ unsigned int reformat_time; /**< Earliest time to attempt a
+ period reflow while fetching a
+ page's objects. */
+
+ unsigned int size; /**< Estimated size of all data
+ associated with this content, except
+ alloced as talloc children of this. */
+ off_t talloc_size; /**< Used by content_clean() */
+ char *title; /**< Title for browser window. */
+ unsigned int active; /**< Number of child fetches or
+ conversions currently in progress. */
+ struct content_user *user_list; /**< List of users. */
+ char status_message[120]; /**< Full text for status bar. */
+ char sub_status[80]; /**< Status of content. */
+ /** Content is being processed: data structures may be inconsistent
+ * and content must not be redrawn or modified. */
+ bool locked;
+
+ unsigned long total_size; /**< Total data size, 0 if unknown. */
+ long http_code; /**< HTTP status code, 0 if not HTTP. */
+
+ /** Array of first n rendering errors or warnings. */
+ struct {
+ const char *token;
+ unsigned int line; /**< Line no, 0 if not applicable. */
+ } error_list[40];
+ unsigned int error_count; /**< Number of valid error entries. */
+};
+
+extern const char * const content_type_name[];
+extern const char * const content_status_name[];
+
+void content_set_done(struct content *c);
+void content_set_status(struct content *c, const char *status_message, ...);
+void content_broadcast(struct content *c, content_msg msg,
+ union content_msg_data data);
+void content_add_error(struct content *c, const char *token,
+ unsigned int line);
+
+void content__reformat(struct content *c, int width, int height);
+
+
+content_type content__get_type(struct content *c);
+const char *content__get_url(struct content *c);
+const char *content__get_title(struct content *c);
+content_status content__get_status(struct content *c);
+const char *content__get_status_message(struct content *c);
+int content__get_width(struct content *c);
+int content__get_height(struct content *c);
+int content__get_available_width(struct content *c);
+const char *content__get_source_data(struct content *c, unsigned long *size);
+void content__invalidate_reuse_data(struct content *c);
+const char *content__get_refresh_url(struct content *c);
+struct bitmap *content__get_bitmap(struct content *c);
+
+#endif
diff --git a/content/fetch.c b/content/fetch.c
index f835ac121..627d7caf0 100644
--- a/content/fetch.c
+++ b/content/fetch.c
@@ -41,7 +41,6 @@
#include "content/urldb.h"
#include "desktop/netsurf.h"
#include "desktop/options.h"
-#include "render/form.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/url.h"
@@ -213,7 +212,7 @@ void fetch_unref_fetcher(scheme_fetcher *fetcher)
struct fetch * fetch_start(const char *url, const char *referer,
fetch_callback callback,
void *p, bool only_2xx, const char *post_urlenc,
- struct form_successful_control *post_multipart,
+ struct fetch_multipart_data *post_multipart,
bool verifiable, struct content *parent,
char *headers[])
{
@@ -598,6 +597,78 @@ bool fetch_get_verifiable(struct fetch *fetch)
return fetch->verifiable;
}
+/**
+ * Clone a linked list of fetch_multipart_data.
+ *
+ * \param list List to clone
+ * \return Pointer to head of cloned list, or NULL on failure
+ */
+struct fetch_multipart_data *fetch_multipart_data_clone(
+ const struct fetch_multipart_data *list)
+{
+ struct fetch_multipart_data *clone, *last = NULL;
+ struct fetch_multipart_data *result = NULL;
+
+ for (; list != NULL; list = list->next) {
+ clone = malloc(sizeof(struct fetch_multipart_data));
+ if (clone == NULL) {
+ if (result != NULL)
+ fetch_multipart_data_destroy(result);
+
+ return NULL;
+ }
+
+ clone->file = list->file;
+
+ clone->name = strdup(list->name);
+ if (clone->name == NULL) {
+ free(clone);
+ if (result != NULL)
+ fetch_multipart_data_destroy(result);
+
+ return NULL;
+ }
+
+ clone->value = strdup(list->value);
+ if (clone->value == NULL) {
+ free(clone->name);
+ free(clone);
+ if (result != NULL)
+ fetch_multipart_data_destroy(result);
+
+ return NULL;
+ }
+
+ clone->next = NULL;
+
+ if (result == NULL)
+ result = clone;
+ else
+ last->next = clone;
+
+ last = clone;
+ }
+
+ return result;
+}
+
+/**
+ * Free a linked list of fetch_multipart_data.
+ *
+ * \param list Pointer to head of list to free
+ */
+void fetch_multipart_data_destroy(struct fetch_multipart_data *list)
+{
+ struct fetch_multipart_data *next;
+
+ for (; list != NULL; list = next) {
+ next = list->next;
+ free(list->name);
+ free(list->value);
+ free(list);
+ }
+}
+
void
fetch_send_callback(fetch_msg msg, struct fetch *fetch, const void *data,
unsigned long size, fetch_error_code errorcode)
@@ -665,8 +736,8 @@ fetch_set_cookie(struct fetch *fetch, const char *data)
* that the request uri and the parent domain match,
* so don't pass in the parent in this case. */
urldb_set_cookie(data, fetch->url,
- fetch->verifiable ? NULL
- : fetch->parent->url);
+ fetch->verifiable ? NULL
+ : content_get_url(fetch->parent));
}
}
diff --git a/content/fetch.h b/content/fetch.h
index 168c9b252..16dae63d0 100644
--- a/content/fetch.h
+++ b/content/fetch.h
@@ -54,7 +54,15 @@ typedef enum {
struct content;
struct fetch;
-struct form_successful_control;
+
+/** Fetch POST multipart data */
+struct fetch_multipart_data {
+ bool file; /**< Item is a file */
+ char *name; /**< Name of item */
+ char *value; /**< Item value */
+
+ struct fetch_multipart_data *next; /**< Next in linked list */
+};
struct ssl_cert_info {
long version; /**< Certificate version */
@@ -77,7 +85,7 @@ void fetch_init(void);
struct fetch * fetch_start(const char *url, const char *referer,
fetch_callback callback,
void *p, bool only_2xx, const char *post_urlenc,
- struct form_successful_control *post_multipart,
+ struct fetch_multipart_data *post_multipart,
bool verifiable, struct content *parent,
char *headers[]);
void fetch_abort(struct fetch *f);
@@ -94,12 +102,16 @@ const char *fetch_get_referer(struct fetch *fetch);
struct content *fetch_get_parent(struct fetch *fetch);
bool fetch_get_verifiable(struct fetch *fetch);
+void fetch_multipart_data_destroy(struct fetch_multipart_data *list);
+struct fetch_multipart_data *fetch_multipart_data_clone(
+ const struct fetch_multipart_data *list);
+
/* API for fetchers themselves */
typedef bool (*fetcher_initialise)(const char *);
typedef void* (*fetcher_setup_fetch)(struct fetch *, const char *,
bool, const char *,
- struct form_successful_control *,
+ struct fetch_multipart_data *,
const char **);
typedef bool (*fetcher_start_fetch)(void *);
typedef void (*fetcher_abort_fetch)(void *);
diff --git a/content/fetchcache.c b/content/fetchcache.c
index 243d5c04b..3a0b667f9 100644
--- a/content/fetchcache.c
+++ b/content/fetchcache.c
@@ -102,7 +102,7 @@ struct content * fetchcache(const char *url,
int width, int height,
bool no_error_pages,
char *post_urlenc,
- struct form_successful_control *post_multipart,
+ struct fetch_multipart_data *post_multipart,
bool verifiable,
bool download)
{
@@ -250,7 +250,7 @@ void fetchcache_go(struct content *content, const char *referer,
intptr_t p1, intptr_t p2,
int width, int height,
char *post_urlenc,
- struct form_successful_control *post_multipart,
+ struct fetch_multipart_data *post_multipart,
bool verifiable, struct content *parent)
{
char error_message[500];
diff --git a/content/fetchcache.h b/content/fetchcache.h
index 24ed564ad..46e9b3d94 100644
--- a/content/fetchcache.h
+++ b/content/fetchcache.h
@@ -30,7 +30,7 @@
#include <stdint.h>
#include "content/content.h"
-struct form_successful_control;
+struct fetch_multipart_data;
void fetchcache_init(void);
struct content * fetchcache(const char *url,
@@ -40,7 +40,7 @@ struct content * fetchcache(const char *url,
int width, int height,
bool no_error_pages,
char *post_urlenc,
- struct form_successful_control *post_multipart,
+ struct fetch_multipart_data *post_multipart,
bool verifiable,
bool download);
void fetchcache_go(struct content *content, const char *referer,
@@ -49,7 +49,7 @@ void fetchcache_go(struct content *content, const char *referer,
intptr_t p1, intptr_t p2,
int width, int height,
char *post_urlenc,
- struct form_successful_control *post_multipart,
+ struct fetch_multipart_data *post_multipart,
bool verifiable, struct content *parent);
#endif
diff --git a/content/fetchers/fetch_curl.c b/content/fetchers/fetch_curl.c
index ca2d86845..9ac3ad7b3 100644
--- a/content/fetchers/fetch_curl.c
+++ b/content/fetchers/fetch_curl.c
@@ -30,6 +30,7 @@
#include <assert.h>
#include <errno.h>
+#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
@@ -43,7 +44,6 @@
#include "content/urldb.h"
#include "desktop/netsurf.h"
#include "desktop/options.h"
-#include "render/form.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/url.h"
@@ -104,7 +104,7 @@ static bool fetch_curl_initialise(const char *scheme);
static void fetch_curl_finalise(const char *scheme);
static void * fetch_curl_setup(struct fetch *parent_fetch, const char *url,
bool only_2xx, const char *post_urlenc,
- struct form_successful_control *post_multipart,
+ struct fetch_multipart_data *post_multipart,
const char **headers);
static bool fetch_curl_start(void *vfetch);
static bool fetch_curl_initiate_fetch(struct curl_fetch_info *fetch,
@@ -132,7 +132,7 @@ static size_t fetch_curl_header(char *data, size_t size, size_t nmemb,
void *_f);
static bool fetch_curl_process_headers(struct curl_fetch_info *f);
static struct curl_httppost *fetch_curl_post_convert(
- struct form_successful_control *control);
+ struct fetch_multipart_data *control);
static int fetch_curl_verify_callback(int preverify_ok,
X509_STORE_CTX *x509_ctx);
static int fetch_curl_cert_verify_callback(X509_STORE_CTX *x509_ctx,
@@ -294,7 +294,7 @@ void fetch_curl_finalise(const char *scheme)
void * fetch_curl_setup(struct fetch *parent_fetch, const char *url,
bool only_2xx, const char *post_urlenc,
- struct form_successful_control *post_multipart,
+ struct fetch_multipart_data *post_multipart,
const char **headers)
{
char *host;
@@ -1108,10 +1108,7 @@ size_t fetch_curl_header(char *data, size_t size, size_t nmemb,
bool fetch_curl_process_headers(struct curl_fetch_info *f)
{
long http_code;
- const char *type;
CURLcode code;
- struct stat s;
- char *url_path = 0;
f->had_headers = true;
@@ -1142,7 +1139,7 @@ bool fetch_curl_process_headers(struct curl_fetch_info *f)
/* handle HTTP 401 (Authentication errors) */
if (http_code == 401) {
- fetch_send_callback(FETCH_AUTH, f->fetch_handle, f->realm,0,
+ fetch_send_callback(FETCH_AUTH, f->fetch_handle, f->realm, 0,
FETCH_ERROR_AUTHENTICATION);
return true;
}
@@ -1156,49 +1153,64 @@ bool fetch_curl_process_headers(struct curl_fetch_info *f)
return true;
}
- /* find MIME type from headers or filetype for local files */
- code = curl_easy_getinfo(f->curl_handle, CURLINFO_CONTENT_TYPE, &type);
- assert(code == CURLE_OK);
-
- if (strncmp(f->url, "file:///", 8) == 0)
- url_path = curl_unescape(f->url + 7,
- (int) strlen(f->url) - 7);
-
- if (url_path && stat(url_path, &s) == 0) {
- /* file: URL and file exists */
- /* create etag */
- char etag_buf[20];
- snprintf(etag_buf, sizeof etag_buf,
- "ETag: \"%10d\"", (int) s.st_mtime);
- /* And send it to the header handler */
- fetch_send_callback(FETCH_HEADER, f->fetch_handle, etag_buf,
- strlen(etag_buf), FETCH_ERROR_NO_ERROR);
-
- /* don't set last modified time so as to ensure that local
- * files are revalidated at all times. */
-
- /* If performed a conditional request and unmodified ... */
- if (f->last_modified && f->file_etag &&
- f->last_modified > s.st_mtime &&
- f->file_etag == s.st_mtime) {
- fetch_send_callback(FETCH_NOTMODIFIED, f->fetch_handle,
- 0, 0, FETCH_ERROR_NO_ERROR);
- curl_free(url_path);
- return true;
- }
- }
-
- if (type == 0) {
- type = "text/plain";
- if (url_path) {
+ /* find MIME type from filetype for local files */
+ if (strncmp(f->url, "file:///", 8) == 0) {
+ struct stat s;
+ char *url_path = curl_unescape(f->url + 7,
+ (int) strlen(f->url + 7));
+
+ if (url_path != NULL && stat(url_path, &s) == 0) {
+ /* file: URL and file exists */
+ char header[64];
+ const char *type;
+
+ /* create etag */
+ snprintf(header, sizeof header,
+ "ETag: \"%10" PRId64 "\"",
+ (int64_t) s.st_mtime);
+ /* And send it to the header handler */
+ fetch_send_callback(FETCH_HEADER, f->fetch_handle,
+ header, strlen(header),
+ FETCH_ERROR_NO_ERROR);
+
+ /* create Content-Type */
type = fetch_filetype(url_path);
+ snprintf(header, sizeof header,
+ "Content-Type: %s", type);
+ /* Send it to the header handler */
+ fetch_send_callback(FETCH_HEADER, f->fetch_handle,
+ header, strlen(header),
+ FETCH_ERROR_NO_ERROR);
+
+ /* create Content-Length */
+ type = fetch_filetype(url_path);
+ snprintf(header, sizeof header,
+ "Content-Length: %" PRId64,
+ (int64_t) s.st_size);
+ /* Send it to the header handler */
+ fetch_send_callback(FETCH_HEADER, f->fetch_handle,
+ header, strlen(header),
+ FETCH_ERROR_NO_ERROR);
+
+ /* don't set last modified time so as to ensure that
+ * local files are revalidated at all times. */
+
+ /* Report not modified, if appropriate */
+ if (f->last_modified && f->file_etag &&
+ f->last_modified > s.st_mtime &&
+ f->file_etag == s.st_mtime) {
+ fetch_send_callback(FETCH_NOTMODIFIED,
+ f->fetch_handle, 0, 0,
+ FETCH_ERROR_NO_ERROR);
+ curl_free(url_path);
+ return true;
+ }
}
- }
- curl_free(url_path);
+ if (url_path != NULL)
+ curl_free(url_path);
+ }
- LOG(("FETCH_TYPE, '%s'", type));
- fetch_send_callback(FETCH_TYPE, f->fetch_handle, type, f->content_length, FETCH_ERROR_NO_ERROR);
if (f->abort)
return true;
@@ -1207,11 +1219,11 @@ bool fetch_curl_process_headers(struct curl_fetch_info *f)
/**
- * Convert a list of struct ::form_successful_control to a list of
+ * Convert a list of struct ::fetch_multipart_data to a list of
* struct curl_httppost for libcurl.
*/
struct curl_httppost *
-fetch_curl_post_convert(struct form_successful_control *control)
+fetch_curl_post_convert(struct fetch_multipart_data *control)
{
struct curl_httppost *post = 0, *last = 0;
CURLFORMcode code;
diff --git a/content/fetchers/fetch_data.c b/content/fetchers/fetch_data.c
index f2a90c50d..1790de56f 100644
--- a/content/fetchers/fetch_data.c
+++ b/content/fetchers/fetch_data.c
@@ -35,7 +35,6 @@
#include "content/urldb.h"
#include "desktop/netsurf.h"
#include "desktop/options.h"
-#include "render/form.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/url.h"
@@ -78,7 +77,7 @@ static void fetch_data_finalise(const char *scheme)
static void *fetch_data_setup(struct fetch *parent_fetch, const char *url,
bool only_2xx, const char *post_urlenc,
- struct form_successful_control *post_multipart,
+ struct fetch_multipart_data *post_multipart,
const char **headers)
{
struct fetch_data_context *ctx = calloc(1, sizeof(*ctx));
@@ -232,20 +231,9 @@ static bool fetch_data_process(struct fetch_data_context *c)
static void fetch_data_poll(const char *scheme)
{
struct fetch_data_context *c, *next;
- struct cache_data cachedata;
if (ring == NULL) return;
- cachedata.req_time = time(NULL);
- cachedata.res_time = time(NULL);
- cachedata.date = 0;
- cachedata.expires = 0;
- cachedata.age = INVALID_AGE;
- cachedata.max_age = 0;
- cachedata.no_cache = true;
- cachedata.etag = NULL;
- cachedata.last_modified = 0;
-
/* Iterate over ring, processing each pending fetch */
c = ring;
do {
@@ -265,6 +253,8 @@ static void fetch_data_poll(const char *scheme)
/* Only process non-aborted fetches */
if (!c->aborted && fetch_data_process(c) == true) {
+ char header[64];
+
fetch_set_http_code(c->parent_fetch, 200);
LOG(("setting data: MIME type to %s, length to %zd",
c->mimetype, c->datalen));
@@ -272,9 +262,16 @@ static void fetch_data_poll(const char *scheme)
* Therefore, we _must_ check for this after _every_
* call to fetch_data_send_callback().
*/
- fetch_data_send_callback(FETCH_TYPE,
- c, c->mimetype, c->datalen,
- FETCH_ERROR_NO_ERROR);
+ snprintf(header, sizeof header, "Content-Type: %s",
+ c->mimetype);
+ fetch_data_send_callback(FETCH_HEADER, c, header,
+ strlen(header), FETCH_ERROR_NO_ERROR);
+
+ snprintf(header, sizeof header, "Content-Length: %zd",
+ c->datalen);
+ fetch_data_send_callback(FETCH_HEADER, c, header,
+ strlen(header), FETCH_ERROR_NO_ERROR);
+
if (!c->aborted) {
fetch_data_send_callback(FETCH_DATA,
c, c->data, c->datalen,
@@ -282,8 +279,7 @@ static void fetch_data_poll(const char *scheme)
}
if (!c->aborted) {
fetch_data_send_callback(FETCH_FINISHED,
- c, &cachedata, 0,
- FETCH_ERROR_NO_ERROR);
+ c, 0, 0, FETCH_ERROR_NO_ERROR);
}
} else {
LOG(("Processing of %s failed!", c->url));
diff --git a/content/hlcache.c b/content/hlcache.c
new file mode 100644
index 000000000..94d5f0036
--- /dev/null
+++ b/content/hlcache.c
@@ -0,0 +1,362 @@
+/*
+ * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * High-level resource cache (implementation)
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "content/content.h"
+#include "content/hlcache.h"
+#include "utils/log.h"
+#include "utils/url.h"
+
+typedef struct hlcache_entry hlcache_entry;
+typedef struct hlcache_retrieval_ctx hlcache_retrieval_ctx;
+
+/** High-level cache retrieval context */
+struct hlcache_retrieval_ctx {
+ llcache_handle *llcache; /**< Low-level cache handle */
+
+ hlcache_handle *handle; /**< High-level handle for object */
+
+ /* The following are only used if a child content is requested */
+ const char *charset; /**< Fallback charset, or NULL */
+ bool quirks; /**< Whether object should be quirky */
+};
+
+/** High-level cache handle */
+struct hlcache_handle {
+ hlcache_entry *entry; /**< Pointer to cache entry */
+
+ hlcache_handle_callback cb; /**< Client callback */
+ void *pw; /**< Client data */
+};
+
+/** Entry in high-level cache */
+struct hlcache_entry {
+ struct content *content; /**< Pointer to associated content */
+
+ hlcache_entry *next; /**< Next sibling */
+ hlcache_entry *prev; /**< Previous sibling */
+};
+
+/** List of cached content objects */
+static hlcache_entry *hlcache_content_list;
+
+static nserror hlcache_llcache_callback(llcache_handle *handle,
+ const llcache_event *event, void *pw);
+static nserror hlcache_find_content(hlcache_retrieval_ctx *ctx);
+static void hlcache_content_callback(struct content *c,
+ content_msg msg, union content_msg_data data, void *pw);
+
+/******************************************************************************
+ * Public API *
+ ******************************************************************************/
+
+/**
+ * Retrieve a high-level cache handle for an object
+ *
+ * \param url URL of the object to retrieve handle for
+ * \param flags Object retrieval flags
+ * \param referer Referring URL, or NULL if none
+ * \param post POST data, or NULL for a GET request
+ * \param width Available width for content
+ * \param height Available height for content
+ * \param cb Callback to handle object events
+ * \param pw Pointer to client-specific data for callback
+ * \param child Child retrieval context, or NULL for top-level content
+ * \param result Pointer to location to recieve cache handle
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \todo Is there any way to sensibly reduce the number of parameters here?
+ */
+nserror hlcache_handle_retrieve(const char *url, uint32_t flags,
+ const char *referer, llcache_post_data *post,
+ uint32_t width, uint32_t height,
+ hlcache_handle_callback cb, void *pw,
+ hlcache_child_context *child, hlcache_handle **result)
+{
+ hlcache_retrieval_ctx *ctx;
+ nserror error;
+
+ assert(cb != NULL);
+
+ ctx = calloc(1, sizeof(hlcache_retrieval_ctx));
+ if (ctx == NULL)
+ return NSERROR_NOMEM;
+
+ ctx->handle = calloc(1, sizeof(hlcache_handle));
+ if (ctx->handle == NULL) {
+ free(ctx);
+ return NSERROR_NOMEM;
+ }
+
+ if (child != NULL) {
+ /** \todo Is the charset guaranteed to exist during fetch? */
+ ctx->charset = child->charset;
+ ctx->quirks = child->quirks;
+ }
+
+ /** \todo What happens with width/height? */
+
+ ctx->handle->cb = cb;
+ ctx->handle->pw = pw;
+
+ error = llcache_handle_retrieve(url, flags, referer, post,
+ hlcache_llcache_callback, ctx,
+ &ctx->llcache);
+ if (error != NSERROR_OK) {
+ free(ctx->handle);
+ free(ctx);
+ return error;
+ }
+
+ *result = ctx->handle;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Release a high-level cache handle
+ *
+ * \param handle Handle to release
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror hlcache_handle_release(hlcache_handle *handle)
+{
+ /** \todo What if this is called during fetch? */
+
+ if (handle->entry != NULL) {
+ content_remove_user(handle->entry->content,
+ hlcache_content_callback, handle);
+ }
+
+ handle->cb = NULL;
+ handle->pw = NULL;
+
+ /** \todo Provide hlcache_poll() to perform cache maintenance */
+
+ return NSERROR_OK;
+}
+
+/**
+ * Retrieve a content object from a cache handle
+ *
+ * \param handle Cache handle to dereference
+ * \return Pointer to content object, or NULL if there is none
+ *
+ * \todo This may not be correct. Ideally, the client should never need to
+ * directly access a content object. It may, therefore, be better to provide a
+ * bunch of veneers here that take a hlcache_handle and invoke the
+ * corresponding content_ API. If there's no content object associated with the
+ * hlcache_handle (e.g. because the source data is still being fetched, so it
+ * doesn't exist yet), then these veneers would behave as a NOP. The important
+ * thing being that the client need not care about this possibility and can
+ * just call the functions with impugnity.
+ */
+struct content *hlcache_handle_get_content(const hlcache_handle *handle)
+{
+ assert(handle != NULL);
+
+ if (handle->entry != NULL)
+ return handle->entry->content;
+
+ return NULL;
+}
+
+/******************************************************************************
+ * High-level cache internals *
+ ******************************************************************************/
+
+/**
+ * Handler for low-level cache events
+ *
+ * \param handle Handle for which event is issued
+ * \param event Event data
+ * \param pw Pointer to client-specific data
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror hlcache_llcache_callback(llcache_handle *handle,
+ const llcache_event *event, void *pw)
+{
+ hlcache_retrieval_ctx *ctx = pw;
+ nserror error;
+
+ assert(ctx->llcache == handle);
+
+ switch (event->type) {
+ case LLCACHE_EVENT_HAD_HEADERS:
+ error = hlcache_find_content(ctx);
+ if (error != NSERROR_OK)
+ return error;
+ /* No longer require retrieval context */
+ free(ctx);
+ break;
+ case LLCACHE_EVENT_HAD_DATA:
+ /* fall through */
+ case LLCACHE_EVENT_DONE:
+ /* should never happen: the handler must be changed */
+ break;
+ case LLCACHE_EVENT_ERROR:
+ /** \todo handle errors */
+ break;
+ case LLCACHE_EVENT_PROGRESS:
+ break;
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Find a content for the high-level cache handle
+ *
+ * \param ctx High-level cache retrieval context
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \pre handle::state == HLCACHE_HANDLE_NEW
+ * \pre Headers must have been received for associated low-level handle
+ * \post Low-level handle is either released, or associated with new content
+ * \post High-level handle is registered with content
+ */
+nserror hlcache_find_content(hlcache_retrieval_ctx *ctx)
+{
+ hlcache_entry *entry;
+ hlcache_event event;
+
+ /* Search list of cached contents for a suitable one */
+ for (entry = hlcache_content_list; entry != NULL; entry = entry->next) {
+ const llcache_handle *entry_llcache;
+
+ /** \todo Need to ensure that quirks mode matches */
+ /** \todo Need to ensure that content is shareable */
+ /** \todo Need to ensure that content can be reused */
+ if (entry->content == NULL)
+ continue;
+
+ /* Ensure that content uses same low-level object as
+ * low-level handle */
+ entry_llcache = content_get_llcache_handle(entry->content);
+
+ if (llcache_handle_references_same_object(entry_llcache,
+ ctx->llcache))
+ break;
+ }
+
+ if (entry == NULL) {
+ /* No existing entry, so need to create one */
+ entry = malloc(sizeof(hlcache_entry));
+ if (entry == NULL)
+ return NSERROR_NOMEM;
+
+ /* Create content using llhandle */
+ entry->content = content_create(ctx->llcache,
+ ctx->charset, ctx->quirks);
+ if (entry->content == NULL) {
+ free(entry);
+ return NSERROR_NOMEM;
+ }
+
+ /* Insert into cache */
+ entry->prev = NULL;
+ entry->next = hlcache_content_list;
+ if (hlcache_content_list != NULL)
+ hlcache_content_list->prev = entry;
+ hlcache_content_list = entry;
+ } else {
+ /* Found a suitable content: no longer need low-level handle */
+ llcache_handle_release(ctx->llcache);
+ }
+
+ /* Associate handle with content */
+ if (content_add_user(entry->content,
+ hlcache_content_callback, ctx->handle) == false)
+ return NSERROR_NOMEM;
+
+ /* Associate cache entry with handle */
+ ctx->handle->entry = entry;
+
+ /* Catch handle up with state of content */
+ if (ctx->handle->cb != NULL) {
+ content_status status = content_get_status(ctx->handle);
+
+ if (status == CONTENT_STATUS_LOADING) {
+ event.type = CONTENT_MSG_LOADING;
+ ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
+ } else if (status == CONTENT_STATUS_READY) {
+ event.type = CONTENT_MSG_LOADING;
+ ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
+
+ if (ctx->handle->cb != NULL) {
+ event.type = CONTENT_MSG_READY;
+ ctx->handle->cb(ctx->handle, &event,
+ ctx->handle->pw);
+ }
+ } else if (status == CONTENT_STATUS_DONE) {
+ event.type = CONTENT_MSG_LOADING;
+ ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
+
+ /** \todo Reflow content to new width
+ if (content_get_available_width(ctx->handle) != width)
+ content_reformat(ctx->handle, width, height);
+ */
+
+ if (ctx->handle->cb != NULL) {
+ event.type = CONTENT_MSG_READY;
+ ctx->handle->cb(ctx->handle, &event,
+ ctx->handle->pw);
+ }
+
+ if (ctx->handle->cb != NULL) {
+ event.type = CONTENT_MSG_DONE;
+ ctx->handle->cb(ctx->handle, &event,
+ ctx->handle->pw);
+ }
+ }
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Veneer between content callback API and hlcache callback API
+ *
+ * \param c Content to emit message for
+ * \param msg Message to emit
+ * \param data Data for message
+ * \param pw Pointer to private data (hlcache_handle)
+ */
+void hlcache_content_callback(struct content *c, content_msg msg,
+ union content_msg_data data, void *pw)
+{
+ hlcache_handle *handle = pw;
+ hlcache_event event;
+ nserror error;
+
+ event.type = msg;
+ event.data = data;
+
+
+ error = handle->cb(handle, &event, handle->pw);
+ if (error != NSERROR_OK)
+ LOG(("Error in callback: %d", error));
+}
+
diff --git a/content/hlcache.h b/content/hlcache.h
new file mode 100644
index 000000000..fb6ba219c
--- /dev/null
+++ b/content/hlcache.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * High-level resource cache (interface)
+ */
+
+#ifndef NETSURF_CONTENT_HLCACHE_H_
+#define NETSURF_CONTENT_HLCACHE_H_
+
+#include "content/content.h"
+#include "content/llcache.h"
+#include "utils/errors.h"
+
+/** High-level cache handle */
+typedef struct hlcache_handle hlcache_handle;
+
+/** Context for retrieving a child object */
+typedef struct hlcache_child_context {
+ const char *charset; /**< Charset of parent */
+ bool quirks; /**< Whether parent is quirky */
+} hlcache_child_context;
+
+/** High-level cache event */
+typedef struct {
+ content_msg type; /**< Event type */
+ union content_msg_data data; /**< Event data */
+} hlcache_event;
+
+/**
+ * Client callback for high-level cache events
+ *
+ * \param handle Handle to object generating event
+ * \param event Event data
+ * \param pw Pointer to client-specific data
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+typedef nserror (*hlcache_handle_callback)(hlcache_handle *handle,
+ const hlcache_event *event, void *pw);
+
+/**
+ * Retrieve a high-level cache handle for an object
+ *
+ * \param url URL of the object to retrieve handle for
+ * \param flags Object retrieval flags
+ * \param referer Referring URL, or NULL if none
+ * \param post POST data, or NULL for a GET request
+ * \param width Available width for content
+ * \param height Available height for content
+ * \param cb Callback to handle object events
+ * \param pw Pointer to client-specific data for callback
+ * \param child Child retrieval context, or NULL for top-level content
+ * \param result Pointer to location to recieve cache handle
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * Child contents are keyed on the tuple < URL, quirks >.
+ * The quirks field is ignored for child contents whose behaviour is not
+ * affected by quirks mode.
+ *
+ * \todo The above rules should be encoded in the handler_map.
+ *
+ * \todo Is there any way to sensibly reduce the number of parameters here?
+ */
+nserror hlcache_handle_retrieve(const char *url, uint32_t flags,
+ const char *referer, llcache_post_data *post,
+ uint32_t width, uint32_t height,
+ hlcache_handle_callback cb, void *pw,
+ hlcache_child_context *child, hlcache_handle **result);
+
+/**
+ * Release a high-level cache handle
+ *
+ * \param handle Handle to release
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror hlcache_handle_release(hlcache_handle *handle);
+
+/**
+ * Retrieve a content object from a cache handle
+ *
+ * \param handle Cache handle to dereference
+ * \return Pointer to content object, or NULL if there is none
+ *
+ * \todo This may not be correct. Ideally, the client should never need to
+ * directly access a content object. It may, therefore, be better to provide a
+ * bunch of veneers here that take a hlcache_handle and invoke the
+ * corresponding content_ API. If there's no content object associated with the
+ * hlcache_handle (e.g. because the source data is still being fetched, so it
+ * doesn't exist yet), then these veneers would behave as a NOP. The important
+ * thing being that the client need not care about this possibility and can
+ * just call the functions with impugnity.
+ */
+struct content *hlcache_handle_get_content(const hlcache_handle *handle);
+
+#endif
diff --git a/content/llcache.c b/content/llcache.c
new file mode 100644
index 000000000..4d2a7f0b5
--- /dev/null
+++ b/content/llcache.c
@@ -0,0 +1,1815 @@
+/*
+ * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * Low-level resource cache (implementation)
+ */
+
+#define _GNU_SOURCE /* For strndup. Ugh. */
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <curl/curl.h>
+
+#include "content/fetch.h"
+#include "content/llcache.h"
+#include "utils/messages.h"
+#include "utils/url.h"
+#include "utils/utils.h"
+
+/** State of a low-level cache object fetch */
+typedef enum {
+ LLCACHE_FETCH_INIT, /**< Initial state, before fetch */
+ LLCACHE_FETCH_HEADERS, /**< Fetching headers */
+ LLCACHE_FETCH_DATA, /**< Fetching object data */
+ LLCACHE_FETCH_COMPLETE /**< Fetch completed */
+} llcache_fetch_state;
+
+/** Type of low-level cache object */
+typedef struct llcache_object llcache_object;
+
+/** Handle to low-level cache object */
+struct llcache_handle {
+ llcache_object *object; /**< Pointer to associated object */
+
+ llcache_handle_callback cb; /**< Client callback */
+ void *pw; /**< Client data */
+
+ llcache_fetch_state state; /**< Last known state of object fetch */
+ size_t bytes; /**< Last reported byte count */
+};
+
+/** Low-level cache object user record */
+typedef struct llcache_object_user {
+ /* Must be first in struct */
+ llcache_handle handle; /**< Handle data for client */
+
+ bool iterator_target; /**< This is the an iterator target */
+ bool queued_for_delete; /**< This user is queued for deletion */
+
+ struct llcache_object_user *prev; /**< Previous in list */
+ struct llcache_object_user *next; /**< Next in list */
+} llcache_object_user;
+
+/** Low-level cache object fetch context */
+typedef struct {
+ uint32_t flags; /**< Fetch flags */
+ char *referer; /**< Referring URL, or NULL if none */
+ llcache_post_data *post; /**< POST data, or NULL for GET */
+
+ struct fetch *fetch; /**< Fetch handle for this object */
+
+ llcache_fetch_state state; /**< Current state of object fetch */
+} llcache_fetch_ctx;
+
+/** Cache control data */
+typedef struct {
+ 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 */
+ time_t last_modified; /**< Last-Modified: response header */
+} llcache_cache_control;
+
+/** Representation of a fetch header */
+typedef struct {
+ char *name; /**< Header name */
+ char *value; /**< Header value */
+} llcache_header;
+
+/** Low-level cache object */
+/** \todo Consider whether a list is a sane container */
+struct llcache_object {
+ llcache_object *prev; /**< Previous in list */
+ llcache_object *next; /**< Next in list */
+
+ char *url; /**< Post-redirect URL for object */
+
+ /** \todo We need a generic dynamic buffer object */
+ uint8_t *source_data; /**< Source data for object */
+ size_t source_len; /**< Byte length of source data */
+ size_t source_alloc; /**< Allocated size of source buffer */
+
+ llcache_object_user *users; /**< List of users */
+
+ llcache_fetch_ctx fetch; /**< Fetch context for object */
+
+ llcache_cache_control cache; /**< Cache control data for object */
+ llcache_object *candidate; /**< Object to use, if fetch determines
+ * that it is still fresh */
+ uint32_t candidate_count; /**< Count of objects this is a
+ * candidate for */
+
+ llcache_header *headers; /**< Fetch headers */
+ size_t num_headers; /**< Number of fetch headers */
+};
+
+/** Handler for fetch-related queries */
+static llcache_query_callback query_cb;
+/** Data for fetch-related query handler */
+static void *query_cb_pw;
+
+/** Head of the low-level cached object list */
+static llcache_object *llcache_cached_objects;
+/** Head of the low-level uncached object list */
+static llcache_object *llcache_uncached_objects;
+
+static nserror llcache_object_user_new(llcache_handle_callback cb, void *pw,
+ llcache_object_user **user);
+static nserror llcache_object_user_destroy(llcache_object_user *user);
+
+static nserror llcache_object_retrieve(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_object **result);
+static nserror llcache_object_retrieve_from_cache(const char *url,
+ uint32_t flags, const char *referer,
+ const llcache_post_data *post, llcache_object **result);
+static bool llcache_object_is_fresh(const llcache_object *object);
+static nserror llcache_object_cache_update(llcache_object *object);
+static nserror llcache_object_clone_cache_data(const llcache_object *source,
+ llcache_object *destination, bool deep);
+static nserror llcache_object_fetch(llcache_object *object, uint32_t flags,
+ const char *referer, const llcache_post_data *post);
+static nserror llcache_object_refetch(llcache_object *object);
+
+static nserror llcache_object_new(const char *url, llcache_object **result);
+static nserror llcache_object_destroy(llcache_object *object);
+static nserror llcache_object_add_user(llcache_object *object,
+ llcache_object_user *user);
+static nserror llcache_object_remove_user(llcache_object *object,
+ llcache_object_user *user);
+
+static nserror llcache_object_add_to_list(llcache_object *object,
+ llcache_object **list);
+static nserror llcache_object_remove_from_list(llcache_object *object,
+ llcache_object **list);
+
+static nserror llcache_object_notify_users(llcache_object *object);
+
+static nserror llcache_clean(void);
+
+static nserror llcache_post_data_clone(const llcache_post_data *orig,
+ llcache_post_data **clone);
+
+static nserror llcache_query_handle_response(bool proceed, void *cbpw);
+
+static void llcache_fetch_callback(fetch_msg msg, void *p, const void *data,
+ unsigned long size, fetch_error_code errorcode);
+static nserror llcache_fetch_redirect(llcache_object *object,
+ const char *target, llcache_object **replacement);
+static nserror llcache_fetch_notmodified(llcache_object *object,
+ llcache_object **replacement);
+static nserror llcache_fetch_split_header(const char *data, size_t len,
+ char **name, char **value);
+static nserror llcache_fetch_parse_header(llcache_object *object,
+ const char *data, size_t len, char **name, char **value);
+static nserror llcache_fetch_process_header(llcache_object *object,
+ const char *data, size_t len);
+static nserror llcache_fetch_process_data(llcache_object *object,
+ const uint8_t *data, size_t len);
+static nserror llcache_fetch_auth(llcache_object *object,
+ const char *realm);
+static nserror llcache_fetch_cert_error(llcache_object *object,
+ const struct ssl_cert_info *certs, size_t num);
+
+
+/******************************************************************************
+ * Public API *
+ ******************************************************************************/
+
+/**
+ * Initialise the low-level cache
+ *
+ * \param cb Query handler
+ * \param pw Pointer to query handler data
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_initialise(llcache_query_callback cb, void *pw)
+{
+ query_cb = cb;
+ query_cb_pw = pw;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Poll the low-level cache
+ *
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_poll(void)
+{
+ llcache_object *object;
+
+ /* Catch new users up with state of objects */
+ for (object = llcache_cached_objects; object != NULL;
+ object = object->next) {
+ llcache_object_notify_users(object);
+ }
+
+ for (object = llcache_uncached_objects; object != NULL;
+ object = object->next) {
+ llcache_object_notify_users(object);
+ }
+
+ /* Attempt to clean the cache */
+ llcache_clean();
+
+ return NSERROR_OK;
+}
+
+/**
+ * Retrieve a handle for a low-level cache object
+ *
+ * \param url URL of the object to fetch
+ * \param flags Object retrieval flags
+ * \param referer Referring URL, or NULL if none
+ * \param post POST data, or NULL for a GET request
+ * \param cb Client callback for events
+ * \param pw Pointer to client-specific data
+ * \param result Pointer to location to recieve cache handle
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_retrieve(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_handle_callback cb, void *pw,
+ llcache_handle **result)
+{
+ nserror error;
+ llcache_object_user *user;
+ llcache_object *object;
+
+ /* Can we fetch this URL at all? */
+ if (fetch_can_fetch(url) == false)
+ return NSERROR_NO_FETCH_HANDLER;
+
+ /* Create a new object user */
+ error = llcache_object_user_new(cb, pw, &user);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Retrieve a suitable object from the cache,
+ * creating a new one if needed. */
+ error = llcache_object_retrieve(url, flags, referer, post, &object);
+ if (error != NSERROR_OK) {
+ llcache_object_user_destroy(user);
+ return error;
+ }
+
+ /* Add user to object */
+ llcache_object_add_user(object, user);
+
+ *result = &user->handle;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Change the callback associated with a low-level cache handle
+ *
+ * \param handle Handle to change callback of
+ * \param cb New callback
+ * \param pw Client data for new callback
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_change_callback(llcache_handle *handle,
+ llcache_handle_callback cb, void *pw)
+{
+ handle->cb = cb;
+ handle->pw = pw;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Release a low-level cache handle
+ *
+ * \param handle Handle to release
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_release(llcache_handle *handle)
+{
+ nserror error = NSERROR_OK;
+ llcache_object *object = handle->object;
+ llcache_object_user *user = (llcache_object_user *) handle;
+
+ /* Remove the user from the object and destroy it */
+ error = llcache_object_remove_user(object, user);
+ if (error == NSERROR_OK) {
+ /* Can't delete user object if it's the target of an iterator */
+ if (user->iterator_target)
+ user->queued_for_delete = true;
+ else
+ error = llcache_object_user_destroy(user);
+ }
+
+ return error;
+}
+
+/**
+ * Retrieve the post-redirect URL of a low-level cache object
+ *
+ * \param handle Handle to retrieve URL from
+ * \return Post-redirect URL of cache object
+ */
+const char *llcache_handle_get_url(const llcache_handle *handle)
+{
+ return handle->object != NULL ? handle->object->url : NULL;
+}
+
+/**
+ * Retrieve source data of a low-level cache object
+ *
+ * \param handle Handle to retrieve source data from
+ * \param size Pointer to location to receive byte length of data
+ * \return Pointer to source data
+ */
+const uint8_t *llcache_handle_get_source_data(const llcache_handle *handle,
+ size_t *size)
+{
+ *size = handle->object != NULL ? handle->object->source_len : 0;
+
+ return handle->object != NULL ? handle->object->source_data : NULL;
+}
+
+/**
+ * Retrieve a header value associated with a low-level cache object
+ *
+ * \param handle Handle to retrieve header from
+ * \param key Header name
+ * \return Header value, or NULL if header does not exist
+ *
+ * \todo Make the key an enumeration, to avoid needless string comparisons
+ * \todo Forcing the client to parse the header value seems wrong.
+ * Better would be to return the actual value part and an array of
+ * key-value pairs for any additional parameters.
+ */
+const char *llcache_handle_get_header(const llcache_handle *handle,
+ const char *key)
+{
+ const llcache_object *object = handle->object;
+ size_t i;
+
+ if (object == NULL)
+ return NULL;
+
+ /* About as trivial as possible */
+ for (i = 0; i < object->num_headers; i++) {
+ if (strcasecmp(key, object->headers[i].name) == 0)
+ return object->headers[i].value;
+ }
+
+ return NULL;
+}
+
+/**
+ * Determine if the same underlying object is referenced by the given handles
+ *
+ * \param a First handle
+ * \param b Second handle
+ * \return True if handles reference the same object, false otherwise
+ */
+bool llcache_handle_references_same_object(const llcache_handle *a,
+ const llcache_handle *b)
+{
+ return a->object == b->object;
+}
+
+/******************************************************************************
+ * Low-level cache internals *
+ ******************************************************************************/
+
+/**
+ * Create a new object user
+ *
+ * \param cb Callback routine
+ * \param pw Private data for callback
+ * \param user Pointer to location to receive result
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_user_new(llcache_handle_callback cb, void *pw,
+ llcache_object_user **user)
+{
+ llcache_object_user *u = calloc(1, sizeof(llcache_object_user));
+ if (u == NULL)
+ return NSERROR_NOMEM;
+
+ u->handle.cb = cb;
+ u->handle.pw = pw;
+
+ *user = u;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Destroy an object user
+ *
+ * \param user User to destroy
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \pre User is not attached to an object
+ */
+nserror llcache_object_user_destroy(llcache_object_user *user)
+{
+ free(user);
+
+ return NSERROR_OK;
+}
+
+/**
+ * Retrieve an object from the cache, fetching it if necessary.
+ *
+ * \param url URL of object to retrieve
+ * \param flags Fetch flags
+ * \param referer Referring URL, or NULL if none
+ * \param post POST data, or NULL for a GET request
+ * \param result Pointer to location to recieve retrieved object
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_retrieve(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_object **result)
+{
+ nserror error;
+ llcache_object *obj;
+ bool has_query;
+ url_func_result res;
+ struct url_components components;
+
+ /**
+ * Caching Rules:
+ *
+ * 1) Forced fetches are never cached
+ * 2) GET requests with query segments are never cached
+ * 3) POST requests are never cached
+ *
+ * \todo Find out if restriction (2) can be removed
+ */
+
+ /* Look for a query segment */
+ res = url_get_components(url, &components);
+ if (res == URL_FUNC_NOMEM)
+ return NSERROR_NOMEM;
+
+ has_query = (components.query != NULL);
+
+ url_destroy_components(&components);
+
+ if (flags & LLCACHE_RETRIEVE_FORCE_FETCH || has_query || post != NULL) {
+ /* Create new object */
+ error = llcache_object_new(url, &obj);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Attempt to kick-off fetch */
+ error = llcache_object_fetch(obj, flags, referer, post);
+ if (error != NSERROR_OK) {
+ llcache_object_destroy(obj);
+ return error;
+ }
+
+ /* Add new object to uncached list */
+ llcache_object_add_to_list(obj, &llcache_uncached_objects);
+ } else {
+ error = llcache_object_retrieve_from_cache(url, flags, referer,
+ post, &obj);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Returned object is already in the cached list */
+ }
+
+ *result = obj;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Retrieve a potentially cached object
+ *
+ * \param url URL of object to retrieve
+ * \param flags Fetch flags
+ * \param referer Referring URL, or NULL if none
+ * \param post POST data, or NULL for a GET request
+ * \param result Pointer to location to recieve retrieved object
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_retrieve_from_cache(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_object **result)
+{
+ nserror error;
+ llcache_object *obj, *newest = NULL;
+
+ /* Search for the most recently fetched matching object */
+ for (obj = llcache_cached_objects; obj != NULL; obj = obj->next) {
+ if (strcasecmp(obj->url, url) == 0 && (newest == NULL ||
+ obj->cache.req_time > newest->cache.req_time))
+ newest = obj;
+ }
+
+ if (newest != NULL && llcache_object_is_fresh(newest)) {
+ /* Found a suitable object, and it's still fresh, so use it */
+ obj = newest;
+
+ /* The client needs to catch up with the object's state.
+ * This will occur the next time that llcache_poll is called.
+ */
+ } else if (newest != NULL) {
+ /* Found a candidate object but it needs freshness validation */
+ /* Create a new object */
+ error = llcache_object_new(url, &obj);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Clone candidate's cache data */
+ error = llcache_object_clone_cache_data(newest, obj, true);
+ if (error != NSERROR_OK) {
+ llcache_object_destroy(obj);
+ return error;
+ }
+
+ /* Record candidate, so we can fall back if it is still fresh */
+ newest->candidate_count++;
+ obj->candidate = newest;
+
+ /* Attempt to kick-off fetch */
+ error = llcache_object_fetch(obj, flags, referer, post);
+ if (error != NSERROR_OK) {
+ newest->candidate_count--;
+ llcache_object_destroy(obj);
+ return error;
+ }
+
+ /* Add new object to cache */
+ llcache_object_add_to_list(obj, &llcache_cached_objects);
+ } else {
+ /* No object found; create a new one */
+ /* Create new object */
+ error = llcache_object_new(url, &obj);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Attempt to kick-off fetch */
+ error = llcache_object_fetch(obj, flags, referer, post);
+ if (error != NSERROR_OK) {
+ llcache_object_destroy(obj);
+ return error;
+ }
+
+ /* Add new object to cache */
+ llcache_object_add_to_list(obj, &llcache_cached_objects);
+ }
+
+ *result = obj;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Determine if an object is still fresh
+ *
+ * \param object Object to consider
+ * \return True if object is still fresh, false otherwise
+ */
+bool llcache_object_is_fresh(const llcache_object *object)
+{
+ const llcache_cache_control *cd = &object->cache;
+ int current_age, freshness_lifetime;
+ time_t now = time(NULL);
+
+ /* Calculate staleness of cached object 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 + now - cd->res_time;
+
+ /* Determine freshness lifetime of this object */
+ if (cd->max_age != INVALID_AGE)
+ freshness_lifetime = cd->max_age;
+ else if (cd->expires != 0)
+ freshness_lifetime = cd->expires - cd->date;
+ else if (cd->last_modified != 0)
+ freshness_lifetime = (now - cd->last_modified) / 10;
+ else
+ freshness_lifetime = 0;
+
+ /* The object is fresh if its current age is within the freshness
+ * lifetime or if we're still fetching the object */
+ return (freshness_lifetime > current_age ||
+ object->fetch.state != LLCACHE_FETCH_COMPLETE);
+}
+
+/**
+ * Update an object's cache state
+ *
+ * \param object Object to update cache for
+ * \return NSERROR_OK.
+ */
+nserror llcache_object_cache_update(llcache_object *object)
+{
+ if (object->cache.date == 0)
+ object->cache.date = time(NULL);
+
+ /** \todo Any magic we need to do for no_cache? */
+
+ return NSERROR_OK;
+}
+
+/**
+ * Clone an object's cache data
+ *
+ * \param source Source object containing cache data to clone
+ * \param destination Destination object to clone cache data into
+ * \param deep Whether to deep-copy the data or not
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_clone_cache_data(const llcache_object *source,
+ llcache_object *destination, bool deep)
+{
+ /* ETag must be first, as it can fail when deep cloning */
+ if (source->cache.etag != NULL) {
+ char *etag = source->cache.etag;
+
+ if (deep) {
+ /* Copy the etag */
+ etag = strdup(source->cache.etag);
+ if (etag == NULL)
+ return NSERROR_NOMEM;
+ }
+
+ if (destination->cache.etag != NULL)
+ free(destination->cache.etag);
+
+ destination->cache.etag = etag;
+ }
+
+ destination->cache.req_time = source->cache.req_time;
+ destination->cache.res_time = source->cache.res_time;
+
+ if (source->cache.date != 0)
+ destination->cache.date = source->cache.date;
+
+ if (source->cache.expires != 0)
+ destination->cache.expires = source->cache.expires;
+
+ if (source->cache.age != INVALID_AGE)
+ destination->cache.age = source->cache.age;
+
+ if (source->cache.max_age != INVALID_AGE)
+ destination->cache.max_age = source->cache.max_age;
+
+ if (source->cache.no_cache)
+ destination->cache.no_cache = source->cache.no_cache;
+
+ if (source->cache.last_modified != 0)
+ destination->cache.last_modified = source->cache.last_modified;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Kick-off a fetch for an object
+ *
+ * \param object Object to fetch
+ * \param flags Fetch flags
+ * \param referer Referring URL, or NULL for none
+ * \param post POST data, or NULL for GET
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \pre object::url must contain the URL to fetch
+ * \pre If there is a freshness validation candidate,
+ * object::candidate and object::cache must be filled in
+ * \pre There must not be a fetch in progress for \a object
+ */
+nserror llcache_object_fetch(llcache_object *object, uint32_t flags,
+ const char *referer, const llcache_post_data *post)
+{
+ nserror error;
+ char *referer_clone = NULL;
+ llcache_post_data *post_clone = NULL;
+
+ if (referer != NULL) {
+ referer_clone = strdup(referer);
+ if (referer_clone == NULL)
+ return NSERROR_NOMEM;
+ }
+
+ if (post != NULL) {
+ error = llcache_post_data_clone(post, &post_clone);
+ if (error != NSERROR_OK) {
+ free(referer_clone);
+ return error;
+ }
+ }
+
+ object->fetch.flags = flags;
+ object->fetch.referer = referer_clone;
+ object->fetch.post = post_clone;
+
+ return llcache_object_refetch(object);
+}
+
+/**
+ * (Re)fetch an object
+ *
+ * \param object Object to refetch
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \pre The fetch parameters in object->fetch must be populated
+ */
+nserror llcache_object_refetch(llcache_object *object)
+{
+ const char *urlenc = NULL;
+ /** \todo Why is fetch_start's post_multipart parameter not const? */
+ struct fetch_multipart_data *multipart = NULL;
+ /** \todo Why is the headers parameter of fetch_start not const? */
+ char **headers = NULL;
+ int header_idx = 0;
+
+ if (object->fetch.post != NULL) {
+ if (object->fetch.post->type == LLCACHE_POST_URL_ENCODED)
+ urlenc = object->fetch.post->data.urlenc;
+ else
+ multipart = object->fetch.post->data.multipart;
+ }
+
+ /* Generate cache-control headers */
+ headers = malloc(3 * sizeof(char *));
+ if (headers == NULL)
+ return NSERROR_NOMEM;
+
+ if (object->cache.etag != NULL) {
+ const size_t len = SLEN("If-None-Match: ") +
+ strlen(object->cache.etag) + 1;
+
+ headers[header_idx] = malloc(len);
+ if (headers[header_idx] == NULL) {
+ free(headers);
+ return NSERROR_NOMEM;
+ }
+
+ snprintf(headers[header_idx], len, "If-None-Match: %s",
+ object->cache.etag);
+
+ header_idx++;
+ }
+ if (object->cache.date != 0) {
+ /* Maximum length of an RFC 1123 date is 29 bytes */
+ const size_t len = SLEN("If-Modified-Since: ") + 29 + 1;
+
+ headers[header_idx] = malloc(len);
+ if (headers[header_idx] == NULL) {
+ while (--header_idx >= 0)
+ free(headers[header_idx]);
+ free(headers);
+ return NSERROR_NOMEM;
+ }
+
+ snprintf(headers[header_idx], len, "If-Modified-Since: %s",
+ rfc1123_date(object->cache.date));
+
+ header_idx++;
+ }
+ headers[header_idx] = NULL;
+
+ /* Reset cache control data */
+ object->cache.req_time = time(NULL);
+ object->cache.res_time = 0;
+ object->cache.date = 0;
+ object->cache.expires = 0;
+ object->cache.age = INVALID_AGE;
+ object->cache.max_age = INVALID_AGE;
+ object->cache.no_cache = false;
+ free(object->cache.etag);
+ object->cache.etag = NULL;
+ object->cache.last_modified = 0;
+
+ /* Kick off fetch */
+ object->fetch.fetch = fetch_start(object->url, object->fetch.referer,
+ llcache_fetch_callback, object,
+ object->fetch.flags & LLCACHE_RETRIEVE_NO_ERROR_PAGES,
+ urlenc, multipart,
+ object->fetch.flags & LLCACHE_RETRIEVE_VERIFIABLE,
+ NULL, /** \todo Remove parent from this API */
+ headers);
+
+ /* Clean up cache-control headers */
+ while (--header_idx >= 0)
+ free(headers[header_idx]);
+ free(headers);
+
+ /* Did we succeed in creating a fetch? */
+ if (object->fetch.fetch == NULL)
+ return NSERROR_NOMEM;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Create a new low-level cache object
+ *
+ * \param url URL of object to create
+ * \param result Pointer to location to receive result
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_new(const char *url, llcache_object **result)
+{
+ llcache_object *obj = calloc(1, sizeof(llcache_object));
+ if (obj == NULL)
+ return NSERROR_NOMEM;
+
+ obj->url = strdup(url);
+ if (obj->url == NULL) {
+ free(obj);
+ return NSERROR_NOMEM;
+ }
+
+ *result = obj;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Destroy a low-level cache object
+ *
+ * \param object Object to destroy
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \pre Object is detached from cache list
+ * \pre Object has no users
+ * \pre Object is not a candidate (i.e. object::candidate_count == 0)
+ */
+nserror llcache_object_destroy(llcache_object *object)
+{
+ size_t i;
+
+ free(object->url);
+ free(object->source_data);
+
+ if (object->fetch.fetch != NULL) {
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ }
+
+ free(object->fetch.referer);
+
+ if (object->fetch.post != NULL) {
+ if (object->fetch.post->type == LLCACHE_POST_URL_ENCODED) {
+ free(object->fetch.post->data.urlenc);
+ } else {
+ fetch_multipart_data_destroy(
+ object->fetch.post->data.multipart);
+ }
+
+ free(object->fetch.post);
+ }
+
+ free(object->cache.etag);
+
+ for (i = 0; i < object->num_headers; i++) {
+ free(object->headers[i].name);
+ free(object->headers[i].value);
+ }
+ free(object->headers);
+
+ free(object);
+
+ return NSERROR_OK;
+}
+
+/**
+ * Add a user to a low-level cache object
+ *
+ * \param object Object to add user to
+ * \param user User to add
+ * \return NSERROR_OK.
+ */
+nserror llcache_object_add_user(llcache_object *object,
+ llcache_object_user *user)
+{
+ user->handle.object = object;
+
+ user->prev = NULL;
+ user->next = object->users;
+
+ if (object->users != NULL)
+ object->users->prev = user;
+ object->users = user;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Remove a user from a low-level cache object
+ *
+ * \param object Object to remove user from
+ * \param user User to remove
+ * \return NSERROR_OK.
+ */
+nserror llcache_object_remove_user(llcache_object *object,
+ llcache_object_user *user)
+{
+ if (user == object->users)
+ object->users = user->next;
+ else
+ user->prev->next = user->next;
+
+ if (user->next != NULL)
+ user->next->prev = user->prev;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Add a low-level cache object to a cache list
+ *
+ * \param object Object to add
+ * \param list List to add to
+ * \return NSERROR_OK
+ */
+nserror llcache_object_add_to_list(llcache_object *object,
+ llcache_object **list)
+{
+ object->prev = NULL;
+ object->next = *list;
+
+ if (*list != NULL)
+ (*list)->prev = object;
+ *list = object;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Remove a low-level cache object from a cache list
+ *
+ * \param object Object to remove
+ * \param list List to remove from
+ * \return NSERROR_OK
+ */
+nserror llcache_object_remove_from_list(llcache_object *object,
+ llcache_object **list)
+{
+ if (object == *list)
+ *list = object->next;
+ else
+ object->prev->next = object->next;
+
+ if (object->next != NULL)
+ object->next->prev = object->next;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Notify users of an object's current state
+ *
+ * \param object Object to notify users about
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_notify_users(llcache_object *object)
+{
+ nserror error;
+ llcache_object_user *user, *next_user;
+ llcache_event event;
+
+ /**
+ * State transitions and event emission for users.
+ * Rows: user state. Cols: object state.
+ *
+ * User\Obj INIT HEADERS DATA COMPLETE
+ * INIT - T T* T*
+ * HEADERS - - T T*
+ * DATA - - M T
+ * COMPLETE - - - -
+ *
+ * T => transition user to object state
+ * M => no transition required, but may need to emit event
+ *
+ * The transitions marked with an asterisk can be removed by moving
+ * the user context into the subsequent state and then reevaluating.
+ *
+ * Events are issued as follows:
+ *
+ * HAD_HEADERS: on transition from HEADERS -> DATA state
+ * HAD_DATA : in DATA state, whenever there's new source data
+ * DONE : on transition from DATA -> COMPLETE state
+ */
+
+ for (user = object->users; user != NULL; user = next_user) {
+ /* Emit necessary events to bring the user up-to-date */
+ llcache_handle *handle = &user->handle;
+ llcache_fetch_state hstate = handle->state;
+ llcache_fetch_state objstate = object->fetch.state;
+
+ /* Save identity of next user in case client destroys
+ * the user underneath us */
+ user->iterator_target = true;
+ next_user = user->next;
+
+ /* User: INIT, Obj: HEADERS, DATA, COMPLETE => User->HEADERS */
+ if (hstate == LLCACHE_FETCH_INIT &&
+ objstate > LLCACHE_FETCH_INIT) {
+ hstate = LLCACHE_FETCH_HEADERS;
+ }
+
+ /* User: HEADERS, Obj: DATA, COMPLETE => User->DATA */
+ if (hstate == LLCACHE_FETCH_HEADERS &&
+ objstate > LLCACHE_FETCH_HEADERS) {
+ /* Emit HAD_HEADERS event */
+ event.type = LLCACHE_EVENT_HAD_HEADERS;
+
+ error = handle->cb(handle, &event, handle->pw);
+ if (error != NSERROR_OK) {
+ user->iterator_target = false;
+ return error;
+ }
+
+ if (user->queued_for_delete) {
+ llcache_object_user_destroy(user);
+ continue;
+ }
+
+ hstate = LLCACHE_FETCH_DATA;
+ }
+
+ /* User: DATA, Obj: DATA, COMPLETE, more source available */
+ if (hstate == LLCACHE_FETCH_DATA &&
+ objstate >= LLCACHE_FETCH_DATA &&
+ object->source_len > handle->bytes) {
+ /* Emit HAD_DATA event */
+ event.type = LLCACHE_EVENT_HAD_DATA;
+ event.data.data.buf =
+ object->source_data + handle->bytes;
+ event.data.data.len =
+ object->source_len - handle->bytes;
+
+ error = handle->cb(handle, &event, handle->pw);
+ if (error != NSERROR_OK) {
+ user->iterator_target = false;
+ return error;
+ }
+
+ if (user->queued_for_delete) {
+ llcache_object_user_destroy(user);
+ continue;
+ }
+
+ /* Update record of last byte emitted */
+ handle->bytes = object->source_len;
+ }
+
+ /* User: DATA, Obj: COMPLETE => User->COMPLETE */
+ if (hstate == LLCACHE_FETCH_DATA &&
+ objstate > LLCACHE_FETCH_DATA) {
+ /* Emit DONE event */
+ event.type = LLCACHE_EVENT_DONE;
+
+ error = handle->cb(handle, &event, handle->pw);
+ if (error != NSERROR_OK) {
+ user->iterator_target = false;
+ return error;
+ }
+
+ if (user->queued_for_delete) {
+ llcache_object_user_destroy(user);
+ continue;
+ }
+
+ hstate = LLCACHE_FETCH_COMPLETE;
+ }
+
+ /* No longer the target of an iterator */
+ user->iterator_target = false;
+
+ /* Sync handle's state with reality */
+ handle->state = hstate;
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Attempt to clean the cache
+ *
+ * \return NSERROR_OK.
+ */
+nserror llcache_clean(void)
+{
+ llcache_object *object, *next;
+
+ /* Candidates for cleaning are (in order of priority):
+ *
+ * 1) Uncacheable objects with no users
+ * 2) Stale cacheable objects with no users or pending fetches
+ * 3) Fresh cacheable objects with no users or pending fetches
+ */
+
+ /* 1) Uncacheable objects with no users */
+ for (object = llcache_uncached_objects; object != NULL; object = next) {
+ next = object->next;
+
+ /* The candidate count of uncacheable objects is always 0 */
+ if (object->users == NULL && object->candidate_count == 0) {
+ llcache_object_remove_from_list(object,
+ &llcache_uncached_objects);
+ llcache_object_destroy(object);
+ }
+ }
+
+ /* 2) Stale cacheable objects with no users or pending fetches */
+ for (object = llcache_cached_objects; object != NULL; object = next) {
+ next = object->next;
+
+ if (object->users == NULL && object->candidate_count == 0 &&
+ llcache_object_is_fresh(object) == false) {
+ llcache_object_remove_from_list(object,
+ &llcache_cached_objects);
+ llcache_object_destroy(object);
+ }
+ }
+
+ /* 3) Fresh cacheable objects with no users or pending fetches */
+ /** \todo This one only happens if the cache is too large */
+
+ return NSERROR_OK;
+}
+
+/**
+ * Clone a POST data object
+ *
+ * \param orig Object to clone
+ * \param clone Pointer to location to receive clone
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_post_data_clone(const llcache_post_data *orig,
+ llcache_post_data **clone)
+{
+ llcache_post_data *post_clone;
+
+ post_clone = calloc(1, sizeof(llcache_post_data));
+ if (post_clone == NULL)
+ return NSERROR_NOMEM;
+
+ post_clone->type = orig->type;
+
+ /* Deep-copy the type-specific data */
+ if (orig->type == LLCACHE_POST_URL_ENCODED) {
+ post_clone->data.urlenc = strdup(orig->data.urlenc);
+ if (post_clone->data.urlenc == NULL) {
+ free(post_clone);
+
+ return NSERROR_NOMEM;
+ }
+ } else {
+ post_clone->data.multipart = fetch_multipart_data_clone(
+ orig->data.multipart);
+ if (post_clone->data.multipart == NULL) {
+ free(post_clone);
+
+ return NSERROR_NOMEM;
+ }
+ }
+
+ *clone = post_clone;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Handle a query response
+ *
+ * \param proceed Whether to proceed with fetch
+ * \param cbpw Our context for query
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_query_handle_response(bool proceed, void *cbpw)
+{
+ nserror error;
+ llcache_event event;
+ llcache_object_user *user;
+ llcache_object *object = cbpw;
+
+ /* Refetch, using existing fetch parameters, if client allows us to */
+ if (proceed)
+ return llcache_object_refetch(object);
+
+ /* Inform client(s) that object fetch failed */
+ event.type = LLCACHE_EVENT_ERROR;
+ /** \todo More appropriate error message */
+ event.data.msg = messages_get("FetchFailed");
+
+ for (user = object->users; user != NULL; user = user->next) {
+ error = user->handle.cb(&user->handle, &event, user->handle.pw);
+ if (error != NSERROR_OK)
+ return error;
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Handler for fetch events
+ *
+ * \param msg Type of fetch event
+ * \param p Our private data
+ * \param data Event data
+ * \param size Length of data in bytes
+ * \param errorcode Reason for fetch error
+ */
+void llcache_fetch_callback(fetch_msg msg, void *p, const void *data,
+ unsigned long size, fetch_error_code errorcode)
+{
+ nserror error = NSERROR_OK;
+ llcache_object *object = p;
+ llcache_object_user *user;
+ llcache_event event;
+
+ switch (msg) {
+ /* 3xx responses */
+ case FETCH_REDIRECT:
+ /* Request resulted in a redirect */
+ error = llcache_fetch_redirect(object, data, &object);
+ break;
+ case FETCH_NOTMODIFIED:
+ /* Conditional request determined that cached object is fresh */
+ error = llcache_fetch_notmodified(object, &object);
+ break;
+
+ /* Normal 2xx state machine */
+ case FETCH_HEADER:
+ /* Received a fetch header */
+ object->fetch.state = LLCACHE_FETCH_HEADERS;
+
+ error = llcache_fetch_process_header(object, data, size);
+ case FETCH_TYPE:
+ /** \todo Purge FETCH_TYPE completely */
+ break;
+ case FETCH_DATA:
+ /* Received some data */
+ object->fetch.state = LLCACHE_FETCH_DATA;
+
+ error = llcache_fetch_process_data(object, data, size);
+ break;
+ case FETCH_FINISHED:
+ /* Finished fetching */
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ object->fetch.fetch = NULL;
+
+ llcache_object_cache_update(object);
+ break;
+
+ /* Out-of-band information */
+ case FETCH_ERROR:
+ /* An error occurred while fetching */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ /** \todo Ensure this object becomes stale */
+
+ /** \todo Consider using errorcode for something */
+
+ event.type = LLCACHE_EVENT_ERROR;
+ event.data.msg = data;
+
+ for (user = object->users; user != NULL; user = user->next) {
+ error = user->handle.cb(&user->handle, &event,
+ user->handle.pw);
+ if (error != NSERROR_OK)
+ break;
+ }
+ break;
+ case FETCH_PROGRESS:
+ /* Progress update */
+ event.type = LLCACHE_EVENT_PROGRESS;
+ event.data.msg = data;
+
+ for (user = object->users; user != NULL; user = user->next) {
+ error = user->handle.cb(&user->handle, &event,
+ user->handle.pw);
+ if (error != NSERROR_OK)
+ break;
+ }
+ break;
+
+ /* Events requiring action */
+ case FETCH_AUTH:
+ /* Need Authentication */
+ error = llcache_fetch_auth(object, data);
+ break;
+ case FETCH_CERT_ERR:
+ /* Something went wrong when validating TLS certificates */
+ error = llcache_fetch_cert_error(object, data, size);
+ break;
+ }
+
+ /* Deal with any errors reported by event handlers */
+ if (error != NSERROR_OK) {
+ /** \todo Error handling */
+ if (object->fetch.fetch != NULL) {
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ }
+ return;
+ }
+
+ /* Keep users in sync with reality */
+ error = llcache_object_notify_users(object);
+ if (error != NSERROR_OK) {
+ /** \todo Error handling */
+ if (object->fetch.fetch != NULL) {
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ }
+ }
+}
+
+/**
+ * Handle FETCH_REDIRECT event
+ *
+ * \param object Object being redirected
+ * \param target Target of redirect (may be relative)
+ * \param replacement Pointer to location to receive replacement object
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_redirect(llcache_object *object, const char *target,
+ llcache_object **replacement)
+{
+ nserror error;
+ llcache_object *dest;
+ llcache_object_user *user, *next;
+ const llcache_post_data *post = object->fetch.post;
+ char *url, *absurl;
+ url_func_result result;
+ /* Extract HTTP response code from the fetch object */
+ long http_code = fetch_http_code(object->fetch.fetch);
+
+ /* Abort fetch for this object */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+
+ /** \todo Limit redirect depth, or detect cycles */
+
+ /* Make target absolute */
+ result = url_join(target, object->url, &absurl);
+ if (result != URL_FUNC_OK) {
+ return NSERROR_NOMEM;
+ }
+
+ /* Ensure target is normalised */
+ result = url_normalize(absurl, &url);
+
+ /* No longer require absolute url */
+ free(absurl);
+
+ if (result != URL_FUNC_OK) {
+ return NSERROR_NOMEM;
+ }
+
+ /** \todo Ensure that redirects to file:/// don't happen? */
+
+ /** \todo What happens if we've no way of handling this URL? */
+
+ /** \todo All the magical processing for the various redirect types */
+ if (http_code == 301 || http_code == 302 || http_code == 303) {
+ /* 301, 302, 303 redirects are all unconditional GET requests */
+ post = NULL;
+ } else {
+ /** \todo 300, 305, 307 */
+ free(url);
+ return NSERROR_OK;
+ }
+
+ /* Attempt to fetch target URL */
+ error = llcache_object_retrieve(url, object->fetch.flags,
+ object->fetch.referer, object->fetch.post,
+ &dest);
+
+ /* No longer require url */
+ free(url);
+
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Move user(s) to replacement object */
+ for (user = object->users; user != NULL; user = next) {
+ next = user->next;
+
+ llcache_object_remove_user(object, user);
+ llcache_object_add_user(dest, user);
+ }
+
+ /* Dest is now our object */
+ *replacement = dest;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Handle FETCH_NOTMODIFIED event
+ *
+ * \param object Object to process
+ * \param replacement Pointer to location to receive replacement object
+ * \return NSERROR_OK.
+ */
+nserror llcache_fetch_notmodified(llcache_object *object,
+ llcache_object **replacement)
+{
+ llcache_object_user *user, *next;
+
+ /* Move user(s) to candidate content */
+ for (user = object->users; user != NULL; user = next) {
+ next = user->next;
+
+ llcache_object_remove_user(object, user);
+ llcache_object_add_user(object->candidate, user);
+ }
+
+ /* Candidate is no longer a candidate for us */
+ object->candidate->candidate_count--;
+
+ /* Clone our cache control data into the candidate */
+ llcache_object_clone_cache_data(object, object->candidate, false);
+ /* Bring candidate's cache data up to date */
+ llcache_object_cache_update(object->candidate);
+
+ /* Invalidate our cache-control data */
+ memset(&object->cache, 0, sizeof(llcache_cache_control));
+
+ /* Ensure fetch has stopped */
+ /** \todo Are there any other fields that need invalidating? */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+
+ /* Candidate is now our object */
+ *replacement = object->candidate;
+
+ /** \todo Ensure that old object gets flushed from the cache */
+
+ return NSERROR_OK;
+}
+
+/**
+ * Split a fetch header into name and value
+ *
+ * \param data Header string
+ * \param len Byte length of header
+ * \param name Pointer to location to receive header name
+ * \param value Pointer to location to receive header value
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_split_header(const char *data, size_t len, char **name,
+ char **value)
+{
+ char *n, *v;
+ const char *colon;
+
+ /* Find colon */
+ colon = strchr(data, ':');
+ if (colon == NULL) {
+ /* Failed, assume a key with no value */
+ n = strdup(data);
+ if (n == NULL)
+ return NSERROR_NOMEM;
+
+ v = strdup("");
+ if (v == NULL) {
+ free(n);
+ return NSERROR_NOMEM;
+ }
+ } else {
+ /* Split header into name & value */
+
+ /* Strip leading whitespace from name */
+ while (data[0] == ' ' || data[0] == '\t' ||
+ data[0] == '\r' || data[0] == '\n') {
+ data++;
+ }
+
+ /* Strip trailing whitespace from name */
+ while (colon > data && (colon[-1] == ' ' ||
+ colon[-1] == '\t' || colon[-1] == '\r' ||
+ colon[-1] == '\n'))
+ colon--;
+
+ n = strndup(data, colon - data);
+ if (n == NULL)
+ return NSERROR_NOMEM;
+
+ /* Find colon again */
+ while (*colon != ':') {
+ colon++;
+ }
+
+ /* Skip over colon and any subsequent whitespace */
+ do {
+ colon++;
+ } while (*colon == ' ' || *colon == '\t' ||
+ *colon == '\r' || *colon == '\n');
+
+ /* Strip trailing whitespace from value */
+ while (len > 0 && (data[len - 1] == ' ' ||
+ data[len - 1] == '\t' ||
+ data[len - 1] == '\r' ||
+ data[len - 1] == '\n')) {
+ len--;
+ }
+
+ v = strndup(colon, len - (colon - data));
+ if (v == NULL) {
+ free(n);
+ return NSERROR_NOMEM;
+ }
+ }
+
+ *name = n;
+ *value = v;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Parse a fetch header
+ *
+ * \param object Object to parse header for
+ * \param data Header string
+ * \param len Byte length of header
+ * \param name Pointer to location to receive header name
+ * \param value Pointer to location to receive header value
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_parse_header(llcache_object *object, const char *data,
+ size_t len, char **name, char **value)
+{
+ nserror error;
+
+ /* Set fetch response time if not already set */
+ if (object->cache.res_time == 0)
+ object->cache.res_time = time(NULL);
+
+ /* Decompose header into name-value pair */
+ error = llcache_fetch_split_header(data, len, name, value);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Parse cache headers to populate cache control data */
+#define SKIP_ST(p) while (*p != '\0' && (*p == ' ' || *p == '\t')) p++
+
+ if (5 < len && strcasecmp(*name, "Date") == 0) {
+ /* extract Date header */
+ object->cache.date = curl_getdate(*value, NULL);
+ } else if (4 < len && strcasecmp(*name, "Age") == 0) {
+ /* extract Age header */
+ if ('0' <= **value && **value <= '9')
+ object->cache.age = atoi(*value);
+ } else if (8 < len && strcasecmp(*name, "Expires") == 0) {
+ /* extract Expires header */
+ object->cache.expires = curl_getdate(*value, NULL);
+ } else if (14 < len && strcasecmp(*name, "Cache-Control") == 0) {
+ /* extract and parse Cache-Control header */
+ const char *start = *value;
+ const char *comma = *value;
+
+ while (*comma != '\0') {
+ while (*comma != '\0' && *comma != ',')
+ comma++;
+
+ if (8 < comma - start && (strncasecmp(start,
+ "no-cache", 8) == 0 ||
+ strncasecmp(start, "no-store", 8) == 0))
+ /* When we get a disk cache we should
+ * distinguish between these two */
+ object->cache.no_cache = true;
+ else if (7 < comma - start &&
+ strncasecmp(start, "max-age", 7) == 0) {
+ /* Find '=' */
+ while (start < comma && *start != '=')
+ start++;
+
+ /* Skip over it */
+ start++;
+
+ /* Skip whitespace */
+ SKIP_ST(start);
+
+ if (start < comma)
+ object->cache.max_age = atoi(start);
+ }
+
+ if (*comma != '\0') {
+ /* Skip past comma */
+ comma++;
+ /* Skip whitespace */
+ SKIP_ST(comma);
+ }
+
+ /* Set start for next token */
+ start = comma;
+ }
+ } else if (5 < len && strcasecmp(*name, "ETag") == 0) {
+ /* extract ETag header */
+ free(object->cache.etag);
+ object->cache.etag = strdup(*value);
+ if (object->cache.etag == NULL)
+ return NSERROR_NOMEM;
+ } else if (14 < len && strcasecmp(*name, "Last-Modified") == 0) {
+ /* extract Last-Modified header */
+ object->cache.last_modified = curl_getdate(*value, NULL);
+ }
+
+#undef SKIP_ST
+
+ return NSERROR_OK;
+}
+
+/**
+ * Process a fetch header
+ *
+ * \param object Object being fetched
+ * \param data Header string
+ * \param len Byte length of header
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_process_header(llcache_object *object, const char *data,
+ size_t len)
+{
+ nserror error;
+ char *name, *value;
+ llcache_header *temp;
+
+ error = llcache_fetch_parse_header(object, data, len, &name, &value);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Append header data to the object's headers array */
+ temp = realloc(object->headers, (object->num_headers + 1) *
+ sizeof(llcache_header));
+ if (temp == NULL) {
+ free(name);
+ free(value);
+ return NSERROR_NOMEM;
+ }
+
+ object->headers = temp;
+
+ object->headers[object->num_headers].name = name;
+ object->headers[object->num_headers].value = value;
+
+ object->num_headers++;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Process a chunk of fetched data
+ *
+ * \param object Object being fetched
+ * \param data Data to process
+ * \param len Byte length of data
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_fetch_process_data(llcache_object *object, const uint8_t *data,
+ size_t len)
+{
+ /* Resize source buffer if it's too small */
+ if (object->source_len + len >= object->source_alloc) {
+ const size_t new_len = object->source_len + len + 64 * 1024;
+ uint8_t *temp = realloc(object->source_data, new_len);
+ if (temp == NULL)
+ return NSERROR_NOMEM;
+
+ object->source_data = temp;
+ object->source_alloc = new_len;
+ }
+
+ /* Append this data chunk to source buffer */
+ memcpy(object->source_data + object->source_len, data, len);
+ object->source_len += len;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Handle an authentication request
+ *
+ * \param object Object being fetched
+ * \param realm Authentication realm
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_fetch_auth(llcache_object *object, const char *realm)
+{
+ nserror error = NSERROR_OK;
+
+ /* Abort fetch for this object */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+
+ if (query_cb != NULL) {
+ llcache_query query;
+
+ /* Destroy headers */
+ while (object->num_headers > 0) {
+ object->num_headers--;
+
+ free(object->headers[object->num_headers].name);
+ free(object->headers[object->num_headers].value);
+ }
+ free(object->headers);
+ object->headers = NULL;
+
+ /* Emit query for authentication details */
+ query.type = LLCACHE_QUERY_AUTH;
+ query.url = object->url;
+ query.data.auth.realm = realm;
+
+ error = query_cb(&query, query_cb_pw,
+ llcache_query_handle_response, object);
+ } else {
+ llcache_object_user *user;
+ llcache_event event;
+
+ /* Inform client(s) that object fetch failed */
+ event.type = LLCACHE_EVENT_ERROR;
+ /** \todo More appropriate error message */
+ event.data.msg = messages_get("FetchFailed");
+
+ for (user = object->users; user != NULL; user = user->next) {
+ error = user->handle.cb(&user->handle, &event,
+ user->handle.pw);
+ if (error != NSERROR_OK)
+ break;
+ }
+ }
+
+ return error;
+}
+
+/**
+ * Handle a TLS certificate verification failure
+ *
+ * \param object Object being fetched
+ * \param certs Certificate chain
+ * \param num Number of certificates in chain
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_cert_error(llcache_object *object,
+ const struct ssl_cert_info *certs, size_t num)
+{
+ nserror error = NSERROR_OK;
+
+ /* Abort fetch for this object */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+
+ if (query_cb != NULL) {
+ llcache_query query;
+
+ /* Emit query for TLS */
+ query.type = LLCACHE_QUERY_SSL;
+ query.url = object->url;
+ query.data.ssl.certs = certs;
+ query.data.ssl.num = num;
+
+ error = query_cb(&query, query_cb_pw,
+ llcache_query_handle_response, object);
+ } else {
+ llcache_object_user *user;
+ llcache_event event;
+
+ /* Inform client(s) that object fetch failed */
+ event.type = LLCACHE_EVENT_ERROR;
+ /** \todo More appropriate error message */
+ event.data.msg = messages_get("FetchFailed");
+
+ for (user = object->users; user != NULL; user = user->next) {
+ error = user->handle.cb(&user->handle, &event,
+ user->handle.pw);
+ if (error != NSERROR_OK)
+ break;
+ }
+ }
+
+ return error;
+}
+
diff --git a/content/llcache.h b/content/llcache.h
new file mode 100644
index 000000000..b2c856f3b
--- /dev/null
+++ b/content/llcache.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * Low-level resource cache (interface)
+ */
+
+#ifndef NETSURF_CONTENT_LLCACHE_H_
+#define NETSURF_CONTENT_LLCACHE_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "utils/errors.h"
+
+struct ssl_cert_info;
+struct fetch_multipart_data;
+
+/** Handle for low-level cache object */
+typedef struct llcache_handle llcache_handle;
+
+/** POST data object for low-level cache requests */
+typedef struct {
+ enum {
+ LLCACHE_POST_URL_ENCODED,
+ LLCACHE_POST_MULTIPART
+ } type; /**< Type of POST data */
+ union {
+ char *urlenc; /**< URL encoded data */
+ struct fetch_multipart_data *multipart; /**< Multipart data */
+ } data; /**< POST data content */
+} llcache_post_data;
+
+/** Low-level cache event types */
+typedef enum {
+ LLCACHE_EVENT_HAD_HEADERS, /**< Received all headers */
+ LLCACHE_EVENT_HAD_DATA, /**< Received some data */
+ LLCACHE_EVENT_DONE, /**< Finished fetching data */
+
+ LLCACHE_EVENT_ERROR, /**< An error occurred during fetch */
+ LLCACHE_EVENT_PROGRESS, /**< Fetch progress update */
+} llcache_event_type;
+
+/** Low-level cache events */
+typedef struct {
+ llcache_event_type type; /**< Type of event */
+ union {
+ struct {
+ const uint8_t *buf; /**< Buffer of data */
+ size_t len; /**< Length of buffer, in bytes */
+ } data; /**< Received data */
+ const char *msg; /**< Error or progress message */
+ } data; /**< Event data */
+} llcache_event;
+
+/**
+ * Client callback for low-level cache events
+ *
+ * \param handle Handle for which event is issued
+ * \param event Event data
+ * \param pw Pointer to client-specific data
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+typedef nserror (*llcache_handle_callback)(llcache_handle *handle,
+ const llcache_event *event, void *pw);
+
+/** Flags for low-level cache object retrieval */
+#define LLCACHE_RETRIEVE_FORCE_FETCH (1 << 0) /* Force a new fetch */
+#define LLCACHE_RETRIEVE_VERIFIABLE (1 << 1) /* Requested URL was verified */
+#define LLCACHE_RETRIEVE_SNIFF_TYPE (1 << 2) /* Permit content-type sniffing */
+#define LLCACHE_RETRIEVE_NO_ERROR_PAGES (1 << 3) /* No error pages */
+
+/** Low-level cache query types */
+typedef enum {
+ LLCACHE_QUERY_AUTH, /**< Need authentication details */
+ LLCACHE_QUERY_REDIRECT, /**< Need permission to redirect */
+ LLCACHE_QUERY_SSL /**< SSL chain needs inspection */
+} llcache_query_type;
+
+/** Low-level cache query */
+typedef struct {
+ llcache_query_type type; /**< Type of query */
+
+ const char *url; /**< URL being fetched */
+
+ union {
+ struct {
+ const char *realm; /**< Authentication realm */
+ } auth;
+
+ struct {
+ const char *target; /**< Redirect target */
+ } redirect;
+
+ struct {
+ const struct ssl_cert_info *certs;
+ size_t num; /**< Number of certs in chain */
+ } ssl;
+ } data;
+} llcache_query;
+
+/**
+ * Response handler for fetch-related queries
+ *
+ * \param proceed Whether to proceed with the fetch or not
+ * \param cbpw Opaque value provided to llcache_query_callback
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+typedef nserror (*llcache_query_response)(bool proceed, void *cbpw);
+
+/**
+ * Callback to handle fetch-related queries
+ *
+ * \param query Object containing details of query
+ * \param pw Pointer to callback-specific data
+ * \param cb Callback that client should call once query is satisfied
+ * \param cbpw Opaque value to pass into \a cb
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \note This callback should return immediately. Once a suitable answer to
+ * the query has been obtained, the provided response callback should be
+ * called. This is intended to be an entirely asynchronous process.
+ */
+typedef nserror (*llcache_query_callback)(const llcache_query *query, void *pw,
+ llcache_query_response cb, void *cbpw);
+
+/**
+ * Initialise the low-level cache
+ *
+ * \param cb Query handler
+ * \param pw Pointer to query handler data
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_initialise(llcache_query_callback cb, void *pw);
+
+/**
+ * Poll the low-level cache
+ *
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_poll(void);
+
+/**
+ * Retrieve a handle for a low-level cache object
+ *
+ * \param url URL of the object to fetch
+ * \param flags Object retrieval flags
+ * \param referer Referring URL, or NULL if none
+ * \param post POST data, or NULL for a GET request
+ * \param cb Client callback for events
+ * \param pw Pointer to client-specific data
+ * \param result Pointer to location to recieve cache handle
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_retrieve(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_handle_callback cb, void *pw,
+ llcache_handle **result);
+
+/**
+ * Change the callback associated with a low-level cache handle
+ *
+ * \param handle Handle to change callback of
+ * \param cb New callback
+ * \param pw Client data for new callback
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_change_callback(llcache_handle *handle,
+ llcache_handle_callback cb, void *pw);
+
+/**
+ * Release a low-level cache handle
+ *
+ * \param handle Handle to release
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_release(llcache_handle *handle);
+
+/**
+ * Retrieve the post-redirect URL of a low-level cache object
+ *
+ * \param handle Handle to retrieve URL from
+ * \return Post-redirect URL of cache object
+ */
+const char *llcache_handle_get_url(const llcache_handle *handle);
+
+/**
+ * Retrieve source data of a low-level cache object
+ *
+ * \param handle Handle to retrieve source data from
+ * \param size Pointer to location to receive byte length of data
+ * \return Pointer to source data
+ */
+const uint8_t *llcache_handle_get_source_data(const llcache_handle *handle,
+ size_t *size);
+
+/**
+ * Retrieve a header value associated with a low-level cache object
+ *
+ * \param handle Handle to retrieve header from
+ * \param key Header name
+ * \return Header value, or NULL if header does not exist
+ *
+ * \todo Make the key an enumeration, to avoid needless string comparisons
+ * \todo Forcing the client to parse the header value seems wrong.
+ * Better would be to return the actual value part and an array of
+ * key-value pairs for any additional parameters.
+ */
+const char *llcache_handle_get_header(const llcache_handle *handle,
+ const char *key);
+
+/**
+ * Determine if the same underlying object is referenced by the given handles
+ *
+ * \param a First handle
+ * \param b Second handle
+ * \return True if handles reference the same object, false otherwise
+ */
+bool llcache_handle_references_same_object(const llcache_handle *a,
+ const llcache_handle *b);
+
+#endif