Beispiel #1
0
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;
}
Beispiel #2
0
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;
}
Beispiel #3
0
/**
 * Find the status code and content type and inform the caller.
 *
 * Return true if the fetch is being aborted.
 */
static bool fetch_curl_process_headers(struct curl_fetch_info *f)
{
	long http_code;
	CURLcode code;
	fetch_msg msg;

	f->had_headers = true;

	if (!f->http_code) {
		code = curl_easy_getinfo(f->curl_handle, CURLINFO_HTTP_CODE,
					 &f->http_code);
		fetch_set_http_code(f->fetch_handle, f->http_code);
		assert(code == CURLE_OK);
	}
	http_code = f->http_code;
	LOG("HTTP status code %li", http_code);

	if (http_code == 304 && !f->post_urlenc && !f->post_multipart) {
		/* Not Modified && GET request */
		msg.type = FETCH_NOTMODIFIED;
		fetch_send_callback(&msg, f->fetch_handle);
		return true;
	}

	/* handle HTTP redirects (3xx response codes) */
	if (300 <= http_code && http_code < 400 && f->location != 0) {
		LOG("FETCH_REDIRECT, '%s'", f->location);
		msg.type = FETCH_REDIRECT;
		msg.data.redirect = f->location;
		fetch_send_callback(&msg, f->fetch_handle);
		return true;
	}

	/* handle HTTP 401 (Authentication errors) */
	if (http_code == 401) {
		msg.type = FETCH_AUTH;
		msg.data.auth.realm = f->realm;
		fetch_send_callback(&msg, f->fetch_handle);
		return true;
	}

	/* handle HTTP errors (non 2xx response codes) */
	if (f->only_2xx && strncmp(nsurl_access(f->url), "http", 4) == 0 &&
			(http_code < 200 || 299 < http_code)) {
		msg.type = FETCH_ERROR;
		msg.data.error = messages_get("Not2xx");
		fetch_send_callback(&msg, f->fetch_handle);
		return true;
	}

	if (f->abort)
		return true;

	return false;
}
Beispiel #4
0
/** 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;
}
Beispiel #5
0
/**
 * called from poll to progress fetch.
 *
 * \todo This is currently completely unimplemented and just returns 204
 */
static bool fetch_javascript_handler(struct fetch_javascript_context *ctx)
{
	fetch_msg msg;
	int code = 204;

	/* content is going to return error code */
	fetch_set_http_code(ctx->fetchh, code);

	msg.type = FETCH_FINISHED;
	fetch_javascript_send_callback(&msg, ctx);

	return true;
}
Beispiel #6
0
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;
}
Beispiel #7
0
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;
}
Beispiel #8
0
/**
 * Callback function for cURL.
 */
static size_t fetch_curl_data(char *data, size_t size, size_t nmemb, void *_f)
{
	struct curl_fetch_info *f = _f;
	CURLcode code;
	fetch_msg msg;

	/* ensure we only have to get this information once */
	if (!f->http_code)
	{
		code = curl_easy_getinfo(f->curl_handle, CURLINFO_HTTP_CODE,
					 &f->http_code);
		fetch_set_http_code(f->fetch_handle, f->http_code);
		assert(code == CURLE_OK);
	}

	/* ignore body if this is a 401 reply by skipping it and reset
	 * the HTTP response code to enable follow up fetches.
	 */
	if (f->http_code == 401)
	{
		f->http_code = 0;
		return size * nmemb;
	}

	if (f->abort || (!f->had_headers && fetch_curl_process_headers(f))) {
		f->stopped = true;
		return 0;
	}

	/* send data to the caller */
	msg.type = FETCH_DATA;
	msg.data.header_or_data.buf = (const uint8_t *) data;
	msg.data.header_or_data.len = size * nmemb;
	fetch_send_callback(&msg, f->fetch_handle);

	if (f->abort) {
		f->stopped = true;
		return 0;
	}

	return size * nmemb;
}
Beispiel #9
0
static void fetch_rsrc_poll(lwc_string *scheme)
{
	fetch_msg msg;
	struct fetch_rsrc_context *c, *next;

	if (ring == NULL) return;
	
	/* Iterate over ring, processing each pending fetch */
	c = ring;
	do {
		/* Take a copy of the next pointer as we may destroy
		 * the ring item we're currently processing */
		next = c->r_next;

		/* 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) {
			continue;
		}

		/* Only process non-aborted fetches */
		if (!c->aborted && fetch_rsrc_process(c) == true) {
			char header[64];

			fetch_set_http_code(c->parent_fetch, 200);
			LOG(("setting rsrc: MIME type to %s, length to %zd",
					c->mimetype, c->datalen));
			/* Any callback can result in the fetch being aborted.
			 * Therefore, we _must_ check for this after _every_
			 * call to fetch_rsrc_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_rsrc_send_callback(&msg, c);

			snprintf(header, sizeof header, "Content-Length: %zd",
					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_rsrc_send_callback(&msg, c);

			if (!c->aborted) {
				msg.type = FETCH_DATA;
				msg.data.header_or_data.buf = (const uint8_t *) c->data;
				msg.data.header_or_data.len = c->datalen;
				fetch_rsrc_send_callback(&msg, c);
			}
			if (!c->aborted) {
				msg.type = FETCH_FINISHED;
				fetch_rsrc_send_callback(&msg, c);
			}
		} else {
			LOG(("Processing of %s failed!", c->url));

			/* Ensure that we're unlocked here. If we aren't, 
			 * then fetch_rsrc_process() is broken.
			 */
			assert(c->locked == false);
		}

		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);
}
Beispiel #10
0
void ami_fetch_file_poll(const char *scheme_ignored)
{
	struct nsObject *node;
	struct nsObject *nnode;
	struct ami_file_fetch_info *fetch;
	fetch_error_code errorcode;
	
	if(IsMinListEmpty(ami_file_fetcher_list)) return;

	node = (struct nsObject *)GetHead((struct List *)ami_file_fetcher_list);

	do
	{
		errorcode = FETCH_ERROR_NO_ERROR;
		nnode=(struct nsObject *)GetSucc((struct Node *)node);

		fetch = (struct ami_file_fetch_info *)node->objstruct;

		if(fetch->locked) continue;

		if(!fetch->aborted)
		{
			if(fetch->fh)
			{
				ULONG len;

				len = FRead(fetch->fh,ami_file_fetcher_buffer,1,1024);

				if (len == (ULONG)-1)
					errorcode = FETCH_ERROR_MISC;
				else if (len > 0)
					ami_fetch_file_send_callback(
							FETCH_DATA, fetch,
							ami_file_fetcher_buffer,
							len, errorcode);

				if((len<1024) && (!fetch->aborted))
				{
					ami_fetch_file_send_callback(FETCH_FINISHED,
						fetch, NULL, 0,
						errorcode);

					fetch->aborted = true;
				}
			}
			else
			{
				fetch->fh = FOpen(fetch->path,MODE_OLDFILE,0);

				if(fetch->fh)
				{
					char header[64];
					struct ExamineData *fib;
					if(fib = ExamineObjectTags(EX_FileHandleInput,fetch->fh,TAG_DONE))
					{
						fetch->len = fib->FileSize;
						FreeDosObject(DOS_EXAMINEDATA,fib);
					}

					fetch_set_http_code(fetch->fetch_handle,200);
					fetch->mimetype = fetch_mimetype(fetch->path);
					LOG(("mimetype %s len %ld",fetch->mimetype,fetch->len));

					snprintf(header, sizeof header,
							"Content-Type: %s",
							fetch->mimetype);
					ami_fetch_file_send_callback(FETCH_HEADER,
						fetch, header, strlen(header), errorcode);

					snprintf(header, sizeof header,
							"Content-Length: %ld",
							fetch->len);
					ami_fetch_file_send_callback(FETCH_HEADER,
						fetch, header, strlen(header), errorcode);

				}
				else
				{
					STRPTR errorstring;

					errorstring = ASPrintf("%s %s",messages_get("FileError"),fetch->path);
					fetch_set_http_code(fetch->fetch_handle,404);
					
					errorcode = FETCH_ERROR_HTTP_NOT2;
					ami_fetch_file_send_callback(FETCH_ERROR, fetch,
						errorstring, 0,
						errorcode);
					fetch->aborted = true;
					FreeVec(errorstring);
				}
			}
		}

		if(fetch && fetch->aborted)
		{
			fetch_remove_from_queues(fetch->fetch_handle);
			fetch_free(fetch->fetch_handle);
			return;
		}
	}while(node=nnode);
}
Beispiel #11
0
/**
 * 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;
}
Beispiel #12
0
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;
}
Beispiel #13
0
/** 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;
}
Beispiel #14
0
/** 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;
}