Author: jmb
Date: Sun Oct 11 10:02:27 2009
New Revision: 9620
URL:
http://source.netsurf-browser.org?rev=9620&view=rev
Log:
Implement rather more of the fetch state machine
Modified:
branches/jmb/new-cache/content/llcache.c
Modified: branches/jmb/new-cache/content/llcache.c
URL:
http://source.netsurf-browser.org/branches/jmb/new-cache/content/llcache....
==============================================================================
--- branches/jmb/new-cache/content/llcache.c (original)
+++ branches/jmb/new-cache/content/llcache.c Sun Oct 11 10:02:27 2009
@@ -20,9 +20,12 @@
* Low-level resource cache (implementation)
*/
+#define _GNU_SOURCE /* For strndup. Ugh. */
#include <stdlib.h>
#include <string.h>
#include <time.h>
+
+#include <curl/curl.h>
#include "content/fetch.h"
#include "content/llcache.h"
@@ -82,6 +85,12 @@
time_t last_modified; /**< Last-Modified: response header */
} llcache_cache_control;
+/** Representation of a fetch header */
+typedef struct {
+ char *name; /**< Header name */
+ char *value; /**< Header value */
+} llcache_header;
+
/** Low-level cache object */
/** \todo Consider whether a list is a sane container */
struct llcache_object {
@@ -105,7 +114,8 @@
uint32_t candidate_count; /**< Count of objects this is a
* candidate for */
- /** \todo Need fetch headers */
+ llcache_header *headers; /**< Fetch headers */
+ size_t num_headers; /**< Number of fetch headers */
};
/** Handler for fetch-related queries */
@@ -157,6 +167,15 @@
const char *target, llcache_object **replacement);
static nserror llcache_fetch_notmodified(llcache_object *object,
llcache_object **replacement);
+static nserror llcache_fetch_split_header(const char *data, size_t len,
+ char **name, char **value);
+static nserror llcache_fetch_parse_header(llcache_object *object,
+ const char *data, size_t len, char **name, char **value);
+static nserror llcache_fetch_process_header(llcache_object *object,
+ const char *data, size_t len);
+static nserror llcache_fetch_process_data(llcache_object *object,
+ const uint8_t *data, size_t len);
+
/******************************************************************************
* Public API *
@@ -676,6 +695,8 @@
*/
nserror llcache_object_destroy(llcache_object *object)
{
+ size_t i;
+
free(object->url);
free(object->source_data);
@@ -689,7 +710,11 @@
free(object->cache.etag);
- /** \todo Headers */
+ for (i = 0; i < object->num_headers; i++) {
+ free(object->headers[i].name);
+ free(object->headers[i].value);
+ }
+ free(object->headers);
free(object);
@@ -936,38 +961,45 @@
void llcache_fetch_callback(fetch_msg msg, void *p, const void *data,
unsigned long size)
{
- nserror error;
+ nserror error = NSERROR_OK;
llcache_object *object = p;
switch (msg) {
/* 3xx responses */
case FETCH_REDIRECT:
/* Request resulted in a redirect */
- llcache_fetch_redirect(object, data, &object);
+ error = llcache_fetch_redirect(object, data, &object);
break;
case FETCH_NOTMODIFIED:
/* Conditional request determined that cached object is fresh */
- llcache_fetch_notmodified(object, &object);
+ error = llcache_fetch_notmodified(object, &object);
break;
/** \todo Handle the rest of the FETCH_ events */
/* Normal 2xx state machine */
- /** \todo Merge FETCH_TYPE and FETCH_HEADER */
+ /** \todo Merge FETCH_TYPE and FETCH_HEADER? */
case FETCH_TYPE:
/* Received MIME type for object */
break;
case FETCH_HEADER:
/* Received a fetch header */
object->fetch.state = LLCACHE_FETCH_HEADERS;
+
+ error = llcache_fetch_process_header(object, data, size);
break;
case FETCH_DATA:
/* Received some data */
object->fetch.state = LLCACHE_FETCH_DATA;
+
+ error = llcache_fetch_process_data(object, data, size);
break;
case FETCH_FINISHED:
/* Finished fetching */
object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ object->fetch.fetch = NULL;
+
+ llcache_object_cache_update(object);
break;
/* Out-of-band progress information */
@@ -987,10 +1019,20 @@
break;
}
+ /* Deal with any errors reported by event handlers */
+ if (error != NSERROR_OK) {
+ /** \todo Error handling */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ return;
+ }
+
/* Keep users in sync with reality */
error = llcache_object_notify_users(object);
if (error != NSERROR_OK) {
/** \todo Error handling */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
}
}
@@ -1117,3 +1159,240 @@
return NSERROR_OK;
}
+
+/**
+ * Split a fetch header into name and value
+ *
+ * \param data Header string
+ * \param len Byte length of header
+ * \param name Pointer to location to receive header name
+ * \param value Pointer to location to receive header value
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_split_header(const char *data, size_t len, char **name,
+ char **value)
+{
+ char *n, *v;
+ const char *colon;
+
+ /* Find colon */
+ colon = strchr(data, ':');
+ if (colon == NULL) {
+ /* Failed, assume a key with no value */
+ n = strdup(data);
+ if (n == NULL)
+ return NSERROR_NOMEM;
+
+ v = strdup("");
+ if (v == NULL) {
+ free(n);
+ return NSERROR_NOMEM;
+ }
+ } else {
+ /* Split header into name & value */
+
+ /* Strip leading whitespace from name */
+ while (data[0] == ' ' || data[0] == '\t' ||
+ data[0] == '\r' || data[0] == '\n') {
+ data++;
+ }
+
+ /* Strip trailing whitespace from name */
+ do {
+ colon--;
+ } while (*colon == ' ' || *colon == '\t' ||
+ *colon == '\r' || *colon == '\n');
+
+ n = strndup(data, colon - data - 1);
+ if (n == NULL)
+ return NSERROR_NOMEM;
+
+ /* Find colon again */
+ while (*colon != ':') {
+ colon++;
+ }
+
+ /* Skip over colon and any subsequent whitespace */
+ do {
+ colon++;
+ } while (*colon == ' ' || *colon == '\t' ||
+ *colon == '\r' || *colon == '\n');
+
+ /* Strip trailing whitespace from value */
+ while (len > 0 && (data[len - 1] == ' ' ||
+ data[len - 1] == '\t' ||
+ data[len - 1] == '\r' ||
+ data[len - 1] == '\n')) {
+ len--;
+ }
+
+ v = strndup(colon, len - (colon - data));
+ if (v == NULL) {
+ free(n);
+ return NSERROR_NOMEM;
+ }
+ }
+
+ *name = n;
+ *value = v;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Parse a fetch header
+ *
+ * \param object Object to parse header for
+ * \param data Header string
+ * \param len Byte length of header
+ * \param name Pointer to location to receive header name
+ * \param value Pointer to location to receive header value
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_parse_header(llcache_object *object, const char *data,
+ size_t len, char **name, char **value)
+{
+ nserror error;
+
+ /* Set fetch response time if not already set */
+ if (object->cache.res_time == 0)
+ object->cache.res_time = time(NULL);
+
+ /* Decompose header into name-value pair */
+ error = llcache_fetch_split_header(data, len, name, value);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Parse cache headers to populate cache control data */
+#define SKIP_ST(p) while (*p != '\0' && (*p == ' ' || *p ==
'\t')) p++
+
+ if (5 < len && strcasecmp(*name, "Date") == 0) {
+ /* extract Date header */
+ object->cache.date = curl_getdate(*value, NULL);
+ } else if (4 < len && strcasecmp(*name, "Age") == 0) {
+ /* extract Age header */
+ if ('0' <= **value && **value <= '9')
+ object->cache.age = atoi(*value);
+ } else if (8 < len && strcasecmp(*name, "Expires") == 0) {
+ /* extract Expires header */
+ object->cache.expires = curl_getdate(*value, NULL);
+ } else if (14 < len && strcasecmp(*name, "Cache-Control") == 0) {
+ /* extract and parse Cache-Control header */
+ const char *start = *value;
+ const char *comma = *value;
+
+ while (*comma != '\0') {
+ while (*comma != '\0' && *comma != ',')
+ comma++;
+
+ if (8 < comma - start && (strncasecmp(start,
+ "no-cache", 8) == 0 ||
+ strncasecmp(start, "no-store", 8) == 0))
+ /* When we get a disk cache we should
+ * distinguish between these two */
+ object->cache.no_cache = true;
+ else if (7 < comma - start &&
+ strncasecmp(start, "max-age", 7) == 0) {
+ /* Find '=' */
+ while (start < comma && *start != '=')
+ start++;
+
+ /* Skip over it */
+ start++;
+
+ /* Skip whitespace */
+ SKIP_ST(start);
+
+ if (start < comma)
+ object->cache.max_age = atoi(start);
+ }
+
+ /* Skip past comma */
+ comma++;
+ /* Skip whitespace */
+ SKIP_ST(comma);
+ /* Set start for next token */
+ start = comma;
+ }
+ } else if (5 < len && strcasecmp(*name, "ETag") == 0) {
+ /* extract ETag header */
+ free(object->cache.etag);
+ object->cache.etag = strdup(*value);
+ if (object->cache.etag == NULL)
+ return NSERROR_NOMEM;
+ } else if (14 < len && strcasecmp(*name, "Last-Modified") == 0) {
+ /* extract Last-Modified header */
+ object->cache.last_modified = curl_getdate(*value, NULL);
+ }
+
+#undef SKIP_ST
+
+ return NSERROR_OK;
+}
+
+/**
+ * Process a fetch header
+ *
+ * \param object Object being fetched
+ * \param data Header string
+ * \param len Byte length of header
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_process_header(llcache_object *object, const char *data,
+ size_t len)
+{
+ nserror error;
+ char *name, *value;
+ llcache_header *temp;
+
+ error = llcache_fetch_parse_header(object, data, len, &name, &value);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Append header data to the object's headers array */
+ temp = realloc(object->headers, (object->num_headers + 1) *
+ sizeof(llcache_header));
+ if (temp == NULL) {
+ free(name);
+ free(value);
+ return NSERROR_NOMEM;
+ }
+
+ object->headers = temp;
+
+ object->headers[object->num_headers].name = name;
+ object->headers[object->num_headers].value = value;
+
+ object->num_headers++;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Process a chunk of fetched data
+ *
+ * \param object Object being fetched
+ * \param data Data to process
+ * \param len Byte length of data
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_fetch_process_data(llcache_object *object, const uint8_t *data,
+ size_t len)
+{
+ /* Resize source buffer if it's too small */
+ if (object->source_len + len >= object->source_alloc) {
+ const size_t new_len = object->source_len + len + 64 * 1024;
+ uint8_t *temp = realloc(object->source_data, new_len);
+ if (temp == NULL)
+ return NSERROR_NOMEM;
+
+ object->source_data = temp;
+ object->source_alloc = new_len;
+ }
+
+ /* Append this data chunk to source buffer */
+ memcpy(object->source_data + object->source_len, data, len);
+ object->source_len += len;
+
+ return NSERROR_OK;
+}