/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("&") + 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 = ∅ |
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 |