diff options
author | Daniel Silverstone <dsilvers@netsurf-browser.org> | 2010-03-28 12:56:39 +0000 |
---|---|---|
committer | Daniel Silverstone <dsilvers@netsurf-browser.org> | 2010-03-28 12:56:39 +0000 |
commit | 270ef59a98d34fef418fb6cd27e46f3edc912948 (patch) | |
tree | 9d363b42d441640e1d2dbff3ba548a2cdf8d67a9 /content | |
parent | 21da4f5bdf74c6654730c32dfcc1c6b3d24da4b4 (diff) | |
download | netsurf-270ef59a98d34fef418fb6cd27e46f3edc912948.tar.gz netsurf-270ef59a98d34fef418fb6cd27e46f3edc912948.tar.bz2 |
Merge jmb/new-cache; r=dsilvers,rs=vince
svn path=/trunk/netsurf/; revision=10180
Diffstat (limited to 'content')
-rw-r--r-- | content/content.c | 999 | ||||
-rw-r--r-- | content/content.h | 295 | ||||
-rw-r--r-- | content/content_protected.h | 221 | ||||
-rw-r--r-- | content/fetch.c | 79 | ||||
-rw-r--r-- | content/fetch.h | 18 | ||||
-rw-r--r-- | content/fetchcache.c | 4 | ||||
-rw-r--r-- | content/fetchcache.h | 6 | ||||
-rw-r--r-- | content/fetchers/fetch_curl.c | 110 | ||||
-rw-r--r-- | content/fetchers/fetch_data.c | 32 | ||||
-rw-r--r-- | content/hlcache.c | 362 | ||||
-rw-r--r-- | content/hlcache.h | 110 | ||||
-rw-r--r-- | content/llcache.c | 1815 | ||||
-rw-r--r-- | content/llcache.h | 238 |
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, + ¶ms); + 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 |