netsurf/desktop/searchweb.c

564 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 "utils/log.h"
#include "utils/url.h"
#include "utils/nsoption.h"
#include "content/hlcache.h"
#include "desktop/gui_factory.h"
#include "desktop/searchweb.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 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, 0, 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;
}