netsurf/utils/messages.c

465 lines
10 KiB
C
Raw Normal View History

/*
* Copyright 2004 James Bursa <bursa@users.sourceforge.net>
* Copyright 2006 Rob Kendrick <rjek@rjek.com>
*
* 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
* Localised message support implementation.
*
* Native language messages are loaded from a file and stored hashed by key for
* fast access.
*/
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <zlib.h>
#include <stdarg.h>
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/utils.h"
#include "utils/hashtable.h"
/** Messages are stored in a fixed-size hash table. */
#define HASH_SIZE 101
/** The hash table used to store the standard Messages file for the old API */
static struct hash_table *messages_hash = NULL;
/**
* process a line of input.
*/
static nserror
message_process_line(struct hash_table *hash, uint8_t *ln, int lnlen)
{
uint8_t *value;
uint8_t *colon;
/* empty or comment lines */
if (ln[0] == 0 || ln[0] == '#') {
return NSERROR_OK;
}
/* find first colon as key/value separator */
for (colon = ln; colon < (ln + lnlen); colon++) {
if (*colon == ':') {
break;
}
}
if (colon == (ln + lnlen)) {
/* no colon found */
return NSERROR_INVALID;
}
*colon = 0; /* terminate key */
value = colon + 1;
if (hash_add(hash, (char *)ln, (char *)value) == false) {
LOG("Unable to add %s:%s to hash table", ln, value);
return NSERROR_INVALID;
}
return NSERROR_OK;
}
/**
* Read keys and values from messages file.
*
* \param path pathname of messages file
* \param ctx reference of hash table to merge with.
* \return NSERROR_OK on sucess and ctx updated or error code on faliure.
*/
static nserror messages_load_ctx(const char *path, struct hash_table **ctx)
{
char s[400]; /* line buffer */
gzFile fp; /* compressed file handle */
struct hash_table *nctx; /* new context */
assert(path != NULL);
fp = gzopen(path, "r");
if (!fp) {
LOG("Unable to open messages file \"%.100s\": %s", path, strerror(errno));
return NSERROR_NOT_FOUND;
}
if (*ctx == NULL) {
nctx = hash_create(HASH_SIZE);
} else {
/**
* \note The passed hash is not copied here so this
* updates in place.
*/
nctx = *ctx;
}
if (nctx == NULL) {
LOG("Unable to create hash table for messages file %s", path);
gzclose(fp);
return NSERROR_NOMEM;
}
while (gzgets(fp, s, sizeof s)) {
char *colon, *value;
if (s[0] == 0 || s[0] == '#')
continue;
s[strlen(s) - 1] = 0; /* remove \n at end */
colon = strchr(s, ':');
if (!colon)
continue;
*colon = 0; /* terminate key */
value = colon + 1;
if (hash_add(nctx, s, value) == false) {
LOG("Unable to add %s:%s to hash table of %s", s, value, path);
gzclose(fp);
if (*ctx == NULL) {
hash_destroy(nctx);
}
return NSERROR_INVALID;
}
}
gzclose(fp);
*ctx = nctx;
return NSERROR_OK;
}
/**
* Fast lookup of a message by key.
*
* \param key key of message
* \param ctx context of messages file to look up in
* \return value of message, or key if not found
*/
static const char *
messages_get_ctx(const char *key, struct hash_table *ctx)
{
const char *r = NULL;
assert(key != NULL);
/* allow attempts to retrieve messages before context is set up. */
if (ctx != NULL) {
r = hash_get(ctx, key);
}
/* If called with no context or unable to retrive a value
* return the key.
*/
if (r == NULL) {
r = key;
}
return r;
}
/**
* Free memory used by a messages hash.
* The context will not be valid after this function returns.
*
* \param ctx context of messages file to free
*/
static void messages_destroy_ctx(struct hash_table *ctx)
{
if (ctx == NULL)
return;
hash_destroy(ctx);
}
/* exported interface documented in messages.h */
nserror messages_add_from_file(const char *path)
{
nserror err;
if (path == NULL) {
return NSERROR_BAD_PARAMETER;
}
LOG("Loading Messages from '%s'", path);
err = messages_load_ctx(path, &messages_hash);
return err;
}
/* exported interface documented in messages.h */
nserror messages_add_from_inline(const uint8_t *data, size_t data_size)
{
z_stream strm;
int ret;
uint8_t s[512]; /* line buffer */
size_t used = 0; /* number of bytes in buffer in use */
uint8_t *nl;
/* ensure the hash table is initialised */
if (messages_hash == NULL) {
messages_hash = hash_create(HASH_SIZE);
}
if (messages_hash == NULL) {
LOG("Unable to create hash table");
return NSERROR_NOMEM;
}
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.next_in = (uint8_t *)data;
strm.avail_in = data_size;
ret = inflateInit2(&strm, 32 + MAX_WBITS);
if (ret != Z_OK) {
LOG("inflateInit returned %d", ret);
return NSERROR_INVALID;
}
do {
strm.next_out = s + used;
strm.avail_out = sizeof(s) - used;
ret = inflate(&strm, Z_NO_FLUSH);
if ((ret != Z_OK) && (ret != Z_STREAM_END)) {
break;
}
used = sizeof(s) - strm.avail_out;
while (used > 0) {
/* find nl */
for (nl = &s[0]; nl < &s[used]; nl++) {
if (*nl == '\n') {
break;
}
}
if (nl == &s[used]) {
/* no nl found */
break;
}
/* found newline */
*nl = 0; /* null terminate line */
message_process_line(messages_hash, &s[0], nl - &s[0]);
memmove(&s[0], nl + 1, used - ((nl + 1) - &s[0]) );
used -= ((nl +1) - &s[0]);
}
if (used == sizeof(s)) {
/* entire buffer used and no newline */
LOG("Overlength line");
used = 0;
}
} while (ret != Z_STREAM_END);
inflateEnd(&strm);
if (ret != Z_STREAM_END) {
LOG("inflate returned %d", ret);
return NSERROR_INVALID;
}
return NSERROR_OK;
}
/* exported interface documented in messages.h */
char *messages_get_buff(const char *key, ...)
{
const char *msg_fmt;
char *buff = NULL; /* formatted buffer to return */
int buff_len = 0;
va_list ap;
msg_fmt = messages_get_ctx(key, messages_hash);
va_start(ap, key);
buff_len = vsnprintf(buff, buff_len, msg_fmt, ap);
va_end(ap);
buff = malloc(buff_len + 1);
if (buff != NULL) {
va_start(ap, key);
vsnprintf(buff, buff_len + 1, msg_fmt, ap);
va_end(ap);
}
return buff;
}
/* exported function documented in utils/messages.h */
const char *messages_get(const char *key)
{
return messages_get_ctx(key, messages_hash);
}
/* exported function documented in utils/messages.h */
const char *messages_get_errorcode(nserror code)
{
switch (code) {
case NSERROR_OK:
2014-10-25 16:28:41 +04:00
/* No error */
return messages_get_ctx("OK", messages_hash);
case NSERROR_NOMEM:
2014-10-25 16:28:41 +04:00
/* Memory exhaustion */
return messages_get_ctx("NoMemory", messages_hash);
case NSERROR_NO_FETCH_HANDLER:
2014-10-25 16:28:41 +04:00
/* No fetch handler for URL scheme */
return messages_get_ctx("NoHandler", messages_hash);
case NSERROR_NOT_FOUND:
2014-10-25 16:28:41 +04:00
/* Requested item not found */
return messages_get_ctx("NotFound", messages_hash);
case NSERROR_NOT_DIRECTORY:
/* Missing directory */
return messages_get_ctx("NotDirectory", messages_hash);
case NSERROR_SAVE_FAILED:
2014-10-25 16:28:41 +04:00
/* Failed to save data */
return messages_get_ctx("SaveFailed", messages_hash);
case NSERROR_CLONE_FAILED:
2014-10-25 16:28:41 +04:00
/* Failed to clone handle */
return messages_get_ctx("CloneFailed", messages_hash);
case NSERROR_INIT_FAILED:
2014-10-25 16:28:41 +04:00
/* Initialisation failed */
return messages_get_ctx("InitFailed", messages_hash);
case NSERROR_BMP_ERROR:
/* A BMP error occurred */
return messages_get_ctx("BMPError", messages_hash);
case NSERROR_GIF_ERROR:
/* A GIF error occurred */
return messages_get_ctx("GIFError", messages_hash);
case NSERROR_ICO_ERROR:
/* A ICO error occurred */
return messages_get_ctx("ICOError", messages_hash);
case NSERROR_PNG_ERROR:
/* A PNG error occurred */
return messages_get_ctx("PNGError", messages_hash);
case NSERROR_SVG_ERROR:
/* A SVG error occurred */
return messages_get_ctx("SVGError", messages_hash);
case NSERROR_BAD_ENCODING:
2014-10-25 16:28:41 +04:00
/* The character set is unknown */
return messages_get_ctx("BadEncoding", messages_hash);
case NSERROR_NEED_DATA:
2014-10-25 16:28:41 +04:00
/* More data needed */
return messages_get_ctx("NeedData", messages_hash);
case NSERROR_ENCODING_CHANGE:
2014-10-25 16:28:41 +04:00
/* The character set encoding change was unhandled */
return messages_get_ctx("EncodingChanged", messages_hash);
case NSERROR_BAD_PARAMETER:
2014-10-25 16:28:41 +04:00
/* Bad Parameter */
return messages_get_ctx("BadParameter", messages_hash);
case NSERROR_INVALID:
2014-10-25 16:28:41 +04:00
/* Invalid data */
return messages_get_ctx("Invalid", messages_hash);
case NSERROR_BOX_CONVERT:
2014-10-25 16:28:41 +04:00
/* Box conversion failed */
return messages_get_ctx("BoxConvert", messages_hash);
case NSERROR_STOPPED:
2014-10-25 16:28:41 +04:00
/* Content conversion stopped */
return messages_get_ctx("Stopped", messages_hash);
case NSERROR_DOM:
2014-10-25 16:28:41 +04:00
/* DOM call returned error */
return messages_get_ctx("ParsingFail", messages_hash);
case NSERROR_CSS:
/* CSS call returned error */
return messages_get_ctx("CSSGeneric", messages_hash);
case NSERROR_CSS_BASE:
2014-10-25 16:28:41 +04:00
/* CSS base sheet failed */
return messages_get_ctx("CSSBase", messages_hash);
case NSERROR_BAD_URL:
2014-10-25 16:28:41 +04:00
/* Bad URL */
return messages_get_ctx("BadURL", messages_hash);
case NSERROR_BAD_CONTENT:
/* Bad Content */
return messages_get_ctx("BadContent", messages_hash);
case NSERROR_FRAME_DEPTH:
/* Exceeded frame depth */
return messages_get_ctx("FrameDepth", messages_hash);
case NSERROR_PERMISSION:
/* Permission error */
return messages_get_ctx("PermissionError", messages_hash);
case NSERROR_BAD_SIZE:
/* Bad size */
return messages_get_ctx("BadSize", messages_hash);
case NSERROR_NOSPACE:
/* Insufficient space */
return messages_get_ctx("NoSpace", messages_hash);
case NSERROR_NOT_IMPLEMENTED:
/* Functionality is not implemented */
return messages_get_ctx("NotImplemented", messages_hash);
case NSERROR_UNKNOWN:
/* Unknown error */
return messages_get_ctx("Unknown", messages_hash);
}
/* The switch has no default, so the compiler should tell us when we
* forget to add messages for new error codes. As such, we should
* never get here.
*/
assert(0);
}
/* exported function documented in utils/messages.h */
void messages_destroy(void)
{
messages_destroy_ctx(messages_hash);
messages_hash = NULL;
}