Subversion Repositories Kolibri OS

Compare Revisions

Regard whitespace Rev 4363 → Rev 4364

/contrib/network/netsurf/netsurf/content/fetchers/curl.c
0,0 → 1,524
/*
* Copyright 2006 Daniel Silverstone <dsilvers@digital-scurf.org>
* Copyright 2007 James Bursa <bursa@users.sourceforge.net>
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Fetching of data from a URL (implementation).
*
* This implementation uses libcurl's 'multi' interface.
*
*
* The CURL handles are cached in the curl_handle_ring. There are at most
* ::max_cached_fetch_handles in this ring.
*/
 
 
#include "http.c"
 
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <sys/stat.h>
 
#include <libwapcaplet/libwapcaplet.h>
 
#include "utils/config.h"
//#include <openssl/ssl.h>
#include "content/fetch.h"
#include "content/fetchers/curl.h"
#include "content/urldb.h"
#include "desktop/netsurf.h"
#include "desktop/options.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/schedule.h"
#include "utils/utils.h"
#include "utils/ring.h"
#include "utils/useragent.h"
 
/* BIG FAT WARNING: This is here because curl doesn't give you an FD to
* poll on, until it has processed a bit of the handle. So we need schedules
* in order to make this work.
*/
#include <desktop/browser.h>
 
/* uncomment this to use scheduler based calling
#define FETCHER_CURLL_SCHEDULED 1
*/
 
 
struct fetch_curl_context {
struct fetch_curl_context *r_next, *r_prev;
 
struct fetch *fetchh; /**< Handle for this fetch */
 
bool aborted; /**< Flag indicating fetch has been aborted */
bool locked; /**< Flag indicating entry is already entered */
 
nsurl *url; /**< The full url the fetch refers to */
char *path; /**< The actual path to be used with open() */
 
time_t file_etag; /**< Request etag for file (previous st.m_time) */
};
 
static struct fetch_curl_context *ring = NULL;
 
 
static bool fetch_curl_initialise(lwc_string *scheme); //here
static void fetch_curl_finalise(lwc_string *scheme); //here
static bool fetch_curl_can_fetch(const nsurl *url); //here
static void * fetch_curl_setup(struct fetch *parent_fetch, nsurl *url,
bool only_2xx, bool downgrade_tls, const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
const char **headers); //here
static bool fetch_curl_start(void *vfetch); //here
 
static void fetch_curl_abort(void *vf); //here
static void fetch_curl_free(void *f); //here
static void fetch_curl_poll(lwc_string *scheme_ignored); //here
 
 
/**
* Initialise the fetcher.
*
* Must be called once before any other function.
*/
 
void fetch_curl_register(void)
{
 
lwc_string *scheme;
 
 
LOG(("curl register\n"));
 
lwc_intern_string("http", SLEN("http"), &scheme);
 
if (!fetch_add_fetcher(scheme,
fetch_curl_initialise, //here
fetch_curl_can_fetch, //here
fetch_curl_setup,
fetch_curl_start,
fetch_curl_abort, //here
fetch_curl_free, //here
#ifdef FETCHER_CURLL_SCHEDULED
NULL,
#else
fetch_curl_poll, //here
#endif
fetch_curl_finalise)) { //here
LOG(("Unable to register cURL fetcher for HTTP"));
}
lwc_intern_string("https", SLEN("https"), &scheme);
 
if (!fetch_add_fetcher(scheme,
fetch_curl_initialise,
fetch_curl_can_fetch,
fetch_curl_setup,
fetch_curl_start,
fetch_curl_abort,
fetch_curl_free,
#ifdef FETCHER_CURLL_SCHEDULED
NULL,
#else
fetch_curl_poll,
#endif
fetch_curl_finalise)) {
LOG(("Unable to register cURL fetcher for HTTPS"));
}
 
}
 
 
/**
* Initialise a cURL fetcher.
*/
 
bool fetch_curl_initialise(lwc_string *scheme)
{
LOG(("curl initi lwc\n"));
return true; /* Always succeeds */
}
 
 
/**
* Finalise a cURL fetcher
*/
 
void fetch_curl_finalise(lwc_string *scheme)
{
LOG(("curl finali\n"));
}
 
static bool fetch_curl_can_fetch(const nsurl *url)
{
LOG(("curl can fetch\n"));
return true; //let's lie a bit
}
 
/**
* Start fetching data for the given URL.
*
* The function returns immediately. The fetch may be queued for later
* processing.
*
* A pointer to an opaque struct curl_fetch_info is returned, which can be
* passed to fetch_abort() to abort the fetch at any time. Returns 0 if memory
* is exhausted (or some other fatal error occurred).
*
* The caller must supply a callback function which is called when anything
* interesting happens. The callback function is first called with msg
* FETCH_HEADER, with the header in data, then one or more times
* with FETCH_DATA with some data for the url, and finally with
* FETCH_FINISHED. Alternatively, FETCH_ERROR indicates an error occurred:
* data contains an error message. FETCH_REDIRECT may replace the FETCH_HEADER,
* FETCH_DATA, FETCH_FINISHED sequence if the server sends a replacement URL.
*
* Some private data can be passed as the last parameter to fetch_start, and
* callbacks will contain this.
*/
 
void * fetch_curl_setup (struct fetch *fetchh,
nsurl *url,
bool only_2xx,
bool downgrade_tls,
const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
const char **headers)
{
 
LOG(("curl setup\n"));
struct fetch_curl_context *ctx;
int i;
 
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL)
return NULL;
 
//ctx->path = url_to_path(nsurl_access(url));
char *zz;
int pr;
nsurl_get(url, NSURL_WITH_FRAGMENT, &zz, &pr);
ctx->path = zz;
if (ctx->path == NULL) {
free(ctx);
return NULL;
}
 
ctx->url = nsurl_ref(url);
 
 
ctx->fetchh = fetchh;
 
RING_INSERT(ring, ctx);
 
return ctx;
}
 
 
/**
* Dispatch a single job
*/
bool fetch_curl_start(void *vfetch)
{
LOG(("curl start\n"));
return true;
}
 
 
 
 
 
 
/**
* Abort a fetch.
*/
 
void fetch_curl_abort(void *ctx)
{
struct fetch_curl_context *c = ctx;
 
/* To avoid the poll loop having to deal with the fetch context
* disappearing from under it, we simply flag the abort here.
* The poll loop itself will perform the appropriate cleanup.
*/
c->aborted = true;
}
 
 
/**
* Free a fetch structure and associated resources.
*/
 
void fetch_curl_free(void *ctx)
{
struct fetch_curl_context *c = ctx;
nsurl_unref(c->url);
free(c->path);
RING_REMOVE(ring, c);
free(ctx);
}
 
static inline bool fetch_curl_send_callback(const fetch_msg *msg,
struct fetch_curl_context *ctx)
{
ctx->locked = true;
fetch_send_callback(msg, ctx->fetchh);
ctx->locked = false;
 
return ctx->aborted;
}
 
static bool fetch_curl_send_header(struct fetch_curl_context *ctx,
const char *fmt, ...)
{
fetch_msg msg;
char header[64];
va_list ap;
 
va_start(ap, fmt);
 
vsnprintf(header, sizeof header, fmt, ap);
 
va_end(ap);
 
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf = (const uint8_t *) header;
msg.data.header_or_data.len = strlen(header);
fetch_curl_send_callback(&msg, ctx);
 
return ctx->aborted;
}
 
static void fetch_curl_process_error(struct fetch_curl_context *ctx, int code)
{
fetch_msg msg;
char buffer[1024];
const char *title;
char key[8];
 
/* content is going to return error code */
fetch_set_http_code(ctx->fetchh, code);
 
/* content type */
if (fetch_curl_send_header(ctx, "Content-Type: text/html"))
goto fetch_file_process_error_aborted;
 
snprintf(key, sizeof key, "HTTP%03d", code);
title = messages_get(key);
 
snprintf(buffer, sizeof buffer, "<html><head><title>%s</title></head>"
"<body><h1>%s</h1>"
"<p>Error %d while fetching file %s</p></body></html>",
title, title, code, nsurl_access(ctx->url));
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
msg.data.header_or_data.len = strlen(buffer);
if (fetch_curl_send_callback(&msg, ctx))
goto fetch_file_process_error_aborted;
 
msg.type = FETCH_FINISHED;
fetch_curl_send_callback(&msg, ctx);
 
fetch_file_process_error_aborted:
return;
}
 
 
int is_pid(int k)
{
int error;
asm volatile ("int $0x40":"=a"(error):"a"(18), "b"(21), "c"(k));
return error;
}
 
 
int kill_pid(int k)
{
int error;
asm volatile ("int $0x40":"=a"(error):"a"(18), "b"(18), "c"(k));
return error;
}
 
 
static void fetch_curl_process(struct fetch_curl_context *ctx) {
char ps[96], str[128];
sprintf(ps, "Yay! Path is %s", ctx->path);
execl ("/sys/@notify", ps, 0);
fetch_msg msg;
 
/* ERSATZ DOWNLOADER */
/*
char zapzap[]="<html><body><h1>HOOLE!</h1></body></html>";
size_t file_size=strlen(zapzap);
char *buffer = (char*)malloc(file_size * sizeof(char));
memcpy(buffer, zapzap, file_size * sizeof(char));
*/
 
 
 
 
__menuet__debug_out("AHOY!\n");
struct http_msg *http_ahoy;
 
unsigned int wererat = 0;
char * pa=ctx->path;
asm volatile ("pusha");
wererat = http_get(pa);
asm volatile ("popa");
__menuet__debug_out("HTTP GOT!\n");
int result;
 
http_ahoy=wererat;
 
sprintf (str, "Header %d bytes, content %d bytes, recieved %d bytes\n", http_ahoy->header_length, http_ahoy->content_length, http_ahoy->content_received);
__menuet__debug_out(str);
 
asm volatile ("pusha");
result = http_process(wererat);
asm volatile ("popa");
while (result == -1) {
asm volatile ("pusha");
result = http_process(wererat);
asm volatile ("popa");
}
 
http_ahoy=wererat;
sprintf (str, "Header %d bytes, content %d bytes, recieved %d bytes\n", http_ahoy->header_length, http_ahoy->content_length, http_ahoy->content_received);
__menuet__debug_out(str);
 
__menuet__debug_out("All content is here\n");
size_t file_size=http_ahoy->content_received;
char *buffer = (char*)malloc(file_size * sizeof(char));
memcpy(buffer, &(http_ahoy->data)+http_ahoy->header_length, file_size);
 
// http_free(wererat);
__menuet__debug_out("memcopied\n==\n");
 
//__menuet__debug_out(buffer);
//__menuet__debug_out("memcopied\n==\n");
 
 
//char zapzap[]="<html><body><h1>HOOLE!</h1></body></html>";
//file_size=strlen(zapzap);
//char *buffer = (char*)malloc(file_size * sizeof(char));
//memcpy(buffer, zapzap, file_size * sizeof(char));
 
 
/* fetch is going to be successful */
fetch_set_http_code(ctx->fetchh, 200);
 
/* Any callback can result in the fetch being aborted.
* Therefore, we _must_ check for this after _every_ call to
* fetch_file_send_callback().
*/
 
if (fetch_curl_send_header(ctx, "Content-Type: %s",
fetch_filetype(ctx->path)))
goto fetch_file_process_aborted;
 
/* main data loop */
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;//&(http_ahoy->data) ; //buffer;
msg.data.header_or_data.len = file_size;
fetch_curl_send_callback(&msg, ctx);
 
if (ctx->aborted == false) {
msg.type = FETCH_FINISHED;
fetch_curl_send_callback(&msg, ctx);
}
 
fetch_file_process_aborted:
return;
 
}
 
 
/**
* Do some work on current fetches.
*
* Must be called regularly to make progress on fetches.
*/
 
void fetch_curl_poll(lwc_string *scheme_ignored)
{
LOG(("curl poll\n"));
struct fetch_curl_context *c, *next;
 
if (ring == NULL) return;
 
/* Iterate over ring, processing each pending fetch */
c = ring;
do {
/* Ignore fetches that have been flagged as locked.
* This allows safe re-entrant calls to this function.
* Re-entrancy can occur if, as a result of a callback,
* the interested party causes fetch_poll() to be called
* again.
*/
if (c->locked == true) {
next = c->r_next;
continue;
}
 
/* Only process non-aborted fetches */
if (c->aborted == false) {
/* file fetches can be processed in one go */
fetch_curl_process(c);
}
 
/* Compute next fetch item at the last possible moment as
* processing this item may have added to the ring.
*/
next = c->r_next;
 
fetch_remove_from_queues(c->fetchh);
fetch_free(c->fetchh);
 
/* Advance to next ring entry, exiting if we've reached
* the start of the ring or the ring has become empty
*/
} while ( (c = next) != ring && ring != NULL);
 
}
 
 
 
 
/contrib/network/netsurf/netsurf/content/fetchers/http.c
0,0 → 1,86
#include <menuet/os.h>
#define NULL 0
#define __stdcall __attribute__((stdcall))
 
extern int dll_load();
extern int mem_Free();
extern int mem_Alloc();
extern int mem_ReAlloc();
 
 
 
int kol_exit(){
__menuet__sys_exit();
}
 
 
 
struct http_msg {
unsigned int socket;
unsigned int flags;
unsigned int write_ptr;
unsigned int buffer_length;
unsigned int chunk_ptr;
unsigned int timestamp;
unsigned int status;
unsigned int header_length;
unsigned int content_length;
unsigned int content_received;
char data; //unknown size
};
 
 
int (* __stdcall http_init)();
unsigned int (* __stdcall http_get) (char * url); //yay, it's NOT uint, but hey, C is stubborn, and I'm dumb
int (* __stdcall http_process) (unsigned int identifier);
void (* __stdcall http_free) (unsigned int identifier);
 
 
int HTTP_YAY(){
asm volatile ("pusha\n\
movl $mem_Alloc, %eax\n\
movl $mem_Free, %ebx\n\
movl $mem_ReAlloc, %ecx\n\
movl $dll_load, %edx\n\
movl http_init, %esi\n\
call *%esi\n\
popa");
}
 
///===========================
 
void HTTP_INIT()
{
IMP_ENTRY *imp;
 
imp = __kolibri__cofflib_load("/sys/lib/http.obj");
if (imp == NULL)
kol_exit();
 
http_init = ( __stdcall int(*)())
__kolibri__cofflib_getproc (imp, "lib_init");
if (http_init == NULL)
kol_exit();
 
http_get = ( __stdcall unsigned int (*)(char*))
__kolibri__cofflib_getproc (imp, "get");
if (http_get == NULL)
kol_exit();
 
http_free = ( __stdcall void (*)(unsigned int))
__kolibri__cofflib_getproc (imp, "free");
if (http_free == NULL)
kol_exit();
 
http_process = ( __stdcall int (*)(unsigned int))
__kolibri__cofflib_getproc (imp, "process");
if (http_process == NULL)
kol_exit();
 
__menuet__debug_out("HTTP init...\n");
HTTP_YAY();
 
__menuet__debug_out("ok...\n");
 
}
/contrib/network/netsurf/netsurf/content/fetchers/about.c
0,0 → 1,858
/*
* Copyright 2011 Vincent Sanders <vince@netsurf-browser.org>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/* about: URL handling.
*
* Based on the data fetcher by Rob Kendrick
* This fetcher provides a simple scheme for the user to access
* information from the browser from a known, fixed URL.
*/
 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <inttypes.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <stdio.h>
#include <dirent.h>
#include <limits.h>
#include <stdarg.h>
 
#include <libwapcaplet/libwapcaplet.h>
 
#include "utils/config.h"
#include "content/dirlist.h"
#include "content/fetch.h"
#include "content/fetchers/about.h"
#include "content/urldb.h"
#include "desktop/netsurf.h"
#include "desktop/options.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/url.h"
#include "utils/utils.h"
#include "utils/ring.h"
#include "utils/testament.h"
#include "image/image_cache.h"
 
struct fetch_about_context;
 
typedef bool (*fetch_about_handler)(struct fetch_about_context *);
 
/** Context for an about fetch */
struct fetch_about_context {
struct fetch_about_context *r_next, *r_prev;
 
struct fetch *fetchh; /**< Handle for this fetch */
 
bool aborted; /**< Flag indicating fetch has been aborted */
bool locked; /**< Flag indicating entry is already entered */
 
nsurl *url; /**< The full url the fetch refers to */
 
fetch_about_handler handler;
};
 
static struct fetch_about_context *ring = NULL;
 
/** issue fetch callbacks with locking */
static inline bool fetch_about_send_callback(const fetch_msg *msg,
struct fetch_about_context *ctx)
{
ctx->locked = true;
fetch_send_callback(msg, ctx->fetchh);
ctx->locked = false;
 
return ctx->aborted;
}
 
static bool fetch_about_send_header(struct fetch_about_context *ctx,
const char *fmt, ...)
{
char header[64];
fetch_msg msg;
va_list ap;
 
va_start(ap, fmt);
 
vsnprintf(header, sizeof header, fmt, ap);
 
va_end(ap);
 
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf = (const uint8_t *) header;
msg.data.header_or_data.len = strlen(header);
 
fetch_about_send_callback(&msg, ctx);
 
return ctx->aborted;
}
 
 
 
 
static bool fetch_about_blank_handler(struct fetch_about_context *ctx)
{
fetch_msg msg;
const char buffer[2] = { ' ', '\0' };
 
/* content is going to return ok */
fetch_set_http_code(ctx->fetchh, 200);
 
/* content type */
if (fetch_about_send_header(ctx, "Content-Type: text/html"))
goto fetch_about_blank_handler_aborted;
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
msg.data.header_or_data.len = strlen(buffer);
 
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_blank_handler_aborted;
 
msg.type = FETCH_FINISHED;
 
fetch_about_send_callback(&msg, ctx);
 
return true;
 
fetch_about_blank_handler_aborted:
return false;
}
 
 
static bool fetch_about_credits_handler(struct fetch_about_context *ctx)
{
fetch_msg msg;
 
/* content is going to return redirect */
fetch_set_http_code(ctx->fetchh, 302);
 
msg.type = FETCH_REDIRECT;
msg.data.redirect = "resource:credits.html";
 
fetch_about_send_callback(&msg, ctx);
 
return true;
}
 
 
static bool fetch_about_licence_handler(struct fetch_about_context *ctx)
{
fetch_msg msg;
 
/* content is going to return redirect */
fetch_set_http_code(ctx->fetchh, 302);
 
msg.type = FETCH_REDIRECT;
msg.data.redirect = "resource:licence.html";
 
fetch_about_send_callback(&msg, ctx);
 
return true;
}
 
/** Handler to generate about:cache page.
*
* Shows details of current iamge cache
*
*/
static bool fetch_about_imagecache_handler(struct fetch_about_context *ctx)
{
fetch_msg msg;
char buffer[2048]; /* output buffer */
int code = 200;
int slen;
unsigned int cent_loop = 0;
int res = 0;
 
/* content is going to return ok */
fetch_set_http_code(ctx->fetchh, code);
 
/* content type */
if (fetch_about_send_header(ctx, "Content-Type: text/html"))
goto fetch_about_imagecache_handler_aborted;
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
 
/* page head */
slen = snprintf(buffer, sizeof buffer,
"<html>\n<head>\n"
"<title>NetSurf Browser Image Cache Status</title>\n"
"<link rel=\"stylesheet\" type=\"text/css\" "
"href=\"resource:internal.css\">\n"
"</head>\n"
"<body id =\"cachelist\">\n"
"<p class=\"banner\">"
"<a href=\"http://www.netsurf-browser.org/\">"
"<img src=\"resource:netsurf.png\" alt=\"NetSurf\"></a>"
"</p>\n"
"<h1>NetSurf Browser Image Cache Status</h1>\n" );
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_imagecache_handler_aborted;
 
/* image cache summary */
slen = image_cache_snsummaryf(buffer, sizeof(buffer),
"<p>Configured limit of %a hysteresis of %b</p>\n"
"<p>Total bitmap size in use %c (in %d)</p>\n"
"<p>Age %es</p>\n"
"<p>Peak size %f (in %g)</p>\n"
"<p>Peak image count %h (size %i)</p>\n"
"<p>Cache total/hit/miss/fail (counts) %j/%k/%l/%m "
"(%pj%%/%pk%%/%pl%%/%pm%%)</p>\n"
"<p>Cache total/hit/miss/fail (size) %n/%o/%q/%r "
"(%pn%%/%po%%/%pq%%/%pr%%)</p>\n"
"<p>Total images never rendered: %s "
"(includes %t that were converted)</p>\n"
"<p>Total number of excessive conversions: %u "
"(from %v images converted more than once)"
"</p>\n"
"<p>Bitmap of size %w had most (%x) conversions</p>\n"
"<h2>Current image cache contents</h2>\n");
if (slen >= (int) (sizeof(buffer)))
goto fetch_about_imagecache_handler_aborted; /* overflow */
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_imagecache_handler_aborted;
 
 
/* image cache entry table */
slen = snprintf(buffer, sizeof buffer,
"<p class=\"imagecachelist\">\n"
"<strong>"
"<span>Entry</span>"
"<span>Content Key</span>"
"<span>Redraw Count</span>"
"<span>Conversion Count</span>"
"<span>Last Redraw</span>"
"<span>Bitmap Age</span>"
"<span>Bitmap Size</span>"
"<span>Source</span>"
"</strong>\n");
do {
res = image_cache_snentryf(buffer + slen, sizeof buffer - slen,
cent_loop,
"<a href=\"%U\">"
"<span>%e</span>"
"<span>%k</span>"
"<span>%r</span>"
"<span>%c</span>"
"<span>%a</span>"
"<span>%g</span>"
"<span>%s</span>"
"<span>%o</span>"
"</a>\n");
if (res <= 0)
break; /* last option */
 
if (res >= (int) (sizeof buffer - slen)) {
/* last entry would not fit in buffer, submit buffer */
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_imagecache_handler_aborted;
slen = 0;
} else {
/* normal addition */
slen += res;
cent_loop++;
}
} while (res > 0);
 
slen += snprintf(buffer + slen, sizeof buffer - slen,
"</p>\n</body>\n</html>\n");
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_imagecache_handler_aborted;
 
msg.type = FETCH_FINISHED;
fetch_about_send_callback(&msg, ctx);
 
return true;
 
fetch_about_imagecache_handler_aborted:
return false;
}
 
/** Handler to generate about:config page */
static bool fetch_about_config_handler(struct fetch_about_context *ctx)
{
fetch_msg msg;
char buffer[1024];
int code = 200;
int slen;
unsigned int opt_loop = 0;
int res = 0;
 
/* content is going to return ok */
fetch_set_http_code(ctx->fetchh, code);
 
/* content type */
if (fetch_about_send_header(ctx, "Content-Type: text/html"))
goto fetch_about_config_handler_aborted;
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
 
slen = snprintf(buffer, sizeof buffer,
"<html>\n<head>\n"
"<title>NetSurf Browser Config</title>\n"
"<link rel=\"stylesheet\" type=\"text/css\" "
"href=\"resource:internal.css\">\n"
"</head>\n"
"<body id =\"configlist\">\n"
"<p class=\"banner\">"
"<a href=\"http://www.netsurf-browser.org/\">"
"<img src=\"resource:netsurf.png\" alt=\"NetSurf\"></a>"
"</p>\n"
"<h1>NetSurf Browser Config</h1>\n"
"<table class=\"config\">\n"
"<tr><th></th><th></th><th></th></tr>\n");
 
do {
res = nsoption_snoptionf(buffer + slen, sizeof buffer - slen,
opt_loop,
"<tr><th>%k</th><td>%t</td><td>%V</td></tr>\n");
if (res <= 0)
break; /* last option */
 
if (res >= (int) (sizeof buffer - slen)) {
/* last entry would not fit in buffer, submit buffer */
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_config_handler_aborted;
slen = 0;
} else {
/* normal addition */
slen += res;
opt_loop++;
}
} while (res > 0);
 
slen += snprintf(buffer + slen, sizeof buffer - slen,
"</table>\n</body>\n</html>\n");
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_config_handler_aborted;
 
msg.type = FETCH_FINISHED;
fetch_about_send_callback(&msg, ctx);
 
return true;
 
fetch_about_config_handler_aborted:
return false;
}
 
 
/** Generate the text of a Choices file which represents the current
* in use options.
*/
static bool fetch_about_choices_handler(struct fetch_about_context *ctx)
{
fetch_msg msg;
char buffer[1024];
int code = 200;
int slen;
unsigned int opt_loop = 0;
int res = 0;
 
/* content is going to return ok */
fetch_set_http_code(ctx->fetchh, code);
 
/* content type */
if (fetch_about_send_header(ctx, "Content-Type: text/plain"))
goto fetch_about_choices_handler_aborted;
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
 
slen = snprintf(buffer, sizeof buffer,
"# Automatically generated current NetSurf browser Choices\n");
 
do {
res = nsoption_snoptionf(buffer + slen,
sizeof buffer - slen,
opt_loop,
"%k:%v\n");
if (res <= 0)
break; /* last option */
 
if (res >= (int) (sizeof buffer - slen)) {
/* last entry would not fit in buffer, submit buffer */
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_choices_handler_aborted;
slen = 0;
} else {
/* normal addition */
slen += res;
opt_loop++;
}
} while (res > 0);
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_choices_handler_aborted;
 
msg.type = FETCH_FINISHED;
fetch_about_send_callback(&msg, ctx);
 
return true;
 
fetch_about_choices_handler_aborted:
return false;
}
 
/** Generate the text of an svn testament which represents the current
* build-tree status
*/
typedef struct { const char *leaf; const char *modtype; } modification_t;
static bool fetch_about_testament_handler(struct fetch_about_context *ctx)
{
static modification_t modifications[] = WT_MODIFICATIONS;
fetch_msg msg;
char buffer[1024];
int code = 200;
int slen;
int i;
 
/* content is going to return ok */
fetch_set_http_code(ctx->fetchh, code);
 
/* content type */
if (fetch_about_send_header(ctx, "Content-Type: text/plain"))
goto fetch_about_testament_handler_aborted;
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
 
slen = snprintf(buffer, sizeof buffer,
"# Automatically generated by NetSurf build system\n\n");
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_testament_handler_aborted;
slen = snprintf(buffer, sizeof buffer,
#if defined(WT_BRANCHISTRUNK) || defined(WT_BRANCHISMASTER)
"# This is a *DEVELOPMENT* build from the main line.\n\n"
#elif defined(WT_BRANCHISTAG) && (WT_MODIFIED == 0)
"# This is a tagged build of NetSurf\n"
#ifdef WT_TAGIS
"# The tag used was '" WT_TAGIS "'\n\n"
#else
"\n"
#endif
#elif defined(WT_NO_SVN) || defined(WT_NO_GIT)
"# This NetSurf was built outside of our revision "
"control environment.\n"
"# This testament is therefore very useful.\n\n"
#else
"# This NetSurf was built from a branch (" WT_BRANCHPATH ").\n\n"
#endif
#if defined(CI_BUILD)
"# This build carries the CI build number '" CI_BUILD "'\n\n"
#endif
);
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_testament_handler_aborted;
 
slen = snprintf(buffer, sizeof buffer,
"Built by %s (%s) from %s at revision %s\n\n",
GECOS, USERNAME, WT_BRANCHPATH, WT_REVID);
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_testament_handler_aborted;
slen = snprintf(buffer, sizeof buffer,
"Built on %s in %s\n\n",
WT_HOSTNAME, WT_ROOT);
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_testament_handler_aborted;
if (WT_MODIFIED > 0) {
slen = snprintf(buffer, sizeof buffer,
"Working tree has %d modification%s\n\n",
WT_MODIFIED, WT_MODIFIED == 1 ? "" : "s");
} else {
slen = snprintf(buffer, sizeof buffer,
"Working tree is not modified.\n");
}
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_testament_handler_aborted;
for (i = 0; i < WT_MODIFIED; ++i) {
slen = snprintf(buffer, sizeof buffer,
" %s %s\n",
modifications[i].modtype,
modifications[i].leaf);
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_testament_handler_aborted;
}
 
msg.type = FETCH_FINISHED;
fetch_about_send_callback(&msg, ctx);
 
return true;
 
fetch_about_testament_handler_aborted:
return false;
}
 
static bool fetch_about_logo_handler(struct fetch_about_context *ctx)
{
fetch_msg msg;
 
/* content is going to return redirect */
fetch_set_http_code(ctx->fetchh, 302);
 
msg.type = FETCH_REDIRECT;
msg.data.redirect = "resource:netsurf.png";
 
fetch_about_send_callback(&msg, ctx);
 
return true;
}
 
static bool fetch_about_welcome_handler(struct fetch_about_context *ctx)
{
fetch_msg msg;
 
/* content is going to return redirect */
fetch_set_http_code(ctx->fetchh, 302);
 
msg.type = FETCH_REDIRECT;
msg.data.redirect = "resource:welcome.html";
 
fetch_about_send_callback(&msg, ctx);
 
return true;
}
 
/* Forward declaration because this handler requires the handler table. */
static bool fetch_about_about_handler(struct fetch_about_context *ctx);
 
struct about_handlers {
const char *name; /**< name to match in url */
int name_len;
lwc_string *lname; /**< Interned name */
fetch_about_handler handler; /* handler for the url */
bool hidden; /* Flag indicating if entry should show in listing */
};
 
/** List of about paths and their handlers */
struct about_handlers about_handler_list[] = {
{ "credits", SLEN("credits"), NULL,
fetch_about_credits_handler, false },
{ "licence", SLEN("licence"), NULL,
fetch_about_licence_handler, false },
{ "license", SLEN("license"), NULL,
fetch_about_licence_handler, true },
{ "welcome", SLEN("welcome"), NULL,
fetch_about_welcome_handler, false },
{ "config", SLEN("config"), NULL,
fetch_about_config_handler, false },
{ "Choices", SLEN("Choices"), NULL,
fetch_about_choices_handler, false },
{ "testament", SLEN("testament"), NULL,
fetch_about_testament_handler, false },
{ "about", SLEN("about"), NULL,
fetch_about_about_handler, true },
{ "logo", SLEN("logo"), NULL,
fetch_about_logo_handler, true },
/* details about the image cache */
{ "imagecache", SLEN("imagecache"), NULL,
fetch_about_imagecache_handler, true },
/* The default blank page */
{ "blank", SLEN("blank"), NULL,
fetch_about_blank_handler, true }
};
 
#define about_handler_list_len (sizeof(about_handler_list) / \
sizeof(struct about_handlers))
 
/**
* List all the valid about: paths available
*
* \param ctx The fetch context.
* \return true for sucess or false to generate an error.
*/
static bool fetch_about_about_handler(struct fetch_about_context *ctx)
{
fetch_msg msg;
char buffer[1024];
int code = 200;
int slen;
unsigned int abt_loop = 0;
int res = 0;
 
/* content is going to return ok */
fetch_set_http_code(ctx->fetchh, code);
 
/* content type */
if (fetch_about_send_header(ctx, "Content-Type: text/html"))
goto fetch_about_config_handler_aborted;
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
 
slen = snprintf(buffer, sizeof buffer,
"<html>\n<head>\n"
"<title>NetSurf List of About pages</title>\n"
"<link rel=\"stylesheet\" type=\"text/css\" "
"href=\"resource:internal.css\">\n"
"</head>\n"
"<body id =\"aboutlist\">\n"
"<p class=\"banner\">"
"<a href=\"http://www.netsurf-browser.org/\">"
"<img src=\"resource:netsurf.png\" alt=\"NetSurf\"></a>"
"</p>\n"
"<h1>NetSurf List of About pages</h1>\n"
"<ul>\n");
 
for (abt_loop = 0; abt_loop < about_handler_list_len; abt_loop++) {
 
/* Skip over hidden entries */
if (about_handler_list[abt_loop].hidden)
continue;
 
res = snprintf(buffer + slen, sizeof buffer - slen,
"<li><a href=\"about:%s\">about:%s</a></li>\n",
about_handler_list[abt_loop].name,
about_handler_list[abt_loop].name);
if (res <= 0)
break; /* last option */
 
if (res >= (int)(sizeof buffer - slen)) {
/* last entry would not fit in buffer, submit buffer */
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_config_handler_aborted;
slen = 0;
} else {
/* normal addition */
slen += res;
}
}
 
slen += snprintf(buffer + slen, sizeof buffer - slen,
"</ul>\n</body>\n</html>\n");
 
msg.data.header_or_data.len = slen;
if (fetch_about_send_callback(&msg, ctx))
goto fetch_about_config_handler_aborted;
 
msg.type = FETCH_FINISHED;
fetch_about_send_callback(&msg, ctx);
 
return true;
 
fetch_about_config_handler_aborted:
return false;
}
 
 
/** callback to initialise the about fetcher. */
static bool fetch_about_initialise(lwc_string *scheme)
{
unsigned int abt_loop = 0;
lwc_error error;
 
for (abt_loop = 0; abt_loop < about_handler_list_len; abt_loop++) {
error = lwc_intern_string(about_handler_list[abt_loop].name,
about_handler_list[abt_loop].name_len,
&about_handler_list[abt_loop].lname);
if (error != lwc_error_ok) {
while (abt_loop-- != 0) {
lwc_string_unref(about_handler_list[abt_loop].lname);
}
return false;
}
}
 
return true;
}
 
/** callback to finalise the about fetcher. */
static void fetch_about_finalise(lwc_string *scheme)
{
unsigned int abt_loop = 0;
for (abt_loop = 0; abt_loop < about_handler_list_len; abt_loop++) {
lwc_string_unref(about_handler_list[abt_loop].lname);
}
}
 
static bool fetch_about_can_fetch(const nsurl *url)
{
return true;
}
 
/** callback to set up a about fetch context. */
static void *
fetch_about_setup(struct fetch *fetchh,
nsurl *url,
bool only_2xx,
bool downgrade_tls,
const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
const char **headers)
{
struct fetch_about_context *ctx;
unsigned int handler_loop;
lwc_string *path;
bool match;
 
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL)
return NULL;
 
path = nsurl_get_component(url, NSURL_PATH);
 
for (handler_loop = 0;
handler_loop < about_handler_list_len;
handler_loop++) {
ctx->handler = about_handler_list[handler_loop].handler;
if (lwc_string_isequal(path,
about_handler_list[handler_loop].lname,
&match) == lwc_error_ok && match) {
break;
}
}
 
if (path != NULL)
lwc_string_unref(path);
 
ctx->fetchh = fetchh;
ctx->url = nsurl_ref(url);
 
RING_INSERT(ring, ctx);
 
return ctx;
}
 
/** callback to free a about fetch */
static void fetch_about_free(void *ctx)
{
struct fetch_about_context *c = ctx;
nsurl_unref(c->url);
RING_REMOVE(ring, c);
free(ctx);
}
 
/** callback to start a about fetch */
static bool fetch_about_start(void *ctx)
{
return true;
}
 
/** callback to abort a about fetch */
static void fetch_about_abort(void *ctx)
{
struct fetch_about_context *c = ctx;
 
/* To avoid the poll loop having to deal with the fetch context
* disappearing from under it, we simply flag the abort here.
* The poll loop itself will perform the appropriate cleanup.
*/
c->aborted = true;
}
 
 
/** callback to poll for additional about fetch contents */
static void fetch_about_poll(lwc_string *scheme)
{
struct fetch_about_context *c, *next;
 
if (ring == NULL) return;
 
/* Iterate over ring, processing each pending fetch */
c = ring;
do {
/* Ignore fetches that have been flagged as locked.
* This allows safe re-entrant calls to this function.
* Re-entrancy can occur if, as a result of a callback,
* the interested party causes fetch_poll() to be called
* again.
*/
if (c->locked == true) {
next = c->r_next;
continue;
}
 
/* Only process non-aborted fetches */
if (c->aborted == false) {
/* about fetches can be processed in one go */
c->handler(c);
}
 
/* Compute next fetch item at the last possible moment
* as processing this item may have added to the ring
*/
next = c->r_next;
 
fetch_remove_from_queues(c->fetchh);
fetch_free(c->fetchh);
 
/* Advance to next ring entry, exiting if we've reached
* the start of the ring or the ring has become empty
*/
} while ( (c = next) != ring && ring != NULL);
}
 
void fetch_about_register(void)
{
lwc_string *scheme;
 
if (lwc_intern_string("about", SLEN("about"),
&scheme) != lwc_error_ok) {
die("Failed to initialise the fetch module "
"(couldn't intern \"about\").");
}
 
fetch_add_fetcher(scheme,
fetch_about_initialise,
fetch_about_can_fetch,
fetch_about_setup,
fetch_about_start,
fetch_about_abort,
fetch_about_free,
fetch_about_poll,
fetch_about_finalise);
}
/contrib/network/netsurf/netsurf/content/fetchers/about.h
0,0 → 1,28
/*
* Copyright 2011 Vincent Sanders <vince@netsurf-browser.org>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* about: URL method handler
*/
 
#ifndef NETSURF_CONTENT_FETCHERS_FETCH_ABOUT_H
#define NETSURF_CONTENT_FETCHERS_FETCH_ABOUT_H
 
void fetch_about_register(void);
 
#endif
/contrib/network/netsurf/netsurf/content/fetchers/curl.h
0,0 → 1,33
/*
* Copyright 2007 Daniel Silverstone <dsilvers@digital-scurf.org>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Fetching of data from a URL (Registration).
*/
 
#ifndef NETSURF_CONTENT_FETCHERS_FETCH_CURL_H
#define NETSURF_CONTENT_FETCHERS_FETCH_CURL_H
 
#include <curl/curl.h>
 
void fetch_curl_register(void);
 
/** Global cURL multi handle. */
extern CURLM *fetch_curl_multi;
 
#endif
/contrib/network/netsurf/netsurf/content/fetchers/data.c
0,0 → 1,345
/*
* Copyright 2008 Rob Kendrick <rjek@netsurf-browser.org>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/* data: URL handling. See http://tools.ietf.org/html/rfc2397 */
 
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <time.h>
 
#include <curl/curl.h> /* for URL unescaping functions */
 
#include <libwapcaplet/libwapcaplet.h>
 
#include "utils/config.h"
#include "content/fetch.h"
#include "content/fetchers/data.h"
#include "content/urldb.h"
#include "desktop/netsurf.h"
#include "desktop/options.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/url.h"
#include "utils/utils.h"
#include "utils/ring.h"
#include "utils/base64.h"
 
struct fetch_data_context {
struct fetch *parent_fetch;
char *url;
char *mimetype;
char *data;
size_t datalen;
bool base64;
 
bool aborted;
bool locked;
struct fetch_data_context *r_next, *r_prev;
};
 
static struct fetch_data_context *ring = NULL;
 
static CURL *curl;
 
static bool fetch_data_initialise(lwc_string *scheme)
{
LOG(("fetch_data_initialise called for %s", lwc_string_data(scheme)));
if ( (curl = curl_easy_init()) == NULL)
return false;
else
return true;
}
 
static void fetch_data_finalise(lwc_string *scheme)
{
LOG(("fetch_data_finalise called for %s", lwc_string_data(scheme)));
curl_easy_cleanup(curl);
}
 
static bool fetch_data_can_fetch(const nsurl *url)
{
return true;
}
 
static void *fetch_data_setup(struct fetch *parent_fetch, nsurl *url,
bool only_2xx, bool downgrade_tls, const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
const char **headers)
{
struct fetch_data_context *ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL)
return NULL;
ctx->parent_fetch = parent_fetch;
 
/* TODO: keep as nsurl to avoid copy */
ctx->url = malloc(nsurl_length(url) + 1);
if (ctx->url == NULL) {
free(ctx);
return NULL;
}
memcpy(ctx->url, nsurl_access(url), nsurl_length(url) + 1);
 
RING_INSERT(ring, ctx);
return ctx;
}
 
static bool fetch_data_start(void *ctx)
{
return true;
}
 
static void fetch_data_free(void *ctx)
{
struct fetch_data_context *c = ctx;
 
free(c->url);
free(c->data);
free(c->mimetype);
RING_REMOVE(ring, c);
free(ctx);
}
 
static void fetch_data_abort(void *ctx)
{
struct fetch_data_context *c = ctx;
 
/* To avoid the poll loop having to deal with the fetch context
* disappearing from under it, we simply flag the abort here.
* The poll loop itself will perform the appropriate cleanup.
*/
c->aborted = true;
}
 
static void fetch_data_send_callback(const fetch_msg *msg,
struct fetch_data_context *c)
{
c->locked = true;
fetch_send_callback(msg, c->parent_fetch);
c->locked = false;
}
 
static bool fetch_data_process(struct fetch_data_context *c)
{
fetch_msg msg;
char *params;
char *comma;
char *unescaped;
int templen;
/* format of a data: URL is:
* data:[<mimetype>][;base64],<data>
* The mimetype is optional. If it is missing, the , before the
* data must still be there.
*/
LOG(("url: %.140s", c->url));
if (strlen(c->url) < 6) {
/* 6 is the minimum possible length (data:,) */
msg.type = FETCH_ERROR;
msg.data.error = "Malformed data: URL";
fetch_data_send_callback(&msg, c);
return false;
}
/* skip the data: part */
params = c->url + SLEN("data:");
/* find the comma */
if ( (comma = strchr(params, ',')) == NULL) {
msg.type = FETCH_ERROR;
msg.data.error = "Malformed data: URL";
fetch_data_send_callback(&msg, c);
return false;
}
if (params[0] == ',') {
/* there is no mimetype here, assume text/plain */
c->mimetype = strdup("text/plain;charset=US-ASCII");
} else {
/* make a copy of everything between data: and the comma */
c->mimetype = strndup(params, comma - params);
}
if (c->mimetype == NULL) {
msg.type = FETCH_ERROR;
msg.data.error =
"Unable to allocate memory for mimetype in data: URL";
fetch_data_send_callback(&msg, c);
return false;
}
if (strcmp(c->mimetype + strlen(c->mimetype) - 7, ";base64") == 0) {
c->base64 = true;
c->mimetype[strlen(c->mimetype) - 7] = '\0';
} else {
c->base64 = false;
}
/* we URL unescape the data first, just incase some insane page
* decides to nest URL and base64 encoding. Like, say, Acid2.
*/
templen = c->datalen;
unescaped = curl_easy_unescape(curl, comma + 1, 0, &templen);
c->datalen = templen;
if (unescaped == NULL) {
msg.type = FETCH_ERROR;
msg.data.error = "Unable to URL decode data: URL";
fetch_data_send_callback(&msg, c);
return false;
}
if (c->base64) {
c->data = malloc(c->datalen); /* safe: always gets smaller */
if (base64_decode(unescaped, c->datalen, c->data,
&(c->datalen)) == false) {
msg.type = FETCH_ERROR;
msg.data.error = "Unable to Base64 decode data: URL";
fetch_data_send_callback(&msg, c);
curl_free(unescaped);
return false;
}
} else {
c->data = malloc(c->datalen);
if (c->data == NULL) {
msg.type = FETCH_ERROR;
msg.data.error =
"Unable to allocate memory for data: URL";
fetch_data_send_callback(&msg, c);
curl_free(unescaped);
return false;
}
memcpy(c->data, unescaped, c->datalen);
}
curl_free(unescaped);
return true;
}
 
static void fetch_data_poll(lwc_string *scheme)
{
fetch_msg msg;
struct fetch_data_context *c, *next;
if (ring == NULL) return;
/* Iterate over ring, processing each pending fetch */
c = ring;
do {
/* Ignore fetches that have been flagged as locked.
* This allows safe re-entrant calls to this function.
* Re-entrancy can occur if, as a result of a callback,
* the interested party causes fetch_poll() to be called
* again.
*/
if (c->locked == true) {
next = c->r_next;
continue;
}
 
/* Only process non-aborted fetches */
if (c->aborted == false && fetch_data_process(c) == true) {
char header[64];
 
fetch_set_http_code(c->parent_fetch, 200);
LOG(("setting data: MIME type to %s, length to %zd",
c->mimetype, c->datalen));
/* Any callback can result in the fetch being aborted.
* Therefore, we _must_ check for this after _every_
* call to fetch_data_send_callback().
*/
snprintf(header, sizeof header, "Content-Type: %s",
c->mimetype);
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf = (const uint8_t *) header;
msg.data.header_or_data.len = strlen(header);
fetch_data_send_callback(&msg, c);
 
if (c->aborted == false) {
snprintf(header, sizeof header,
"Content-Length: %"SSIZET_FMT,
c->datalen);
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf =
(const uint8_t *) header;
msg.data.header_or_data.len = strlen(header);
fetch_data_send_callback(&msg, c);
}
 
if (c->aborted == false) {
msg.type = FETCH_DATA;
msg.data.header_or_data.buf =
(const uint8_t *) c->data;
msg.data.header_or_data.len = c->datalen;
fetch_data_send_callback(&msg, c);
}
 
if (c->aborted == false) {
msg.type = FETCH_FINISHED;
fetch_data_send_callback(&msg, c);
}
} else {
LOG(("Processing of %s failed!", c->url));
 
/* Ensure that we're unlocked here. If we aren't,
* then fetch_data_process() is broken.
*/
assert(c->locked == false);
}
 
/* Compute next fetch item at the last possible moment as
* processing this item may have added to the ring.
*/
next = c->r_next;
 
fetch_remove_from_queues(c->parent_fetch);
fetch_free(c->parent_fetch);
 
/* Advance to next ring entry, exiting if we've reached
* the start of the ring or the ring has become empty
*/
} while ( (c = next) != ring && ring != NULL);
}
 
void fetch_data_register(void)
{
lwc_string *scheme;
 
if (lwc_intern_string("data", SLEN("data"), &scheme) != lwc_error_ok) {
die("Failed to initialise the fetch module "
"(couldn't intern \"data\").");
}
 
fetch_add_fetcher(scheme,
fetch_data_initialise,
fetch_data_can_fetch,
fetch_data_setup,
fetch_data_start,
fetch_data_abort,
fetch_data_free,
fetch_data_poll,
fetch_data_finalise);
}
/contrib/network/netsurf/netsurf/content/fetchers/data.h
0,0 → 1,28
/*
* Copyright 2008 Rob Kendrick <rjek@netsurf-browser.org>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* data: URL method handler
*/
 
#ifndef NETSURF_CONTENT_FETCHERS_FETCH_DATA_H
#define NETSURF_CONTENT_FETCHERS_FETCH_DATA_H
 
void fetch_data_register(void);
 
#endif
/contrib/network/netsurf/netsurf/content/fetchers/file.c
0,0 → 1,744
/*
* Copyright 2010 Vincent Sanders <vince@netsurf-browser.org>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/* file: URL handling. Based on the data fetcher by Rob Kendrick */
 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <inttypes.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <stdio.h>
#include <dirent.h>
#include <limits.h>
#include <stdarg.h>
 
#include "utils/config.h"
 
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
 
#include <libwapcaplet/libwapcaplet.h>
 
#include "content/dirlist.h"
#include "content/fetch.h"
#include "content/fetchers/file.h"
#include "content/urldb.h"
#include "desktop/netsurf.h"
#include "desktop/options.h"
#include "utils/errors.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/url.h"
#include "utils/utils.h"
#include "utils/ring.h"
 
/* Maximum size of read buffer */
#define FETCH_FILE_MAX_BUF_SIZE (1024 * 1024)
 
/** Context for a fetch */
struct fetch_file_context {
struct fetch_file_context *r_next, *r_prev;
 
struct fetch *fetchh; /**< Handle for this fetch */
 
bool aborted; /**< Flag indicating fetch has been aborted */
bool locked; /**< Flag indicating entry is already entered */
 
nsurl *url; /**< The full url the fetch refers to */
char *path; /**< The actual path to be used with open() */
 
time_t file_etag; /**< Request etag for file (previous st.m_time) */
};
 
static struct fetch_file_context *ring = NULL;
 
/** issue fetch callbacks with locking */
static inline bool fetch_file_send_callback(const fetch_msg *msg,
struct fetch_file_context *ctx)
{
ctx->locked = true;
fetch_send_callback(msg, ctx->fetchh);
ctx->locked = false;
 
return ctx->aborted;
}
 
static bool fetch_file_send_header(struct fetch_file_context *ctx,
const char *fmt, ...)
{
fetch_msg msg;
char header[64];
va_list ap;
 
va_start(ap, fmt);
 
vsnprintf(header, sizeof header, fmt, ap);
 
va_end(ap);
 
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf = (const uint8_t *) header;
msg.data.header_or_data.len = strlen(header);
fetch_file_send_callback(&msg, ctx);
 
return ctx->aborted;
}
 
/** callback to initialise the file fetcher. */
static bool fetch_file_initialise(lwc_string *scheme)
{
return true;
}
 
/** callback to initialise the file fetcher. */
static void fetch_file_finalise(lwc_string *scheme)
{
}
 
static bool fetch_file_can_fetch(const nsurl *url)
{
return true;
}
 
/** callback to set up a file fetch context. */
static void *
fetch_file_setup(struct fetch *fetchh,
nsurl *url,
bool only_2xx,
bool downgrade_tls,
const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
const char **headers)
{
struct fetch_file_context *ctx;
int i;
 
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL)
return NULL;
 
ctx->path = url_to_path(nsurl_access(url));
if (ctx->path == NULL) {
free(ctx);
return NULL;
}
 
ctx->url = nsurl_ref(url);
 
/* Scan request headers looking for If-None-Match */
for (i = 0; headers[i] != NULL; i++) {
if (strncasecmp(headers[i], "If-None-Match:",
SLEN("If-None-Match:")) == 0) {
/* If-None-Match: "12345678" */
const char *d = headers[i] + SLEN("If-None-Match:");
 
/* Scan to first digit, if any */
while (*d != '\0' && (*d < '0' || '9' < *d))
d++;
 
/* Convert to time_t */
if (*d != '\0')
ctx->file_etag = atoi(d);
}
}
 
ctx->fetchh = fetchh;
 
RING_INSERT(ring, ctx);
 
return ctx;
}
 
/** callback to free a file fetch */
static void fetch_file_free(void *ctx)
{
struct fetch_file_context *c = ctx;
nsurl_unref(c->url);
free(c->path);
RING_REMOVE(ring, c);
free(ctx);
}
 
/** callback to start a file fetch */
static bool fetch_file_start(void *ctx)
{
return true;
}
 
/** callback to abort a file fetch */
static void fetch_file_abort(void *ctx)
{
struct fetch_file_context *c = ctx;
 
/* To avoid the poll loop having to deal with the fetch context
* disappearing from under it, we simply flag the abort here.
* The poll loop itself will perform the appropriate cleanup.
*/
c->aborted = true;
}
 
static int fetch_file_errno_to_http_code(int error_no)
{
switch (error_no) {
case ENAMETOOLONG:
return 400;
case EACCES:
return 403;
case ENOENT:
return 404;
default:
break;
}
 
return 500;
}
 
static void fetch_file_process_error(struct fetch_file_context *ctx, int code)
{
fetch_msg msg;
char buffer[1024];
const char *title;
char key[8];
 
/* content is going to return error code */
fetch_set_http_code(ctx->fetchh, code);
 
/* content type */
if (fetch_file_send_header(ctx, "Content-Type: text/html"))
goto fetch_file_process_error_aborted;
 
snprintf(key, sizeof key, "HTTP%03d", code);
title = messages_get(key);
 
snprintf(buffer, sizeof buffer, "<html><head><title>%s</title></head>"
"<body><h1>%s</h1>"
"<p>Error %d while fetching file %s</p></body></html>",
title, title, code, nsurl_access(ctx->url));
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
msg.data.header_or_data.len = strlen(buffer);
if (fetch_file_send_callback(&msg, ctx))
goto fetch_file_process_error_aborted;
 
msg.type = FETCH_FINISHED;
fetch_file_send_callback(&msg, ctx);
 
fetch_file_process_error_aborted:
return;
}
 
 
/** Process object as a regular file */
static void fetch_file_process_plain(struct fetch_file_context *ctx,
struct stat *fdstat)
{
#ifdef HAVE_MMAP
fetch_msg msg;
char *buf = NULL;
size_t buf_size;
 
int fd; /**< The file descriptor of the object */
 
/* Check if we can just return not modified */
if (ctx->file_etag != 0 && ctx->file_etag == fdstat->st_mtime) {
fetch_set_http_code(ctx->fetchh, 304);
msg.type = FETCH_NOTMODIFIED;
fetch_file_send_callback(&msg, ctx);
return;
}
 
fd = open(ctx->path, O_RDONLY);
if (fd < 0) {
/* process errors as appropriate */
fetch_file_process_error(ctx,
fetch_file_errno_to_http_code(errno));
return;
}
 
/* set buffer size */
buf_size = fdstat->st_size;
 
/* allocate the buffer storage */
if (buf_size > 0) {
buf = mmap(NULL, buf_size, PROT_READ, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED) {
msg.type = FETCH_ERROR;
msg.data.error = "Unable to map memory for file data buffer";
fetch_file_send_callback(&msg, ctx);
close(fd);
return;
}
}
 
/* fetch is going to be successful */
fetch_set_http_code(ctx->fetchh, 200);
 
/* Any callback can result in the fetch being aborted.
* Therefore, we _must_ check for this after _every_ call to
* fetch_file_send_callback().
*/
 
/* content type */
if (fetch_file_send_header(ctx, "Content-Type: %s",
fetch_filetype(ctx->path)))
goto fetch_file_process_aborted;
 
/* content length */
if (fetch_file_send_header(ctx, "Content-Length: %"SSIZET_FMT, fdstat->st_size))
goto fetch_file_process_aborted;
 
/* create etag */
if (fetch_file_send_header(ctx, "ETag: \"%10" PRId64 "\"",
(int64_t) fdstat->st_mtime))
goto fetch_file_process_aborted;
 
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buf;
msg.data.header_or_data.len = buf_size;
fetch_file_send_callback(&msg, ctx);
 
if (ctx->aborted == false) {
msg.type = FETCH_FINISHED;
fetch_file_send_callback(&msg, ctx);
}
 
fetch_file_process_aborted:
 
if (buf != NULL)
munmap(buf, buf_size);
close(fd);
#else
fetch_msg msg;
char *buf;
size_t buf_size;
 
ssize_t tot_read = 0;
ssize_t res;
 
FILE *infile;
 
/* Check if we can just return not modified */
if (ctx->file_etag != 0 && ctx->file_etag == fdstat->st_mtime) {
fetch_set_http_code(ctx->fetchh, 304);
msg.type = FETCH_NOTMODIFIED;
fetch_file_send_callback(&msg, ctx);
return;
}
 
infile = fopen(ctx->path, "rb");
if (infile == NULL) {
/* process errors as appropriate */
fetch_file_process_error(ctx,
fetch_file_errno_to_http_code(errno));
return;
}
 
/* set buffer size */
buf_size = fdstat->st_size;
if (buf_size > FETCH_FILE_MAX_BUF_SIZE)
buf_size = FETCH_FILE_MAX_BUF_SIZE;
 
/* allocate the buffer storage */
buf = malloc(buf_size);
if (buf == NULL) {
msg.type = FETCH_ERROR;
msg.data.error =
"Unable to allocate memory for file data buffer";
fetch_file_send_callback(&msg, ctx);
fclose(infile);
return;
}
 
/* fetch is going to be successful */
fetch_set_http_code(ctx->fetchh, 200);
 
/* Any callback can result in the fetch being aborted.
* Therefore, we _must_ check for this after _every_ call to
* fetch_file_send_callback().
*/
 
/* content type */
if (fetch_file_send_header(ctx, "Content-Type: %s",
fetch_filetype(ctx->path)))
goto fetch_file_process_aborted;
 
/* content length */
if (fetch_file_send_header(ctx, "Content-Length: %"SSIZET_FMT, fdstat->st_size))
goto fetch_file_process_aborted;
 
/* create etag */
if (fetch_file_send_header(ctx, "ETag: \"%10" PRId64 "\"",
(int64_t) fdstat->st_mtime))
goto fetch_file_process_aborted;
 
/* main data loop */
while (tot_read < fdstat->st_size) {
res = fread(buf, 1, buf_size, infile);
if (res == 0) {
if (feof(infile)) {
msg.type = FETCH_ERROR;
msg.data.error = "Unexpected EOF reading file";
fetch_file_send_callback(&msg, ctx);
goto fetch_file_process_aborted;
} else {
msg.type = FETCH_ERROR;
msg.data.error = "Error reading file";
fetch_file_send_callback(&msg, ctx);
goto fetch_file_process_aborted;
}
}
tot_read += res;
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buf;
msg.data.header_or_data.len = res;
if (fetch_file_send_callback(&msg, ctx))
break;
}
 
if (ctx->aborted == false) {
msg.type = FETCH_FINISHED;
fetch_file_send_callback(&msg, ctx);
}
 
fetch_file_process_aborted:
 
fclose(infile);
free(buf);
#endif
return;
}
 
static char *gen_nice_title(char *path)
{
char *nice_path, *cnv, *tmp;
char *title;
int title_length;
 
/* Convert path for display */
nice_path = malloc(strlen(path) * SLEN("&amp;") + 1);
if (nice_path == NULL) {
return NULL;
}
 
/* Escape special HTML characters */
for (cnv = nice_path, tmp = path; *tmp != '\0'; tmp++) {
if (*tmp == '<') {
*cnv++ = '&';
*cnv++ = 'l';
*cnv++ = 't';
*cnv++ = ';';
} else if (*tmp == '>') {
*cnv++ = '&';
*cnv++ = 'g';
*cnv++ = 't';
*cnv++ = ';';
} else if (*tmp == '&') {
*cnv++ = '&';
*cnv++ = 'a';
*cnv++ = 'm';
*cnv++ = 'p';
*cnv++ = ';';
} else {
*cnv++ = *tmp;
}
}
*cnv = '\0';
 
/* Construct a localised title string */
title_length = (cnv - nice_path) + strlen(messages_get("FileIndex"));
title = malloc(title_length + 1);
 
if (title == NULL) {
free(nice_path);
return NULL;
}
 
/* Set title to localised "Index of <nice_path>" */
snprintf(title, title_length, messages_get("FileIndex"), nice_path);
 
free(nice_path);
 
return title;
}
 
 
static void fetch_file_process_dir(struct fetch_file_context *ctx,
struct stat *fdstat)
{
fetch_msg msg;
char buffer[1024]; /* Output buffer */
bool even = false; /* formatting flag */
char *title; /* pretty printed title */
nserror err; /* result from url routines */
nsurl *up; /* url of parent */
char *path; /* url for list entries */
 
DIR *scandir; /* handle for enumerating the directory */
struct dirent* ent; /* leaf directory entry */
struct stat ent_stat; /* stat result of leaf entry */
char datebuf[64]; /* buffer for date text */
char timebuf[64]; /* buffer for time text */
char urlpath[PATH_MAX]; /* buffer for leaf entry path */
 
scandir = opendir(ctx->path);
if (scandir == NULL) {
fetch_file_process_error(ctx,
fetch_file_errno_to_http_code(errno));
return;
}
 
/* fetch is going to be successful */
fetch_set_http_code(ctx->fetchh, 200);
 
/* force no-cache */
if (fetch_file_send_header(ctx, "Cache-Control: no-cache"))
goto fetch_file_process_dir_aborted;
 
/* content type */
if (fetch_file_send_header(ctx, "Content-Type: text/html"))
goto fetch_file_process_dir_aborted;
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
 
/* directory listing top */
dirlist_generate_top(buffer, sizeof buffer);
msg.data.header_or_data.len = strlen(buffer);
if (fetch_file_send_callback(&msg, ctx))
goto fetch_file_process_dir_aborted;
 
/* directory listing title */
title = gen_nice_title(ctx->path);
dirlist_generate_title(title, buffer, sizeof buffer);
free(title);
msg.data.header_or_data.len = strlen(buffer);
if (fetch_file_send_callback(&msg, ctx))
goto fetch_file_process_dir_aborted;
 
/* Print parent directory link */
err = nsurl_parent(ctx->url, &up);
if (err == NSERROR_OK) {
if (nsurl_compare(ctx->url, up, NSURL_COMPLETE) == false) {
/* different URL; have parent */
dirlist_generate_parent_link(nsurl_access(up),
buffer, sizeof buffer);
 
msg.data.header_or_data.len = strlen(buffer);
fetch_file_send_callback(&msg, ctx);
}
nsurl_unref(up);
 
if (ctx->aborted)
goto fetch_file_process_dir_aborted;
 
}
 
/* directory list headings */
dirlist_generate_headings(buffer, sizeof buffer);
msg.data.header_or_data.len = strlen(buffer);
if (fetch_file_send_callback(&msg, ctx))
goto fetch_file_process_dir_aborted;
 
while ((ent = readdir(scandir)) != NULL) {
 
if (ent->d_name[0] == '.')
continue;
 
strncpy(urlpath, ctx->path, sizeof urlpath);
if (path_add_part(urlpath, sizeof urlpath,
ent->d_name) == false)
continue;
 
if (stat(urlpath, &ent_stat) != 0) {
ent_stat.st_mode = 0;
datebuf[0] = 0;
timebuf[0] = 0;
} else {
/* Get date in output format */
if (strftime((char *)&datebuf, sizeof datebuf,
"%a %d %b %Y",
localtime(&ent_stat.st_mtime)) == 0) {
strncpy(datebuf, "-", sizeof datebuf);
}
 
/* Get time in output format */
if (strftime((char *)&timebuf, sizeof timebuf,
"%H:%M",
localtime(&ent_stat.st_mtime)) == 0) {
strncpy(timebuf, "-", sizeof timebuf);
}
}
 
if((path = path_to_url(urlpath)) == NULL)
continue;
 
if (S_ISREG(ent_stat.st_mode)) {
/* regular file */
dirlist_generate_row(even,
false,
path,
ent->d_name,
fetch_filetype(urlpath),
ent_stat.st_size,
datebuf, timebuf,
buffer, sizeof(buffer));
} else if (S_ISDIR(ent_stat.st_mode)) {
/* directory */
dirlist_generate_row(even,
true,
path,
ent->d_name,
messages_get("FileDirectory"),
-1,
datebuf, timebuf,
buffer, sizeof(buffer));
} else {
/* something else */
dirlist_generate_row(even,
false,
path,
ent->d_name,
"",
-1,
datebuf, timebuf,
buffer, sizeof(buffer));
}
 
free(path);
 
msg.data.header_or_data.len = strlen(buffer);
if (fetch_file_send_callback(&msg, ctx))
goto fetch_file_process_dir_aborted;
 
even = !even;
}
 
/* directory listing bottom */
dirlist_generate_bottom(buffer, sizeof buffer);
msg.data.header_or_data.len = strlen(buffer);
if (fetch_file_send_callback(&msg, ctx))
goto fetch_file_process_dir_aborted;
 
msg.type = FETCH_FINISHED;
fetch_file_send_callback(&msg, ctx);
 
fetch_file_process_dir_aborted:
 
closedir(scandir);
}
 
 
/* process a file fetch */
static void fetch_file_process(struct fetch_file_context *ctx)
{
struct stat fdstat; /**< The objects stat */
 
if (stat(ctx->path, &fdstat) != 0) {
/* process errors as appropriate */
fetch_file_process_error(ctx,
fetch_file_errno_to_http_code(errno));
return;
}
 
if (S_ISDIR(fdstat.st_mode)) {
/* directory listing */
fetch_file_process_dir(ctx, &fdstat);
return;
} else if (S_ISREG(fdstat.st_mode)) {
/* regular file */
fetch_file_process_plain(ctx, &fdstat);
return;
} else {
/* unhandled type of file */
fetch_file_process_error(ctx, 501);
}
 
return;
}
 
/** callback to poll for additional file fetch contents */
static void fetch_file_poll(lwc_string *scheme)
{
struct fetch_file_context *c, *next;
 
if (ring == NULL) return;
 
/* Iterate over ring, processing each pending fetch */
c = ring;
do {
/* Ignore fetches that have been flagged as locked.
* This allows safe re-entrant calls to this function.
* Re-entrancy can occur if, as a result of a callback,
* the interested party causes fetch_poll() to be called
* again.
*/
if (c->locked == true) {
next = c->r_next;
continue;
}
 
/* Only process non-aborted fetches */
if (c->aborted == false) {
/* file fetches can be processed in one go */
fetch_file_process(c);
}
 
/* Compute next fetch item at the last possible moment as
* processing this item may have added to the ring.
*/
next = c->r_next;
 
fetch_remove_from_queues(c->fetchh);
fetch_free(c->fetchh);
 
/* Advance to next ring entry, exiting if we've reached
* the start of the ring or the ring has become empty
*/
} while ( (c = next) != ring && ring != NULL);
}
 
void fetch_file_register(void)
{
lwc_string *scheme;
 
if (lwc_intern_string("file", SLEN("file"), &scheme) != lwc_error_ok) {
die("Failed to initialise the fetch module "
"(couldn't intern \"file\").");
}
 
fetch_add_fetcher(scheme,
fetch_file_initialise,
fetch_file_can_fetch,
fetch_file_setup,
fetch_file_start,
fetch_file_abort,
fetch_file_free,
fetch_file_poll,
fetch_file_finalise);
}
/contrib/network/netsurf/netsurf/content/fetchers/file.h
0,0 → 1,28
/*
* Copyright 2010 Vincent Sanders <vince@netsurf-browser.org>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* file: URL method handler
*/
 
#ifndef NETSURF_CONTENT_FETCHERS_FETCH_FILE_H
#define NETSURF_CONTENT_FETCHERS_FETCH_FILE_H
 
void fetch_file_register(void);
 
#endif
/contrib/network/netsurf/netsurf/content/fetchers/make.fetch
0,0 → 1,21
CFLAGS += -O2
NETSURF_FB_FRONTEND := sdl
NETSURF_FB_FONTLIB := internal
 
NETSURF_FRAMEBUFFER_BIN := $(PREFIX)/bin/
 
# Default resource install path
NETSURF_FRAMEBUFFER_RESOURCES := $(PREFIX)/share/netsurf/
 
# Default framebuffer search path
NETSURF_FB_RESPATH := $${HOME}/.netsurf/:$${NETSURFRES}:$(NETSURF_FRAMEBUFFER_RESOURCES):./framebuffer/res
 
# freetype compiled in font serch path
NETSURF_FB_FONTPATH := /usr/share/fonts/truetype/ttf-dejavu:/usr/share/fonts/truetype/msttcorefonts
OBJS := curl.o data.o file.o about.o resource.o
 
 
OUTFILE = TEST.o
CFLAGS += -I ../include/ -I ../ -I../../ -I./ -I/home/sourcerer/kos_src/newenginek/kolibri/include
include $(MENUETDEV)/makefiles/Makefile_for_o_lib
/contrib/network/netsurf/netsurf/content/fetchers/resource.c
0,0 → 1,369
/*
* Copyright 2011 Vincent Sanders <vince@netsurf-browser.org>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/* resource: URL handling. Based on the data fetcher by Rob Kendrick */
 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <inttypes.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <stdio.h>
#include <dirent.h>
#include <limits.h>
#include <stdarg.h>
 
#include <libwapcaplet/libwapcaplet.h>
 
#include "utils/config.h"
#include "content/dirlist.h"
#include "content/fetch.h"
#include "content/fetchers/resource.h"
#include "content/urldb.h"
#include "desktop/gui.h"
#include "desktop/options.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/url.h"
#include "utils/utils.h"
#include "utils/ring.h"
 
struct fetch_resource_context;
 
typedef bool (*fetch_resource_handler)(struct fetch_resource_context *);
 
/** Context for an resource fetch */
struct fetch_resource_context {
struct fetch_resource_context *r_next, *r_prev;
 
struct fetch *fetchh; /**< Handle for this fetch */
 
bool aborted; /**< Flag indicating fetch has been aborted */
bool locked; /**< Flag indicating entry is already entered */
 
nsurl *url;
nsurl *redirect_url; /**< The url the fetch redirects to */
 
fetch_resource_handler handler;
};
 
static struct fetch_resource_context *ring = NULL;
 
/** Valid resource paths */
static const char *fetch_resource_paths[] = {
"adblock.css",
"default.css",
"internal.css",
"quirks.css",
"user.css",
"credits.html",
"licence.html",
"welcome.html",
"favicon.ico",
"netsurf.png"
};
static struct fetch_resource_map_entry {
lwc_string *path;
nsurl *url;
} fetch_resource_map[NOF_ELEMENTS(fetch_resource_paths)];
 
static uint32_t fetch_resource_path_count;
 
/** issue fetch callbacks with locking */
static inline bool fetch_resource_send_callback(const fetch_msg *msg,
struct fetch_resource_context *ctx)
{
ctx->locked = true;
fetch_send_callback(msg, ctx->fetchh);
ctx->locked = false;
 
return ctx->aborted;
}
 
static bool fetch_resource_send_header(struct fetch_resource_context *ctx,
const char *fmt, ...)
{
fetch_msg msg;
char header[64];
va_list ap;
 
va_start(ap, fmt);
 
vsnprintf(header, sizeof header, fmt, ap);
 
va_end(ap);
 
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf = (const uint8_t *) header;
msg.data.header_or_data.len = strlen(header);
fetch_resource_send_callback(&msg, ctx);
 
return ctx->aborted;
}
 
 
 
 
static bool fetch_resource_redirect_handler(struct fetch_resource_context *ctx)
{
fetch_msg msg;
 
/* content is going to return redirect */
fetch_set_http_code(ctx->fetchh, 302);
 
msg.type = FETCH_REDIRECT;
msg.data.redirect = nsurl_access(ctx->redirect_url);
fetch_resource_send_callback(&msg, ctx);
 
return true;
}
 
 
static bool fetch_resource_notfound_handler(struct fetch_resource_context *ctx)
{
fetch_msg msg;
int code = 404;
char buffer[1024];
const char *title;
char key[8];
 
/* content is going to return error code */
fetch_set_http_code(ctx->fetchh, code);
 
/* content type */
if (fetch_resource_send_header(ctx, "Content-Type: text/html"))
goto fetch_resource_notfound_handler_aborted;
 
snprintf(key, sizeof key, "HTTP%03d", code);
title = messages_get(key);
 
snprintf(buffer, sizeof buffer, "<html><head><title>%s</title></head>"
"<body><h1>%s</h1>"
"<p>Error %d while fetching file %s</p></body></html>",
title, title, code, nsurl_access(ctx->url));
 
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) buffer;
msg.data.header_or_data.len = strlen(buffer);
if (fetch_resource_send_callback(&msg, ctx))
goto fetch_resource_notfound_handler_aborted;
 
msg.type = FETCH_FINISHED;
fetch_resource_send_callback(&msg, ctx);
 
fetch_resource_notfound_handler_aborted:
return false;
}
 
 
 
/** callback to initialise the resource fetcher. */
static bool fetch_resource_initialise(lwc_string *scheme)
{
struct fetch_resource_map_entry *e;
uint32_t i;
 
fetch_resource_path_count = 0;
 
for (i = 0; i < NOF_ELEMENTS(fetch_resource_paths); i++) {
e = &fetch_resource_map[fetch_resource_path_count];
 
if (lwc_intern_string(fetch_resource_paths[i],
strlen(fetch_resource_paths[i]),
&e->path) != lwc_error_ok) {
while (i > 0) {
i--;
lwc_string_unref(fetch_resource_map[i].path);
nsurl_unref(fetch_resource_map[i].url);
}
}
 
e->url = gui_get_resource_url(fetch_resource_paths[i]);
LOG(("URL is %s " ,lwc_string_data(e->path)));
if (e->url == NULL) {
lwc_string_unref(e->path);
} else {
fetch_resource_path_count++;
}
}
 
return true;
}
 
/** callback to finalise the resource fetcher. */
static void fetch_resource_finalise(lwc_string *scheme)
{
uint32_t i;
 
for (i = 0; i < fetch_resource_path_count; i++) {
lwc_string_unref(fetch_resource_map[i].path);
nsurl_unref(fetch_resource_map[i].url);
}
}
 
static bool fetch_resource_can_fetch(const nsurl *url)
{
return true;
}
 
/** callback to set up a resource fetch context. */
static void *
fetch_resource_setup(struct fetch *fetchh,
nsurl *url,
bool only_2xx,
bool downgrade_tls,
const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
const char **headers)
{
struct fetch_resource_context *ctx;
lwc_string *path;
 
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL)
return NULL;
 
ctx->handler = fetch_resource_notfound_handler;
 
if ((path = nsurl_get_component(url, NSURL_PATH)) != NULL) {
uint32_t i;
bool match;
 
/* Ensure requested path is valid */
for (i = 0; i < fetch_resource_path_count; i++) {
if (lwc_string_isequal(path,
fetch_resource_map[i].path,
&match) == lwc_error_ok && match) {
ctx->redirect_url =
nsurl_ref(fetch_resource_map[i].url);
ctx->handler =
fetch_resource_redirect_handler;
break;
}
}
 
lwc_string_unref(path);
}
 
ctx->url = nsurl_ref(url);
 
ctx->fetchh = fetchh;
 
RING_INSERT(ring, ctx);
 
return ctx;
}
 
/** callback to free a resource fetch */
static void fetch_resource_free(void *ctx)
{
struct fetch_resource_context *c = ctx;
if (c->redirect_url != NULL)
nsurl_unref(c->redirect_url);
if (c->url != NULL)
nsurl_unref(c->url);
RING_REMOVE(ring, c);
free(ctx);
}
 
/** callback to start a resource fetch */
static bool fetch_resource_start(void *ctx)
{
return true;
}
 
/** callback to abort a resource fetch */
static void fetch_resource_abort(void *ctx)
{
struct fetch_resource_context *c = ctx;
 
/* To avoid the poll loop having to deal with the fetch context
* disappearing from under it, we simply flag the abort here.
* The poll loop itself will perform the appropriate cleanup.
*/
c->aborted = true;
}
 
 
/** callback to poll for additional resource fetch contents */
static void fetch_resource_poll(lwc_string *scheme)
{
struct fetch_resource_context *c, *next;
 
if (ring == NULL) return;
 
/* Iterate over ring, processing each pending fetch */
c = ring;
do {
/* Ignore fetches that have been flagged as locked.
* This allows safe re-entrant calls to this function.
* Re-entrancy can occur if, as a result of a callback,
* the interested party causes fetch_poll() to be called
* again.
*/
if (c->locked == true) {
next = c->r_next;
continue;
}
 
/* Only process non-aborted fetches */
if (c->aborted == false) {
/* resource fetches can be processed in one go */
c->handler(c);
}
 
/* Compute next fetch item at the last possible moment
* as processing this item may have added to the ring
*/
next = c->r_next;
 
fetch_remove_from_queues(c->fetchh);
fetch_free(c->fetchh);
 
/* Advance to next ring entry, exiting if we've reached
* the start of the ring or the ring has become empty
*/
} while ( (c = next) != ring && ring != NULL);
}
 
void fetch_resource_register(void)
{
lwc_string *scheme;
 
if (lwc_intern_string("resource", SLEN("resource"),
&scheme) != lwc_error_ok) {
die("Failed to initialise the fetch module "
"(couldn't intern \"resource\").");
}
 
fetch_add_fetcher(scheme,
fetch_resource_initialise,
fetch_resource_can_fetch,
fetch_resource_setup,
fetch_resource_start,
fetch_resource_abort,
fetch_resource_free,
fetch_resource_poll,
fetch_resource_finalise);
}
/contrib/network/netsurf/netsurf/content/fetchers/resource.h
0,0 → 1,40
/*
* Copyright 2011 Vincent Sanders <vince@netsurf-browser.org>
*
* This file is part of NetSurf.
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* resource: URL method handler.
*
* The resource fetcher is intended to provide a flat uniform URL
* space for browser local resources referenced by URL. Using this
* scheme each frontend is only required to provide a single entry
* point to locate resources which can be accessed by the standard URL
* type scheme.
*
*/
 
#ifndef NETSURF_CONTENT_FETCHERS_FETCH_RESOURCE_H
#define NETSURF_CONTENT_FETCHERS_FETCH_RESOURCE_H
 
/**
* Register the resource scheme.
*
* should only be called from the fetch initialise
*/
void fetch_resource_register(void);
 
#endif
/contrib/network/netsurf/netsurf/content/content.c
0,0 → 1,1375
/*
* Copyright 2005-2007 James Bursa <bursa@users.sourceforge.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Content handling (implementation).
*
* This implementation is based on the ::handler_map array, which maps
* ::content_type to the functions which implement that type.
*/
 
#include <assert.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include "utils/config.h"
#include "content/content_protected.h"
#include "content/hlcache.h"
#include "css/css.h"
#include "image/bitmap.h"
#include "desktop/browser.h"
#include "desktop/options.h"
#include "render/html.h"
#include "render/textplain.h"
 
#include "utils/http.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/utils.h"
 
#define URL_FMT_SPC "%.140s"
 
const char * const content_status_name[] = {
"LOADING",
"READY",
"DONE",
"ERROR"
};
 
static nserror content_llcache_callback(llcache_handle *llcache,
const llcache_event *event, void *pw);
static void content_convert(struct content *c);
 
 
/**
* Initialise a new content structure.
*
* \param c Content to initialise
* \param handler Content handler
* \param imime_type MIME type of content
* \param params HTTP parameters
* \param llcache Source data handle
* \param fallback_charset Fallback charset
* \param quirks Quirkiness of content
* \return NSERROR_OK on success, appropriate error otherwise
*/
 
nserror content__init(struct content *c, const content_handler *handler,
lwc_string *imime_type, const http_parameter *params,
llcache_handle *llcache, const char *fallback_charset,
bool quirks)
{
struct content_user *user_sentinel;
nserror error;
LOG(("url "URL_FMT_SPC" -> %p",
nsurl_access(llcache_handle_get_url(llcache)), c));
 
user_sentinel = calloc(1, sizeof(struct content_user));
if (user_sentinel == NULL) {
return NSERROR_NOMEM;
}
 
if (fallback_charset != NULL) {
c->fallback_charset = strdup(fallback_charset);
if (c->fallback_charset == NULL) {
return NSERROR_NOMEM;
}
}
 
c->llcache = llcache;
c->mime_type = lwc_string_ref(imime_type);
c->handler = handler;
c->status = CONTENT_STATUS_LOADING;
c->width = 0;
c->height = 0;
c->available_width = 0;
c->quirks = quirks;
c->refresh = 0;
c->time = wallclock();
c->size = 0;
c->title = NULL;
c->active = 0;
user_sentinel->callback = NULL;
user_sentinel->pw = NULL;
user_sentinel->next = NULL;
c->user_list = user_sentinel;
c->sub_status[0] = 0;
c->locked = false;
c->total_size = 0;
c->http_code = 0;
c->error_count = 0;
 
content_set_status(c, messages_get("Loading"));
 
/* Finally, claim low-level cache events */
error = llcache_handle_change_callback(llcache,
content_llcache_callback, c);
if (error != NSERROR_OK) {
lwc_string_unref(c->mime_type);
return error;
}
 
return NSERROR_OK;
}
 
/**
* Handler for low-level cache events
*
* \param llcache Low-level cache handle
* \param event Event details
* \param pw Pointer to our context
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror content_llcache_callback(llcache_handle *llcache,
const llcache_event *event, void *pw)
{
struct content *c = pw;
union content_msg_data msg_data;
nserror error = NSERROR_OK;
 
switch (event->type) {
case LLCACHE_EVENT_HAD_HEADERS:
/* Will never happen: handled in hlcache */
break;
case LLCACHE_EVENT_HAD_DATA:
if (c->handler->process_data != NULL) {
if (c->handler->process_data(c,
(const char *) event->data.data.buf,
event->data.data.len) == false) {
llcache_handle_abort(c->llcache);
c->status = CONTENT_STATUS_ERROR;
/** \todo It's not clear what error this is */
error = NSERROR_NOMEM;
}
}
break;
case LLCACHE_EVENT_DONE:
{
size_t source_size;
 
(void) llcache_handle_get_source_data(llcache, &source_size);
 
content_set_status(c, messages_get("Processing"));
msg_data.explicit_status_text = NULL;
content_broadcast(c, CONTENT_MSG_STATUS, msg_data);
 
content_convert(c);
}
break;
case LLCACHE_EVENT_ERROR:
/** \todo Error page? */
c->status = CONTENT_STATUS_ERROR;
msg_data.error = event->data.msg;
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
break;
case LLCACHE_EVENT_PROGRESS:
content_set_status(c, event->data.msg);
msg_data.explicit_status_text = NULL;
content_broadcast(c, CONTENT_MSG_STATUS, msg_data);
break;
}
 
return error;
}
 
/**
* Get whether a content can reformat
*
* \param h content to check
* \return whether the content can reformat
*/
bool content_can_reformat(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
 
if (c == NULL)
return false;
 
return (c->handler->reformat != NULL);
}
 
 
static void content_update_status(struct content *c)
{
if (c->status == CONTENT_STATUS_LOADING ||
c->status == CONTENT_STATUS_READY) {
/* Not done yet */
snprintf(c->status_message, sizeof (c->status_message),
"%s%s%s", messages_get("Fetching"),
c->sub_status ? ", " : " ", c->sub_status);
} else {
unsigned int time = c->time;
snprintf(c->status_message, sizeof (c->status_message),
"%s (%.1fs)", messages_get("Done"),
(float) time / 100);
}
}
 
 
/**
* Updates content with new status.
*
* The textual status contained in the content is updated with given string.
*
* \param status_message new textual status
*/
 
void content_set_status(struct content *c, const char *status_message)
{
size_t len = strlen(status_message);
 
if (len >= sizeof(c->sub_status)) {
len = sizeof(c->sub_status) - 1;
}
memcpy(c->sub_status, status_message, len);
c->sub_status[len] = '\0';
 
content_update_status(c);
}
 
 
/**
* All data has arrived, convert for display.
*
* Calls the convert function for the content.
*
* - If the conversion succeeds, but there is still some processing required
* (eg. loading images), the content gets status CONTENT_STATUS_READY, and a
* CONTENT_MSG_READY is sent to all users.
* - If the conversion succeeds and is complete, the content gets status
* CONTENT_STATUS_DONE, and CONTENT_MSG_READY then CONTENT_MSG_DONE are sent.
* - If the conversion fails, CONTENT_MSG_ERROR is sent. The content will soon
* be destroyed and must no longer be used.
*/
 
void content_convert(struct content *c)
{
assert(c);
assert(c->status == CONTENT_STATUS_LOADING ||
c->status == CONTENT_STATUS_ERROR);
 
if (c->status != CONTENT_STATUS_LOADING)
return;
 
if (c->locked == true)
return;
LOG(("content "URL_FMT_SPC" (%p)",
nsurl_access(llcache_handle_get_url(c->llcache)), c));
 
if (c->handler->data_complete != NULL) {
c->locked = true;
if (c->handler->data_complete(c) == false) {
content_set_error(c);
}
/* Conversion to the READY state will unlock the content */
} else {
content_set_ready(c);
content_set_done(c);
}
}
 
/**
* Put a content in status CONTENT_STATUS_READY and unlock the content.
*/
 
void content_set_ready(struct content *c)
{
union content_msg_data msg_data;
 
/* The content must be locked at this point, as it can only
* become READY after conversion. */
assert(c->locked);
c->locked = false;
 
c->status = CONTENT_STATUS_READY;
content_update_status(c);
content_broadcast(c, CONTENT_MSG_READY, msg_data);
}
 
/**
* Put a content in status CONTENT_STATUS_DONE.
*/
 
void content_set_done(struct content *c)
{
union content_msg_data msg_data;
 
c->status = CONTENT_STATUS_DONE;
c->time = wallclock() - c->time;
content_update_status(c);
content_broadcast(c, CONTENT_MSG_DONE, msg_data);
}
 
/**
* Put a content in status CONTENT_STATUS_ERROR and unlock the content.
*
* \note We expect the caller to broadcast an error report if needed.
*/
 
void content_set_error(struct content *c)
{
c->locked = false;
c->status = CONTENT_STATUS_ERROR;
}
 
/**
* Reformat to new size.
*
* Calls the reformat function for the content.
*/
 
void content_reformat(hlcache_handle *h, bool background,
int width, int height)
{
content__reformat(hlcache_handle_get_content(h), background,
width, height);
}
 
void content__reformat(struct content *c, bool background,
int width, int height)
{
union content_msg_data data;
assert(c != 0);
assert(c->status == CONTENT_STATUS_READY ||
c->status == CONTENT_STATUS_DONE);
assert(c->locked == false);
LOG(("%p %s", c, nsurl_access(llcache_handle_get_url(c->llcache))));
c->available_width = width;
if (c->handler->reformat != NULL) {
 
c->locked = true;
c->handler->reformat(c, width, height);
c->locked = false;
 
data.background = background;
content_broadcast(c, CONTENT_MSG_REFORMAT, data);
}
}
 
 
/**
* Destroy and free a content.
*
* Calls the destroy function for the content, and frees the structure.
*/
 
void content_destroy(struct content *c)
{
struct content_rfc5988_link *link;
 
assert(c);
LOG(("content %p %s", c,
nsurl_access(llcache_handle_get_url(c->llcache))));
assert(c->locked == false);
 
if (c->handler->destroy != NULL)
c->handler->destroy(c);
 
llcache_handle_release(c->llcache);
c->llcache = NULL;
 
lwc_string_unref(c->mime_type);
 
/* release metadata links */
link = c->links;
while (link != NULL) {
link = content__free_rfc5988_link(link);
}
 
/* free the user list */
if (c->user_list != NULL) {
free(c->user_list);
}
 
/* free the title */
if (c->title != NULL) {
free(c->title);
}
 
/* free the fallback characterset */
if (c->fallback_charset != NULL) {
free(c->fallback_charset);
}
 
free(c);
}
 
 
/**
* Handle mouse movements in a content window.
*
* \param h Content handle
* \param bw browser window
* \param mouse state of mouse buttons and modifier keys
* \param x coordinate of mouse
* \param y coordinate of mouse
*/
 
void content_mouse_track(hlcache_handle *h, struct browser_window *bw,
browser_mouse_state mouse, int x, int y)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != NULL);
 
if (c->handler->mouse_track != NULL) {
c->handler->mouse_track(c, bw, mouse, x, y);
} else {
union content_msg_data msg_data;
msg_data.pointer = BROWSER_POINTER_AUTO;
content_broadcast(c, CONTENT_MSG_POINTER, msg_data);
}
 
 
return;
}
 
 
/**
* Handle mouse clicks and movements in a content window.
*
* \param h Content handle
* \param bw browser window
* \param mouse state of mouse buttons and modifier keys
* \param x coordinate of mouse
* \param y coordinate of mouse
*
* This function handles both hovering and clicking. It is important that the
* code path is identical (except that hovering doesn't carry out the action),
* so that the status bar reflects exactly what will happen. Having separate
* code paths opens the possibility that an attacker will make the status bar
* show some harmless action where clicking will be harmful.
*/
 
void content_mouse_action(hlcache_handle *h, struct browser_window *bw,
browser_mouse_state mouse, int x, int y)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != NULL);
 
if (c->handler->mouse_action != NULL)
c->handler->mouse_action(c, bw, mouse, x, y);
 
return;
}
 
 
/**
* Request a redraw of an area of a content
*
* \param h high-level cache handle
* \param x x co-ord of left edge
* \param y y co-ord of top edge
* \param width Width of rectangle
* \param height Height of rectangle
*/
void content_request_redraw(struct hlcache_handle *h,
int x, int y, int width, int height)
{
content__request_redraw(hlcache_handle_get_content(h),
x, y, width, height);
}
 
 
/**
* Request a redraw of an area of a content
*
* \param c Content
* \param x x co-ord of left edge
* \param y y co-ord of top edge
* \param width Width of rectangle
* \param height Height of rectangle
*/
void content__request_redraw(struct content *c,
int x, int y, int width, int height)
{
union content_msg_data data;
 
if (c == NULL)
return;
 
data.redraw.x = x;
data.redraw.y = y;
data.redraw.width = width;
data.redraw.height = height;
 
data.redraw.full_redraw = true;
 
data.redraw.object = c;
data.redraw.object_x = 0;
data.redraw.object_y = 0;
data.redraw.object_width = c->width;
data.redraw.object_height = c->height;
 
content_broadcast(c, CONTENT_MSG_REDRAW, data);
}
 
/**
* Display content on screen with optional tiling.
*
* Calls the redraw_tile function for the content, or emulates it with the
* redraw function if it doesn't exist.
*/
 
bool content_redraw(hlcache_handle *h, struct content_redraw_data *data,
const struct rect *clip, const struct redraw_context *ctx)
{
struct content *c = hlcache_handle_get_content(h);
 
assert(c != NULL);
 
if (c->locked) {
/* not safe to attempt redraw */
return true;
}
 
/* ensure we have a redrawable content */
if (c->handler->redraw == NULL) {
return true;
}
 
return c->handler->redraw(c, data, clip, ctx);
}
 
 
/**
* Register a user for callbacks.
*
* \param c the content to register
* \param callback the callback function
* \param pw callback private data
* \return true on success, false otherwise on memory exhaustion
*
* The callback will be called when content_broadcast() is
* called with the content.
*/
 
bool content_add_user(struct content *c,
void (*callback)(struct content *c, content_msg msg,
union content_msg_data data, void *pw),
void *pw)
{
struct content_user *user;
 
LOG(("content "URL_FMT_SPC" (%p), user %p %p",
nsurl_access(llcache_handle_get_url(c->llcache)),
c, callback, pw));
user = malloc(sizeof(struct content_user));
if (!user)
return false;
user->callback = callback;
user->pw = pw;
user->next = c->user_list->next;
c->user_list->next = user;
 
return true;
}
 
 
/**
* Remove a callback user.
*
* The callback function and pw must be identical to those passed to
* content_add_user().
*/
 
void content_remove_user(struct content *c,
void (*callback)(struct content *c, content_msg msg,
union content_msg_data data, void *pw),
void *pw)
{
struct content_user *user, *next;
LOG(("content "URL_FMT_SPC" (%p), user %p %p",
nsurl_access(llcache_handle_get_url(c->llcache)), c,
callback, pw));
 
/* user_list starts with a sentinel */
for (user = c->user_list; user->next != 0 &&
!(user->next->callback == callback &&
user->next->pw == pw); user = user->next)
;
if (user->next == 0) {
LOG(("user not found in list"));
assert(0);
return;
}
next = user->next;
user->next = next->next;
free(next);
}
 
/**
* Count users for the content.
*/
 
uint32_t content_count_users(struct content *c)
{
struct content_user *user;
uint32_t counter = 0;
 
assert(c != NULL);
for (user = c->user_list; user != NULL; user = user->next)
counter += 1;
return counter - 1; /* Subtract 1 for the sentinel */
}
 
/**
* Determine if quirks mode matches
*
* \param c Content to consider
* \param quirks Quirks mode to match
* \return True if quirks match, false otherwise
*/
bool content_matches_quirks(struct content *c, bool quirks)
{
if (c->handler->matches_quirks == NULL)
return true;
 
return c->handler->matches_quirks(c, quirks);
}
 
/**
* Determine if a content is shareable
*
* \param c Content to consider
* \return True if content is shareable, false otherwise
*/
bool content_is_shareable(struct content *c)
{
return c->handler->no_share == false;
}
 
/**
* Send a message to all users.
*/
 
void content_broadcast(struct content *c, content_msg msg,
union content_msg_data data)
{
struct content_user *user, *next;
assert(c);
// LOG(("%p %s -> %d", c, c->url, msg));
for (user = c->user_list->next; user != 0; user = next) {
next = user->next; /* user may be destroyed during callback */
if (user->callback != 0)
user->callback(c, msg, data, user->pw);
}
}
 
/* exported interface documented in content_protected.h */
void content_broadcast_errorcode(struct content *c, nserror errorcode)
{
struct content_user *user, *next;
union content_msg_data data;
 
assert(c);
 
data.errorcode = errorcode;
 
for (user = c->user_list->next; user != 0; user = next) {
next = user->next; /* user may be destroyed during callback */
if (user->callback != 0)
user->callback(c, CONTENT_MSG_ERRORCODE, data, user->pw);
}
}
 
 
/**
* A window containing the content has been opened.
*
* \param c content that has been opened
* \param bw browser window containing the content
* \param page content of type CONTENT_HTML containing c, or 0 if not an
* object within a page
* \param params object parameters, or 0 if not an object
*
* Calls the open function for the content.
*/
 
void content_open(hlcache_handle *h, struct browser_window *bw,
struct content *page, struct object_params *params)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
LOG(("content %p %s", c,
nsurl_access(llcache_handle_get_url(c->llcache))));
if (c->handler->open != NULL)
c->handler->open(c, bw, page, params);
}
 
 
/**
* The window containing the content has been closed.
*
* Calls the close function for the content.
*/
 
void content_close(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
LOG(("content %p %s", c,
nsurl_access(llcache_handle_get_url(c->llcache))));
if (c->handler->close != NULL)
c->handler->close(c);
}
 
 
/**
* Find this content's selection context, if it has one.
*/
 
struct selection *content_get_selection(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
 
if (c->handler->get_selection != NULL)
return c->handler->get_selection(c);
else
return NULL;
}
 
 
void content_get_contextual_content(struct hlcache_handle *h,
int x, int y, struct contextual_content *data)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
 
if (c->handler->get_contextual_content != NULL) {
c->handler->get_contextual_content(c, x, y, data);
return;
} else {
data->object = h;
return;
}
}
 
 
bool content_scroll_at_point(struct hlcache_handle *h,
int x, int y, int scrx, int scry)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
 
if (c->handler->scroll_at_point != NULL)
return c->handler->scroll_at_point(c, x, y, scrx, scry);
 
return false;
}
 
 
bool content_drop_file_at_point(struct hlcache_handle *h,
int x, int y, char *file)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
 
if (c->handler->drop_file_at_point != NULL)
return c->handler->drop_file_at_point(c, x, y, file);
 
return false;
}
 
 
void content_debug_dump(struct hlcache_handle *h, FILE *f)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
 
if (c->handler->debug_dump != NULL)
c->handler->debug_dump(c, f);
}
 
 
void content_add_error(struct content *c, const char *token,
unsigned int line)
{
}
 
bool content__set_title(struct content *c, const char *title)
{
char *new_title = strdup(title);
if (new_title == NULL)
return false;
 
if (c->title != NULL)
free(c->title);
 
c->title = new_title;
 
return true;
}
 
struct content_rfc5988_link *
content_find_rfc5988_link(hlcache_handle *h, lwc_string *rel)
{
struct content *c = hlcache_handle_get_content(h);
struct content_rfc5988_link *link = c->links;
bool rel_match = false;
 
while (link != NULL) {
if (lwc_string_caseless_isequal(link->rel, rel,
&rel_match) == lwc_error_ok && rel_match) {
break;
}
link = link->next;
}
return link;
}
 
struct content_rfc5988_link *
content__free_rfc5988_link(struct content_rfc5988_link *link)
{
struct content_rfc5988_link *next;
 
next = link->next;
 
lwc_string_unref(link->rel);
nsurl_unref(link->href);
if (link->hreflang != NULL) {
lwc_string_unref(link->hreflang);
}
if (link->type != NULL) {
lwc_string_unref(link->type);
}
if (link->media != NULL) {
lwc_string_unref(link->media);
}
if (link->sizes != NULL) {
lwc_string_unref(link->sizes);
}
free(link);
 
return next;
}
 
bool content__add_rfc5988_link(struct content *c,
const struct content_rfc5988_link *link)
{
struct content_rfc5988_link *newlink;
union content_msg_data msg_data;
 
/* a link relation must be present for it to be a link */
if (link->rel == NULL) {
return false;
}
 
/* a link href must be present for it to be a link */
if (link->href == NULL) {
return false;
}
 
newlink = calloc(1, sizeof(struct content_rfc5988_link));
if (newlink == NULL) {
return false;
}
 
/* copy values */
newlink->rel = lwc_string_ref(link->rel);
newlink->href = nsurl_ref(link->href);
if (link->hreflang != NULL) {
newlink->hreflang = lwc_string_ref(link->hreflang);
}
if (link->type != NULL) {
newlink->type = lwc_string_ref(link->type);
}
if (link->media != NULL) {
newlink->media = lwc_string_ref(link->media);
}
if (link->sizes != NULL) {
newlink->sizes = lwc_string_ref(link->sizes);
}
 
/* add to metadata link to list */
newlink->next = c->links;
c->links = newlink;
 
/* broadcast the data */
msg_data.rfc5988_link = newlink;
content_broadcast(c, CONTENT_MSG_LINK, msg_data);
 
return true;
}
 
/**
* Retrieve computed type of content
*
* \param c Content to retrieve type of
* \return Computed content type
*/
content_type content_get_type(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
 
if (c == NULL)
return CONTENT_NONE;
 
return c->handler->type();
}
 
/**
* Retrieve mime-type of content
*
* \param c Content to retrieve mime-type of
* \return Pointer to referenced mime-type, or NULL if not found.
*/
lwc_string *content_get_mime_type(hlcache_handle *h)
{
return content__get_mime_type(hlcache_handle_get_content(h));
}
 
lwc_string *content__get_mime_type(struct content *c)
{
if (c == NULL)
return NULL;
 
return lwc_string_ref(c->mime_type);
}
 
/**
* Retrieve URL associated with content
*
* \param c Content to retrieve URL from
* \return Pointer to URL, or NULL if not found.
*/
nsurl *content_get_url(struct content *c)
{
if (c == NULL)
return NULL;
 
return llcache_handle_get_url(c->llcache);
}
 
/**
* Retrieve title associated with content
*
* \param c Content to retrieve title from
* \return Pointer to title, or NULL if not found.
*/
const char *content_get_title(hlcache_handle *h)
{
return content__get_title(hlcache_handle_get_content(h));
}
 
const char *content__get_title(struct content *c)
{
if (c == NULL)
return NULL;
 
return c->title != NULL ? c->title :
nsurl_access(llcache_handle_get_url(c->llcache));
}
 
/**
* Retrieve status of content
*
* \param c Content to retrieve status of
* \return Content status
*/
content_status content_get_status(hlcache_handle *h)
{
return content__get_status(hlcache_handle_get_content(h));
}
 
content_status content__get_status(struct content *c)
{
if (c == NULL)
return CONTENT_STATUS_ERROR;
 
return c->status;
}
 
/**
* Retrieve status message associated with content
*
* \param c Content to retrieve status message from
* \return Pointer to status message, or NULL if not found.
*/
const char *content_get_status_message(hlcache_handle *h)
{
return content__get_status_message(hlcache_handle_get_content(h));
}
 
const char *content__get_status_message(struct content *c)
{
if (c == NULL)
return NULL;
 
return c->status_message;
}
 
/**
* Retrieve width of content
*
* \param c Content to retrieve width of
* \return Content width
*/
int content_get_width(hlcache_handle *h)
{
return content__get_width(hlcache_handle_get_content(h));
}
 
int content__get_width(struct content *c)
{
if (c == NULL)
return 0;
 
return c->width;
}
 
/**
* Retrieve height of content
*
* \param c Content to retrieve height of
* \return Content height
*/
int content_get_height(hlcache_handle *h)
{
return content__get_height(hlcache_handle_get_content(h));
}
 
int content__get_height(struct content *c)
{
if (c == NULL)
return 0;
 
return c->height;
}
 
/**
* Retrieve available width of content
*
* \param c Content to retrieve available width of
* \return Available width of content
*/
int content_get_available_width(hlcache_handle *h)
{
return content__get_available_width(hlcache_handle_get_content(h));
}
 
int content__get_available_width(struct content *c)
{
if (c == NULL)
return 0;
 
return c->available_width;
}
 
 
/**
* Retrieve source of content
*
* \param c Content to retrieve source of
* \param size Pointer to location to receive byte size of source
* \return Pointer to source data
*/
const char *content_get_source_data(hlcache_handle *h, unsigned long *size)
{
return content__get_source_data(hlcache_handle_get_content(h), size);
}
 
const char *content__get_source_data(struct content *c, unsigned long *size)
{
const uint8_t *data;
size_t len;
 
assert(size != NULL);
 
if (c == NULL)
return NULL;
 
data = llcache_handle_get_source_data(c->llcache, &len);
 
*size = (unsigned long) len;
 
return (const char *) data;
}
 
/**
* Invalidate content reuse data: causes subsequent requests for content URL
* to query server to determine if content can be reused. This is required
* behaviour for forced reloads etc.
*
* \param c Content to invalidate
*/
void content_invalidate_reuse_data(hlcache_handle *h)
{
content__invalidate_reuse_data(hlcache_handle_get_content(h));
}
 
void content__invalidate_reuse_data(struct content *c)
{
if (c == NULL || c->llcache == NULL)
return;
 
/* Invalidate low-level cache data */
llcache_handle_invalidate_cache_data(c->llcache);
}
 
/**
* Retrieve the refresh URL for a content
*
* \param c Content to retrieve refresh URL from
* \return Pointer to URL, or NULL if none
*/
nsurl *content_get_refresh_url(hlcache_handle *h)
{
return content__get_refresh_url(hlcache_handle_get_content(h));
}
 
nsurl *content__get_refresh_url(struct content *c)
{
if (c == NULL)
return NULL;
 
return c->refresh;
}
 
 
/**
* Retrieve the bitmap contained in an image content
*
* \param c Content to retrieve bitmap from
* \return Pointer to bitmap, or NULL if none.
*/
struct bitmap *content_get_bitmap(hlcache_handle *h)
{
return content__get_bitmap(hlcache_handle_get_content(h));
}
 
struct bitmap *content__get_bitmap(struct content *c)
{
struct bitmap *bitmap = NULL;
 
if ((c != NULL) &&
(c->handler != NULL) &&
(c->handler->type != NULL) &&
(c->handler->type() == CONTENT_IMAGE) &&
(c->handler->get_internal != NULL) ) {
bitmap = c->handler->get_internal(c, NULL);
}
 
return bitmap;
}
 
 
/**
* Determine if a content is opaque from handle
*
* \param h high level cache handle to retrieve opacity from.
* \return false if the content is not opaque or information is not
* known else true.
*/
bool content_get_opaque(hlcache_handle *h)
{
return content__get_opaque(hlcache_handle_get_content(h));
}
 
/**
* Determine if a content is opaque
*
* \param c Content to retrieve opacity from
* \return false if the content is not opaque or information is not
* known else true.
*/
bool content__get_opaque(struct content *c)
{
bool opaque = false;
 
if ((c != NULL) &&
(c->handler != NULL) &&
(c->handler->type != NULL) &&
(c->handler->type() == CONTENT_IMAGE) &&
(c->handler->get_internal != NULL) ) {
struct bitmap *bitmap = NULL;
bitmap = c->handler->get_internal(c, NULL);
if (bitmap != NULL) {
opaque = bitmap_get_opaque(bitmap);
}
}
 
return opaque;
}
 
 
/**
* Retrieve quirkiness of a content
*
* \param h Content to examine
* \return True if content is quirky, false otherwise
*/
bool content_get_quirks(hlcache_handle *h)
{
struct content *c = hlcache_handle_get_content(h);
 
if (c == NULL)
return false;
 
return c->quirks;
}
 
 
/**
* Return whether a content is currently locked
*
* \param c Content to test
* \return true iff locked, else false
*/
 
bool content_is_locked(hlcache_handle *h)
{
return content__is_locked(hlcache_handle_get_content(h));
}
 
bool content__is_locked(struct content *c)
{
return c->locked;
}
 
/**
* Retrieve the low-level cache handle for a content
*
* \param h Content to retrieve from
* \return Low-level cache handle
*/
const llcache_handle *content_get_llcache_handle(struct content *c)
{
if (c == NULL)
return NULL;
 
return c->llcache;
}
 
/**
* Clone a content object in its current state.
*
* \param c Content to clone
* \return Clone of \a c
*/
struct content *content_clone(struct content *c)
{
struct content *nc;
nserror error;
 
error = c->handler->clone(c, &nc);
if (error != NSERROR_OK)
return NULL;
 
return nc;
};
 
/**
* Clone a content's data members
*
* \param c Content to clone
* \param nc Content to populate
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror content__clone(const struct content *c, struct content *nc)
{
struct content_user *user_sentinel;
nserror error;
 
user_sentinel = calloc(1, sizeof(struct content_user));
if (user_sentinel == NULL) {
return NSERROR_NOMEM;
}
 
error = llcache_handle_clone(c->llcache, &(nc->llcache));
if (error != NSERROR_OK) {
return error;
}
llcache_handle_change_callback(nc->llcache,
content_llcache_callback, nc);
 
nc->mime_type = lwc_string_ref(c->mime_type);
nc->handler = c->handler;
 
nc->status = c->status;
nc->width = c->width;
nc->height = c->height;
nc->available_width = c->available_width;
nc->quirks = c->quirks;
if (c->fallback_charset != NULL) {
nc->fallback_charset = strdup(c->fallback_charset);
if (nc->fallback_charset == NULL) {
return NSERROR_NOMEM;
}
}
if (c->refresh != NULL) {
nc->refresh = nsurl_ref(c->refresh);
if (nc->refresh == NULL) {
return NSERROR_NOMEM;
}
}
 
nc->time = c->time;
nc->reformat_time = c->reformat_time;
nc->size = c->size;
if (c->title != NULL) {
nc->title = strdup(c->title);
if (nc->title == NULL) {
return NSERROR_NOMEM;
}
}
nc->active = c->active;
 
nc->user_list = user_sentinel;
memcpy(&(nc->status_message), &(c->status_message), 120);
memcpy(&(nc->sub_status), &(c->sub_status), 80);
nc->locked = c->locked;
nc->total_size = c->total_size;
nc->http_code = c->http_code;
return NSERROR_OK;
}
 
/**
* Abort a content object
*
* \param c The content object to abort
* \return NSERROR_OK on success, otherwise appropriate error
*/
nserror content_abort(struct content *c)
{
LOG(("Aborting %p", c));
if (c->handler->stop != NULL)
c->handler->stop(c);
/* And for now, abort our llcache object */
return llcache_handle_abort(c->llcache);
}
 
/contrib/network/netsurf/netsurf/content/content.h
0,0 → 1,255
/*
* Copyright 2005-2007 James Bursa <bursa@users.sourceforge.net>
* Copyright 2003 Philip Pemberton <philpem@users.sourceforge.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Content handling (interface).
*
* The content functions manipulate struct contents, which correspond to URLs.
*/
 
#ifndef _NETSURF_CONTENT_CONTENT_H_
#define _NETSURF_CONTENT_CONTENT_H_
 
#include <stdbool.h>
#include <stdio.h>
 
#include <libwapcaplet/libwapcaplet.h>
 
#include "utils/config.h"
#include "utils/errors.h"
#include "utils/http.h"
#include "utils/nsurl.h"
#include "utils/types.h"
#include "content/content_factory.h"
#include "content/content_type.h"
#include "desktop/mouse.h"
#include "desktop/plot_style.h"
 
struct browser_window;
struct content;
struct llcache_handle;
struct hlcache_handle;
struct object_params;
struct rect;
struct redraw_context;
 
 
/** Status of a content */
typedef enum {
CONTENT_STATUS_LOADING, /**< Content is being fetched or
converted and is not safe to display. */
CONTENT_STATUS_READY, /**< Some parts of content still being
loaded, but can be displayed. */
CONTENT_STATUS_DONE, /**< All finished. */
CONTENT_STATUS_ERROR /**< Error occurred, content will be
destroyed imminently. */
} content_status;
 
/** Used in callbacks to indicate what has occurred. */
typedef enum {
CONTENT_MSG_LOADING, /**< fetching or converting */
CONTENT_MSG_READY, /**< may be displayed */
CONTENT_MSG_DONE, /**< finished */
CONTENT_MSG_ERROR, /**< error occurred */
CONTENT_MSG_ERRORCODE, /**< error occurred return nserror */
CONTENT_MSG_STATUS, /**< new status string */
CONTENT_MSG_REFORMAT, /**< content_reformat done */
CONTENT_MSG_REDRAW, /**< needs redraw (eg. new animation frame) */
CONTENT_MSG_REFRESH, /**< wants refresh */
CONTENT_MSG_DOWNLOAD, /**< download, not for display */
CONTENT_MSG_LINK, /**< RFC5988 link */
CONTENT_MSG_GETCTX, /**< Javascript context */
CONTENT_MSG_SCROLL, /**< Request to scroll content */
CONTENT_MSG_DRAGSAVE, /**< Allow drag saving of content */
CONTENT_MSG_SAVELINK, /**< Allow URL to be saved */
CONTENT_MSG_POINTER /**< Wants a specific mouse pointer set */
} content_msg;
 
/** RFC5988 metadata link */
struct content_rfc5988_link {
struct content_rfc5988_link *next; /**< next rfc5988_link in list */
 
lwc_string *rel; /**< the link relationship - must be present */
nsurl *href; /**< the link href - must be present */
lwc_string *hreflang;
lwc_string *type;
lwc_string *media;
lwc_string *sizes;
};
 
/** Extra data for some content_msg messages. */
union content_msg_data {
/** CONTENT_MSG_ERROR - Error message */
const char *error;
/** CONTENT_MSG_ERRORCODE - Error code */
nserror errorcode;
/** CONTENT_MSG_REDRAW - Area of content which needs redrawing */
struct {
int x, y, width, height;
/** Redraw the area fully. If false, object must be set,
* and only the object will be redrawn. */
bool full_redraw;
/** Object to redraw if full_redraw is false. */
struct content *object;
/** Coordinates to plot object at. */
int object_x, object_y;
/** Dimensions to plot object with. */
int object_width, object_height;
} redraw;
/** CONTENT_MSG_REFRESH - Minimum delay */
int delay;
/** CONTENT_MSG_REFORMAT - Reformat should not cause a redraw */
bool background;
/** CONTENT_MSG_STATUS - Status message update. If NULL, the content's
* internal status text has been updated, and listener should use
* content_get_status_message() */
const char *explicit_status_text;
/** CONTENT_MSG_DOWNLOAD - Low-level cache handle */
struct llcache_handle *download;
/** CONTENT_MSG_RFC5988_LINK - rfc5988 link data */
struct content_rfc5988_link *rfc5988_link;
/** CONTENT_MSG_GETCTX - Javascript context */
struct jscontext **jscontext;
/** CONTENT_MSG_SCROLL - Part of content to scroll to show */
struct {
/** if true, scroll to show area given by (x0, y0) and (x1,y1).
* if false, scroll point (x0, y0) to top left of viewport */
bool area;
int x0, y0;
int x1, y1;
} scroll;
/** CONTENT_MSG_DRAGSAVE - Drag save a content */
struct {
enum {
CONTENT_SAVE_ORIG,
CONTENT_SAVE_NATIVE,
CONTENT_SAVE_COMPLETE,
CONTENT_SAVE_SOURCE
} type;
/** if NULL, save the content generating the message */
struct hlcache_handle *content;
} dragsave;
/** CONTENT_MSG_SAVELINK - Save a URL */
struct {
const char *url;
const char *title;
} savelink;
/** CONTENT_MSG_POINTER - Mouse pointer to set */
browser_pointer_shape pointer;
/** CONTENT_MSG_PASTE - Content requests that clipboard is pasted */
struct {
/* TODO: Get rid of these coords.
* browser_window_paste_text doesn't take coords, but
* RISC OS front end is doing something different. */
int x;
int y;
} paste;
};
 
/** parameters to content redraw */
struct content_redraw_data {
int x; /**< coordinate for top-left of redraw */
int y; /**< coordinate for top-left of redraw */
 
/** dimensions to render content at
* (for scaling contents with intrinsic dimensions) */
int width; /**< horizontal dimension */
int height; /**< vertical dimension */
 
/** The background colour */
colour background_colour;
 
/** Scale for redraw
* (for scaling contents without intrinsic dimensions) */
float scale; /**< Scale factor for redraw */
 
bool repeat_x; /**< whether content is tiled in x direction */
bool repeat_y; /**< whether content is tiled in y direction */
};
 
/* The following are for hlcache */
void content_destroy(struct content *c);
 
bool content_add_user(struct content *h,
void (*callback)(struct content *c, content_msg msg,
union content_msg_data data, void *pw),
void *pw);
void content_remove_user(struct content *c,
void (*callback)(struct content *c, content_msg msg,
union content_msg_data data, void *pw),
void *pw);
 
uint32_t content_count_users(struct content *c);
bool content_matches_quirks(struct content *c, bool quirks);
bool content_is_shareable(struct content *c);
content_status content__get_status(struct content *c);
 
const struct llcache_handle *content_get_llcache_handle(struct content *c);
nsurl *content_get_url(struct content *c);
 
struct content *content_clone(struct content *c);
 
nserror content_abort(struct content *c);
 
/* Client functions */
bool content_can_reformat(struct hlcache_handle *h);
void content_reformat(struct hlcache_handle *h, bool background,
int width, int height);
void content_request_redraw(struct hlcache_handle *h,
int x, int y, int width, int height);
void content_mouse_track(struct hlcache_handle *h, struct browser_window *bw,
browser_mouse_state mouse, int x, int y);
void content_mouse_action(struct hlcache_handle *h, struct browser_window *bw,
browser_mouse_state mouse, int x, int y);
bool content_redraw(struct hlcache_handle *h, struct content_redraw_data *data,
const struct rect *clip, const struct redraw_context *ctx);
void content_open(struct hlcache_handle *h, struct browser_window *bw,
struct content *page, struct object_params *params);
void content_close(struct hlcache_handle *h);
struct selection *content_get_selection(struct hlcache_handle *h);
void content_get_contextual_content(struct hlcache_handle *h,
int x, int y, struct contextual_content *data);
bool content_scroll_at_point(struct hlcache_handle *h,
int x, int y, int scrx, int scry);
bool content_drop_file_at_point(struct hlcache_handle *h,
int x, int y, char *file);
void content_debug_dump(struct hlcache_handle *h, FILE *f);
struct content_rfc5988_link *content_find_rfc5988_link(struct hlcache_handle *c,
lwc_string *rel);
 
/* Member accessors */
content_type content_get_type(struct hlcache_handle *c);
lwc_string *content_get_mime_type(struct hlcache_handle *c);
const char *content_get_title(struct hlcache_handle *c);
content_status content_get_status(struct hlcache_handle *c);
const char *content_get_status_message(struct hlcache_handle *c);
int content_get_width(struct hlcache_handle *c);
int content_get_height(struct hlcache_handle *c);
int content_get_available_width(struct hlcache_handle *c);
const char *content_get_source_data(struct hlcache_handle *c,
unsigned long *size);
void content_invalidate_reuse_data(struct hlcache_handle *c);
nsurl *content_get_refresh_url(struct hlcache_handle *c);
struct bitmap *content_get_bitmap(struct hlcache_handle *c);
bool content_get_opaque(struct hlcache_handle *h);
bool content_get_quirks(struct hlcache_handle *c);
 
bool content_is_locked(struct hlcache_handle *h);
 
#endif
/contrib/network/netsurf/netsurf/content/content_factory.c
0,0 → 1,201
/*
* Copyright 2011 John-Mark Bell <jmb@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Content factory (implementation)
*/
 
#include <assert.h>
#include <stdlib.h>
#include <string.h>
 
#include "content/content.h"
#include "content/content_factory.h"
#include "content/content_protected.h"
#include "content/llcache.h"
 
/**
* Entry in list of content handlers
*/
typedef struct content_handler_entry {
/** Next entry */
struct content_handler_entry *next;
 
/** MIME type handled by handler */
lwc_string *mime_type;
/** Content handler object */
const content_handler *handler;
} content_handler_entry;
 
static content_handler_entry *content_handlers;
 
/**
* Clean up after the content factory
*/
void content_factory_fini(void)
{
content_handler_entry *victim;
 
while (content_handlers != NULL) {
victim = content_handlers;
 
content_handlers = content_handlers->next;
 
if (victim->handler->fini != NULL)
victim->handler->fini();
 
lwc_string_unref(victim->mime_type);
 
free(victim);
}
}
 
/**
* Register a handler with the content factory
*
* \param mime_type MIME type to handle
* \param handler Content handler for MIME type
* \return NSERROR_OK on success, appropriate error otherwise
*
* \note Latest registration for a MIME type wins
*/
nserror content_factory_register_handler(const char *mime_type,
const content_handler *handler)
{
lwc_string *imime_type;
lwc_error lerror;
content_handler_entry *entry;
bool match;
 
lerror = lwc_intern_string(mime_type, strlen(mime_type), &imime_type);
if (lerror != lwc_error_ok)
return NSERROR_NOMEM;
 
for (entry = content_handlers; entry != NULL; entry = entry->next) {
if (lwc_string_caseless_isequal(imime_type, entry->mime_type,
&match) == lwc_error_ok && match)
break;
}
 
if (entry == NULL) {
entry = malloc(sizeof(content_handler_entry));
if (entry == NULL)
return NSERROR_NOMEM;
 
entry->next = content_handlers;
content_handlers = entry;
 
entry->mime_type = imime_type;
} else {
lwc_string_unref(imime_type);
}
 
entry->handler = handler;
 
return NSERROR_OK;
}
 
/**
* Find a handler for a MIME type.
*
* \param mime_type MIME type to search for
* \return Associated handler, or NULL if none
*/
static const content_handler *content_lookup(lwc_string *mime_type)
{
content_handler_entry *entry;
bool match;
 
for (entry = content_handlers; entry != NULL; entry = entry->next) {
if (lwc_string_caseless_isequal(mime_type, entry->mime_type,
&match) == lwc_error_ok && match)
break;
}
 
if (entry != NULL)
return entry->handler;
 
return NULL;
}
 
/**
* Compute the generic content type for a MIME type
*
* \param mime_type MIME type to consider
* \return Generic content type
*/
content_type content_factory_type_from_mime_type(lwc_string *mime_type)
{
const content_handler *handler;
content_type type = CONTENT_NONE;
 
handler = content_lookup(mime_type);
if (handler != NULL) {
type = handler->type();
}
 
return type;
}
 
/**
* Create a content object
*
* \param llcache Underlying source data handle
* \param fallback_charset Character set to fall back to if none specified
* \param quirks Quirkiness of containing document
* \param effective_type Effective MIME type of content
* \return Pointer to content object, or NULL on failure
*/
struct content *content_factory_create_content(llcache_handle *llcache,
const char *fallback_charset, bool quirks,
lwc_string *effective_type)
{
struct content *c;
const char *content_type_header;
const content_handler *handler;
http_content_type *ct = NULL;
nserror error;
 
handler = content_lookup(effective_type);
if (handler == NULL)
return NULL;
 
assert(handler->create != NULL);
 
/* Use the parameters from the declared Content-Type header */
content_type_header =
llcache_handle_get_header(llcache, "Content-Type");
if (content_type_header != NULL) {
/* We don't care if this fails */
http_parse_content_type(content_type_header, &ct);
}
 
error = handler->create(handler, effective_type,
ct != NULL ? ct->parameters : NULL,
llcache, fallback_charset, quirks,
&c);
 
if (ct != NULL)
http_content_type_destroy(ct);
 
if (error != NSERROR_OK)
return NULL;
 
return c;
}
 
/contrib/network/netsurf/netsurf/content/content_factory.h
0,0 → 1,64
/*
* Copyright 2011 John-Mark Bell <jmb@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
#ifndef NETSURF_CONTENT_CONTENT_FACTORY_H_
#define NETSURF_CONTENT_CONTENT_FACTORY_H_
 
#include <stdbool.h>
 
#include <libwapcaplet/libwapcaplet.h>
 
#include "content/content_type.h"
#include "utils/errors.h"
#include "utils/utils.h"
 
#define CONTENT_FACTORY_REGISTER_TYPES(HNAME, HTYPELIST, HHANDLER) \
\
nserror HNAME##_init(void) \
{ \
uint32_t i; \
nserror error = NSERROR_OK; \
\
for (i = 0; i < NOF_ELEMENTS(HTYPELIST); i++) { \
error = content_factory_register_handler( \
HTYPELIST[i], \
&HHANDLER); \
if (error != NSERROR_OK) \
break; \
} \
\
return error; \
}
 
struct content;
struct llcache_handle;
 
typedef struct content_handler content_handler;
 
void content_factory_fini(void);
 
nserror content_factory_register_handler(const char *mime_type,
const content_handler *handler);
 
struct content *content_factory_create_content(struct llcache_handle *llcache,
const char *fallback_charset, bool quirks,
lwc_string *effective_type);
 
content_type content_factory_type_from_mime_type(lwc_string *mime_type);
 
#endif
/contrib/network/netsurf/netsurf/content/content_protected.h
0,0 → 1,197
/*
* Copyright 2005-2007 James Bursa <bursa@users.sourceforge.net>
* Copyright 2003 Philip Pemberton <philpem@users.sourceforge.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Content handling (interface).
*
* The content functions manipulate struct contents, which correspond to URLs.
*/
 
#ifndef _NETSURF_CONTENT_CONTENT_PROTECTED_H_
#define _NETSURF_CONTENT_CONTENT_PROTECTED_H_
 
#include <stdint.h>
#include <time.h>
#include "utils/config.h"
#include "content/content.h"
#include "content/content_factory.h"
#include "content/llcache.h"
#include "utils/errors.h"
 
struct bitmap;
struct content;
struct rect;
struct redraw_context;
 
struct content_handler {
void (*fini)(void);
 
nserror (*create)(const content_handler *handler,
lwc_string *imime_type, const http_parameter *params,
llcache_handle *llcache,
const char *fallback_charset, bool quirks,
struct content **c);
 
bool (*process_data)(struct content *c,
const char *data, unsigned int size);
bool (*data_complete)(struct content *c);
void (*reformat)(struct content *c, int width, int height);
void (*destroy)(struct content *c);
void (*stop)(struct content *c);
void (*mouse_track)(struct content *c, struct browser_window *bw,
browser_mouse_state mouse, int x, int y);
void (*mouse_action)(struct content *c, struct browser_window *bw,
browser_mouse_state mouse, int x, int y);
bool (*redraw)(struct content *c, struct content_redraw_data *data,
const struct rect *clip,
const struct redraw_context *ctx);
void (*open)(struct content *c, struct browser_window *bw,
struct content *page, struct object_params *params);
void (*close)(struct content *c);
struct selection * (*get_selection)(struct content *c);
void (*get_contextual_content)(struct content *c, int x, int y,
struct contextual_content *data);
bool (*scroll_at_point)(struct content *c, int x, int y,
int scrx, int scry);
bool (*drop_file_at_point)(struct content *c, int x, int y,
char *file);
void (*debug_dump)(struct content *c, FILE *f);
nserror (*clone)(const struct content *old, struct content **newc);
bool (*matches_quirks)(const struct content *c, bool quirks);
content_type (*type)(void);
 
/** handler dependant content sensitive internal data interface. */
void * (*get_internal)(const struct content *c, void *context);
 
/** There must be one content per user for this type. */
bool no_share;
};
 
/** Linked list of users of a content. */
struct content_user
{
void (*callback)(struct content *c, content_msg msg,
union content_msg_data data, void *pw);
void *pw;
 
struct content_user *next;
};
 
/** Corresponds to a single URL. */
struct content {
llcache_handle *llcache; /**< Low-level cache object */
 
lwc_string *mime_type; /**< Original MIME type of data */
 
const content_handler *handler; /**< Handler for content */
 
content_status status; /**< Current status. */
 
int width, height; /**< Dimensions, if applicable. */
int available_width; /**< Available width (eg window width). */
 
bool quirks; /**< Content is in quirks mode */
char *fallback_charset; /**< Fallback charset, or NULL */
 
nsurl *refresh; /**< URL for refresh request */
 
struct content_rfc5988_link *links; /**< list of metadata links */
 
unsigned int time; /**< Creation time,
if LOADING or READY,
otherwise total time. */
 
unsigned int reformat_time; /**< Earliest time to attempt a
period reflow while fetching a
page's objects. */
 
unsigned int size; /**< Estimated size of all data
associated with this content */
char *title; /**< Title for browser window. */
unsigned int active; /**< Number of child fetches or
conversions currently in progress. */
struct content_user *user_list; /**< List of users. */
char status_message[120]; /**< Full text for status bar. */
char sub_status[80]; /**< Status of content. */
/** Content is being processed: data structures may be inconsistent
* and content must not be redrawn or modified. */
bool locked;
 
unsigned long total_size; /**< Total data size, 0 if unknown. */
long http_code; /**< HTTP status code, 0 if not HTTP. */
 
/** Array of first n rendering errors or warnings. */
struct {
const char *token;
unsigned int line; /**< Line no, 0 if not applicable. */
} error_list[40];
unsigned int error_count; /**< Number of valid error entries. */
};
 
extern const char * const content_type_name[];
extern const char * const content_status_name[];
 
nserror content__init(struct content *c, const content_handler *handler,
lwc_string *imime_type, const http_parameter *params,
struct llcache_handle *llcache, const char *fallback_charset,
bool quirks);
nserror content__clone(const struct content *c, struct content *nc);
 
void content_set_ready(struct content *c);
void content_set_done(struct content *c);
void content_set_error(struct content *c);
 
void content_set_status(struct content *c, const char *status_message);
void content_broadcast(struct content *c, content_msg msg,
union content_msg_data data);
/**
* Send an errorcode message to all users.
*/
void content_broadcast_errorcode(struct content *c, nserror errorcode);
 
void content_add_error(struct content *c, const char *token,
unsigned int line);
 
bool content__add_rfc5988_link(struct content *c,
const struct content_rfc5988_link *link);
struct content_rfc5988_link *content__free_rfc5988_link(
struct content_rfc5988_link *link);
 
void content__reformat(struct content *c, bool background,
int width, int height);
void content__request_redraw(struct content *c,
int x, int y, int width, int height);
 
bool content__set_title(struct content *c, const char *title);
 
lwc_string *content__get_mime_type(struct content *c);
const char *content__get_title(struct content *c);
const char *content__get_status_message(struct content *c);
int content__get_width(struct content *c);
int content__get_height(struct content *c);
int content__get_available_width(struct content *c);
const char *content__get_source_data(struct content *c, unsigned long *size);
void content__invalidate_reuse_data(struct content *c);
nsurl *content__get_refresh_url(struct content *c);
struct bitmap *content__get_bitmap(struct content *c);
bool content__get_opaque(struct content *c);
 
bool content__is_locked(struct content *c);
 
#endif
/contrib/network/netsurf/netsurf/content/content_type.h
0,0 → 1,58
/*
* Copyright 2003 James Bursa <bursa@users.sourceforge.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Declaration of content_type enum.
*
* The content_type enum is defined here to prevent cyclic dependencies.
*/
 
#ifndef _NETSURF_DESKTOP_CONTENT_TYPE_H_
#define _NETSURF_DESKTOP_CONTENT_TYPE_H_
 
#include "utils/config.h"
 
 
/** The type of a content. */
typedef enum {
CONTENT_NONE = 0x00,
 
CONTENT_HTML = 0x01,
CONTENT_TEXTPLAIN = 0x02,
CONTENT_CSS = 0x04,
 
/** All images */
CONTENT_IMAGE = 0x08,
 
/** Navigator API Plugins */
CONTENT_PLUGIN = 0x10,
 
/** Themes (only GTK and RISC OS) */
CONTENT_THEME = 0x20,
 
/** Javascript */
CONTENT_JS = 0x40,
/** All script types. */
CONTENT_SCRIPT = 0x40,
 
/** Any content matches */
CONTENT_ANY = 0x7f
} content_type;
 
 
#endif
/contrib/network/netsurf/netsurf/content/dirlist.c
0,0 → 1,385
/*
* Copyright 2010 Michael Drake <tlsa@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Generate HTML content for displaying directory listings (implementation).
*/
 
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "content/dirlist.h"
#include "utils/messages.h"
 
static const char footer[] = "</div>\n</body>\n</html>\n";
 
static int dirlist_filesize_calculate(unsigned long *bytesize);
static int dirlist_filesize_value(unsigned long bytesize);
static char* dirlist_filesize_unit(unsigned long bytesize);
 
 
/**
* Generates the top part of an HTML directory listing page
*
* \return Top of directory listing HTML
*
* This is part of a series of functions. To generate a complete page,
* call the following functions in order:
*
* dirlist_generate_top()
* dirlist_generate_hide_columns() -- optional
* dirlist_generate_title()
* dirlist_generate_parent_link() -- optional
* dirlist_generate_headings()
* dirlist_generate_row() -- call 'n' times for 'n' rows
* dirlist_generate_bottom()
*/
 
bool dirlist_generate_top(char *buffer, int buffer_length)
{
int error = snprintf(buffer, buffer_length,
"<html>\n"
"<head>\n"
"<link rel=\"stylesheet\" title=\"Standard\" "
"type=\"text/css\" href=\"resource:internal.css\">\n"
"<style>\n");
if (error < 0 || error >= buffer_length)
/* Error or buffer too small */
return false;
else
/* OK */
return true;
 
}
 
 
/**
* Generates the part of an HTML directory listing page that can suppress
* particular columns
*
* \param flags flags for which cols to suppress. 0 to suppress none
* \param buffer buffer to fill with generated HTML
* \param buffer_length maximum size of buffer
* \return true iff buffer filled without error
*
* This is part of a series of functions. To generate a complete page,
* call the following functions in order:
*
* dirlist_generate_top()
* dirlist_generate_hide_columns() -- optional
* dirlist_generate_title()
* dirlist_generate_parent_link() -- optional
* dirlist_generate_headings()
* dirlist_generate_row() -- call 'n' times for 'n' rows
* dirlist_generate_bottom()
*/
 
bool dirlist_generate_hide_columns(int flags, char *buffer, int buffer_length)
{
int error = snprintf(buffer, buffer_length,
"%s\n%s\n%s\n%s\n%s\n",
(flags & DIRLIST_NO_NAME_COLUMN) ?
"span.name { display: none; }\n" : "",
(flags & DIRLIST_NO_TYPE_COLUMN) ?
"span.type { display: none; }\n" : "",
(flags & DIRLIST_NO_SIZE_COLUMN) ?
"span.size { display: none; }\n" : "",
(flags & DIRLIST_NO_DATE_COLUMN) ?
"span.date { display: none; }\n" : "",
(flags & DIRLIST_NO_TIME_COLUMN) ?
"span.time { display: none; }\n" : "");
if (error < 0 || error >= buffer_length)
/* Error or buffer too small */
return false;
else
/* OK */
return true;
}
 
 
/**
* Generates the part of an HTML directory listing page that contains the title
*
* \param title title to use
* \param buffer buffer to fill with generated HTML
* \param buffer_length maximum size of buffer
* \return true iff buffer filled without error
*
* This is part of a series of functions. To generate a complete page,
* call the following functions in order:
*
* dirlist_generate_top()
* dirlist_generate_hide_columns() -- optional
* dirlist_generate_title()
* dirlist_generate_parent_link() -- optional
* dirlist_generate_headings()
* dirlist_generate_row() -- call 'n' times for 'n' rows
* dirlist_generate_bottom()
*/
 
bool dirlist_generate_title(const char *title, char *buffer, int buffer_length)
{
int error;
 
if (title == NULL)
title = "";
 
error = snprintf(buffer, buffer_length,
"</style>\n"
"<title>%s</title>\n"
"</head>\n"
"<body id=\"dirlist\">\n"
"<h1>%s</h1>\n",
title, title);
if (error < 0 || error >= buffer_length)
/* Error or buffer too small */
return false;
else
/* OK */
return true;
}
 
 
/**
* Generates the part of an HTML directory listing page that links to the parent
* directory
*
* \param parent url of parent directory
* \param buffer buffer to fill with generated HTML
* \param buffer_length maximum size of buffer
* \return true iff buffer filled without error
*
* This is part of a series of functions. To generate a complete page,
* call the following functions in order:
*
* dirlist_generate_top()
* dirlist_generate_hide_columns() -- optional
* dirlist_generate_title()
* dirlist_generate_parent_link() -- optional
* dirlist_generate_headings()
* dirlist_generate_row() -- call 'n' times for 'n' rows
* dirlist_generate_bottom()
*/
 
bool dirlist_generate_parent_link(const char *parent, char *buffer,
int buffer_length)
{
int error = snprintf(buffer, buffer_length,
"<p><a href=\"%s\">%s</a></p>",
parent, messages_get("FileParent"));
if (error < 0 || error >= buffer_length)
/* Error or buffer too small */
return false;
else
/* OK */
return true;
}
 
 
/**
* Generates the part of an HTML directory listing page that displays the column
* headings
*
* \param buffer buffer to fill with generated HTML
* \param buffer_length maximum size of buffer
* \return true iff buffer filled without error
*
* This is part of a series of functions. To generate a complete page,
* call the following functions in order:
*
* dirlist_generate_top()
* dirlist_generate_hide_columns() -- optional
* dirlist_generate_title()
* dirlist_generate_parent_link() -- optional
* dirlist_generate_headings()
* dirlist_generate_row() -- call 'n' times for 'n' rows
* dirlist_generate_bottom()
*/
 
bool dirlist_generate_headings(char *buffer, int buffer_length)
{
int error = snprintf(buffer, buffer_length,
"<div>\n"
"<strong>\n"
"\t<span class=\"name\">%s</span>\n"
"\t<span class=\"type\">%s</span>\n"
"\t<span class=\"size\">%s</span>"
"<span class=\"size\"></span>\n"
"\t<span class=\"date\">%s</span>\n"
"\t<span class=\"time\">%s</span>\n"
"</strong>\n",
messages_get("FileName"), messages_get("FileType"),
messages_get("FileSize"), messages_get("FileDate"),
messages_get("FileTime"));
if (error < 0 || error >= buffer_length)
/* Error or buffer too small */
return false;
else
/* OK */
return true;
}
 
 
/**
* Generates the part of an HTML directory listing page that displays a row
* in the directory contents table
*
* \param even evenness of row number, for alternate row colouring
* \param directory whether this row is for a directory (or a file)
* \param url url for row entry
* \param name name of row entry
* \param mimetype MIME type of row entry
* \param size size of row entry. If negative, size is left blank
* \param date date row entry was last modified
* \param time time row entry was last modified
* \param buffer buffer to fill with generated HTML
* \param buffer_length maximum size of buffer
* \return true iff buffer filled without error
*
* This is part of a series of functions. To generate a complete page,
* call the following functions in order:
*
* dirlist_generate_top()
* dirlist_generate_hide_columns() -- optional
* dirlist_generate_title()
* dirlist_generate_parent_link() -- optional
* dirlist_generate_headings()
* dirlist_generate_row() -- call 'n' times for 'n' rows
* dirlist_generate_bottom()
*/
 
bool dirlist_generate_row(bool even, bool directory, char *url, char *name,
const char *mimetype, long long size, char *date, char *time,
char *buffer, int buffer_length)
{
const char *unit;
char size_string[100];
int error;
 
if (size < 0) {
unit = "";
strncpy(size_string, "", sizeof size_string);
} else {
unit = messages_get(dirlist_filesize_unit((unsigned long)size));
snprintf(size_string, sizeof size_string, "%d",
dirlist_filesize_value((unsigned long)size));
}
 
error = snprintf(buffer, buffer_length,
"<a href=\"%s\" class=\"%s %s\">\n"
"\t<span class=\"name\">%s</span>\n"
"\t<span class=\"type\">%s</span>\n"
"\t<span class=\"size\">%s</span>"
"<span class=\"size\">%s</span>\n"
"\t<span class=\"date\">%s</span>\n"
"\t<span class=\"time\">%s</span>\n"
"</a>\n",
url, even ? "even" : "odd",
directory ? "dir" : "file",
name, mimetype, size_string, unit, date, time);
if (error < 0 || error >= buffer_length)
/* Error or buffer too small */
return false;
else
/* OK */
return true;
}
 
 
/**
* Generates the bottom part of an HTML directory listing page
*
* \return Bottom of directory listing HTML
*
* This is part of a series of functions. To generate a complete page,
* call the following functions in order:
*
* dirlist_generate_top()
* dirlist_generate_hide_columns() -- optional
* dirlist_generate_title()
* dirlist_generate_parent_link() -- optional
* dirlist_generate_headings()
* dirlist_generate_row() -- call 'n' times for 'n' rows
* dirlist_generate_bottom()
*/
 
bool dirlist_generate_bottom(char *buffer, int buffer_length)
{
int error = snprintf(buffer, buffer_length,
"</div>\n"
"</body>\n"
"</html>\n");
if (error < 0 || error >= buffer_length)
/* Error or buffer too small */
return false;
else
/* OK */
return true;
}
 
 
/**
* Obtain display value and units for filesize after conversion to B/kB/MB/GB,
* as appropriate.
*
* \param bytesize file size in bytes, updated to filesize in output units
* \return number of times bytesize has been divided by 1024
*/
 
int dirlist_filesize_calculate(unsigned long *bytesize)
{
int i = 0;
while (*bytesize > 1024 * 4) {
*bytesize /= 1024;
i++;
if (i == 3)
break;
}
return i;
}
 
 
/**
* Obtain display value for filesize after conversion to B/kB/MB/GB,
* as appropriate
*
* \param bytesize file size in bytes
* \return Value to display for file size, in units given by filesize_unit()
*/
 
int dirlist_filesize_value(unsigned long bytesize)
{
dirlist_filesize_calculate(&bytesize);
return (int)bytesize;
}
 
 
/**
* Obtain display units for filesize after conversion to B/kB/MB/GB,
* as appropriate
*
* \param bytesize file size in bytes
* \return Units to display for file size, for value given by filesize_value()
*/
 
char* dirlist_filesize_unit(unsigned long bytesize)
{
const char* units[] = { "Bytes", "kBytes", "MBytes", "GBytes" };
return (char*)units[dirlist_filesize_calculate(&bytesize)];
}
/contrib/network/netsurf/netsurf/content/dirlist.h
0,0 → 1,47
/*
* Copyright 2010 Michael Drake <tlsa@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Generate HTML content for displaying directory listings (interface).
*
* These functions should in general be called via the content interface.
*/
 
#ifndef _NETSURF_CONTENT_DIRLIST_H_
#define _NETSURF_CONTENT_DIRLIST_H_
 
#include <stdbool.h>
 
#define DIRLIST_NO_NAME_COLUMN 1
#define DIRLIST_NO_TYPE_COLUMN 1 << 1
#define DIRLIST_NO_SIZE_COLUMN 1 << 2
#define DIRLIST_NO_DATE_COLUMN 1 << 3
#define DIRLIST_NO_TIME_COLUMN 1 << 4
 
bool dirlist_generate_top(char *buffer, int buffer_length);
bool dirlist_generate_hide_columns(int flags, char *buffer, int buffer_length);
bool dirlist_generate_title(const char *title, char *buffer, int buffer_length);
bool dirlist_generate_parent_link(const char *parent, char *buffer,
int buffer_length);
bool dirlist_generate_headings(char *buffer, int buffer_length);
bool dirlist_generate_row(bool even, bool directory, char *url, char *name,
const char *mimetype, long long size, char *date, char *time,
char *buffer, int buffer_length);
bool dirlist_generate_bottom(char *buffer, int buffer_length);
 
#endif
/contrib/network/netsurf/netsurf/content/fetch.c
0,0 → 1,743
/*
* Copyright 2006,2007 Daniel Silverstone <dsilvers@digital-scurf.org>
* Copyright 2007 James Bursa <bursa@users.sourceforge.net>
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Fetching of data from a URL (implementation).
*
* Active fetches are held in the circular linked list ::fetch_ring. There may
* be at most ::option_max_fetchers_per_host active requests per Host: header.
* There may be at most ::option_max_fetchers active requests overall. Inactive
* fetchers are stored in the ::queue_ring waiting for use.
*/
 
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <time.h>
 
#include <libwapcaplet/libwapcaplet.h>
 
#include "utils/config.h"
#include "content/fetch.h"
#include "content/fetchers/resource.h"
#include "content/fetchers/about.h"
#include "content/fetchers/curl.h"
#include "content/fetchers/data.h"
#include "content/fetchers/file.h"
#include "content/urldb.h"
#include "desktop/netsurf.h"
#include "desktop/options.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/nsurl.h"
#include "utils/utils.h"
#include "utils/ring.h"
 
/* Define this to turn on verbose fetch logging */
#undef DEBUG_FETCH_VERBOSE
 
bool fetch_active; /**< Fetches in progress, please call fetch_poll(). */
 
/** Information about a fetcher for a given scheme. */
typedef struct scheme_fetcher_s {
lwc_string *scheme_name; /**< The scheme. */
fetcher_can_fetch can_fetch; /**< Ensure an URL can be fetched. */
fetcher_setup_fetch setup_fetch; /**< Set up a fetch. */
fetcher_start_fetch start_fetch; /**< Start a fetch. */
fetcher_abort_fetch abort_fetch; /**< Abort a fetch. */
fetcher_free_fetch free_fetch; /**< Free a fetch. */
fetcher_poll_fetcher poll_fetcher; /**< Poll this fetcher. */
fetcher_finalise finaliser; /**< Clean up this fetcher. */
int refcount; /**< When zero, clean up the fetcher. */
struct scheme_fetcher_s *next_fetcher; /**< Next fetcher in the list. */
struct scheme_fetcher_s *prev_fetcher; /**< Prev fetcher in the list. */
} scheme_fetcher;
 
static scheme_fetcher *fetchers = NULL;
 
/** Information for a single fetch. */
struct fetch {
fetch_callback callback;/**< Callback function. */
nsurl *url; /**< URL. */
nsurl *referer; /**< Referer URL. */
bool send_referer; /**< Valid to send the referer */
bool verifiable; /**< Transaction is verifiable */
void *p; /**< Private data for callback. */
lwc_string *host; /**< Host part of URL, interned */
long http_code; /**< HTTP response code, or 0. */
scheme_fetcher *ops; /**< Fetcher operations for this fetch,
NULL if not set. */
void *fetcher_handle; /**< The handle for the fetcher. */
bool fetch_is_active; /**< This fetch is active. */
struct fetch *r_prev; /**< Previous active fetch in ::fetch_ring. */
struct fetch *r_next; /**< Next active fetch in ::fetch_ring. */
};
 
static struct fetch *fetch_ring = 0; /**< Ring of active fetches. */
static struct fetch *queue_ring = 0; /**< Ring of queued fetches */
 
#define fetch_ref_fetcher(F) F->refcount++
static void fetch_unref_fetcher(scheme_fetcher *fetcher);
static void fetch_dispatch_jobs(void);
static bool fetch_choose_and_dispatch(void);
static bool fetch_dispatch_job(struct fetch *fetch);
 
/* Static lwc_strings */
static lwc_string *fetch_http_lwc;
static lwc_string *fetch_https_lwc;
 
 
/**
* Initialise the fetcher.
*/
 
void fetch_init(void)
{
fetch_curl_register();
fetch_data_register();
fetch_file_register();
fetch_resource_register();
fetch_about_register();
fetch_active = false;
 
if (lwc_intern_string("http", SLEN("http"), &fetch_http_lwc) !=
lwc_error_ok) {
die("Failed to initialise the fetch module "
"(couldn't intern \"http\").");
}
 
if (lwc_intern_string("https", SLEN("https"), &fetch_https_lwc) !=
lwc_error_ok) {
die("Failed to initialise the fetch module "
"(couldn't intern \"https\").");
}
}
 
 
/**
* Clean up for quit.
*
* Must be called before exiting.
*/
 
void fetch_quit(void)
{
while (fetchers != NULL) {
if (fetchers->refcount != 1) {
LOG(("Fetcher for scheme %s still active?!",
lwc_string_data(fetchers->scheme_name)));
/* We shouldn't do this, but... */
fetchers->refcount = 1;
}
fetch_unref_fetcher(fetchers);
}
 
lwc_string_unref(fetch_http_lwc);
lwc_string_unref(fetch_https_lwc);
}
 
 
bool fetch_add_fetcher(lwc_string *scheme,
fetcher_initialise initialiser,
fetcher_can_fetch can_fetch,
fetcher_setup_fetch setup_fetch,
fetcher_start_fetch start_fetch,
fetcher_abort_fetch abort_fetch,
fetcher_free_fetch free_fetch,
fetcher_poll_fetcher poll_fetcher,
fetcher_finalise finaliser)
{
scheme_fetcher *new_fetcher;
if (!initialiser(scheme))
return false;
new_fetcher = malloc(sizeof(scheme_fetcher));
if (new_fetcher == NULL) {
finaliser(scheme);
return false;
}
new_fetcher->scheme_name = scheme;
new_fetcher->refcount = 0;
new_fetcher->can_fetch = can_fetch;
new_fetcher->setup_fetch = setup_fetch;
new_fetcher->start_fetch = start_fetch;
new_fetcher->abort_fetch = abort_fetch;
new_fetcher->free_fetch = free_fetch;
new_fetcher->poll_fetcher = poll_fetcher;
new_fetcher->finaliser = finaliser;
new_fetcher->next_fetcher = fetchers;
fetchers = new_fetcher;
fetch_ref_fetcher(new_fetcher);
return true;
}
 
 
void fetch_unref_fetcher(scheme_fetcher *fetcher)
{
if (--fetcher->refcount == 0) {
fetcher->finaliser(fetcher->scheme_name);
lwc_string_unref(fetcher->scheme_name);
if (fetcher == fetchers) {
fetchers = fetcher->next_fetcher;
if (fetchers)
fetchers->prev_fetcher = NULL;
} else {
fetcher->prev_fetcher->next_fetcher =
fetcher->next_fetcher;
if (fetcher->next_fetcher != NULL)
fetcher->next_fetcher->prev_fetcher =
fetcher->prev_fetcher;
}
free(fetcher);
}
}
 
 
/**
* Start fetching data for the given URL.
*
* The function returns immediately. The fetch may be queued for later
* processing.
*
* A pointer to an opaque struct fetch is returned, which can be passed to
* fetch_abort() to abort the fetch at any time. Returns 0 if memory is
* exhausted (or some other fatal error occurred).
*
* The caller must supply a callback function which is called when anything
* interesting happens. The callback function is first called with msg
* FETCH_HEADER, with the header in data, then one or more times
* with FETCH_DATA with some data for the url, and finally with
* FETCH_FINISHED. Alternatively, FETCH_ERROR indicates an error occurred:
* data contains an error message. FETCH_REDIRECT may replace the FETCH_HEADER,
* FETCH_DATA, FETCH_FINISHED sequence if the server sends a replacement URL.
*
*/
 
struct fetch * fetch_start(nsurl *url, nsurl *referer,
fetch_callback callback,
void *p, bool only_2xx, const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
bool verifiable, bool downgrade_tls,
const char *headers[])
{
struct fetch *fetch;
scheme_fetcher *fetcher = fetchers;
lwc_string *scheme;
bool match;
 
fetch = malloc(sizeof (*fetch));
if (fetch == NULL)
return NULL;
 
/* The URL we're fetching must have a scheme */
scheme = nsurl_get_component(url, NSURL_SCHEME);
assert(scheme != NULL);
 
#ifdef DEBUG_FETCH_VERBOSE
LOG(("fetch %p, url '%s'", fetch, nsurl_access(url)));
#endif
 
/* construct a new fetch structure */
fetch->callback = callback;
fetch->url = nsurl_ref(url);
fetch->verifiable = verifiable;
fetch->p = p;
fetch->http_code = 0;
fetch->r_prev = NULL;
fetch->r_next = NULL;
fetch->referer = NULL;
fetch->send_referer = false;
fetch->fetcher_handle = NULL;
fetch->ops = NULL;
fetch->fetch_is_active = false;
fetch->host = nsurl_get_component(url, NSURL_HOST);
if (referer != NULL) {
lwc_string *ref_scheme;
fetch->referer = nsurl_ref(referer);
 
ref_scheme = nsurl_get_component(referer, NSURL_SCHEME);
/* Not a problem if referer has no scheme */
 
/* Determine whether to send the Referer header */
if (nsoption_bool(send_referer) && ref_scheme != NULL) {
/* User permits us to send the header
* Only send it if:
* 1) The fetch and referer schemes match
* or 2) The fetch is https and the referer is http
*
* This ensures that referer information is only sent
* across schemes in the special case of an https
* request from a page served over http. The inverse
* (https -> http) should not send the referer (15.1.3)
*/
bool match1;
bool match2;
if (lwc_string_isequal(scheme, ref_scheme, &match) != lwc_error_ok) {
match = false;
}
if (lwc_string_isequal(scheme, fetch_https_lwc, &match1) != lwc_error_ok) {
match1 = false;
}
if (lwc_string_isequal(ref_scheme, fetch_http_lwc, &match2) != lwc_error_ok) {
match2= false;
}
if (match == true || (match1 == true && match2 == true))
fetch->send_referer = true;
}
if (ref_scheme != NULL)
lwc_string_unref(ref_scheme);
}
 
/* Pick the scheme ops */
while (fetcher) {
if ((lwc_string_isequal(fetcher->scheme_name, scheme,
&match) == lwc_error_ok) && (match == true)) {
fetch->ops = fetcher;
break;
}
fetcher = fetcher->next_fetcher;
}
 
if (fetch->ops == NULL)
goto failed;
 
/* Got a scheme fetcher, try and set up the fetch */
fetch->fetcher_handle = fetch->ops->setup_fetch(fetch, url,
only_2xx, downgrade_tls,
post_urlenc, post_multipart,
headers);
 
if (fetch->fetcher_handle == NULL)
goto failed;
 
/* Rah, got it, so ref the fetcher. */
fetch_ref_fetcher(fetch->ops);
 
/* these aren't needed past here */
lwc_string_unref(scheme);
 
/* Dump us in the queue and ask the queue to run. */
RING_INSERT(queue_ring, fetch);
fetch_dispatch_jobs();
 
return fetch;
 
failed:
lwc_string_unref(scheme);
 
if (fetch->host != NULL)
lwc_string_unref(fetch->host);
if (fetch->url != NULL)
nsurl_unref(fetch->url);
if (fetch->referer != NULL)
nsurl_unref(fetch->referer);
 
free(fetch);
 
return NULL;
}
 
 
/**
* Dispatch as many jobs as we have room to dispatch.
*/
void fetch_dispatch_jobs(void)
{
int all_active, all_queued;
#ifdef DEBUG_FETCH_VERBOSE
struct fetch *q;
struct fetch *f;
#endif
 
if (!queue_ring)
return; /* Nothing to do, the queue is empty */
RING_GETSIZE(struct fetch, queue_ring, all_queued);
RING_GETSIZE(struct fetch, fetch_ring, all_active);
 
#ifdef DEBUG_FETCH_VERBOSE
LOG(("queue_ring %i, fetch_ring %i", all_queued, all_active));
 
q = queue_ring;
if (q) {
do {
LOG(("queue_ring: %s", q->url));
q = q->r_next;
} while (q != queue_ring);
}
f = fetch_ring;
if (f) {
do {
LOG(("fetch_ring: %s", f->url));
f = f->r_next;
} while (f != fetch_ring);
}
#endif
 
while ( all_queued && all_active < nsoption_int(max_fetchers) ) {
/*LOG(("%d queued, %d fetching", all_queued, all_active));*/
if (fetch_choose_and_dispatch()) {
all_queued--;
all_active++;
} else {
/* Either a dispatch failed or we ran out. Just stop */
break;
}
}
fetch_active = (all_active > 0);
#ifdef DEBUG_FETCH_VERBOSE
LOG(("Fetch ring is now %d elements.", all_active));
LOG(("Queue ring is now %d elements.", all_queued));
#endif
}
 
 
/**
* Choose and dispatch a single job. Return false if we failed to dispatch
* anything.
*
* We don't check the overall dispatch size here because we're not called unless
* there is room in the fetch queue for us.
*/
bool fetch_choose_and_dispatch(void)
{
bool same_host;
struct fetch *queueitem;
queueitem = queue_ring;
do {
/* We can dispatch the selected item if there is room in the
* fetch ring
*/
int countbyhost;
RING_COUNTBYLWCHOST(struct fetch, fetch_ring, countbyhost,
queueitem->host);
if (countbyhost < nsoption_int(max_fetchers_per_host)) {
/* We can dispatch this item in theory */
return fetch_dispatch_job(queueitem);
}
/* skip over other items with the same host */
same_host = true;
while (same_host == true && queueitem->r_next != queue_ring) {
if (lwc_string_isequal(queueitem->host,
queueitem->r_next->host, &same_host) ==
lwc_error_ok && same_host == true) {
queueitem = queueitem->r_next;
}
}
queueitem = queueitem->r_next;
} while (queueitem != queue_ring);
return false;
}
 
 
/**
* Dispatch a single job
*/
bool fetch_dispatch_job(struct fetch *fetch)
{
RING_REMOVE(queue_ring, fetch);
#ifdef DEBUG_FETCH_VERBOSE
LOG(("Attempting to start fetch %p, fetcher %p, url %s", fetch,
fetch->fetcher_handle, nsurl_access(fetch->url)));
#endif
if (!fetch->ops->start_fetch(fetch->fetcher_handle)) {
RING_INSERT(queue_ring, fetch); /* Put it back on the end of the queue */
return false;
} else {
RING_INSERT(fetch_ring, fetch);
fetch->fetch_is_active = true;
return true;
}
}
 
 
/**
* Abort a fetch.
*/
 
void fetch_abort(struct fetch *f)
{
assert(f);
#ifdef DEBUG_FETCH_VERBOSE
LOG(("fetch %p, fetcher %p, url '%s'", f, f->fetcher_handle,
nsurl_access(f->url)));
#endif
f->ops->abort_fetch(f->fetcher_handle);
}
 
 
/**
* Free a fetch structure and associated resources.
*/
 
void fetch_free(struct fetch *f)
{
#ifdef DEBUG_FETCH_VERBOSE
LOG(("Freeing fetch %p, fetcher %p", f, f->fetcher_handle));
#endif
f->ops->free_fetch(f->fetcher_handle);
fetch_unref_fetcher(f->ops);
nsurl_unref(f->url);
if (f->referer != NULL)
nsurl_unref(f->referer);
if (f->host != NULL)
lwc_string_unref(f->host);
free(f);
}
 
 
/**
* Do some work on current fetches.
*
* Must be called regularly to make progress on fetches.
*/
 
void fetch_poll(void)
{
scheme_fetcher *fetcher = fetchers;
scheme_fetcher *next_fetcher;
 
fetch_dispatch_jobs();
 
if (!fetch_active)
return; /* No point polling, there's no fetch active. */
while (fetcher != NULL) {
next_fetcher = fetcher->next_fetcher;
if (fetcher->poll_fetcher != NULL) {
/* LOG(("Polling fetcher for %s",
lwc_string_data(fetcher->scheme_name))); */
fetcher->poll_fetcher(fetcher->scheme_name);
}
fetcher = next_fetcher;
}
}
 
 
/**
* Check if a URL's scheme can be fetched.
*
* \param url URL to check
* \return true if the scheme is supported
*/
 
bool fetch_can_fetch(const nsurl *url)
{
scheme_fetcher *fetcher = fetchers;
bool match;
lwc_string *scheme = nsurl_get_component(url, NSURL_SCHEME);
 
while (fetcher != NULL) {
if (lwc_string_isequal(fetcher->scheme_name, scheme, &match) == lwc_error_ok && match == true) {
break;
}
 
fetcher = fetcher->next_fetcher;
}
 
lwc_string_unref(scheme);
 
return fetcher == NULL ? false : fetcher->can_fetch(url);
}
 
 
/**
* Change the callback function for a fetch.
*/
 
void fetch_change_callback(struct fetch *fetch,
fetch_callback callback,
void *p)
{
assert(fetch);
fetch->callback = callback;
fetch->p = p;
}
 
 
/**
* Get the HTTP response code.
*/
 
long fetch_http_code(struct fetch *fetch)
{
return fetch->http_code;
}
 
/**
* Determine if a fetch was verifiable
*
* \param fetch Fetch to consider
* \return Verifiable status of fetch
*/
bool fetch_get_verifiable(struct fetch *fetch)
{
assert(fetch);
 
return fetch->verifiable;
}
 
/**
* Clone a linked list of fetch_multipart_data.
*
* \param list List to clone
* \return Pointer to head of cloned list, or NULL on failure
*/
struct fetch_multipart_data *fetch_multipart_data_clone(
const struct fetch_multipart_data *list)
{
struct fetch_multipart_data *clone, *last = NULL;
struct fetch_multipart_data *result = NULL;
 
for (; list != NULL; list = list->next) {
clone = malloc(sizeof(struct fetch_multipart_data));
if (clone == NULL) {
if (result != NULL)
fetch_multipart_data_destroy(result);
 
return NULL;
}
 
clone->file = list->file;
 
clone->name = strdup(list->name);
if (clone->name == NULL) {
free(clone);
if (result != NULL)
fetch_multipart_data_destroy(result);
 
return NULL;
}
 
clone->value = strdup(list->value);
if (clone->value == NULL) {
free(clone->name);
free(clone);
if (result != NULL)
fetch_multipart_data_destroy(result);
 
return NULL;
}
 
clone->next = NULL;
 
if (result == NULL)
result = clone;
else
last->next = clone;
 
last = clone;
}
 
return result;
}
 
/**
* Free a linked list of fetch_multipart_data.
*
* \param list Pointer to head of list to free
*/
void fetch_multipart_data_destroy(struct fetch_multipart_data *list)
{
struct fetch_multipart_data *next;
 
for (; list != NULL; list = next) {
next = list->next;
free(list->name);
free(list->value);
free(list);
}
}
 
void
fetch_send_callback(const fetch_msg *msg, struct fetch *fetch)
{
fetch->callback(msg, fetch->p);
}
 
 
void fetch_remove_from_queues(struct fetch *fetch)
{
int all_active, all_queued;
 
/* Go ahead and free the fetch properly now */
#ifdef DEBUG_FETCH_VERBOSE
LOG(("Fetch %p, fetcher %p can be freed", fetch, fetch->fetcher_handle));
#endif
 
if (fetch->fetch_is_active) {
RING_REMOVE(fetch_ring, fetch);
} else {
RING_REMOVE(queue_ring, fetch);
}
 
RING_GETSIZE(struct fetch, fetch_ring, all_active);
RING_GETSIZE(struct fetch, queue_ring, all_queued);
 
fetch_active = (all_active > 0);
 
#ifdef DEBUG_FETCH_VERBOSE
LOG(("Fetch ring is now %d elements.", all_active));
LOG(("Queue ring is now %d elements.", all_queued));
#endif
}
 
 
void
fetch_set_http_code(struct fetch *fetch, long http_code)
{
#ifdef DEBUG_FETCH_VERBOSE
LOG(("Setting HTTP code to %ld", http_code));
#endif
fetch->http_code = http_code;
}
 
const char *fetch_get_referer_to_send(struct fetch *fetch)
{
if (fetch->send_referer)
return nsurl_access(fetch->referer);
return NULL;
}
 
void
fetch_set_cookie(struct fetch *fetch, const char *data)
{
assert(fetch && data);
 
/* If the fetch is unverifiable err on the side of caution and
* do not set the cookie */
 
if (fetch->verifiable) {
/* If the transaction's verifiable, we don't require
* that the request uri and the parent domain match,
* so don't pass in any referer/parent in this case. */
urldb_set_cookie(data, fetch->url, NULL);
} else if (fetch->referer != NULL) {
/* Permit the cookie to be set if the fetch is unverifiable
* and the fetch URI domain matches the referer. */
/** \todo Long-term, this needs to be replaced with a
* comparison against the origin fetch URI. In the case
* where a nested object requests a fetch, the origin URI
* is the nested object's parent URI, whereas the referer
* for the fetch will be the nested object's URI. */
urldb_set_cookie(data, fetch->url, fetch->referer);
}
}
 
/contrib/network/netsurf/netsurf/content/fetch.h
0,0 → 1,169
/*
* Copyright 2003 James Bursa <bursa@users.sourceforge.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Fetching of data from a URL (interface).
*/
 
#ifndef _NETSURF_DESKTOP_FETCH_H_
#define _NETSURF_DESKTOP_FETCH_H_
 
#include <stdbool.h>
 
#include <libwapcaplet/libwapcaplet.h>
 
#include "utils/config.h"
#include "utils/nsurl.h"
 
struct content;
struct fetch;
struct ssl_cert_info;
 
typedef enum {
FETCH_PROGRESS,
FETCH_HEADER,
FETCH_DATA,
FETCH_FINISHED,
FETCH_ERROR,
FETCH_REDIRECT,
FETCH_NOTMODIFIED,
FETCH_AUTH,
FETCH_CERT_ERR,
FETCH_SSL_ERR
} fetch_msg_type;
 
typedef struct fetch_msg {
fetch_msg_type type;
 
union {
const char *progress;
 
struct {
const uint8_t *buf;
size_t len;
} header_or_data;
 
const char *error;
 
/** \todo Use nsurl */
const char *redirect;
 
struct {
const char *realm;
} auth;
 
struct {
const struct ssl_cert_info *certs;
size_t num_certs;
} cert_err;
} data;
} fetch_msg;
 
/** Fetch POST multipart data */
struct fetch_multipart_data {
bool file; /**< Item is a file */
char *name; /**< Name of item */
char *value; /**< Item value */
 
struct fetch_multipart_data *next; /**< Next in linked list */
};
 
struct ssl_cert_info {
long version; /**< Certificate version */
char not_before[32]; /**< Valid from date */
char not_after[32]; /**< Valid to date */
int sig_type; /**< Signature type */
long serial; /**< Serial number */
char issuer[256]; /**< Issuer details */
char subject[256]; /**< Subject details */
int cert_type; /**< Certificate type */
};
 
extern bool fetch_active;
 
typedef void (*fetch_callback)(const fetch_msg *msg, void *p);
 
 
void fetch_init(void);
struct fetch * fetch_start(nsurl *url, nsurl *referer,
fetch_callback callback,
void *p, bool only_2xx, const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
bool verifiable, bool downgrade_tls,
const char *headers[]);
void fetch_abort(struct fetch *f);
void fetch_poll(void);
void fetch_quit(void);
const char *fetch_filetype(const char *unix_path);
char *fetch_mimetype(const char *ro_path);
bool fetch_can_fetch(const nsurl *url);
void fetch_change_callback(struct fetch *fetch,
fetch_callback callback,
void *p);
long fetch_http_code(struct fetch *fetch);
bool fetch_get_verifiable(struct fetch *fetch);
 
void fetch_multipart_data_destroy(struct fetch_multipart_data *list);
struct fetch_multipart_data *fetch_multipart_data_clone(
const struct fetch_multipart_data *list);
 
/* API for fetchers themselves */
 
typedef bool (*fetcher_initialise)(lwc_string *scheme);
typedef bool (*fetcher_can_fetch)(const nsurl *url);
typedef void *(*fetcher_setup_fetch)(struct fetch *parent_fetch, nsurl *url,
bool only_2xx, bool downgrade_tls, const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
const char **headers);
typedef bool (*fetcher_start_fetch)(void *fetch);
typedef void (*fetcher_abort_fetch)(void *fetch);
typedef void (*fetcher_free_fetch)(void *fetch);
typedef void (*fetcher_poll_fetcher)(lwc_string *scheme);
typedef void (*fetcher_finalise)(lwc_string *scheme);
 
/** Register a fetcher for a scheme
*
* \param scheme scheme fetcher is for (caller relinquishes ownership)
* \param initialiser fetcher initialiser
* \param can_fetch fetcher can fetch function
* \param setup_fetch fetcher fetch setup function
* \param start_fetch fetcher fetch start function
* \param abort_fetch fetcher fetch abort function
* \param free_fetch fetcher fetch free function
* \param poll_fetcher fetcher poll function
* \param finaliser fetcher finaliser
* \return true iff success
*/
bool fetch_add_fetcher(lwc_string *scheme,
fetcher_initialise initialiser,
fetcher_can_fetch can_fetch,
fetcher_setup_fetch setup_fetch,
fetcher_start_fetch start_fetch,
fetcher_abort_fetch abort_fetch,
fetcher_free_fetch free_fetch,
fetcher_poll_fetcher poll_fetcher,
fetcher_finalise finaliser);
 
void fetch_send_callback(const fetch_msg *msg, struct fetch *fetch);
void fetch_remove_from_queues(struct fetch *fetch);
void fetch_free(struct fetch *f);
void fetch_set_http_code(struct fetch *fetch, long http_code);
const char *fetch_get_referer_to_send(struct fetch *fetch);
void fetch_set_cookie(struct fetch *fetch, const char *data);
 
#endif
/contrib/network/netsurf/netsurf/content/hlcache.c
0,0 → 1,850
/*
* Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* High-level resource cache (implementation)
*/
 
#include <assert.h>
#include <stdlib.h>
#include <string.h>
 
#include "content/content.h"
#include "content/hlcache.h"
#include "content/mimesniff.h"
#include "utils/http.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/ring.h"
#include "utils/schedule.h"
#include "utils/url.h"
#include "utils/utils.h"
 
typedef struct hlcache_entry hlcache_entry;
typedef struct hlcache_retrieval_ctx hlcache_retrieval_ctx;
 
/** High-level cache retrieval context */
struct hlcache_retrieval_ctx {
struct hlcache_retrieval_ctx *r_prev; /**< Previous retrieval context in the ring */
struct hlcache_retrieval_ctx *r_next; /**< Next retrieval context in the ring */
 
llcache_handle *llcache; /**< Low-level cache handle */
 
hlcache_handle *handle; /**< High-level handle for object */
 
uint32_t flags; /**< Retrieval flags */
 
content_type accepted_types; /**< Accepted types */
 
hlcache_child_context child; /**< Child context */
 
bool migrate_target; /**< Whether this context is the migration target */
};
 
/** High-level cache handle */
struct hlcache_handle {
hlcache_entry *entry; /**< Pointer to cache entry */
 
hlcache_handle_callback cb; /**< Client callback */
void *pw; /**< Client data */
};
 
/** Entry in high-level cache */
struct hlcache_entry {
struct content *content; /**< Pointer to associated content */
 
hlcache_entry *next; /**< Next sibling */
hlcache_entry *prev; /**< Previous sibling */
};
 
/** Current state of the cache.
*
* Global state of the cache.
*/
struct hlcache_s {
struct hlcache_parameters params;
 
/** List of cached content objects */
hlcache_entry *content_list;
 
/** Ring of retrieval contexts */
hlcache_retrieval_ctx *retrieval_ctx_ring;
 
/* statsistics */
unsigned int hit_count;
unsigned int miss_count;
};
 
/** high level cache state */
static struct hlcache_s *hlcache = NULL;
 
 
static void hlcache_clean(void *ignored);
 
static nserror hlcache_llcache_callback(llcache_handle *handle,
const llcache_event *event, void *pw);
static nserror hlcache_migrate_ctx(hlcache_retrieval_ctx *ctx,
lwc_string *effective_type);
static bool hlcache_type_is_acceptable(lwc_string *mime_type,
content_type accepted_types, content_type *computed_type);
static nserror hlcache_find_content(hlcache_retrieval_ctx *ctx,
lwc_string *effective_type);
static void hlcache_content_callback(struct content *c,
content_msg msg, union content_msg_data data, void *pw);
 
/******************************************************************************
* Public API *
******************************************************************************/
 
nserror
hlcache_initialise(const struct hlcache_parameters *hlcache_parameters)
{
nserror ret;
 
hlcache = calloc(1, sizeof(struct hlcache_s));
if (hlcache == NULL) {
return NSERROR_NOMEM;
}
 
ret = llcache_initialise(hlcache_parameters->cb,
hlcache_parameters->cb_ctx,
hlcache_parameters->limit);
if (ret != NSERROR_OK) {
free(hlcache);
hlcache = NULL;
return ret;
}
 
hlcache->params = *hlcache_parameters;
 
/* Schedule the cache cleanup */
schedule(hlcache->params.bg_clean_time / 10, hlcache_clean, NULL);
 
return NSERROR_OK;
}
 
/* See hlcache.h for documentation */
void hlcache_stop(void)
{
/* Remove the hlcache_clean schedule */
schedule_remove(hlcache_clean, NULL);
}
 
/* See hlcache.h for documentation */
void hlcache_finalise(void)
{
uint32_t num_contents, prev_contents;
hlcache_entry *entry;
hlcache_retrieval_ctx *ctx, *next;
 
/* Obtain initial count of contents remaining */
for (num_contents = 0, entry = hlcache->content_list;
entry != NULL; entry = entry->next) {
num_contents++;
}
 
LOG(("%d contents remain before cache drain", num_contents));
 
/* Drain cache */
do {
prev_contents = num_contents;
 
hlcache_clean(NULL);
 
for (num_contents = 0, entry = hlcache->content_list;
entry != NULL; entry = entry->next) {
num_contents++;
}
} while (num_contents > 0 && num_contents != prev_contents);
 
LOG(("%d contents remaining:", num_contents));
for (entry = hlcache->content_list; entry != NULL; entry = entry->next) {
hlcache_handle entry_handle = { entry, NULL, NULL };
 
if (entry->content != NULL) {
LOG((" %p : %s (%d users)", entry,
nsurl_access(hlcache_handle_get_url(&entry_handle)), content_count_users(entry->content)));
} else {
LOG((" %p", entry));
}
}
 
/* Clean up retrieval contexts */
if (hlcache->retrieval_ctx_ring != NULL) {
ctx = hlcache->retrieval_ctx_ring;
 
do {
next = ctx->r_next;
 
if (ctx->llcache != NULL)
llcache_handle_release(ctx->llcache);
 
if (ctx->handle != NULL)
free(ctx->handle);
 
if (ctx->child.charset != NULL)
free((char *) ctx->child.charset);
 
free(ctx);
 
ctx = next;
} while (ctx != hlcache->retrieval_ctx_ring);
 
hlcache->retrieval_ctx_ring = NULL;
}
 
LOG(("hit/miss %d/%d", hlcache->hit_count, hlcache->miss_count));
 
free(hlcache);
hlcache = NULL;
 
LOG(("Finalising low-level cache"));
llcache_finalise();
}
 
/* See hlcache.h for documentation */
nserror hlcache_poll(void)
{
 
llcache_poll();
 
return NSERROR_OK;
}
 
/* See hlcache.h for documentation */
nserror hlcache_handle_retrieve(nsurl *url, uint32_t flags,
nsurl *referer, llcache_post_data *post,
hlcache_handle_callback cb, void *pw,
hlcache_child_context *child,
content_type accepted_types, hlcache_handle **result)
{
hlcache_retrieval_ctx *ctx;
nserror error;
 
assert(cb != NULL);
 
ctx = calloc(1, sizeof(hlcache_retrieval_ctx));
if (ctx == NULL)
return NSERROR_NOMEM;
 
ctx->handle = calloc(1, sizeof(hlcache_handle));
if (ctx->handle == NULL) {
free(ctx);
return NSERROR_NOMEM;
}
 
if (child != NULL) {
if (child->charset != NULL) {
ctx->child.charset = strdup(child->charset);
if (ctx->child.charset == NULL) {
free(ctx->handle);
free(ctx);
return NSERROR_NOMEM;
}
}
ctx->child.quirks = child->quirks;
}
 
ctx->flags = flags;
ctx->accepted_types = accepted_types;
 
ctx->handle->cb = cb;
ctx->handle->pw = pw;
 
error = llcache_handle_retrieve(url, flags, referer, post,
hlcache_llcache_callback, ctx,
&ctx->llcache);
if (error != NSERROR_OK) {
free((char *) ctx->child.charset);
free(ctx->handle);
free(ctx);
return error;
}
 
RING_INSERT(hlcache->retrieval_ctx_ring, ctx);
 
*result = ctx->handle;
 
return NSERROR_OK;
}
 
/* See hlcache.h for documentation */
nserror hlcache_handle_release(hlcache_handle *handle)
{
if (handle->entry != NULL) {
content_remove_user(handle->entry->content,
hlcache_content_callback, handle);
} else {
RING_ITERATE_START(struct hlcache_retrieval_ctx,
hlcache->retrieval_ctx_ring,
ictx) {
if (ictx->handle == handle &&
ictx->migrate_target == false) {
/* This is the nascent context for us,
* so abort the fetch */
llcache_handle_abort(ictx->llcache);
llcache_handle_release(ictx->llcache);
/* Remove us from the ring */
RING_REMOVE(hlcache->retrieval_ctx_ring, ictx);
/* Throw us away */
free((char *) ictx->child.charset);
free(ictx);
/* And stop */
RING_ITERATE_STOP(hlcache->retrieval_ctx_ring,
ictx);
}
} RING_ITERATE_END(hlcache->retrieval_ctx_ring, ictx);
}
 
handle->cb = NULL;
handle->pw = NULL;
 
free(handle);
 
return NSERROR_OK;
}
 
/* See hlcache.h for documentation */
struct content *hlcache_handle_get_content(const hlcache_handle *handle)
{
assert(handle != NULL);
 
if (handle->entry != NULL)
return handle->entry->content;
 
return NULL;
}
 
/* See hlcache.h for documentation */
nserror hlcache_handle_abort(hlcache_handle *handle)
{
struct hlcache_entry *entry = handle->entry;
struct content *c;
 
if (entry == NULL) {
/* This handle is not yet associated with a cache entry.
* The implication is that the fetch for the handle has
* not progressed to the point where the entry can be
* created. */
 
RING_ITERATE_START(struct hlcache_retrieval_ctx,
hlcache->retrieval_ctx_ring,
ictx) {
if (ictx->handle == handle &&
ictx->migrate_target == false) {
/* This is the nascent context for us,
* so abort the fetch */
llcache_handle_abort(ictx->llcache);
llcache_handle_release(ictx->llcache);
/* Remove us from the ring */
RING_REMOVE(hlcache->retrieval_ctx_ring, ictx);
/* Throw us away */
free((char *) ictx->child.charset);
free(ictx);
/* And stop */
RING_ITERATE_STOP(hlcache->retrieval_ctx_ring,
ictx);
}
} RING_ITERATE_END(hlcache->retrieval_ctx_ring, ictx);
 
return NSERROR_OK;
}
 
c = entry->content;
 
if (content_count_users(c) > 1) {
/* We are not the only user of 'c' so clone it. */
struct content *clone = content_clone(c);
 
if (clone == NULL)
return NSERROR_NOMEM;
 
entry = calloc(sizeof(struct hlcache_entry), 1);
 
if (entry == NULL) {
content_destroy(clone);
return NSERROR_NOMEM;
}
 
if (content_add_user(clone,
hlcache_content_callback, handle) == false) {
content_destroy(clone);
free(entry);
return NSERROR_NOMEM;
}
 
content_remove_user(c, hlcache_content_callback, handle);
 
entry->content = clone;
handle->entry = entry;
entry->prev = NULL;
entry->next = hlcache->content_list;
if (hlcache->content_list != NULL)
hlcache->content_list->prev = entry;
hlcache->content_list = entry;
 
c = clone;
}
 
return content_abort(c);
}
 
/* See hlcache.h for documentation */
nserror hlcache_handle_replace_callback(hlcache_handle *handle,
hlcache_handle_callback cb, void *pw)
{
handle->cb = cb;
handle->pw = pw;
 
return NSERROR_OK;
}
 
nserror hlcache_handle_clone(hlcache_handle *handle, hlcache_handle **result)
{
*result = NULL;
return NSERROR_CLONE_FAILED;
}
 
/* See hlcache.h for documentation */
nsurl *hlcache_handle_get_url(const hlcache_handle *handle)
{
nsurl *result = NULL;
 
assert(handle != NULL);
 
if (handle->entry != NULL) {
result = content_get_url(handle->entry->content);
} else {
RING_ITERATE_START(struct hlcache_retrieval_ctx,
hlcache->retrieval_ctx_ring,
ictx) {
if (ictx->handle == handle) {
/* This is the nascent context for us */
result = llcache_handle_get_url(ictx->llcache);
 
/* And stop */
RING_ITERATE_STOP(hlcache->retrieval_ctx_ring,
ictx);
}
} RING_ITERATE_END(hlcache->retrieval_ctx_ring, ictx);
}
 
return result;
}
 
/******************************************************************************
* High-level cache internals *
******************************************************************************/
 
/**
* Attempt to clean the cache
*/
void hlcache_clean(void *ignored)
{
hlcache_entry *entry, *next;
 
for (entry = hlcache->content_list; entry != NULL; entry = next) {
next = entry->next;
 
if (entry->content == NULL)
continue;
 
if (content__get_status(entry->content) ==
CONTENT_STATUS_LOADING)
continue;
 
if (content_count_users(entry->content) != 0)
continue;
 
/** \todo This is over-zealous: all unused contents
* will be immediately destroyed. Ideally, we want to
* purge all unused contents that are using stale
* source data, and enough fresh contents such that
* the cache fits in the configured cache size limit.
*/
 
/* Remove entry from cache */
if (entry->prev == NULL)
hlcache->content_list = entry->next;
else
entry->prev->next = entry->next;
 
if (entry->next != NULL)
entry->next->prev = entry->prev;
 
/* Destroy content */
content_destroy(entry->content);
 
/* Destroy entry */
free(entry);
}
 
/* Attempt to clean the llcache */
llcache_clean();
 
/* Re-schedule ourselves */
schedule(hlcache->params.bg_clean_time / 10, hlcache_clean, NULL);
}
 
/**
* Handler for low-level cache events
*
* \param handle Handle for which event is issued
* \param event Event data
* \param pw Pointer to client-specific data
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror hlcache_llcache_callback(llcache_handle *handle,
const llcache_event *event, void *pw)
{
hlcache_retrieval_ctx *ctx = pw;
lwc_string *effective_type = NULL;
nserror error;
 
assert(ctx->llcache == handle);
 
switch (event->type) {
case LLCACHE_EVENT_HAD_HEADERS:
error = mimesniff_compute_effective_type(handle, NULL, 0,
ctx->flags & HLCACHE_RETRIEVE_SNIFF_TYPE,
ctx->accepted_types == CONTENT_IMAGE,
&effective_type);
if (error == NSERROR_OK || error == NSERROR_NOT_FOUND) {
/* If the sniffer was successful or failed to find
* a Content-Type header when sniffing was
* prohibited, we must migrate the retrieval context. */
error = hlcache_migrate_ctx(ctx, effective_type);
 
if (effective_type != NULL)
lwc_string_unref(effective_type);
}
 
/* No need to report that we need data:
* we'll get some anyway if there is any */
if (error == NSERROR_NEED_DATA)
error = NSERROR_OK;
 
return error;
 
break;
case LLCACHE_EVENT_HAD_DATA:
error = mimesniff_compute_effective_type(handle,
event->data.data.buf, event->data.data.len,
ctx->flags & HLCACHE_RETRIEVE_SNIFF_TYPE,
ctx->accepted_types == CONTENT_IMAGE,
&effective_type);
if (error != NSERROR_OK) {
assert(0 && "MIME sniff failed with data");
}
 
error = hlcache_migrate_ctx(ctx, effective_type);
 
lwc_string_unref(effective_type);
 
return error;
 
break;
case LLCACHE_EVENT_DONE:
/* DONE event before we could determine the effective MIME type.
*/
error = mimesniff_compute_effective_type(handle,
NULL, 0, false, false, &effective_type);
if (error == NSERROR_OK) {
error = hlcache_migrate_ctx(ctx, effective_type);
 
lwc_string_unref(effective_type);
 
return error;
}
 
if (ctx->handle->cb != NULL) {
hlcache_event hlevent;
 
hlevent.type = CONTENT_MSG_ERROR;
hlevent.data.error = messages_get("BadType");
 
ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
}
break;
case LLCACHE_EVENT_ERROR:
if (ctx->handle->cb != NULL) {
hlcache_event hlevent;
 
hlevent.type = CONTENT_MSG_ERROR;
hlevent.data.error = event->data.msg;
 
ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
}
break;
case LLCACHE_EVENT_PROGRESS:
break;
}
 
return NSERROR_OK;
}
 
/**
* Migrate a retrieval context into its final destination content
*
* \param ctx Context to migrate
* \param effective_type The effective MIME type of the content, or NULL
* \return NSERROR_OK on success,
* NSERROR_NEED_DATA on success where data is needed,
* appropriate error otherwise
*/
nserror hlcache_migrate_ctx(hlcache_retrieval_ctx *ctx,
lwc_string *effective_type)
{
content_type type = CONTENT_NONE;
nserror error = NSERROR_OK;
 
ctx->migrate_target = true;
 
if (effective_type != NULL &&
hlcache_type_is_acceptable(effective_type,
ctx->accepted_types, &type)) {
error = hlcache_find_content(ctx, effective_type);
if (error != NSERROR_OK && error != NSERROR_NEED_DATA) {
if (ctx->handle->cb != NULL) {
hlcache_event hlevent;
 
hlevent.type = CONTENT_MSG_ERROR;
hlevent.data.error = messages_get("MiscError");
 
ctx->handle->cb(ctx->handle, &hlevent,
ctx->handle->pw);
}
 
llcache_handle_abort(ctx->llcache);
llcache_handle_release(ctx->llcache);
}
} else if (type == CONTENT_NONE &&
(ctx->flags & HLCACHE_RETRIEVE_MAY_DOWNLOAD)) {
/* Unknown type, and we can download, so convert */
llcache_handle_force_stream(ctx->llcache);
 
if (ctx->handle->cb != NULL) {
hlcache_event hlevent;
 
hlevent.type = CONTENT_MSG_DOWNLOAD;
hlevent.data.download = ctx->llcache;
 
ctx->handle->cb(ctx->handle, &hlevent,
ctx->handle->pw);
}
 
/* Ensure caller knows we need data */
error = NSERROR_NEED_DATA;
} else {
/* Unacceptable type: report error */
if (ctx->handle->cb != NULL) {
hlcache_event hlevent;
 
hlevent.type = CONTENT_MSG_ERROR;
hlevent.data.error = messages_get("UnacceptableType");
 
ctx->handle->cb(ctx->handle, &hlevent,
ctx->handle->pw);
}
 
llcache_handle_abort(ctx->llcache);
llcache_handle_release(ctx->llcache);
}
 
ctx->migrate_target = false;
 
/* No longer require retrieval context */
RING_REMOVE(hlcache->retrieval_ctx_ring, ctx);
free((char *) ctx->child.charset);
free(ctx);
 
return error;
}
 
/**
* Determine if the specified MIME type is acceptable
*
* \param mime_type MIME type to consider
* \param accepted_types Array of acceptable types, or NULL for any
* \param computed_type Pointer to location to receive computed type of object
* \return True if the type is acceptable, false otherwise
*/
bool hlcache_type_is_acceptable(lwc_string *mime_type,
content_type accepted_types, content_type *computed_type)
{
content_type type;
 
type = content_factory_type_from_mime_type(mime_type);
 
*computed_type = type;
 
return ((accepted_types & type) != 0);
}
 
/**
* Find a content for the high-level cache handle
*
* \param ctx High-level cache retrieval context
* \param effective_type Effective MIME type of content
* \return NSERROR_OK on success,
* NSERROR_NEED_DATA on success where data is needed,
* appropriate error otherwise
*
* \pre handle::state == HLCACHE_HANDLE_NEW
* \pre Headers must have been received for associated low-level handle
* \post Low-level handle is either released, or associated with new content
* \post High-level handle is registered with content
*/
nserror hlcache_find_content(hlcache_retrieval_ctx *ctx,
lwc_string *effective_type)
{
hlcache_entry *entry;
hlcache_event event;
nserror error = NSERROR_OK;
 
/* Search list of cached contents for a suitable one */
for (entry = hlcache->content_list; entry != NULL; entry = entry->next) {
hlcache_handle entry_handle = { entry, NULL, NULL };
const llcache_handle *entry_llcache;
 
if (entry->content == NULL)
continue;
 
/* Ignore contents in the error state */
if (content_get_status(&entry_handle) == CONTENT_STATUS_ERROR)
continue;
 
/* Ensure that content is shareable */
if (content_is_shareable(entry->content) == false)
continue;
 
/* Ensure that quirks mode is acceptable */
if (content_matches_quirks(entry->content,
ctx->child.quirks) == false)
continue;
 
/* Ensure that content uses same low-level object as
* low-level handle */
entry_llcache = content_get_llcache_handle(entry->content);
 
if (llcache_handle_references_same_object(entry_llcache,
ctx->llcache))
break;
}
 
if (entry == NULL) {
/* No existing entry, so need to create one */
entry = malloc(sizeof(hlcache_entry));
if (entry == NULL)
return NSERROR_NOMEM;
 
/* Create content using llhandle */
entry->content = content_factory_create_content(ctx->llcache,
ctx->child.charset, ctx->child.quirks,
effective_type);
if (entry->content == NULL) {
free(entry);
return NSERROR_NOMEM;
}
 
/* Insert into cache */
entry->prev = NULL;
entry->next = hlcache->content_list;
if (hlcache->content_list != NULL)
hlcache->content_list->prev = entry;
hlcache->content_list = entry;
 
/* Signal to caller that we created a content */
error = NSERROR_NEED_DATA;
 
hlcache->miss_count++;
} else {
/* Found a suitable content: no longer need low-level handle */
llcache_handle_release(ctx->llcache);
hlcache->hit_count++;
}
 
/* Associate handle with content */
if (content_add_user(entry->content,
hlcache_content_callback, ctx->handle) == false)
return NSERROR_NOMEM;
 
/* Associate cache entry with handle */
ctx->handle->entry = entry;
 
/* Catch handle up with state of content */
if (ctx->handle->cb != NULL) {
content_status status = content_get_status(ctx->handle);
 
if (status == CONTENT_STATUS_LOADING) {
event.type = CONTENT_MSG_LOADING;
ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
} else if (status == CONTENT_STATUS_READY) {
event.type = CONTENT_MSG_LOADING;
ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
 
if (ctx->handle->cb != NULL) {
event.type = CONTENT_MSG_READY;
ctx->handle->cb(ctx->handle, &event,
ctx->handle->pw);
}
} else if (status == CONTENT_STATUS_DONE) {
event.type = CONTENT_MSG_LOADING;
ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
 
if (ctx->handle->cb != NULL) {
event.type = CONTENT_MSG_READY;
ctx->handle->cb(ctx->handle, &event,
ctx->handle->pw);
}
 
if (ctx->handle->cb != NULL) {
event.type = CONTENT_MSG_DONE;
ctx->handle->cb(ctx->handle, &event,
ctx->handle->pw);
}
}
}
 
return error;
}
 
/**
* Veneer between content callback API and hlcache callback API
*
* \param c Content to emit message for
* \param msg Message to emit
* \param data Data for message
* \param pw Pointer to private data (hlcache_handle)
*/
void hlcache_content_callback(struct content *c, content_msg msg,
union content_msg_data data, void *pw)
{
hlcache_handle *handle = pw;
hlcache_event event;
nserror error = NSERROR_OK;
 
event.type = msg;
event.data = data;
 
if (handle->cb != NULL)
error = handle->cb(handle, &event, handle->pw);
 
if (error != NSERROR_OK)
LOG(("Error in callback: %d", error));
}
/contrib/network/netsurf/netsurf/content/hlcache.h
0,0 → 1,201
/*
* Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* High-level resource cache (interface)
*/
 
#ifndef NETSURF_CONTENT_HLCACHE_H_
#define NETSURF_CONTENT_HLCACHE_H_
 
#include "content/content.h"
#include "content/llcache.h"
#include "utils/errors.h"
#include "utils/nsurl.h"
 
/** High-level cache handle */
typedef struct hlcache_handle hlcache_handle;
 
/** Context for retrieving a child object */
typedef struct hlcache_child_context {
const char *charset; /**< Charset of parent */
bool quirks; /**< Whether parent is quirky */
} hlcache_child_context;
 
/** High-level cache event */
typedef struct {
content_msg type; /**< Event type */
union content_msg_data data; /**< Event data */
} hlcache_event;
 
struct hlcache_parameters {
llcache_query_callback cb; /**< Query handler for llcache */
void *cb_ctx; /**< Pointer to llcache query handler data */
 
/** How frequently the background cache clean process is run (ms) */
unsigned int bg_clean_time;
 
/** The target upper bound for the cache size */
size_t limit;
 
/** The hysteresis allowed round the target size */
size_t hysteresis;
 
};
 
/**
* Client callback for high-level cache events
*
* \param handle Handle to object generating event
* \param event Event data
* \param pw Pointer to client-specific data
* \return NSERROR_OK on success, appropriate error otherwise.
*/
typedef nserror (*hlcache_handle_callback)(hlcache_handle *handle,
const hlcache_event *event, void *pw);
 
/** Flags for high-level cache object retrieval */
enum hlcache_retrieve_flag {
/* Note: low-level cache retrieval flags occupy the bottom 16 bits of
* the flags word. High-level cache flags occupy the top 16 bits.
* To avoid confusion, high-level flags are allocated from bit 31 down.
*/
/** It's permitted to convert this request into a download */
HLCACHE_RETRIEVE_MAY_DOWNLOAD = (1 << 31),
/* Permit content-type sniffing */
HLCACHE_RETRIEVE_SNIFF_TYPE = (1 << 30)
};
 
/**
* Initialise the high-level cache, preparing the llcache also.
*
* \param hlcache_parameters Settings to initialise cache with
* \return NSERROR_OK on success, appropriate error otherwise.
*/
nserror hlcache_initialise(const struct hlcache_parameters *hlcache_parameters);
 
/**
* Stop the high-level cache periodic functionality so that the
* exit sequence can run.
*/
void hlcache_stop(void);
 
/**
* Finalise the high-level cache, destroying any remaining contents
*/
void hlcache_finalise(void);
 
/**
* Drive the low-level cache poll loop, and attempt to clean the cache.
* No guarantee is made about what, if any, cache cleaning will occur.
*
* \return NSERROR_OK
*/
nserror hlcache_poll(void);
 
/**
* Retrieve a high-level cache handle for an object
*
* \param url URL of the object to retrieve handle for
* \param flags Object retrieval flags
* \param referer Referring URL, or NULL if none
* \param post POST data, or NULL for a GET request
* \param cb Callback to handle object events
* \param pw Pointer to client-specific data for callback
* \param child Child retrieval context, or NULL for top-level content
* \param accepted_types Bitmap of acceptable content types
* \param result Pointer to location to recieve cache handle
* \return NSERROR_OK on success, appropriate error otherwise
*
* Child contents are keyed on the tuple < URL, quirks >.
* The quirks field is ignored for child contents whose behaviour is not
* affected by quirks mode.
*
* \todo The above rules should be encoded in the handler_map.
*
* \todo Is there any way to sensibly reduce the number of parameters here?
*/
nserror hlcache_handle_retrieve(nsurl *url, uint32_t flags,
nsurl *referer, llcache_post_data *post,
hlcache_handle_callback cb, void *pw,
hlcache_child_context *child,
content_type accepted_types, hlcache_handle **result);
 
/**
* Release a high-level cache handle
*
* \param handle Handle to release
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror hlcache_handle_release(hlcache_handle *handle);
 
/**
* Abort a high-level cache fetch
*
* \param handle Handle to abort
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror hlcache_handle_abort(hlcache_handle *handle);
 
/**
* Replace a high-level cache handle's callback
*
* \param handle Handle to replace callback of
* \param cb New callback routine
* \param pw Private data for callback
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror hlcache_handle_replace_callback(hlcache_handle *handle,
hlcache_handle_callback cb, void *pw);
 
/**
* Retrieve a content object from a cache handle
*
* \param handle Cache handle to dereference
* \return Pointer to content object, or NULL if there is none
*
* \todo This may not be correct. Ideally, the client should never need to
* directly access a content object. It may, therefore, be better to provide a
* bunch of veneers here that take a hlcache_handle and invoke the
* corresponding content_ API. If there's no content object associated with the
* hlcache_handle (e.g. because the source data is still being fetched, so it
* doesn't exist yet), then these veneers would behave as a NOP. The important
* thing being that the client need not care about this possibility and can
* just call the functions with impugnity.
*/
struct content *hlcache_handle_get_content(const hlcache_handle *handle);
 
/**
* Clone a high level cache handle.
*
* \param handle The handle to clone.
* \param result The cloned handle.
* \return NSERROR_OK on success, appropriate error otherwise
*
*/
nserror hlcache_handle_clone(hlcache_handle *handle, hlcache_handle **result);
 
/**
* Retrieve the URL associated with a high level cache handle
*
* \param handle The handle to inspect
* \return Pointer to URL.
*/
nsurl *hlcache_handle_get_url(const hlcache_handle *handle);
 
#endif
/contrib/network/netsurf/netsurf/content/llcache.c
0,0 → 1,2590
/*
* Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Low-level resource cache (implementation)
*/
 
#include <stdlib.h>
#include <string.h>
#include <time.h>
 
#include <curl/curl.h>
 
#include "content/fetch.h"
#include "content/llcache.h"
#include "content/urldb.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/nsurl.h"
#include "utils/utils.h"
 
/** Define to enable tracing of llcache operations. */
#undef LLCACHE_TRACE
 
/** State of a low-level cache object fetch */
typedef enum {
LLCACHE_FETCH_INIT, /**< Initial state, before fetch */
LLCACHE_FETCH_HEADERS, /**< Fetching headers */
LLCACHE_FETCH_DATA, /**< Fetching object data */
LLCACHE_FETCH_COMPLETE /**< Fetch completed */
} llcache_fetch_state;
 
/** Type of low-level cache object */
typedef struct llcache_object llcache_object;
 
/** Handle to low-level cache object */
struct llcache_handle {
llcache_object *object; /**< Pointer to associated object */
 
llcache_handle_callback cb; /**< Client callback */
void *pw; /**< Client data */
 
llcache_fetch_state state; /**< Last known state of object fetch */
size_t bytes; /**< Last reported byte count */
};
 
/** Low-level cache object user record */
typedef struct llcache_object_user {
llcache_handle *handle; /**< Handle data for client */
 
bool iterator_target; /**< This is the an iterator target */
bool queued_for_delete; /**< This user is queued for deletion */
 
struct llcache_object_user *prev; /**< Previous in list */
struct llcache_object_user *next; /**< Next in list */
} llcache_object_user;
 
/** Low-level cache object fetch context */
typedef struct {
uint32_t flags; /**< Fetch flags */
nsurl *referer; /**< Referring URL, or NULL if none */
llcache_post_data *post; /**< POST data, or NULL for GET */
 
struct fetch *fetch; /**< Fetch handle for this object */
 
llcache_fetch_state state; /**< Current state of object fetch */
 
uint32_t redirect_count; /**< Count of redirects followed */
 
bool tried_with_auth; /**< Whether we've tried with auth */
 
bool tried_with_tls_downgrade; /**< Whether we've tried TLS <= 1.0 */
 
bool outstanding_query; /**< Waiting for a query response */
} llcache_fetch_ctx;
 
typedef enum {
LLCACHE_VALIDATE_FRESH, /**< Only revalidate if not fresh */
LLCACHE_VALIDATE_ALWAYS, /**< Always revalidate */
LLCACHE_VALIDATE_ONCE /**< Revalidate once only */
} llcache_validate;
 
/** Cache control data */
typedef struct {
time_t req_time; /**< Time of request */
time_t res_time; /**< Time of response */
time_t date; /**< Date: response header */
time_t expires; /**< Expires: response header */
#define INVALID_AGE -1
int age; /**< Age: response header */
int max_age; /**< Max-Age Cache-control parameter */
llcache_validate no_cache; /**< No-Cache Cache-control parameter */
char *etag; /**< Etag: response header */
time_t last_modified; /**< Last-Modified: response header */
} llcache_cache_control;
 
/** Representation of a fetch header */
typedef struct {
char *name; /**< Header name */
char *value; /**< Header value */
} llcache_header;
 
/** Low-level cache object */
/** \todo Consider whether a list is a sane container */
struct llcache_object {
llcache_object *prev; /**< Previous in list */
llcache_object *next; /**< Next in list */
 
nsurl *url; /**< Post-redirect URL for object */
bool has_query; /**< URL has a query segment */
/** \todo We need a generic dynamic buffer object */
uint8_t *source_data; /**< Source data for object */
size_t source_len; /**< Byte length of source data */
size_t source_alloc; /**< Allocated size of source buffer */
 
llcache_object_user *users; /**< List of users */
 
llcache_fetch_ctx fetch; /**< Fetch context for object */
 
llcache_cache_control cache; /**< Cache control data for object */
llcache_object *candidate; /**< Object to use, if fetch determines
* that it is still fresh */
uint32_t candidate_count; /**< Count of objects this is a
* candidate for */
 
llcache_header *headers; /**< Fetch headers */
size_t num_headers; /**< Number of fetch headers */
};
 
struct llcache_s {
/** Handler for fetch-related queries */
llcache_query_callback query_cb;
 
/** Data for fetch-related query handler */
void *query_cb_pw;
 
/** Head of the low-level cached object list */
llcache_object *cached_objects;
 
/** Head of the low-level uncached object list */
llcache_object *uncached_objects;
 
uint32_t limit;
};
 
/** low level cache state */
static struct llcache_s *llcache = NULL;
 
/* Static lwc_strings */
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)
{
llcache_handle *h;
llcache_object_user *u;
 
h = calloc(1, sizeof(llcache_handle));
if (h == NULL)
return NSERROR_NOMEM;
 
u = calloc(1, sizeof(llcache_object_user));
if (u == NULL) {
free(h);
return NSERROR_NOMEM;
}
 
h->cb = cb;
h->pw = pw;
 
u->handle = h;
 
#ifdef LLCACHE_TRACE
LOG(("Created user %p (%p, %p, %p)", u, h, (void *) cb, pw));
#endif
 
*user = u;
 
return NSERROR_OK;
}
 
/**
* Destroy an object user
*
* \param user User to destroy
* \return NSERROR_OK on success, appropriate error otherwise
*
* \pre User is not attached to an object
*/
static nserror llcache_object_user_destroy(llcache_object_user *user)
{
#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(user);
 
return NSERROR_OK;
}
 
/**
* Remove a user from a low-level cache object
*
* \param object Object to remove user from
* \param user User to remove
* \return NSERROR_OK.
*/
static nserror llcache_object_remove_user(llcache_object *object,
llcache_object_user *user)
{
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;
 
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;
 
error = user->handle->cb(user->handle, event,
user->handle->pw);
 
next_user = user->next;
 
user->iterator_target = false;
 
if (user->queued_for_delete) {
llcache_object_remove_user(object, user);
llcache_object_user_destroy(user);
}
 
if (error != NSERROR_OK)
break;
 
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;
 
#ifdef LLCACHE_TRACE
LOG(("Created object %p (%s)", obj, nsurl_access(url)));
#endif
 
obj->url = nsurl_ref(url);
 
*result = obj;
 
return NSERROR_OK;
}
 
/**
* Clone a POST data object
*
* \param orig Object to clone
* \param clone Pointer to location to receive clone
* \return NSERROR_OK on success, appropriate error otherwise
*/
static nserror llcache_post_data_clone(const llcache_post_data *orig,
llcache_post_data **clone)
{
llcache_post_data *post_clone;
 
post_clone = calloc(1, sizeof(llcache_post_data));
if (post_clone == NULL)
return NSERROR_NOMEM;
 
post_clone->type = orig->type;
 
/* Deep-copy the type-specific data */
if (orig->type == LLCACHE_POST_URL_ENCODED) {
post_clone->data.urlenc = strdup(orig->data.urlenc);
if (post_clone->data.urlenc == NULL) {
free(post_clone);
 
return NSERROR_NOMEM;
}
} else {
post_clone->data.multipart = fetch_multipart_data_clone(
orig->data.multipart);
if (post_clone->data.multipart == NULL) {
free(post_clone);
 
return NSERROR_NOMEM;
}
}
 
*clone = post_clone;
 
return NSERROR_OK;
}
 
/**
* 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;
 
/* 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;
 
v = strdup("");
if (v == NULL) {
free(n);
return NSERROR_NOMEM;
}
} else {
/* Split header into name & value */
 
/* Strip leading whitespace from name */
while (data[0] == ' ' || data[0] == '\t' ||
data[0] == '\r' || data[0] == '\n') {
data++;
}
 
/* Strip trailing whitespace from name */
while (colon > data && (colon[-1] == ' ' ||
colon[-1] == '\t' || colon[-1] == '\r' ||
colon[-1] == '\n'))
colon--;
 
n = strndup((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;
}
}
 
*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
*
* \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;
 
/* 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);
 
if (start < comma)
object->cache.max_age = atoi(start);
}
 
if (*comma != '\0') {
/* Skip past comma */
comma++;
/* Skip whitespace */
SKIP_ST(comma);
}
 
/* Set start for next token */
start = comma;
}
} else if (5 < len && strcasecmp(*name, "ETag") == 0) {
/* extract ETag header */
free(object->cache.etag);
object->cache.etag = strdup(*value);
if (object->cache.etag == NULL)
return NSERROR_NOMEM;
} else if (14 < len && strcasecmp(*name, "Last-Modified") == 0) {
/* extract Last-Modified header */
object->cache.last_modified = curl_getdate(*value, NULL);
}
 
#undef SKIP_ST
 
return NSERROR_OK;
}
 
/* Destroy headers */
static inline void llcache_destroy_headers(llcache_object *object)
{
while (object->num_headers > 0) {
object->num_headers--;
 
free(object->headers[object->num_headers].name);
free(object->headers[object->num_headers].value);
}
free(object->headers);
object->headers = NULL;
}
 
/* Invalidate cache control data */
static inline void llcache_invalidate_cache_control_data(llcache_object *object)
{
free(object->cache.etag);
memset(&(object->cache), 0, sizeof(llcache_cache_control));
 
object->cache.age = INVALID_AGE;
object->cache.max_age = INVALID_AGE;
}
 
/**
* 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;
 
/* 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);
}
 
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;
}
 
/**
* (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 char *urlenc = NULL;
struct fetch_multipart_data *multipart = NULL;
char **headers = NULL;
int header_idx = 0;
 
if (object->fetch.post != NULL) {
if (object->fetch.post->type == LLCACHE_POST_URL_ENCODED)
urlenc = object->fetch.post->data.urlenc;
else
multipart = object->fetch.post->data.multipart;
}
 
/* Generate cache-control headers */
headers = malloc(3 * sizeof(char *));
if (headers == NULL)
return NSERROR_NOMEM;
 
if (object->cache.etag != NULL) {
const size_t len = SLEN("If-None-Match: ") +
strlen(object->cache.etag) + 1;
 
headers[header_idx] = malloc(len);
if (headers[header_idx] == NULL) {
free(headers);
return NSERROR_NOMEM;
}
 
snprintf(headers[header_idx], len, "If-None-Match: %s",
object->cache.etag);
 
header_idx++;
}
if (object->cache.date != 0) {
/* Maximum length of an RFC 1123 date is 29 bytes */
const size_t len = SLEN("If-Modified-Since: ") + 29 + 1;
 
headers[header_idx] = malloc(len);
if (headers[header_idx] == NULL) {
while (--header_idx >= 0)
free(headers[header_idx]);
free(headers);
return NSERROR_NOMEM;
}
 
snprintf(headers[header_idx], len, "If-Modified-Since: %s",
rfc1123_date(object->cache.date));
 
header_idx++;
}
headers[header_idx] = NULL;
 
/* Reset cache control data */
llcache_invalidate_cache_control_data(object);
object->cache.req_time = time(NULL);
 
/* Reset fetch state */
object->fetch.state = LLCACHE_FETCH_INIT;
 
#ifdef LLCACHE_TRACE
LOG(("Refetching %p", object));
#endif
 
/* 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,
object->fetch.tried_with_tls_downgrade,
(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;
}
 
/**
* 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
*/
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(("Starting fetch for %p", object));
#endif
 
if (post != NULL) {
error = llcache_post_data_clone(post, &post_clone);
if (error != NSERROR_OK)
return error;
}
 
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);
}
 
/**
* Destroy a low-level cache object
*
* \param object Object to destroy
* \return NSERROR_OK on success, appropriate error otherwise
*
* \pre Object is detached from cache list
* \pre Object has no users
* \pre Object is not a candidate (i.e. object::candidate_count == 0)
*/
static nserror llcache_object_destroy(llcache_object *object)
{
size_t i;
 
#ifdef LLCACHE_TRACE
LOG(("Destroying object %p", object));
#endif
 
nsurl_unref(object->url);
free(object->source_data);
 
if (object->fetch.fetch != NULL) {
fetch_abort(object->fetch.fetch);
object->fetch.fetch = NULL;
}
 
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);
}
 
free(object->fetch.post);
}
 
free(object->cache.etag);
 
for (i = 0; i < object->num_headers; i++) {
free(object->headers[i].name);
free(object->headers[i].value);
}
free(object->headers);
 
free(object);
 
return NSERROR_OK;
}
 
/**
* Add a low-level cache object to a cache list
*
* \param object Object to add
* \param list List to add to
* \return NSERROR_OK
*/
static 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;
}
 
/**
* Determine the remaining lifetime of a cache object using the
*
* \param object Object to consider
* \return True if object is still fresh, false otherwise
*/
static int
llcache_object_rfc2616_remaining_lifetime(const llcache_cache_control *cd)
{
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(("%d:%d", freshness_lifetime, current_age));
#endif
 
if ((cd->no_cache == LLCACHE_VALIDATE_FRESH) &&
(freshness_lifetime > current_age)) {
/* object was not forbidden from being returned from
* the cache unvalidated (i.e. the response contained
* a no-cache directive)
*
* The object current age is within the freshness lifetime.
*/
return freshness_lifetime - current_age;
}
 
return 0; /* object has no remaining lifetime */
}
 
/**
* 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)
{
int remaining_lifetime;
const llcache_cache_control *cd = &object->cache;
 
remaining_lifetime = llcache_object_rfc2616_remaining_lifetime(cd);
 
#ifdef LLCACHE_TRACE
LOG(("%p: (%d > 0 || %d != %d)", object,
remaining_lifetime,
object->fetch.state, LLCACHE_FETCH_COMPLETE));
#endif
 
/* The object is fresh if:
* - it was not forbidden from being returned from the cache
* unvalidated.
*
* - it has remaining lifetime or still being fetched.
*/
return ((cd->no_cache == LLCACHE_VALIDATE_FRESH) &&
((remaining_lifetime > 0) ||
(object->fetch.state != LLCACHE_FETCH_COMPLETE)));
}
 
/**
* 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;
 
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;
}
 
destination->cache.req_time = source->cache.req_time;
destination->cache.res_time = source->cache.res_time;
 
if (source->cache.date != 0)
destination->cache.date = source->cache.date;
 
if (source->cache.expires != 0)
destination->cache.expires = source->cache.expires;
 
if (source->cache.age != INVALID_AGE)
destination->cache.age = source->cache.age;
 
if (source->cache.max_age != INVALID_AGE)
destination->cache.max_age = source->cache.max_age;
 
if (source->cache.no_cache != 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;
}
 
/**
* Retrieve a potentially cached object
*
* \param url URL of object to retrieve
* \param flags Fetch flags
* \param referer Referring URL, or NULL if none
* \param post POST data, or NULL for a GET request
* \param 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
*/
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)
{
nserror error;
llcache_object *obj, *newest = NULL;
 
#ifdef LLCACHE_TRACE
LOG(("Searching cache for %s (%x %s %p)", url, flags, referer, post));
#endif
 
/* Search for the most recently fetched matching object */
for (obj = llcache->cached_objects; obj != NULL; obj = obj->next) {
 
if ((newest == NULL ||
obj->cache.req_time > newest->cache.req_time) &&
nsurl_compare(obj->url, url,
NSURL_COMPLETE) == true) {
newest = obj;
}
}
 
if (newest != NULL && llcache_object_is_fresh(newest)) {
/* Found a suitable object, and it's still fresh, so use it */
obj = newest;
 
#ifdef LLCACHE_TRACE
LOG(("Found fresh %p", obj));
#endif
 
/* The client needs to catch up with the object's state.
* This will occur the next time that llcache_poll is called.
*/
} else if (newest != NULL) {
/* Found a candidate object but it needs freshness validation */
 
/* Create a new object */
error = llcache_object_new(url, &obj);
if (error != NSERROR_OK)
return error;
 
#ifdef LLCACHE_TRACE
LOG(("Found candidate %p (%p)", obj, newest));
#endif
 
/* Clone candidate's cache data */
error = llcache_object_clone_cache_data(newest, obj, true);
if (error != NSERROR_OK) {
llcache_object_destroy(obj);
return error;
}
 
/* Record candidate, so we can fall back if it is still fresh */
newest->candidate_count++;
obj->candidate = newest;
 
/* Attempt to kick-off fetch */
error = llcache_object_fetch(obj, flags, referer, post,
redirect_count);
if (error != NSERROR_OK) {
newest->candidate_count--;
llcache_object_destroy(obj);
return error;
}
 
/* Add new object to cache */
llcache_object_add_to_list(obj, &llcache->cached_objects);
} else {
/* No object found; create a new one */
/* Create new object */
error = llcache_object_new(url, &obj);
if (error != NSERROR_OK)
return error;
 
#ifdef LLCACHE_TRACE
LOG(("Not found %p", obj));
#endif
 
/* Attempt to kick-off fetch */
error = llcache_object_fetch(obj, flags, referer, post,
redirect_count);
if (error != NSERROR_OK) {
llcache_object_destroy(obj);
return error;
}
 
/* Add new object to cache */
llcache_object_add_to_list(obj, &llcache->cached_objects);
}
 
*result = obj;
 
return NSERROR_OK;
}
 
/**
* Retrieve an object from the cache, fetching it if necessary.
*
* \param url URL of object to retrieve
* \param flags Fetch flags
* \param referer Referring URL, or NULL if none
* \param post POST data, or NULL for a GET request
* \param 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
*/
static nserror llcache_object_retrieve(nsurl *url, uint32_t flags,
nsurl *referer, const llcache_post_data *post,
uint32_t redirect_count, llcache_object **result)
{
nserror error;
llcache_object *obj;
bool has_query;
nsurl *defragmented_url;
 
#ifdef LLCACHE_TRACE
LOG(("Retrieve %s (%x, %s, %p)", url, flags, referer, post));
#endif
 
/**
* Caching Rules:
*
* 1) Forced fetches are never cached
* 2) POST requests are never cached
*/
 
/* 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;
}
 
/**
* Add a user to a low-level cache object
*
* \param object Object to add user to
* \param user User to add
* \return NSERROR_OK.
*/
static nserror llcache_object_add_user(llcache_object *object,
llcache_object_user *user)
{
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;
}
 
/**
* 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
*/
static nserror llcache_fetch_redirect(llcache_object *object, const char *target,
llcache_object **replacement)
{
nserror error;
llcache_object *dest;
llcache_object_user *user, *next;
const llcache_post_data *post = object->fetch.post;
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;
/* Invalidate the cache control data */
llcache_invalidate_cache_control_data(object);
 
/* 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;
 
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;
}
}
 
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;
}
 
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;
}
 
/* Attempt to fetch target URL */
error = llcache_object_retrieve(url, object->fetch.flags,
object->fetch.referer, post,
object->fetch.redirect_count + 1, &dest);
 
/* No longer require url */
nsurl_unref(url);
 
if (error != NSERROR_OK)
return error;
 
/* Move user(s) to replacement object */
for (user = object->users; user != NULL; user = next) {
next = user->next;
 
llcache_object_remove_user(object, user);
llcache_object_add_user(dest, user);
}
 
/* Dest is now our object */
*replacement = dest;
 
return NSERROR_OK;
}
 
/**
* Update an object's cache state
*
* \param object Object to update cache for
* \return NSERROR_OK.
*/
static nserror llcache_object_cache_update(llcache_object *object)
{
if (object->cache.date == 0)
object->cache.date = time(NULL);
 
return NSERROR_OK;
}
 
/**
* 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;
 
/* 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 */
 
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.
*/
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;
 
object->source_data = temp;
object->source_alloc = new_len;
}
 
/* Append this data chunk to source buffer */
memcpy(object->source_data + object->source_len, data, len);
object->source_len += len;
 
return NSERROR_OK;
}
 
/**
* Handle a query response
*
* \param proceed Whether to proceed with fetch
* \param cbpw Our context for query
* \return NSERROR_OK on success, appropriate error otherwise
*/
static nserror llcache_query_handle_response(bool proceed, void *cbpw)
{
llcache_event event;
llcache_object *object = cbpw;
 
object->fetch.outstanding_query = false;
 
/* Refetch, using existing fetch parameters, if client allows us to */
if (proceed)
return llcache_object_refetch(object);
 
/* Invalidate cache-control data */
llcache_invalidate_cache_control_data(object);
 
/* 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);
}
 
/**
* Handle an authentication request
*
* \param object Object being fetched
* \param realm Authentication realm
* \return NSERROR_OK on success, appropriate error otherwise.
*/
static nserror llcache_fetch_auth(llcache_object *object, const char *realm)
{
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(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;
 
/* Emit query for authentication details */
query.type = LLCACHE_QUERY_AUTH;
query.url = object->url;
query.data.auth.realm = realm;
 
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);
}
} 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;
}
 
/**
* 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
*/
static nserror llcache_fetch_cert_error(llcache_object *object,
const struct ssl_cert_info *certs, size_t num)
{
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;
}
 
/**
* Handle a TLS connection setup failure
*
* \param object Object being fetched
* \return NSERROR_OK on success, appropriate error otherwise
*/
static nserror llcache_fetch_ssl_error(llcache_object *object)
{
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 (object->fetch.tried_with_tls_downgrade == true) {
/* Have already tried to downgrade, so give up */
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 downgrade, so that if the
* fetch fails again, we give up */
object->fetch.tried_with_tls_downgrade = true;
error = llcache_object_refetch(object);
}
 
return error;
}
 
/**
* Handler for fetch events
*
* \param msg Fetch event
* \param p Our private data
*/
static void llcache_fetch_callback(const fetch_msg *msg, void *p)
{
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 */
 
/* 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;
case FETCH_SSL_ERR:
/* TLS connection setup failed */
 
/* Release candidate, if any */
if (object->candidate != NULL) {
object->candidate->candidate_count--;
object->candidate = NULL;
}
 
error = llcache_fetch_ssl_error(object);
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;
}
}
 
/**
* Find a user of a low-level cache object
*
* \param handle External cache handle to search for
* \return Pointer to corresponding user, or NULL if not found
*/
static llcache_object_user *llcache_object_find_user(const llcache_handle *handle)
{
llcache_object_user *user;
 
assert(handle->object != NULL);
 
for (user = handle->object->users; user != NULL; user = user->next) {
if (user->handle == handle)
break;
}
 
return user;
}
 
/**
* Remove a low-level cache object from a cache list
*
* \param object Object to remove
* \param list List to remove from
* \return NSERROR_OK
*/
static nserror llcache_object_remove_from_list(llcache_object *object,
llcache_object **list)
{
if (object == *list)
*list = object->next;
else
object->prev->next = object->next;
 
if (object->next != NULL)
object->next->prev = object->prev;
 
return NSERROR_OK;
}
 
/**
* Determine if a low-level cache object resides in a given list
*
* \param object Object to search for
* \param list List to search in
* \return True if object resides in list, false otherwise
*/
static bool llcache_object_in_list(const llcache_object *object,
const llcache_object *list)
{
while (list != NULL) {
if (list == object)
break;
 
list = list->next;
}
 
return list != NULL;
}
 
/**
* Notify users of an object's current state
*
* \param object Object to notify users about
* \return NSERROR_OK on success, appropriate error otherwise
*/
static nserror llcache_object_notify_users(llcache_object *object)
{
nserror error;
llcache_object_user *user, *next_user;
llcache_event event;
 
#ifdef LLCACHE_TRACE
bool emitted_notify = false;
#endif
 
/**
* State transitions and event emission for users.
* Rows: user state. Cols: object state.
*
* User\Obj INIT HEADERS DATA COMPLETE
* INIT - T T* T*
* HEADERS - - T T*
* DATA - - M T
* COMPLETE - - - -
*
* T => transition user to object state
* M => no transition required, but may need to emit event
*
* The transitions marked with an asterisk can be removed by moving
* the user context into the subsequent state and then reevaluating.
*
* Events are issued as follows:
*
* HAD_HEADERS: on transition from HEADERS -> DATA state
* HAD_DATA : in DATA state, whenever there's new source data
* DONE : on transition from DATA -> COMPLETE state
*/
 
for (user = object->users; user != NULL; user = next_user) {
/* Emit necessary events to bring the user up-to-date */
llcache_handle *handle = user->handle;
const llcache_fetch_state objstate = object->fetch.state;
 
/* Flag that this user is the current iteration target
* in case the client attempts to destroy it underneath us */
user->iterator_target = true;
 
/* A note on the computation of next_user:
*
* Within this loop, we may make a number of calls to
* client code. Our contract with clients is that they
* can do whatever they like from within their callback
* handlers. This is so that we limit the pain of
* reentrancy to this module alone.
*
* One of the things a client can do from within its
* callback handler is to remove users from this object's
* user list. In the common case, the user they attempt
* to remove is the current iteration target, and we
* already protect against that causing problems here.
* However, no such protection exists if the client
* attempts to remove other users from this object's
* user list.
*
* Therefore, we cannot compute next_user up-front
* and expect it to remain valid across calls to
* client code (as the identity of the next user
* in the list may change underneath us). Instead,
* we must compute next_user at the point where we
* are about to cause another iteration of this loop
* (i.e. at the very end, and also at the points where
* continue is used)
*/
 
#ifdef LLCACHE_TRACE
if (handle->state != objstate) {
if (emitted_notify == false) {
LOG(("Notifying users of %p", object));
emitted_notify = true;
}
 
LOG(("User %p state: %d Object state: %d",
user, handle->state, objstate));
}
#endif
 
/* User: INIT, Obj: HEADERS, DATA, COMPLETE => User->HEADERS */
if (handle->state == LLCACHE_FETCH_INIT &&
objstate > LLCACHE_FETCH_INIT) {
handle->state = LLCACHE_FETCH_HEADERS;
}
 
/* User: HEADERS, Obj: DATA, COMPLETE => User->DATA */
if (handle->state == LLCACHE_FETCH_HEADERS &&
objstate > LLCACHE_FETCH_HEADERS) {
handle->state = LLCACHE_FETCH_DATA;
 
/* Emit HAD_HEADERS event */
event.type = LLCACHE_EVENT_HAD_HEADERS;
 
error = handle->cb(handle, &event, handle->pw);
 
if (user->queued_for_delete) {
next_user = user->next;
llcache_object_remove_user(object, user);
llcache_object_user_destroy(user);
 
if (error != NSERROR_OK)
return error;
 
continue;
} else if (error == NSERROR_NEED_DATA) {
/* User requested replay */
handle->state = LLCACHE_FETCH_HEADERS;
 
/* Continue with the next user -- we'll
* reemit the event next time round */
user->iterator_target = false;
next_user = user->next;
continue;
} else if (error != NSERROR_OK) {
user->iterator_target = false;
return error;
}
}
 
/* User: DATA, Obj: DATA, COMPLETE, more source available */
if (handle->state == LLCACHE_FETCH_DATA &&
objstate >= LLCACHE_FETCH_DATA &&
object->source_len > handle->bytes) {
size_t orig_handle_read;
 
/* Construct HAD_DATA event */
event.type = LLCACHE_EVENT_HAD_DATA;
event.data.data.buf =
object->source_data + handle->bytes;
event.data.data.len =
object->source_len - handle->bytes;
 
/* Update record of last byte emitted */
if (object->fetch.flags &
LLCACHE_RETRIEVE_STREAM_DATA) {
/* Streaming, so reset to zero to
* minimise amount of cached source data.
* Additionally, we don't support replay
* when streaming. */
orig_handle_read = 0;
handle->bytes = object->source_len = 0;
} else {
orig_handle_read = handle->bytes;
handle->bytes = object->source_len;
}
 
/* Emit event */
error = handle->cb(handle, &event, handle->pw);
if (user->queued_for_delete) {
next_user = user->next;
llcache_object_remove_user(object, user);
llcache_object_user_destroy(user);
 
if (error != NSERROR_OK)
return error;
 
continue;
} else if (error == NSERROR_NEED_DATA) {
/* User requested replay */
handle->bytes = orig_handle_read;
 
/* Continue with the next user -- we'll
* reemit the data next time round */
user->iterator_target = false;
next_user = user->next;
continue;
} else if (error != NSERROR_OK) {
user->iterator_target = false;
return error;
}
}
 
/* User: DATA, Obj: COMPLETE => User->COMPLETE */
if (handle->state == LLCACHE_FETCH_DATA &&
objstate > LLCACHE_FETCH_DATA) {
handle->state = LLCACHE_FETCH_COMPLETE;
 
/* Emit DONE event */
event.type = LLCACHE_EVENT_DONE;
 
error = handle->cb(handle, &event, handle->pw);
if (user->queued_for_delete) {
next_user = user->next;
llcache_object_remove_user(object, user);
llcache_object_user_destroy(user);
 
if (error != NSERROR_OK)
return error;
 
continue;
} else if (error == NSERROR_NEED_DATA) {
/* User requested replay */
handle->state = LLCACHE_FETCH_DATA;
 
/* Continue with the next user -- we'll
* reemit the event next time round */
user->iterator_target = false;
next_user = user->next;
continue;
} else if (error != NSERROR_OK) {
user->iterator_target = false;
return error;
}
}
 
/* No longer the target of an iterator */
user->iterator_target = false;
 
next_user = user->next;
}
 
return NSERROR_OK;
}
 
/**
* Make a snapshot of the current state of an llcache_object.
*
* This has the side-effect of the new object being non-cacheable,
* also not-fetching and not a candidate for any other object.
*
* Also note that this new object has no users and at least one
* should be assigned to it before llcache_clean is entered or it
* will be immediately cleaned up.
*
* \param object The object to take a snapshot of
* \param snapshot Pointer to receive snapshot of \a object
* \return NSERROR_OK on success, appropriate error otherwise
*/
static nserror llcache_object_snapshot(llcache_object *object,
llcache_object **snapshot)
{
llcache_object *newobj;
nserror error;
error = llcache_object_new(object->url, &newobj);
if (error != NSERROR_OK)
return error;
newobj->has_query = object->has_query;
 
newobj->source_alloc = newobj->source_len = object->source_len;
if (object->source_len > 0) {
newobj->source_data = malloc(newobj->source_alloc);
if (newobj->source_data == NULL) {
llcache_object_destroy(newobj);
return NSERROR_NOMEM;
}
memcpy(newobj->source_data, object->source_data,
newobj->source_len);
}
if (object->num_headers > 0) {
newobj->headers = calloc(sizeof(llcache_header),
object->num_headers);
if (newobj->headers == NULL) {
llcache_object_destroy(newobj);
return NSERROR_NOMEM;
}
while (newobj->num_headers < object->num_headers) {
llcache_header *nh =
&(newobj->headers[newobj->num_headers]);
llcache_header *oh =
&(object->headers[newobj->num_headers]);
newobj->num_headers += 1;
nh->name = strdup(oh->name);
nh->value = strdup(oh->value);
if (nh->name == NULL || nh->value == NULL) {
llcache_object_destroy(newobj);
return NSERROR_NOMEM;
}
}
}
newobj->fetch.state = LLCACHE_FETCH_COMPLETE;
*snapshot = newobj;
return NSERROR_OK;
}
 
 
/******************************************************************************
* Public API *
******************************************************************************/
 
/**
* Attempt to clean the cache
*/
/* Exported interface documented in llcache.h */
void llcache_clean(void)
{
llcache_object *object, *next;
uint32_t llcache_size = 0;
int remaining_lifetime;
 
#ifdef LLCACHE_TRACE
LOG(("Attempting cache clean"));
#endif
 
/* Candidates for cleaning are (in order of priority):
*
* 1) Uncacheable objects with no users
* 2) Stale cacheable objects with no users or pending fetches
* 3) Fresh cacheable objects with no users or pending fetches
*/
 
/* 1) Uncacheable objects with no users or fetches */
for (object = llcache->uncached_objects; object != NULL; object = next) {
next = object->next;
 
/* The candidate count of uncacheable objects is always 0 */
if ((object->users == NULL) &&
(object->candidate_count == 0) &&
(object->fetch.fetch == NULL) &&
(object->fetch.outstanding_query == false)) {
#ifdef LLCACHE_TRACE
LOG(("Found victim %p", object));
#endif
llcache_object_remove_from_list(object,
&llcache->uncached_objects);
llcache_object_destroy(object);
} else {
llcache_size += object->source_len + sizeof(*object);
}
}
 
/* 2) Stale cacheable objects with no users or pending fetches */
for (object = llcache->cached_objects; object != NULL; object = next) {
next = object->next;
 
remaining_lifetime = llcache_object_rfc2616_remaining_lifetime(&object->cache);
 
if ((object->users == NULL) &&
(object->candidate_count == 0) &&
(object->fetch.fetch == NULL) &&
(object->fetch.outstanding_query == false)) {
 
if (remaining_lifetime > 0) {
/* object is fresh */
llcache_size += object->source_len + sizeof(*object);
} else {
/* object is not fresh */
#ifdef LLCACHE_TRACE
LOG(("Found stale cacheable object (%p) with no users or pending fetches", object));
#endif
llcache_object_remove_from_list(object,
&llcache->cached_objects);
llcache_object_destroy(object);
}
} else {
llcache_size += object->source_len + sizeof(*object);
}
}
 
/* 3) Fresh cacheable objects with no users or pending
* fetches, only if the cache exceeds the configured size.
*/
if (llcache->limit < llcache_size) {
for (object = llcache->cached_objects; object != NULL;
object = next) {
next = object->next;
 
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
llcache_size -=
object->source_len + sizeof(*object);
 
llcache_object_remove_from_list(object,
&llcache->cached_objects);
llcache_object_destroy(object);
}
}
}
 
#ifdef LLCACHE_TRACE
LOG(("Size: %u", llcache_size));
#endif
 
}
 
/* 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;
}
 
llcache->query_cb = cb;
llcache->query_cb_pw = pw;
llcache->limit = llcache_limit;
 
/* Create static scheme strings */
if (lwc_intern_string("file", SLEN("file"),
&llcache_file_lwc) != lwc_error_ok)
return NSERROR_NOMEM;
 
if (lwc_intern_string("about", SLEN("about"),
&llcache_about_lwc) != lwc_error_ok)
return NSERROR_NOMEM;
 
if (lwc_intern_string("resource", SLEN("resource"),
&llcache_resource_lwc) != lwc_error_ok)
return NSERROR_NOMEM;
 
LOG(("llcache initialised with a limit of %d bytes", llcache_limit));
 
return NSERROR_OK;
}
 
/* See llcache.h for documentation */
void llcache_finalise(void)
{
llcache_object *object, *next;
 
/* Clean uncached objects */
for (object = llcache->uncached_objects; object != NULL; object = next) {
llcache_object_user *user, *next_user;
 
next = object->next;
 
for (user = object->users; user != NULL; user = next_user) {
next_user = user->next;
 
if (user->handle != NULL)
free(user->handle);
 
free(user);
}
 
/* Fetch system has already been destroyed */
object->fetch.fetch = NULL;
 
llcache_object_destroy(object);
}
 
/* Clean cached objects */
for (object = llcache->cached_objects; object != NULL; object = next) {
llcache_object_user *user, *next_user;
 
next = object->next;
 
for (user = object->users; user != NULL; user = next_user) {
next_user = user->next;
 
if (user->handle != NULL)
free(user->handle);
 
free(user);
}
 
/* Fetch system has already been destroyed */
object->fetch.fetch = NULL;
 
llcache_object_destroy(object);
}
 
/* Unref static scheme lwc strings */
lwc_string_unref(llcache_file_lwc);
lwc_string_unref(llcache_about_lwc);
lwc_string_unref(llcache_resource_lwc);
 
free(llcache);
llcache = NULL;
}
 
/* 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);
}
 
for (object = llcache->uncached_objects; object != NULL;
object = object->next) {
llcache_object_notify_users(object);
}
 
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)
{
nserror error;
llcache_object_user *user;
llcache_object *object;
 
/* Can we fetch this URL at all? */
if (fetch_can_fetch(url) == false)
return NSERROR_NO_FETCH_HANDLER;
 
/* Create a new object user */
error = llcache_object_user_new(cb, pw, &user);
if (error != NSERROR_OK)
return error;
 
/* Retrieve a suitable object from the cache,
* creating a new one if needed. */
error = llcache_object_retrieve(url, flags, referer, post, 0, &object);
if (error != NSERROR_OK) {
llcache_object_user_destroy(user);
return error;
}
 
/* Add user to object */
llcache_object_add_user(object, user);
 
*result = user->handle;
 
return NSERROR_OK;
}
 
/* 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;
 
return NSERROR_OK;
}
 
/* 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);
 
assert(user != NULL);
 
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);
}
}
return error;
}
 
/* 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;
}
 
/* 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;
 
/* 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;
}
 
/* Move handle across to clone */
newuser->handle = user->handle;
user->handle = NULL;
 
/* 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);
}
/* 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;
 
/* 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->fetch.flags |= LLCACHE_RETRIEVE_STREAM_DATA;
 
return NSERROR_OK;
}
 
/* 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;
}
 
return NSERROR_OK;
}
 
/* See llcache.h for documentation */
nsurl *llcache_handle_get_url(const llcache_handle *handle)
{
return handle->object != NULL ? handle->object->url : NULL;
}
 
/* 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;
 
return handle->object != NULL ? handle->object->source_data : NULL;
}
 
/* 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;
 
if (object == NULL)
return NULL;
 
/* About as trivial as possible */
for (i = 0; i < object->num_headers; i++) {
if (strcasecmp(key, object->headers[i].name) == 0)
return object->headers[i].value;
}
 
return NULL;
}
 
/* See llcache.h for documentation */
bool llcache_handle_references_same_object(const llcache_handle *a,
const llcache_handle *b)
{
return a->object == b->object;
}
 
/contrib/network/netsurf/netsurf/content/llcache.h
0,0 → 1,295
/*
* Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Low-level resource cache (interface)
*/
 
#ifndef NETSURF_CONTENT_LLCACHE_H_
#define NETSURF_CONTENT_LLCACHE_H_
 
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
 
#include "utils/errors.h"
#include "utils/nsurl.h"
 
struct ssl_cert_info;
struct fetch_multipart_data;
 
/** Handle for low-level cache object */
typedef struct llcache_handle llcache_handle;
 
/** POST data object for low-level cache requests */
typedef struct {
enum {
LLCACHE_POST_URL_ENCODED,
LLCACHE_POST_MULTIPART
} type; /**< Type of POST data */
union {
char *urlenc; /**< URL encoded data */
struct fetch_multipart_data *multipart; /**< Multipart data */
} data; /**< POST data content */
} llcache_post_data;
 
/** Low-level cache event types */
typedef enum {
LLCACHE_EVENT_HAD_HEADERS, /**< Received all headers */
LLCACHE_EVENT_HAD_DATA, /**< Received some data */
LLCACHE_EVENT_DONE, /**< Finished fetching data */
 
LLCACHE_EVENT_ERROR, /**< An error occurred during fetch */
LLCACHE_EVENT_PROGRESS, /**< Fetch progress update */
} llcache_event_type;
 
/** Low-level cache events */
typedef struct {
llcache_event_type type; /**< Type of event */
union {
struct {
const uint8_t *buf; /**< Buffer of data */
size_t len; /**< Length of buffer, in bytes */
} data; /**< Received data */
const char *msg; /**< Error or progress message */
} data; /**< Event data */
} llcache_event;
 
/**
* Client callback for low-level cache events
*
* \param handle Handle for which event is issued
* \param event Event data
* \param pw Pointer to client-specific data
* \return NSERROR_OK on success, appropriate error otherwise.
*/
typedef nserror (*llcache_handle_callback)(llcache_handle *handle,
const llcache_event *event, void *pw);
 
/** Flags for low-level cache object retrieval */
enum llcache_retrieve_flag {
/* Note: We're permitted a maximum of 16 flags which must reside in the
* bottom 16 bits of the flags word. See hlcache.h for further details.
*/
/** Force a new fetch */
LLCACHE_RETRIEVE_FORCE_FETCH = (1 << 0),
/** Requested URL was verified */
LLCACHE_RETRIEVE_VERIFIABLE = (1 << 1),
/**< No error pages */
LLCACHE_RETRIEVE_NO_ERROR_PAGES = (1 << 2),
/**< Stream data (implies that object is not cacheable) */
LLCACHE_RETRIEVE_STREAM_DATA = (1 << 3)
};
 
/** Low-level cache query types */
typedef enum {
LLCACHE_QUERY_AUTH, /**< Need authentication details */
LLCACHE_QUERY_REDIRECT, /**< Need permission to redirect */
LLCACHE_QUERY_SSL /**< SSL chain needs inspection */
} llcache_query_type;
 
/** Low-level cache query */
typedef struct {
llcache_query_type type; /**< Type of query */
 
nsurl *url; /**< URL being fetched */
 
union {
struct {
const char *realm; /**< Authentication realm */
} auth;
 
struct {
const char *target; /**< Redirect target */
} redirect;
 
struct {
const struct ssl_cert_info *certs;
size_t num; /**< Number of certs in chain */
} ssl;
} data;
} llcache_query;
 
/**
* Response handler for fetch-related queries
*
* \param proceed Whether to proceed with the fetch or not
* \param cbpw Opaque value provided to llcache_query_callback
* \return NSERROR_OK on success, appropriate error otherwise
*/
typedef nserror (*llcache_query_response)(bool proceed, void *cbpw);
 
/**
* Callback to handle fetch-related queries
*
* \param query Object containing details of query
* \param pw Pointer to callback-specific data
* \param cb Callback that client should call once query is satisfied
* \param cbpw Opaque value to pass into \a cb
* \return NSERROR_OK on success, appropriate error otherwise
*
* \note This callback should return immediately. Once a suitable answer to
* the query has been obtained, the provided response callback should be
* called. This is intended to be an entirely asynchronous process.
*/
typedef nserror (*llcache_query_callback)(const llcache_query *query, void *pw,
llcache_query_response cb, void *cbpw);
 
/**
* Initialise the low-level cache
*
* \param cb Query handler
* \param pw Pointer to query handler data
* \return NSERROR_OK on success, appropriate error otherwise.
*/
nserror llcache_initialise(llcache_query_callback cb, void *pw, uint32_t llcache_limit);
 
/**
* Finalise the low-level cache
*/
void llcache_finalise(void);
 
/**
* Cause the low-level cache to emit any pending notifications.
*
* \return NSERROR_OK on success, appropriate error otherwise.
*/
nserror llcache_poll(void);
 
/**
* Cause the low-level cache to attempt to perform cleanup. No
* guarantees are made as to whether or not cleanups will take
* place and what, if any, space savings will be made.
*/
void llcache_clean(void);
 
/**
* Retrieve a handle for a low-level cache object
*
* \param url URL of the object to fetch
* \param flags Object retrieval flags
* \param referer Referring URL, or NULL if none
* \param post POST data, or NULL for a GET request
* \param cb Client callback for events
* \param pw Pointer to client-specific data
* \param result Pointer to location to recieve cache handle
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror llcache_handle_retrieve(nsurl *url, uint32_t flags,
nsurl *referer, const llcache_post_data *post,
llcache_handle_callback cb, void *pw,
llcache_handle **result);
 
/**
* Change the callback associated with a low-level cache handle
*
* \param handle Handle to change callback of
* \param cb New callback
* \param pw Client data for new callback
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror llcache_handle_change_callback(llcache_handle *handle,
llcache_handle_callback cb, void *pw);
 
/**
* Release a low-level cache handle
*
* \param handle Handle to release
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror llcache_handle_release(llcache_handle *handle);
 
/**
* Clone a low-level cache handle, producing a new handle to
* the same fetch/content.
*
* \param handle Handle to clone
* \param result Pointer to location to receive cloned handle
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror llcache_handle_clone(llcache_handle *handle, llcache_handle **result);
 
/**
* Abort a low-level fetch, informing all users of this action.
*
* \param handle Handle to abort
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror llcache_handle_abort(llcache_handle *handle);
 
/**
* Force a low-level cache handle into streaming mode
*
* \param handle Handle to stream
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror llcache_handle_force_stream(llcache_handle *handle);
 
/**
* Invalidate cache data for a low-level cache object
*
* \param handle Handle to invalidate
* \return NSERROR_OK on success, appropriate error otherwise
*/
nserror llcache_handle_invalidate_cache_data(llcache_handle *handle);
 
/**
* Retrieve the post-redirect URL of a low-level cache object
*
* \param handle Handle to retrieve URL from
* \return Post-redirect URL of cache object
*/
nsurl *llcache_handle_get_url(const llcache_handle *handle);
 
/**
* Retrieve source data of a low-level cache object
*
* \param handle Handle to retrieve source data from
* \param size Pointer to location to receive byte length of data
* \return Pointer to source data
*/
const uint8_t *llcache_handle_get_source_data(const llcache_handle *handle,
size_t *size);
 
/**
* Retrieve a header value associated with a low-level cache object
*
* \param handle Handle to retrieve header from
* \param key Header name
* \return Header value, or NULL if header does not exist
*
* \todo Make the key an enumeration, to avoid needless string comparisons
* \todo Forcing the client to parse the header value seems wrong.
* Better would be to return the actual value part and an array of
* key-value pairs for any additional parameters.
* \todo Deal with multiple headers of the same key (e.g. Set-Cookie)
*/
const char *llcache_handle_get_header(const llcache_handle *handle,
const char *key);
 
/**
* Determine if the same underlying object is referenced by the given handles
*
* \param a First handle
* \param b Second handle
* \return True if handles reference the same object, false otherwise
*/
bool llcache_handle_references_same_object(const llcache_handle *a,
const llcache_handle *b);
 
#endif
/contrib/network/netsurf/netsurf/content/make.content
0,0 → 1,22
CFLAGS += -O2
NETSURF_FB_FRONTEND := sdl
NETSURF_FB_FONTLIB := internal
 
NETSURF_FRAMEBUFFER_BIN := $(PREFIX)/bin/
 
# Default resource install path
NETSURF_FRAMEBUFFER_RESOURCES := $(PREFIX)/share/netsurf/
 
# Default framebuffer search path
NETSURF_FB_RESPATH := $${HOME}/.netsurf/:$${NETSURFRES}:$(NETSURF_FRAMEBUFFER_RESOURCES):./framebuffer/res
 
# freetype compiled in font serch path
NETSURF_FB_FONTPATH := /usr/share/fonts/truetype/ttf-dejavu:/usr/share/fonts/truetype/msttcorefonts
OBJS := content.o content_factory.o dirlist.o fetch.o hlcache.o \
llcache.o mimesniff.o urldb.o
 
 
OUTFILE = cont.o
CFLAGS += -I ../include/ -I ../ -I../../ -I./ -I/home/sourcerer/kos_src/newenginek/kolibri/include
include $(MENUETDEV)/makefiles/Makefile_for_o_lib
/contrib/network/netsurf/netsurf/content/mimesniff.c
0,0 → 1,778
/*
* Copyright 2011 John-Mark Bell <jmb@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* MIME type sniffer (implementation)
*
* Spec version: 2011-11-27
*/
 
#include<string.h>
 
#include "content/content_factory.h"
#include "content/llcache.h"
#include "content/mimesniff.h"
#include "utils/http.h"
#include "utils/utils.h"
 
struct map_s {
const uint8_t *sig;
size_t len;
bool safe;
lwc_string **type;
};
 
static lwc_string *unknown_unknown;
static lwc_string *application_unknown;
static lwc_string *any;
static lwc_string *text_xml;
static lwc_string *application_xml;
static lwc_string *text_html;
static lwc_string *text_plain;
static lwc_string *application_octet_stream;
static lwc_string *image_gif;
static lwc_string *image_png;
static lwc_string *image_jpeg;
static lwc_string *image_bmp;
static lwc_string *image_vnd_microsoft_icon;
static lwc_string *image_webp;
static lwc_string *application_rss_xml;
static lwc_string *application_atom_xml;
static lwc_string *audio_wave;
static lwc_string *application_ogg;
static lwc_string *video_webm;
static lwc_string *application_x_rar_compressed;
static lwc_string *application_zip;
static lwc_string *application_x_gzip;
static lwc_string *application_postscript;
static lwc_string *application_pdf;
static lwc_string *video_mp4;
static lwc_string *image_svg;
 
nserror mimesniff_init(void)
{
lwc_error lerror;
 
#define SINIT(v, s) \
lerror = lwc_intern_string(s, SLEN(s), &v); \
if (lerror != lwc_error_ok) \
return NSERROR_NOMEM
 
SINIT(unknown_unknown, "unknown/unknown");
SINIT(application_unknown, "application/unknown");
SINIT(any, "*/*");
SINIT(text_xml, "text/xml");
SINIT(application_xml, "application/xml");
SINIT(text_html, "text/html");
SINIT(text_plain, "text/plain");
SINIT(application_octet_stream, "application/octet-stream");
SINIT(image_gif, "image/gif");
SINIT(image_png, "image/png");
SINIT(image_jpeg, "image/jpeg");
SINIT(image_bmp, "image/bmp");
SINIT(image_vnd_microsoft_icon, "image/vnd.microsoft.icon");
SINIT(image_webp, "image/webp");
SINIT(application_rss_xml, "application/rss+xml");
SINIT(application_atom_xml, "application/atom+xml");
SINIT(audio_wave, "audio/wave");
SINIT(application_ogg, "application/ogg");
SINIT(video_webm, "video/webm");
SINIT(application_x_rar_compressed, "application/x-rar-compressed");
SINIT(application_zip, "application/zip");
SINIT(application_x_gzip, "application/x-gzip");
SINIT(application_postscript, "application/postscript");
SINIT(application_pdf, "application/pdf");
SINIT(video_mp4, "video/mp4");
SINIT(image_svg, "image/svg+xml");
#undef SINIT
 
return NSERROR_OK;
}
 
void mimesniff_fini(void)
{
lwc_string_unref(image_svg);
lwc_string_unref(video_mp4);
lwc_string_unref(application_pdf);
lwc_string_unref(application_postscript);
lwc_string_unref(application_x_gzip);
lwc_string_unref(application_zip);
lwc_string_unref(application_x_rar_compressed);
lwc_string_unref(video_webm);
lwc_string_unref(application_ogg);
lwc_string_unref(audio_wave);
lwc_string_unref(application_atom_xml);
lwc_string_unref(application_rss_xml);
lwc_string_unref(image_webp);
lwc_string_unref(image_vnd_microsoft_icon);
lwc_string_unref(image_bmp);
lwc_string_unref(image_jpeg);
lwc_string_unref(image_png);
lwc_string_unref(image_gif);
lwc_string_unref(application_octet_stream);
lwc_string_unref(text_plain);
lwc_string_unref(text_html);
lwc_string_unref(application_xml);
lwc_string_unref(text_xml);
lwc_string_unref(any);
lwc_string_unref(application_unknown);
lwc_string_unref(unknown_unknown);
}
 
static bool mimesniff__has_binary_octets(const uint8_t *data, size_t len)
{
const uint8_t *end = data + len;
 
while (data != end) {
const uint8_t c = *data;
 
/* Binary iff in C0 and not ESC, CR, FF, LF, HT */
if (c <= 0x1f && c != 0x1b && c != '\r' && c != '\f' &&
c != '\n' && c != '\t')
break;
 
data++;
}
 
return data != end;
}
 
static nserror mimesniff__match_mp4(const uint8_t *data, size_t len,
lwc_string **effective_type)
{
size_t box_size, i;
 
/* ISO/IEC 14496-12:2008 $4.3 says (effectively):
*
* struct ftyp_box {
* uint32_t size; (in octets, including size+type words)
* uint32_t type; (== 'ftyp')
* uint32_t major_brand;
* uint32_t minor_version;
* uint32_t compatible_brands[];
* }
*
* Note 1: A size of 0 implies that the length of the box is designated
* by the remaining input data (and thus may only occur in the last
* box in the input). We'll reject this below, as it's pointless
* sniffing input that contains no boxes other than 'ftyp'.
*
* Note 2: A size of 1 implies an additional uint64_t field after
* the type which contains the extended box size. We'll reject this,
* too, as it implies a minimum of (2^32 - 24) / 4 compatible brands,
* which is decidely unlikely.
*/
 
/* 12 reflects the minimum number of octets needed to sniff useful
* information out of an 'ftyp' box (i.e. the size, type,
* and major_brand words). */
if (len < 12)
return NSERROR_NOT_FOUND;
 
/* Box size is big-endian */
box_size = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
 
/* Require that we can read the entire box, and reject bad box sizes */
if (len < box_size || box_size % 4 != 0)
return NSERROR_NOT_FOUND;
 
/* Ensure this is an 'ftyp' box */
if (data[4] != 'f' || data[5] != 't' ||
data[6] != 'y' || data[7] != 'p')
return NSERROR_NOT_FOUND;
 
/* Check if major brand begins with 'mp4' */
if (data[8] == 'm' && data[9] == 'p' && data[10] == '4') {
*effective_type = lwc_string_ref(video_mp4);
return NSERROR_OK;
}
 
/* Search each compatible brand in the box for "mp4" */
for (i = 16; i <= box_size - 4; i += 4) {
if (data[i] == 'm' && data[i+1] == 'p' && data[i+2] == '4') {
*effective_type = lwc_string_ref(video_mp4);
return NSERROR_OK;
}
}
 
return NSERROR_NOT_FOUND;
}
 
static nserror mimesniff__match_unknown_ws(const uint8_t *data, size_t len,
lwc_string **effective_type)
{
#define SIG(t, s, x) { (const uint8_t *) s, SLEN(s), x, t }
static const struct map_s ws_exact_match_types[] = {
SIG(&text_xml, "<?xml", false),
{ NULL, 0, false, NULL }
};
 
static const struct map_s ws_inexact_match_types[] = {
SIG(&text_html, "<!DOCTYPE HTML", false),
SIG(&text_html, "<HTML", false),
SIG(&text_html, "<HEAD", false),
SIG(&text_html, "<SCRIPT", false),
SIG(&text_html, "<IFRAME", false),
SIG(&text_html, "<H1", false),
SIG(&text_html, "<DIV", false),
SIG(&text_html, "<FONT", false),
SIG(&text_html, "<TABLE", false),
SIG(&text_html, "<A", false),
SIG(&text_html, "<STYLE", false),
SIG(&text_html, "<TITLE", false),
SIG(&text_html, "<B", false),
SIG(&text_html, "<BODY", false),
SIG(&text_html, "<BR", false),
SIG(&text_html, "<P", false),
SIG(&text_html, "<!--", false),
{ NULL, 0, false, NULL }
};
#undef SIG
const uint8_t *end = data + len;
const struct map_s *it;
 
/* Skip leading whitespace */
while (data != end) {
const uint8_t c = *data;
 
if (c != '\t' && c != '\n' && c != '\f' &&
c != '\r' && c != ' ')
break;
 
data++;
}
 
if (data == end)
return NSERROR_NOT_FOUND;
 
len = end - data;
 
for (it = ws_exact_match_types; it->sig != NULL; it++) {
if (it->len <= len && memcmp(data, it->sig, it->len) == 0) {
*effective_type = lwc_string_ref(*it->type);
return NSERROR_OK;
}
}
 
for (it = ws_inexact_match_types; it->sig != NULL; it++) {
/* +1 for trailing space or > */
if (len < it->len + 1)
continue;
 
if (strncasecmp((const char *) data,
(const char *) it->sig, it->len) == 0 &&
(data[it->len] == ' ' ||
data[it->len] == '>')) {
*effective_type = lwc_string_ref(*it->type);
return NSERROR_OK;
}
}
 
return NSERROR_NOT_FOUND;
}
 
static nserror mimesniff__match_unknown_bom(const uint8_t *data, size_t len,
lwc_string **effective_type)
{
#define SIG(t, s, x) { (const uint8_t *) s, SLEN(s), x, t }
static const struct map_s bom_match_types[] = {
SIG(&text_plain, "\xfe\xff", false),
SIG(&text_plain, "\xff\xfe", false),
SIG(&text_plain, "\xef\xbb\xbf", false),
{ NULL, 0, false, NULL }
};
#undef SIG
const struct map_s *it;
 
for (it = bom_match_types; it->sig != NULL; it++) {
if (it->len <= len && memcmp(data, it->sig, it->len) == 0) {
*effective_type = lwc_string_ref(*it->type);
return NSERROR_OK;
}
}
 
return NSERROR_NOT_FOUND;
}
 
static nserror mimesniff__match_unknown_riff(const uint8_t *data, size_t len,
lwc_string **effective_type)
{
#define SIG(t, s, x) { (const uint8_t *) s, SLEN(s), x, t }
static const struct map_s riff_match_types[] = {
SIG(&image_webp, "WEBPVP", true),
SIG(&audio_wave, "WAVE", true),
{ NULL, 0, false, NULL }
};
#undef SIG
const struct map_s *it;
 
for (it = riff_match_types; it->sig != NULL; it++) {
if (it->len + SLEN("RIFF????") <= len &&
memcmp(data, "RIFF", SLEN("RIFF")) == 0 &&
memcmp(data + SLEN("RIFF????"),
it->sig, it->len) == 0) {
*effective_type = lwc_string_ref(*it->type);
return NSERROR_OK;
}
}
 
return NSERROR_NOT_FOUND;
}
 
static nserror mimesniff__match_unknown_exact(const uint8_t *data, size_t len,
bool allow_unsafe, lwc_string **effective_type)
{
#define SIG(t, s, x) { (const uint8_t *) s, SLEN(s), x, t }
static const struct map_s exact_match_types[] = {
SIG(&image_gif, "GIF87a", true),
SIG(&image_gif, "GIF89a", true),
SIG(&image_png, "\x89PNG\r\n\x1a\n", true),
SIG(&image_jpeg, "\xff\xd8\xff", true),
SIG(&image_bmp, "BM", true),
SIG(&image_vnd_microsoft_icon, "\x00\x00\x01\x00", true),
SIG(&application_ogg, "OggS\x00", true),
SIG(&video_webm, "\x1a\x45\xdf\xa3", true),
SIG(&application_x_rar_compressed, "Rar \x1a\x07\x00", true),
SIG(&application_zip, "PK\x03\x04", true),
SIG(&application_x_gzip, "\x1f\x8b\x08", true),
SIG(&application_postscript, "%!PS-Adobe-", true),
SIG(&application_pdf, "%PDF-", false),
{ NULL, 0, false, NULL }
};
#undef SIG
const struct map_s *it;
 
for (it = exact_match_types; it->sig != NULL; it++) {
if (it->len <= len && memcmp(data, it->sig, it->len) == 0 &&
(allow_unsafe || it->safe)) {
*effective_type = lwc_string_ref(*it->type);
return NSERROR_OK;
}
}
 
return NSERROR_NOT_FOUND;
}
 
static nserror mimesniff__match_unknown(const uint8_t *data, size_t len,
bool allow_unsafe, lwc_string **effective_type)
{
if (mimesniff__match_unknown_exact(data, len, allow_unsafe,
effective_type) == NSERROR_OK)
return NSERROR_OK;
 
if (mimesniff__match_unknown_riff(data, len,
effective_type) == NSERROR_OK)
return NSERROR_OK;
 
if (allow_unsafe == false)
return NSERROR_NOT_FOUND;
 
if (mimesniff__match_unknown_bom(data, len,
effective_type) == NSERROR_OK)
return NSERROR_OK;
 
if (mimesniff__match_unknown_ws(data, len,
effective_type) == NSERROR_OK)
return NSERROR_OK;
 
if (mimesniff__match_mp4(data, len, effective_type) == NSERROR_OK)
return NSERROR_OK;
 
return NSERROR_NOT_FOUND;
}
 
static nserror mimesniff__compute_unknown(const uint8_t *data, size_t len,
lwc_string **effective_type)
{
if (data == NULL)
return NSERROR_NEED_DATA;
 
len = min(len, 512);
 
if (mimesniff__match_unknown(data, len, true,
effective_type) == NSERROR_OK)
return NSERROR_OK;
 
if (mimesniff__has_binary_octets(data, len) == false) {
/* No binary octets => text/plain */
*effective_type = lwc_string_ref(text_plain);
return NSERROR_OK;
}
 
*effective_type = lwc_string_ref(application_octet_stream);
 
return NSERROR_OK;
}
 
static nserror mimesniff__compute_text_or_binary(const uint8_t *data,
size_t len, lwc_string **effective_type)
{
if (data == NULL)
return NSERROR_NEED_DATA;
 
len = min(len, 512);
 
if (len >= 3 && ((data[0] == 0xfe && data[1] == 0xff) ||
(data[0] == 0xff && data[1] == 0xfe) ||
(data[0] == 0xef && data[1] == 0xbb &&
data[2] == 0xbf))) {
/* Found a BOM => text/plain */
*effective_type = lwc_string_ref(text_plain);
return NSERROR_OK;
}
 
if (mimesniff__has_binary_octets(data, len) == false) {
/* No binary octets => text/plain */
*effective_type = lwc_string_ref(text_plain);
return NSERROR_OK;
}
 
if (mimesniff__match_unknown(data, len, false,
effective_type) == NSERROR_OK)
return NSERROR_OK;
 
*effective_type = lwc_string_ref(application_octet_stream);
 
return NSERROR_OK;
}
 
static nserror mimesniff__compute_image(lwc_string *official_type,
const uint8_t *data, size_t len, lwc_string **effective_type)
{
#define SIG(t, s) { (const uint8_t *) s, SLEN(s), t }
static const struct it_s {
const uint8_t *sig;
size_t len;
lwc_string **type;
} image_types[] = {
SIG(&image_gif, "GIF87a"),
SIG(&image_gif, "GIF89a"),
SIG(&image_png, "\x89PNG\r\n\x1a\n"),
SIG(&image_jpeg, "\xff\xd8\xff"),
SIG(&image_bmp, "BM"),
SIG(&image_vnd_microsoft_icon, "\x00\x00\x01\x00"),
{ NULL, 0, NULL }
};
#undef SIG
 
const struct it_s *it;
 
if (data == NULL) {
lwc_string_unref(official_type);
return NSERROR_NEED_DATA;
}
 
for (it = image_types; it->sig != NULL; it++) {
if (it->len <= len && memcmp(data, it->sig, it->len) == 0) {
lwc_string_unref(official_type);
*effective_type = lwc_string_ref(*it->type);
return NSERROR_OK;
}
}
 
/* WebP has a signature that doesn't fit into the above table */
if (SLEN("RIFF????WEBPVP") <= len &&
memcmp(data, "RIFF", SLEN("RIFF")) == 0 &&
memcmp(data + SLEN("RIFF????"),
"WEBPVP", SLEN("WEBPVP")) == 0 ) {
lwc_string_unref(official_type);
*effective_type = lwc_string_ref(image_webp);
return NSERROR_OK;
}
 
*effective_type = official_type;
 
return NSERROR_OK;
}
 
static nserror mimesniff__compute_feed_or_html(const uint8_t *data,
size_t len, lwc_string **effective_type)
{
#define RDF_NS "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
#define RSS_NS "http://purl.org/rss/1.0"
 
enum state_e {
BEFORE_BOM,
BEFORE_MARKUP,
MARKUP_START,
COMMENT_OR_DOCTYPE,
IN_COMMENT,
IN_DOCTYPE,
IN_PI,
IN_TAG,
IN_RDF
} state = BEFORE_BOM;
 
bool rdf = false, rss = false;
const uint8_t *end;
 
if (data == NULL)
return NSERROR_NEED_DATA;
 
end = data + min(len, 512);
 
while (data < end) {
const uint8_t c = *data;
 
#define MATCH(s) SLEN(s) <= (size_t) (end - data) && \
memcmp(data, s, SLEN(s)) == 0
 
switch (state) {
case BEFORE_BOM:
if (3 <= end - data && c == 0xef && data[1] == 0xbb &&
data[2] == 0xbf) {
data += 3;
}
 
state = BEFORE_MARKUP;
break;
case BEFORE_MARKUP:
if (c == '\t' || c == '\n' || c == '\r' || c == ' ')
data++;
else if (c != '<')
data = end;
else {
state = MARKUP_START;
data++;
}
break;
case MARKUP_START:
if (c == '!') {
state = COMMENT_OR_DOCTYPE;
data++;
} else if (c == '?') {
state = IN_PI;
data++;
} else {
/* Reconsume input */
state = IN_TAG;
}
break;
case COMMENT_OR_DOCTYPE:
if (2 <= end - data && c == '-' && data[1] == '-') {
state = IN_COMMENT;
data += 2;
} else {
/* Reconsume input */
state = IN_DOCTYPE;
}
break;
case IN_COMMENT:
if (3 <= end - data && c == '-' && data[1] == '-' &&
data[2] == '>') {
state = BEFORE_MARKUP;
data += 3;
} else
data++;
break;
case IN_DOCTYPE:
if (c == '>')
state = BEFORE_MARKUP;
data++;
break;
case IN_PI:
if (2 <= end - data && c == '?' && data[1] == '>') {
state = BEFORE_MARKUP;
data += 2;
} else
data++;
break;
case IN_TAG:
if (MATCH("rss")) {
*effective_type =
lwc_string_ref(application_rss_xml);
return NSERROR_OK;
} else if (MATCH("feed")) {
*effective_type =
lwc_string_ref(application_atom_xml);
return NSERROR_OK;
} else if (MATCH("rdf:RDF")) {
state = IN_RDF;
data += SLEN("rdf:RDF");
} else
data = end;
break;
case IN_RDF:
if (MATCH(RSS_NS)) {
rss = true;
data += SLEN(RSS_NS);
} else if (MATCH(RDF_NS)) {
rdf = true;
data += SLEN(RDF_NS);
} else
data++;
 
if (rdf && rss) {
*effective_type =
lwc_string_ref(application_rss_xml);
return NSERROR_OK;
}
 
break;
}
#undef MATCH
}
 
*effective_type = lwc_string_ref(text_html);
 
return NSERROR_OK;
 
#undef RSS_NS
#undef RDF_NS
}
 
/* See mimesniff.h for documentation */
nserror mimesniff_compute_effective_type(llcache_handle *handle,
const uint8_t *data, size_t len, bool sniff_allowed,
bool image_only, lwc_string **effective_type)
{
#define S(s) { s, SLEN(s) }
static const struct tt_s {
const char *data;
size_t len;
} text_types[] = {
S("text/plain"),
S("text/plain; charset=ISO-8859-1"),
S("text/plain; charset=iso-8859-1"),
S("text/plain; charset=UTF-8"),
{ NULL, 0 }
};
#undef S
 
const char *content_type_header;
size_t content_type_header_len;
http_content_type *ct;
const struct tt_s *tt;
bool match;
nserror error;
 
content_type_header =
llcache_handle_get_header(handle, "Content-Type");
if (content_type_header == NULL) {
if (sniff_allowed == false)
return NSERROR_NOT_FOUND;
 
/* No official type => unknown */
return mimesniff__compute_unknown(data, len, effective_type);
}
 
error = http_parse_content_type(content_type_header, &ct);
if (error != NSERROR_OK) {
if (sniff_allowed == false)
return NSERROR_NOT_FOUND;
 
/* Unparseable => unknown */
return mimesniff__compute_unknown(data, len, effective_type);
}
 
if (sniff_allowed == false) {
*effective_type = lwc_string_ref(ct->media_type);
http_content_type_destroy(ct);
return NSERROR_OK;
}
 
if (image_only) {
lwc_string *official_type;
 
if (lwc_string_caseless_isequal(ct->media_type, image_svg,
&match) == lwc_error_ok && match) {
*effective_type = lwc_string_ref(image_svg);
http_content_type_destroy(ct);
return NSERROR_OK;
}
 
official_type = lwc_string_ref(ct->media_type);
http_content_type_destroy(ct);
return mimesniff__compute_image(official_type,
data, len, effective_type);
}
 
content_type_header_len = strlen(content_type_header);
 
/* Look for text types */
for (tt = text_types; tt->data != NULL; tt++) {
if (tt->len == content_type_header_len &&
memcmp(tt->data, content_type_header,
content_type_header_len) == 0) {
http_content_type_destroy(ct);
return mimesniff__compute_text_or_binary(data, len,
effective_type);
}
}
 
/* unknown/unknown, application/unknown, * / * */
if ((lwc_string_caseless_isequal(ct->media_type, unknown_unknown,
&match) == lwc_error_ok && match) ||
(lwc_string_caseless_isequal(ct->media_type,
application_unknown, &match) == lwc_error_ok &&
match) ||
(lwc_string_caseless_isequal(ct->media_type, any,
&match) == lwc_error_ok && match)) {
http_content_type_destroy(ct);
return mimesniff__compute_unknown(data, len, effective_type);
}
 
/* +xml */
if (lwc_string_length(ct->media_type) > SLEN("+xml") &&
strncasecmp(lwc_string_data(ct->media_type) +
lwc_string_length(ct->media_type) -
SLEN("+xml"),
"+xml", SLEN("+xml")) == 0) {
/* Use official type */
*effective_type = lwc_string_ref(ct->media_type);
http_content_type_destroy(ct);
return NSERROR_OK;
}
 
/* text/xml, application/xml */
if ((lwc_string_caseless_isequal(ct->media_type, text_xml,
&match) == lwc_error_ok && match) ||
(lwc_string_caseless_isequal(ct->media_type,
application_xml, &match) == lwc_error_ok &&
match)) {
/* Use official type */
*effective_type = lwc_string_ref(ct->media_type);
http_content_type_destroy(ct);
return NSERROR_OK;
}
/* Image types */
if (content_factory_type_from_mime_type(ct->media_type) ==
CONTENT_IMAGE) {
lwc_string *official_type = lwc_string_ref(ct->media_type);
http_content_type_destroy(ct);
return mimesniff__compute_image(official_type,
data, len, effective_type);
}
 
/* text/html */
if ((lwc_string_caseless_isequal(ct->media_type, text_html,
&match) == lwc_error_ok && match)) {
http_content_type_destroy(ct);
return mimesniff__compute_feed_or_html(data, len,
effective_type);
}
 
/* Use official type */
*effective_type = lwc_string_ref(ct->media_type);
 
http_content_type_destroy(ct);
 
return NSERROR_OK;
}
 
/contrib/network/netsurf/netsurf/content/mimesniff.h
0,0 → 1,55
/*
* Copyright 2011 John-Mark Bell <jmb@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* MIME type sniffer (interface)
*/
 
#ifndef NETSURF_CONTENT_MIMESNIFF_H_
#define NETSURF_CONTENT_MIMESNIFF_H_
 
#include <stdbool.h>
 
#include <libwapcaplet/libwapcaplet.h>
#include "utils/errors.h"
 
struct llcache_handle;
 
/**
* Compute the effective MIME type for an object using the sniffing
* algorithm described in http://mimesniff.spec.whatwg.org/
*
* \param handle Source data handle to sniff
* \param data First data chunk, or NULL
* \param len Length of \a data, in bytes
* \param sniff_allowed Whether MIME type sniffing is allowed
* \param image_only Sniff image types only
* \param effective_type Location to receive computed type
* \return NSERROR_OK on success,
* NSERROR_NEED_DATA iff \a data is NULL and data is needed
* NSERROR_NOT_FOUND if sniffing is prohibited and no
* Content-Type header was found
*/
nserror mimesniff_compute_effective_type(struct llcache_handle *handle,
const uint8_t *data, size_t len, bool sniff_allowed,
bool image_only, lwc_string **effective_type);
 
nserror mimesniff_init(void);
void mimesniff_fini(void);
 
#endif
/contrib/network/netsurf/netsurf/content/urldb.c
0,0 → 1,4107
/*
* Copyright 2006 John M Bell <jmb202@ecs.soton.ac.uk>
* Copyright 2009 John Tytgat <joty@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Unified URL information database (implementation)
*
* URLs are stored in a tree-based structure as follows:
*
* The host component is extracted from each URL and, if a FQDN, split on
* every '.'.The tree is constructed by inserting each FQDN segment in
* reverse order. Duplicate nodes are merged.
*
* If the host part of an URL is an IP address, then this is added to the
* tree verbatim (as if it were a TLD).
*
* This provides something looking like:
*
* root (a sentinel)
* |
* -------------------------------------------------
* | | | | | | |
* com edu gov 127.0.0.1 net org uk TLDs
* | | | | | |
* google ... ... ... ... co 2LDs
* | |
* www bbc Hosts/Subdomains
* |
* www ...
*
* Each of the nodes in this tree is a struct host_part. This stores the
* FQDN segment (or IP address) with which the node is concerned. Each node
* may contain further information about paths on a host (struct path_data)
* or SSL certificate processing on a host-wide basis
* (host_part::permit_invalid_certs).
*
* Path data is concerned with storing various metadata about the path in
* question. This includes global history data, HTTP authentication details
* and any associated HTTP cookies. This is stored as a tree of path segments
* hanging off the relevant host_part node.
*
* Therefore, to find the last visited time of the URL
* http://www.example.com/path/to/resource.html, the FQDN tree would be
* traversed in the order root -> "com" -> "example" -> "www". The "www"
* node would have attached to it a tree of struct path_data:
*
* (sentinel)
* |
* path
* |
* to
* |
* resource.html
*
* This represents the absolute path "/path/to/resource.html". The leaf node
* "resource.html" contains the last visited time of the resource.
*
* The mechanism described above is, however, not particularly conducive to
* fast searching of the database for a given URL (or URLs beginning with a
* given prefix). Therefore, an anciliary data structure is used to enable
* fast searching. This structure simply reflects the contents of the
* database, with entries being added/removed at the same time as for the
* core database. In order to ensure that degenerate cases are kept to a
* minimum, we use an AAtree. This is an approximation of a Red-Black tree
* with similar performance characteristics, but with a significantly
* simpler implementation. Entries in this tree comprise pointers to the
* leaf nodes of the host tree described above.
*
* REALLY IMPORTANT NOTE: urldb expects all URLs to be normalised. Use of
* non-normalised URLs with urldb will result in undefined behaviour and
* potential crashes.
*/
 
#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
 
#include <curl/curl.h>
 
#include "image/bitmap.h"
#include "content/content.h"
#include "content/urldb.h"
#include "desktop/cookies.h"
#include "desktop/options.h"
#include "utils/log.h"
#include "utils/corestrings.h"
#include "utils/filename.h"
#include "utils/url.h"
#include "utils/utils.h"
 
struct cookie_internal_data {
char *name; /**< Cookie name */
char *value; /**< Cookie value */
bool value_was_quoted; /**< Value was quoted in Set-Cookie: */
char *comment; /**< Cookie comment */
bool domain_from_set; /**< Domain came from Set-Cookie: header */
char *domain; /**< Domain */
bool path_from_set; /**< Path came from Set-Cookie: header */
char *path; /**< Path */
time_t expires; /**< Expiry timestamp, or -1 for session */
time_t last_used; /**< Last used time */
bool secure; /**< Only send for HTTPS requests */
bool http_only; /**< Only expose to HTTP(S) requests */
cookie_version version; /**< Specification compliance */
bool no_destroy; /**< Never destroy this cookie,
* unless it's expired */
 
struct cookie_internal_data *prev; /**< Previous in list */
struct cookie_internal_data *next; /**< Next in list */
};
 
/* A protection space is defined as a tuple canonical_root_url and realm.
* This structure lives as linked list element in a leaf host_part struct
* so we need additional scheme and port to have a canonical_root_url. */
struct prot_space_data {
lwc_string *scheme; /**< URL scheme of canonical hostname of this
* protection space. */
unsigned int port; /**< Port number of canonical hostname of this
* protection space. When 0, it means the
* default port for given scheme, i.e. 80
* (http), 443 (https). */
char *realm; /**< Protection realm */
 
char *auth; /**< Authentication details for this
* protection space in form
* username:password */
struct prot_space_data *next; /**< Next sibling */
};
 
struct cache_internal_data {
char filename[12]; /**< Cached filename, or first byte 0 for none */
};
 
struct url_internal_data {
char *title; /**< Resource title */
unsigned int visits; /**< Visit count */
time_t last_visit; /**< Last visit time */
content_type type; /**< Type of resource */
};
 
struct path_data {
nsurl *url; /**< Full URL */
lwc_string *scheme; /**< URL scheme for data */
unsigned int port; /**< Port number for data. When 0, it means
* the default port for given scheme, i.e.
* 80 (http), 443 (https). */
char *segment; /**< Path segment for this node */
unsigned int frag_cnt; /**< Number of entries in path_data::fragment */
char **fragment; /**< Array of fragments */
bool persistent; /**< This entry should persist */
 
struct bitmap *thumb; /**< Thumbnail image of resource */
struct url_internal_data urld; /**< URL data for resource */
struct cache_internal_data cache; /**< Cache data for resource */
const struct prot_space_data *prot_space; /**< Protection space
* to which this resource belongs too. Can be
* NULL when it does not belong to a protection
* space or when it is not known. No
* ownership (is with struct host_part::prot_space). */
struct cookie_internal_data *cookies; /**< Cookies associated with resource */
struct cookie_internal_data *cookies_end; /**< Last cookie in list */
 
struct path_data *next; /**< Next sibling */
struct path_data *prev; /**< Previous sibling */
struct path_data *parent; /**< Parent path segment */
struct path_data *children; /**< Child path segments */
struct path_data *last; /**< Last child */
};
 
struct host_part {
/**< Known paths on this host. This _must_ be first so that
* struct host_part *h = (struct host_part *)mypath; works */
struct path_data paths;
bool permit_invalid_certs; /**< Allow access to SSL protected
* resources on this host without
* verifying certificate authenticity
*/
 
char *part; /**< Part of host string */
 
struct prot_space_data *prot_space; /**< Linked list of all known
* proctection spaces known for his host and
* all its schems and ports. */
 
struct host_part *next; /**< Next sibling */
struct host_part *prev; /**< Previous sibling */
struct host_part *parent; /**< Parent host part */
struct host_part *children; /**< Child host parts */
};
 
struct search_node {
const struct host_part *data; /**< Host tree entry */
 
unsigned int level; /**< Node level */
 
struct search_node *left; /**< Left subtree */
struct search_node *right; /**< Right subtree */
};
 
/* Destruction */
static void urldb_destroy_host_tree(struct host_part *root);
static void urldb_destroy_path_tree(struct path_data *root);
static void urldb_destroy_path_node_content(struct path_data *node);
static void urldb_destroy_cookie(struct cookie_internal_data *c);
static void urldb_destroy_prot_space(struct prot_space_data *space);
static void urldb_destroy_search_tree(struct search_node *root);
 
/* Saving */
static void urldb_save_search_tree(struct search_node *root, FILE *fp);
static void urldb_count_urls(const struct path_data *root, time_t expiry,
unsigned int *count);
static void urldb_write_paths(const struct path_data *parent,
const char *host, FILE *fp, char **path, int *path_alloc,
int *path_used, time_t expiry);
 
/* Iteration */
static bool urldb_iterate_partial_host(struct search_node *root,
const char *prefix, bool (*callback)(nsurl *url,
const struct url_data *data));
static bool urldb_iterate_partial_path(const struct path_data *parent,
const char *prefix, bool (*callback)(nsurl *url,
const struct url_data *data));
static bool urldb_iterate_entries_host(struct search_node *parent,
bool (*url_callback)(nsurl *url,
const struct url_data *data),
bool (*cookie_callback)(const struct cookie_data *data));
static bool urldb_iterate_entries_path(const struct path_data *parent,
bool (*url_callback)(nsurl *url,
const struct url_data *data),
bool (*cookie_callback)(const struct cookie_data *data));
 
/* Insertion */
static struct host_part *urldb_add_host_node(const char *part,
struct host_part *parent);
static struct path_data *urldb_add_path_node(lwc_string *scheme,
unsigned int port, const char *segment, lwc_string *fragment,
struct path_data *parent);
static int urldb_add_path_fragment_cmp(const void *a, const void *b);
static struct path_data *urldb_add_path_fragment(struct path_data *segment,
lwc_string *fragment);
 
/* Lookup */
static struct path_data *urldb_find_url(nsurl *url);
static struct path_data *urldb_match_path(const struct path_data *parent,
const char *path, lwc_string *scheme, unsigned short port);
static struct search_node **urldb_get_search_tree_direct(const char *host);
static struct search_node *urldb_get_search_tree(const char *host);
 
/* Dump */
static void urldb_dump_hosts(struct host_part *parent);
static void urldb_dump_paths(struct path_data *parent);
static void urldb_dump_search(struct search_node *parent, int depth);
 
/* Search tree */
static struct search_node *urldb_search_insert(struct search_node *root,
const struct host_part *data);
static struct search_node *urldb_search_insert_internal(
struct search_node *root, struct search_node *n);
/* for urldb_search_remove, see r5531 which removed it */
static const struct host_part *urldb_search_find(struct search_node *root,
const char *host);
static struct search_node *urldb_search_skew(struct search_node *root);
static struct search_node *urldb_search_split(struct search_node *root);
static int urldb_search_match_host(const struct host_part *a,
const struct host_part *b);
static int urldb_search_match_string(const struct host_part *a,
const char *b);
static int urldb_search_match_prefix(const struct host_part *a,
const char *b);
 
/* Cookies */
static struct cookie_internal_data *urldb_parse_cookie(nsurl *url,
const char **cookie);
static bool urldb_parse_avpair(struct cookie_internal_data *c, char *n,
char *v, bool was_quoted);
static bool urldb_insert_cookie(struct cookie_internal_data *c,
lwc_string *scheme, nsurl *url);
static void urldb_free_cookie(struct cookie_internal_data *c);
static bool urldb_concat_cookie(struct cookie_internal_data *c, int version,
int *used, int *alloc, char **buf);
static void urldb_delete_cookie_hosts(const char *domain, const char *path,
const char *name, struct host_part *parent);
static void urldb_delete_cookie_paths(const char *domain, const char *path,
const char *name, struct path_data *parent);
static void urldb_save_cookie_hosts(FILE *fp, struct host_part *parent);
static void urldb_save_cookie_paths(FILE *fp, struct path_data *parent);
 
/** Root database handle */
static struct host_part db_root;
 
/** Search trees - one per letter + 1 for IPs + 1 for Everything Else */
#define NUM_SEARCH_TREES 28
#define ST_IP 0
#define ST_EE 1
#define ST_DN 2
static struct search_node empty = { 0, 0, &empty, &empty };
static struct search_node *search_trees[NUM_SEARCH_TREES] = {
&empty, &empty, &empty, &empty, &empty, &empty, &empty, &empty,
&empty, &empty, &empty, &empty, &empty, &empty, &empty, &empty,
&empty, &empty, &empty, &empty, &empty, &empty, &empty, &empty,
&empty, &empty, &empty, &empty
};
 
#define MIN_COOKIE_FILE_VERSION 100
#define COOKIE_FILE_VERSION 102
static int loaded_cookie_file_version;
#define MIN_URL_FILE_VERSION 106
#define URL_FILE_VERSION 106
 
/**
* Import an URL database from file, replacing any existing database
*
* \param filename Name of file containing data
*/
void urldb_load(const char *filename)
{
#define MAXIMUM_URL_LENGTH 4096
char s[MAXIMUM_URL_LENGTH];
char host[256];
struct host_part *h;
int urls;
int i;
int version;
int length;
FILE *fp;
 
assert(filename);
 
LOG(("Loading URL file"));
 
fp = fopen(filename, "r");
if (!fp) {
LOG(("Failed to open file '%s' for reading", filename));
return;
}
 
if (!fgets(s, MAXIMUM_URL_LENGTH, fp)) {
fclose(fp);
return;
}
 
version = atoi(s);
if (version < MIN_URL_FILE_VERSION) {
LOG(("Unsupported URL file version."));
fclose(fp);
return;
}
if (version > URL_FILE_VERSION) {
LOG(("Unknown URL file version."));
fclose(fp);
return;
}
 
while (fgets(host, sizeof host, fp)) {
/* get the hostname */
length = strlen(host) - 1;
host[length] = '\0';
 
/* skip data that has ended up with a host of '' */
if (length == 0) {
if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
break;
urls = atoi(s);
/* Eight fields/url */
for (i = 0; i < (8 * urls); i++) {
if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
break;
}
continue;
}
 
/* read number of URLs */
if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
break;
urls = atoi(s);
 
/* no URLs => try next host */
if (urls == 0) {
LOG(("No URLs for '%s'", host));
continue;
}
 
h = urldb_add_host(host);
if (!h) {
LOG(("Failed adding host: '%s'", host));
die("Memory exhausted whilst loading URL file");
}
 
/* load the non-corrupt data */
for (i = 0; i < urls; i++) {
struct path_data *p = NULL;
char scheme[64], ports[10];
char url[64 + 3 + 256 + 6 + 4096 + 1];
unsigned int port;
bool is_file = false;
nsurl *nsurl;
lwc_string *scheme_lwc, *fragment_lwc;
char *path_query;
size_t len;
 
if (!fgets(scheme, sizeof scheme, fp))
break;
length = strlen(scheme) - 1;
scheme[length] = '\0';
 
if (!fgets(ports, sizeof ports, fp))
break;
length = strlen(ports) - 1;
ports[length] = '\0';
port = atoi(ports);
 
if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
break;
length = strlen(s) - 1;
s[length] = '\0';
 
if (!strcasecmp(host, "localhost") &&
!strcasecmp(scheme, "file"))
is_file = true;
 
snprintf(url, sizeof url, "%s://%s%s%s%s",
scheme,
/* file URLs have no host */
(is_file ? "" : host),
(port ? ":" : ""),
(port ? ports : ""),
s);
 
/* TODO: store URLs in pre-parsed state, and make
* a nsurl_load to generate the nsurl more
* swiftly.
* Need a nsurl_save too.
*/
if (nsurl_create(url, &nsurl) != NSERROR_OK) {
LOG(("Failed inserting '%s'", url));
die("Memory exhausted whilst loading "
"URL file");
}
 
/* Copy and merge path/query strings */
if (nsurl_get(nsurl, NSURL_PATH | NSURL_QUERY,
&path_query, &len) != NSERROR_OK) {
LOG(("Failed inserting '%s'", url));
die("Memory exhausted whilst loading "
"URL file");
}
 
scheme_lwc = nsurl_get_component(nsurl, NSURL_SCHEME);
fragment_lwc = nsurl_get_component(nsurl,
NSURL_FRAGMENT);
p = urldb_add_path(scheme_lwc, port, h, path_query,
fragment_lwc, nsurl);
if (!p) {
LOG(("Failed inserting '%s'", url));
die("Memory exhausted whilst loading "
"URL file");
}
nsurl_unref(nsurl);
lwc_string_unref(scheme_lwc);
if (fragment_lwc != NULL)
lwc_string_unref(fragment_lwc);
 
if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
break;
if (p)
p->urld.visits = (unsigned int)atoi(s);
 
if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
break;
if (p)
p->urld.last_visit = (time_t)atoi(s);
 
if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
break;
if (p)
p->urld.type = (content_type)atoi(s);
 
if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
break;
 
 
if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
break;
length = strlen(s) - 1;
if (p && length > 0) {
s[length] = '\0';
p->urld.title = malloc(length + 1);
if (p->urld.title)
memcpy(p->urld.title, s, length + 1);
}
}
}
 
fclose(fp);
LOG(("Successfully loaded URL file"));
#undef MAXIMUM_URL_LENGTH
}
 
/**
* Export the current database to file
*
* \param filename Name of file to export to
*/
void urldb_save(const char *filename)
{
FILE *fp;
int i;
 
assert(filename);
 
fp = fopen(filename, "w");
if (!fp) {
LOG(("Failed to open file '%s' for writing", filename));
return;
}
 
/* file format version number */
fprintf(fp, "%d\n", URL_FILE_VERSION);
 
for (i = 0; i != NUM_SEARCH_TREES; i++) {
urldb_save_search_tree(search_trees[i], fp);
}
 
fclose(fp);
}
 
/**
* Save a search (sub)tree
*
* \param root Root of (sub)tree to save
* \param fp File to write to
*/
void urldb_save_search_tree(struct search_node *parent, FILE *fp)
{
char host[256];
const struct host_part *h;
unsigned int path_count = 0;
char *path, *p, *end;
int path_alloc = 64, path_used = 1;
time_t expiry;
 
expiry = time(NULL) - ((60 * 60 * 24) * nsoption_int(expire_url));
 
if (parent == &empty)
return;
 
urldb_save_search_tree(parent->left, fp);
 
path = malloc(path_alloc);
if (!path)
return;
 
path[0] = '\0';
 
for (h = parent->data, p = host, end = host + sizeof host;
h && h != &db_root && p < end; h = h->parent) {
int written = snprintf(p, end - p, "%s%s", h->part,
(h->parent && h->parent->parent) ? "." : "");
if (written < 0) {
free(path);
return;
}
p += written;
}
 
urldb_count_urls(&parent->data->paths, expiry, &path_count);
 
if (path_count > 0) {
fprintf(fp, "%s\n%i\n", host, path_count);
 
urldb_write_paths(&parent->data->paths, host, fp,
&path, &path_alloc, &path_used, expiry);
}
 
free(path);
 
urldb_save_search_tree(parent->right, fp);
}
 
/**
* Count number of URLs associated with a host
*
* \param root Root of path data tree
* \param expiry Expiry time for URLs
* \param count Pointer to count
*/
void urldb_count_urls(const struct path_data *root, time_t expiry,
unsigned int *count)
{
const struct path_data *p = root;
 
do {
if (p->children != NULL) {
/* Drill down into children */
p = p->children;
} else {
/* No more children, increment count if required */
if (p->persistent || ((p->urld.last_visit > expiry) &&
(p->urld.visits > 0)))
(*count)++;
 
/* Now, find next node to process. */
while (p != root) {
if (p->next != NULL) {
/* Have a sibling, process that */
p = p->next;
break;
}
 
/* Ascend tree */
p = p->parent;
}
}
} while (p != root);
}
 
/**
* Write paths associated with a host
*
* \param parent Root of (sub)tree to write
* \param host Current host name
* \param fp File to write to
* \param path Current path string
* \param path_alloc Allocated size of path
* \param path_used Used size of path
* \param expiry Expiry time of URLs
*/
void urldb_write_paths(const struct path_data *parent, const char *host,
FILE *fp, char **path, int *path_alloc, int *path_used,
time_t expiry)
{
const struct path_data *p = parent;
int i;
 
do {
int seglen = p->segment != NULL ? strlen(p->segment) : 0;
int len = *path_used + seglen + 1;
 
if (*path_alloc < len) {
char *temp = realloc(*path,
(len > 64) ? len : *path_alloc + 64);
if (!temp)
return;
*path = temp;
*path_alloc = (len > 64) ? len : *path_alloc + 64;
}
 
if (p->segment != NULL)
memcpy(*path + *path_used - 1, p->segment, seglen);
 
if (p->children != NULL) {
(*path)[*path_used + seglen - 1] = '/';
(*path)[*path_used + seglen] = '\0';
} else {
(*path)[*path_used + seglen - 1] = '\0';
len -= 1;
}
 
*path_used = len;
 
if (p->children != NULL) {
/* Drill down into children */
p = p->children;
} else {
/* leaf node */
if (p->persistent ||((p->urld.last_visit > expiry) &&
(p->urld.visits > 0))) {
fprintf(fp, "%s\n", lwc_string_data(p->scheme));
 
if (p->port)
fprintf(fp,"%d\n", p->port);
else
fprintf(fp, "\n");
 
fprintf(fp, "%s\n", *path);
 
/** \todo handle fragments? */
 
fprintf(fp, "%i\n%i\n%i\n", p->urld.visits,
(int)p->urld.last_visit,
(int)p->urld.type);
 
fprintf(fp, "\n");
 
if (p->urld.title) {
uint8_t *s = (uint8_t *) p->urld.title;
 
for (i = 0; s[i] != '\0'; i++)
if (s[i] < 32)
s[i] = ' ';
for (--i; ((i > 0) && (s[i] == ' '));
i--)
s[i] = '\0';
fprintf(fp, "%s\n", p->urld.title);
} else
fprintf(fp, "\n");
}
 
/* Now, find next node to process. */
while (p != parent) {
int seglen = p->segment != NULL
? strlen(p->segment) : 0;
 
/* Remove our segment from the path */
*path_used -= seglen;
(*path)[*path_used - 1] = '\0';
 
if (p->next != NULL) {
/* Have a sibling, process that */
p = p->next;
break;
}
 
/* Going up, so remove '/' */
*path_used -= 1;
(*path)[*path_used - 1] = '\0';
 
/* Ascend tree */
p = p->parent;
}
}
} while (p != parent);
}
 
/**
* Set the cross-session persistence of the entry for an URL
*
* \param url Absolute URL to persist
* \param persist True to persist, false otherwise
*/
void urldb_set_url_persistence(nsurl *url, bool persist)
{
struct path_data *p;
 
assert(url);
 
p = urldb_find_url(url);
if (!p)
return;
 
p->persistent = persist;
}
 
/**
* Insert an URL into the database
*
* \param url Absolute URL to insert
* \return true on success, false otherwise
*/
bool urldb_add_url(nsurl *url)
{
struct host_part *h;
struct path_data *p;
lwc_string *scheme;
lwc_string *port;
lwc_string *host;
lwc_string *fragment;
const char *host_str;
char *path_query;
size_t len;
bool match;
unsigned int port_int;
 
assert(url);
 
/* Copy and merge path/query strings */
if (nsurl_get(url, NSURL_PATH | NSURL_QUERY, &path_query, &len) !=
NSERROR_OK) {
return false;
}
 
scheme = nsurl_get_component(url, NSURL_SCHEME);
if (scheme == NULL)
return false;
 
host = nsurl_get_component(url, NSURL_HOST);
if (host != NULL) {
host_str = lwc_string_data(host);
lwc_string_unref(host);
 
} else if (lwc_string_isequal(scheme, corestring_lwc_file, &match) ==
lwc_error_ok && match == true) {
host_str = "localhost";
 
} else {
lwc_string_unref(scheme);
return false;
}
 
fragment = nsurl_get_component(url, NSURL_FRAGMENT);
 
port = nsurl_get_component(url, NSURL_PORT);
if (port != NULL) {
port_int = atoi(lwc_string_data(port));
lwc_string_unref(port);
} else {
port_int = 0;
}
 
/* Get host entry */
h = urldb_add_host(host_str);
 
/* Get path entry */
p = (h != NULL) ? urldb_add_path(scheme, port_int, h, path_query,
fragment, url) : NULL;
 
lwc_string_unref(scheme);
if (fragment != NULL)
lwc_string_unref(fragment);
 
return (p != NULL);
}
 
/**
* Set an URL's title string, replacing any existing one
*
* \param url The URL to look for
* \param title The title string to use (copied)
*/
void urldb_set_url_title(nsurl *url, const char *title)
{
struct path_data *p;
char *temp;
 
assert(url && title);
 
p = urldb_find_url(url);
if (!p)
return;
 
temp = strdup(title);
if (!temp)
return;
 
free(p->urld.title);
p->urld.title = temp;
}
 
/**
* Set an URL's content type
*
* \param url The URL to look for
* \param type The type to set
*/
void urldb_set_url_content_type(nsurl *url, content_type type)
{
struct path_data *p;
 
assert(url);
 
p = urldb_find_url(url);
if (!p)
return;
 
p->urld.type = type;
}
 
/**
* Update an URL's visit data
*
* \param url The URL to update
*/
void urldb_update_url_visit_data(nsurl *url)
{
struct path_data *p;
 
assert(url);
 
p = urldb_find_url(url);
if (!p)
return;
 
p->urld.last_visit = time(NULL);
p->urld.visits++;
}
 
/**
* Reset an URL's visit statistics
*
* \param url The URL to reset
*/
void urldb_reset_url_visit_data(nsurl *url)
{
struct path_data *p;
 
assert(url);
 
p = urldb_find_url(url);
if (!p)
return;
 
p->urld.last_visit = (time_t)0;
p->urld.visits = 0;
}
 
 
/**
* Find data for an URL.
*
* \param url Absolute URL to look for
* \return Pointer to result struct, or NULL
*/
const struct url_data *urldb_get_url_data(nsurl *url)
{
struct path_data *p;
struct url_internal_data *u;
 
assert(url);
 
p = urldb_find_url(url);
if (!p)
return NULL;
 
u = &p->urld;
 
return (const struct url_data *) u;
}
 
/**
* Extract an URL from the db
*
* \param url URL to extract
* \return Pointer to database's copy of URL or NULL if not found
*/
nsurl *urldb_get_url(nsurl *url)
{
struct path_data *p;
 
assert(url);
 
p = urldb_find_url(url);
if (!p)
return NULL;
 
return p->url;
}
 
/**
* Look up authentication details in database
*
* \param url Absolute URL to search for
* \param realm When non-NULL, it is realm which can be used to determine
* the protection space when that's not been done before for given URL.
* \return Pointer to authentication details, or NULL if not found
*/
const char *urldb_get_auth_details(nsurl *url, const char *realm)
{
struct path_data *p, *p_cur, *p_top;
 
assert(url);
 
/* add to the db, so our lookup will work */
urldb_add_url(url);
 
p = urldb_find_url(url);
if (!p)
return NULL;
 
/* Check for any auth details attached to the path_data node or any of
* its parents. */
for (p_cur = p; p_cur != NULL; p_top = p_cur, p_cur = p_cur->parent) {
if (p_cur->prot_space) {
return p_cur->prot_space->auth;
}
}
 
/* Only when we have a realm (and canonical root of given URL), we can
* uniquely locate the protection space. */
if (realm != NULL) {
const struct host_part *h = (const struct host_part *)p_top;
const struct prot_space_data *space;
bool match;
 
/* Search for a possible matching protection space. */
for (space = h->prot_space; space != NULL;
space = space->next) {
if (!strcmp(space->realm, realm) &&
lwc_string_isequal(space->scheme,
p->scheme, &match) ==
lwc_error_ok &&
match == true &&
space->port == p->port) {
p->prot_space = space;
return p->prot_space->auth;
}
}
}
 
return NULL;
}
 
/**
* Retrieve certificate verification permissions from database
*
* \param url Absolute URL to search for
* \return true to permit connections to hosts with invalid certificates,
* false otherwise.
*/
bool urldb_get_cert_permissions(nsurl *url)
{
struct path_data *p;
const struct host_part *h;
 
assert(url);
 
p = urldb_find_url(url);
if (!p)
return false;
 
for (; p && p->parent; p = p->parent)
/* do nothing */;
assert(p);
 
h = (const struct host_part *)p;
 
return h->permit_invalid_certs;
}
 
/**
* Set authentication data for an URL
*
* \param url The URL to consider
* \param realm The authentication realm
* \param auth The authentication details (in form username:password)
*/
void urldb_set_auth_details(nsurl *url, const char *realm,
const char *auth)
{
struct path_data *p, *pi;
struct host_part *h;
struct prot_space_data *space, *space_alloc;
char *realm_alloc, *auth_alloc;
bool match;
 
assert(url && realm && auth);
 
/* add url, in case it's missing */
urldb_add_url(url);
 
p = urldb_find_url(url);
 
if (!p)
return;
 
/* Search for host_part */
for (pi = p; pi->parent != NULL; pi = pi->parent)
;
h = (struct host_part *)pi;
 
/* Search if given URL belongs to a protection space we already know of. */
for (space = h->prot_space; space; space = space->next) {
if (!strcmp(space->realm, realm) &&
lwc_string_isequal(space->scheme, p->scheme,
&match) == lwc_error_ok &&
match == true &&
space->port == p->port)
break;
}
 
if (space != NULL) {
/* Overrule existing auth. */
free(space->auth);
space->auth = strdup(auth);
} else {
/* Create a new protection space. */
space = space_alloc = malloc(sizeof(struct prot_space_data));
realm_alloc = strdup(realm);
auth_alloc = strdup(auth);
 
if (!space_alloc || !realm_alloc || !auth_alloc) {
free(space_alloc);
free(realm_alloc);
free(auth_alloc);
return;
}
 
space->scheme = lwc_string_ref(p->scheme);
space->port = p->port;
space->realm = realm_alloc;
space->auth = auth_alloc;
space->next = h->prot_space;
h->prot_space = space;
}
 
p->prot_space = space;
}
 
/**
* Set certificate verification permissions
*
* \param url URL to consider
* \param permit Set to true to allow invalid certificates
*/
void urldb_set_cert_permissions(nsurl *url, bool permit)
{
struct path_data *p;
struct host_part *h;
 
assert(url);
 
/* add url, in case it's missing */
urldb_add_url(url);
 
p = urldb_find_url(url);
if (!p)
return;
 
for (; p && p->parent; p = p->parent)
/* do nothing */;
assert(p);
 
h = (struct host_part *)p;
 
h->permit_invalid_certs = permit;
}
 
/**
* Set thumbnail for url, replacing any existing thumbnail
*
* \param url Absolute URL to consider
* \param bitmap Opaque pointer to thumbnail data, or NULL to invalidate
*/
void urldb_set_thumbnail(nsurl *url, struct bitmap *bitmap)
{
struct path_data *p;
 
assert(url);
 
p = urldb_find_url(url);
if (!p)
return;
 
if (p->thumb && p->thumb != bitmap)
bitmap_destroy(p->thumb);
 
p->thumb = bitmap;
}
 
/**
* Retrieve thumbnail data for given URL
*
* \param url Absolute URL to search for
* \return Pointer to thumbnail data, or NULL if not found.
*/
struct bitmap *urldb_get_thumbnail(nsurl *url)
{
struct path_data *p;
 
assert(url);
 
p = urldb_find_url(url);
if (!p)
return NULL;
 
return p->thumb;
}
 
/**
* Iterate over entries in the database which match the given prefix
*
* \param prefix Prefix to match
* \param callback Callback function
*/
void urldb_iterate_partial(const char *prefix,
bool (*callback)(nsurl *url,
const struct url_data *data))
{
char host[256];
char buf[260]; /* max domain + "www." */
const char *slash, *scheme_sep;
struct search_node *tree;
const struct host_part *h;
 
assert(prefix && callback);
 
/* strip scheme */
scheme_sep = strstr(prefix, "://");
if (scheme_sep)
prefix = scheme_sep + 3;
 
slash = strchr(prefix, '/');
tree = urldb_get_search_tree(prefix);
 
if (slash) {
/* if there's a slash in the input, then we can
* assume that we're looking for a path */
snprintf(host, sizeof host, "%.*s",
(int) (slash - prefix), prefix);
 
h = urldb_search_find(tree, host);
if (!h) {
int len = slash - prefix;
 
if (len <= 3 || strncasecmp(host, "www.", 4) != 0) {
snprintf(buf, sizeof buf, "www.%s", host);
h = urldb_search_find(
search_trees[ST_DN + 'w' - 'a'],
buf);
if (!h)
return;
} else
return;
}
 
if (h->paths.children) {
/* Have paths, iterate them */
urldb_iterate_partial_path(&h->paths, slash + 1,
callback);
}
 
} else {
int len = strlen(prefix);
 
/* looking for hosts */
if (!urldb_iterate_partial_host(tree, prefix, callback))
return;
 
if (len <= 3 || strncasecmp(prefix, "www.", 4) != 0) {
/* now look for www.prefix */
snprintf(buf, sizeof buf, "www.%s", prefix);
if(!urldb_iterate_partial_host(
search_trees[ST_DN + 'w' - 'a'],
buf, callback))
return;
}
}
}
 
/**
* Partial host iterator (internal)
*
* \param root Root of (sub)tree to traverse
* \param prefix Prefix to match
* \param callback Callback function
* \return true to continue, false otherwise
*/
bool urldb_iterate_partial_host(struct search_node *root, const char *prefix,
bool (*callback)(nsurl *url, const struct url_data *data))
{
int c;
 
assert(root && prefix && callback);
 
if (root == &empty)
return true;
 
c = urldb_search_match_prefix(root->data, prefix);
 
if (c > 0)
/* No match => look in left subtree */
return urldb_iterate_partial_host(root->left, prefix,
callback);
else if (c < 0)
/* No match => look in right subtree */
return urldb_iterate_partial_host(root->right, prefix,
callback);
else {
/* Match => iterate over l/r subtrees & process this node */
if (!urldb_iterate_partial_host(root->left, prefix,
callback))
return false;
 
if (root->data->paths.children) {
/* and extract all paths attached to this host */
if (!urldb_iterate_entries_path(&root->data->paths,
callback, NULL)) {
return false;
}
}
 
if (!urldb_iterate_partial_host(root->right, prefix,
callback))
return false;
}
 
return true;
}
 
/**
* Partial path iterator (internal)
*
* \param parent Root of (sub)tree to traverse
* \param prefix Prefix to match
* \param callback Callback function
* \return true to continue, false otherwise
*/
bool urldb_iterate_partial_path(const struct path_data *parent,
const char *prefix, bool (*callback)(nsurl *url,
const struct url_data *data))
{
const struct path_data *p = parent->children;
const char *slash, *end = prefix + strlen(prefix);
 
/*
* Given: http://www.example.org/a/b/c/d//e
* and assuming a path tree:
* .
* / \
* a1 b1
* / \
* a2 b2
* /|\
* a b c
* 3 3 |
* d
* |
* e
* / \
* f g
*
* Prefix will be: p will be:
*
* a/b/c/d//e a1
* b/c/d//e a2
* b/c/d//e b3
* c/d//e a3
* c/d//e b3
* c/d//e c
* d//e d
* /e e (skip /)
* e e
*
* I.E. we perform a breadth-first search of the tree.
*/
 
do {
slash = strchr(prefix, '/');
if (!slash)
slash = end;
 
if (slash == prefix && *prefix == '/') {
/* Ignore "//" */
prefix++;
continue;
}
if (strncasecmp(p->segment, prefix, slash - prefix) == 0) {
/* prefix matches so far */
if (slash == end) {
/* we've run out of prefix, so all
* paths below this one match */
if (!urldb_iterate_entries_path(p, callback,
NULL))
return false;
 
/* Progress to next sibling */
p = p->next;
} else {
/* Skip over this segment */
prefix = slash + 1;
 
p = p->children;
}
} else {
/* Doesn't match this segment, try next sibling */
p = p->next;
}
} while (p != NULL);
 
return true;
}
 
/**
* Iterate over all entries in database
*
* \param callback Function to callback for each entry
*/
void urldb_iterate_entries(bool (*callback)(nsurl *url,
const struct url_data *data))
{
int i;
 
assert(callback);
 
for (i = 0; i < NUM_SEARCH_TREES; i++) {
if (!urldb_iterate_entries_host(search_trees[i],
callback, NULL))
break;
}
}
 
/**
* Iterate over all cookies in database
*
* \param callback Function to callback for each entry
*/
void urldb_iterate_cookies(bool (*callback)(const struct cookie_data *data))
{
int i;
 
assert(callback);
 
for (i = 0; i < NUM_SEARCH_TREES; i++) {
if (!urldb_iterate_entries_host(search_trees[i],
NULL, callback))
break;
}
}
 
/**
* Host data iterator (internal)
*
* \param parent Root of subtree to iterate over
* \param url_callback Callback function
* \param cookie_callback Callback function
* \return true to continue, false otherwise
*/
bool urldb_iterate_entries_host(struct search_node *parent,
bool (*url_callback)(nsurl *url,
const struct url_data *data),
bool (*cookie_callback)(const struct cookie_data *data))
{
if (parent == &empty)
return true;
 
if (!urldb_iterate_entries_host(parent->left,
url_callback, cookie_callback))
return false;
 
if ((parent->data->paths.children) || ((cookie_callback) &&
(parent->data->paths.cookies))) {
/* We have paths (or domain cookies), so iterate them */
if (!urldb_iterate_entries_path(&parent->data->paths,
url_callback, cookie_callback)) {
return false;
}
}
 
if (!urldb_iterate_entries_host(parent->right,
url_callback, cookie_callback))
return false;
 
return true;
}
 
/**
* Path data iterator (internal)
*
* \param parent Root of subtree to iterate over
* \param url_callback Callback function
* \param cookie_callback Callback function
* \return true to continue, false otherwise
*/
bool urldb_iterate_entries_path(const struct path_data *parent,
bool (*url_callback)(nsurl *url,
const struct url_data *data),
bool (*cookie_callback)(const struct cookie_data *data))
{
const struct path_data *p = parent;
const struct cookie_data *c;
do {
if (p->children != NULL) {
/* Drill down into children */
p = p->children;
} else {
/* All leaf nodes in the path tree should have an URL or
* cookies attached to them. If this is not the case, it
* indicates that there's a bug in the file loader/URL
* insertion code. Therefore, assert this here. */
assert(url_callback || cookie_callback);
 
/** \todo handle fragments? */
if (url_callback) {
const struct url_internal_data *u = &p->urld;
 
assert(p->url);
 
if (!url_callback(p->url,
(const struct url_data *) u))
return false;
} else {
c = (const struct cookie_data *)p->cookies;
for (; c != NULL; c = c->next)
if (!cookie_callback(c))
return false;
}
 
/* Now, find next node to process. */
while (p != parent) {
if (p->next != NULL) {
/* Have a sibling, process that */
p = p->next;
break;
}
 
/* Ascend tree */
p = p->parent;
}
}
} while (p != parent);
 
return true;
}
 
/**
* Add a host node to the tree
*
* \param part Host segment to add (or whole IP address) (copied)
* \param parent Parent node to add to
* \return Pointer to added node, or NULL on memory exhaustion
*/
struct host_part *urldb_add_host_node(const char *part,
struct host_part *parent)
{
struct host_part *d;
 
assert(part && parent);
 
d = calloc(1, sizeof(struct host_part));
if (!d)
return NULL;
 
d->part = strdup(part);
if (!d->part) {
free(d);
return NULL;
}
 
d->next = parent->children;
if (parent->children)
parent->children->prev = d;
d->parent = parent;
parent->children = d;
 
return d;
}
 
/**
* Add a host to the database, creating any intermediate entries
*
* \param host Hostname to add
* \return Pointer to leaf node, or NULL on memory exhaustion
*/
struct host_part *urldb_add_host(const char *host)
{
struct host_part *d = (struct host_part *) &db_root, *e;
struct search_node *s;
char buf[256]; /* 256 bytes is sufficient - domain names are
* limited to 255 chars. */
char *part;
 
assert(host);
 
if (url_host_is_ip_address(host)) {
/* Host is an IP, so simply add as TLD */
 
/* Check for existing entry */
for (e = d->children; e; e = e->next)
if (strcasecmp(host, e->part) == 0)
/* found => return it */
return e;
 
d = urldb_add_host_node(host, d);
 
s = urldb_search_insert(search_trees[ST_IP], d);
if (!s) {
/* failed */
d = NULL;
} else {
search_trees[ST_IP] = s;
}
 
return d;
}
 
/* Copy host string, so we can corrupt it */
strncpy(buf, host, sizeof buf);
buf[sizeof buf - 1] = '\0';
 
/* Process FQDN segments backwards */
do {
part = strrchr(buf, '.');
if (!part) {
/* last segment */
/* Check for existing entry */
for (e = d->children; e; e = e->next)
if (strcasecmp(buf, e->part) == 0)
break;
 
if (e) {
d = e;
} else {
d = urldb_add_host_node(buf, d);
}
 
/* And insert into search tree */
if (d) {
struct search_node **r;
 
r = urldb_get_search_tree_direct(buf);
s = urldb_search_insert(*r, d);
if (!s) {
/* failed */
d = NULL;
} else {
*r = s;
}
}
break;
}
 
/* Check for existing entry */
for (e = d->children; e; e = e->next)
if (strcasecmp(part + 1, e->part) == 0)
break;
 
d = e ? e : urldb_add_host_node(part + 1, d);
if (!d)
break;
 
*part = '\0';
} while (1);
 
return d;
}
 
/**
* Add a path node to the tree
*
* \param scheme URL scheme associated with path (copied)
* \param port Port number on host associated with path
* \param segment Path segment to add (copied)
* \param fragment URL fragment (copied), or NULL
* \param parent Parent node to add to
* \return Pointer to added node, or NULL on memory exhaustion
*/
struct path_data *urldb_add_path_node(lwc_string *scheme, unsigned int port,
const char *segment, lwc_string *fragment,
struct path_data *parent)
{
struct path_data *d, *e;
 
assert(scheme && segment && parent);
 
d = calloc(1, sizeof(struct path_data));
if (!d)
return NULL;
 
d->scheme = lwc_string_ref(scheme);
 
d->port = port;
 
d->segment = strdup(segment);
if (!d->segment) {
lwc_string_unref(d->scheme);
free(d);
return NULL;
}
 
if (fragment) {
if (!urldb_add_path_fragment(d, fragment)) {
free(d->segment);
lwc_string_unref(d->scheme);
free(d);
return NULL;
}
}
 
for (e = parent->children; e; e = e->next)
if (strcmp(e->segment, d->segment) > 0)
break;
 
if (e) {
d->prev = e->prev;
d->next = e;
if (e->prev)
e->prev->next = d;
else
parent->children = d;
e->prev = d;
} else if (!parent->children) {
d->prev = d->next = NULL;
parent->children = parent->last = d;
} else {
d->next = NULL;
d->prev = parent->last;
parent->last->next = d;
parent->last = d;
}
d->parent = parent;
 
return d;
}
 
/**
* Add a path to the database, creating any intermediate entries
*
* \param scheme URL scheme associated with path
* \param port Port number on host associated with path
* \param host Host tree node to attach to
* \param path_query Absolute path plus query to add (freed)
* \param fragment URL fragment, or NULL
* \param url URL (fragment ignored)
* \return Pointer to leaf node, or NULL on memory exhaustion
*/
struct path_data *urldb_add_path(lwc_string *scheme, unsigned int port,
const struct host_part *host, char *path_query,
lwc_string *fragment, nsurl *url)
{
struct path_data *d, *e;
char *buf = path_query;
char *segment, *slash;
bool match;
 
assert(scheme && host && url);
 
d = (struct path_data *) &host->paths;
 
/* skip leading '/' */
segment = buf;
if (*segment == '/')
segment++;
 
/* Process path segments */
do {
slash = strchr(segment, '/');
if (!slash) {
/* last segment */
/* look for existing entry */
for (e = d->children; e; e = e->next)
if (strcmp(segment, e->segment) == 0 &&
lwc_string_isequal(scheme,
e->scheme, &match) ==
lwc_error_ok &&
match == true &&
e->port == port)
break;
 
d = e ? urldb_add_path_fragment(e, fragment) :
urldb_add_path_node(scheme, port,
segment, fragment, d);
break;
}
 
*slash = '\0';
 
/* look for existing entry */
for (e = d->children; e; e = e->next)
if (strcmp(segment, e->segment) == 0 &&
lwc_string_isequal(scheme, e->scheme,
&match) == lwc_error_ok &&
match == true &&
e->port == port)
break;
 
d = e ? e : urldb_add_path_node(scheme, port, segment, NULL, d);
if (!d)
break;
 
segment = slash + 1;
} while (1);
 
free(path_query);
 
if (d && !d->url) {
/* Insert URL */
if (nsurl_has_component(url, NSURL_FRAGMENT)) {
nserror err = nsurl_defragment(url, &d->url);
if (err != NSERROR_OK)
return NULL;
} else {
d->url = nsurl_ref(url);
}
}
 
return d;
}
 
/**
* Fragment comparator callback for qsort
*/
int urldb_add_path_fragment_cmp(const void *a, const void *b)
{
return strcasecmp(*((const char **) a), *((const char **) b));
}
 
/**
* Add a fragment to a path segment
*
* \param segment Path segment to add to
* \param fragment Fragment to add (copied), or NULL
* \return segment or NULL on memory exhaustion
*/
struct path_data *urldb_add_path_fragment(struct path_data *segment,
lwc_string *fragment)
{
char **temp;
 
assert(segment);
 
/* If no fragment, this function is a NOP
* This may seem strange, but it makes the rest
* of the code cleaner */
if (!fragment)
return segment;
 
temp = realloc(segment->fragment,
(segment->frag_cnt + 1) * sizeof(char *));
if (!temp)
return NULL;
 
segment->fragment = temp;
segment->fragment[segment->frag_cnt] =
strdup(lwc_string_data(fragment));
if (!segment->fragment[segment->frag_cnt]) {
/* Don't free temp - it's now our buffer */
return NULL;
}
 
segment->frag_cnt++;
 
/* We want fragments in alphabetical order, so sort them
* It may prove better to insert in alphabetical order instead */
qsort(segment->fragment, segment->frag_cnt, sizeof (char *),
urldb_add_path_fragment_cmp);
 
return segment;
}
 
/**
* Find an URL in the database
*
* \param url Absolute URL to find
* \return Pointer to path data, or NULL if not found
*/
struct path_data *urldb_find_url(nsurl *url)
{
const struct host_part *h;
struct path_data *p;
struct search_node *tree;
char *plq;
const char *host_str;
lwc_string *scheme, *host, *port;
size_t len = 0;
unsigned int port_int;
bool match;
 
assert(url);
 
scheme = nsurl_get_component(url, NSURL_SCHEME);
if (scheme == NULL)
return NULL;
 
host = nsurl_get_component(url, NSURL_HOST);
if (host != NULL) {
host_str = lwc_string_data(host);
lwc_string_unref(host);
 
} else if (lwc_string_isequal(scheme, corestring_lwc_file, &match) ==
lwc_error_ok && match == true) {
host_str = "localhost";
 
} else {
lwc_string_unref(scheme);
return NULL;
}
 
tree = urldb_get_search_tree(host_str);
h = urldb_search_find(tree, host_str);
if (!h) {
lwc_string_unref(scheme);
return NULL;
}
 
/* generate plq (path, leaf, query) */
if (nsurl_get(url, NSURL_PATH | NSURL_QUERY, &plq, &len) !=
NSERROR_OK) {
lwc_string_unref(scheme);
return NULL;
}
 
/* Get port */
port = nsurl_get_component(url, NSURL_PORT);
if (port != NULL) {
port_int = atoi(lwc_string_data(port));
lwc_string_unref(port);
} else {
port_int = 0;
}
 
p = urldb_match_path(&h->paths, plq, scheme, port_int);
 
free(plq);
lwc_string_unref(scheme);
 
return p;
}
 
/**
* Match a path string
*
* \param parent Path (sub)tree to look in
* \param path The path to search for
* \param scheme The URL scheme associated with the path
* \param port The port associated with the path
* \return Pointer to path data or NULL if not found.
*/
struct path_data *urldb_match_path(const struct path_data *parent,
const char *path, lwc_string *scheme, unsigned short port)
{
const struct path_data *p;
const char *slash;
bool match;
 
assert(parent != NULL);
assert(parent->segment == NULL);
assert(path[0] == '/');
 
/* Start with children, as parent has no segment */
p = parent->children;
 
while (p != NULL) {
slash = strchr(path + 1, '/');
if (!slash)
slash = path + strlen(path);
 
if (strncmp(p->segment, path + 1, slash - path - 1) == 0 &&
lwc_string_isequal(p->scheme, scheme, &match) ==
lwc_error_ok &&
match == true &&
p->port == port) {
if (*slash == '\0') {
/* Complete match */
return (struct path_data *) p;
}
 
/* Match so far, go down tree */
p = p->children;
 
path = slash;
} else {
/* No match, try next sibling */
p = p->next;
}
}
 
return NULL;
}
 
/**
* Get the search tree for a particular host
*
* \param host the host to lookup
* \return the corresponding search tree
*/
struct search_node **urldb_get_search_tree_direct(const char *host) {
assert(host);
 
if (url_host_is_ip_address(host))
return &search_trees[ST_IP];
else if (isalpha(*host))
return &search_trees[ST_DN + tolower(*host) - 'a'];
return &search_trees[ST_EE];
}
 
/**
* Get the search tree for a particular host
*
* \param host the host to lookup
* \return the corresponding search tree
*/
struct search_node *urldb_get_search_tree(const char *host) {
return *urldb_get_search_tree_direct(host);
}
 
/**
* Dump URL database to stderr
*/
void urldb_dump(void)
{
int i;
 
urldb_dump_hosts(&db_root);
 
for (i = 0; i != NUM_SEARCH_TREES; i++)
urldb_dump_search(search_trees[i], 0);
}
 
/**
* Dump URL database hosts to stderr
*
* \param parent Parent node of tree to dump
*/
void urldb_dump_hosts(struct host_part *parent)
{
struct host_part *h;
 
if (parent->part) {
LOG(("%s", parent->part));
 
LOG(("\t%s invalid SSL certs",
parent->permit_invalid_certs ? "Permits" : "Denies"));
}
 
/* Dump path data */
urldb_dump_paths(&parent->paths);
 
/* and recurse */
for (h = parent->children; h; h = h->next)
urldb_dump_hosts(h);
}
 
/**
* Dump URL database paths to stderr
*
* \param parent Parent node of tree to dump
*/
void urldb_dump_paths(struct path_data *parent)
{
const struct path_data *p = parent;
unsigned int i;
 
do {
if (p->segment != NULL) {
LOG(("\t%s : %u", lwc_string_data(p->scheme), p->port));
 
LOG(("\t\t'%s'", p->segment));
 
for (i = 0; i != p->frag_cnt; i++)
LOG(("\t\t\t#%s", p->fragment[i]));
}
 
if (p->children != NULL) {
p = p->children;
} else {
while (p != parent) {
if (p->next != NULL) {
p = p->next;
break;
}
 
p = p->parent;
}
}
} while (p != parent);
}
 
/**
* Dump search tree
*
* \param parent Parent node of tree to dump
* \param depth Tree depth
*/
void urldb_dump_search(struct search_node *parent, int depth)
{
const struct host_part *h;
int i;
 
if (parent == &empty)
return;
 
urldb_dump_search(parent->left, depth + 1);
 
for (i = 0; i != depth; i++)
fputc(' ', stderr);
 
for (h = parent->data; h; h = h->parent) {
if (h->part)
fprintf(stderr, "%s", h->part);
 
if (h->parent && h->parent->parent)
fputc('.', stderr);
}
 
fputc('\n', stderr);
 
urldb_dump_search(parent->right, depth + 1);
}
 
/**
* Insert a node into the search tree
*
* \param root Root of tree to insert into
* \param data User data to insert
* \return Pointer to updated root, or NULL if failed
*/
struct search_node *urldb_search_insert(struct search_node *root,
const struct host_part *data)
{
struct search_node *n;
 
assert(root && data);
 
n = malloc(sizeof(struct search_node));
if (!n)
return NULL;
 
n->level = 1;
n->data = data;
n->left = n->right = &empty;
 
root = urldb_search_insert_internal(root, n);
 
return root;
}
 
/**
* Insert node into search tree
*
* \param root Root of (sub)tree to insert into
* \param n Node to insert
* \return Pointer to updated root
*/
struct search_node *urldb_search_insert_internal(struct search_node *root,
struct search_node *n)
{
assert(root && n);
 
if (root == &empty) {
root = n;
} else {
int c = urldb_search_match_host(root->data, n->data);
 
if (c > 0) {
root->left = urldb_search_insert_internal(
root->left, n);
} else if (c < 0) {
root->right = urldb_search_insert_internal(
root->right, n);
} else {
/* exact match */
free(n);
return root;
}
 
root = urldb_search_skew(root);
root = urldb_search_split(root);
}
 
return root;
}
 
/**
* Find a node in a search tree
*
* \param root Tree to look in
* \param host Host to find
* \return Pointer to host tree node, or NULL if not found
*/
const struct host_part *urldb_search_find(struct search_node *root,
const char *host)
{
int c;
 
assert(root && host);
 
if (root == &empty) {
return NULL;
}
 
c = urldb_search_match_string(root->data, host);
 
if (c > 0)
return urldb_search_find(root->left, host);
else if (c < 0)
return urldb_search_find(root->right, host);
else
return root->data;
}
 
/**
* Compare a pair of host_parts
*
* \param a
* \param b
* \return 0 if match, non-zero, otherwise
*/
int urldb_search_match_host(const struct host_part *a,
const struct host_part *b)
{
int ret;
 
assert(a && b);
 
/* traverse up tree to root, comparing parts as we go. */
for (; a && a != &db_root && b && b != &db_root;
a = a->parent, b = b->parent)
if ((ret = strcasecmp(a->part, b->part)) != 0)
/* They differ => return the difference here */
return ret;
 
/* If we get here then either:
* a) The path lengths differ
* or b) The hosts are identical
*/
if (a && a != &db_root && (!b || b == &db_root))
/* len(a) > len(b) */
return 1;
else if ((!a || a == &db_root) && b && b != &db_root)
/* len(a) < len(b) */
return -1;
 
/* identical */
return 0;
}
 
/**
* Compare host_part with a string
*
* \param a
* \param b
* \return 0 if match, non-zero, otherwise
*/
int urldb_search_match_string(const struct host_part *a,
const char *b)
{
const char *end, *dot;
int plen, ret;
 
assert(a && a != &db_root && b);
 
if (url_host_is_ip_address(b)) {
/* IP address */
return strcasecmp(a->part, b);
}
 
end = b + strlen(b) + 1;
 
while (b < end && a && a != &db_root) {
dot = strchr(b, '.');
if (!dot) {
/* last segment */
dot = end - 1;
}
 
/* Compare strings (length limited) */
if ((ret = strncasecmp(a->part, b, dot - b)) != 0)
/* didn't match => return difference */
return ret;
 
/* The strings matched, now check that the lengths do, too */
plen = strlen(a->part);
 
if (plen > dot - b)
/* len(a) > len(b) */
return 1;
else if (plen < dot - b)
/* len(a) < len(b) */
return -1;
 
b = dot + 1;
a = a->parent;
}
 
/* If we get here then either:
* a) The path lengths differ
* or b) The hosts are identical
*/
if (a && a != &db_root && b >= end)
/* len(a) > len(b) */
return 1;
else if ((!a || a == &db_root) && b < end)
/* len(a) < len(b) */
return -1;
 
/* Identical */
return 0;
}
 
/**
* Compare host_part with prefix
*
* \param a
* \param b
* \return 0 if match, non-zero, otherwise
*/
int urldb_search_match_prefix(const struct host_part *a,
const char *b)
{
const char *end, *dot;
int plen, ret;
 
assert(a && a != &db_root && b);
 
if (url_host_is_ip_address(b)) {
/* IP address */
return strncasecmp(a->part, b, strlen(b));
}
 
end = b + strlen(b) + 1;
 
while (b < end && a && a != &db_root) {
dot = strchr(b, '.');
if (!dot) {
/* last segment */
dot = end - 1;
}
 
/* Compare strings (length limited) */
if ((ret = strncasecmp(a->part, b, dot - b)) != 0)
/* didn't match => return difference */
return ret;
 
/* The strings matched */
if (dot < end - 1) {
/* Consider segment lengths only in the case
* where the prefix contains segments */
plen = strlen(a->part);
if (plen > dot - b)
/* len(a) > len(b) */
return 1;
else if (plen < dot - b)
/* len(a) < len(b) */
return -1;
}
 
b = dot + 1;
a = a->parent;
}
 
/* If we get here then either:
* a) The path lengths differ
* or b) The hosts are identical
*/
if (a && a != &db_root && b >= end)
/* len(a) > len(b) => prefix matches */
return 0;
else if ((!a || a == &db_root) && b < end)
/* len(a) < len(b) => prefix does not match */
return -1;
 
/* Identical */
return 0;
}
 
/**
* Rotate a subtree right
*
* \param root Root of subtree to rotate
* \return new root of subtree
*/
struct search_node *urldb_search_skew(struct search_node *root)
{
struct search_node *temp;
 
assert(root);
 
if (root->left->level == root->level) {
temp = root->left;
root->left = temp->right;
temp->right = root;
root = temp;
}
 
return root;
}
 
/**
* Rotate a node left, increasing the parent's level
*
* \param root Root of subtree to rotate
* \return New root of subtree
*/
struct search_node *urldb_search_split(struct search_node *root)
{
struct search_node *temp;
 
assert(root);
 
if (root->right->right->level == root->level) {
temp = root->right;
root->right = temp->left;
temp->left = root;
root = temp;
 
root->level++;
}
 
return root;
}
 
/**
* Retrieve cookies for an URL
*
* \param url URL being fetched
* \param include_http_only Whether to include HTTP(S) only cookies.
* \return Cookies string for libcurl (on heap), or NULL on error/no cookies
*/
char *urldb_get_cookie(nsurl *url, bool include_http_only)
{
const struct path_data *p, *q;
const struct host_part *h;
lwc_string *path_lwc;
struct cookie_internal_data *c;
int count = 0, version = COOKIE_RFC2965;
struct cookie_internal_data **matched_cookies;
int matched_cookies_size = 20;
int ret_alloc = 4096, ret_used = 1;
const char *path;
char *ret;
lwc_string *scheme;
time_t now;
int i;
bool match;
 
assert(url != NULL);
 
/* The URL must exist in the db in order to find relevant cookies, since
* we search up the tree from the URL node, and cookies from further
* up also apply. */
urldb_add_url(url);
 
p = urldb_find_url(url);
if (!p)
return NULL;
 
scheme = p->scheme;
 
matched_cookies = malloc(matched_cookies_size *
sizeof(struct cookie_internal_data *));
if (!matched_cookies)
return NULL;
 
#define GROW_MATCHED_COOKIES \
do { \
if (count == matched_cookies_size) { \
struct cookie_internal_data **temp; \
temp = realloc(matched_cookies, \
(matched_cookies_size + 20) * \
sizeof(struct cookie_internal_data *)); \
\
if (temp == NULL) { \
free(ret); \
free(matched_cookies); \
return NULL; \
} \
\
matched_cookies = temp; \
matched_cookies_size += 20; \
} \
} while(0)
 
ret = malloc(ret_alloc);
if (!ret) {
free(matched_cookies);
return NULL;
}
 
ret[0] = '\0';
 
path_lwc = nsurl_get_component(url, NSURL_PATH);
if (path_lwc == NULL) {
free(ret);
free(matched_cookies);
return NULL;
}
path = lwc_string_data(path_lwc);
lwc_string_unref(path_lwc);
 
now = time(NULL);
 
if (*(p->segment) != '\0') {
/* Match exact path, unless directory, when prefix matching
* will handle this case for us. */
for (q = p->parent->children; q; q = q->next) {
if (strcmp(q->segment, p->segment))
continue;
 
/* Consider all cookies associated with
* this exact path */
for (c = q->cookies; c; c = c->next) {
if (c->expires != -1 && c->expires < now)
/* cookie has expired => ignore */
continue;
 
if (c->secure && lwc_string_isequal(
q->scheme,
corestring_lwc_https,
&match) &&
match == false)
/* secure cookie for insecure host.
* ignore */
continue;
 
if (c->http_only && !include_http_only)
/* Ignore HttpOnly */
continue;
 
matched_cookies[count++] = c;
 
GROW_MATCHED_COOKIES;
 
if (c->version < (unsigned int)version)
version = c->version;
 
c->last_used = now;
cookies_schedule_update((struct cookie_data *)c);
}
}
}
 
/* Now consider cookies whose paths prefix-match ours */
for (p = p->parent; p; p = p->parent) {
/* Find directory's path entry(ies) */
/* There are potentially multiple due to differing schemes */
for (q = p->children; q; q = q->next) {
if (*(q->segment) != '\0')
continue;
 
for (c = q->cookies; c; c = c->next) {
if (c->expires != -1 && c->expires < now)
/* cookie has expired => ignore */
continue;
 
if (c->secure && lwc_string_isequal(
q->scheme,
corestring_lwc_https,
&match) &&
match == false)
/* Secure cookie for insecure server
* => ignore */
continue;
 
matched_cookies[count++] = c;
 
GROW_MATCHED_COOKIES;
 
if (c->version < (unsigned int) version)
version = c->version;
 
c->last_used = now;
cookies_schedule_update((struct cookie_data *)c);
}
}
 
if (!p->parent) {
/* No parent, so bail here. This can't go in
* the loop exit condition as we also want to
* process the top-level node.
*
* If p->parent is NULL then p->cookies are
* the domain cookies and thus we don't even
* try matching against them.
*/
break;
}
 
/* Consider p itself - may be the result of Path=/foo */
for (c = p->cookies; c; c = c->next) {
if (c->expires != -1 && c->expires < now)
/* cookie has expired => ignore */
continue;
 
/* Ensure cookie path is a prefix of the resource */
if (strncmp(c->path, path, strlen(c->path)) != 0)
/* paths don't match => ignore */
continue;
 
if (c->secure && lwc_string_isequal(p->scheme,
corestring_lwc_https,
&match) &&
match == false)
/* Secure cookie for insecure server
* => ignore */
continue;
 
matched_cookies[count++] = c;
 
GROW_MATCHED_COOKIES;
 
if (c->version < (unsigned int) version)
version = c->version;
 
c->last_used = now;
cookies_schedule_update((struct cookie_data *)c);
}
 
}
 
/* Finally consider domain cookies for hosts which domain match ours */
for (h = (const struct host_part *)p; h && h != &db_root;
h = h->parent) {
for (c = h->paths.cookies; c; c = c->next) {
if (c->expires != -1 && c->expires < now)
/* cookie has expired => ignore */
continue;
 
/* Ensure cookie path is a prefix of the resource */
if (strncmp(c->path, path, strlen(c->path)) != 0)
/* paths don't match => ignore */
continue;
 
if (c->secure && lwc_string_isequal(scheme,
corestring_lwc_https,
&match) &&
match == false)
/* secure cookie for insecure host. ignore */
continue;
 
matched_cookies[count++] = c;
 
GROW_MATCHED_COOKIES;
 
if (c->version < (unsigned int)version)
version = c->version;
 
c->last_used = now;
cookies_schedule_update((struct cookie_data *)c);
}
}
 
if (count == 0) {
/* No cookies found */
free(ret);
free(matched_cookies);
return NULL;
}
 
/* and build output string */
if (version > COOKIE_NETSCAPE) {
sprintf(ret, "$Version=%d", version);
ret_used = strlen(ret) + 1;
}
 
for (i = 0; i < count; i++) {
if (!urldb_concat_cookie(matched_cookies[i], version,
&ret_used, &ret_alloc, &ret)) {
free(ret);
free(matched_cookies);
return NULL;
}
}
 
if (version == COOKIE_NETSCAPE) {
/* Old-style cookies => no version & skip "; " */
memmove(ret, ret + 2, ret_used - 2);
ret_used -= 2;
}
 
/* Now, shrink the output buffer to the required size */
{
char *temp = realloc(ret, ret_used);
if (!temp) {
free(ret);
free(matched_cookies);
return NULL;
}
 
ret = temp;
}
 
free(matched_cookies);
 
return ret;
 
#undef GROW_MATCHED_COOKIES
}
 
/**
* Parse Set-Cookie header and insert cookie(s) into database
*
* \param header Header to parse, with Set-Cookie: stripped
* \param url URL being fetched
* \param referer Referring resource, or 0 for verifiable transaction
* \return true on success, false otherwise
*/
bool urldb_set_cookie(const char *header, nsurl *url, nsurl *referer)
{
const char *cur = header, *end;
lwc_string *path, *host, *scheme;
nsurl *urlt;
bool match;
 
assert(url && header);
 
/* Get defragmented URL, as 'urlt' */
if (nsurl_has_component(url, NSURL_FRAGMENT)) {
if (nsurl_defragment(url, &urlt) != NSERROR_OK)
return NULL;
} else {
urlt = nsurl_ref(url);
}
 
scheme = nsurl_get_component(url, NSURL_SCHEME);
if (scheme == NULL) {
nsurl_unref(urlt);
return false;
}
 
path = nsurl_get_component(url, NSURL_PATH);
if (path == NULL) {
lwc_string_unref(scheme);
nsurl_unref(urlt);
return false;
}
 
host = nsurl_get_component(url, NSURL_HOST);
if (host == NULL) {
lwc_string_unref(path);
lwc_string_unref(scheme);
nsurl_unref(urlt);
return false;
}
 
if (referer) {
lwc_string *rhost;
 
/* Ensure that url's host name domain matches
* referer's (4.3.5) */
rhost = nsurl_get_component(url, NSURL_HOST);
if (rhost == NULL) {
goto error;
}
 
/* Domain match host names */
if (lwc_string_isequal(host, rhost, &match) == lwc_error_ok &&
match == false) {
const char *hptr;
const char *rptr;
const char *dot;
const char *host_data = lwc_string_data(host);
const char *rhost_data = lwc_string_data(rhost);
 
/* Ensure neither host nor rhost are IP addresses */
if (url_host_is_ip_address(host_data) ||
url_host_is_ip_address(rhost_data)) {
/* IP address, so no partial match */
lwc_string_unref(rhost);
goto error;
}
 
/* Not exact match, so try the following:
*
* 1) Find the longest common suffix of host and rhost
* (may be all of host/rhost)
* 2) Discard characters from the start of the suffix
* until the suffix starts with a dot
* (prevents foobar.com matching bar.com)
* 3) Ensure the suffix is non-empty and contains
* embedded dots (to avoid permitting .com as a
* suffix)
*
* Note that the above in no way resembles the
* domain matching algorithm found in RFC2109.
* It does, however, model the real world rather
* more accurately.
*/
 
/** \todo In future, we should consult a TLD service
* instead of just looking for embedded dots.
*/
 
hptr = host_data + lwc_string_length(host) - 1;
rptr = rhost_data + lwc_string_length(rhost) - 1;
 
/* 1 */
while (hptr >= host_data && rptr >= rhost_data) {
if (*hptr != *rptr)
break;
hptr--;
rptr--;
}
/* Ensure we end up pointing at the start of the
* common suffix. The above loop will exit pointing
* to the byte before the start of the suffix. */
hptr++;
 
/* 2 */
while (*hptr != '\0' && *hptr != '.')
hptr++;
 
/* 3 */
if (*hptr == '\0' ||
(dot = strchr(hptr + 1, '.')) == NULL ||
*(dot + 1) == '\0') {
lwc_string_unref(rhost);
goto error;
}
}
 
lwc_string_unref(rhost);
}
 
end = cur + strlen(cur) - 2 /* Trailing CRLF */;
 
do {
struct cookie_internal_data *c;
char *dot;
size_t len;
 
c = urldb_parse_cookie(url, &cur);
if (!c) {
/* failed => stop parsing */
goto error;
}
 
/* validate cookie */
 
/* 4.2.2:i Cookie must have NAME and VALUE */
if (!c->name || !c->value) {
urldb_free_cookie(c);
goto error;
}
 
/* 4.3.2:i Cookie path must be a prefix of URL path */
len = strlen(c->path);
if (len > lwc_string_length(path) ||
strncmp(c->path, lwc_string_data(path),
len) != 0) {
urldb_free_cookie(c);
goto error;
}
 
/* 4.3.2:ii Cookie domain must contain embedded dots */
dot = strchr(c->domain + 1, '.');
if (!dot || *(dot + 1) == '\0') {
/* no embedded dots */
urldb_free_cookie(c);
goto error;
}
 
/* Domain match fetch host with cookie domain */
if (strcasecmp(lwc_string_data(host), c->domain) != 0) {
int hlen, dlen;
char *domain = c->domain;
 
/* c->domain must be a domain cookie here because:
* c->domain is either:
* + specified in the header as a domain cookie
* (non-domain cookies in the header are ignored
* by urldb_parse_cookie / urldb_parse_avpair)
* + defaulted to the URL's host part
* (by urldb_parse_cookie if no valid domain was
* specified in the header)
*
* The latter will pass the strcasecmp above, which
* leaves the former (i.e. a domain cookie)
*/
assert(c->domain[0] == '.');
 
/* 4.3.2:iii */
if (url_host_is_ip_address(lwc_string_data(host))) {
/* IP address, so no partial match */
urldb_free_cookie(c);
goto error;
}
 
hlen = lwc_string_length(host);
dlen = strlen(c->domain);
 
if (hlen <= dlen && hlen != dlen - 1) {
/* Partial match not possible */
urldb_free_cookie(c);
goto error;
}
 
if (hlen == dlen - 1) {
/* Relax matching to allow
* host a.com to match .a.com */
domain++;
dlen--;
}
 
if (strcasecmp(lwc_string_data(host) + (hlen - dlen),
domain)) {
urldb_free_cookie(c);
goto error;
}
 
/* 4.3.2:iv Ensure H contains no dots
*
* If you believe the spec, H should contain no
* dots in _any_ cookie. Unfortunately, however,
* reality differs in that many sites send domain
* cookies of the form .foo.com from hosts such
* as bar.bat.foo.com and then expect domain
* matching to work. Thus we have to do what they
* expect, regardless of any potential security
* implications.
*
* This is what code conforming to the spec would
* look like:
*
* for (int i = 0; i < (hlen - dlen); i++) {
* if (host[i] == '.') {
* urldb_free_cookie(c);
* goto error;
* }
* }
*/
}
 
/* Now insert into database */
if (!urldb_insert_cookie(c, scheme, urlt))
goto error;
} while (cur < end);
 
lwc_string_unref(host);
lwc_string_unref(path);
lwc_string_unref(scheme);
nsurl_unref(urlt);
 
return true;
 
error:
lwc_string_unref(host);
lwc_string_unref(path);
lwc_string_unref(scheme);
nsurl_unref(urlt);
 
return false;
}
 
/**
* Parse a cookie
*
* \param url URL being fetched
* \param cookie Pointer to cookie string (updated on exit)
* \return Pointer to cookie structure (on heap, caller frees) or NULL
*/
struct cookie_internal_data *urldb_parse_cookie(nsurl *url,
const char **cookie)
{
struct cookie_internal_data *c;
const char *cur;
char name[1024], value[4096];
char *n = name, *v = value;
bool in_value = false;
bool had_value_data = false;
bool value_verbatim = false;
bool quoted = false;
bool was_quoted = false;
 
assert(url && cookie && *cookie);
 
c = calloc(1, sizeof(struct cookie_internal_data));
if (c == NULL)
return NULL;
 
c->expires = -1;
 
name[0] = '\0';
value[0] = '\0';
 
for (cur = *cookie; *cur; cur++) {
if (*cur == '\r' && *(cur + 1) == '\n') {
/* End of header */
if (quoted) {
/* Unmatched quote encountered */
 
/* Match Firefox 2.0.0.11 */
value[0] = '\0';
 
#if 0
/* This is what IE6/7 & Safari 3 do */
/* Opera 9.25 discards the entire cookie */
 
/* Shuffle value up by 1 */
memmove(value + 1, value,
min(v - value, sizeof(value) - 2));
v++;
/* And insert " character at the start */
value[0] = '"';
 
/* Now, run forwards through the value
* looking for a semicolon. If one exists,
* terminate the value at this point. */
for (char *s = value; s < v; s++) {
if (*s == ';') {
*s = '\0';
v = s;
break;
}
}
#endif
}
 
break;
} else if (*cur == '\r') {
/* Spurious linefeed */
continue;
} else if (*cur == '\n') {
/* Spurious newline */
continue;
}
 
if (in_value && !had_value_data) {
if (*cur == ' ' || *cur == '\t') {
/* Strip leading whitespace from value */
continue;
} else {
had_value_data = true;
 
/* Value is taken verbatim if first non-space
* character is not a " */
if (*cur != '"') {
value_verbatim = true;
}
}
}
 
if (in_value && !value_verbatim && (*cur == '"')) {
/* Only non-verbatim values may be quoted */
if (cur == *cookie || *(cur - 1) != '\\') {
/* Only unescaped quotes count */
was_quoted = quoted;
quoted = !quoted;
 
continue;
}
}
 
if (!quoted && !in_value && *cur == '=') {
/* First equals => attr-value separator */
in_value = true;
continue;
}
 
if (!quoted && (was_quoted || *cur == ';')) {
/* Semicolon or after quoted value
* => end of current avpair */
 
/* NUL-terminate tokens */
*n = '\0';
*v = '\0';
 
if (!urldb_parse_avpair(c, name, value, was_quoted)) {
/* Memory exhausted */
urldb_free_cookie(c);
return NULL;
}
 
/* And reset to start */
n = name;
v = value;
in_value = false;
had_value_data = false;
value_verbatim = false;
was_quoted = false;
 
/* Now, if the current input is anything other than a
* semicolon, we must be sure to reprocess it */
if (*cur != ';') {
cur--;
}
 
continue;
}
 
/* And now handle commas. These are a pain as they may mean
* any of the following:
*
* + End of cookie
* + Day separator in Expires avpair
* + (Invalid) comma in unquoted value
*
* Therefore, in order to handle all 3 cases (2 and 3 are
* identical, the difference being that 2 is in the spec and
* 3 isn't), we need to determine where the comma actually
* lies. We use the following heuristic:
*
* Given a comma at the current input position, find the
* immediately following semicolon (or end of input if none
* found). Then, consider the input characters between
* these two positions. If any of these characters is an
* '=', we must assume that the comma signified the end of
* the current cookie.
*
* This holds as the first avpair of any cookie must be
* NAME=VALUE, so the '=' is guaranteed to appear in the
* case where the comma marks the end of a cookie.
*
* This will fail, however, in the case where '=' appears in
* the value of the current avpair after the comma or the
* subsequent cookie does not start with NAME=VALUE. Neither
* of these is particularly likely and if they do occur, the
* website is more broken than we can be bothered to handle.
*/
if (!quoted && *cur == ',') {
/* Find semi-colon, if any */
const char *p;
const char *semi = strchr(cur + 1, ';');
if (!semi)
semi = cur + strlen(cur) - 2 /* CRLF */;
 
/* Look for equals sign between comma and semi */
for (p = cur + 1; p < semi; p++)
if (*p == '=')
break;
 
if (p == semi) {
/* none found => comma internal to value */
/* do nothing */
} else {
/* found one => comma marks end of cookie */
cur++;
break;
}
}
 
/* Accumulate into buffers, always leaving space for a NUL */
/** \todo is silently truncating overlong names/values wise? */
if (!in_value) {
if (n < name + (sizeof(name) - 1))
*n++ = *cur;
} else {
if (v < value + (sizeof(value) - 1))
*v++ = *cur;
}
}
 
/* Parse final avpair */
*n = '\0';
*v = '\0';
 
if (!urldb_parse_avpair(c, name, value, was_quoted)) {
/* Memory exhausted */
urldb_free_cookie(c);
return NULL;
}
 
/* Now fix-up default values */
if (c->domain == NULL) {
lwc_string *host = nsurl_get_component(url, NSURL_HOST);
if (host == NULL) {
urldb_free_cookie(c);
return NULL;
}
c->domain = strdup(lwc_string_data(host));
lwc_string_unref(host);
}
 
if (c->path == NULL) {
const char *path_data;
char *path, *slash;
lwc_string *path_lwc;
 
path_lwc = nsurl_get_component(url, NSURL_PATH);
if (path_lwc == NULL) {
urldb_free_cookie(c);
return NULL;
}
path_data = lwc_string_data(path_lwc);
 
/* Strip leafname and trailing slash (4.3.1) */
slash = strrchr(path_data, '/');
if (slash != NULL) {
/* Special case: retain first slash in path */
if (slash == path_data)
slash++;
 
slash = strndup(path_data, slash - path_data);
if (slash == NULL) {
lwc_string_unref(path_lwc);
urldb_free_cookie(c);
return NULL;
}
 
path = slash;
lwc_string_unref(path_lwc);
} else {
path = strdup(lwc_string_data(path_lwc));
lwc_string_unref(path_lwc);
if (path == NULL) {
urldb_free_cookie(c);
return NULL;
}
}
 
c->path = path;
}
 
/* Write back current position */
*cookie = cur;
 
return c;
}
 
/**
* Parse a cookie avpair
*
* \param c Cookie struct to populate
* \param n Name component
* \param v Value component
* \param was_quoted Whether ::v was quoted in the input
* \return true on success, false on memory exhaustion
*/
bool urldb_parse_avpair(struct cookie_internal_data *c, char *n, char *v,
bool was_quoted)
{
int vlen;
 
assert(c && n && v);
 
/* Strip whitespace from start of name */
for (; *n; n++) {
if (*n != ' ' && *n != '\t')
break;
}
 
/* Strip whitespace from end of name */
for (vlen = strlen(n); vlen; vlen--) {
if (n[vlen] == ' ' || n[vlen] == '\t')
n[vlen] = '\0';
else
break;
}
 
/* Strip whitespace from start of value */
for (; *v; v++) {
if (*v != ' ' && *v != '\t')
break;
}
 
/* Strip whitespace from end of value */
for (vlen = strlen(v); vlen; vlen--) {
if (v[vlen] == ' ' || v[vlen] == '\t')
v[vlen] = '\0';
else
break;
}
 
if (!c->comment && strcasecmp(n, "Comment") == 0) {
c->comment = strdup(v);
if (!c->comment)
return false;
} else if (!c->domain && strcasecmp(n, "Domain") == 0) {
if (v[0] == '.') {
/* Domain must start with a dot */
c->domain_from_set = true;
c->domain = strdup(v);
if (!c->domain)
return false;
}
} else if (strcasecmp(n, "Max-Age") == 0) {
int temp = atoi(v);
if (temp == 0)
/* Special case - 0 means delete */
c->expires = 0;
else
c->expires = time(NULL) + temp;
} else if (!c->path && strcasecmp(n, "Path") == 0) {
c->path_from_set = true;
c->path = strdup(v);
if (!c->path)
return false;
} else if (strcasecmp(n, "Version") == 0) {
c->version = atoi(v);
} else if (strcasecmp(n, "Expires") == 0) {
char *datenoday;
time_t expires;
 
/* Strip dayname from date (these are hugely
* variable and liable to break the parser.
* They also serve no useful purpose) */
for (datenoday = v; *datenoday && !isdigit(*datenoday);
datenoday++)
; /* do nothing */
 
expires = curl_getdate(datenoday, NULL);
if (expires == -1) {
/* assume we have an unrepresentable
* date => force it to the maximum
* possible value of a 32bit time_t
* (this may break in 2038. We'll
* deal with that once we come to
* it) */
expires = (time_t)0x7fffffff;
}
c->expires = expires;
} else if (strcasecmp(n, "Secure") == 0) {
c->secure = true;
} else if (strcasecmp(n, "HttpOnly") == 0) {
c->http_only = true;
} else if (!c->name) {
c->name = strdup(n);
c->value = strdup(v);
c->value_was_quoted = was_quoted;
if (!c->name || !c->value)
return false;
}
 
return true;
}
 
/**
* Insert a cookie into the database
*
* \param c The cookie to insert
* \param scheme URL scheme associated with cookie path
* \param url URL (sans fragment) associated with cookie
* \return true on success, false on memory exhaustion (c will be freed)
*/
bool urldb_insert_cookie(struct cookie_internal_data *c, lwc_string *scheme,
nsurl *url)
{
struct cookie_internal_data *d;
const struct host_part *h;
struct path_data *p;
time_t now = time(NULL);
 
assert(c);
 
if (c->domain[0] == '.') {
h = urldb_search_find(
urldb_get_search_tree(&(c->domain[1])),
c->domain + 1);
if (!h) {
h = urldb_add_host(c->domain + 1);
if (!h) {
urldb_free_cookie(c);
return false;
}
}
 
p = (struct path_data *) &h->paths;
} else {
/* Need to have a URL and scheme, if it's not a domain cookie */
assert(url != NULL);
assert(scheme != NULL);
 
h = urldb_search_find(
urldb_get_search_tree(c->domain),
c->domain);
 
if (!h) {
h = urldb_add_host(c->domain);
if (!h) {
urldb_free_cookie(c);
return false;
}
}
 
/* find path */
p = urldb_add_path(scheme, 0, h,
strdup(c->path), NULL, url);
if (!p) {
urldb_free_cookie(c);
return false;
}
}
 
/* add cookie */
for (d = p->cookies; d; d = d->next) {
if (!strcmp(d->domain, c->domain) &&
!strcmp(d->path, c->path) &&
!strcmp(d->name, c->name))
break;
}
 
if (d) {
if (c->expires != -1 && c->expires < now) {
/* remove cookie */
if (d->next)
d->next->prev = d->prev;
else
p->cookies_end = d->prev;
if (d->prev)
d->prev->next = d->next;
else
p->cookies = d->next;
cookies_remove((struct cookie_data *)d);
urldb_free_cookie(d);
urldb_free_cookie(c);
} else {
/* replace d with c */
c->prev = d->prev;
c->next = d->next;
if (c->next)
c->next->prev = c;
else
p->cookies_end = c;
if (c->prev)
c->prev->next = c;
else
p->cookies = c;
cookies_remove((struct cookie_data *)d);
urldb_free_cookie(d);
cookies_schedule_update((struct cookie_data *)c);
}
} else {
c->prev = p->cookies_end;
c->next = NULL;
if (p->cookies_end)
p->cookies_end->next = c;
else
p->cookies = c;
p->cookies_end = c;
 
cookies_schedule_update((struct cookie_data *)c);
}
 
return true;
}
 
/**
* Free a cookie
*
* \param c The cookie to free
*/
void urldb_free_cookie(struct cookie_internal_data *c)
{
assert(c);
 
free(c->comment);
free(c->domain);
free(c->path);
free(c->name);
free(c->value);
free(c);
}
 
/**
* Concatenate a cookie into the provided buffer
*
* \param c Cookie to concatenate
* \param version The version of the cookie string to output
* \param used Pointer to amount of buffer used (updated)
* \param alloc Pointer to allocated size of buffer (updated)
* \param buf Pointer to Pointer to buffer (updated)
* \return true on success, false on memory exhaustion
*/
bool urldb_concat_cookie(struct cookie_internal_data *c, int version,
int *used, int *alloc, char **buf)
{
/* Combined (A)BNF for the Cookie: request header:
*
* CHAR = <any US-ASCII character (octets 0 - 127)>
* CTL = <any US-ASCII control character
* (octets 0 - 31) and DEL (127)>
* CR = <US-ASCII CR, carriage return (13)>
* LF = <US-ASCII LF, linefeed (10)>
* SP = <US-ASCII SP, space (32)>
* HT = <US-ASCII HT, horizontal-tab (9)>
* <"> = <US-ASCII double-quote mark (34)>
*
* CRLF = CR LF
*
* LWS = [CRLF] 1*( SP | HT )
*
* TEXT = <any OCTET except CTLs,
* but including LWS>
*
* token = 1*<any CHAR except CTLs or separators>
* separators = "(" | ")" | "<" | ">" | "@"
* | "," | ";" | ":" | "\" | <">
* | "/" | "[" | "]" | "?" | "="
* | "{" | "}" | SP | HT
*
* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
* qdtext = <any TEXT except <">>
* quoted-pair = "\" CHAR
*
* attr = token
* value = word
* word = token | quoted-string
*
* cookie = "Cookie:" cookie-version
* 1*((";" | ",") cookie-value)
* cookie-value = NAME "=" VALUE [";" path] [";" domain]
* cookie-version = "$Version" "=" value
* NAME = attr
* VALUE = value
* path = "$Path" "=" value
* domain = "$Domain" "=" value
*
* A note on quoted-string handling:
* The cookie data stored in the db is verbatim (i.e. sans enclosing
* <">, if any, and with all quoted-pairs intact) thus all that we
* need to do here is ensure that value strings which were quoted
* in Set-Cookie or which include any of the separators are quoted
* before use.
*
* A note on cookie-value separation:
* We use semicolons for all separators, including between
* cookie-values. This simplifies things and is backwards compatible.
*/
const char * const separators = "()<>@,;:\\\"/[]?={} \t";
 
int max_len;
 
assert(c && used && alloc && buf && *buf);
 
/* "; " cookie-value
* We allow for the possibility that values are quoted
*/
max_len = 2 + strlen(c->name) + 1 + strlen(c->value) + 2 +
(c->path_from_set ?
8 + strlen(c->path) + 2 : 0) +
(c->domain_from_set ?
10 + strlen(c->domain) + 2 : 0);
 
if (*used + max_len >= *alloc) {
char *temp = realloc(*buf, *alloc + 4096);
if (!temp) {
return false;
}
*buf = temp;
*alloc += 4096;
}
 
if (version == COOKIE_NETSCAPE) {
/* Original Netscape cookie */
sprintf(*buf + *used - 1, "; %s=", c->name);
*used += 2 + strlen(c->name) + 1;
 
/* The Netscape spec doesn't mention quoting of cookie values.
* RFC 2109 $10.1.3 indicates that values must not be quoted.
*
* However, other browsers preserve quoting, so we should, too
*/
if (c->value_was_quoted) {
sprintf(*buf + *used - 1, "\"%s\"", c->value);
*used += 1 + strlen(c->value) + 1;
} else {
/** \todo should we %XX-encode [;HT,SP] ? */
/** \todo Should we strip escaping backslashes? */
sprintf(*buf + *used - 1, "%s", c->value);
*used += strlen(c->value);
}
 
/* We don't send path/domain information -- that's what the
* Netscape spec suggests we should do, anyway. */
} else {
/* RFC2109 or RFC2965 cookie */
sprintf(*buf + *used - 1, "; %s=", c->name);
*used += 2 + strlen(c->name) + 1;
 
/* Value needs quoting if it contains any separator or if
* it needs preserving from the Set-Cookie header */
if (c->value_was_quoted ||
strpbrk(c->value, separators) != NULL) {
sprintf(*buf + *used - 1, "\"%s\"", c->value);
*used += 1 + strlen(c->value) + 1;
} else {
sprintf(*buf + *used - 1, "%s", c->value);
*used += strlen(c->value);
}
 
if (c->path_from_set) {
/* Path, quoted if necessary */
sprintf(*buf + *used - 1, "; $Path=");
*used += 8;
 
if (strpbrk(c->path, separators) != NULL) {
sprintf(*buf + *used - 1, "\"%s\"", c->path);
*used += 1 + strlen(c->path) + 1;
} else {
sprintf(*buf + *used - 1, "%s", c->path);
*used += strlen(c->path);
}
}
 
if (c->domain_from_set) {
/* Domain, quoted if necessary */
sprintf(*buf + *used - 1, "; $Domain=");
*used += 10;
 
if (strpbrk(c->domain, separators) != NULL) {
sprintf(*buf + *used - 1, "\"%s\"", c->domain);
*used += 1 + strlen(c->domain) + 1;
} else {
sprintf(*buf + *used - 1, "%s", c->domain);
*used += strlen(c->domain);
}
}
}
 
return true;
}
 
/**
* Load a cookie file into the database
*
* \param filename File to load
*/
void urldb_load_cookies(const char *filename)
{
FILE *fp;
char s[16*1024];
 
assert(filename);
 
fp = fopen(filename, "r");
if (!fp)
return;
 
#define FIND_T { \
for (; *p && *p != '\t'; p++) \
; /* do nothing */ \
if (p >= end) { \
LOG(("Overran input")); \
continue; \
} \
*p++ = '\0'; \
}
 
#define SKIP_T { \
for (; *p && *p == '\t'; p++) \
; /* do nothing */ \
if (p >= end) { \
LOG(("Overran input")); \
continue; \
} \
}
 
while (fgets(s, sizeof s, fp)) {
char *p = s, *end = 0,
*domain, *path, *name, *value, *scheme, *url,
*comment;
int version, domain_specified, path_specified,
secure, http_only, no_destroy, value_quoted;
time_t expires, last_used;
struct cookie_internal_data *c;
 
if(s[0] == 0 || s[0] == '#')
/* Skip blank lines or comments */
continue;
 
s[strlen(s) - 1] = '\0'; /* lose terminating newline */
end = s + strlen(s);
 
/* Look for file version first
* (all input is ignored until this is read)
*/
if (strncasecmp(s, "Version:", 8) == 0) {
FIND_T; SKIP_T; loaded_cookie_file_version = atoi(p);
 
if (loaded_cookie_file_version <
MIN_COOKIE_FILE_VERSION) {
LOG(("Unsupported Cookie file version"));
break;
}
 
continue;
} else if (loaded_cookie_file_version == 0) {
/* Haven't yet seen version; skip this input */
continue;
}
 
/* One cookie/line */
 
/* Parse input */
FIND_T; version = atoi(s);
SKIP_T; domain = p; FIND_T;
SKIP_T; domain_specified = atoi(p); FIND_T;
SKIP_T; path = p; FIND_T;
SKIP_T; path_specified = atoi(p); FIND_T;
SKIP_T; secure = atoi(p); FIND_T;
if (loaded_cookie_file_version > 101) {
/* Introduced in version 1.02 */
SKIP_T; http_only = atoi(p); FIND_T;
} else {
http_only = 0;
}
SKIP_T; expires = (time_t)atoi(p); FIND_T;
SKIP_T; last_used = (time_t)atoi(p); FIND_T;
SKIP_T; no_destroy = atoi(p); FIND_T;
SKIP_T; name = p; FIND_T;
SKIP_T; value = p; FIND_T;
if (loaded_cookie_file_version > 100) {
/* Introduced in version 1.01 */
SKIP_T; value_quoted = atoi(p); FIND_T;
} else {
value_quoted = 0;
}
SKIP_T; scheme = p; FIND_T;
SKIP_T; url = p; FIND_T;
 
/* Comment may have no content, so don't
* use macros as they'll break */
for (; *p && *p == '\t'; p++)
; /* do nothing */
comment = p;
 
assert(p <= end);
 
/* Now create cookie */
c = malloc(sizeof(struct cookie_internal_data));
if (!c)
break;
 
c->name = strdup(name);
c->value = strdup(value);
c->value_was_quoted = value_quoted;
c->comment = strdup(comment);
c->domain_from_set = domain_specified;
c->domain = strdup(domain);
c->path_from_set = path_specified;
c->path = strdup(path);
c->expires = expires;
c->last_used = last_used;
c->secure = secure;
c->http_only = http_only;
c->version = version;
c->no_destroy = no_destroy;
 
if (!(c->name && c->value && c->comment &&
c->domain && c->path)) {
urldb_free_cookie(c);
break;
}
 
if (c->domain[0] != '.') {
lwc_string *scheme_lwc = NULL;
nsurl *url_nsurl = NULL;
 
assert(scheme[0] != 'u');
 
if (nsurl_create(url, &url_nsurl) != NSERROR_OK) {
urldb_free_cookie(c);
break;
}
scheme_lwc = nsurl_get_component(url_nsurl,
NSURL_SCHEME);
 
/* And insert it into database */
if (!urldb_insert_cookie(c, scheme_lwc, url_nsurl)) {
/* Cookie freed for us */
nsurl_unref(url_nsurl);
lwc_string_unref(scheme_lwc);
break;
}
nsurl_unref(url_nsurl);
lwc_string_unref(scheme_lwc);
 
} else {
if (!urldb_insert_cookie(c, NULL, NULL)) {
/* Cookie freed for us */
break;
}
}
}
 
#undef SKIP_T
#undef FIND_T
 
fclose(fp);
}
 
/**
* Delete a cookie
*
* \param domain The cookie's domain
* \param path The cookie's path
* \param name The cookie's name
*/
void urldb_delete_cookie(const char *domain, const char *path,
const char *name)
{
urldb_delete_cookie_hosts(domain, path, name, &db_root);
}
 
void urldb_delete_cookie_hosts(const char *domain, const char *path,
const char *name, struct host_part *parent)
{
struct host_part *h;
assert(parent);
 
urldb_delete_cookie_paths(domain, path, name, &parent->paths);
 
for (h = parent->children; h; h = h->next)
urldb_delete_cookie_hosts(domain, path, name, h);
}
 
void urldb_delete_cookie_paths(const char *domain, const char *path,
const char *name, struct path_data *parent)
{
struct cookie_internal_data *c;
struct path_data *p = parent;
 
assert(parent);
 
do {
for (c = p->cookies; c; c = c->next) {
if (strcmp(c->domain, domain) == 0 &&
strcmp(c->path, path) == 0 &&
strcmp(c->name, name) == 0) {
if (c->prev)
c->prev->next = c->next;
else
p->cookies = c->next;
 
if (c->next)
c->next->prev = c->prev;
else
p->cookies_end = c->prev;
 
cookies_remove((struct cookie_data *)c);
urldb_free_cookie(c);
 
return;
}
}
 
if (p->children) {
p = p->children;
} else {
while (p != parent) {
if (p->next != NULL) {
p = p->next;
break;
}
 
p = p->parent;
}
}
} while(p != parent);
}
 
/**
* Save persistent cookies to file
*
* \param filename Path to save to
*/
void urldb_save_cookies(const char *filename)
{
FILE *fp;
int cookie_file_version = max(loaded_cookie_file_version,
COOKIE_FILE_VERSION);
 
assert(filename);
 
fp = fopen(filename, "w");
if (!fp)
return;
 
fprintf(fp, "# >%s\n", filename);
fprintf(fp, "# NetSurf cookies file.\n"
"#\n"
"# Lines starting with a '#' are comments, "
"blank lines are ignored.\n"
"#\n"
"# All lines prior to \"Version:\t%d\" are discarded.\n"
"#\n"
"# Version\tDomain\tDomain from Set-Cookie\tPath\t"
"Path from Set-Cookie\tSecure\tHTTP-Only\tExpires\tLast used\t"
"No destroy\tName\tValue\tValue was quoted\tScheme\t"
"URL\tComment\n",
cookie_file_version);
fprintf(fp, "Version:\t%d\n", cookie_file_version);
 
urldb_save_cookie_hosts(fp, &db_root);
 
fclose(fp);
}
 
/**
* Save a host subtree's cookies
*
* \param fp File pointer to write to
* \param parent Parent host
*/
void urldb_save_cookie_hosts(FILE *fp, struct host_part *parent)
{
struct host_part *h;
assert(fp && parent);
 
urldb_save_cookie_paths(fp, &parent->paths);
 
for (h = parent->children; h; h = h->next)
urldb_save_cookie_hosts(fp, h);
}
 
/**
* Save a path subtree's cookies
*
* \param fp File pointer to write to
* \param parent Parent path
*/
void urldb_save_cookie_paths(FILE *fp, struct path_data *parent)
{
struct path_data *p = parent;
time_t now = time(NULL);
 
assert(fp && parent);
 
do {
if (p->cookies != NULL) {
struct cookie_internal_data *c;
 
for (c = p->cookies; c != NULL; c = c->next) {
if (c->expires == -1 || c->expires < now)
/* Skip expired & session cookies */
continue;
 
fprintf(fp,
"%d\t%s\t%d\t%s\t%d\t%d\t%d\t%d\t%d\t%d\t"
"%s\t%s\t%d\t%s\t%s\t%s\n",
c->version, c->domain,
c->domain_from_set, c->path,
c->path_from_set, c->secure,
c->http_only,
(int)c->expires, (int)c->last_used,
c->no_destroy, c->name, c->value,
c->value_was_quoted,
p->scheme ? lwc_string_data(p->scheme) :
"unused",
p->url ? nsurl_access(p->url) :
"unused",
c->comment ? c->comment : "");
}
}
 
if (p->children != NULL) {
p = p->children;
} else {
while (p != parent) {
if (p->next != NULL) {
p = p->next;
break;
}
 
p = p->parent;
}
}
} while (p != parent);
}
 
 
/**
* Destroy urldb
*/
void urldb_destroy(void)
{
struct host_part *a, *b;
int i;
 
/* Clean up search trees */
for (i = 0; i < NUM_SEARCH_TREES; i++) {
if (search_trees[i] != &empty)
urldb_destroy_search_tree(search_trees[i]);
}
 
/* And database */
for (a = db_root.children; a; a = b) {
b = a->next;
urldb_destroy_host_tree(a);
}
}
 
/**
* Destroy a host tree
*
* \param root Root node of tree to destroy
*/
void urldb_destroy_host_tree(struct host_part *root)
{
struct host_part *a, *b;
struct path_data *p, *q;
struct prot_space_data *s, *t;
 
/* Destroy children */
for (a = root->children; a; a = b) {
b = a->next;
urldb_destroy_host_tree(a);
}
 
/* Now clean up paths */
for (p = root->paths.children; p; p = q) {
q = p->next;
urldb_destroy_path_tree(p);
}
 
/* Root path */
urldb_destroy_path_node_content(&root->paths);
 
/* Proctection space data */
for (s = root->prot_space; s; s = t) {
t = s->next;
urldb_destroy_prot_space(s);
}
 
/* And ourselves */
free(root->part);
free(root);
}
 
/**
* Destroy a path tree
*
* \param root Root node of tree to destroy
*/
void urldb_destroy_path_tree(struct path_data *root)
{
struct path_data *p = root;
 
do {
if (p->children != NULL) {
p = p->children;
} else {
struct path_data *q = p;
 
while (p != root) {
if (p->next != NULL) {
p = p->next;
break;
}
 
p = p->parent;
 
urldb_destroy_path_node_content(q);
free(q);
 
q = p;
}
 
urldb_destroy_path_node_content(q);
free(q);
}
} while (p != root);
}
 
/**
* Destroy the contents of a path node
*
* \param node Node to destroy contents of (does not destroy node)
*/
void urldb_destroy_path_node_content(struct path_data *node)
{
struct cookie_internal_data *a, *b;
unsigned int i;
 
if (node->url != NULL)
nsurl_unref(node->url);
 
if (node->scheme != NULL)
lwc_string_unref(node->scheme);
 
free(node->segment);
for (i = 0; i < node->frag_cnt; i++)
free(node->fragment[i]);
free(node->fragment);
 
if (node->thumb)
bitmap_destroy(node->thumb);
 
free(node->urld.title);
 
for (a = node->cookies; a; a = b) {
b = a->next;
urldb_destroy_cookie(a);
}
}
 
/**
* Destroy a cookie node
*
* \param c Cookie to destroy
*/
void urldb_destroy_cookie(struct cookie_internal_data *c)
{
free(c->name);
free(c->value);
free(c->comment);
free(c->domain);
free(c->path);
 
free(c);
}
 
/**
* Destroy protection space data
*
* \param space Protection space to destroy
*/
void urldb_destroy_prot_space(struct prot_space_data *space)
{
lwc_string_unref(space->scheme);
free(space->realm);
free(space->auth);
 
free(space);
}
 
 
/**
* Destroy a search tree
*
* \param root Root node of tree to destroy
*/
void urldb_destroy_search_tree(struct search_node *root)
{
/* Destroy children */
if (root->left != &empty)
urldb_destroy_search_tree(root->left);
if (root->right != &empty)
urldb_destroy_search_tree(root->right);
 
/* And destroy ourselves */
free(root);
}
 
/contrib/network/netsurf/netsurf/content/urldb.h
0,0 → 1,124
/*
* Copyright 2006 John M Bell <jmb202@ecs.soton.ac.uk>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
 
/** \file
* Unified URL information database (interface)
*/
 
#ifndef _NETSURF_CONTENT_URLDB_H_
#define _NETSURF_CONTENT_URLDB_H_
 
#include <stdbool.h>
#include <time.h>
#include "content/content.h"
#include "content/content_type.h"
#include "utils/nsurl.h"
 
typedef enum {
COOKIE_NETSCAPE = 0,
COOKIE_RFC2109 = 1,
COOKIE_RFC2965 = 2
} cookie_version;
 
struct url_data {
const char *title; /**< Resource title */
unsigned int visits; /**< Visit count */
time_t last_visit; /**< Last visit time */
content_type type; /**< Type of resource */
};
 
struct cookie_data {
const char *name; /**< Cookie name */
const char *value; /**< Cookie value */
const bool value_was_quoted; /**< Value was quoted in Set-Cookie: */
const char *comment; /**< Cookie comment */
const bool domain_from_set; /**< Domain came from Set-Cookie: header */
const char *domain; /**< Domain */
const bool path_from_set; /**< Path came from Set-Cookie: header */
const char *path; /**< Path */
const time_t expires; /**< Expiry timestamp, or 1 for session */
const time_t last_used; /**< Last used time */
const bool secure; /**< Only send for HTTPS requests */
const bool http_only; /**< Only expose to HTTP(S) requests */
cookie_version version; /**< Specification compliance */
const bool no_destroy; /**< Never destroy this cookie,
* unless it's expired */
 
const struct cookie_data *prev; /**< Previous in list */
const struct cookie_data *next; /**< Next in list */
};
 
struct bitmap;
 
/* Destruction */
void urldb_destroy(void);
 
/* Persistence support */
void urldb_load(const char *filename);
void urldb_save(const char *filename);
void urldb_set_url_persistence(nsurl *url, bool persist);
 
/* URL insertion */
bool urldb_add_url(nsurl *url);
struct host_part *urldb_add_host(const char *host);
struct path_data *urldb_add_path(lwc_string *scheme, unsigned int port,
const struct host_part *host, char *path_query,
lwc_string *fragment, nsurl *url);
 
/* URL data modification / lookup */
void urldb_set_url_title(nsurl *url, const char *title);
void urldb_set_url_content_type(nsurl *url, content_type type);
void urldb_update_url_visit_data(nsurl *url);
void urldb_reset_url_visit_data(nsurl *url);
const struct url_data *urldb_get_url_data(nsurl *url);
nsurl *urldb_get_url(nsurl *url);
 
/* Authentication modification / lookup */
void urldb_set_auth_details(nsurl *url, const char *realm,
const char *auth);
const char *urldb_get_auth_details(nsurl *url, const char *realm);
 
/* SSL certificate permissions */
void urldb_set_cert_permissions(nsurl *url, bool permit);
bool urldb_get_cert_permissions(nsurl *url);
 
/* Thumbnail handling */
void urldb_set_thumbnail(nsurl *url, struct bitmap *bitmap);
struct bitmap *urldb_get_thumbnail(nsurl *url);
 
/* URL completion */
void urldb_iterate_partial(const char *prefix,
bool (*callback)(nsurl *url,
const struct url_data *data));
 
/* Iteration */
void urldb_iterate_entries(bool (*callback)(nsurl *url,
const struct url_data *data));
void urldb_iterate_cookies(bool (*callback)(const struct cookie_data *cookie));
 
/* Debug */
void urldb_dump(void);
 
/* Cookies */
bool urldb_set_cookie(const char *header, nsurl *url, nsurl *referer);
char *urldb_get_cookie(nsurl *url, bool include_http_only);
void urldb_delete_cookie(const char *domain, const char *path, const char *name);
void urldb_load_cookies(const char *filename);
void urldb_save_cookies(const char *filename);
 
#endif