564 lines
13 KiB
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/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, 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;
|
|
}
|