netsurf/desktop/searchweb.c
Michael Drake a122b94efd URL escape: Simplify to avoid unnecessary allocation.
This removes the toskip parameter, which was only used by the RISC OS
front end.  The toskip param was used to skip 8 characters which did
not need to be escaped from the start of the URL.  The RISC OS front
end now orders the steps of its URL construction to avoid the need
for this.
2016-07-25 09:04:35 +01:00

562 lines
13 KiB
C

/*
* Copyright 2014 Vincent Sanders <vince@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 desktop/searchweb.c
* \brief core web search facilities implementation.
*/
#include <stdlib.h>
#include "utils/utils.h"
#include "utils/log.h"
#include "utils/url.h"
#include "utils/nsoption.h"
#include "netsurf/content.h"
#include "content/hlcache.h"
#include "desktop/searchweb.h"
#include "desktop/gui_internal.h"
struct search_provider {
char *name; /**< readable name such as 'google', 'yahoo', etc */
char *hostname; /**< host address such as www.google.com */
char *searchstring; /** < such as "www.google.com?search=%s" */
char *ico; /** < location of domain's favicon */
hlcache_handle *ico_handle;
};
static struct search_web_ctx_s {
struct search_provider *providers; /* web search providers */
size_t providers_count; /* number of providers */
size_t current; /* current provider */
hlcache_handle *default_ico_handle;
} search_web_ctx;
static const char *default_providers = "Google|www.google.com|http://www.google.com/search?q=%s|http://www.google.com/favicon.ico|\n";
static const char *default_search_icon_url = "resource:icons/search.png";
/**
* Read providers file.
*
* Allocates stoage of sufficient size for the providers file and
* reads the entire file in.
*
* \param fname The filename to read.
* \param providers_out A pointer to place the result buffer in.
* \param providers_size_out Size of buffer.
* \return NSERROR_OK and providers_out updated or appropriate error code.
*/
static nserror
read_providers(const char *fname,
char **providers_out,
size_t *providers_size_out)
{
FILE *providersf;
long ftellsize;
size_t fsize;
char *providersd;
if (fname == NULL) {
return NSERROR_BAD_PARAMETER;
}
providersf = fopen(fname, "r");
if (providersf == NULL) {
return NSERROR_NOT_FOUND;
}
if (fseek(providersf, 0, SEEK_END) != 0) {
fclose(providersf);
return NSERROR_INVALID;
}
ftellsize = ftell(providersf);
if (ftellsize < 0) {
fclose(providersf);
return NSERROR_INVALID;
}
fsize = ftellsize;
if (fseek(providersf, 0, SEEK_SET) != 0) {
fclose(providersf);
return NSERROR_INVALID;
}
providersd = malloc(fsize + 1);
if (providersd == NULL) {
fclose(providersf);
return NSERROR_NOMEM;
}
if (fread(providersd, 1, fsize, providersf) != fsize) {
fclose(providersf);
free(providersd);
return NSERROR_BAD_SIZE;
}
providersd[fsize] = 0; /* ensure null terminated */
fclose(providersf);
*providers_out = providersd;
*providers_size_out = fsize;
return NSERROR_OK;
}
/**
* parse search providers from a memory block.
*
* \param providersd The provider info data.
* \param providers_size The size of the provider data.
* \param providers_out The resulting provider array.
* \param providers_count The number of providers in the output array.
* \return NSERROR_OK on success or error code on faliure.
*/
static nserror
parse_providers(char *providersd,
size_t providers_size,
struct search_provider **providers_out,
size_t *providers_count)
{
size_t pcount = 0; /* number of providers */
size_t pidx;
char *nl = providersd;
struct search_provider *providers;
/* count newlines */
while (nl != NULL) {
nl = strchr(nl, '\n');
if (nl != NULL) {
nl++;
pcount+=1;
}
}
if (pcount == 0) {
return NSERROR_INVALID;
}
providers = malloc(pcount * sizeof(*providers));
if (providers == NULL) {
return NSERROR_NOMEM;
}
nl = providersd;
for (pidx = 0; pidx < pcount; pidx++) {
providers[pidx].name = nl;
nl = strchr(nl, '|');
if (nl == NULL) {
free(providers);
return NSERROR_INVALID;
}
*nl = 0;
nl++;
providers[pidx].hostname = nl;
nl = strchr(nl, '|');
if (nl == NULL) {
free(providers);
return NSERROR_INVALID;
}
*nl = 0;
nl++;
providers[pidx].searchstring = nl;
nl = strchr(nl, '|');
if (nl == NULL) {
free(providers);
return NSERROR_INVALID;
}
*nl = 0;
nl++;
providers[pidx].ico = nl;
nl = strchr(nl, '|');
if (nl == NULL) {
free(providers);
return NSERROR_INVALID;
}
*nl = 0;
nl++;
/* skip newline */
nl = strchr(nl, '\n');
if (nl == NULL) {
free(providers);
return NSERROR_INVALID;
}
nl++;
providers[pidx].ico_handle = NULL;
}
*providers_out = providers;
*providers_count = pcount;
return NSERROR_OK;
}
/**
* create a url for a search provider and a term
*
* \param provider The provider to use.
* \param term The term being searched for.
* \param url_out The resulting url.
* \return NSERROR_OK on sucess or appropriate error code.
*/
static nserror
make_search_nsurl(struct search_provider *provider,
const char *term,
nsurl **url_out)
{
nserror ret;
nsurl *url;
char *eterm; /* escaped term */
char *searchstr; /* the providers search string */
char *urlstr; /* the escaped term substituted into the provider */
char *urlstro;
size_t urlstr_len;
/* escape the search term and join it to the search url */
ret = url_escape(term, true, NULL, &eterm);
if (ret != NSERROR_OK) {
return ret;
}
searchstr = provider->searchstring;
urlstr_len = strlen(searchstr) + strlen(eterm) + 1;
urlstro = urlstr = malloc(urlstr_len);
if (urlstr == NULL) {
free(eterm);
return NSERROR_NOMEM;
}
/* composite search url */
for ( ; *searchstr != 0; searchstr++, urlstro++) {
*urlstro = *searchstr;
if ((*searchstr == '%') && (searchstr[1] == 's')) {
searchstr++; /* skip % */
memcpy(urlstro, eterm, strlen(eterm));
urlstro += strlen(eterm) - 1;
}
}
free(eterm);
*urlstro = '\0'; /* ensure string is NULL-terminated */
ret = nsurl_create(urlstr, &url);
free(urlstr);
if (ret != NSERROR_OK) {
return ret;
}
*url_out = url;
return NSERROR_OK;
}
/**
* callback for hlcache icon fetch events.
*/
static nserror
search_web_ico_callback(hlcache_handle *ico,
const hlcache_event *event,
void *pw)
{
struct search_provider *provider = pw;
switch (event->type) {
case CONTENT_MSG_DONE:
LOG("icon '%s' retrived", nsurl_access(hlcache_handle_get_url(ico)));
guit->search_web->provider_update(provider->name,
content_get_bitmap(ico));
break;
case CONTENT_MSG_ERROR:
LOG("icon %s error: %s", nsurl_access(hlcache_handle_get_url(ico)), event->data.error);
hlcache_handle_release(ico);
/* clear reference to released handle */
provider->ico_handle = NULL;
break;
default:
break;
}
return NSERROR_OK;
}
/* exported interface documented in desktop/searchweb.h */
nserror
search_web_omni(const char *term,
enum search_web_omni_flags flags,
struct nsurl **url_out)
{
nserror ret;
nsurl *url;
char *eterm; /* encoded/altered search term */
if ((flags & SEARCH_WEB_OMNI_SEARCHONLY) == 0) {
/* first check to see if the term is a url */
ret = nsurl_create(term, &url);
if (ret == NSERROR_OK) {
*url_out = url;
return NSERROR_OK;
}
/* try with adding default scheme */
eterm = malloc(strlen(term) + SLEN("http://") + 1);
if (eterm == NULL) {
return NSERROR_NOMEM;
}
sprintf(eterm, "http://%s", term);
ret = nsurl_create(eterm, &url);
free(eterm);
if (ret == NSERROR_OK) {
*url_out = url;
return NSERROR_OK;
}
/* do not pass to search if user has disabled the option */
if (nsoption_bool(search_url_bar) == false) {
return NSERROR_BAD_URL;
}
}
/* must be initialised */
if (search_web_ctx.providers == NULL) {
return NSERROR_INIT_FAILED;
}
/* turn search into a nsurl */
ret = make_search_nsurl(&search_web_ctx.providers[search_web_ctx.current], term, &url);
if (ret != NSERROR_OK) {
return ret;
}
*url_out = url;
return NSERROR_OK;
}
/* exported interface documented in desktop/searchweb.h */
nserror search_web_select_provider(int selection)
{
struct search_provider *provider;
struct bitmap *ico_bitmap = NULL;
/* must be initialised */
if (search_web_ctx.providers == NULL) {
return NSERROR_INIT_FAILED;
}
/* negative value just selects whatevers current */
if (selection >= 0) {
/* ensure selection lies within acceptable range */
if ((size_t)selection < search_web_ctx.providers_count) {
search_web_ctx.current = selection;
} else {
/* out of range */
search_web_ctx.current = 0;
}
}
provider = &search_web_ctx.providers[search_web_ctx.current];
/* set the icon now (if we can) at least to the default */
if (provider->ico_handle != NULL) {
ico_bitmap = content_get_bitmap(provider->ico_handle);
}
if ((ico_bitmap == NULL) &&
(search_web_ctx.default_ico_handle != NULL)) {
ico_bitmap = content_get_bitmap(search_web_ctx.default_ico_handle);
}
/* update the callback with the provider change. Bitmap may
* be NULL at this point.
*/
guit->search_web->provider_update(provider->name, ico_bitmap);
/* if the providers icon has not been retrived get it now */
if (provider->ico_handle == NULL) {
nsurl *icon_nsurl;
nserror ret;
/* create search icon url */
ret = nsurl_create(provider->ico, &icon_nsurl);
if (ret != NSERROR_OK) {
return ret;
}
ret = hlcache_handle_retrieve(icon_nsurl, 0, NULL, NULL,
search_web_ico_callback,
provider,
NULL, CONTENT_IMAGE,
&provider->ico_handle);
nsurl_unref(icon_nsurl);
if (ret != NSERROR_OK) {
provider->ico_handle = NULL;
return ret;
}
}
return NSERROR_OK;
}
/**
* callback for hlcache icon fetch events.
*/
static nserror
default_ico_callback(hlcache_handle *ico,
const hlcache_event *event,
void *pw)
{
struct search_web_ctx_s *ctx = pw;
switch (event->type) {
case CONTENT_MSG_DONE:
LOG("default icon '%s' retrived", nsurl_access(hlcache_handle_get_url(ico)));
/* only set to default icon if providers icon has no handle */
if (ctx->providers[search_web_ctx.current].ico_handle == NULL) {
guit->search_web->provider_update(
ctx->providers[search_web_ctx.current].name,
content_get_bitmap(ico));
}
break;
case CONTENT_MSG_ERROR:
LOG("icon %s error: %s", nsurl_access(hlcache_handle_get_url(ico)), event->data.error);
hlcache_handle_release(ico);
/* clear reference to released handle */
ctx->default_ico_handle = NULL;
break;
default:
break;
}
return NSERROR_OK;
}
/* exported interface documented in desktop/searchweb.h */
ssize_t search_web_iterate_providers(ssize_t from, const char **name)
{
if (from < 0)
return -1;
if ((size_t)from >= search_web_ctx.providers_count)
return -1;
*name = search_web_ctx.providers[from].name;
return from + 1;
}
/* exported interface documented in desktop/searchweb.h */
nserror search_web_init(const char *provider_fname)
{
nserror ret;
char *providers;
size_t providers_size;
nsurl *icon_nsurl;
/* create search icon url */
ret = nsurl_create(default_search_icon_url, &icon_nsurl);
if (ret != NSERROR_OK) {
return ret;
}
/* get a list of providers */
ret = read_providers(provider_fname, &providers, &providers_size);
if (ret != NSERROR_OK) {
providers = strdup(default_providers);
if (providers == NULL) {
return NSERROR_NOMEM;
}
providers_size = strlen(providers);
}
/* parse list of providers */
ret = parse_providers(providers,
providers_size,
&search_web_ctx.providers,
&search_web_ctx.providers_count);
if (ret != NSERROR_OK) {
free(providers);
return ret;
}
/* get default search icon */
ret = hlcache_handle_retrieve(icon_nsurl, 0, NULL, NULL,
default_ico_callback,
&search_web_ctx,
NULL, CONTENT_IMAGE,
&search_web_ctx.default_ico_handle);
nsurl_unref(icon_nsurl);
if (ret != NSERROR_OK) {
search_web_ctx.default_ico_handle = NULL;
free(search_web_ctx.providers);
search_web_ctx.providers = NULL;
free(providers);
return ret;
}
return NSERROR_OK;
}
/* exported interface documented in desktop/searchweb.h */
nserror search_web_finalise(void)
{
size_t pidx;
/* must be initialised */
if (search_web_ctx.providers == NULL) {
return NSERROR_INIT_FAILED;
}
if (search_web_ctx.default_ico_handle != NULL) {
hlcache_handle_release(search_web_ctx.default_ico_handle);
}
for (pidx = 0; pidx < search_web_ctx.providers_count; pidx++) {
if (search_web_ctx.providers[pidx].ico_handle != NULL) {
hlcache_handle_release(search_web_ctx.providers[pidx].ico_handle);
}
}
/* All the search provider data is held in a single block for
* efficiency.
*/
free(search_web_ctx.providers[0].name);
free(search_web_ctx.providers);
search_web_ctx.providers = NULL;
return NSERROR_OK;
}