1 /*
2  * Copyright 2014 Vincent Sanders <vince@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  * \brief core web search facilities implementation.
22  */
23 
24 #include <stdlib.h>
25 
26 #include "utils/utils.h"
27 #include "utils/log.h"
28 #include "utils/url.h"
29 #include "utils/nsoption.h"
30 #include "netsurf/content.h"
31 #include "content/hlcache.h"
32 
33 #include "desktop/searchweb.h"
34 #include "desktop/gui_internal.h"
35 
36 struct search_provider {
37 	char *name; /**< readable name such as 'google', 'yahoo', etc */
38 	char *hostname; /**< host address such as www.google.com */
39 	char *searchstring; /** < such as "www.google.com?search=%s" */
40 	char *ico; /** < location of domain's favicon */
41 	hlcache_handle *ico_handle;
42 };
43 
44 static struct search_web_ctx_s {
45 	struct search_provider *providers; /* web search providers */
46 	size_t providers_count; /* number of providers */
47 
48 	size_t current; /* current provider */
49 
50 	hlcache_handle *default_ico_handle;
51 
52 } search_web_ctx;
53 
54 
55 static const char *default_providers = "Google|www.google.com|http://www.google.com/search?q=%s|http://www.google.com/favicon.ico|\n";
56 
57 static const char *default_search_icon_url = "resource:icons/search.png";
58 
59 
60 /**
61  * Read providers file.
62  *
63  * Allocates storage of sufficient size for the providers file and
64  * reads the entire file in.
65  *
66  * \param fname The filename to read.
67  * \param providers_out A pointer to place the result buffer in.
68  * \param providers_size_out Size of buffer.
69  * \return NSERROR_OK and providers_out updated or appropriate error code.
70  */
71 static nserror
read_providers(const char * fname,char ** providers_out,size_t * providers_size_out)72 read_providers(const char *fname,
73 	       char **providers_out,
74 	       size_t *providers_size_out)
75 {
76 	FILE *providersf;
77 	long ftellsize;
78 	size_t fsize;
79 	char *providersd;
80 
81 	if (fname == NULL) {
82 		return NSERROR_BAD_PARAMETER;
83 	}
84 
85 	providersf = fopen(fname, "r");
86 	if (providersf == NULL) {
87 		return NSERROR_NOT_FOUND;
88 	}
89 
90 	if (fseek(providersf, 0, SEEK_END) != 0) {
91 		fclose(providersf);
92 		return NSERROR_INVALID;
93 	}
94 
95 	ftellsize = ftell(providersf);
96 	if (ftellsize < 0) {
97 		fclose(providersf);
98 		return NSERROR_INVALID;
99 	}
100 	fsize = ftellsize;
101 
102 	if (fseek(providersf, 0, SEEK_SET) != 0) {
103 		fclose(providersf);
104 		return NSERROR_INVALID;
105 	}
106 
107 	providersd = malloc(fsize + 1);
108 	if (providersd == NULL) {
109 		fclose(providersf);
110 		return NSERROR_NOMEM;
111 	}
112 
113 	if (fread(providersd, 1, fsize, providersf) != fsize) {
114 		fclose(providersf);
115 		free(providersd);
116 		return NSERROR_BAD_SIZE;
117 	}
118 	providersd[fsize] = 0; /* ensure null terminated */
119 
120 	fclose(providersf);
121 
122 	*providers_out = providersd;
123 	*providers_size_out = fsize;
124 
125 	return NSERROR_OK;
126 }
127 
128 /**
129  * parse search providers from a memory block.
130  *
131  * \param providersd The provider info data.
132  * \param providers_size The size of the provider data.
133  * \param providers_out The resulting provider array.
134  * \param providers_count The number of providers in the output array.
135  * \return NSERROR_OK on success or error code on failure.
136  */
137 static nserror
parse_providers(char * providersd,size_t providers_size,struct search_provider ** providers_out,size_t * providers_count)138 parse_providers(char *providersd,
139 		size_t providers_size,
140 		struct search_provider **providers_out,
141 		size_t *providers_count)
142 {
143 	size_t pcount = 0; /* number of providers */
144 	size_t pidx;
145 	char *nl = providersd;
146 	struct search_provider *providers;
147 
148 	/* count newlines */
149 	while (nl != NULL) {
150 		nl = strchr(nl, '\n');
151 		if (nl != NULL) {
152 			nl++;
153 			pcount+=1;
154 		}
155 	}
156 
157 	if (pcount == 0) {
158 		return NSERROR_INVALID;
159 	}
160 
161 	providers = malloc(pcount * sizeof(*providers));
162 	if (providers == NULL) {
163 		return NSERROR_NOMEM;
164 	}
165 
166 	nl = providersd;
167 	for (pidx = 0; pidx < pcount; pidx++) {
168 		providers[pidx].name = nl;
169 		nl = strchr(nl, '|');
170 		if (nl == NULL) {
171 			free(providers);
172 			return NSERROR_INVALID;
173 		}
174 		*nl = 0;
175 		nl++;
176 
177 		providers[pidx].hostname = nl;
178 		nl = strchr(nl, '|');
179 		if (nl == NULL) {
180 			free(providers);
181 			return NSERROR_INVALID;
182 		}
183 		*nl = 0;
184 		nl++;
185 
186 		providers[pidx].searchstring = nl;
187 		nl = strchr(nl, '|');
188 		if (nl == NULL) {
189 			free(providers);
190 			return NSERROR_INVALID;
191 		}
192 		*nl = 0;
193 		nl++;
194 
195 		providers[pidx].ico = nl;
196 		nl = strchr(nl, '|');
197 		if (nl == NULL) {
198 			free(providers);
199 			return NSERROR_INVALID;
200 		}
201 		*nl = 0;
202 		nl++;
203 
204 		/* skip newline */
205 		nl = strchr(nl, '\n');
206 		if (nl == NULL) {
207 			free(providers);
208 			return NSERROR_INVALID;
209 		}
210 		nl++;
211 
212 		providers[pidx].ico_handle = NULL;
213 	}
214 
215 	*providers_out = providers;
216 	*providers_count = pcount;
217 
218 	return NSERROR_OK;
219 }
220 
221 /**
222  * create a url for a search provider and a term
223  *
224  * \param provider The provider to use.
225  * \param term The term being searched for.
226  * \param url_out The resulting url.
227  * \return NSERROR_OK on success or appropriate error code.
228  */
229 static nserror
make_search_nsurl(struct search_provider * provider,const char * term,nsurl ** url_out)230 make_search_nsurl(struct search_provider *provider,
231 		const char *term,
232 		nsurl **url_out)
233 {
234 	nserror ret;
235 	nsurl *url;
236 	char *eterm; /* escaped term */
237 	char *searchstr; /* the providers search string */
238 	char *urlstr; /* the escaped term substituted into the provider */
239 	char *urlstro;
240 	size_t urlstr_len;
241 
242 	/* escape the search term and join it to the search url */
243 	ret = url_escape(term, true, NULL, &eterm);
244 	if (ret != NSERROR_OK) {
245 		return ret;
246 	}
247 
248 	searchstr = provider->searchstring;
249 
250 	urlstr_len = strlen(searchstr) + strlen(eterm)  + 1;
251 	urlstro = urlstr = malloc(urlstr_len);
252 	if (urlstr == NULL) {
253 		free(eterm);
254 		return NSERROR_NOMEM;
255 	}
256 
257 	/* composite search url */
258 	for ( ; *searchstr != 0; searchstr++, urlstro++) {
259 		*urlstro = *searchstr;
260 		if ((*searchstr == '%') && (searchstr[1] == 's')) {
261 			searchstr++; /* skip % */
262 			memcpy(urlstro, eterm, strlen(eterm));
263 			urlstro += strlen(eterm) - 1;
264 		}
265 	}
266 	free(eterm);
267 	*urlstro = '\0'; /* ensure string is NULL-terminated */
268 
269 	ret = nsurl_create(urlstr, &url);
270 	free(urlstr);
271 	if (ret != NSERROR_OK) {
272 		return ret;
273 	}
274 
275 	*url_out = url;
276 	return NSERROR_OK;
277 }
278 
279 /**
280  * callback for hlcache icon fetch events.
281  */
282 static nserror
search_web_ico_callback(hlcache_handle * ico,const hlcache_event * event,void * pw)283 search_web_ico_callback(hlcache_handle *ico,
284 			const hlcache_event *event,
285 			void *pw)
286 {
287 	struct search_provider *provider = pw;
288 
289 	switch (event->type) {
290 
291 	case CONTENT_MSG_DONE:
292 		NSLOG(netsurf, INFO, "icon '%s' retrieved",
293 		      nsurl_access(hlcache_handle_get_url(ico)));
294 		guit->search_web->provider_update(provider->name,
295 						  content_get_bitmap(ico));
296 		break;
297 
298 	case CONTENT_MSG_ERROR:
299 		NSLOG(netsurf, INFO, "icon %s error: %s",
300 		      nsurl_access(hlcache_handle_get_url(ico)),
301 		      event->data.errordata.errormsg);
302 
303 		hlcache_handle_release(ico);
304 		/* clear reference to released handle */
305 		provider->ico_handle = NULL;
306 		break;
307 
308 	default:
309 		break;
310 	}
311 
312 	return NSERROR_OK;
313 }
314 
315 /* exported interface documented in desktop/searchweb.h */
316 nserror
search_web_omni(const char * term,enum search_web_omni_flags flags,struct nsurl ** url_out)317 search_web_omni(const char *term,
318 		enum search_web_omni_flags flags,
319 		struct nsurl **url_out)
320 {
321 	nserror ret;
322 	nsurl *url;
323 	char *eterm; /* encoded/altered search term */
324 
325 	if ((flags & SEARCH_WEB_OMNI_SEARCHONLY) == 0) {
326 
327 		/* first check to see if the term is a url */
328 		ret = nsurl_create(term, &url);
329 		if (ret == NSERROR_OK) {
330 			*url_out = url;
331 			return NSERROR_OK;
332 		}
333 
334 		/* try with adding default scheme */
335 		eterm = malloc(strlen(term) + SLEN("http://") + 1);
336 		if (eterm == NULL) {
337 			return NSERROR_NOMEM;
338 		}
339 		sprintf(eterm, "http://%s", term);
340 		ret = nsurl_create(eterm, &url);
341 		free(eterm);
342 		if (ret == NSERROR_OK) {
343 			*url_out = url;
344 			return NSERROR_OK;
345 		}
346 
347 		/* do not pass to search if user has disabled the option */
348 		if (nsoption_bool(search_url_bar) == false) {
349 			return NSERROR_BAD_URL;
350 		}
351 	}
352 
353 	/* must be initialised */
354 	if (search_web_ctx.providers == NULL) {
355 		return NSERROR_INIT_FAILED;
356 	}
357 
358 	/* turn search into a nsurl */
359 	ret = make_search_nsurl(&search_web_ctx.providers[search_web_ctx.current], term, &url);
360 	if (ret != NSERROR_OK) {
361 		return ret;
362 	}
363 
364 	*url_out = url;
365 	return NSERROR_OK;
366 }
367 
368 /* exported interface documented in desktop/searchweb.h */
search_web_get_provider_bitmap(struct bitmap ** bitmap_out)369 nserror search_web_get_provider_bitmap(struct bitmap **bitmap_out)
370 {
371 	struct search_provider *provider;
372 	struct bitmap *ico_bitmap = NULL;
373 
374 	/* must be initialised */
375 	if (search_web_ctx.providers == NULL) {
376 		return NSERROR_INIT_FAILED;
377 	}
378 
379 	provider = &search_web_ctx.providers[search_web_ctx.current];
380 
381 	/* set the icon now (if we can) at least to the default */
382 	if (provider->ico_handle != NULL) {
383 		ico_bitmap = content_get_bitmap(provider->ico_handle);
384 	}
385 	if ((ico_bitmap == NULL) &&
386 	    (search_web_ctx.default_ico_handle != NULL)) {
387 		ico_bitmap = content_get_bitmap(search_web_ctx.default_ico_handle);
388 	}
389 
390 	*bitmap_out = ico_bitmap;
391 	return NSERROR_OK;
392 }
393 
394 
395 /* exported interface documented in desktop/searchweb.h */
search_web_select_provider(int selection)396 nserror search_web_select_provider(int selection)
397 {
398 	struct search_provider *provider;
399 	struct bitmap *ico_bitmap = NULL;
400 
401 	/* must be initialised */
402 	if (search_web_ctx.providers == NULL) {
403 		return NSERROR_INIT_FAILED;
404 	}
405 
406 	/* negative value just selects whatevers current */
407 	if (selection >= 0) {
408 		/* ensure selection lies within acceptable range */
409 		if ((size_t)selection < search_web_ctx.providers_count) {
410 			search_web_ctx.current = selection;
411 		} else {
412 			/* out of range */
413 			search_web_ctx.current = 0;
414 		}
415 	}
416 
417 	provider = &search_web_ctx.providers[search_web_ctx.current];
418 
419 	/* set the icon now (if we can) at least to the default */
420 	if (provider->ico_handle != NULL) {
421 		ico_bitmap = content_get_bitmap(provider->ico_handle);
422 	}
423 	if ((ico_bitmap == NULL) &&
424 	    (search_web_ctx.default_ico_handle != NULL)) {
425 		ico_bitmap = content_get_bitmap(search_web_ctx.default_ico_handle);
426 	}
427 	/* update the callback with the provider change. Bitmap may
428 	 * be NULL at this point.
429 	 */
430 	guit->search_web->provider_update(provider->name, ico_bitmap);
431 
432 
433 	/* if the providers icon has not been retrieved get it now */
434 	if (provider->ico_handle == NULL) {
435 		nsurl *icon_nsurl;
436 		nserror ret;
437 
438 		/* create search icon url */
439 		ret = nsurl_create(provider->ico, &icon_nsurl);
440 		if (ret != NSERROR_OK) {
441 			return ret;
442 		}
443 
444 		ret = hlcache_handle_retrieve(icon_nsurl, 0, NULL, NULL,
445 					      search_web_ico_callback,
446 					      provider,
447 					      NULL, CONTENT_IMAGE,
448 					      &provider->ico_handle);
449 		nsurl_unref(icon_nsurl);
450 		if (ret != NSERROR_OK) {
451 			provider->ico_handle = NULL;
452 			return ret;
453 		}
454 	}
455 
456 	return NSERROR_OK;
457 }
458 
459 /**
460  * callback for hlcache icon fetch events.
461  */
462 static nserror
default_ico_callback(hlcache_handle * ico,const hlcache_event * event,void * pw)463 default_ico_callback(hlcache_handle *ico,
464 		     const hlcache_event *event,
465 		     void *pw)
466 {
467 	struct search_web_ctx_s *ctx = pw;
468 
469 	switch (event->type) {
470 
471 	case CONTENT_MSG_DONE:
472 		NSLOG(netsurf, INFO, "default icon '%s' retrieved",
473 		      nsurl_access(hlcache_handle_get_url(ico)));
474 
475 		/* only set to default icon if providers icon has no handle */
476 		if (ctx->providers[search_web_ctx.current].ico_handle == NULL) {
477 			guit->search_web->provider_update(
478 				ctx->providers[search_web_ctx.current].name,
479 				content_get_bitmap(ico));
480 		}
481 		break;
482 
483 	case CONTENT_MSG_ERROR:
484 		NSLOG(netsurf, INFO, "icon %s error: %s",
485 		      nsurl_access(hlcache_handle_get_url(ico)),
486 		      event->data.errordata.errormsg);
487 
488 		hlcache_handle_release(ico);
489 		/* clear reference to released handle */
490 		ctx->default_ico_handle = NULL;
491 		break;
492 
493 	default:
494 		break;
495 	}
496 
497 	return NSERROR_OK;
498 }
499 
500 /* exported interface documented in desktop/searchweb.h */
search_web_iterate_providers(ssize_t from,const char ** name)501 ssize_t search_web_iterate_providers(ssize_t from, const char **name)
502 {
503 	if (from < 0)
504 		return -1;
505 
506 	if ((size_t)from >= search_web_ctx.providers_count)
507 		return -1;
508 
509 	*name = search_web_ctx.providers[from].name;
510 
511 	return from + 1;
512 }
513 
514 
515 /* exported interface documented in desktop/searchweb.h */
search_web_init(const char * provider_fname)516 nserror search_web_init(const char *provider_fname)
517 {
518 	nserror ret;
519 	char *providers;
520 	size_t providers_size;
521 	nsurl *icon_nsurl;
522 
523 	/* create search icon url */
524 	ret = nsurl_create(default_search_icon_url, &icon_nsurl);
525 	if (ret != NSERROR_OK) {
526 		return ret;
527 	}
528 
529 	/* get a list of providers */
530 	ret = read_providers(provider_fname, &providers, &providers_size);
531 	if (ret != NSERROR_OK) {
532 		providers = strdup(default_providers);
533 		if (providers == NULL) {
534 			return NSERROR_NOMEM;
535 		}
536 		providers_size = strlen(providers);
537 	}
538 
539 	/* parse list of providers */
540 	ret = parse_providers(providers,
541 			      providers_size,
542 			      &search_web_ctx.providers,
543 			      &search_web_ctx.providers_count);
544 	if (ret != NSERROR_OK) {
545 		free(providers);
546 		return ret;
547 	}
548 
549 	/* get default search icon */
550 	ret = hlcache_handle_retrieve(icon_nsurl,
551 				      0,
552 				      NULL,
553 				      NULL,
554 				      default_ico_callback,
555 				      &search_web_ctx,
556 				      NULL,
557 				      CONTENT_IMAGE,
558 				      &search_web_ctx.default_ico_handle);
559 	nsurl_unref(icon_nsurl);
560 	if (ret != NSERROR_OK) {
561 		search_web_ctx.default_ico_handle = NULL;
562 		free(search_web_ctx.providers);
563 		search_web_ctx.providers = NULL;
564 		free(providers);
565 		return ret;
566 	}
567 
568 
569 	return NSERROR_OK;
570 }
571 
572 /* exported interface documented in desktop/searchweb.h */
search_web_finalise(void)573 nserror search_web_finalise(void)
574 {
575 	size_t pidx;
576 
577 	/* must be initialised */
578 	if (search_web_ctx.providers == NULL) {
579 		return NSERROR_INIT_FAILED;
580 	}
581 
582 	if (search_web_ctx.default_ico_handle != NULL) {
583 		hlcache_handle_release(search_web_ctx.default_ico_handle);
584 	}
585 	for (pidx = 0; pidx < search_web_ctx.providers_count; pidx++) {
586 		if (search_web_ctx.providers[pidx].ico_handle != NULL) {
587 			hlcache_handle_release(search_web_ctx.providers[pidx].ico_handle);
588 		}
589 	}
590 
591 	/* All the search provider data is held in a single block for
592 	 * efficiency.
593 	 */
594 	free(search_web_ctx.providers[0].name);
595 
596 	free(search_web_ctx.providers);
597 	search_web_ctx.providers = NULL;
598 
599 	return NSERROR_OK;
600 }
601