summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/llcache.c2896
1 files changed, 1420 insertions, 1476 deletions
diff --git a/content/llcache.c b/content/llcache.c
index 503fea40c..29f1a557c 100644
--- a/content/llcache.c
+++ b/content/llcache.c
@@ -144,6 +144,7 @@ struct llcache_object {
struct llcache_s {
/** Handler for fetch-related queries */
llcache_query_callback query_cb;
+
/** Data for fetch-related query handler */
void *query_cb_pw;
@@ -164,617 +165,785 @@ static lwc_string *llcache_file_lwc;
static lwc_string *llcache_about_lwc;
static lwc_string *llcache_resource_lwc;
+/* forward referenced callback function */
+static void llcache_fetch_callback(const fetch_msg *msg, void *p);
+
+/******************************************************************************
+ * 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
+ */
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);
+ llcache_object_user **user)
+{
+ llcache_handle *h;
+ llcache_object_user *u;
-static nserror llcache_object_retrieve(nsurl *url, uint32_t flags,
- nsurl *referer, const llcache_post_data *post,
- uint32_t redirect_count, llcache_object **result);
-static nserror llcache_object_retrieve_from_cache(nsurl *url,
- uint32_t flags, nsurl *referer,
- const llcache_post_data *post, uint32_t redirect_count,
- 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(llcache_object *source,
- llcache_object *destination, bool deep);
-static nserror llcache_object_fetch(llcache_object *object, uint32_t flags,
- nsurl *referer, const llcache_post_data *post,
- uint32_t redirect_count);
-static nserror llcache_object_refetch(llcache_object *object);
+ h = calloc(1, sizeof(llcache_handle));
+ if (h == NULL)
+ return NSERROR_NOMEM;
-static nserror llcache_object_new(nsurl *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 llcache_object_user *llcache_object_find_user(
- const llcache_handle *handle);
-
-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 bool llcache_object_in_list(const llcache_object *object,
- const llcache_object *list);
+ u = calloc(1, sizeof(llcache_object_user));
+ if (u == NULL) {
+ free(h);
+ return NSERROR_NOMEM;
+ }
-static nserror llcache_object_notify_users(llcache_object *object);
+ h->cb = cb;
+ h->pw = pw;
-static nserror llcache_object_snapshot(llcache_object *object,
- llcache_object **snapshot);
+ u->handle = h;
-static nserror llcache_post_data_clone(const llcache_post_data *orig,
- llcache_post_data **clone);
+#ifdef LLCACHE_TRACE
+ LOG(("Created user %p (%p, %p, %p)", u, h, (void *) cb, pw));
+#endif
-static nserror llcache_query_handle_response(bool proceed, void *cbpw);
+ *user = u;
-static void llcache_fetch_callback(const fetch_msg *msg, void *p);
-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 uint8_t *data, size_t len,
- char **name, char **value);
-static nserror llcache_fetch_parse_header(llcache_object *object,
- const uint8_t *data, size_t len, char **name, char **value);
-static nserror llcache_fetch_process_header(llcache_object *object,
- const uint8_t *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);
+ return NSERROR_OK;
+}
-/* Destroy headers */
-static inline void llcache_destroy_headers(llcache_object *object)
+/**
+ * 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
+ */
+static nserror llcache_object_user_destroy(llcache_object_user *user)
{
- while (object->num_headers > 0) {
- object->num_headers--;
+#ifdef LLCACHE_TRACE
+ LOG(("Destroyed user %p", user));
+#endif
+
+ assert(user->next == NULL);
+ assert(user->prev == NULL);
+
+ if (user->handle != NULL)
+ free(user->handle);
- free(object->headers[object->num_headers].name);
- free(object->headers[object->num_headers].value);
- }
- free(object->headers);
- object->headers = NULL;
+ free(user);
+
+ return NSERROR_OK;
}
-/* Invalidate cache control data */
-static inline void llcache_invalidate_cache_control_data(llcache_object *object)
+/**
+ * Remove a user from a low-level cache object
+ *
+ * \param object Object to remove user from
+ * \param user User to remove
+ * \return NSERROR_OK.
+ */
+static nserror llcache_object_remove_user(llcache_object *object,
+ llcache_object_user *user)
{
- free(object->cache.etag);
- memset(&(object->cache), 0, sizeof(llcache_cache_control));
+ assert(user != NULL);
+ assert(object != NULL);
+ assert(object->users != NULL);
+ assert(user->handle == NULL || user->handle->object == object);
+ assert((user->prev != NULL) || (object->users == user));
+
+ if (user == object->users)
+ object->users = user->next;
+ else
+ user->prev->next = user->next;
- object->cache.age = INVALID_AGE;
- object->cache.max_age = INVALID_AGE;
+ if (user->next != NULL)
+ user->next->prev = user->prev;
+
+ user->next = user->prev = NULL;
+
+#ifdef LLCACHE_TRACE
+ LOG(("Removing user %p from %p", user, object));
+#endif
+
+ return NSERROR_OK;
}
+/**
+ * Iterate the users of an object, calling their callbacks.
+ *
+ * \param object The object to iterate
+ * \param event The event to pass to the callback.
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+static nserror llcache_send_event_to_users(llcache_object *object,
+ llcache_event *event)
+{
+ nserror error = NSERROR_OK;
+ llcache_object_user *user, *next_user;
+
+ user = object->users;
+ while (user != NULL) {
+ user->iterator_target = true;
-/******************************************************************************
- * Public API *
- ******************************************************************************/
+ error = user->handle->cb(user->handle, event,
+ user->handle->pw);
-/* See llcache.h for documentation */
-nserror
-llcache_initialise(llcache_query_callback cb, void *pw, uint32_t llcache_limit)
-{
- llcache = calloc(1, sizeof(struct llcache_s));
- if (llcache == NULL) {
- return NSERROR_NOMEM;
- }
+ next_user = user->next;
- llcache->query_cb = cb;
- llcache->query_cb_pw = pw;
- llcache->limit = llcache_limit;
+ user->iterator_target = false;
- /* Create static scheme strings */
- if (lwc_intern_string("file", SLEN("file"),
- &llcache_file_lwc) != lwc_error_ok)
- return NSERROR_NOMEM;
+ if (user->queued_for_delete) {
+ llcache_object_remove_user(object, user);
+ llcache_object_user_destroy(user);
+ }
- if (lwc_intern_string("about", SLEN("about"),
- &llcache_about_lwc) != lwc_error_ok)
- return NSERROR_NOMEM;
+ if (error != NSERROR_OK)
+ break;
- if (lwc_intern_string("resource", SLEN("resource"),
- &llcache_resource_lwc) != lwc_error_ok)
+ user = next_user;
+ }
+
+ return error;
+}
+
+/**
+ * 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
+ */
+static nserror llcache_object_new(nsurl *url, llcache_object **result)
+{
+ llcache_object *obj = calloc(1, sizeof(llcache_object));
+ if (obj == NULL)
return NSERROR_NOMEM;
- LOG(("llcache initialised with a limit of %d bytes", llcache_limit));
+#ifdef LLCACHE_TRACE
+ LOG(("Created object %p (%s)", obj, nsurl_access(url)));
+#endif
+
+ obj->url = nsurl_ref(url);
+
+ *result = obj;
return NSERROR_OK;
}
-/* See llcache.h for documentation */
-void llcache_finalise(void)
+/**
+ * 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
+ */
+static nserror llcache_post_data_clone(const llcache_post_data *orig,
+ llcache_post_data **clone)
{
- llcache_object *object, *next;
-
- /* Clean uncached objects */
- for (object = llcache->uncached_objects; object != NULL; object = next) {
- llcache_object_user *user, *next_user;
+ llcache_post_data *post_clone;
- next = object->next;
+ post_clone = calloc(1, sizeof(llcache_post_data));
+ if (post_clone == NULL)
+ return NSERROR_NOMEM;
- for (user = object->users; user != NULL; user = next_user) {
- next_user = user->next;
+ post_clone->type = orig->type;
- if (user->handle != NULL)
- free(user->handle);
+ /* 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);
- free(user);
+ return NSERROR_NOMEM;
}
+ } else {
+ post_clone->data.multipart = fetch_multipart_data_clone(
+ orig->data.multipart);
+ if (post_clone->data.multipart == NULL) {
+ free(post_clone);
- /* Fetch system has already been destroyed */
- object->fetch.fetch = NULL;
-
- llcache_object_destroy(object);
+ return NSERROR_NOMEM;
+ }
}
- /* Clean cached objects */
- for (object = llcache->cached_objects; object != NULL; object = next) {
- llcache_object_user *user, *next_user;
+ *clone = post_clone;
- next = object->next;
+ return NSERROR_OK;
+}
- for (user = object->users; user != NULL; user = next_user) {
- next_user = user->next;
+/**
+ * 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
+ */
+static nserror llcache_fetch_split_header(const uint8_t *data, size_t len,
+ char **name, char **value)
+{
+ char *n, *v;
+ const uint8_t *colon;
- if (user->handle != NULL)
- free(user->handle);
+ /* Find colon */
+ colon = (const uint8_t *) strchr((const char *) data, ':');
+ if (colon == NULL) {
+ /* Failed, assume a key with no value */
+ n = strdup((const char *) data);
+ if (n == NULL)
+ return NSERROR_NOMEM;
- free(user);
+ v = strdup("");
+ if (v == NULL) {
+ free(n);
+ return NSERROR_NOMEM;
}
+ } else {
+ /* Split header into name & value */
- /* Fetch system has already been destroyed */
- object->fetch.fetch = NULL;
+ /* Strip leading whitespace from name */
+ while (data[0] == ' ' || data[0] == '\t' ||
+ data[0] == '\r' || data[0] == '\n') {
+ data++;
+ }
- llcache_object_destroy(object);
- }
+ /* Strip trailing whitespace from name */
+ while (colon > data && (colon[-1] == ' ' ||
+ colon[-1] == '\t' || colon[-1] == '\r' ||
+ colon[-1] == '\n'))
+ colon--;
- /* Unref static scheme lwc strings */
- lwc_string_unref(llcache_file_lwc);
- lwc_string_unref(llcache_about_lwc);
- lwc_string_unref(llcache_resource_lwc);
+ n = strndup((const char *) data, colon - data);
+ if (n == NULL)
+ return NSERROR_NOMEM;
- free(llcache);
- llcache = NULL;
-}
+ /* Find colon again */
+ while (*colon != ':') {
+ colon++;
+ }
-/* See llcache.h for documentation */
-nserror llcache_poll(void)
-{
- llcache_object *object;
-
- fetch_poll();
-
- /* Catch new users up with state of objects */
- for (object = llcache->cached_objects; object != NULL;
- object = object->next) {
- llcache_object_notify_users(object);
- }
+ /* Skip over colon and any subsequent whitespace */
+ do {
+ colon++;
+ } while (*colon == ' ' || *colon == '\t' ||
+ *colon == '\r' || *colon == '\n');
- for (object = llcache->uncached_objects; object != NULL;
- object = object->next) {
- llcache_object_notify_users(object);
+ /* 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((const char *) colon, len - (colon - data));
+ if (v == NULL) {
+ free(n);
+ return NSERROR_NOMEM;
+ }
}
+ *name = n;
+ *value = v;
+
return NSERROR_OK;
}
-/* See llcache.h for documentation */
-nserror llcache_handle_retrieve(nsurl *url, uint32_t flags,
- nsurl *referer, const llcache_post_data *post,
- llcache_handle_callback cb, void *pw,
- llcache_handle **result)
+/**
+ * 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
+ *
+ * \note This function also has the side-effect of updating
+ * the cache control data for the object if an interesting
+ * header is encountered
+ */
+static nserror llcache_fetch_parse_header(llcache_object *object,
+ const uint8_t *data, size_t len, char **name, char **value)
{
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;
+ /* Set fetch response time if not already set */
+ if (object->cache.res_time == 0)
+ object->cache.res_time = time(NULL);
- /* Create a new object user */
- error = llcache_object_user_new(cb, pw, &user);
+ /* Decompose header into name-value pair */
+ error = llcache_fetch_split_header(data, len, name, value);
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, 0, &object);
- if (error != NSERROR_OK) {
- llcache_object_user_destroy(user);
- return error;
- }
+ /* Parse cache headers to populate cache control data */
+#define SKIP_ST(p) while (*p != '\0' && (*p == ' ' || *p == '\t')) p++
- /* Add user to object */
- llcache_object_add_user(object, user);
+ 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;
- *result = user->handle;
+ while (*comma != '\0') {
+ while (*comma != '\0' && *comma != ',')
+ comma++;
- return NSERROR_OK;
-}
+ 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 = LLCACHE_VALIDATE_ALWAYS;
+ else if (7 < comma - start &&
+ strncasecmp(start, "max-age", 7) == 0) {
+ /* Find '=' */
+ while (start < comma && *start != '=')
+ start++;
-/* See llcache.h for documentation */
-nserror llcache_handle_change_callback(llcache_handle *handle,
- llcache_handle_callback cb, void *pw)
-{
- handle->cb = cb;
- handle->pw = pw;
+ /* Skip over it */
+ start++;
- return NSERROR_OK;
-}
+ /* Skip whitespace */
+ SKIP_ST(start);
-/* See llcache.h for documentation */
-nserror llcache_handle_release(llcache_handle *handle)
-{
- nserror error = NSERROR_OK;
- llcache_object *object = handle->object;
- llcache_object_user *user = llcache_object_find_user(handle);
+ if (start < comma)
+ object->cache.max_age = atoi(start);
+ }
- assert(user != NULL);
+ if (*comma != '\0') {
+ /* Skip past comma */
+ comma++;
+ /* Skip whitespace */
+ SKIP_ST(comma);
+ }
- if (user->iterator_target) {
- /* Can't remove / delete user object if it's
- * the target of an iterator */
- user->queued_for_delete = true;
- } else {
- /* Remove the user from the object and destroy it */
- error = llcache_object_remove_user(object, user);
- if (error == NSERROR_OK) {
- error = llcache_object_user_destroy(user);
+ /* 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);
}
-
- return error;
+
+#undef SKIP_ST
+
+ return NSERROR_OK;
}
-/* See llcache.h for documentation */
-nserror llcache_handle_clone(llcache_handle *handle, llcache_handle **result)
+/* Destroy headers */
+static inline void llcache_destroy_headers(llcache_object *object)
{
- nserror error;
- llcache_object_user *newuser;
-
- error = llcache_object_user_new(handle->cb, handle->pw, &newuser);
- if (error == NSERROR_OK) {
- llcache_object_add_user(handle->object, newuser);
- newuser->handle->state = handle->state;
- *result = newuser->handle;
+ while (object->num_headers > 0) {
+ object->num_headers--;
+
+ free(object->headers[object->num_headers].name);
+ free(object->headers[object->num_headers].value);
}
-
- return error;
+ free(object->headers);
+ object->headers = NULL;
}
-/* See llcache.h for documentation */
-nserror llcache_handle_abort(llcache_handle *handle)
+/* Invalidate cache control data */
+static inline void llcache_invalidate_cache_control_data(llcache_object *object)
{
- llcache_object_user *user = llcache_object_find_user(handle);
- llcache_object *object = handle->object, *newobject;
- nserror error = NSERROR_OK;
- bool all_alone = true;
-
- /* Determine if we are the only user */
- if (user->prev != NULL)
- all_alone = false;
- if (user->next != NULL)
- all_alone = false;
-
- if (all_alone == false) {
- /* We must snapshot this object */
- error = llcache_object_snapshot(object, &newobject);
- if (error != NSERROR_OK)
- return error;
+ free(object->cache.etag);
+ memset(&(object->cache), 0, sizeof(llcache_cache_control));
- /* Move across to the new object */
- if (user->iterator_target) {
- /* User is current iterator target, clone it */
- llcache_object_user *newuser =
- calloc(1, sizeof(llcache_object_user));
- if (newuser == NULL) {
- llcache_object_destroy(newobject);
- return NSERROR_NOMEM;
- }
+ object->cache.age = INVALID_AGE;
+ object->cache.max_age = INVALID_AGE;
+}
- /* Move handle across to clone */
- newuser->handle = user->handle;
- user->handle = NULL;
+/**
+ * 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
+ */
+static nserror llcache_fetch_process_header(llcache_object *object,
+ const uint8_t *data, size_t len)
+{
+ nserror error;
+ char *name, *value;
+ llcache_header *temp;
- /* Mark user as needing deletion */
- user->queued_for_delete = true;
+ /* The headers for multiple HTTP responses may be delivered to us if
+ * the fetch layer receives a 401 response for which it has
+ * authentication credentials. This will result in a silent re-request
+ * after which we'll receive the actual response headers for the
+ * object we want to fetch (assuming that the credentials were correct
+ * of course)
+ *
+ * Therefore, if the header is an HTTP response start marker, then we
+ * must discard any headers we've read so far, reset the cache data
+ * that we might have computed, and start again.
+ */
+ /** \todo Properly parse the response line */
+ if (strncmp((const char *) data, "HTTP/", SLEN("HTTP/")) == 0) {
+ time_t req_time = object->cache.req_time;
- llcache_object_add_user(newobject, newuser);
- } else {
- llcache_object_remove_user(object, user);
- llcache_object_add_user(newobject, user);
- }
-
- /* Add new object to uncached list */
- llcache_object_add_to_list(newobject,
- &llcache->uncached_objects);
- } else {
- /* We're the only user, so abort any fetch in progress */
- if (object->fetch.fetch != NULL) {
- fetch_abort(object->fetch.fetch);
- object->fetch.fetch = NULL;
- }
-
- object->fetch.state = LLCACHE_FETCH_COMPLETE;
-
- /* Invalidate cache control data */
llcache_invalidate_cache_control_data(object);
- }
-
- return error;
-}
-
-/* See llcache.h for documentation */
-nserror llcache_handle_force_stream(llcache_handle *handle)
-{
- llcache_object_user *user = llcache_object_find_user(handle);
- llcache_object *object = handle->object;
- /* Cannot stream if there are multiple users */
- if (user->prev != NULL || user->next != NULL)
- return NSERROR_OK;
+ /* Restore request time, so we compute object's age correctly */
+ object->cache.req_time = req_time;
- /* Forcibly uncache this object */
- if (llcache_object_in_list(object, llcache->cached_objects)) {
- llcache_object_remove_from_list(object,
- &llcache->cached_objects);
- llcache_object_add_to_list(object, &llcache->uncached_objects);
+ llcache_destroy_headers(object);
}
- object->fetch.flags |= LLCACHE_RETRIEVE_STREAM_DATA;
-
- return NSERROR_OK;
-}
+ error = llcache_fetch_parse_header(object, data, len, &name, &value);
+ if (error != NSERROR_OK)
+ return error;
-/* See llcache.h for documentation */
-nserror llcache_handle_invalidate_cache_data(llcache_handle *handle)
-{
- if (handle->object != NULL && handle->object->fetch.fetch == NULL &&
- handle->object->cache.no_cache ==
- LLCACHE_VALIDATE_FRESH) {
- handle->object->cache.no_cache = LLCACHE_VALIDATE_ONCE;
+ /* 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;
}
- return NSERROR_OK;
-}
+ object->headers = temp;
-/* See llcache.h for documentation */
-nsurl *llcache_handle_get_url(const llcache_handle *handle)
-{
- return handle->object != NULL ? handle->object->url : NULL;
-}
+ object->headers[object->num_headers].name = name;
+ object->headers[object->num_headers].value = value;
-/* See llcache.h for documentation */
-const uint8_t *llcache_handle_get_source_data(const llcache_handle *handle,
- size_t *size)
-{
- *size = handle->object != NULL ? handle->object->source_len : 0;
+ object->num_headers++;
- return handle->object != NULL ? handle->object->source_data : NULL;
+ return NSERROR_OK;
}
-/* See llcache.h for documentation */
-const char *llcache_handle_get_header(const llcache_handle *handle,
- const char *key)
+/**
+ * (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
+ */
+static nserror llcache_object_refetch(llcache_object *object)
{
- const llcache_object *object = handle->object;
- size_t i;
-
- if (object == NULL)
- return NULL;
+ const char *urlenc = NULL;
+ struct fetch_multipart_data *multipart = NULL;
+ char **headers = NULL;
+ int header_idx = 0;
- /* 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;
+ 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;
}
- return NULL;
-}
+ /* Generate cache-control headers */
+ headers = malloc(3 * sizeof(char *));
+ if (headers == NULL)
+ return NSERROR_NOMEM;
-/* See llcache.h for documentation */
-bool llcache_handle_references_same_object(const llcache_handle *a,
- const llcache_handle *b)
-{
- return a->object == b->object;
-}
+ if (object->cache.etag != NULL) {
+ const size_t len = SLEN("If-None-Match: ") +
+ strlen(object->cache.etag) + 1;
-/******************************************************************************
- * Low-level cache internals *
- ******************************************************************************/
+ headers[header_idx] = malloc(len);
+ if (headers[header_idx] == NULL) {
+ free(headers);
+ return NSERROR_NOMEM;
+ }
-/**
- * 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_handle *h;
- llcache_object_user *u;
+ snprintf(headers[header_idx], len, "If-None-Match: %s",
+ object->cache.etag);
- h = calloc(1, sizeof(llcache_handle));
- if (h == NULL)
- return NSERROR_NOMEM;
+ 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;
- u = calloc(1, sizeof(llcache_object_user));
- if (u == NULL) {
- free(h);
- return NSERROR_NOMEM;
+ 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;
- h->cb = cb;
- h->pw = pw;
+ /* Reset cache control data */
+ llcache_invalidate_cache_control_data(object);
+ object->cache.req_time = time(NULL);
- u->handle = h;
+ /* Reset fetch state */
+ object->fetch.state = LLCACHE_FETCH_INIT;
#ifdef LLCACHE_TRACE
- LOG(("Created user %p (%p, %p, %p)", u, h, (void *) cb, pw));
+ LOG(("Refetching %p", object));
#endif
- *user = u;
+ /* 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,
+ (const char **) 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;
}
/**
- * Destroy an object user
+ * Kick-off a fetch for an object
*
- * \param user User to destroy
+ * \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
+ * \param redirect_count Number of redirects followed so far
* \return NSERROR_OK on success, appropriate error otherwise
*
- * \pre User is not attached to an object
+ * \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_user_destroy(llcache_object_user *user)
+static nserror llcache_object_fetch(llcache_object *object, uint32_t flags,
+ nsurl *referer, const llcache_post_data *post,
+ uint32_t redirect_count)
{
+ nserror error;
+ nsurl *referer_clone = NULL;
+ llcache_post_data *post_clone = NULL;
+
#ifdef LLCACHE_TRACE
- LOG(("Destroyed user %p", user));
+ LOG(("Starting fetch for %p", object));
#endif
-
- assert(user->next == NULL);
- assert(user->prev == NULL);
-
- if (user->handle != NULL)
- free(user->handle);
- free(user);
+ if (post != NULL) {
+ error = llcache_post_data_clone(post, &post_clone);
+ if (error != NSERROR_OK)
+ return error;
+ }
- return NSERROR_OK;
+ if (referer != NULL)
+ referer_clone = nsurl_ref(referer);
+
+ object->fetch.flags = flags;
+ object->fetch.referer = referer_clone;
+ object->fetch.post = post_clone;
+ object->fetch.redirect_count = redirect_count;
+
+ return llcache_object_refetch(object);
}
/**
- * Iterate the users of an object, calling their callbacks.
+ * Destroy a low-level cache object
*
- * \param object The object to iterate
- * \param event The event to pass to the callback.
- * \return NSERROR_OK on success, appropriate error otherwise.
+ * \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)
*/
-static nserror llcache_send_event_to_users(llcache_object *object,
- llcache_event *event)
+static nserror llcache_object_destroy(llcache_object *object)
{
- nserror error = NSERROR_OK;
- llcache_object_user *user, *next_user;
-
- user = object->users;
- while (user != NULL) {
- user->iterator_target = true;
+ size_t i;
- error = user->handle->cb(user->handle, event,
- user->handle->pw);
+#ifdef LLCACHE_TRACE
+ LOG(("Destroying object %p", object));
+#endif
- next_user = user->next;
+ nsurl_unref(object->url);
+ free(object->source_data);
- user->iterator_target = false;
+ if (object->fetch.fetch != NULL) {
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ }
- if (user->queued_for_delete) {
- llcache_object_remove_user(object, user);
- llcache_object_user_destroy(user);
+ if (object->fetch.referer != NULL)
+ nsurl_unref(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);
}
- if (error != NSERROR_OK)
- break;
+ free(object->fetch.post);
+ }
- user = next_user;
+ free(object->cache.etag);
+
+ for (i = 0; i < object->num_headers; i++) {
+ free(object->headers[i].name);
+ free(object->headers[i].value);
}
-
- return error;
+ free(object->headers);
+
+ free(object);
+
+ return NSERROR_OK;
}
/**
- * Retrieve an object from the cache, fetching it if necessary.
+ * Add a low-level cache object to a cache list
*
- * \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 redirect_count Number of redirects followed so far
- * \param result Pointer to location to recieve retrieved object
- * \return NSERROR_OK on success, appropriate error otherwise
+ * \param object Object to add
+ * \param list List to add to
+ * \return NSERROR_OK
*/
-nserror llcache_object_retrieve(nsurl *url, uint32_t flags,
- nsurl *referer, const llcache_post_data *post,
- uint32_t redirect_count, llcache_object **result)
+static nserror llcache_object_add_to_list(llcache_object *object,
+ llcache_object **list)
{
- nserror error;
- llcache_object *obj;
- bool has_query;
- nsurl *defragmented_url;
+ object->prev = NULL;
+ object->next = *list;
+
+ if (*list != NULL)
+ (*list)->prev = object;
+ *list = object;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Determine if an object is still fresh
+ *
+ * \param object Object to consider
+ * \return True if object is still fresh, false otherwise
+ */
+static 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;
#ifdef LLCACHE_TRACE
- LOG(("Retrieve %s (%x, %s, %p)", url, flags, referer, post));
+ LOG(("%p: (%d > %d || %d != %d)", object,
+ freshness_lifetime, current_age,
+ object->fetch.state, LLCACHE_FETCH_COMPLETE));
#endif
- /**
- * Caching Rules:
+ /* The object is fresh if:
*
- * 1) Forced fetches are never cached
- * 2) POST requests are never cached
+ * it was not forbidden from being returned from the cache
+ * unvalidated (i.e. the response contained a no-cache directive)
+ *
+ * and:
+ *
+ * its current age is within the freshness lifetime
+ * or if we're still fetching the object
*/
+ return (cd->no_cache == LLCACHE_VALIDATE_FRESH &&
+ (freshness_lifetime > current_age ||
+ object->fetch.state != LLCACHE_FETCH_COMPLETE));
+}
- /* Look for a query segment */
- has_query = nsurl_has_component(url, NSURL_QUERY);
+/**
+ * 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
+ *
+ * \post If \a deep is false, then any pointers in \a source will be set to NULL
+ */
+static nserror llcache_object_clone_cache_data(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;
- /* Get rid of any url fragment */
- if (nsurl_has_component(url, NSURL_FRAGMENT)) {
- error = nsurl_defragment(url, &defragmented_url);
- if (error != NSERROR_OK)
- return error;
- } else {
- defragmented_url = nsurl_ref(url);
+ if (deep) {
+ /* Copy the etag */
+ etag = strdup(source->cache.etag);
+ if (etag == NULL)
+ return NSERROR_NOMEM;
+ } else {
+ /* Destination takes ownership */
+ source->cache.etag = NULL;
+ }
+
+ if (destination->cache.etag != NULL)
+ free(destination->cache.etag);
+
+ destination->cache.etag = etag;
}
- if (flags & LLCACHE_RETRIEVE_FORCE_FETCH || post != NULL) {
- /* Create new object */
- error = llcache_object_new(defragmented_url, &obj);
- if (error != NSERROR_OK) {
- nsurl_unref(defragmented_url);
- return error;
- }
+ destination->cache.req_time = source->cache.req_time;
+ destination->cache.res_time = source->cache.res_time;
- /* Attempt to kick-off fetch */
- error = llcache_object_fetch(obj, flags, referer, post,
- redirect_count);
- if (error != NSERROR_OK) {
- llcache_object_destroy(obj);
- nsurl_unref(defragmented_url);
- return error;
- }
+ if (source->cache.date != 0)
+ destination->cache.date = source->cache.date;
- /* Add new object to uncached list */
- llcache_object_add_to_list(obj, &llcache->uncached_objects);
- } else {
- error = llcache_object_retrieve_from_cache(defragmented_url,
- flags, referer, post, redirect_count, &obj);
- if (error != NSERROR_OK) {
- nsurl_unref(defragmented_url);
- return error;
- }
+ if (source->cache.expires != 0)
+ destination->cache.expires = source->cache.expires;
- /* Returned object is already in the cached list */
- }
-
- obj->has_query = has_query;
+ if (source->cache.age != INVALID_AGE)
+ destination->cache.age = source->cache.age;
-#ifdef LLCACHE_TRACE
- LOG(("Retrieved %p", obj));
-#endif
-
- *result = obj;
-
- nsurl_unref(defragmented_url);
+ if (source->cache.max_age != INVALID_AGE)
+ destination->cache.max_age = source->cache.max_age;
+
+ if (source->cache.no_cache != LLCACHE_VALIDATE_FRESH)
+ 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;
}
@@ -789,7 +958,7 @@ nserror llcache_object_retrieve(nsurl *url, uint32_t flags,
* \param result Pointer to location to recieve retrieved object
* \return NSERROR_OK on success, appropriate error otherwise
*/
-nserror llcache_object_retrieve_from_cache(nsurl *url, uint32_t flags,
+static nserror llcache_object_retrieve_from_cache(nsurl *url, uint32_t flags,
nsurl *referer, const llcache_post_data *post,
uint32_t redirect_count, llcache_object **result)
{
@@ -885,397 +1054,672 @@ nserror llcache_object_retrieve_from_cache(nsurl *url, uint32_t flags,
}
/**
- * Determine if an object is still fresh
+ * Retrieve an object from the cache, fetching it if necessary.
*
- * \param object Object to consider
- * \return True if object is still fresh, false otherwise
+ * \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 redirect_count Number of redirects followed so far
+ * \param result Pointer to location to recieve retrieved object
+ * \return NSERROR_OK on success, appropriate error otherwise
*/
-bool llcache_object_is_fresh(const llcache_object *object)
+static nserror llcache_object_retrieve(nsurl *url, uint32_t flags,
+ nsurl *referer, const llcache_post_data *post,
+ uint32_t redirect_count, llcache_object **result)
{
- 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;
+ nserror error;
+ llcache_object *obj;
+ bool has_query;
+ nsurl *defragmented_url;
#ifdef LLCACHE_TRACE
- LOG(("%p: (%d > %d || %d != %d)", object,
- freshness_lifetime, current_age,
- object->fetch.state, LLCACHE_FETCH_COMPLETE));
+ LOG(("Retrieve %s (%x, %s, %p)", url, flags, referer, post));
#endif
- /* The object is fresh if:
- *
- * it was not forbidden from being returned from the cache
- * unvalidated (i.e. the response contained a no-cache directive)
- *
- * and:
+ /**
+ * Caching Rules:
*
- * its current age is within the freshness lifetime
- * or if we're still fetching the object
+ * 1) Forced fetches are never cached
+ * 2) POST requests are never cached
*/
- return (cd->no_cache == LLCACHE_VALIDATE_FRESH &&
- (freshness_lifetime > current_age ||
- object->fetch.state != LLCACHE_FETCH_COMPLETE));
+
+ /* Look for a query segment */
+ has_query = nsurl_has_component(url, NSURL_QUERY);
+
+ /* Get rid of any url fragment */
+ if (nsurl_has_component(url, NSURL_FRAGMENT)) {
+ error = nsurl_defragment(url, &defragmented_url);
+ if (error != NSERROR_OK)
+ return error;
+ } else {
+ defragmented_url = nsurl_ref(url);
+ }
+
+ if (flags & LLCACHE_RETRIEVE_FORCE_FETCH || post != NULL) {
+ /* Create new object */
+ error = llcache_object_new(defragmented_url, &obj);
+ if (error != NSERROR_OK) {
+ nsurl_unref(defragmented_url);
+ return error;
+ }
+
+ /* Attempt to kick-off fetch */
+ error = llcache_object_fetch(obj, flags, referer, post,
+ redirect_count);
+ if (error != NSERROR_OK) {
+ llcache_object_destroy(obj);
+ nsurl_unref(defragmented_url);
+ return error;
+ }
+
+ /* Add new object to uncached list */
+ llcache_object_add_to_list(obj, &llcache->uncached_objects);
+ } else {
+ error = llcache_object_retrieve_from_cache(defragmented_url,
+ flags, referer, post, redirect_count, &obj);
+ if (error != NSERROR_OK) {
+ nsurl_unref(defragmented_url);
+ return error;
+ }
+
+ /* Returned object is already in the cached list */
+ }
+
+ obj->has_query = has_query;
+
+#ifdef LLCACHE_TRACE
+ LOG(("Retrieved %p", obj));
+#endif
+
+ *result = obj;
+
+ nsurl_unref(defragmented_url);
+
+ return NSERROR_OK;
}
/**
- * Update an object's cache state
+ * Add a user to a low-level cache object
*
- * \param object Object to update cache for
+ * \param object Object to add user to
+ * \param user User to add
* \return NSERROR_OK.
*/
-nserror llcache_object_cache_update(llcache_object *object)
+static nserror llcache_object_add_user(llcache_object *object,
+ llcache_object_user *user)
{
- if (object->cache.date == 0)
- object->cache.date = time(NULL);
+ assert(user->next == NULL);
+ assert(user->prev == NULL);
+
+ user->handle->object = object;
+
+ user->prev = NULL;
+ user->next = object->users;
+
+ if (object->users != NULL)
+ object->users->prev = user;
+ object->users = user;
+
+#ifdef LLCACHE_TRACE
+ LOG(("Adding user %p to %p", user, object));
+#endif
return NSERROR_OK;
}
/**
- * Clone an object's cache data
+ * Handle FETCH_REDIRECT event
*
- * \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
+ * \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
- *
- * \post If \a deep is false, then any pointers in \a source will be set to NULL
*/
-nserror llcache_object_clone_cache_data(llcache_object *source,
- llcache_object *destination, bool deep)
+static nserror llcache_fetch_redirect(llcache_object *object, const char *target,
+ llcache_object **replacement)
{
- /* ETag must be first, as it can fail when deep cloning */
- if (source->cache.etag != NULL) {
- char *etag = source->cache.etag;
+ nserror error;
+ llcache_object *dest;
+ llcache_object_user *user, *next;
+ const llcache_post_data *post = object->fetch.post;
+ nsurl *url;
+ lwc_string *scheme;
+ lwc_string *object_scheme;
+ bool match;
+ /* Extract HTTP response code from the fetch object */
+ long http_code = fetch_http_code(object->fetch.fetch);
- if (deep) {
- /* Copy the etag */
- etag = strdup(source->cache.etag);
- if (etag == NULL)
- return NSERROR_NOMEM;
- } else {
- /* Destination takes ownership */
- source->cache.etag = NULL;
- }
+ /* Abort fetch for this object */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+
+ /* Invalidate the cache control data */
+ llcache_invalidate_cache_control_data(object);
- if (destination->cache.etag != NULL)
- free(destination->cache.etag);
+ /* And mark it complete */
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+
+ /* Forcibly stop redirecting if we've followed too many redirects */
+#define REDIRECT_LIMIT 10
+ if (object->fetch.redirect_count > REDIRECT_LIMIT) {
+ llcache_event event;
- destination->cache.etag = etag;
+ LOG(("Too many nested redirects"));
+
+ event.type = LLCACHE_EVENT_ERROR;
+ event.data.msg = messages_get("BadRedirect");
+
+ return llcache_send_event_to_users(object, &event);
}
+#undef REDIRECT_LIMIT
- destination->cache.req_time = source->cache.req_time;
- destination->cache.res_time = source->cache.res_time;
+ /* Make target absolute */
+ error = nsurl_join(object->url, target, &url);
+ if (error != NSERROR_OK)
+ return error;
- if (source->cache.date != 0)
- destination->cache.date = source->cache.date;
+ /* Reject attempts to redirect from unvalidated to validated schemes
+ * A "validated" scheme is one over which we have some guarantee that
+ * the source is trustworthy. */
+ object_scheme = nsurl_get_component(object->url, NSURL_SCHEME);
+ scheme = nsurl_get_component(url, NSURL_SCHEME);
- if (source->cache.expires != 0)
- destination->cache.expires = source->cache.expires;
+ /* resource: and about: are allowed to redirect anywhere */
+ if ((lwc_string_isequal(object_scheme, llcache_resource_lwc,
+ &match) == lwc_error_ok && match == false) &&
+ (lwc_string_isequal(object_scheme, llcache_about_lwc,
+ &match) == lwc_error_ok && match == false)) {
+ /* file, about and resource are not valid redirect targets */
+ if ((lwc_string_isequal(object_scheme, llcache_file_lwc,
+ &match) == lwc_error_ok && match == true) ||
+ (lwc_string_isequal(object_scheme, llcache_about_lwc,
+ &match) == lwc_error_ok && match == true) ||
+ (lwc_string_isequal(object_scheme, llcache_resource_lwc,
+ &match) == lwc_error_ok && match == true)) {
+ lwc_string_unref(object_scheme);
+ lwc_string_unref(scheme);
+ nsurl_unref(url);
+ return NSERROR_OK;
+ }
+ }
- if (source->cache.age != INVALID_AGE)
- destination->cache.age = source->cache.age;
+ lwc_string_unref(scheme);
+ lwc_string_unref(object_scheme);
- if (source->cache.max_age != INVALID_AGE)
- destination->cache.max_age = source->cache.max_age;
+ /* Bail out if we've no way of handling this URL */
+ if (fetch_can_fetch(url) == false) {
+ nsurl_unref(url);
+ return NSERROR_OK;
+ }
- if (source->cache.no_cache != LLCACHE_VALIDATE_FRESH)
- destination->cache.no_cache = source->cache.no_cache;
-
- if (source->cache.last_modified != 0)
- destination->cache.last_modified = source->cache.last_modified;
+ if (http_code == 301 || http_code == 302 || http_code == 303) {
+ /* 301, 302, 303 redirects are all unconditional GET requests */
+ post = NULL;
+ } else if (http_code != 307 || post != NULL) {
+ /** \todo 300, 305, 307 with POST */
+ nsurl_unref(url);
+ return NSERROR_OK;
+ }
- return NSERROR_OK;
-}
+ /* Attempt to fetch target URL */
+ error = llcache_object_retrieve(url, object->fetch.flags,
+ object->fetch.referer, post,
+ object->fetch.redirect_count + 1, &dest);
-/**
- * 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
- * \param redirect_count Number of redirects followed so far
- * \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,
- nsurl *referer, const llcache_post_data *post,
- uint32_t redirect_count)
-{
- nserror error;
- nsurl *referer_clone = NULL;
- llcache_post_data *post_clone = NULL;
+ /* No longer require url */
+ nsurl_unref(url);
-#ifdef LLCACHE_TRACE
- LOG(("Starting fetch for %p", object));
-#endif
+ if (error != NSERROR_OK)
+ return error;
- if (post != NULL) {
- error = llcache_post_data_clone(post, &post_clone);
- if (error != NSERROR_OK)
- return error;
- }
+ /* Move user(s) to replacement object */
+ for (user = object->users; user != NULL; user = next) {
+ next = user->next;
- if (referer != NULL)
- referer_clone = nsurl_ref(referer);
+ llcache_object_remove_user(object, user);
+ llcache_object_add_user(dest, user);
+ }
- object->fetch.flags = flags;
- object->fetch.referer = referer_clone;
- object->fetch.post = post_clone;
- object->fetch.redirect_count = redirect_count;
+ /* Dest is now our object */
+ *replacement = dest;
- return llcache_object_refetch(object);
+ return NSERROR_OK;
}
/**
- * (Re)fetch an object
- *
- * \param object Object to refetch
- * \return NSERROR_OK on success, appropriate error otherwise
+ * Update an object's cache state
*
- * \pre The fetch parameters in object->fetch must be populated
- */
-nserror llcache_object_refetch(llcache_object *object)
+ * \param object Object to update cache for
+ * \return NSERROR_OK.
+ */
+static nserror llcache_object_cache_update(llcache_object *object)
{
- const char *urlenc = NULL;
- struct fetch_multipart_data *multipart = NULL;
- char **headers = NULL;
- int header_idx = 0;
+ if (object->cache.date == 0)
+ object->cache.date = time(NULL);
- 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;
- }
+ return NSERROR_OK;
+}
- /* Generate cache-control headers */
- headers = malloc(3 * sizeof(char *));
- if (headers == NULL)
- return NSERROR_NOMEM;
+/**
+ * Handle FETCH_NOTMODIFIED event
+ *
+ * \param object Object to process
+ * \param replacement Pointer to location to receive replacement object
+ * \return NSERROR_OK.
+ */
+static nserror llcache_fetch_notmodified(llcache_object *object,
+ llcache_object **replacement)
+{
+ /* There may be no candidate if the server erroneously responded
+ * to an unconditional request with a 304 Not Modified response.
+ * In this case, we simply retain the initial object, having
+ * invalidated it and marked it as complete.
+ */
+ if (object->candidate != NULL) {
+ llcache_object_user *user, *next;
- if (object->cache.etag != NULL) {
- const size_t len = SLEN("If-None-Match: ") +
- strlen(object->cache.etag) + 1;
+ /* Move user(s) to candidate content */
+ for (user = object->users; user != NULL; user = next) {
+ next = user->next;
- headers[header_idx] = malloc(len);
- if (headers[header_idx] == NULL) {
- free(headers);
- return NSERROR_NOMEM;
+ llcache_object_remove_user(object, user);
+ llcache_object_add_user(object->candidate, user);
}
- 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;
+ /* Candidate is no longer a candidate for us */
+ object->candidate->candidate_count--;
- headers[header_idx] = malloc(len);
- if (headers[header_idx] == NULL) {
- while (--header_idx >= 0)
- free(headers[header_idx]);
- free(headers);
- return NSERROR_NOMEM;
+ /* 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);
+ /* Revert no-cache to normal, if required */
+ if (object->candidate->cache.no_cache ==
+ LLCACHE_VALIDATE_ONCE) {
+ object->candidate->cache.no_cache =
+ LLCACHE_VALIDATE_FRESH;
}
- snprintf(headers[header_idx], len, "If-Modified-Since: %s",
- rfc1123_date(object->cache.date));
-
- header_idx++;
+ /* Candidate is now our object */
+ *replacement = object->candidate;
+ object->candidate = NULL;
+ } else {
+ /* There was no candidate: retain object */
+ *replacement = object;
}
- headers[header_idx] = NULL;
- /* Reset cache control data */
+ /* Ensure fetch has stopped */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+
+ /* Invalidate our cache-control data */
llcache_invalidate_cache_control_data(object);
- object->cache.req_time = time(NULL);
- /* Reset fetch state */
- object->fetch.state = LLCACHE_FETCH_INIT;
+ /* Mark it complete */
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
-#ifdef LLCACHE_TRACE
- LOG(("Refetching %p", object));
-#endif
+ /* Old object will be flushed from the cache on the next poll */
- /* 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,
- (const char **) headers);
+ return NSERROR_OK;
+}
- /* Clean up cache-control headers */
- while (--header_idx >= 0)
- free(headers[header_idx]);
- free(headers);
+/**
+ * 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.
+ */
+static 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;
- /* Did we succeed in creating a fetch? */
- if (object->fetch.fetch == 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;
}
/**
- * Create a new low-level cache object
+ * Handle a query response
*
- * \param url URL of object to create
- * \param result Pointer to location to receive result
+ * \param proceed Whether to proceed with fetch
+ * \param cbpw Our context for query
* \return NSERROR_OK on success, appropriate error otherwise
*/
-nserror llcache_object_new(nsurl *url, llcache_object **result)
+static nserror llcache_query_handle_response(bool proceed, void *cbpw)
{
- llcache_object *obj = calloc(1, sizeof(llcache_object));
- if (obj == NULL)
- return NSERROR_NOMEM;
+ llcache_event event;
+ llcache_object *object = cbpw;
-#ifdef LLCACHE_TRACE
- LOG(("Created object %p (%s)", obj, nsurl_access(url)));
-#endif
+ object->fetch.outstanding_query = false;
- obj->url = nsurl_ref(url);
+ /* Refetch, using existing fetch parameters, if client allows us to */
+ if (proceed)
+ return llcache_object_refetch(object);
- *result = obj;
+ /* Invalidate cache-control data */
+ llcache_invalidate_cache_control_data(object);
- return NSERROR_OK;
+ /* Mark it complete */
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+
+ /* Inform client(s) that object fetch failed */
+ event.type = LLCACHE_EVENT_ERROR;
+ /** \todo More appropriate error message */
+ event.data.msg = messages_get("FetchFailed");
+
+ return llcache_send_event_to_users(object, &event);
}
/**
- * Destroy a low-level cache object
- *
- * \param object Object to destroy
- * \return NSERROR_OK on success, appropriate error otherwise
+ * Handle an authentication request
*
- * \pre Object is detached from cache list
- * \pre Object has no users
- * \pre Object is not a candidate (i.e. object::candidate_count == 0)
+ * \param object Object being fetched
+ * \param realm Authentication realm
+ * \return NSERROR_OK on success, appropriate error otherwise.
*/
-nserror llcache_object_destroy(llcache_object *object)
+static nserror llcache_fetch_auth(llcache_object *object, const char *realm)
{
- size_t i;
+ const char *auth;
+ nserror error = NSERROR_OK;
-#ifdef LLCACHE_TRACE
- LOG(("Destroying object %p", object));
-#endif
+ /* Abort fetch for this object */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
- nsurl_unref(object->url);
- free(object->source_data);
+ /* Invalidate cache-control data */
+ llcache_invalidate_cache_control_data(object);
- if (object->fetch.fetch != NULL) {
- fetch_abort(object->fetch.fetch);
- object->fetch.fetch = NULL;
- }
+ /* Destroy headers */
+ llcache_destroy_headers(object);
- if (object->fetch.referer != NULL)
- nsurl_unref(object->fetch.referer);
+ /* If there was no realm, then default to the URL */
+ /** \todo If there was no WWW-Authenticate header, use response body */
+ if (realm == NULL)
+ realm = nsurl_access(object->url);
- 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);
- }
+ auth = urldb_get_auth_details(nsurl_access(object->url), realm);
- free(object->fetch.post);
- }
+ if (auth == NULL || object->fetch.tried_with_auth == true) {
+ /* No authentication details, or tried what we had, so ask */
+ object->fetch.tried_with_auth = false;
- free(object->cache.etag);
+ if (llcache->query_cb != NULL) {
+ llcache_query query;
- for (i = 0; i < object->num_headers; i++) {
- free(object->headers[i].name);
- free(object->headers[i].value);
- }
- free(object->headers);
+ /* Emit query for authentication details */
+ query.type = LLCACHE_QUERY_AUTH;
+ query.url = object->url;
+ query.data.auth.realm = realm;
- free(object);
+ object->fetch.outstanding_query = true;
- return NSERROR_OK;
+ error = llcache->query_cb(&query, llcache->query_cb_pw,
+ llcache_query_handle_response, object);
+ } else {
+ llcache_event event;
+
+ /* Mark object complete */
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+
+ /* Inform client(s) that object fetch failed */
+ event.type = LLCACHE_EVENT_ERROR;
+ /** \todo More appropriate error message */
+ event.data.msg = messages_get("FetchFailed");
+
+ error = llcache_send_event_to_users(object, &event);
+ }
+ } else {
+ /* Flag that we've tried to refetch with credentials, so
+ * that if the fetch fails again, we ask the user again */
+ object->fetch.tried_with_auth = true;
+ error = llcache_object_refetch(object);
+ }
+
+ return error;
}
/**
- * Add a user to a low-level cache object
+ * Handle a TLS certificate verification failure
*
- * \param object Object to add user to
- * \param user User to add
- * \return NSERROR_OK.
+ * \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_object_add_user(llcache_object *object,
- llcache_object_user *user)
+static nserror llcache_fetch_cert_error(llcache_object *object,
+ const struct ssl_cert_info *certs, size_t num)
{
- assert(user->next == NULL);
- assert(user->prev == NULL);
+ nserror error = NSERROR_OK;
- user->handle->object = object;
+ /* Fetch has been stopped, and destroyed. Invalidate object's pointer */
+ object->fetch.fetch = NULL;
- user->prev = NULL;
- user->next = object->users;
+ /* Invalidate cache-control data */
+ llcache_invalidate_cache_control_data(object);
- if (object->users != NULL)
- object->users->prev = user;
- object->users = user;
+ if (llcache->query_cb != NULL) {
+ llcache_query query;
-#ifdef LLCACHE_TRACE
- LOG(("Adding user %p to %p", user, object));
-#endif
+ /* Emit query for TLS */
+ query.type = LLCACHE_QUERY_SSL;
+ query.url = object->url;
+ query.data.ssl.certs = certs;
+ query.data.ssl.num = num;
- return NSERROR_OK;
+ object->fetch.outstanding_query = true;
+
+ error = llcache->query_cb(&query, llcache->query_cb_pw,
+ llcache_query_handle_response, object);
+ } else {
+ llcache_event event;
+
+ /* Mark object complete */
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+
+ /* Inform client(s) that object fetch failed */
+ event.type = LLCACHE_EVENT_ERROR;
+ /** \todo More appropriate error message */
+ event.data.msg = messages_get("FetchFailed");
+
+ error = llcache_send_event_to_users(object, &event);
+ }
+
+ return error;
}
/**
- * Remove a user from a low-level cache object
+ * Handler for fetch events
*
- * \param object Object to remove user from
- * \param user User to remove
- * \return NSERROR_OK.
+ * \param msg Fetch event
+ * \param p Our private data
*/
-nserror llcache_object_remove_user(llcache_object *object,
- llcache_object_user *user)
+static void llcache_fetch_callback(const fetch_msg *msg, void *p)
{
- assert(user != NULL);
- assert(object != NULL);
- assert(object->users != NULL);
- assert(user->handle == NULL || user->handle->object == object);
- assert((user->prev != NULL) || (object->users == user));
-
- if (user == object->users)
- object->users = user->next;
- else
- user->prev->next = user->next;
+ nserror error = NSERROR_OK;
+ llcache_object *object = p;
+ llcache_event event;
- if (user->next != NULL)
- user->next->prev = user->prev;
-
- user->next = user->prev = NULL;
-
#ifdef LLCACHE_TRACE
- LOG(("Removing user %p from %p", user, object));
+ LOG(("Fetch event %d for %p", msg->type, object));
#endif
- return NSERROR_OK;
+ switch (msg->type) {
+ case FETCH_HEADER:
+ /* Received a fetch header */
+ object->fetch.state = LLCACHE_FETCH_HEADERS;
+
+ error = llcache_fetch_process_header(object,
+ msg->data.header_or_data.buf,
+ msg->data.header_or_data.len);
+ break;
+
+ /* 3xx responses */
+ case FETCH_REDIRECT:
+ /* Request resulted in a redirect */
+
+ /* Release candidate, if any */
+ if (object->candidate != NULL) {
+ object->candidate->candidate_count--;
+ object->candidate = NULL;
+ }
+
+ error = llcache_fetch_redirect(object,
+ msg->data.redirect, &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_DATA:
+ /* Received some data */
+ if (object->fetch.state != LLCACHE_FETCH_DATA) {
+ /* On entry into this state, check if we need to
+ * invalidate the cache control data. We are guaranteed
+ * to have received all response headers.
+ *
+ * There are two cases in which we want to suppress
+ * cacheing of an object:
+ *
+ * 1) The HTTP response code is not 200 or 203
+ * 2) The request URI had a query string and the
+ * response headers did not provide an explicit
+ * object expiration time.
+ */
+ long http_code = fetch_http_code(object->fetch.fetch);
+
+ if ((http_code != 200 && http_code != 203) ||
+ (object->has_query &&
+ (object->cache.max_age == INVALID_AGE &&
+ object->cache.expires == 0))) {
+ /* Invalidate cache control data */
+ llcache_invalidate_cache_control_data(object);
+ }
+
+ /* Release candidate, if any */
+ if (object->candidate != NULL) {
+ object->candidate->candidate_count--;
+ object->candidate = NULL;
+ }
+ }
+
+ object->fetch.state = LLCACHE_FETCH_DATA;
+
+ error = llcache_fetch_process_data(object,
+ msg->data.header_or_data.buf,
+ msg->data.header_or_data.len);
+ break;
+ case FETCH_FINISHED:
+ /* Finished fetching */
+ {
+ uint8_t *temp;
+
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ object->fetch.fetch = NULL;
+
+ /* Shrink source buffer to required size */
+ temp = realloc(object->source_data,
+ object->source_len);
+ /* If source_len is 0, then temp may be NULL */
+ if (temp != NULL || object->source_len == 0) {
+ object->source_data = temp;
+ object->source_alloc = object->source_len;
+ }
+
+ llcache_object_cache_update(object);
+ }
+ break;
+
+ /* Out-of-band information */
+ case FETCH_ERROR:
+ /* An error occurred while fetching */
+ /* The fetch has has already been cleaned up by the fetcher */
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ object->fetch.fetch = NULL;
+
+ /* Release candidate, if any */
+ if (object->candidate != NULL) {
+ object->candidate->candidate_count--;
+ object->candidate = NULL;
+ }
+
+ /* Invalidate cache control data */
+ llcache_invalidate_cache_control_data(object);
+
+ /** \todo Consider using errorcode for something */
+
+ event.type = LLCACHE_EVENT_ERROR;
+ event.data.msg = msg->data.error;
+
+ error = llcache_send_event_to_users(object, &event);
+
+ break;
+ case FETCH_PROGRESS:
+ /* Progress update */
+ event.type = LLCACHE_EVENT_PROGRESS;
+ event.data.msg = msg->data.progress;
+
+ error = llcache_send_event_to_users(object, &event);
+
+ break;
+
+ /* Events requiring action */
+ case FETCH_AUTH:
+ /* Need Authentication */
+
+ /* Release candidate, if any */
+ if (object->candidate != NULL) {
+ object->candidate->candidate_count--;
+ object->candidate = NULL;
+ }
+
+ error = llcache_fetch_auth(object, msg->data.auth.realm);
+ break;
+ case FETCH_CERT_ERR:
+ /* Something went wrong when validating TLS certificates */
+
+ /* Release candidate, if any */
+ if (object->candidate != NULL) {
+ object->candidate->candidate_count--;
+ object->candidate = NULL;
+ }
+
+ error = llcache_fetch_cert_error(object,
+ msg->data.cert_err.certs,
+ msg->data.cert_err.num_certs);
+ break;
+ }
+
+ /* Deal with any errors reported by event handlers */
+ if (error != NSERROR_OK) {
+ if (object->fetch.fetch != NULL) {
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+
+ /* Invalidate cache control data */
+ llcache_invalidate_cache_control_data(object);
+
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ }
+ return;
+ }
}
/**
@@ -1284,7 +1728,7 @@ nserror llcache_object_remove_user(llcache_object *object,
* \param handle External cache handle to search for
* \return Pointer to corresponding user, or NULL if not found
*/
-llcache_object_user *llcache_object_find_user(const llcache_handle *handle)
+static llcache_object_user *llcache_object_find_user(const llcache_handle *handle)
{
llcache_object_user *user;
@@ -1299,33 +1743,13 @@ llcache_object_user *llcache_object_find_user(const llcache_handle *handle)
}
/**
- * 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,
+static nserror llcache_object_remove_from_list(llcache_object *object,
llcache_object **list)
{
if (object == *list)
@@ -1346,7 +1770,7 @@ nserror llcache_object_remove_from_list(llcache_object *object,
* \param list List to search in
* \return True if object resides in list, false otherwise
*/
-bool llcache_object_in_list(const llcache_object *object,
+static bool llcache_object_in_list(const llcache_object *object,
const llcache_object *list)
{
while (list != NULL) {
@@ -1365,7 +1789,7 @@ bool llcache_object_in_list(const llcache_object *object,
* \param object Object to notify users about
* \return NSERROR_OK on success, appropriate error otherwise
*/
-nserror llcache_object_notify_users(llcache_object *object)
+static nserror llcache_object_notify_users(llcache_object *object)
{
nserror error;
llcache_object_user *user, *next_user;
@@ -1595,7 +2019,7 @@ nserror llcache_object_notify_users(llcache_object *object)
* \param snapshot Pointer to receive snapshot of \a object
* \return NSERROR_OK on success, appropriate error otherwise
*/
-nserror llcache_object_snapshot(llcache_object *object,
+static nserror llcache_object_snapshot(llcache_object *object,
llcache_object **snapshot)
{
llcache_object *newobj;
@@ -1649,9 +2073,15 @@ nserror llcache_object_snapshot(llcache_object *object,
return NSERROR_OK;
}
+
+/******************************************************************************
+ * Public API *
+ ******************************************************************************/
+
/**
* Attempt to clean the cache
*/
+/* Exported interface documented in llcache.h */
void llcache_clean(void)
{
llcache_object *object, *next;
@@ -1673,9 +2103,10 @@ void llcache_clean(void)
next = object->next;
/* The candidate count of uncacheable objects is always 0 */
- if (object->users == NULL && object->candidate_count == 0 &&
- object->fetch.fetch == NULL &&
- object->fetch.outstanding_query == false) {
+ if ((object->users == NULL) &&
+ (object->candidate_count == 0) &&
+ (object->fetch.fetch == NULL) &&
+ (object->fetch.outstanding_query == false)) {
#ifdef LLCACHE_TRACE
LOG(("Found victim %p", object));
#endif
@@ -1691,10 +2122,11 @@ void llcache_clean(void)
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 &&
- object->fetch.fetch == NULL &&
- object->fetch.outstanding_query == false) {
+ if ((object->users == NULL) &&
+ (object->candidate_count == 0) &&
+ (llcache_object_is_fresh(object) == false) &&
+ (object->fetch.fetch == NULL) &&
+ (object->fetch.outstanding_query == false)) {
#ifdef LLCACHE_TRACE
LOG(("Found victim %p", object));
#endif
@@ -1706,9 +2138,10 @@ void llcache_clean(void)
}
}
+ /* 3) Fresh cacheable objects with no users or pending
+ * fetches, only if the cache exceeds the configured size.
+ */
if (llcache->limit < llcache_size) {
- /* 3) Fresh cacheable objects with
- * no users or pending fetches */
for (object = llcache->cached_objects; object != NULL;
object = next) {
next = object->next;
@@ -1737,822 +2170,333 @@ void llcache_clean(void)
}
-/**
- * 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)
+/* See llcache.h for documentation */
+nserror
+llcache_initialise(llcache_query_callback cb, void *pw, uint32_t llcache_limit)
{
- llcache_post_data *post_clone;
-
- post_clone = calloc(1, sizeof(llcache_post_data));
- if (post_clone == NULL)
+ llcache = calloc(1, sizeof(struct llcache_s));
+ if (llcache == 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)
-{
- llcache_event event;
- llcache_object *object = cbpw;
+ llcache->query_cb = cb;
+ llcache->query_cb_pw = pw;
+ llcache->limit = llcache_limit;
- object->fetch.outstanding_query = false;
+ /* Create static scheme strings */
+ if (lwc_intern_string("file", SLEN("file"),
+ &llcache_file_lwc) != lwc_error_ok)
+ return NSERROR_NOMEM;
- /* Refetch, using existing fetch parameters, if client allows us to */
- if (proceed)
- return llcache_object_refetch(object);
+ if (lwc_intern_string("about", SLEN("about"),
+ &llcache_about_lwc) != lwc_error_ok)
+ return NSERROR_NOMEM;
- /* Invalidate cache-control data */
- llcache_invalidate_cache_control_data(object);
+ if (lwc_intern_string("resource", SLEN("resource"),
+ &llcache_resource_lwc) != lwc_error_ok)
+ return NSERROR_NOMEM;
- /* Mark it complete */
- object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ LOG(("llcache initialised with a limit of %d bytes", llcache_limit));
- /* Inform client(s) that object fetch failed */
- event.type = LLCACHE_EVENT_ERROR;
- /** \todo More appropriate error message */
- event.data.msg = messages_get("FetchFailed");
-
- return llcache_send_event_to_users(object, &event);
+ return NSERROR_OK;
}
-/**
- * Handler for fetch events
- *
- * \param msg Fetch event
- * \param p Our private data
- */
-void llcache_fetch_callback(const fetch_msg *msg, void *p)
+/* See llcache.h for documentation */
+void llcache_finalise(void)
{
- nserror error = NSERROR_OK;
- llcache_object *object = p;
- llcache_event event;
-
-#ifdef LLCACHE_TRACE
- LOG(("Fetch event %d for %p", msg->type, object));
-#endif
-
- switch (msg->type) {
- case FETCH_HEADER:
- /* Received a fetch header */
- object->fetch.state = LLCACHE_FETCH_HEADERS;
-
- error = llcache_fetch_process_header(object,
- msg->data.header_or_data.buf,
- msg->data.header_or_data.len);
- break;
-
- /* 3xx responses */
- case FETCH_REDIRECT:
- /* Request resulted in a redirect */
+ llcache_object *object, *next;
- /* Release candidate, if any */
- if (object->candidate != NULL) {
- object->candidate->candidate_count--;
- object->candidate = NULL;
- }
+ /* Clean uncached objects */
+ for (object = llcache->uncached_objects; object != NULL; object = next) {
+ llcache_object_user *user, *next_user;
- error = llcache_fetch_redirect(object,
- msg->data.redirect, &object);
- break;
- case FETCH_NOTMODIFIED:
- /* Conditional request determined that cached object is fresh */
- error = llcache_fetch_notmodified(object, &object);
- break;
+ next = object->next;
- /* Normal 2xx state machine */
- case FETCH_DATA:
- /* Received some data */
- if (object->fetch.state != LLCACHE_FETCH_DATA) {
- /* On entry into this state, check if we need to
- * invalidate the cache control data. We are guaranteed
- * to have received all response headers.
- *
- * There are two cases in which we want to suppress
- * cacheing of an object:
- *
- * 1) The HTTP response code is not 200 or 203
- * 2) The request URI had a query string and the
- * response headers did not provide an explicit
- * object expiration time.
- */
- long http_code = fetch_http_code(object->fetch.fetch);
+ for (user = object->users; user != NULL; user = next_user) {
+ next_user = user->next;
- if ((http_code != 200 && http_code != 203) ||
- (object->has_query &&
- (object->cache.max_age == INVALID_AGE &&
- object->cache.expires == 0))) {
- /* Invalidate cache control data */
- llcache_invalidate_cache_control_data(object);
- }
+ if (user->handle != NULL)
+ free(user->handle);
- /* Release candidate, if any */
- if (object->candidate != NULL) {
- object->candidate->candidate_count--;
- object->candidate = NULL;
- }
+ free(user);
}
- object->fetch.state = LLCACHE_FETCH_DATA;
-
- error = llcache_fetch_process_data(object,
- msg->data.header_or_data.buf,
- msg->data.header_or_data.len);
- break;
- case FETCH_FINISHED:
- /* Finished fetching */
- {
- uint8_t *temp;
-
- object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ /* Fetch system has already been destroyed */
object->fetch.fetch = NULL;
- /* Shrink source buffer to required size */
- temp = realloc(object->source_data,
- object->source_len);
- /* If source_len is 0, then temp may be NULL */
- if (temp != NULL || object->source_len == 0) {
- object->source_data = temp;
- object->source_alloc = object->source_len;
- }
-
- llcache_object_cache_update(object);
+ llcache_object_destroy(object);
}
- break;
- /* Out-of-band information */
- case FETCH_ERROR:
- /* An error occurred while fetching */
- /* The fetch has has already been cleaned up by the fetcher */
- object->fetch.state = LLCACHE_FETCH_COMPLETE;
- object->fetch.fetch = NULL;
-
- /* Release candidate, if any */
- if (object->candidate != NULL) {
- object->candidate->candidate_count--;
- object->candidate = NULL;
- }
-
- /* Invalidate cache control data */
- llcache_invalidate_cache_control_data(object);
-
- /** \todo Consider using errorcode for something */
+ /* Clean cached objects */
+ for (object = llcache->cached_objects; object != NULL; object = next) {
+ llcache_object_user *user, *next_user;
- event.type = LLCACHE_EVENT_ERROR;
- event.data.msg = msg->data.error;
-
- error = llcache_send_event_to_users(object, &event);
-
- break;
- case FETCH_PROGRESS:
- /* Progress update */
- event.type = LLCACHE_EVENT_PROGRESS;
- event.data.msg = msg->data.progress;
+ next = object->next;
- error = llcache_send_event_to_users(object, &event);
-
- break;
+ for (user = object->users; user != NULL; user = next_user) {
+ next_user = user->next;
- /* Events requiring action */
- case FETCH_AUTH:
- /* Need Authentication */
+ if (user->handle != NULL)
+ free(user->handle);
- /* Release candidate, if any */
- if (object->candidate != NULL) {
- object->candidate->candidate_count--;
- object->candidate = NULL;
+ free(user);
}
- error = llcache_fetch_auth(object, msg->data.auth.realm);
- break;
- case FETCH_CERT_ERR:
- /* Something went wrong when validating TLS certificates */
-
- /* Release candidate, if any */
- if (object->candidate != NULL) {
- object->candidate->candidate_count--;
- object->candidate = NULL;
- }
+ /* Fetch system has already been destroyed */
+ object->fetch.fetch = NULL;
- error = llcache_fetch_cert_error(object,
- msg->data.cert_err.certs,
- msg->data.cert_err.num_certs);
- break;
+ llcache_object_destroy(object);
}
- /* Deal with any errors reported by event handlers */
- if (error != NSERROR_OK) {
- if (object->fetch.fetch != NULL) {
- fetch_abort(object->fetch.fetch);
- object->fetch.fetch = NULL;
-
- /* Invalidate cache control data */
- llcache_invalidate_cache_control_data(object);
+ /* Unref static scheme lwc strings */
+ lwc_string_unref(llcache_file_lwc);
+ lwc_string_unref(llcache_about_lwc);
+ lwc_string_unref(llcache_resource_lwc);
- object->fetch.state = LLCACHE_FETCH_COMPLETE;
- }
- return;
- }
+ free(llcache);
+ llcache = 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)
+/* See llcache.h for documentation */
+nserror llcache_poll(void)
{
- nserror error;
- llcache_object *dest;
- llcache_object_user *user, *next;
- const llcache_post_data *post = object->fetch.post;
- nsurl *url;
- lwc_string *scheme;
- lwc_string *object_scheme;
- bool match;
- /* 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;
+ llcache_object *object;
- /* Invalidate the cache control data */
- llcache_invalidate_cache_control_data(object);
-
- /* And mark it complete */
- object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ fetch_poll();
- /* Forcibly stop redirecting if we've followed too many redirects */
-#define REDIRECT_LIMIT 10
- if (object->fetch.redirect_count > REDIRECT_LIMIT) {
- llcache_event event;
-
- LOG(("Too many nested redirects"));
-
- event.type = LLCACHE_EVENT_ERROR;
- event.data.msg = messages_get("BadRedirect");
-
- return llcache_send_event_to_users(object, &event);
- }
-#undef REDIRECT_LIMIT
-
- /* Make target absolute */
- error = nsurl_join(object->url, target, &url);
- if (error != NSERROR_OK)
- return error;
-
- /* Reject attempts to redirect from unvalidated to validated schemes
- * A "validated" scheme is one over which we have some guarantee that
- * the source is trustworthy. */
- object_scheme = nsurl_get_component(object->url, NSURL_SCHEME);
- scheme = nsurl_get_component(url, NSURL_SCHEME);
-
- /* resource: and about: are allowed to redirect anywhere */
- if ((lwc_string_isequal(object_scheme, llcache_resource_lwc,
- &match) == lwc_error_ok && match == false) &&
- (lwc_string_isequal(object_scheme, llcache_about_lwc,
- &match) == lwc_error_ok && match == false)) {
- /* file, about and resource are not valid redirect targets */
- if ((lwc_string_isequal(object_scheme, llcache_file_lwc,
- &match) == lwc_error_ok && match == true) ||
- (lwc_string_isequal(object_scheme, llcache_about_lwc,
- &match) == lwc_error_ok && match == true) ||
- (lwc_string_isequal(object_scheme, llcache_resource_lwc,
- &match) == lwc_error_ok && match == true)) {
- lwc_string_unref(object_scheme);
- lwc_string_unref(scheme);
- nsurl_unref(url);
- return NSERROR_OK;
- }
+ /* Catch new users up with state of objects */
+ for (object = llcache->cached_objects; object != NULL;
+ object = object->next) {
+ llcache_object_notify_users(object);
}
- lwc_string_unref(scheme);
- lwc_string_unref(object_scheme);
-
- /* Bail out if we've no way of handling this URL */
- if (fetch_can_fetch(url) == false) {
- nsurl_unref(url);
- return NSERROR_OK;
+ for (object = llcache->uncached_objects; object != NULL;
+ object = object->next) {
+ llcache_object_notify_users(object);
}
- if (http_code == 301 || http_code == 302 || http_code == 303) {
- /* 301, 302, 303 redirects are all unconditional GET requests */
- post = NULL;
- } else if (http_code != 307 || post != NULL) {
- /** \todo 300, 305, 307 with POST */
- nsurl_unref(url);
- return NSERROR_OK;
- }
+ return NSERROR_OK;
+}
- /* Attempt to fetch target URL */
- error = llcache_object_retrieve(url, object->fetch.flags,
- object->fetch.referer, post,
- object->fetch.redirect_count + 1, &dest);
+/* See llcache.h for documentation */
+nserror llcache_handle_retrieve(nsurl *url, uint32_t flags,
+ nsurl *referer, const llcache_post_data *post,
+ llcache_handle_callback cb, void *pw,
+ llcache_handle **result)
+{
+ nserror error;
+ llcache_object_user *user;
+ llcache_object *object;
- /* No longer require url */
- nsurl_unref(url);
+ /* 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;
- /* 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);
+ /* Retrieve a suitable object from the cache,
+ * creating a new one if needed. */
+ error = llcache_object_retrieve(url, flags, referer, post, 0, &object);
+ if (error != NSERROR_OK) {
+ llcache_object_user_destroy(user);
+ return error;
}
- /* Dest is now our object */
- *replacement = dest;
+ /* Add user to object */
+ llcache_object_add_user(object, user);
- return NSERROR_OK;
+ *result = user->handle;
+
+ 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)
+/* See llcache.h for documentation */
+nserror llcache_handle_change_callback(llcache_handle *handle,
+ llcache_handle_callback cb, void *pw)
{
- /* There may be no candidate if the server erroneously responded
- * to an unconditional request with a 304 Not Modified response.
- * In this case, we simply retain the initial object, having
- * invalidated it and marked it as complete.
- */
- if (object->candidate != NULL) {
- 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);
- /* Revert no-cache to normal, if required */
- if (object->candidate->cache.no_cache ==
- LLCACHE_VALIDATE_ONCE) {
- object->candidate->cache.no_cache =
- LLCACHE_VALIDATE_FRESH;
- }
-
- /* Candidate is now our object */
- *replacement = object->candidate;
- object->candidate = NULL;
- } else {
- /* There was no candidate: retain object */
- *replacement = object;
- }
-
- /* Ensure fetch has stopped */
- fetch_abort(object->fetch.fetch);
- object->fetch.fetch = NULL;
-
- /* Invalidate our cache-control data */
- llcache_invalidate_cache_control_data(object);
-
- /* Mark it complete */
- object->fetch.state = LLCACHE_FETCH_COMPLETE;
-
- /* Old object will be flushed from the cache on the next poll */
+ handle->cb = cb;
+ handle->pw = pw;
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 uint8_t *data, size_t len,
- char **name, char **value)
+/* See llcache.h for documentation */
+nserror llcache_handle_release(llcache_handle *handle)
{
- char *n, *v;
- const uint8_t *colon;
+ nserror error = NSERROR_OK;
+ llcache_object *object = handle->object;
+ llcache_object_user *user = llcache_object_find_user(handle);
- /* Find colon */
- colon = (const uint8_t *) strchr((const char *) data, ':');
- if (colon == NULL) {
- /* Failed, assume a key with no value */
- n = strdup((const char *) data);
- if (n == NULL)
- return NSERROR_NOMEM;
+ assert(user != NULL);
- v = strdup("");
- if (v == NULL) {
- free(n);
- return NSERROR_NOMEM;
- }
+ if (user->iterator_target) {
+ /* Can't remove / delete user object if it's
+ * the target of an iterator */
+ user->queued_for_delete = true;
} 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((const char *) 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((const char *) colon, len - (colon - data));
- if (v == NULL) {
- free(n);
- return NSERROR_NOMEM;
+ /* Remove the user from the object and destroy it */
+ error = llcache_object_remove_user(object, user);
+ if (error == NSERROR_OK) {
+ error = llcache_object_user_destroy(user);
}
}
-
- *name = n;
- *value = v;
-
- return NSERROR_OK;
+
+ return error;
}
-/**
- * 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
- *
- * \note This function also has the side-effect of updating
- * the cache control data for the object if an interesting
- * header is encountered
- */
-nserror llcache_fetch_parse_header(llcache_object *object,
- const uint8_t *data, size_t len, char **name, char **value)
+/* See llcache.h for documentation */
+nserror llcache_handle_clone(llcache_handle *handle, llcache_handle **result)
{
nserror error;
+ llcache_object_user *newuser;
+
+ error = llcache_object_user_new(handle->cb, handle->pw, &newuser);
+ if (error == NSERROR_OK) {
+ llcache_object_add_user(handle->object, newuser);
+ newuser->handle->state = handle->state;
+ *result = newuser->handle;
+ }
+
+ return 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 = LLCACHE_VALIDATE_ALWAYS;
- 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);
+/* See llcache.h for documentation */
+nserror llcache_handle_abort(llcache_handle *handle)
+{
+ llcache_object_user *user = llcache_object_find_user(handle);
+ llcache_object *object = handle->object, *newobject;
+ nserror error = NSERROR_OK;
+ bool all_alone = true;
+
+ /* Determine if we are the only user */
+ if (user->prev != NULL)
+ all_alone = false;
+ if (user->next != NULL)
+ all_alone = false;
+
+ if (all_alone == false) {
+ /* We must snapshot this object */
+ error = llcache_object_snapshot(object, &newobject);
+ if (error != NSERROR_OK)
+ return error;
- if (start < comma)
- object->cache.max_age = atoi(start);
+ /* Move across to the new object */
+ if (user->iterator_target) {
+ /* User is current iterator target, clone it */
+ llcache_object_user *newuser =
+ calloc(1, sizeof(llcache_object_user));
+ if (newuser == NULL) {
+ llcache_object_destroy(newobject);
+ return NSERROR_NOMEM;
}
- if (*comma != '\0') {
- /* Skip past comma */
- comma++;
- /* Skip whitespace */
- SKIP_ST(comma);
- }
+ /* Move handle across to clone */
+ newuser->handle = user->handle;
+ user->handle = NULL;
- /* Set start for next token */
- start = comma;
+ /* Mark user as needing deletion */
+ user->queued_for_delete = true;
+
+ llcache_object_add_user(newobject, newuser);
+ } else {
+ llcache_object_remove_user(object, user);
+ llcache_object_add_user(newobject, user);
}
- } 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);
+
+ /* Add new object to uncached list */
+ llcache_object_add_to_list(newobject,
+ &llcache->uncached_objects);
+ } else {
+ /* We're the only user, so abort any fetch in progress */
+ if (object->fetch.fetch != NULL) {
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ }
+
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+
+ /* Invalidate cache control data */
+ llcache_invalidate_cache_control_data(object);
}
-
-#undef SKIP_ST
-
- return NSERROR_OK;
+
+ return error;
}
-/**
- * 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 uint8_t *data, size_t len)
+/* See llcache.h for documentation */
+nserror llcache_handle_force_stream(llcache_handle *handle)
{
- nserror error;
- char *name, *value;
- llcache_header *temp;
-
- /* The headers for multiple HTTP responses may be delivered to us if
- * the fetch layer receives a 401 response for which it has
- * authentication credentials. This will result in a silent re-request
- * after which we'll receive the actual response headers for the
- * object we want to fetch (assuming that the credentials were correct
- * of course)
- *
- * Therefore, if the header is an HTTP response start marker, then we
- * must discard any headers we've read so far, reset the cache data
- * that we might have computed, and start again.
- */
- /** \todo Properly parse the response line */
- if (strncmp((const char *) data, "HTTP/", SLEN("HTTP/")) == 0) {
- time_t req_time = object->cache.req_time;
-
- llcache_invalidate_cache_control_data(object);
-
- /* Restore request time, so we compute object's age correctly */
- object->cache.req_time = req_time;
-
- llcache_destroy_headers(object);
- }
+ llcache_object_user *user = llcache_object_find_user(handle);
+ llcache_object *object = handle->object;
- error = llcache_fetch_parse_header(object, data, len, &name, &value);
- if (error != NSERROR_OK)
- return error;
+ /* Cannot stream if there are multiple users */
+ if (user->prev != NULL || user->next != NULL)
+ return NSERROR_OK;
- /* 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;
+ /* Forcibly uncache this object */
+ if (llcache_object_in_list(object, llcache->cached_objects)) {
+ llcache_object_remove_from_list(object,
+ &llcache->cached_objects);
+ llcache_object_add_to_list(object, &llcache->uncached_objects);
}
- object->headers = temp;
-
- object->headers[object->num_headers].name = name;
- object->headers[object->num_headers].value = value;
-
- object->num_headers++;
+ object->fetch.flags |= LLCACHE_RETRIEVE_STREAM_DATA;
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)
+/* See llcache.h for documentation */
+nserror llcache_handle_invalidate_cache_data(llcache_handle *handle)
{
- /* 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;
+ if (handle->object != NULL && handle->object->fetch.fetch == NULL &&
+ handle->object->cache.no_cache ==
+ LLCACHE_VALIDATE_FRESH) {
+ handle->object->cache.no_cache = LLCACHE_VALIDATE_ONCE;
}
- /* 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)
+/* See llcache.h for documentation */
+nsurl *llcache_handle_get_url(const llcache_handle *handle)
{
- const char *auth;
- nserror error = NSERROR_OK;
-
- /* Abort fetch for this object */
- fetch_abort(object->fetch.fetch);
- object->fetch.fetch = NULL;
-
- /* Invalidate cache-control data */
- llcache_invalidate_cache_control_data(object);
-
- /* Destroy headers */
- llcache_destroy_headers(object);
-
- /* If there was no realm, then default to the URL */
- /** \todo If there was no WWW-Authenticate header, use response body */
- if (realm == NULL)
- realm = nsurl_access(object->url);
-
- auth = urldb_get_auth_details(nsurl_access(object->url), realm);
-
- if (auth == NULL || object->fetch.tried_with_auth == true) {
- /* No authentication details, or tried what we had, so ask */
- object->fetch.tried_with_auth = false;
-
- if (llcache->query_cb != NULL) {
- llcache_query query;
+ return handle->object != NULL ? handle->object->url : NULL;
+}
- /* Emit query for authentication details */
- query.type = LLCACHE_QUERY_AUTH;
- query.url = object->url;
- query.data.auth.realm = realm;
+/* See llcache.h for documentation */
+const uint8_t *llcache_handle_get_source_data(const llcache_handle *handle,
+ size_t *size)
+{
+ *size = handle->object != NULL ? handle->object->source_len : 0;
- object->fetch.outstanding_query = true;
+ return handle->object != NULL ? handle->object->source_data : NULL;
+}
- error = llcache->query_cb(&query, llcache->query_cb_pw,
- llcache_query_handle_response, object);
- } else {
- llcache_event event;
+/* See llcache.h for documentation */
+const char *llcache_handle_get_header(const llcache_handle *handle,
+ const char *key)
+{
+ const llcache_object *object = handle->object;
+ size_t i;
- /* Mark object complete */
- object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ if (object == NULL)
+ return NULL;
- /* Inform client(s) that object fetch failed */
- event.type = LLCACHE_EVENT_ERROR;
- /** \todo More appropriate error message */
- event.data.msg = messages_get("FetchFailed");
-
- error = llcache_send_event_to_users(object, &event);
- }
- } else {
- /* Flag that we've tried to refetch with credentials, so
- * that if the fetch fails again, we ask the user again */
- object->fetch.tried_with_auth = true;
- error = llcache_object_refetch(object);
+ /* 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 error;
+ return NULL;
}
-/**
- * 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)
+/* See llcache.h for documentation */
+bool llcache_handle_references_same_object(const llcache_handle *a,
+ const llcache_handle *b)
{
- nserror error = NSERROR_OK;
-
- /* Fetch has been stopped, and destroyed. Invalidate object's pointer */
- object->fetch.fetch = NULL;
-
- /* Invalidate cache-control data */
- llcache_invalidate_cache_control_data(object);
-
- if (llcache->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;
-
- object->fetch.outstanding_query = true;
-
- error = llcache->query_cb(&query, llcache->query_cb_pw,
- llcache_query_handle_response, object);
- } else {
- llcache_event event;
-
- /* Mark object complete */
- object->fetch.state = LLCACHE_FETCH_COMPLETE;
-
- /* Inform client(s) that object fetch failed */
- event.type = LLCACHE_EVENT_ERROR;
- /** \todo More appropriate error message */
- event.data.msg = messages_get("FetchFailed");
-
- error = llcache_send_event_to_users(object, &event);
- }
-
- return error;
+ return a->object == b->object;
}