view irccd/jsapi-util.c @ 1105:96c5f34247d2

misc: remove remnant of GNUmakefile
author David Demelier <markand@malikania.fr>
date Wed, 20 Oct 2021 14:15:23 +0200
parents a5eaf207ecc2
children f06e9761cc90
line wrap: on
line source

/*
 * jsapi-util.c -- Irccd.Util API
 *
 * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <string.h>

#include <utlist.h>

#include <irccd/server.h>
#include <irccd/subst.h>
#include <irccd/util.h>

#include "jsapi-util.h"

struct subspack {
	struct irc_subst_keyword *kw;
	struct irc_subst subst;
};

struct string {
	struct string *next;
	struct string *prev;
	char value[];
};

static inline void
subspack_finish(struct subspack *subst)
{
	for (size_t i = 0; i < subst->subst.keywordsz; ++i) {
		free((char *)subst->kw[i].key);
		free((char *)subst->kw[i].value);
	}

	free(subst->kw);
}

/*
 * Read parameters for Irccd.Util.format function, the object is defined as
 * following:
 *
 * {
 *   date: the date object
 *   flags: the flags (not implemented yet)
 *   field1: a field to substitute in #{} pattern
 *   field2: a field to substitute in #{} pattern
 *   fieldn: ...
 * }
 */
static void
subspack_parse(duk_context *ctx, duk_idx_t index, struct subspack *pkg)
{
	memset(pkg, 0, sizeof (*pkg));

	if (!duk_is_object(ctx, index))
		return;

	/* Use current time by default. */
	pkg->subst.time = time(NULL);

	duk_enum(ctx, index, 0);

	while (duk_next(ctx, -1, 1)) {
		if (!duk_is_string(ctx, -2)) {
			subspack_finish(pkg);
			(void)duk_error(ctx, DUK_ERR_TYPE_ERROR, "keyword name must be a string");
		}

		if (strcmp(duk_get_string(ctx, -2), "date") == 0)
			pkg->subst.time = duk_get_number(ctx, -1) / 1000;
		else {
			pkg->kw = irc_util_reallocarray(pkg->kw, ++pkg->subst.keywordsz,
			    sizeof (*pkg->kw));
			pkg->kw[pkg->subst.keywordsz - 1].key =
			    irc_util_strdup(duk_get_string_default(ctx, -2, ""));
			pkg->kw[pkg->subst.keywordsz - 1].value =
			    irc_util_strdup(duk_get_string_default(ctx, -1, ""));
		}

		duk_pop_n(ctx, 2);
	}

	pkg->subst.flags = IRC_SUBST_DATE |
	                   IRC_SUBST_KEYWORDS |
	                   IRC_SUBST_ENV |
	                   IRC_SUBST_IRC_ATTRS;
	pkg->subst.keywords = pkg->kw;
}

static struct string *
string_new(const char *v)
{
	struct string *s;
	const size_t len = strlen(v);

	s = irc_util_malloc(sizeof (*s) + len + 1);
	strcpy(s->value, v);

	return s;
}

static void
stringlist_finish(struct string *list)
{
	struct string *s, *tmp;

	DL_FOREACH_SAFE(list, s, tmp)
		free(s);
}

static void
stringlist_concat(struct string **list, const char *value)
{
	struct string *s;
	char *str = irc_util_strdup(value), *token, *p = str;

	while ((token = strtok_r(p, " \t\n", &p))) {
		s = string_new(token);
		DL_APPEND(*list, s);
	}

	free(str);
}

static void
split_from_string(duk_context *ctx, struct string **list)
{
	stringlist_concat(list, duk_require_string(ctx, 0));
}

static void
split_from_array(duk_context *ctx, struct string **list)
{
	duk_enum(ctx, 0, DUK_ENUM_ARRAY_INDICES_ONLY);

	while (duk_next(ctx, -1, 1)) {
		stringlist_concat(list, duk_to_string(ctx, -1));
		duk_pop_2(ctx);
	}
}

static void
split(duk_context *ctx, duk_idx_t index, struct string **list)
{
	duk_require_type_mask(ctx, index, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_STRING);

	if (duk_is_string(ctx, index))
		split_from_string(ctx, list);
	else if (duk_is_array(ctx, index))
		split_from_array(ctx, list);
}

static int
limit(duk_context *ctx, duk_idx_t index, const char *name, size_t value)
{
	int newvalue;

	if (duk_get_top(ctx) < index || !duk_is_number(ctx, index))
		return value;

	newvalue = duk_to_int(ctx, index);

	if (newvalue <= 0)
		(void)duk_error(ctx, DUK_ERR_RANGE_ERROR,
		    "argument %d (%s) must be positive", index, name);

	return newvalue;
}

static char *
join(duk_context *ctx, size_t maxc, size_t maxl, const struct string *tokens)
{
	FILE *fp;
	char *out = NULL;
	size_t outsz = 0, linesz = 0, tokensz, lineavail = maxl;
	const struct string *token;

	if (!(fp = open_memstream(&out, &outsz)))
		return NULL;

	DL_FOREACH(tokens, token) {
		tokensz = strlen(token->value);

		if (tokensz > maxc) {
			fclose(fp);
			duk_push_error_object(ctx, DUK_ERR_RANGE_ERROR,
			    "token '%s' could not fit in maxc limit (%zu)", token->value, maxc);
			return NULL;
		}

		/*
		 * If there is something at the beginning of the line, we must
		 * append a space.
		 */
		if (linesz > 0)
			tokensz++;

		/*
		 * This token is going past the maximum of the current line so
		 * we append a newline character and reset the length to start
		 * a "new" one.
		 */
		if (linesz + tokensz > maxc) {
			if (--lineavail == 0) {
				fclose(fp);
				duk_push_error_object(ctx, DUK_ERR_RANGE_ERROR, "number of lines exceeds maxl (%zu)", maxl);
				return NULL;
			}

			fputc('\n', fp);
			linesz = 0;
		}

		linesz += fprintf(fp, "%s%s", linesz > 0 ? " " : "", token->value);
	}

	fflush(fp);
	fclose(fp);

	return out;
}

static int
Util_cut(duk_context *ctx)
{
	struct string *tokens = NULL;
	size_t maxc, maxl, i = 0;
	char *lines, *line, *p;

	maxc = limit(ctx, 1, "maxc", 72);
	maxl = limit(ctx, 2, "maxl", SIZE_MAX);

	/* Construct a list of words from a string or an array of strings.  */
	split(ctx, 0, &tokens);

	/* Join as new lines with a limit of maximum columns and lines. */
	if (!(lines = join(ctx, maxc, maxl, tokens))) {
		stringlist_finish(tokens);
		return duk_throw(ctx);
	}

	duk_push_array(ctx);

	for (p = lines; (line = strtok_r(p, "\n", &p)); ) {
		duk_push_string(ctx, line);
		duk_put_prop_index(ctx, -2, i++);
	}

	stringlist_finish(tokens);
	free(lines);

	return 1;
}

static int
Util_format(duk_context *ctx)
{
	const char *str = duk_require_string(ctx, 0);
	struct subspack pkg;
	char buf[1024] = {0};

	subspack_parse(ctx, 1, &pkg);
	irc_subst(buf, sizeof (buf), str, &pkg.subst);
	duk_push_string(ctx, buf);
	subspack_finish(&pkg);

	return 1;
}

static int
Util_splituser(duk_context *ctx)
{
	struct irc_server_user user;

	irc_server_split(duk_require_string(ctx, 0), &user);
	duk_push_string(ctx, user.nickname);

	return 1;
}

static int
Util_splithost(duk_context *ctx)
{
	struct irc_server_user user;

	irc_server_split(duk_require_string(ctx, 0), &user);
	duk_push_string(ctx, user.host);

	return 1;
}

static const duk_function_list_entry functions[] = {
	{ "cut",        Util_cut,       DUK_VARARGS     },
	{ "format",     Util_format,    DUK_VARARGS     },
	{ "splituser",  Util_splituser, 1               },
	{ "splithost",  Util_splithost, 1               },
	{ NULL,         NULL,           0               }
};

void
jsapi_util_load(duk_context *ctx)
{
	duk_get_global_string(ctx, "Irccd");
	duk_push_object(ctx);
	duk_put_function_list(ctx, -1, functions);
	duk_put_prop_string(ctx, -2, "Util");
	duk_pop(ctx);
}