1 /*
2  * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /**
20  * \file
21  * High-level resource cache implementation.
22  */
23 
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "utils/http.h"
29 #include "utils/log.h"
30 #include "utils/messages.h"
31 #include "utils/ring.h"
32 #include "utils/utils.h"
33 #include "netsurf/misc.h"
34 #include "netsurf/content.h"
35 #include "desktop/gui_internal.h"
36 
37 #include "content/mimesniff.h"
38 #include "content/hlcache.h"
39 // Note, this is *ONLY* so that we can abort cleanly during shutdown of the cache
40 #include "content/content_protected.h"
41 #include "content/content_factory.h"
42 
43 typedef struct hlcache_entry hlcache_entry;
44 typedef struct hlcache_retrieval_ctx hlcache_retrieval_ctx;
45 
46 /** High-level cache retrieval context */
47 struct hlcache_retrieval_ctx {
48 	struct hlcache_retrieval_ctx *r_prev; /**< Previous retrieval context in the ring */
49 	struct hlcache_retrieval_ctx *r_next; /**< Next retrieval context in the ring */
50 
51 	llcache_handle *llcache;	/**< Low-level cache handle */
52 
53 	hlcache_handle *handle;		/**< High-level handle for object */
54 
55 	uint32_t flags;			/**< Retrieval flags */
56 
57 	content_type accepted_types;	/**< Accepted types */
58 
59 	hlcache_child_context child;	/**< Child context */
60 
61 	bool migrate_target;		/**< Whether this context is the migration target */
62 };
63 
64 /** High-level cache handle */
65 struct hlcache_handle {
66 	hlcache_entry *entry;		/**< Pointer to cache entry */
67 
68 	hlcache_handle_callback cb;	/**< Client callback */
69 	void *pw;			/**< Client data */
70 };
71 
72 /** Entry in high-level cache */
73 struct hlcache_entry {
74 	struct content *content;	/**< Pointer to associated content */
75 
76 	hlcache_entry *next;		/**< Next sibling */
77 	hlcache_entry *prev;		/**< Previous sibling */
78 };
79 
80 /** Current state of the cache.
81  *
82  * Global state of the cache.
83  */
84 struct hlcache_s {
85 	struct hlcache_parameters params;
86 
87 	/** List of cached content objects */
88 	hlcache_entry *content_list;
89 
90 	/** Ring of retrieval contexts */
91 	hlcache_retrieval_ctx *retrieval_ctx_ring;
92 
93 	/* statistics */
94 	unsigned int hit_count;
95 	unsigned int miss_count;
96 };
97 
98 /** high level cache state */
99 static struct hlcache_s *hlcache = NULL;
100 
101 
102 /******************************************************************************
103  * High-level cache internals						      *
104  ******************************************************************************/
105 
106 
107 /**
108  * Attempt to clean the cache
109  */
hlcache_clean(void * force_clean_flag)110 static void hlcache_clean(void *force_clean_flag)
111 {
112 	hlcache_entry *entry, *next;
113 	bool force_clean = (force_clean_flag != NULL);
114 
115 	for (entry = hlcache->content_list; entry != NULL; entry = next) {
116 		next = entry->next;
117 
118 		if (entry->content == NULL)
119 			continue;
120 
121 		if (content_count_users(entry->content) != 0)
122 			continue;
123 
124 		if (content__get_status(entry->content) == CONTENT_STATUS_LOADING) {
125 			if (force_clean == false)
126 				continue;
127 			NSLOG(netsurf, DEBUG, "Forcing content cleanup during shutdown");
128 			content_abort(entry->content);
129 			content_set_error(entry->content);
130 		}
131 
132 		/** \todo This is over-zealous: all unused contents
133 		 * will be immediately destroyed. Ideally, we want to
134 		 * purge all unused contents that are using stale
135 		 * source data, and enough fresh contents such that
136 		 * the cache fits in the configured cache size limit.
137 		 */
138 
139 		/* Remove entry from cache */
140 		if (entry->prev == NULL)
141 			hlcache->content_list = entry->next;
142 		else
143 			entry->prev->next = entry->next;
144 
145 		if (entry->next != NULL)
146 			entry->next->prev = entry->prev;
147 
148 		/* Destroy content */
149 		content_destroy(entry->content);
150 
151 		/* Destroy entry */
152 		free(entry);
153 	}
154 
155 	/* Attempt to clean the llcache */
156 	llcache_clean(false);
157 
158 	/* Re-schedule ourselves */
159 	guit->misc->schedule(hlcache->params.bg_clean_time, hlcache_clean, NULL);
160 }
161 
162 /**
163  * Determine if the specified MIME type is acceptable
164  *
165  * \param mime_type       MIME type to consider
166  * \param accepted_types  Array of acceptable types, or NULL for any
167  * \param computed_type	  Pointer to location to receive computed type of object
168  * \return True if the type is acceptable, false otherwise
169  */
hlcache_type_is_acceptable(lwc_string * mime_type,content_type accepted_types,content_type * computed_type)170 static bool hlcache_type_is_acceptable(lwc_string *mime_type,
171 		content_type accepted_types, content_type *computed_type)
172 {
173 	content_type type;
174 
175 	type = content_factory_type_from_mime_type(mime_type);
176 
177 	*computed_type = type;
178 
179 	return ((accepted_types & type) != 0);
180 }
181 
182 /**
183  * Veneer between content callback API and hlcache callback API
184  *
185  * \param c	Content to emit message for
186  * \param msg	Message to emit
187  * \param data	Data for message
188  * \param pw	Pointer to private data (hlcache_handle)
189  */
hlcache_content_callback(struct content * c,content_msg msg,const union content_msg_data * data,void * pw)190 static void hlcache_content_callback(struct content *c, content_msg msg,
191 		const union content_msg_data *data, void *pw)
192 {
193 	hlcache_handle *handle = pw;
194 	nserror error = NSERROR_OK;
195 	hlcache_event event = {
196 		.type = msg,
197 	};
198 
199 	if (data != NULL) {
200 		event.data = *data;
201 	}
202 
203 	if (handle->cb != NULL)
204 		error = handle->cb(handle, &event, handle->pw);
205 
206 	if (error != NSERROR_OK)
207 		NSLOG(netsurf, INFO, "Error in callback: %d", error);
208 }
209 
210 /**
211  * Find a content for the high-level cache handle
212  *
213  * \param ctx             High-level cache retrieval context
214  * \param effective_type  Effective MIME type of content
215  * \return NSERROR_OK on success,
216  *         NSERROR_NEED_DATA on success where data is needed,
217  *         appropriate error otherwise
218  *
219  * \pre handle::state == HLCACHE_HANDLE_NEW
220  * \pre Headers must have been received for associated low-level handle
221  * \post Low-level handle is either released, or associated with new content
222  * \post High-level handle is registered with content
223  */
hlcache_find_content(hlcache_retrieval_ctx * ctx,lwc_string * effective_type)224 static nserror hlcache_find_content(hlcache_retrieval_ctx *ctx,
225 		lwc_string *effective_type)
226 {
227 	hlcache_entry *entry;
228 	hlcache_event event;
229 	nserror error = NSERROR_OK;
230 
231 	/* Search list of cached contents for a suitable one */
232 	for (entry = hlcache->content_list; entry != NULL; entry = entry->next) {
233 		hlcache_handle entry_handle = { entry, NULL, NULL };
234 		const llcache_handle *entry_llcache;
235 
236 		if (entry->content == NULL)
237 			continue;
238 
239 		/* Ignore contents in the error state */
240 		if (content_get_status(&entry_handle) == CONTENT_STATUS_ERROR)
241 			continue;
242 
243 		/* Ensure that content is shareable */
244 		if (content_is_shareable(entry->content) == false)
245 			continue;
246 
247 		/* Ensure that quirks mode is acceptable */
248 		if (content_matches_quirks(entry->content,
249 				ctx->child.quirks) == false)
250 			continue;
251 
252 		/* Ensure that content uses same low-level object as
253 		 * low-level handle */
254 		entry_llcache = content_get_llcache_handle(entry->content);
255 
256 		if (llcache_handle_references_same_object(entry_llcache,
257 				ctx->llcache))
258 			break;
259 	}
260 
261 	if (entry == NULL) {
262 		/* No existing entry, so need to create one */
263 		entry = malloc(sizeof(hlcache_entry));
264 		if (entry == NULL)
265 			return NSERROR_NOMEM;
266 
267 		/* Create content using llhandle */
268 		entry->content = content_factory_create_content(ctx->llcache,
269 				ctx->child.charset, ctx->child.quirks,
270 				effective_type);
271 		if (entry->content == NULL) {
272 			free(entry);
273 			return NSERROR_NOMEM;
274 		}
275 
276 		/* Insert into cache */
277 		entry->prev = NULL;
278 		entry->next = hlcache->content_list;
279 		if (hlcache->content_list != NULL)
280 			hlcache->content_list->prev = entry;
281 		hlcache->content_list = entry;
282 
283 		/* Signal to caller that we created a content */
284 		error = NSERROR_NEED_DATA;
285 
286 		hlcache->miss_count++;
287 	} else {
288 		/* Found a suitable content: no longer need low-level handle */
289 		llcache_handle_release(ctx->llcache);
290 		hlcache->hit_count++;
291 	}
292 
293 	/* Associate handle with content */
294 	if (content_add_user(entry->content,
295 			hlcache_content_callback, ctx->handle) == false)
296 		return NSERROR_NOMEM;
297 
298 	/* Associate cache entry with handle */
299 	ctx->handle->entry = entry;
300 
301 	/* Catch handle up with state of content */
302 	if (ctx->handle->cb != NULL) {
303 		content_status status = content_get_status(ctx->handle);
304 
305 		if (status == CONTENT_STATUS_LOADING) {
306 			event.type = CONTENT_MSG_LOADING;
307 			ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
308 		} else if (status == CONTENT_STATUS_READY) {
309 			event.type = CONTENT_MSG_LOADING;
310 			ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
311 
312 			if (ctx->handle->cb != NULL) {
313 				event.type = CONTENT_MSG_READY;
314 				ctx->handle->cb(ctx->handle, &event,
315 						ctx->handle->pw);
316 			}
317 		} else if (status == CONTENT_STATUS_DONE) {
318 			event.type = CONTENT_MSG_LOADING;
319 			ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
320 
321 			if (ctx->handle->cb != NULL) {
322 				event.type = CONTENT_MSG_READY;
323 				ctx->handle->cb(ctx->handle, &event,
324 						ctx->handle->pw);
325 			}
326 
327 			if (ctx->handle->cb != NULL) {
328 				event.type = CONTENT_MSG_DONE;
329 				ctx->handle->cb(ctx->handle, &event,
330 						ctx->handle->pw);
331 			}
332 		}
333 	}
334 
335 	return error;
336 }
337 
338 /**
339  * Migrate a retrieval context into its final destination content
340  *
341  * \param ctx             Context to migrate
342  * \param effective_type  The effective MIME type of the content, or NULL
343  * \return NSERROR_OK on success,
344  *         NSERROR_NEED_DATA on success where data is needed,
345  *         appropriate error otherwise
346  */
hlcache_migrate_ctx(hlcache_retrieval_ctx * ctx,lwc_string * effective_type)347 static nserror hlcache_migrate_ctx(hlcache_retrieval_ctx *ctx,
348 		lwc_string *effective_type)
349 {
350 	content_type type = CONTENT_NONE;
351 	nserror error = NSERROR_OK;
352 
353 	ctx->migrate_target = true;
354 
355 	if ((effective_type != NULL) &&
356 	    hlcache_type_is_acceptable(effective_type,
357 				       ctx->accepted_types,
358 				       &type)) {
359 		error = hlcache_find_content(ctx, effective_type);
360 		if (error != NSERROR_OK && error != NSERROR_NEED_DATA) {
361 			if (ctx->handle->cb != NULL) {
362 				hlcache_event hlevent;
363 
364 				hlevent.type = CONTENT_MSG_ERROR;
365 				hlevent.data.errordata.errorcode = NSERROR_UNKNOWN;
366 				hlevent.data.errordata.errormsg = messages_get("MiscError");
367 
368 				ctx->handle->cb(ctx->handle, &hlevent,
369 						ctx->handle->pw);
370 			}
371 
372 			llcache_handle_abort(ctx->llcache);
373 			llcache_handle_release(ctx->llcache);
374 		}
375 	} else if (type == CONTENT_NONE &&
376 			(ctx->flags & HLCACHE_RETRIEVE_MAY_DOWNLOAD)) {
377 		/* Unknown type, and we can download, so convert */
378 		llcache_handle_force_stream(ctx->llcache);
379 
380 		if (ctx->handle->cb != NULL) {
381 			hlcache_event hlevent;
382 
383 			hlevent.type = CONTENT_MSG_DOWNLOAD;
384 			hlevent.data.download = ctx->llcache;
385 
386 			ctx->handle->cb(ctx->handle, &hlevent,
387 					ctx->handle->pw);
388 		}
389 
390 		/* Ensure caller knows we need data */
391 		error = NSERROR_NEED_DATA;
392 	} else {
393 		/* Unacceptable type: report error */
394 		if (ctx->handle->cb != NULL) {
395 			hlcache_event hlevent;
396 
397 			hlevent.type = CONTENT_MSG_ERROR;
398 			hlevent.data.errordata.errorcode = NSERROR_UNKNOWN;
399 			hlevent.data.errordata.errormsg = messages_get("UnacceptableType");
400 
401 			ctx->handle->cb(ctx->handle, &hlevent,
402 					ctx->handle->pw);
403 		}
404 
405 		llcache_handle_abort(ctx->llcache);
406 		llcache_handle_release(ctx->llcache);
407 	}
408 
409 	ctx->migrate_target = false;
410 
411 	/* No longer require retrieval context */
412 	RING_REMOVE(hlcache->retrieval_ctx_ring, ctx);
413 	free((char *) ctx->child.charset);
414 	free(ctx);
415 
416 	return error;
417 }
418 
419 /**
420  * Handler for low-level cache events
421  *
422  * \param handle  Handle for which event is issued
423  * \param event	  Event data
424  * \param pw	  Pointer to client-specific data
425  * \return NSERROR_OK on success, appropriate error otherwise
426  */
427 static nserror
hlcache_llcache_callback(llcache_handle * handle,const llcache_event * event,void * pw)428 hlcache_llcache_callback(llcache_handle *handle,
429 			 const llcache_event *event,
430 			 void *pw)
431 {
432 	hlcache_retrieval_ctx *ctx = pw;
433 	lwc_string *effective_type = NULL;
434 	nserror error;
435 
436 	assert(ctx->llcache == handle);
437 
438 	switch (event->type) {
439 	case LLCACHE_EVENT_GOT_CERTS:
440 		/* Pass them on upward */
441 		if (ctx->handle->cb != NULL) {
442 			hlcache_event hlevent;
443 
444 			hlevent.type = CONTENT_MSG_SSL_CERTS;
445 			hlevent.data.chain = event->data.chain;
446 
447 			ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
448 		}
449 		break;
450 	case LLCACHE_EVENT_HAD_HEADERS:
451 		error = mimesniff_compute_effective_type(llcache_handle_get_header(handle, "Content-Type"), NULL, 0,
452 				ctx->flags & HLCACHE_RETRIEVE_SNIFF_TYPE,
453 				ctx->accepted_types == CONTENT_IMAGE,
454 				&effective_type);
455 		if (error == NSERROR_OK || error == NSERROR_NOT_FOUND) {
456 			/* If the sniffer was successful or failed to find
457 			 * a Content-Type header when sniffing was
458 			 * prohibited, we must migrate the retrieval context. */
459 			error = hlcache_migrate_ctx(ctx, effective_type);
460 
461 			if (effective_type != NULL)
462 				lwc_string_unref(effective_type);
463 		}
464 
465 		/* No need to report that we need data:
466 		 * we'll get some anyway if there is any */
467 		if (error == NSERROR_NEED_DATA)
468 			error = NSERROR_OK;
469 
470 		return error;
471 
472 		break;
473 	case LLCACHE_EVENT_HAD_DATA:
474 		error = mimesniff_compute_effective_type(llcache_handle_get_header(handle, "Content-Type"),
475 				event->data.data.buf, event->data.data.len,
476 				ctx->flags & HLCACHE_RETRIEVE_SNIFF_TYPE,
477 				ctx->accepted_types == CONTENT_IMAGE,
478 				&effective_type);
479 		if (error != NSERROR_OK) {
480 			assert(0 && "MIME sniff failed with data");
481 		}
482 
483 		error = hlcache_migrate_ctx(ctx, effective_type);
484 
485 		lwc_string_unref(effective_type);
486 
487 		return error;
488 
489 		break;
490 	case LLCACHE_EVENT_DONE:
491 		/* DONE event before we could determine the effective MIME type.
492 		 */
493 		error = mimesniff_compute_effective_type(llcache_handle_get_header(handle, "Content-Type"),
494 				NULL, 0, false, false, &effective_type);
495 		if (error == NSERROR_OK || error == NSERROR_NOT_FOUND) {
496 			error = hlcache_migrate_ctx(ctx, effective_type);
497 
498 			if (effective_type != NULL) {
499 				lwc_string_unref(effective_type);
500 			}
501 
502 			return error;
503 		}
504 
505 		if (ctx->handle->cb != NULL) {
506 			hlcache_event hlevent;
507 
508 			hlevent.type = CONTENT_MSG_ERROR;
509 			hlevent.data.errordata.errorcode = error;
510 			hlevent.data.errordata.errormsg = NULL;
511 
512 			ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
513 		}
514 		break;
515 	case LLCACHE_EVENT_ERROR:
516 		if (ctx->handle->cb != NULL) {
517 			hlcache_event hlevent;
518 
519 			hlevent.type = CONTENT_MSG_ERROR;
520 			hlevent.data.errordata.errorcode = event->data.error.code;
521 			hlevent.data.errordata.errormsg = event->data.error.msg;
522 
523 			ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
524 		}
525 		break;
526 	case LLCACHE_EVENT_PROGRESS:
527 		break;
528 	case LLCACHE_EVENT_REDIRECT:
529 		if (ctx->handle->cb != NULL) {
530 			hlcache_event hlevent;
531 
532 			hlevent.type = CONTENT_MSG_REDIRECT;
533 			hlevent.data.redirect.from = event->data.redirect.from;
534 			hlevent.data.redirect.to = event->data.redirect.to;
535 
536 			ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
537 		}
538 		break;
539 	}
540 
541 	return NSERROR_OK;
542 }
543 
544 
545 /******************************************************************************
546  * Public API								      *
547  ******************************************************************************/
548 
549 
550 nserror
hlcache_initialise(const struct hlcache_parameters * hlcache_parameters)551 hlcache_initialise(const struct hlcache_parameters *hlcache_parameters)
552 {
553 	nserror ret;
554 
555 	hlcache = calloc(1, sizeof(struct hlcache_s));
556 	if (hlcache == NULL) {
557 		return NSERROR_NOMEM;
558 	}
559 
560 	ret = llcache_initialise(&hlcache_parameters->llcache);
561 	if (ret != NSERROR_OK) {
562 		free(hlcache);
563 		hlcache = NULL;
564 		return ret;
565 	}
566 
567 	hlcache->params = *hlcache_parameters;
568 
569 	/* Schedule the cache cleanup */
570 	guit->misc->schedule(hlcache->params.bg_clean_time, hlcache_clean, NULL);
571 
572 	return NSERROR_OK;
573 }
574 
575 /* See hlcache.h for documentation */
hlcache_stop(void)576 void hlcache_stop(void)
577 {
578 	/* Remove the hlcache_clean schedule */
579 	guit->misc->schedule(-1, hlcache_clean, NULL);
580 }
581 
582 /* See hlcache.h for documentation */
hlcache_finalise(void)583 void hlcache_finalise(void)
584 {
585 	uint32_t num_contents, prev_contents;
586 	hlcache_entry *entry;
587 	hlcache_retrieval_ctx *ctx, *next;
588 
589 	/* Obtain initial count of contents remaining */
590 	for (num_contents = 0, entry = hlcache->content_list;
591 			entry != NULL; entry = entry->next) {
592 		num_contents++;
593 	}
594 
595 	NSLOG(netsurf, INFO, "%d contents remain before cache drain",
596 	      num_contents);
597 
598 	/* Drain cache */
599 	do {
600 		prev_contents = num_contents;
601 
602 		hlcache_clean(NULL);
603 
604 		for (num_contents = 0, entry = hlcache->content_list;
605 				entry != NULL; entry = entry->next) {
606 			num_contents++;
607 		}
608 	} while (num_contents > 0 && num_contents != prev_contents);
609 
610 	NSLOG(netsurf, INFO, "%d contents remaining after being polite", num_contents);
611 
612 	/* Drain cache again, forcing the matter */
613 	do {
614 		prev_contents = num_contents;
615 
616 		hlcache_clean(&entry); // Any non-NULL pointer will do
617 
618 		for (num_contents = 0, entry = hlcache->content_list;
619 				entry != NULL; entry = entry->next) {
620 			num_contents++;
621 		}
622 	} while (num_contents > 0 && num_contents != prev_contents);
623 
624 	NSLOG(netsurf, INFO, "%d contents remaining:", num_contents);
625 	for (entry = hlcache->content_list; entry != NULL; entry = entry->next) {
626 		hlcache_handle entry_handle = { entry, NULL, NULL };
627 
628 		if (entry->content != NULL) {
629 			NSLOG(netsurf, INFO, "	%p : %s (%d users)",
630 			      entry,
631 			      nsurl_access(hlcache_handle_get_url(&entry_handle)),
632 			      content_count_users(entry->content));
633 		} else {
634 			NSLOG(netsurf, INFO, "	%p", entry);
635 		}
636 	}
637 
638 	/* Clean up retrieval contexts */
639 	if (hlcache->retrieval_ctx_ring != NULL) {
640 		ctx = hlcache->retrieval_ctx_ring;
641 
642 		do {
643 			next = ctx->r_next;
644 
645 			if (ctx->llcache != NULL)
646 				llcache_handle_release(ctx->llcache);
647 
648 			if (ctx->handle != NULL)
649 				free(ctx->handle);
650 
651 			if (ctx->child.charset != NULL)
652 				free((char *) ctx->child.charset);
653 
654 			free(ctx);
655 
656 			ctx = next;
657 		} while (ctx != hlcache->retrieval_ctx_ring);
658 
659 		hlcache->retrieval_ctx_ring = NULL;
660 	}
661 
662 	NSLOG(netsurf, INFO, "hit/miss %d/%d", hlcache->hit_count,
663 	      hlcache->miss_count);
664 
665 	/* De-schedule ourselves */
666 	guit->misc->schedule(-1, hlcache_clean, NULL);
667 
668 	free(hlcache);
669 	hlcache = NULL;
670 
671 	NSLOG(netsurf, INFO, "Finalising low-level cache");
672 	llcache_finalise();
673 }
674 
675 /* See hlcache.h for documentation */
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)676 nserror hlcache_handle_retrieve(nsurl *url, uint32_t flags,
677 		nsurl *referer, llcache_post_data *post,
678 		hlcache_handle_callback cb, void *pw,
679 		hlcache_child_context *child,
680 		content_type accepted_types, hlcache_handle **result)
681 {
682 	hlcache_retrieval_ctx *ctx;
683 	nserror error;
684 
685 	assert(cb != NULL);
686 
687 	ctx = calloc(1, sizeof(hlcache_retrieval_ctx));
688 	if (ctx == NULL) {
689 		return NSERROR_NOMEM;
690 	}
691 
692 	ctx->handle = calloc(1, sizeof(hlcache_handle));
693 	if (ctx->handle == NULL) {
694 		free(ctx);
695 		return NSERROR_NOMEM;
696 	}
697 
698 	if (child != NULL) {
699 		if (child->charset != NULL) {
700 			ctx->child.charset = strdup(child->charset);
701 			if (ctx->child.charset == NULL) {
702 				free(ctx->handle);
703 				free(ctx);
704 				return NSERROR_NOMEM;
705 			}
706 		}
707 		ctx->child.quirks = child->quirks;
708 	}
709 
710 	ctx->flags = flags;
711 	ctx->accepted_types = accepted_types;
712 
713 	ctx->handle->cb = cb;
714 	ctx->handle->pw = pw;
715 
716 	error = llcache_handle_retrieve(url, flags, referer, post,
717 			hlcache_llcache_callback, ctx,
718 			&ctx->llcache);
719 	if (error != NSERROR_OK) {
720 		/* error retrieving handle so free context and return error */
721 		free((char *) ctx->child.charset);
722 		free(ctx->handle);
723 		free(ctx);
724 	} else {
725 		/* successfully started fetch so add new context to list */
726 		RING_INSERT(hlcache->retrieval_ctx_ring, ctx);
727 
728 		*result = ctx->handle;
729 	}
730 	return error;
731 }
732 
733 /* See hlcache.h for documentation */
hlcache_handle_release(hlcache_handle * handle)734 nserror hlcache_handle_release(hlcache_handle *handle)
735 {
736 	if (handle->entry != NULL) {
737 		content_remove_user(handle->entry->content,
738 				hlcache_content_callback, handle);
739 	} else {
740 		RING_ITERATE_START(struct hlcache_retrieval_ctx,
741 				   hlcache->retrieval_ctx_ring,
742 				   ictx) {
743 			if (ictx->handle == handle &&
744 					ictx->migrate_target == false) {
745 				/* This is the nascent context for us,
746 				 * so abort the fetch */
747 				llcache_handle_abort(ictx->llcache);
748 				llcache_handle_release(ictx->llcache);
749 				/* Remove us from the ring */
750 				RING_REMOVE(hlcache->retrieval_ctx_ring, ictx);
751 				/* Throw us away */
752 				free((char *) ictx->child.charset);
753 				free(ictx);
754 				/* And stop */
755 				RING_ITERATE_STOP(hlcache->retrieval_ctx_ring,
756 						ictx);
757 			}
758 		} RING_ITERATE_END(hlcache->retrieval_ctx_ring, ictx);
759 	}
760 
761 	handle->cb = NULL;
762 	handle->pw = NULL;
763 
764 	free(handle);
765 
766 	return NSERROR_OK;
767 }
768 
769 /* See hlcache.h for documentation */
hlcache_handle_get_content(const hlcache_handle * handle)770 struct content *hlcache_handle_get_content(const hlcache_handle *handle)
771 {
772 	if ((handle != NULL) && (handle->entry != NULL)) {
773 		return handle->entry->content;
774 	}
775 
776 	return NULL;
777 }
778 
779 /* See hlcache.h for documentation */
hlcache_handle_abort(hlcache_handle * handle)780 nserror hlcache_handle_abort(hlcache_handle *handle)
781 {
782 	struct hlcache_entry *entry = handle->entry;
783 	struct content *c;
784 
785 	if (entry == NULL) {
786 		/* This handle is not yet associated with a cache entry.
787 		 * The implication is that the fetch for the handle has
788 		 * not progressed to the point where the entry can be
789 		 * created. */
790 
791 		RING_ITERATE_START(struct hlcache_retrieval_ctx,
792 				   hlcache->retrieval_ctx_ring,
793 				   ictx) {
794 			if (ictx->handle == handle &&
795 					ictx->migrate_target == false) {
796 				/* This is the nascent context for us,
797 				 * so abort the fetch */
798 				llcache_handle_abort(ictx->llcache);
799 				llcache_handle_release(ictx->llcache);
800 				/* Remove us from the ring */
801 				RING_REMOVE(hlcache->retrieval_ctx_ring, ictx);
802 				/* Throw us away */
803 				free((char *) ictx->child.charset);
804 				free(ictx);
805 				/* And stop */
806 				RING_ITERATE_STOP(hlcache->retrieval_ctx_ring,
807 						ictx);
808 			}
809 		} RING_ITERATE_END(hlcache->retrieval_ctx_ring, ictx);
810 
811 		return NSERROR_OK;
812 	}
813 
814 	c = entry->content;
815 
816 	if (content_count_users(c) > 1) {
817 		/* We are not the only user of 'c' so clone it. */
818 		struct content *clone = content_clone(c);
819 
820 		if (clone == NULL)
821 			return NSERROR_NOMEM;
822 
823 		entry = calloc(sizeof(struct hlcache_entry), 1);
824 
825 		if (entry == NULL) {
826 			content_destroy(clone);
827 			return NSERROR_NOMEM;
828 		}
829 
830 		if (content_add_user(clone,
831 				hlcache_content_callback, handle) == false) {
832 			content_destroy(clone);
833 			free(entry);
834 			return NSERROR_NOMEM;
835 		}
836 
837 		content_remove_user(c, hlcache_content_callback, handle);
838 
839 		entry->content = clone;
840 		handle->entry = entry;
841 		entry->prev = NULL;
842 		entry->next = hlcache->content_list;
843 		if (hlcache->content_list != NULL)
844 			hlcache->content_list->prev = entry;
845 		hlcache->content_list = entry;
846 
847 		c = clone;
848 	}
849 
850 	return content_abort(c);
851 }
852 
853 /* See hlcache.h for documentation */
hlcache_handle_replace_callback(hlcache_handle * handle,hlcache_handle_callback cb,void * pw)854 nserror hlcache_handle_replace_callback(hlcache_handle *handle,
855 		hlcache_handle_callback cb, void *pw)
856 {
857 	handle->cb = cb;
858 	handle->pw = pw;
859 
860 	return NSERROR_OK;
861 }
862 
hlcache_handle_clone(hlcache_handle * handle,hlcache_handle ** result)863 nserror hlcache_handle_clone(hlcache_handle *handle, hlcache_handle **result)
864 {
865 	*result = NULL;
866 	return NSERROR_CLONE_FAILED;
867 }
868 
869 /* See hlcache.h for documentation */
hlcache_handle_get_url(const hlcache_handle * handle)870 nsurl *hlcache_handle_get_url(const hlcache_handle *handle)
871 {
872 	nsurl *result = NULL;
873 
874 	assert(handle != NULL);
875 
876 	if (handle->entry != NULL) {
877 		result = content_get_url(handle->entry->content);
878 	} else {
879 		RING_ITERATE_START(struct hlcache_retrieval_ctx,
880 				   hlcache->retrieval_ctx_ring,
881 				   ictx) {
882 			if (ictx->handle == handle) {
883 				/* This is the nascent context for us */
884 				result = llcache_handle_get_url(ictx->llcache);
885 
886 				/* And stop */
887 				RING_ITERATE_STOP(hlcache->retrieval_ctx_ring,
888 						ictx);
889 			}
890 		} RING_ITERATE_END(hlcache->retrieval_ctx_ring, ictx);
891 	}
892 
893 	return result;
894 }
895