diff --git a/Makefile.sources b/Makefile.sources index ec7edc7a5..2e09a3d1a 100644 --- a/Makefile.sources +++ b/Makefile.sources @@ -5,13 +5,13 @@ # for each build. # -S_CONTENT := content.c fetch.c fetchcache.c urldb.c \ +S_CONTENT := content.c fetch.c hlcache.c llcache.c urldb.c \ fetchers/fetch_curl.c fetchers/fetch_data.c S_CSS := css.c dump.c internal.c select.c utils.c S_RENDER := box.c box_construct.c box_normalise.c directory.c favicon.c \ font.c form.c html.c html_redraw.c hubbub_binding.c imagemap.c \ layout.c list.c table.c textplain.c -S_UTILS := base64.c filename.c hashtable.c locale.c \ +S_UTILS := base64.c filename.c hashtable.c http.c locale.c \ messages.c talloc.c url.c utf8.c utils.c useragent.c S_DESKTOP := knockout.c options.c plot_style.c print.c search.c \ searchweb.c scroll.c textarea.c tree.c version.c diff --git a/amiga/download.c b/amiga/download.c index 2172fb8cc..064923eff 100644 --- a/amiga/download.c +++ b/amiga/download.c @@ -252,14 +252,15 @@ void ami_free_download_list(struct List *dllist) }while(node=nnode); } -void gui_window_save_as_link(struct gui_window *g, struct content *c) +void +gui_window_save_link(struct gui_window *g, const char *url, const char *title) { BPTR fh = 0; char fname[1024]; STRPTR openurlstring,linkname; struct DiskObject *dobj = NULL; - linkname = ASPrintf("Link_to_%s",FilePart(c->url)); + linkname = ASPrintf("Link_to_%s",FilePart(url)); if(AslRequestTags(savereq, ASLFR_TitleText,messages_get("NetSurf"), @@ -272,11 +273,11 @@ void gui_window_save_as_link(struct gui_window *g, struct content *c) ami_update_pointer(g->shared->win,GUI_POINTER_WAIT); if(fh = FOpen(fname,MODE_NEWFILE,0)) { - openurlstring = ASPrintf("openurl \"%s\"\n",c->url); + openurlstring = ASPrintf("openurl \"%s\"\n",url); FWrite(fh,openurlstring,1,strlen(openurlstring)); FClose(fh); FreeVec(openurlstring); - SetComment(fname,c->url); + SetComment(fname,url); dobj = GetIconTags(NULL,ICONGETA_GetDefaultName,"url", ICONGETA_GetDefaultType,WBPROJECT, diff --git a/amiga/fetch_file.c b/amiga/fetch_file.c index 953478423..bc03962ab 100755 --- a/amiga/fetch_file.c +++ b/amiga/fetch_file.c @@ -56,7 +56,7 @@ static bool ami_fetch_file_initialise(const char *scheme); static void ami_fetch_file_finalise(const char *scheme); static void * ami_fetch_file_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 ami_fetch_file_start(void *vfetch); static void ami_fetch_file_abort(void *vf); @@ -135,7 +135,7 @@ void ami_fetch_file_finalise(const char *scheme) void * ami_fetch_file_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 ami_file_fetch_info *fetch; @@ -280,6 +280,7 @@ void ami_fetch_file_poll(const char *scheme_ignored) if(fetch->fh) { + char header[64]; struct ExamineData *fib; if(fib = ExamineObjectTags(EX_FileHandleInput,fetch->fh,TAG_DONE)) { @@ -291,9 +292,18 @@ void ami_fetch_file_poll(const char *scheme_ignored) fetch->mimetype = fetch_mimetype(fetch->path); LOG(("mimetype %s len %ld",fetch->mimetype,fetch->len)); - ami_fetch_file_send_callback(FETCH_TYPE, - fetch, fetch->mimetype, (ULONG)fetch->len, - errorcode); + snprintf(header, sizeof header, + "Content-Type: %s", + fetch->mimetype); + ami_fetch_file_send_callback(FETCH_HEADER, + fetch, header, strlen(header), errorcode); + + snprintf(header, sizeof header, + "Content-Length: %ld", + fetch->len); + ami_fetch_file_send_callback(FETCH_HEADER, + fetch, header, strlen(header), errorcode); + } else { diff --git a/beos/beos_fetch_rsrc.cpp b/beos/beos_fetch_rsrc.cpp index 89916cb08..8b2fa5e60 100644 --- a/beos/beos_fetch_rsrc.cpp +++ b/beos/beos_fetch_rsrc.cpp @@ -38,7 +38,6 @@ extern "C" { #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" @@ -83,7 +82,7 @@ static void fetch_rsrc_finalise(const char *scheme) static void *fetch_rsrc_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_rsrc_context *ctx; @@ -277,6 +276,8 @@ static void fetch_rsrc_poll(const char *scheme) /* Only process non-aborted fetches */ if (!c->aborted && fetch_rsrc_process(c) == true) { + char header[64]; + fetch_set_http_code(c->parent_fetch, 200); LOG(("setting rsrc: MIME type to %s, length to %zd", c->mimetype, c->datalen)); @@ -284,8 +285,16 @@ static void fetch_rsrc_poll(const char *scheme) * Therefore, we _must_ check for this after _every_ * call to fetch_rsrc_send_callback(). */ - fetch_rsrc_send_callback(FETCH_TYPE, - c, c->mimetype, c->datalen, FETCH_ERROR_NO_ERROR); + snprintf(header, sizeof header, "Content-Type: %s", + c->mimetype); + fetch_rsrc_send_callback(FETCH_HEADER, c, header, + strlen(header), FETCH_ERROR_NO_ERROR); + + snprintf(header, sizeof header, "Content-Length: %zd", + c->datalen); + fetch_rsrc_send_callback(FETCH_HEADER, c, header, + strlen(header), FETCH_ERROR_NO_ERROR); + if (!c->aborted) { fetch_rsrc_send_callback(FETCH_DATA, c, c->data, c->datalen, FETCH_ERROR_NO_ERROR); diff --git a/beos/beos_gui.cpp b/beos/beos_gui.cpp index 6c05bc401..90db0ff94 100644 --- a/beos/beos_gui.cpp +++ b/beos/beos_gui.cpp @@ -424,7 +424,15 @@ static int32 bapp_thread(void *arg) int main(int argc, char** argv) { setbuf(stderr, NULL); - return netsurf_main(argc, argv); + + /* initialise netsurf */ + netsurf_init(argc, argv); + + netsurf_main_loop(); + + netsurf_exit(); + + return 0; } void gui_init(int argc, char** argv) @@ -843,7 +851,8 @@ void gui_create_form_select_menu(struct browser_window *bw, #endif } -void gui_window_save_as_link(struct gui_window *g, struct content *c) +void +gui_window_save_link(struct gui_window *g, const char *url, const char *title) { } 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 #include #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; + if (handler_map[type].create) { + if (handler_map[type].create(c, params) == false) { + talloc_free(c); + http_parameter_list_destroy(params); + return NULL; + } + } + + http_parameter_list_destroy(params); + + /* Finally, claim low-level cache events */ + if (llcache_handle_change_callback(llcache, + content_llcache_callback, c) != NSERROR_OK) { + talloc_free(c); + return NULL; + } return c; } - /** - * Get a content from the memory cache. + * Handler for low-level cache events * - * \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. + * \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(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; - 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; + 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); + + 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 a READY or DONE content from the memory cache. - * - * \param url URL of content - * \return content if found, or 0 - * - * Searches the list of contents for one corresponding to the given url, and - * which is fresh, shareable and either READY or DONE. - */ - -struct content * content_get_ready(const char *url) -{ - struct content *c; - - for (c = content_list; c; c = c->next) { - if (!c->fresh) - /* not fresh */ - continue; - if (c->status != CONTENT_STATUS_READY && - c->status != CONTENT_STATUS_DONE) - /* not ready or done */ - continue; - if (c->type != CONTENT_UNKNOWN && - handler_map[c->type].no_share && - c->user_list->next) - /* not shareable, and has a user already */ - continue; - if (strcmp(c->url, url)) - continue; - return c; - } - - return 0; -} - - /** * 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); } @@ -732,58 +627,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. * @@ -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) { @@ -878,65 +716,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. * @@ -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; + + data.redraw.x = x; + data.redraw.y = y; + data.redraw.width = width; + data.redraw.height = height; + + data.redraw.full_redraw = true; + + 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); } - -/** - * Free all contents in the content_list. - */ - -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; - } - } - - if (content_list) { - LOG(("bug: some contents could not be destroyed")); - } -} - - /** * 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; @@ -1169,50 +910,26 @@ 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 -#include -#include #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. */ -}; - -extern struct content *content_list; -extern const char * const content_type_name[]; -extern const char * const content_status_name[]; - +/* The following are for hlcache */ 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, +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); + +const struct llcache_handle *content_get_llcache_handle(struct content *c); + + +/* 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 + * Copyright 2003 Philip Pemberton + * + * 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 . + */ + +/** \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 +#include +#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 #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 #include +#include #include #include #include @@ -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); + /* 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 (strncmp(f->url, "file:///", 8) == 0) - 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; - 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); + /* 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); - /* 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) { + /* 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; + } } + + if (url_path != NULL) + curl_free(url_path); } - 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 + * + * 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 . + */ + +/** \file + * High-level resource cache (implementation) + */ + +#include +#include +#include + +#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 + * + * 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 . + */ + +/** \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 + * + * 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 . + */ + +/** \file + * Low-level resource cache (implementation) + */ + +#define _GNU_SOURCE /* For strndup. Ugh. */ +#include +#include +#include + +#include + +#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 + * + * 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 . + */ + +/** \file + * Low-level resource cache (interface) + */ + +#ifndef NETSURF_CONTENT_LLCACHE_H_ +#define NETSURF_CONTENT_LLCACHE_H_ + +#include +#include +#include + +#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 diff --git a/css/css.c b/css/css.c index cf8902037..473ea488a 100644 --- a/css/css.c +++ b/css/css.c @@ -20,17 +20,18 @@ #include -#include "content/content.h" +#include "content/content_protected.h" #include "content/fetch.h" -#include "content/fetchcache.h" +#include "content/hlcache.h" #include "css/css.h" #include "css/internal.h" #include "desktop/gui.h" #include "render/html.h" +#include "utils/http.h" #include "utils/messages.h" -static void nscss_import(content_msg msg, struct content *c, - intptr_t p1, intptr_t p2, union content_msg_data data); +static nserror nscss_import(hlcache_handle *handle, + const hlcache_event *event, void *pw); /** * Allocation callback for libcss @@ -49,119 +50,28 @@ static void *myrealloc(void *ptr, size_t size, void *pw) * Initialise a CSS content * * \param c Content to initialise - * \param parent Parent content, or NULL if top-level * \param params Content-Type parameters * \return true on success, false on failure */ -bool nscss_create(struct content *c, struct content *parent, - const char *params[]) +bool nscss_create(struct content *c, const http_parameter *params) { const char *charset = NULL; - css_origin origin = CSS_ORIGIN_AUTHOR; - uint64_t media = CSS_MEDIA_ALL; - lwc_context *dict = NULL; - bool quirks = true; - uint32_t i; union content_msg_data msg_data; - css_error error; + nserror error; /** \todo what happens about the allocator? */ /** \todo proper error reporting */ /* Find charset specified on HTTP layer, if any */ - /** \todo What happens if there isn't one and parent content exists? */ - for (i = 0; params[i] != NULL; i += 2) { - if (strcasecmp(params[i], "charset") == 0) { - charset = params[i + 1]; - break; - } + error = http_parameter_list_find_item(params, "charset", &charset); + if (error != NSERROR_OK) { + /* No charset specified, use fallback, if any */ + /** \todo libcss will take this as gospel, which is wrong */ + charset = c->fallback_charset; } - if (parent != NULL) { - assert(parent->type == CONTENT_HTML || - parent->type == CONTENT_CSS); - - if (parent->type == CONTENT_HTML) { - assert(parent->data.html.dict != NULL); - - if (c == parent->data.html. - stylesheets[STYLESHEET_BASE].c || - c == parent->data.html. - stylesheets[STYLESHEET_QUIRKS].c || - c == parent->data.html. - stylesheets[STYLESHEET_ADBLOCK].c) - origin = CSS_ORIGIN_UA; - - quirks = (parent->data.html.quirks != - BINDING_QUIRKS_MODE_NONE); - - for (i = 0; i < parent->data.html.stylesheet_count; - i++) { - if (parent->data.html.stylesheets[i].c == c) { - media = parent->data.html. - stylesheets[i].media; - break; - } - } - - dict = parent->data.html.dict; - } else { - assert(parent->data.css.sheet != NULL); - assert(parent->data.css.dict != NULL); - - error = css_stylesheet_get_origin( - parent->data.css.sheet, &origin); - if (error != CSS_OK) { - msg_data.error = "?"; - content_broadcast(c, CONTENT_MSG_ERROR, - msg_data); - return false; - } - - error = css_stylesheet_quirks_allowed( - parent->data.css.sheet, &quirks); - if (error != CSS_OK) { - msg_data.error = "?"; - content_broadcast(c, CONTENT_MSG_ERROR, - msg_data); - return false; - } - - for (i = 0; i < parent->data.css.import_count; i++) { - if (parent->data.css.imports[i].c == c) { - media = parent->data.css. - imports[i].media; - break; - } - } - - dict = parent->data.css.dict; - } - } - - if (dict == NULL) { - lwc_error lerror = lwc_create_context(myrealloc, NULL, &dict); - - if (lerror != lwc_error_ok) { - msg_data.error = messages_get("NoMemory"); - content_broadcast(c, CONTENT_MSG_ERROR, msg_data); - return false; - } - } - - c->data.css.dict = lwc_context_ref(dict); - c->data.css.import_count = 0; - c->data.css.imports = NULL; - - error = css_stylesheet_create(CSS_LEVEL_21, charset, - c->url, NULL, origin, media, quirks, false, - c->data.css.dict, - myrealloc, NULL, - nscss_resolve_url, NULL, - &c->data.css.sheet); - if (error != CSS_OK) { - lwc_context_unref(c->data.css.dict); - c->data.css.dict = NULL; + if (nscss_create_css_data(&c->data.css, content__get_url(c), + charset, c->quirks) != NSERROR_OK) { msg_data.error = messages_get("NoMemory"); content_broadcast(c, CONTENT_MSG_ERROR, msg_data); return false; @@ -170,6 +80,35 @@ bool nscss_create(struct content *c, struct content *parent, return true; } +/** + * Create a struct content_css_data, creating a stylesheet object + * + * \param c Struct to populate + * \param url URL of stylesheet + * \param charset Stylesheet charset + * \param quirks Stylesheet quirks mode + * \return NSERROR_OK on success, NSERROR_NOMEM on memory exhaustion + */ +nserror nscss_create_css_data(struct content_css_data *c, + const char *url, const char *charset, bool quirks) +{ + css_error error; + + c->import_count = 0; + c->imports = NULL; + + error = css_stylesheet_create(CSS_LEVEL_21, charset, + url, NULL, quirks, false, + myrealloc, NULL, + nscss_resolve_url, NULL, + &c->sheet); + if (error != CSS_OK) { + return NSERROR_NOMEM; + } + + return NSERROR_OK; +} + /** * Process CSS source data * @@ -183,9 +122,7 @@ bool nscss_process_data(struct content *c, char *data, unsigned int size) union content_msg_data msg_data; css_error error; - error = css_stylesheet_append_data(c->data.css.sheet, - (const uint8_t *) data, size); - + error = nscss_process_css_data(&c->data.css, data, size); if (error != CSS_OK && error != CSS_NEEDDATA) { msg_data.error = "?"; content_broadcast(c, CONTENT_MSG_ERROR, msg_data); @@ -194,6 +131,21 @@ bool nscss_process_data(struct content *c, char *data, unsigned int size) return (error == CSS_OK || error == CSS_NEEDDATA); } +/** + * Process CSS data + * + * \param c CSS content object + * \param data Data to process + * \param size Number of bytes to process + * \return CSS_OK on success, appropriate error otherwise + */ +css_error nscss_process_css_data(struct content_css_data *c, char *data, + unsigned int size) +{ + return css_stylesheet_append_data(c->sheet, + (const uint8_t *) data, size); +} + /** * Convert a CSS content ready for use * @@ -209,98 +161,12 @@ bool nscss_convert(struct content *c, int w, int h) size_t size; css_error error; - error = css_stylesheet_data_done(c->data.css.sheet); - - /* Process pending imports */ - while (error == CSS_IMPORTS_PENDING) { - struct nscss_import *imports; - lwc_string *uri; - uint64_t media; - css_stylesheet *sheet; - - error = css_stylesheet_next_pending_import(c->data.css.sheet, - &uri, &media); - if (error != CSS_OK && error != CSS_INVALID) { - msg_data.error = "?"; - content_broadcast(c, CONTENT_MSG_ERROR, msg_data); - c->status = CONTENT_STATUS_ERROR; - return false; - } - - /* Give up if there are no more imports */ - if (error == CSS_INVALID) { - error = CSS_OK; - break; - } - - /* Increase space in table */ - imports = realloc(c->data.css.imports, - (c->data.css.import_count + 1) * - sizeof(struct nscss_import)); - if (imports == NULL) { - msg_data.error = "?"; - content_broadcast(c, CONTENT_MSG_ERROR, msg_data); - c->status = CONTENT_STATUS_ERROR; - return false; - } - c->data.css.imports = imports; - - /* Create content */ - i = c->data.css.import_count; - c->data.css.imports[c->data.css.import_count].media = media; - c->data.css.imports[c->data.css.import_count++].c = - fetchcache(lwc_string_data(uri), - nscss_import, (intptr_t) c, i, - c->width, c->height, true, NULL, NULL, - false, false); - if (c->data.css.imports[i].c == NULL) { - msg_data.error = "?"; - content_broadcast(c, CONTENT_MSG_ERROR, msg_data); - c->status = CONTENT_STATUS_ERROR; - return false; - } - - /* Fetch content */ - c->active++; - fetchcache_go(c->data.css.imports[i].c, c->url, - nscss_import, (intptr_t) c, i, - c->width, c->height, NULL, NULL, false, c); - - /* Wait for import to fetch + convert */ - while (c->active > 0) { - fetch_poll(); - gui_multitask(); - } - - if (c->data.css.imports[i].c != NULL) { - sheet = c->data.css.imports[i].c->data.css.sheet; - c->data.css.imports[i].c->data.css.sheet = NULL; - } else { - error = css_stylesheet_create(CSS_LEVEL_DEFAULT, - NULL, "", NULL, CSS_ORIGIN_AUTHOR, - media, false, false, c->data.css.dict, - myrealloc, NULL, - nscss_resolve_url, NULL, - &sheet); - if (error != CSS_OK) { - msg_data.error = messages_get("NoMemory"); - content_broadcast(c, CONTENT_MSG_ERROR, - msg_data); - c->status = CONTENT_STATUS_ERROR; - return false; - } - } - - error = css_stylesheet_register_import( - c->data.css.sheet, sheet); - if (error != CSS_OK) { - msg_data.error = "?"; - content_broadcast(c, CONTENT_MSG_ERROR, msg_data); - c->status = CONTENT_STATUS_ERROR; - return false; - } - - error = CSS_IMPORTS_PENDING; + error = nscss_convert_css_data(&c->data.css, w, h); + if (error != CSS_OK) { + msg_data.error = "?"; + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + c->status = CONTENT_STATUS_ERROR; + return false; } /* Retrieve the size of this sheet */ @@ -313,115 +179,223 @@ bool nscss_convert(struct content *c, int w, int h) } c->size += size; - /* Add on the size of the imported sheets, removing ourselves from - * their user list as we go (they're of no use to us now, as we've - * inserted the sheet into ourselves) */ + /* Add on the size of the imported sheets */ for (i = 0; i < c->data.css.import_count; i++) { - if (c->data.css.imports[i].c != NULL) { - c->size += c->data.css.imports[i].c->size; + struct content *import = hlcache_handle_get_content( + c->data.css.imports[i].c); - content_remove_user(c->data.css.imports[i].c, - nscss_import, (uintptr_t) c, i); + if (import != NULL) { + c->size += import->size; } - - c->data.css.imports[i].c = NULL; } - /* Remove the imports */ - c->data.css.import_count = 0; - free(c->data.css.imports); - c->data.css.imports = NULL; - c->status = CONTENT_STATUS_DONE; - /* Filthy hack to stop this content being reused - * when whatever is using it has finished with it. */ - c->fresh = false; - return error == CSS_OK; } +/** + * Convert CSS data ready for use + * + * \param c CSS data to convert + * \param w Width of area content will be displayed in + * \param h Height of area content will be displayed in + * \return CSS error + */ +css_error nscss_convert_css_data(struct content_css_data *c, int w, int h) +{ + const char *referer; + uint32_t i = 0; + css_error error; + nserror nerror; + + error = css_stylesheet_get_url(c->sheet, &referer); + if (error != CSS_OK) { + return error; + } + + error = css_stylesheet_data_done(c->sheet); + + /* Process pending imports */ + while (error == CSS_IMPORTS_PENDING) { + hlcache_child_context child; + struct nscss_import *imports; + lwc_string *uri; + uint64_t media; + css_stylesheet *sheet; + + error = css_stylesheet_next_pending_import(c->sheet, + &uri, &media); + if (error != CSS_OK && error != CSS_INVALID) { + return error; + } + + /* Give up if there are no more imports */ + if (error == CSS_INVALID) { + error = CSS_OK; + break; + } + + /* Increase space in table */ + imports = realloc(c->imports, (c->import_count + 1) * + sizeof(struct nscss_import)); + if (imports == NULL) { + return CSS_NOMEM; + } + c->imports = imports; + + /** \todo fallback charset */ + child.charset = NULL; + error = css_stylesheet_quirks_allowed(c->sheet, &child.quirks); + if (error != CSS_OK) { + return error; + } + + /* Create content */ + i = c->import_count; + c->imports[c->import_count].media = media; + nerror = hlcache_handle_retrieve(lwc_string_data(uri), + 0, referer, NULL, w, h, nscss_import, c, + &child, &c->imports[c->import_count++].c); + if (error != NSERROR_OK) { + return CSS_NOMEM; + } + + /* Wait for import to fetch + convert */ + /** \todo This blocking approach needs to die */ + while (c->imports[i].c != NULL && + content_get_status(c->imports[i].c) != + CONTENT_STATUS_DONE) { + fetch_poll(); + gui_multitask(); + } + + if (c->imports[i].c != NULL) { + struct content *s = hlcache_handle_get_content( + c->imports[i].c); + sheet = s->data.css.sheet; + } else { + error = css_stylesheet_create(CSS_LEVEL_DEFAULT, + NULL, "", NULL, false, false, + myrealloc, NULL, + nscss_resolve_url, NULL, + &sheet); + if (error != CSS_OK) { + return error; + } + } + + error = css_stylesheet_register_import(c->sheet, sheet); + if (error != CSS_OK) { + return error; + } + + error = CSS_IMPORTS_PENDING; + } + + return error; +} + /** * Clean up a CSS content * * \param c Content to clean up */ void nscss_destroy(struct content *c) +{ + nscss_destroy_css_data(&c->data.css); +} + +/** + * Clean up CSS data + * + * \param c CSS data to clean up + */ +void nscss_destroy_css_data(struct content_css_data *c) { uint32_t i; - for (i = 0; i < c->data.css.import_count; i++) { - if (c->data.css.imports[i].c != NULL) { - content_remove_user(c->data.css.imports[i].c, - nscss_import, (uintptr_t) c, i); + for (i = 0; i < c->import_count; i++) { + if (c->imports[i].c != NULL) { + hlcache_handle_release(c->imports[i].c); } - c->data.css.imports[i].c = NULL; + c->imports[i].c = NULL; } - free(c->data.css.imports); + free(c->imports); - if (c->data.css.sheet != NULL) { - css_stylesheet_destroy(c->data.css.sheet); - c->data.css.sheet = NULL; - } - - if (c->data.css.dict != NULL) { - lwc_context_unref(c->data.css.dict); - c->data.css.dict = NULL; + if (c->sheet != NULL) { + css_stylesheet_destroy(c->sheet); + c->sheet = NULL; } } /** - * Fetchcache handler for imported stylesheets + * Retrieve imported stylesheets * - * \param msg Message type - * \param c Content being fetched - * \param p1 Parent content - * \param p2 Index into parent's imported stylesheet array - * \param data Message data + * \param h Stylesheet containing imports + * \param n Pointer to location to receive number of imports + * \return Pointer to array of imported stylesheets */ -void nscss_import(content_msg msg, struct content *c, - intptr_t p1, intptr_t p2, union content_msg_data data) +struct nscss_import *nscss_get_imports(hlcache_handle *h, uint32_t *n) { - struct content *parent = (struct content *) p1; - uint32_t i = (uint32_t) p2; + struct content *c = hlcache_handle_get_content(h); - switch (msg) { + assert(c != NULL); + assert(c->type == CONTENT_CSS); + assert(n != NULL); + + *n = c->data.css.import_count; + + return c->data.css.imports; +} + +/** + * Handler for imported stylesheet events + * + * \param handle Handle for stylesheet + * \param event Event object + * \param pw Callback context + * \return NSERROR_OK on success, appropriate error otherwise + */ +nserror nscss_import(hlcache_handle *handle, + const hlcache_event *event, void *pw) +{ + struct content_css_data *parent = pw; + uint32_t i = 0; + + switch (event->type) { case CONTENT_MSG_LOADING: - if (c->type != CONTENT_CSS) { - content_remove_user(c, nscss_import, p1, p2); - if (c->user_list->next == NULL) { - fetch_abort(c->fetch); - c->fetch = NULL; - c->status = CONTENT_STATUS_ERROR; - } + if (content_get_type(handle) != CONTENT_CSS) { + hlcache_handle_release(handle); - parent->data.css.imports[i].c = NULL; - parent->active--; - content_add_error(parent, "NotCSS", 0); + for (i = 0; i < parent->import_count; i++) { + if (parent->imports[i].c == handle) { + parent->imports[i].c = NULL; + break; + } + } } break; case CONTENT_MSG_READY: break; case CONTENT_MSG_DONE: - parent->active--; break; - case CONTENT_MSG_AUTH: - case CONTENT_MSG_SSL: - case CONTENT_MSG_LAUNCH: case CONTENT_MSG_ERROR: - if (parent->data.css.imports[i].c == c) { - parent->data.css.imports[i].c = NULL; - parent->active--; + hlcache_handle_release(handle); + for (i = 0; i < parent->import_count; i++) { + if (parent->imports[i].c == handle) { + parent->imports[i].c = NULL; + break; + } } break; case CONTENT_MSG_STATUS: break; - case CONTENT_MSG_NEWPTR: - parent->data.css.imports[i].c = c; - break; default: assert(0); } + + return NSERROR_OK; } diff --git a/css/css.h b/css/css.h index d65de15cf..925b4ae40 100644 --- a/css/css.h +++ b/css/css.h @@ -23,7 +23,11 @@ #include +#include "utils/errors.h" + struct content; +struct hlcache_handle; +struct http_parameter; struct nscss_import; /** @@ -31,8 +35,6 @@ struct nscss_import; */ struct content_css_data { - lwc_context *dict; /**< Dictionary to intern strings in */ - css_stylesheet *sheet; /**< Stylesheet object */ uint32_t import_count; /**< Number of sheets imported */ @@ -43,12 +45,11 @@ struct content_css_data * Imported stylesheet record */ struct nscss_import { - struct content *c; /**< Content containing sheet */ + struct hlcache_handle *c; /**< Content containing sheet */ uint64_t media; /**< Media types that sheet applies to */ }; -bool nscss_create(struct content *c, struct content *parent, - const char *params[]); +bool nscss_create(struct content *c, const struct http_parameter *params); bool nscss_process_data(struct content *c, char *data, unsigned int size); @@ -56,5 +57,14 @@ bool nscss_convert(struct content *c, int w, int h); void nscss_destroy(struct content *c); +nserror nscss_create_css_data(struct content_css_data *c, + const char *url, const char *charset, bool quirks); +css_error nscss_process_css_data(struct content_css_data *c, char *data, + unsigned int size); +css_error nscss_convert_css_data(struct content_css_data *c, int w, int h); +void nscss_destroy_css_data(struct content_css_data *c); + +struct nscss_import *nscss_get_imports(struct hlcache_handle *h, uint32_t *n); + #endif diff --git a/css/internal.c b/css/internal.c index b9aa83f0d..4c80e639e 100644 --- a/css/internal.c +++ b/css/internal.c @@ -26,7 +26,6 @@ * URL resolution callback for libcss * * \param pw Resolution context - * \param ctx Dictionary to intern result in * \param base Base URI * \param rel Relative URL * \param abs Pointer to location to receive resolved URL @@ -34,8 +33,8 @@ * CSS_NOMEM on memory exhaustion, * CSS_INVALID if resolution failed. */ -css_error nscss_resolve_url(void *pw, lwc_context *ctx, - const char *base, lwc_string *rel, lwc_string **abs) +css_error nscss_resolve_url(void *pw, const char *base, + lwc_string *rel, lwc_string **abs) { lwc_error lerror; char *abs_url, *norm_url; @@ -57,7 +56,7 @@ css_error nscss_resolve_url(void *pw, lwc_context *ctx, free(abs_url); /* Intern it */ - lerror = lwc_context_intern(ctx, norm_url, strlen(norm_url), abs); + lerror = lwc_intern_string(norm_url, strlen(norm_url), abs); if (lerror != lwc_error_ok) { *abs = NULL; free(norm_url); diff --git a/css/internal.h b/css/internal.h index e675a4876..0344d6b32 100644 --- a/css/internal.h +++ b/css/internal.h @@ -21,7 +21,7 @@ #include "css/css.h" -css_error nscss_resolve_url(void *pw, lwc_context *ctx, - const char *base, lwc_string *rel, lwc_string **abs); +css_error nscss_resolve_url(void *pw, const char *base, + lwc_string *rel, lwc_string **abs); #endif diff --git a/css/select.c b/css/select.c index aeb7d3c58..be95a29c0 100644 --- a/css/select.c +++ b/css/select.c @@ -21,7 +21,7 @@ #include #include -#include "content/content.h" +#include "content/content_protected.h" #include "content/urldb.h" #include "css/internal.h" #include "css/select.h" @@ -31,12 +31,10 @@ #include "utils/url.h" #include "utils/utils.h" -static css_error node_name(void *pw, void *node, - lwc_context *dict, lwc_string **name); +static css_error node_name(void *pw, void *node, lwc_string **name); static css_error node_classes(void *pw, void *node, - lwc_context *dict, lwc_string ***classes, uint32_t *n_classes); -static css_error node_id(void *pw, void *node, - lwc_context *dict, lwc_string **id); + lwc_string ***classes, uint32_t *n_classes); +static css_error node_id(void *pw, void *node, lwc_string **id); static css_error named_ancestor_node(void *pw, void *node, lwc_string *name, void **ancestor); static css_error named_parent_node(void *pw, void *node, @@ -125,21 +123,20 @@ static css_select_handler selection_handler = { * \param charset Charset of data, or NULL if unknown * \param url URL of document containing data * \param allow_quirks True to permit CSS parsing quirks - * \param dict String internment context * \param alloc Memory allocation function * \param pw Private word for allocator * \return Pointer to stylesheet, or NULL on failure. */ css_stylesheet *nscss_create_inline_style(const uint8_t *data, size_t len, const char *charset, const char *url, bool allow_quirks, - lwc_context *dict, css_allocator_fn alloc, void *pw) + css_allocator_fn alloc, void *pw) { css_stylesheet *sheet; css_error error; error = css_stylesheet_create(CSS_LEVEL_DEFAULT, charset, url, NULL, - CSS_ORIGIN_AUTHOR, CSS_MEDIA_ALL, allow_quirks, true, - dict, alloc, pw, nscss_resolve_url, NULL, &sheet); + allow_quirks, true, alloc, pw, nscss_resolve_url, + NULL, &sheet); if (error != CSS_OK) { LOG(("Failed creating sheet: %d", error)); return NULL; @@ -413,18 +410,16 @@ bool nscss_parse_colour(const char *data, css_color *result) * * \param pw HTML document * \param node DOM node - * \param dict Dictionary to intern result in * \param name Pointer to location to receive node name * \return CSS_OK on success, * CSS_NOMEM on memory exhaustion. */ -css_error node_name(void *pw, void *node, - lwc_context *dict, lwc_string **name) +css_error node_name(void *pw, void *node, lwc_string **name) { xmlNode *n = node; lwc_error lerror; - lerror = lwc_context_intern(dict, (const char *) n->name, + lerror = lwc_intern_string((const char *) n->name, strlen((const char *) n->name), name); switch (lerror) { case lwc_error_oom: @@ -444,7 +439,6 @@ css_error node_name(void *pw, void *node, * * \param pw HTML document * \param node DOM node - * \param dict Dictionary to intern result in * \param classes Pointer to location to receive class name array * \param n_classes Pointer to location to receive length of class name array * \return CSS_OK on success, @@ -454,8 +448,8 @@ css_error node_name(void *pw, void *node, * be allocated using the same allocator as used by libcss during style * selection. */ -css_error node_classes(void *pw, void *node, - lwc_context *dict, lwc_string ***classes, uint32_t *n_classes) +css_error node_classes(void *pw, void *node, + lwc_string ***classes, uint32_t *n_classes) { xmlNode *n = node; xmlAttr *class; @@ -503,8 +497,7 @@ css_error node_classes(void *pw, void *node, } result = temp; - lerror = lwc_context_intern(dict, start, p - start, - &result[items]); + lerror = lwc_intern_string(start, p - start, &result[items]); switch (lerror) { case lwc_error_oom: error = CSS_NOMEM; @@ -536,7 +529,7 @@ cleanup: uint32_t i; for (i = 0; i < items; i++) - lwc_context_string_unref(dict, result[i]); + lwc_string_unref(result[i]); free(result); } @@ -553,13 +546,11 @@ cleanup: * * \param pw HTML document * \param node DOM node - * \param dict Dictionary to intern result in * \param id Pointer to location to receive id value * \return CSS_OK on success, * CSS_NOMEM on memory exhaustion. */ -css_error node_id(void *pw, void *node, - lwc_context *dict, lwc_string **id) +css_error node_id(void *pw, void *node, lwc_string **id) { xmlNode *n = node; xmlAttr *attr; @@ -590,7 +581,7 @@ css_error node_id(void *pw, void *node, } /* Intern value */ - lerror = lwc_context_intern(dict, start, strlen(start), id); + lerror = lwc_intern_string(start, strlen(start), id); switch (lerror) { case lwc_error_oom: error = CSS_NOMEM; @@ -1285,9 +1276,7 @@ css_error node_presentational_hint(void *pw, void *node, lwc_string *iurl; lwc_error lerror; - lerror = lwc_context_intern( - html->data.html.dict, url, - strlen(url), &iurl); + lerror = lwc_intern_string(url, strlen(url), &iurl); free(url); diff --git a/css/select.h b/css/select.h index 7b87b2783..06868d5b2 100644 --- a/css/select.h +++ b/css/select.h @@ -29,7 +29,7 @@ struct content; css_stylesheet *nscss_create_inline_style(const uint8_t *data, size_t len, const char *charset, const char *url, bool allow_quirks, - lwc_context *dict, css_allocator_fn alloc, void *pw); + css_allocator_fn alloc, void *pw); css_computed_style *nscss_get_style(struct content *html, xmlNode *n, uint32_t pseudo_element, uint64_t media, diff --git a/desktop/401login.h b/desktop/401login.h index 8a45477fd..8b5a0a778 100644 --- a/desktop/401login.h +++ b/desktop/401login.h @@ -21,10 +21,10 @@ #include "utils/config.h" -#include "content/content.h" -#include "desktop/browser.h" +struct hlcache_handle; +struct browser_window; -void gui_401login_open(struct browser_window *bw, struct content *c, +void gui_401login_open(struct browser_window *bw, struct hlcache_handle *c, const char *realm); #endif diff --git a/desktop/browser.c b/desktop/browser.c index a4c1e2156..115c16c5c 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -38,7 +38,7 @@ #include "curl/curl.h" #include "utils/config.h" #include "content/fetch.h" -#include "content/fetchcache.h" +#include "content/hlcache.h" #include "content/urldb.h" #include "css/css.h" #include "desktop/401login.h" @@ -66,9 +66,6 @@ /** browser window which is being redrawn. Valid only during redraw. */ struct browser_window *current_redraw_browser; -/** fake content for being saved as a link */ -struct content browser_window_href_content; - /** one or more windows require a reformat */ bool browser_reformat_pending; @@ -77,11 +74,11 @@ bool browser_reformat_pending; static void browser_window_go_post(struct browser_window *bw, const char *url, char *post_urlenc, - struct form_successful_control *post_multipart, + struct fetch_multipart_data *post_multipart, bool add_to_history, const char *referer, bool download, - bool verifiable, struct content *parent); -static void browser_window_callback(content_msg msg, struct content *c, - intptr_t p1, intptr_t p2, union content_msg_data data); + bool verifiable, hlcache_handle *parent); +static nserror browser_window_callback(hlcache_handle *c, + const hlcache_event *event, void *pw); static void browser_window_refresh(void *p); static bool browser_window_check_throbber(struct browser_window *bw); static void browser_window_convert_to_download(struct browser_window *bw); @@ -92,8 +89,8 @@ static void browser_window_set_status(struct browser_window *bw, const char *text); static void browser_window_set_pointer(struct gui_window *g, gui_pointer_shape shape); -static void download_window_callback(fetch_msg msg, void *p, const void *data, - unsigned long size, fetch_error_code errorcode); +static nserror download_window_callback(llcache_handle *handle, + const llcache_event *event, void *pw); static void browser_window_destroy_children(struct browser_window *bw); static void browser_window_destroy_internal(struct browser_window *bw); static void browser_window_set_scale_internal(struct browser_window *bw, @@ -112,7 +109,7 @@ static void browser_window_mouse_track_html(struct browser_window *bw, browser_mouse_state mouse, int x, int y); static void browser_window_mouse_track_text(struct browser_window *bw, browser_mouse_state mouse, int x, int y); -static void browser_radio_set(struct content *content, +static void browser_radio_set(hlcache_handle *content, struct form_control *radio); static gui_pointer_shape get_pointer_shape(struct browser_window *bw, struct box *box, bool imagemap); @@ -253,7 +250,7 @@ void browser_window_download(struct browser_window *bw, const char *url, void browser_window_go_unverifiable(struct browser_window *bw, const char *url, const char *referer, bool history_add, - struct content *parent) + hlcache_handle *parent) { /* All fetches passing through here are unverifiable * (i.e are not the result of user action) */ @@ -284,17 +281,22 @@ void browser_window_go_unverifiable(struct browser_window *bw, void browser_window_go_post(struct browser_window *bw, const char *url, char *post_urlenc, - struct form_successful_control *post_multipart, + struct fetch_multipart_data *post_multipart, bool add_to_history, const char *referer, bool download, - bool verifiable, struct content *parent) + bool verifiable, hlcache_handle *parent) { - struct content *c; + hlcache_handle *c; char *url2; char *fragment; url_func_result res; int depth = 0; struct browser_window *cur; int width, height; + uint32_t fetch_flags = 0; + bool fetch_is_post = (post_urlenc != NULL || post_multipart != NULL); + llcache_post_data post; + hlcache_child_context child; + nserror error; LOG(("bw %p, url %s", bw, url)); assert(bw); @@ -308,16 +310,44 @@ void browser_window_go_post(struct browser_window *bw, const char *url, return; } + /* Set up retrieval parameters */ + if (verifiable) + fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE; + + if (post_multipart != NULL) { + post.type = LLCACHE_POST_MULTIPART; + post.data.multipart = post_multipart; + } else if (post_urlenc != NULL) { + post.type = LLCACHE_POST_URL_ENCODED; + post.data.urlenc = post_urlenc; + } + + if (parent != NULL) { +//newcache extract charset and quirks from parent content + child.charset = NULL; + child.quirks = false; + } + + /* Normalize the request URL */ res = url_normalize(url, &url2); if (res != URL_FUNC_OK) { LOG(("failed to normalize url %s", url)); return; } - /* check we can actually handle this URL */ - if (!fetch_can_fetch(url2)) { - gui_launch_url(url2); + /* Get download out of the way */ + if (download) { + llcache_handle *l; + + error = llcache_handle_retrieve(url2, + fetch_flags | LLCACHE_RETRIEVE_FORCE_FETCH, + referer, fetch_is_post ? &post : NULL, + download_window_callback, NULL, &l); + if (error != NSERROR_OK) + LOG(("Failed to fetch download: %d", error)); + free(url2); + return; } @@ -336,9 +366,10 @@ void browser_window_go_post(struct browser_window *bw, const char *url, bw->frag_id = fragment; /* Compare new URL with existing one (ignoring fragments) */ - if (bw->current_content && bw->current_content->url) { - res = url_compare(bw->current_content->url, url2, - true, &same_url); + if (bw->current_content != NULL && + content_get_url(bw->current_content) != NULL) { + res = url_compare(content_get_url(bw->current_content), + url2, true, &same_url); if (res == URL_FUNC_NOMEM) { free(url2); warn_user("NoMemory", 0); @@ -351,17 +382,17 @@ void browser_window_go_post(struct browser_window *bw, const char *url, /* if we're simply moving to another ID on the same page, * don't bother to fetch, just update the window. */ - if (same_url && !post_urlenc && !post_multipart && - !strchr(url2, '?')) { + if (same_url && fetch_is_post == false && + strchr(url2, '?') == 0) { free(url2); if (add_to_history) history_add(bw->history, bw->current_content, bw->frag_id); browser_window_update(bw, false); - if (bw->current_content) { + if (bw->current_content != NULL) { browser_window_refresh_url_bar(bw, - bw->current_content->url, - bw->frag_id); + content_get_url(bw->current_content), + bw->frag_id); } return; } @@ -376,28 +407,26 @@ void browser_window_go_post(struct browser_window *bw, const char *url, browser_window_set_status(bw, messages_get("Loading")); bw->history_add = add_to_history; - c = fetchcache(url2, browser_window_callback, (intptr_t) bw, 0, - width, height, false, - post_urlenc, post_multipart, verifiable, download); - free(url2); - if (!c) { + + error = hlcache_handle_retrieve(url2, 0, referer, + fetch_is_post ? &post : NULL, width, height, + browser_window_callback, bw, + parent != NULL ? &child : NULL, &c); + if (error == NSERROR_NO_FETCH_HANDLER) { + gui_launch_url(url2); + free(url2); + return; + } else if (error != NSERROR_OK) { + free(url2); browser_window_set_status(bw, messages_get("NoMemory")); warn_user("NoMemory", 0); return; } + free(url2); + bw->loading_content = c; browser_window_start_throbber(bw); - - if (referer && referer != bw->referer) { - free(bw->referer); - bw->referer = strdup(referer); - } - - bw->download = download; - fetchcache_go(c, referer, browser_window_callback, - (intptr_t) bw, 0, width, height, - post_urlenc, post_multipart, verifiable, parent); } @@ -405,89 +434,95 @@ void browser_window_go_post(struct browser_window *bw, const char *url, * Callback for fetchcache() for browser window fetches. */ -void browser_window_callback(content_msg msg, struct content *c, - intptr_t p1, intptr_t p2, union content_msg_data data) +nserror browser_window_callback(hlcache_handle *c, + const hlcache_event *event, void *pw) { - struct browser_window *bw = (struct browser_window *) p1; + struct browser_window *bw = pw; - switch (msg) { + switch (event->type) { case CONTENT_MSG_LOADING: assert(bw->loading_content == c); - if (c->type == CONTENT_OTHER) + if (content_get_type(c) == CONTENT_OTHER) browser_window_convert_to_download(bw); #ifdef WITH_THEME_INSTALL - else if (c->type == CONTENT_THEME) { + else if (content_get_type(c) == CONTENT_THEME) { theme_install_start(c); - bw->loading_content = 0; - content_remove_user(c, browser_window_callback, - (intptr_t) bw, 0); + bw->loading_content = NULL; +//newcache do we not just pass ownership to the theme installation stuff? + hlcache_handle_release(c); browser_window_stop_throbber(bw); } #endif else { bw->refresh_interval = -1; - browser_window_set_status(bw, c->status_message); + browser_window_set_status(bw, + content_get_status_message(c)); } break; case CONTENT_MSG_READY: assert(bw->loading_content == c); - if (bw->current_content) { - if (bw->current_content->status == - CONTENT_STATUS_READY || - bw->current_content->status == - CONTENT_STATUS_DONE) + if (bw->current_content != NULL) { + content_status status = + content_get_status(bw->current_content); + + if (status == CONTENT_STATUS_READY || + status == CONTENT_STATUS_DONE) content_close(bw->current_content); - content_remove_user(bw->current_content, - browser_window_callback, - (intptr_t) bw, 0); + + hlcache_handle_release(bw->current_content); } + bw->current_content = c; bw->loading_content = NULL; + browser_window_remove_caret(bw); + bw->scroll = NULL; + gui_window_new_content(bw->window); - if (bw->current_content) { - browser_window_refresh_url_bar(bw, - bw->current_content->url, - bw->frag_id); - } + + browser_window_refresh_url_bar(bw, + content_get_url(bw->current_content), + bw->frag_id); + /* new content; set scroll_to_top */ browser_window_update(bw, true); content_open(c, bw, 0, 0, 0, 0); - browser_window_set_status(bw, c->status_message); + browser_window_set_status(bw, content_get_status_message(c)); /* history */ if (bw->history_add && bw->history) { + const char *url = content_get_url(c); + history_add(bw->history, c, bw->frag_id); - if (urldb_add_url(c->url)) { - urldb_set_url_title(c->url, - c->title ? c->title : c->url); - urldb_update_url_visit_data(c->url); - urldb_set_url_content_type(c->url, - c->type); - /* This is safe as we've just - * added the URL */ - global_history_add( - urldb_get_url(c->url)); + if (urldb_add_url(url)) { + urldb_set_url_title(url, content_get_title(c)); + urldb_update_url_visit_data(url); + urldb_set_url_content_type(url, + content_get_type(c)); + /* This is safe as we've just added the URL */ + global_history_add(urldb_get_url(url)); } } /* text selection */ - if (c->type == CONTENT_HTML) + if (content_get_type(c) == CONTENT_HTML) selection_init(bw->sel, - bw->current_content->data.html.layout); - if (c->type == CONTENT_TEXTPLAIN) + html_get_box_tree(bw->current_content)); + if (content_get_type(c) == CONTENT_TEXTPLAIN) selection_init(bw->sel, NULL); /* frames */ - if (c->type == CONTENT_HTML && c->data.html.frameset) - browser_window_create_frameset(bw, - c->data.html.frameset); - if (c->type == CONTENT_HTML && c->data.html.iframe) - browser_window_create_iframes(bw, c->data.html.iframe); + if (content_get_type(c) == CONTENT_HTML && + html_get_frameset(c) != NULL) + browser_window_create_frameset(bw, + html_get_frameset(c)); + if (content_get_type(c) == CONTENT_HTML && + html_get_iframe(c) != NULL) + browser_window_create_iframes(bw, html_get_iframe(c)); break; @@ -495,137 +530,75 @@ void browser_window_callback(content_msg msg, struct content *c, assert(bw->current_content == c); browser_window_update(bw, false); - browser_window_set_status(bw, c->status_message); + browser_window_set_status(bw, content_get_status_message(c)); browser_window_stop_throbber(bw); browser_window_set_icon(bw); + history_update(bw->history, c); hotlist_visited(c); - free(bw->referer); - bw->referer = 0; + if (bw->refresh_interval != -1) schedule(bw->refresh_interval, browser_window_refresh, bw); break; case CONTENT_MSG_ERROR: - browser_window_set_status(bw, data.error); + browser_window_set_status(bw, event->data.error); /* Only warn the user about errors in top-level windows */ if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) - warn_user(data.error, 0); + warn_user(event->data.error, 0); if (c == bw->loading_content) - bw->loading_content = 0; + bw->loading_content = NULL; else if (c == bw->current_content) { - bw->current_content = 0; + bw->current_content = NULL; browser_window_remove_caret(bw); bw->scroll = NULL; selection_init(bw->sel, NULL); } + + hlcache_handle_release(c); + browser_window_stop_throbber(bw); - free(bw->referer); - bw->referer = 0; break; case CONTENT_MSG_STATUS: - browser_window_set_status(bw, c->status_message); + browser_window_set_status(bw, content_get_status_message(c)); break; case CONTENT_MSG_REFORMAT: if (c == bw->current_content && - c->type == CONTENT_HTML) { + content_get_type(c) == CONTENT_HTML) { /* reposition frames */ - if (c->data.html.frameset) + if (html_get_frameset(c) != NULL) browser_window_recalculate_frameset(bw); /* reflow iframe positions */ - if (c->data.html.iframe) + if (html_get_iframe(c) != NULL) browser_window_recalculate_iframes(bw); /* box tree may have changed, need to relabel */ - selection_reinit(bw->sel, c->data.html.layout); + selection_reinit(bw->sel, html_get_box_tree(c)); } + if (bw->move_callback) bw->move_callback(bw, bw->caret_p); + browser_window_update(bw, false); break; case CONTENT_MSG_REDRAW: - gui_window_update_box(bw->window, &data); - break; - - case CONTENT_MSG_NEWPTR: - bw->loading_content = c; - if (data.new_url) { - /* Replacement URL too, so check for new fragment */ - char *fragment; - url_func_result res; - - /* Remove any existing fragment */ - free(bw->frag_id); - bw->frag_id = NULL; - - /* Extract new one, if any */ - res = url_fragment(data.new_url, &fragment); - if (res == URL_FUNC_OK) { - /* Save for later use */ - bw->frag_id = fragment; - } - /* Ignore memory exhaustion here -- it'll simply result - * in the window being scrolled to the top rather than - * to the fragment. That's acceptable, given that it's - * likely that more important things will complain - * about memory shortage. */ - } - break; - - case CONTENT_MSG_LAUNCH: - assert(data.launch_url != NULL); - - bw->loading_content = NULL; - - gui_launch_url(data.launch_url); - - browser_window_stop_throbber(bw); - free(bw->referer); - bw->referer = 0; - break; - - case CONTENT_MSG_AUTH: - gui_401login_open(bw, c, data.auth_realm); - if (c == bw->loading_content) - bw->loading_content = 0; - else if (c == bw->current_content) { - bw->current_content = 0; - browser_window_remove_caret(bw); - bw->scroll = NULL; - selection_init(bw->sel, NULL); - } - browser_window_stop_throbber(bw); - free(bw->referer); - bw->referer = 0; - break; - - case CONTENT_MSG_SSL: - gui_cert_verify(bw, c, data.ssl.certs, data.ssl.num); - if (c == bw->loading_content) - bw->loading_content = 0; - else if (c == bw->current_content) { - bw->current_content = 0; - browser_window_remove_caret(bw); - bw->scroll = NULL; - selection_init(bw->sel, NULL); - } - browser_window_stop_throbber(bw); - free(bw->referer); - bw->referer = 0; + gui_window_update_box(bw->window, &event->data); break; case CONTENT_MSG_REFRESH: - bw->refresh_interval = data.delay * 100; + bw->refresh_interval = event->data.delay * 100; break; default: assert(0); } + + return NSERROR_OK; } @@ -636,34 +609,26 @@ void browser_window_callback(content_msg msg, struct content *c, void browser_window_convert_to_download(struct browser_window *bw) { struct gui_download_window *download_window; - struct content *c = bw->loading_content; - struct fetch *fetch; + hlcache_handle *c = bw->loading_content; + llcache_handle *stream; assert(c); - fetch = c->fetch; + stream = content_convert_to_download(c); - if (fetch) { - /* create download window */ - download_window = gui_download_window_create(c->url, - c->mime_type, fetch, c->total_size, - bw->window); + /** \todo Sort parameters out here */ + download_window = gui_download_window_create( + llcache_handle_get_url(stream), + llcache_handle_get_header(stream, "Content-Type"), + NULL, 0, NULL); - if (download_window) { - /* extract fetch from content */ - c->fetch = 0; - c->fresh = false; - fetch_change_callback(fetch, download_window_callback, - download_window); - } - } else { - /* must already be a download window for this fetch */ - /** \todo open it at top of stack */ - } + llcache_handle_change_callback(stream, + download_window_callback, download_window); /* remove content from browser window */ - bw->loading_content = 0; - content_remove_user(c, browser_window_callback, (intptr_t) bw, 0); + hlcache_handle_release(bw->loading_content); + bw->loading_content = NULL; + browser_window_stop_throbber(bw); } @@ -678,23 +643,26 @@ void browser_window_refresh(void *p) { struct browser_window *bw = p; bool history_add = true; + const char *url; + const char *refresh; - assert(bw->current_content && - (bw->current_content->status == CONTENT_STATUS_READY || - bw->current_content->status == CONTENT_STATUS_DONE)); + assert(bw->current_content != NULL && + (content_get_status(bw->current_content) == + CONTENT_STATUS_READY || + content_get_status(bw->current_content) == + CONTENT_STATUS_DONE)); /* Ignore if the refresh URL has gone * (may happen if a fetch error occurred) */ - if (!bw->current_content->refresh) + refresh = content_get_refresh_url(bw->current_content); + if (refresh == NULL) return; /* mark this content as invalid so it gets flushed from the cache */ - bw->current_content->fresh = false; + content_invalidate_reuse_data(bw->current_content); - if ((bw->current_content->url) && - (bw->current_content->refresh) && - (!strcmp(bw->current_content->url, - bw->current_content->refresh))) + url = content_get_url(bw->current_content); + if (url != NULL && strcmp(url, refresh) == 0) history_add = false; /* Treat an (almost) immediate refresh in a top-level browser window as @@ -705,11 +673,9 @@ void browser_window_refresh(void *p) * all. */ if (bw->refresh_interval <= 100 && bw->parent == NULL) { - browser_window_go(bw, bw->current_content->refresh, - bw->current_content->url, history_add); + browser_window_go(bw, refresh, url, history_add); } else { - browser_window_go_unverifiable(bw, bw->current_content->refresh, - bw->current_content->url, history_add, + browser_window_go_unverifiable(bw, refresh, url, history_add, bw->current_content); } } @@ -727,6 +693,7 @@ void browser_window_start_throbber(struct browser_window *bw) while (bw->parent) bw = bw->parent; + gui_window_start_throbber(bw->window); } @@ -780,9 +747,11 @@ void browser_window_set_icon(struct browser_window *bw) { while (bw->parent) bw = bw->parent; - if ((bw->current_content != NULL) && (bw->current_content->type == CONTENT_HTML)) + + if (bw->current_content != NULL && + content_get_type(bw->current_content) == CONTENT_HTML) gui_window_set_icon(bw->window, - bw->current_content->data.html.favicon); + html_get_favicon(bw->current_content)); else gui_window_set_icon(bw->window, NULL); } @@ -794,19 +763,16 @@ void browser_window_set_icon(struct browser_window *bw) * \param scroll_to_top move view to top of page */ -void browser_window_update(struct browser_window *bw, - bool scroll_to_top) +void browser_window_update(struct browser_window *bw, bool scroll_to_top) { struct box *pos; int x, y; - if (!bw->current_content) + if (bw->current_content == NULL) return; - if (bw->current_content->title != NULL) { - gui_window_set_title(bw->window, bw->current_content->title); - } else - gui_window_set_title(bw->window, bw->current_content->url); + gui_window_set_title(bw->window, + content_get_title(bw->current_content)); gui_window_update_extent(bw->window); @@ -815,9 +781,11 @@ void browser_window_update(struct browser_window *bw, /** \todo don't do this if the user has scrolled */ /* if frag_id exists, then try to scroll to it */ - if (bw->frag_id && bw->current_content->type == CONTENT_HTML) { - if ((pos = box_find_by_id(bw->current_content->data.html.layout, - bw->frag_id)) != 0) { + if (bw->frag_id && + content_get_type(bw->current_content) == CONTENT_HTML) { + struct box *layout = html_get_box_tree(bw->current_content); + + if ((pos = box_find_by_id(layout, bw->frag_id)) != 0) { box_coords(pos, &x, &y); gui_window_set_scroll(bw->window, x, y); } @@ -837,17 +805,17 @@ void browser_window_stop(struct browser_window *bw) { int children, index; - if (bw->loading_content) { - content_remove_user(bw->loading_content, - browser_window_callback, (intptr_t) bw, 0); - bw->loading_content = 0; + if (bw->loading_content != NULL) { + hlcache_handle_release(bw->loading_content); + bw->loading_content = NULL; } - if (bw->current_content && - bw->current_content->status != CONTENT_STATUS_DONE) { - assert(bw->current_content->status == CONTENT_STATUS_READY); + if (bw->current_content != NULL && content_get_status( + bw->current_content) != CONTENT_STATUS_DONE) { + assert(content_get_status(bw->current_content) == + CONTENT_STATUS_READY); content_stop(bw->current_content, - browser_window_callback, (intptr_t) bw, 0); + browser_window_callback, bw); } schedule_remove(browser_window_refresh, bw); @@ -876,29 +844,43 @@ void browser_window_stop(struct browser_window *bw) void browser_window_reload(struct browser_window *bw, bool all) { - struct content *c; + hlcache_handle *c; unsigned int i; - if (!bw->current_content || bw->loading_content) + if (bw->current_content == NULL || bw->loading_content != NULL) return; - if (all && bw->current_content->type == CONTENT_HTML) { + if (all && content_get_type(bw->current_content) == CONTENT_HTML) { + struct html_stylesheet *sheets; + struct content_html_object *objects; + unsigned int count; + c = bw->current_content; + /* invalidate objects */ - for (i = 0; i != c->data.html.object_count; i++) { - if (c->data.html.object[i].content) - c->data.html.object[i].content->fresh = false; + objects = html_get_objects(c, &count); + + for (i = 0; i != count; i++) { + if (objects[i].content != NULL) + content_invalidate_reuse_data( + objects[i].content); } + /* invalidate stylesheets */ - for (i = STYLESHEET_START; i != c->data.html.stylesheet_count; - i++) { - if (c->data.html.stylesheets[i].c) - c->data.html.stylesheets[i].c->fresh = - false; + sheets = html_get_stylesheets(c, &count); + + for (i = STYLESHEET_START; i != count; i++) { + if (sheets[i].type == HTML_STYLESHEET_EXTERNAL && + sheets[i].data.external != NULL) { + content_invalidate_reuse_data( + sheets[i].data.external); + } } } - bw->current_content->fresh = false; - browser_window_go_post(bw, bw->current_content->url, 0, 0, + + content_invalidate_reuse_data(bw->current_content); + + browser_window_go_post(bw, content_get_url(bw->current_content), 0, 0, false, 0, false, true, 0); } @@ -1012,21 +994,22 @@ void browser_window_destroy_internal(struct browser_window *bw) LOG(("Destroying window")); - if ((bw->children) || (bw->iframes)) + if (bw->children != NULL || bw->iframes != NULL) browser_window_destroy_children(bw); - if (bw->loading_content) { - content_remove_user(bw->loading_content, - browser_window_callback, (intptr_t) bw, 0); - bw->loading_content = 0; + + if (bw->loading_content != NULL) { + hlcache_handle_release(bw->loading_content); + bw->loading_content = NULL; } - if (bw->current_content) { - if (bw->current_content->status == CONTENT_STATUS_READY || - bw->current_content->status == - CONTENT_STATUS_DONE) + + if (bw->current_content != NULL) { + content_status status = content_get_status(bw->current_content); + if (status == CONTENT_STATUS_READY || + status == CONTENT_STATUS_DONE) content_close(bw->current_content); - content_remove_user(bw->current_content, - browser_window_callback, (intptr_t) bw, 0); - bw->current_content = 0; + + hlcache_handle_release(bw->current_content); + bw->current_content = NULL; } schedule_remove(browser_window_refresh, bw); @@ -1058,15 +1041,15 @@ struct browser_window *browser_window_owner(struct browser_window *bw) return bw->parent; /* the parent of a frameset is either a NORMAL window or an IFRAME */ - while (bw->parent) { + while (bw->parent != NULL) { switch (bw->browser_window_type) { - case BROWSER_WINDOW_NORMAL: - case BROWSER_WINDOW_IFRAME: - return bw; - case BROWSER_WINDOW_FRAME: - case BROWSER_WINDOW_FRAMESET: - bw = bw->parent; - break; + case BROWSER_WINDOW_NORMAL: + case BROWSER_WINDOW_IFRAME: + return bw; + case BROWSER_WINDOW_FRAME: + case BROWSER_WINDOW_FRAMESET: + bw = bw->parent; + break; } } return bw; @@ -1083,9 +1066,9 @@ struct browser_window *browser_window_owner(struct browser_window *bw) void browser_window_reformat(struct browser_window *bw, int width, int height) { - struct content *c = bw->current_content; + hlcache_handle *c = bw->current_content; - if (!c) + if (c == NULL) return; content_reformat(c, width / bw->scale, height / bw->scale); @@ -1104,23 +1087,28 @@ void browser_window_set_scale(struct browser_window *bw, float scale, bool all) { while (bw->parent && all) bw = bw->parent; + browser_window_set_scale_internal(bw, scale); + if (bw->parent) bw = bw->parent; + browser_window_recalculate_frameset(bw); } void browser_window_set_scale_internal(struct browser_window *bw, float scale) { int i; - struct content *c; + hlcache_handle *c; if (fabs(bw->scale-scale) < 0.0001) return; + bw->scale = scale; c = bw->current_content; - if (c) { - if (!content_can_reformat(c)) { + + if (c != NULL) { + if (content_can_reformat(c) == false) { browser_window_update(bw, false); } else { bw->reformat_pending = true; @@ -1189,14 +1177,14 @@ struct browser_window *browser_window_find_target(struct browser_window *bw, { struct browser_window *bw_target; struct browser_window *top; - struct content *c; + hlcache_handle *c; int rdepth; /* use the base target if we don't have one */ c = bw->current_content; - if (!target && c && c->data.html.base_target) - target = c->data.html.base_target; - if (!target) + if (target == NULL && c != NULL && content_get_type(c) == CONTENT_HTML) + target = html_get_base_target(c); + if (target == NULL) target = TARGET_SELF; /* allow the simple case of target="_blank" to be ignored if requested @@ -1348,35 +1336,57 @@ void browser_window_find_target_internal(struct browser_window *bw, * Callback for fetch for download window fetches. */ -void download_window_callback(fetch_msg msg, void *p, const void *data, - unsigned long size, fetch_error_code errorcode) +nserror download_window_callback(llcache_handle *handle, + const llcache_event *event, void *pw) { - struct gui_download_window *download_window = p; + struct gui_download_window *download_window = pw; - switch (msg) { - case FETCH_PROGRESS: - break; - case FETCH_DATA: - gui_download_window_data(download_window, data, size); - break; + switch (event->type) { + case LLCACHE_EVENT_HAD_HEADERS: + assert(download_window == NULL); - case FETCH_FINISHED: - gui_download_window_done(download_window); - break; + /** \todo Ensure parameters are correct here */ + download_window = gui_download_window_create( + llcache_handle_get_url(handle), + llcache_handle_get_header(handle, + "Content-Type"), + NULL, 0, NULL); + if (download_window == NULL) + return NSERROR_NOMEM; - case FETCH_ERROR: - gui_download_window_error(download_window, data); - break; + llcache_handle_change_callback(handle, + download_window_callback, download_window); + break; - case FETCH_TYPE: - case FETCH_NOTMODIFIED: - case FETCH_AUTH: - case FETCH_CERT_ERR: - default: - /* not possible */ - assert(0); - break; + case LLCACHE_EVENT_HAD_DATA: + assert(download_window != NULL); + + /** \todo Lose ugly cast */ + gui_download_window_data(download_window, + (char *) event->data.data.buf, + event->data.data.len); + + break; + + case LLCACHE_EVENT_DONE: + assert(download_window != NULL); + + gui_download_window_done(download_window); + + break; + + case LLCACHE_EVENT_ERROR: + if (download_window != NULL) + gui_download_window_error(download_window, + event->data.msg); + + break; + + case LLCACHE_EVENT_PROGRESS: + break; } + + return NSERROR_OK; } @@ -1392,12 +1402,12 @@ void download_window_callback(fetch_msg msg, void *p, const void *data, void browser_window_mouse_click(struct browser_window *bw, browser_mouse_state mouse, int x, int y) { - struct content *c = bw->current_content; + hlcache_handle *c = bw->current_content; if (!c) return; - switch (c->type) { + switch (content_get_type(c)) { case CONTENT_HTML: browser_window_mouse_action_html(bw, mouse, x, y); break; @@ -1457,12 +1467,12 @@ void browser_window_mouse_action_html(struct browser_window *bw, struct box *url_box = 0; struct box *gadget_box = 0; struct box *text_box = 0; - struct content *c = bw->current_content; + hlcache_handle *c = bw->current_content; struct box *box; - struct content *content = c; - struct content *gadget_content = c; + hlcache_handle *content = c; + hlcache_handle *gadget_content = c; struct form_control *gadget = 0; - struct content *object = NULL; + hlcache_handle *object = NULL; struct box *next_box; struct box *drag_candidate = NULL; struct scroll *scroll = NULL; @@ -1522,7 +1532,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, /* search the box tree for a link, imagemap, form control, or * box with scrollbars */ - box = c->data.html.layout; + box = html_get_box_tree(c); /* Consider the margins of the html page now */ box_x = box->margin[LEFT]; @@ -1715,7 +1725,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, bw->drag_type = DRAGGING_SELECTION; status = messages_get("Selecting"); } else - status = c->status_message; + status = content_get_status_message(c); } else if (mouse & BROWSER_MOUSE_PRESS_1) selection_clear(bw->sel, true); @@ -1787,7 +1797,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, bw->window); /* \todo should have a drag-saving object msg */ - status = c->status_message; + status = content_get_status_message(c); } else if (url) { if (title) { @@ -1803,17 +1813,10 @@ void browser_window_mouse_action_html(struct browser_window *bw, mouse & BROWSER_MOUSE_MOD_1) { /* force download of link */ browser_window_go_post(bw, url, 0, 0, false, - c->url, true, true, 0); + content_get_url(c), true, true, 0); } else if (mouse & BROWSER_MOUSE_CLICK_2 && mouse & BROWSER_MOUSE_MOD_1) { - free(browser_window_href_content.url); - browser_window_href_content.url = strdup(url); - if (!browser_window_href_content.url) - warn_user("NoMemory", 0); - else - gui_window_save_as_link(bw->window, - &browser_window_href_content); - + gui_window_save_link(bw->window, url, title); } else if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)) action = ACTION_GO; @@ -1834,11 +1837,13 @@ void browser_window_mouse_action_html(struct browser_window *bw, /* if clicking in the main page, remove the selection from any * text areas */ if (!done) { + struct box *layout = html_get_box_tree(c); + if (text_box && (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)) && - selection_root(bw->sel) != c->data.html.layout) - selection_init(bw->sel, c->data.html.layout); + selection_root(bw->sel) != layout) + selection_init(bw->sel, layout); if (text_box) { int pixel_offset; @@ -1866,7 +1871,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, status = messages_get("Selecting"); } else - status = c->status_message; + status = content_get_status_message(c); done = true; } @@ -1879,9 +1884,10 @@ void browser_window_mouse_action_html(struct browser_window *bw, if (title) status = title; else if (bw->loading_content) - status = bw->loading_content->status_message; + status = content_get_status_message( + bw->loading_content); else - status = c->status_message; + status = content_get_status_message(c); if (mouse & BROWSER_MOUSE_DRAG_1) { if (mouse & BROWSER_MOUSE_MOD_2) { @@ -1944,7 +1950,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, break; case ACTION_GO: browser_window_go(browser_window_find_target(bw, target, mouse), - url, c->url, true); + url, content_get_url(c), true); break; case ACTION_NONE: break; @@ -1970,7 +1976,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, void browser_window_mouse_action_text(struct browser_window *bw, browser_mouse_state mouse, int x, int y) { - struct content *c = bw->current_content; + hlcache_handle *c = bw->current_content; gui_pointer_shape pointer = GUI_POINTER_DEFAULT; const char *status = 0; size_t idx; @@ -1988,13 +1994,14 @@ void browser_window_mouse_action_text(struct browser_window *bw, status = messages_get("Selecting"); } else - status = c->status_message; + status = content_get_status_message(c); } else { if (bw->loading_content) - status = bw->loading_content->status_message; + status = content_get_status_message( + bw->loading_content); else - status = c->status_message; + status = content_get_status_message(c); if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) { browser_window_page_drag_start(bw, x, y); @@ -2021,7 +2028,7 @@ void browser_window_mouse_action_text(struct browser_window *bw, void browser_window_mouse_track(struct browser_window *bw, browser_mouse_state mouse, int x, int y) { - struct content *c = bw->current_content; + hlcache_handle *c = bw->current_content; if (c == NULL && bw->drag_type != DRAGGING_FRAME) return; @@ -2051,7 +2058,7 @@ void browser_window_mouse_track(struct browser_window *bw, } else { assert(c != NULL); - switch (c->type) { + switch (content_get_type(c)) { case CONTENT_HTML: browser_window_mouse_track_html(bw, mouse, x, y); break; @@ -2129,7 +2136,7 @@ void browser_window_mouse_track_text(struct browser_window *bw, switch (bw->drag_type) { case DRAGGING_SELECTION: { - struct content *c = bw->current_content; + hlcache_handle *c = bw->current_content; int dir = -1; size_t idx; @@ -2198,7 +2205,7 @@ void browser_window_mouse_drag_end(struct browser_window *bw, switch (bw->drag_type) { case DRAGGING_SELECTION: { - struct content *c = bw->current_content; + hlcache_handle *c = bw->current_content; if (c) { bool found = true; int dir = -1; @@ -2206,7 +2213,7 @@ void browser_window_mouse_drag_end(struct browser_window *bw, if (selection_dragging_start(bw->sel)) dir = 1; - if (c->type == CONTENT_HTML) { + if (content_get_type(c) == CONTENT_HTML) { int pixel_offset; struct box *box; int dx, dy; @@ -2236,7 +2243,8 @@ void browser_window_mouse_drag_end(struct browser_window *bw, found = false; } else { - assert(c->type == CONTENT_TEXTPLAIN); + assert(content_get_type(c) == + CONTENT_TEXTPLAIN); idx = textplain_offset_from_coords(c, x, y, dir); } @@ -2263,7 +2271,7 @@ void browser_window_mouse_drag_end(struct browser_window *bw, * \param radio form control of type GADGET_RADIO */ -void browser_radio_set(struct content *content, +void browser_radio_set(hlcache_handle *content, struct form_control *radio) { struct form_control *control; @@ -2309,26 +2317,7 @@ void browser_radio_set(struct content *content, void browser_window_redraw_rect(struct browser_window *bw, int x, int y, int width, int height) { - struct content *c = bw->current_content; - - if (c) { - union content_msg_data data; - - data.redraw.x = x; - data.redraw.y = y; - data.redraw.width = width; - data.redraw.height = height; - - data.redraw.full_redraw = true; - - 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); - } + content_request_redraw(bw->current_content, x, y, width, height); } @@ -2339,29 +2328,15 @@ void browser_window_redraw_rect(struct browser_window *bw, int x, int y, * \param box box to redraw */ -void browser_redraw_box(struct content *c, struct box *box) +void browser_redraw_box(hlcache_handle *c, struct box *box) { int x, y; - union content_msg_data data; box_coords(box, &x, &y); - data.redraw.x = x; - data.redraw.y = y; - data.redraw.width = box->padding[LEFT] + box->width + - box->padding[RIGHT]; - data.redraw.height = box->padding[TOP] + box->height + - box->padding[BOTTOM]; - - data.redraw.full_redraw = true; - - 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); + content_request_redraw(c, x, y, + box->padding[LEFT] + box->width + box->padding[RIGHT], + box->padding[TOP] + box->height + box->padding[BOTTOM]); } @@ -2441,7 +2416,8 @@ gui_pointer_shape get_pointer_shape(struct browser_window *bw, struct box *box, assert(bw); loading = (bw->loading_content != NULL || (bw->current_content && - bw->current_content->status == CONTENT_STATUS_READY)); + content_get_status(bw->current_content) == + CONTENT_STATUS_READY)); if (wallclock() - bw->last_action < 100 && loading) /* If less than 1 second since last link followed, show @@ -2540,14 +2516,15 @@ gui_pointer_shape get_pointer_shape(struct browser_window *bw, struct box *box, * Collect controls and submit a form. */ -void browser_form_submit(struct browser_window *bw, struct browser_window *target, +void browser_form_submit(struct browser_window *bw, + struct browser_window *target, struct form *form, struct form_control *submit_button) { char *data = 0, *url = 0; - struct form_successful_control *success; + struct fetch_multipart_data *success; assert(form); - assert(bw->current_content->type == CONTENT_HTML); + assert(content_get_type(bw->current_content) == CONTENT_HTML); if (!form_successful_controls(form, submit_button, &success)) { warn_user("NoMemory", 0); @@ -2558,14 +2535,14 @@ void browser_form_submit(struct browser_window *bw, struct browser_window *targe case method_GET: data = form_url_encode(form, success); if (!data) { - form_free_successful(success); + fetch_multipart_data_destroy(success); warn_user("NoMemory", 0); return; } url = calloc(1, strlen(form->action) + strlen(data) + 2); if (!url) { - form_free_successful(success); + fetch_multipart_data_destroy(success); free(data); warn_user("NoMemory", 0); return; @@ -2577,25 +2554,27 @@ void browser_form_submit(struct browser_window *bw, struct browser_window *targe sprintf(url, "%s?%s", form->action, data); } browser_window_go(target, url, - bw->current_content->url, true); + content_get_url(bw->current_content), + true); break; case method_POST_URLENC: data = form_url_encode(form, success); if (!data) { - form_free_successful(success); + fetch_multipart_data_destroy(success); warn_user("NoMemory", 0); return; } browser_window_go_post(target, form->action, data, 0, - true, bw->current_content->url, + true, + content_get_url(bw->current_content), false, true, 0); break; case method_POST_MULTIPART: browser_window_go_post(target, form->action, 0, success, true, - bw->current_content->url, + content_get_url(bw->current_content), false, true, 0); break; @@ -2603,7 +2582,7 @@ void browser_form_submit(struct browser_window *bw, struct browser_window *targe assert(0); } - form_free_successful(success); + fetch_multipart_data_destroy(success); free(data); free(url); } @@ -2864,11 +2843,11 @@ bool browser_window_nearest_text_box(struct box *box, int bx, int by, struct box *browser_window_pick_text_box(struct browser_window *bw, int x, int y, int dir, int *dx, int *dy) { - struct content *c = bw->current_content; + hlcache_handle *c = bw->current_content; struct box *text_box = NULL; - if (c && c->type == CONTENT_HTML) { - struct box *box = c->data.html.layout; + if (c && content_get_type(c) == CONTENT_HTML) { + struct box *box = html_get_box_tree(c); int nr_xd, nr_yd; int bx = box->margin[LEFT]; int by = box->margin[TOP]; @@ -3013,5 +2992,6 @@ bool browser_window_stop_available(struct browser_window *bw) { return (bw && (bw->loading_content || (bw->current_content && - (bw->current_content->status != CONTENT_STATUS_DONE)))); + (content_get_status(bw->current_content) != + CONTENT_STATUS_DONE)))); } diff --git a/desktop/browser.h b/desktop/browser.h index 52be9dedb..d72143968 100644 --- a/desktop/browser.h +++ b/desktop/browser.h @@ -31,10 +31,9 @@ #include "render/html.h" struct box; -struct content; +struct hlcache_handle; struct form; struct form_control; -struct form_successful_control; struct gui_window; struct history; struct selection; @@ -44,20 +43,20 @@ struct bitmap; struct scroll_msg_data; typedef bool (*browser_caret_callback)(struct browser_window *bw, - uint32_t key, void *p); + uint32_t key, void *p); typedef bool (*browser_paste_callback)(struct browser_window *bw, - const char *utf8, unsigned utf8_len, bool last, void *p); + const char *utf8, unsigned utf8_len, bool last, void *p); typedef void (*browser_move_callback)(struct browser_window *bw, - void *p); + void *p); /** Browser window data. */ struct browser_window { /** Page currently displayed, or 0. Must have status READY or DONE. */ - struct content *current_content; + struct hlcache_handle *current_content; /** Page being loaded, or 0. */ - struct content *loading_content; + struct hlcache_handle *loading_content; /** Window history structure. */ struct history *history; @@ -109,9 +108,6 @@ struct browser_window { /** Scroll capturing all mouse events */ struct scroll *scroll; - /** Referrer for current fetch, or 0. */ - char *referer; - /** Current fetch is download */ bool download; @@ -230,17 +226,17 @@ extern struct browser_window *current_redraw_browser; extern bool browser_reformat_pending; struct browser_window * browser_window_create(const char *url, - struct browser_window *clone, const char *referrer, - bool history_add, bool new_tab); + struct browser_window *clone, const char *referrer, + bool history_add, bool new_tab); void browser_window_initialise_common(struct browser_window *bw, - struct browser_window *clone); + struct browser_window *clone); void browser_window_go(struct browser_window *bw, const char *url, - const char *referrer, bool history_add); + const char *referrer, bool history_add); void browser_window_go_unverifiable(struct browser_window *bw, - const char *url, const char *referrer, bool history_add, - struct content *parent); + const char *url, const char *referrer, bool history_add, + struct hlcache_handle *parent); void browser_window_download(struct browser_window *bw, - const char *url, const char *referrer); + const char *url, const char *referrer); void browser_window_update(struct browser_window *bw, bool scroll_to_top); void browser_window_stop(struct browser_window *bw); void browser_window_reload(struct browser_window *bw, bool all); @@ -250,31 +246,32 @@ void browser_window_reformat(struct browser_window *bw, int width, int height); void browser_window_set_scale(struct browser_window *bw, float scale, bool all); void browser_window_refresh_url_bar(struct browser_window *bw, const char *url, - const char *frag); + const char *frag); void browser_window_mouse_click(struct browser_window *bw, - browser_mouse_state mouse, int x, int y); + browser_mouse_state mouse, int x, int y); void browser_window_mouse_track(struct browser_window *bw, - browser_mouse_state mouse, int x, int y); + browser_mouse_state mouse, int x, int y); void browser_window_mouse_drag_end(struct browser_window *bw, - browser_mouse_state mouse, int x, int y); + browser_mouse_state mouse, int x, int y); bool browser_window_key_press(struct browser_window *bw, uint32_t key); bool browser_window_paste_text(struct browser_window *bw, const char *utf8, - unsigned utf8_len, bool last); + unsigned utf8_len, bool last); void browser_window_form_select(struct browser_window *bw, - struct form_control *control, int item); -void browser_redraw_box(struct content *c, struct box *box); -void browser_form_submit(struct browser_window *bw, struct browser_window *target, - struct form *form, struct form_control *submit_button); + struct form_control *control, int item); +void browser_redraw_box(struct hlcache_handle *c, struct box *box); +void browser_form_submit(struct browser_window *bw, + struct browser_window *target, struct form *form, + struct form_control *submit_button); void browser_scroll_callback(void *client_data, - struct scroll_msg_data *scroll_data); + struct scroll_msg_data *scroll_data); void browser_select_menu_callback(void *client_data, - int x, int y, int width, int height); + int x, int y, int width, int height); void browser_window_redraw_rect(struct browser_window *bw, int x, int y, - int width, int height); + int width, int height); bool browser_window_back_available(struct browser_window *bw); bool browser_window_forward_available(struct browser_window *bw); @@ -282,7 +279,7 @@ bool browser_window_reload_available(struct browser_window *bw); bool browser_window_stop_available(struct browser_window *bw); /* In platform specific hotlist.c. */ -void hotlist_visited(struct content *content); +void hotlist_visited(struct hlcache_handle *content); /* In platform specific global_history.c. */ void global_history_add(const char *url); @@ -290,8 +287,8 @@ void global_history_add_recent(const char *url); char **global_history_get_recent(int *count); /* In platform specific thumbnail.c. */ -bool thumbnail_create(struct content *content, struct bitmap *bitmap, - const char *url); +bool thumbnail_create(struct hlcache_handle *content, struct bitmap *bitmap, + const char *url); /* In platform specific schedule.c. */ void schedule(int t, void (*callback)(void *p), void *p); @@ -300,7 +297,7 @@ bool schedule_run(void); /* In platform specific theme_install.c. */ #ifdef WITH_THEME_INSTALL -void theme_install_start(struct content *c); +void theme_install_start(struct hlcache_handle *c); #endif #endif diff --git a/desktop/frames.c b/desktop/frames.c index acabdc1b9..663ebcdbf 100644 --- a/desktop/frames.c +++ b/desktop/frames.c @@ -29,6 +29,7 @@ #include #include #include "utils/config.h" +#include "content/hlcache.h" #include "desktop/browser.h" #include "desktop/frames.h" #include "desktop/history_core.h" @@ -105,8 +106,8 @@ void browser_window_create_iframes(struct browser_window *bw, window = &(bw->iframes[index++]); if (cur->url) browser_window_go_unverifiable(window, cur->url, - bw->current_content->url, false, - bw->current_content); + content_get_url(bw->current_content), + false, bw->current_content); } } @@ -155,7 +156,7 @@ void browser_window_create_frameset(struct browser_window *bw, int row, col, index; struct content_html_frames *frame; struct browser_window *window; - struct content *parent; + hlcache_handle *parent; assert(bw && frameset); @@ -230,8 +231,9 @@ void browser_window_create_frameset(struct browser_window *bw, /* Use the URL of the first ancestor window containing html content * as the referer */ for (window = bw; window->parent; window = window->parent) { - if (window->current_content && - window->current_content->type == CONTENT_HTML) + if (window->current_content && + content_get_type(window->current_content) == + CONTENT_HTML) break; } @@ -247,8 +249,7 @@ void browser_window_create_frameset(struct browser_window *bw, if (frame->url) { browser_window_go_unverifiable(window, frame->url, - parent != NULL - ? parent->url : NULL, + content_get_url(parent), true, parent); } diff --git a/desktop/gui.h b/desktop/gui.h index f09e5f20f..162632a1a 100644 --- a/desktop/gui.h +++ b/desktop/gui.h @@ -41,6 +41,7 @@ typedef enum { GUI_SAVE_CLIPBOARD_CONTENTS } gui_save_type; +struct fetch; struct gui_window; struct gui_download_window; @@ -55,6 +56,7 @@ typedef enum { GUI_POINTER_DEFAULT, GUI_POINTER_POINT, GUI_POINTER_CARET, #include #include "utils/config.h" #include "content/content.h" +#include "content/hlcache.h" #include "desktop/browser.h" #include "desktop/search.h" @@ -89,8 +91,8 @@ void gui_window_hide_pointer(struct gui_window *g); void gui_window_set_url(struct gui_window *g, const char *url); void gui_window_start_throbber(struct gui_window *g); void gui_window_stop_throbber(struct gui_window *g); -void gui_window_set_icon(struct gui_window *g, struct content *icon); -void gui_window_set_search_ico(struct content *ico); +void gui_window_set_icon(struct gui_window *g, hlcache_handle *icon); +void gui_window_set_search_ico(hlcache_handle *ico); void gui_window_place_caret(struct gui_window *g, int x, int y, int height); void gui_window_remove_caret(struct gui_window *g); void gui_window_new_content(struct gui_window *g); @@ -98,7 +100,8 @@ bool gui_window_scroll_start(struct gui_window *g); bool gui_window_box_scroll_start(struct gui_window *g, int x0, int y0, int x1, int y1); bool gui_window_frame_resize_start(struct gui_window *g); -void gui_window_save_as_link(struct gui_window *g, struct content *c); +void gui_window_save_link(struct gui_window *g, const char *url, + const char *title); void gui_window_set_scale(struct gui_window *g, float scale); struct gui_download_window *gui_download_window_create(const char *url, @@ -110,7 +113,7 @@ void gui_download_window_error(struct gui_download_window *dw, const char *error_msg); void gui_download_window_done(struct gui_download_window *dw); -void gui_drag_save_object(gui_save_type type, struct content *c, +void gui_drag_save_object(gui_save_type type, hlcache_handle *c, struct gui_window *g); void gui_drag_save_selection(struct selection *s, struct gui_window *g); void gui_start_selection(struct gui_window *g); @@ -133,7 +136,7 @@ bool gui_search_term_highlighted(struct gui_window *g, struct ssl_cert_info; -void gui_cert_verify(struct browser_window *bw, struct content *c, +void gui_cert_verify(struct browser_window *bw, hlcache_handle *c, const struct ssl_cert_info *certs, unsigned long num); #endif diff --git a/desktop/history_core.c b/desktop/history_core.c index 197a42327..fc807e928 100644 --- a/desktop/history_core.c +++ b/desktop/history_core.c @@ -27,6 +27,7 @@ #include #include #include "content/content.h" +#include "content/hlcache.h" #include "content/urldb.h" #include "css/css.h" #include "desktop/gui.h" @@ -220,7 +221,7 @@ struct history_entry *history_clone_entry(struct history *history, * The page is added after the current entry and becomes current. */ -void history_add(struct history *history, struct content *content, +void history_add(struct history *history, hlcache_handle *content, char *frag_id) { url_func_result res; @@ -237,14 +238,14 @@ void history_add(struct history *history, struct content *content, if (entry == NULL) return; - res = url_normalize(content->url, &url); + res = url_normalize(content_get_url(content), &url); if (res != URL_FUNC_OK) { warn_user("NoMemory", 0); free(entry); return; } - title = strdup(content->title ? content->title : url); + title = strdup(content_get_title(content)); if (title == NULL) { warn_user("NoMemory", 0); free(url); @@ -303,11 +304,9 @@ void history_add(struct history *history, struct content *content, * \param content content for current entry */ -void history_update(struct history *history, struct content *content) +void history_update(struct history *history, hlcache_handle *content) { char *title; - char *url; - url_func_result res; if (!history || !history->current || !history->current->bitmap) return; @@ -315,19 +314,10 @@ void history_update(struct history *history, struct content *content) assert(history->current->page.url); assert(history->current->page.title); - if (content->title) { - title = strdup(content->title); - if (!title) { - warn_user("NoMemory", 0); - return; - } - } else { - res = url_normalize(content->url, &url); - if (res != URL_FUNC_OK) { - warn_user("NoMemory", 0); - return; - } - title = url; + title = strdup(content_get_title(content)); + if (!title) { + warn_user("NoMemory", 0); + return; } assert(title); diff --git a/desktop/history_core.h b/desktop/history_core.h index 46de18848..55b4e0bf1 100644 --- a/desktop/history_core.h +++ b/desktop/history_core.h @@ -25,15 +25,15 @@ #include -struct content; +struct hlcache_handle; struct history; struct browser_window; struct history *history_create(void); struct history *history_clone(struct history *history); -void history_add(struct history *history, struct content *content, +void history_add(struct history *history, struct hlcache_handle *content, char *frag_id); -void history_update(struct history *history, struct content *content); +void history_update(struct history *history, struct hlcache_handle *content); void history_destroy(struct history *history); void history_back(struct browser_window *bw, struct history *history); void history_forward(struct browser_window *bw, struct history *history); diff --git a/desktop/netsurf.c b/desktop/netsurf.c index 9acddaf87..192ddc185 100644 --- a/desktop/netsurf.c +++ b/desktop/netsurf.c @@ -28,10 +28,13 @@ #include #include +#include + #include "utils/config.h" #include "utils/utsname.h" #include "content/fetch.h" #include "content/fetchcache.h" +#include "content/llcache.h" #include "content/urldb.h" #include "desktop/netsurf.h" #include "desktop/browser.h" @@ -44,39 +47,11 @@ bool netsurf_quit = false; bool verbose_log = false; -static void netsurf_poll(void); -static void lib_init(void); - - -/** - * Gui NetSurf main(). - */ - -int netsurf_main(int argc, char** argv) +static void *netsurf_lwc_alloc(void *ptr, size_t len, void *pw) { - netsurf_init(argc, argv); - - netsurf_main_loop(); - - netsurf_exit(); - - return EXIT_SUCCESS; + return realloc(ptr, len); } - -/** - * Gui NetSurf main loop. - */ - -int netsurf_main_loop(void) -{ - while (!netsurf_quit) - netsurf_poll(); - - return 0; -} - - /** * Initialise components used by gui NetSurf. */ @@ -126,35 +101,30 @@ void netsurf_init(int argc, char** argv) utsname.nodename, utsname.release, utsname.version, utsname.machine)); - lib_init(); + lwc_initialise(netsurf_lwc_alloc, NULL, 0); url_init(); gui_init(argc, argv); setlocale(LC_ALL, "C"); fetch_init(); - fetchcache_init(); + /** \todo The frontend needs to provide the llcache_query_handler */ + llcache_initialise(NULL, NULL); gui_init2(argc, argv); } + /** - * Poll components which require it. + * Gui NetSurf main loop. */ - -void netsurf_poll(void) +int netsurf_main_loop(void) { - static unsigned int last_clean = 0; - unsigned int current_time = wallclock(); - - /* avoid calling content_clean() more often than once every 5 - * seconds. - */ - if (last_clean + 500 < current_time) { - last_clean = current_time; - content_clean(); + while (!netsurf_quit) { + gui_poll(fetch_active); + fetch_poll(); + llcache_poll(); } - gui_poll(fetch_active); - fetch_poll(); -} + return 0; +} /** * Clean up components used by gui NetSurf. @@ -164,8 +134,6 @@ void netsurf_exit(void) { LOG(("Closing GUI")); gui_quit(); - LOG(("Closing content")); - content_quit(); LOG(("Closing fetches")); fetch_quit(); LOG(("Closing utf8")); @@ -176,18 +144,3 @@ void netsurf_exit(void) } -/** - * Initialises the libraries used in NetSurf. - */ -void lib_init(void) -{ - LOG(("xmlParserVersion %s, LIBXML_VERSION_STRING %s", - xmlParserVersion, LIBXML_VERSION_STRING)); - - /* Using encoding "X-SJIS" (unknown to libxmp2/iconv) instead as - * "Shift-JIS" is rather popular. - */ - if (xmlAddEncodingAlias(xmlGetCharEncodingName( - XML_CHAR_ENCODING_SHIFT_JIS), "X-SJIS") != 0) - die("Failed to add encoding alias"); -} diff --git a/desktop/netsurf.h b/desktop/netsurf.h index 05b870333..33733fa40 100644 --- a/desktop/netsurf.h +++ b/desktop/netsurf.h @@ -29,7 +29,6 @@ extern const int netsurf_version_minor; extern void netsurf_init(int argc, char** argv); extern void netsurf_exit(void); -extern int netsurf_main(int argc, char** argv); extern int netsurf_main_loop(void); #endif diff --git a/desktop/print.c b/desktop/print.c index 4f86bbc22..a5b5c125a 100644 --- a/desktop/print.c +++ b/desktop/print.c @@ -26,6 +26,7 @@ #include #include "content/content.h" +#include "content/hlcache.h" #include "css/utils.h" #include "desktop/options.h" #include "desktop/print.h" @@ -39,11 +40,11 @@ #define DEFAULT_PAGE_HEIGHT 840 #define DEFAULT_COPIES 1 -static struct content *print_init(struct content *, struct print_settings *); -static bool print_apply_settings(struct content *, struct print_settings *); +static hlcache_handle *print_init(hlcache_handle *, struct print_settings *); +static bool print_apply_settings(hlcache_handle *, struct print_settings *); static float page_content_width, page_content_height; -static struct content *printed_content; +static hlcache_handle *printed_content; static float done_height; bool html_redraw_printing = false; @@ -59,7 +60,7 @@ int html_redraw_printing_top_cropped = 0; * \param settings The settings for printing to use * \return true if successful, false otherwise */ -bool print_basic_run(struct content *content, +bool print_basic_run(hlcache_handle *content, const struct printer *printer, struct print_settings *settings) { @@ -69,8 +70,8 @@ bool print_basic_run(struct content *content, if (!print_set_up(content, printer, settings, NULL)) ret = false; - - while (ret && (done_height < printed_content->height) ) + + while (ret && (done_height < content_get_height(printed_content)) ) ret = print_draw_next_page(printer, settings); print_cleanup(content, printer, settings); @@ -88,7 +89,7 @@ bool print_basic_run(struct content *content, * \param height updated to the height of the printed content * \return true if successful, false otherwise */ -bool print_set_up(struct content *content, +bool print_set_up(hlcache_handle *content, const struct printer *printer, struct print_settings *settings, double *height) { @@ -100,7 +101,7 @@ bool print_set_up(struct content *content, print_apply_settings(printed_content, settings); if (height) - *height = printed_content->height; + *height = content_get_height(printed_content); printer->print_begin(settings); @@ -158,11 +159,13 @@ bool print_draw_next_page(const struct printer *printer, * \param settings The settings for printing to use * \return true if successful, false otherwise */ -struct content *print_init(struct content *content, +hlcache_handle *print_init(hlcache_handle *content, struct print_settings *settings) { - struct content* printed_content; - struct content_user *user_sentinel; +// newcache +#if 0 + hlcache_handle* printed_content; + hlcache_handle_user *user_sentinel; content_add_user(content, NULL, (intptr_t) print_init, 0); @@ -173,7 +176,7 @@ struct content *print_init(struct content *content, printed_content->data.html.bw = 0; - user_sentinel = talloc(printed_content, struct content_user); + user_sentinel = talloc(printed_content, hlcache_handle_user); user_sentinel->callback = 0; user_sentinel->p1 = user_sentinel->p2 = 0; user_sentinel->next = 0; @@ -194,6 +197,9 @@ struct content *print_init(struct content *content, printed_content->data.html.font_func = settings->font_func; return printed_content; +#else + return NULL; +#endif } /** @@ -203,7 +209,7 @@ struct content *print_init(struct content *content, * \param settings The settings for printing to use * \return true if successful, false otherwise */ -bool print_apply_settings(struct content *content, +bool print_apply_settings(hlcache_handle *content, struct print_settings *settings) { if (settings == NULL) @@ -222,7 +228,8 @@ bool print_apply_settings(struct content *content, content_reformat(content, page_content_width, 0); LOG(("New layout applied.New height = %d ; New width = %d ", - content->height, content->width)); + content_get_height(content), + content_get_width(content))); return true; } @@ -234,7 +241,7 @@ bool print_apply_settings(struct content *content, * \param printer The printer interface for the printer to be used * \return true if successful, false otherwise */ -bool print_cleanup(struct content *content, const struct printer *printer, +bool print_cleanup(hlcache_handle *content, const struct printer *printer, struct print_settings *settings) { printer->print_end(); @@ -242,12 +249,11 @@ bool print_cleanup(struct content *content, const struct printer *printer, html_redraw_printing = false; if (printed_content) { - content_remove_user(printed_content, NULL, - (intptr_t) print_init, 0); + content_remove_user(printed_content, NULL, print_init); talloc_free(printed_content); } - content_remove_user(content, NULL, (intptr_t)print_init, 0); + content_remove_user(content, NULL, print_init); free((void *)settings->output); free(settings); diff --git a/desktop/print.h b/desktop/print.h index fece526be..63c2935f1 100644 --- a/desktop/print.h +++ b/desktop/print.h @@ -36,7 +36,7 @@ #include "css/css.h" -struct content; +struct hlcache_handle; struct printer; enum { MARGINLEFT = 0, MARGINRIGHT = 1, MARGINTOP = 2, MARGINBOTTOM = 3}; @@ -64,13 +64,13 @@ struct print_settings{ }; -bool print_basic_run(struct content *, const struct printer *, +bool print_basic_run(struct hlcache_handle *, const struct printer *, struct print_settings *); -bool print_set_up(struct content *content, const struct printer *printer, +bool print_set_up(struct hlcache_handle *content, const struct printer *printer, struct print_settings *settings, double *height); bool print_draw_next_page(const struct printer *printer, struct print_settings *settings); -bool print_cleanup(struct content *, const struct printer *, +bool print_cleanup(struct hlcache_handle *, const struct printer *, struct print_settings *settings); struct print_settings *print_make_settings(print_configuration configuration, diff --git a/desktop/save_complete.c b/desktop/save_complete.c index d55cde08f..78b2d7646 100644 --- a/desktop/save_complete.c +++ b/desktop/save_complete.c @@ -35,6 +35,7 @@ #include #include "utils/config.h" #include "content/content.h" +#include "content/hlcache.h" #include "css/css.h" #include "render/box.h" #include "desktop/save_complete.h" @@ -46,14 +47,14 @@ regex_t save_complete_import_re; /** An entry in save_complete_list. */ struct save_complete_entry { - struct content *content; + hlcache_handle *content; struct save_complete_entry *next; /**< Next entry in list */ }; -static bool save_complete_html(struct content *c, const char *path, +static bool save_complete_html(hlcache_handle *c, const char *path, bool index, struct save_complete_entry **list); -static bool save_imported_sheets(struct content *c, const char *path, - struct save_complete_entry **list); +static bool save_imported_sheets(struct nscss_import *imports, uint32_t count, + const char *path, struct save_complete_entry **list); static char * rewrite_stylesheet_urls(const char *source, unsigned int size, int *osize, const char *base, struct save_complete_entry *list); @@ -63,11 +64,11 @@ static bool rewrite_urls(xmlNode *n, const char *base, struct save_complete_entry *list); static bool rewrite_url(xmlNode *n, const char *attr, const char *base, struct save_complete_entry *list); -static bool save_complete_list_add(struct content *content, +static bool save_complete_list_add(hlcache_handle *content, struct save_complete_entry **list); -static struct content * save_complete_list_find(const char *url, +static hlcache_handle * save_complete_list_find(const char *url, struct save_complete_entry *list); -static bool save_complete_list_check(struct content *content, +static bool save_complete_list_check(hlcache_handle *content, struct save_complete_entry *list); /* static void save_complete_list_dump(void); */ static bool save_complete_inventory(const char *path, @@ -81,7 +82,7 @@ static bool save_complete_inventory(const char *path, * \return true on success, false on error and error reported */ -bool save_complete(struct content *c, const char *path) +bool save_complete(hlcache_handle *c, const char *path) { bool result; struct save_complete_entry *list = NULL; @@ -111,50 +112,69 @@ bool save_complete(struct content *c, const char *path) * \return true on success, false on error and error reported */ -bool save_complete_html(struct content *c, const char *path, bool index, +bool save_complete_html(hlcache_handle *c, const char *path, bool index, struct save_complete_entry **list) { + struct html_stylesheet *sheets; + struct content_html_object *objects; + const char *base_url; char filename[256]; - unsigned int i; + unsigned int i, count; xmlDocPtr doc; bool res; - if (c->type != CONTENT_HTML) + if (content_get_type(c) != CONTENT_HTML) return false; if (save_complete_list_check(c, *list)) return true; - + + base_url = html_get_base_url(c); + /* save stylesheets, ignoring the base and adblocking sheets */ - for (i = STYLESHEET_START; i != c->data.html.stylesheet_count; i++) { - struct content *css = c->data.html.stylesheets[i].c; + sheets = html_get_stylesheets(c, &count); + + for (i = STYLESHEET_START; i != count; i++) { + hlcache_handle *css; + const char *css_data; + unsigned long css_size; char *source; int source_len; - bool is_style; + struct nscss_import *imports; + uint32_t import_count; + + if (sheets[i].type == HTML_STYLESHEET_INTERNAL) { + if (save_imported_sheets( + sheets[i].data.internal->imports, + sheets[i].data.internal->import_count, + path, list) == false) + return false; + + continue; + } + + css = sheets[i].data.external; if (!css) continue; if (save_complete_list_check(css, *list)) continue; - is_style = (strcmp(css->url, c->data.html.base_url) == 0); - - if (is_style == false) { - if (!save_complete_list_add(css, list)) { - warn_user("NoMemory", 0); - return false; - } + if (!save_complete_list_add(css, list)) { + warn_user("NoMemory", 0); + return false; } - if (!save_imported_sheets(css, path, list)) + imports = nscss_get_imports(css, &import_count); + if (!save_imported_sheets(imports, import_count, path, list)) return false; - if (is_style) - continue; /* don't save