From 0ba773e7a567939c3d92f01a1f7fc17fbe5301da Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Thu, 20 Oct 2005 20:38:01 +0200 Subject: [PATCH] Add the basics for _browser_ (ecma)scripting ELinks with SEE SEE is David Leonard's Simple Ecmascript Engine. The SEE scripting backend is very raw and not tested very much. The idea was to see what kind of creature SEE is (and contradict pasky's aired opinion that no new features are added anymore ;). echo 'function goto_url() { return 'localhost'; }' > ~/.elinks/hooks.js and get local for maximum security ... FYI: SEE is smaller than Spidermonkey but doesn't have the same kind of data-driven interface, although it looks like it is possible to build that. --- Makefile.config.in | 2 + config/m4/see.m4 | 48 +++++++ configure.in | 9 +- src/scripting/Makefile | 1 + src/scripting/scripting.c | 4 + src/scripting/see/Makefile | 8 ++ src/scripting/see/core.c | 249 +++++++++++++++++++++++++++++++++++++ src/scripting/see/core.h | 21 ++++ src/scripting/see/hooks.c | 232 ++++++++++++++++++++++++++++++++++ src/scripting/see/hooks.h | 10 ++ src/scripting/see/see.c | 23 ++++ src/scripting/see/see.h | 10 ++ 12 files changed, 616 insertions(+), 1 deletion(-) create mode 100644 config/m4/see.m4 create mode 100644 src/scripting/see/Makefile create mode 100644 src/scripting/see/core.c create mode 100644 src/scripting/see/core.h create mode 100644 src/scripting/see/hooks.c create mode 100644 src/scripting/see/hooks.h create mode 100644 src/scripting/see/see.c create mode 100644 src/scripting/see/see.h diff --git a/Makefile.config.in b/Makefile.config.in index 56da64c5..63f4c4cf 100644 --- a/Makefile.config.in +++ b/Makefile.config.in @@ -74,6 +74,7 @@ PYTHON_LIBS = @PYTHON_LIBS@ RANLIB = @RANLIB@ RUBY_CFLAGS = @RUBY_CFLAGS@ RUBY_LIBS = @RUBY_LIBS@ +SEE_CFLAGS = @SEE_CFLAGS@ SPIDERMONKEY_CFLAGS = @SPIDERMONKEY_CFLAGS@ SPIDERMONKEY_LIBS = @SPIDERMONKEY_LIBS@ VERSION = @VERSION@ @@ -130,6 +131,7 @@ CONFIG_RISCOS = @CONFIG_RISCOS@ CONFIG_RUBY = @CONFIG_RUBY@ CONFIG_SCANNER = @CONFIG_SCANNER@ CONFIG_SCRIPTING = @CONFIG_SCRIPTING@ +CONFIG_SEE = @CONFIG_SEE@ CONFIG_SHA1 = @CONFIG_SHA1@ CONFIG_SMALL = @CONFIG_SMALL@ CONFIG_SMB = @CONFIG_SMB@ diff --git a/config/m4/see.m4 b/config/m4/see.m4 new file mode 100644 index 00000000..0c202ee4 --- /dev/null +++ b/config/m4/see.m4 @@ -0,0 +1,48 @@ +AC_DEFUN([EL_CONFIG_SEE], +[ + +enable_see="no"; + +AC_ARG_WITH(see, [ --with-see enable Simple Ecmascript Engine (SEE) support], + [ if test "x$withval" != xno; then enable_see=yes; fi ]) + +# The following is probably bad, ugly and so on. Stolen from Guile's (1.4) +# SEE_FLAGS but I really don't want to require people to have Guile in order +# to compile CVS. Also, the macro seems to be really stupid regarding searching +# for Guile in $PATH etc. --pasky + +AC_MSG_CHECKING([for SEE]) + +if test "$enable_see" = "yes"; then + AC_MSG_RESULT(yes); + ## Based on the SEE_FLAGS macro. + + if test -d "$withval"; then + SEE_PATH="$withval:$PATH" + else + SEE_PATH="$PATH" + fi + + AC_PATH_PROG(SEE_CONFIG, libsee-config, no, $SEE_PATH) + + ## First, let's just see if we can find Guile at all. + if test "$SEE_CONFIG" != no; then + cf_result="yes"; + + SEE_LIBS="`$SEE_CONFIG --libs`" + SEE_CFLAGS="`$SEE_CONFIG --cppflags`" + LIBS="$SEE_LIBS $LIBS" + CPPFLAGS="$CPPFLAGS $SEE_CFLAGS" + EL_CONFIG(CONFIG_SEE, [SEE]) + AC_SUBST(SEE_CFLAGS) + else + if test -n "$withval" && test "x$withval" != xno; then + AC_MSG_ERROR([SEE not found]) + else + AC_MSG_WARN([SEE support disabled]) + fi + fi +else + AC_MSG_RESULT(no); +fi +]) diff --git a/configure.in b/configure.in index cabba3df..194f457d 100644 --- a/configure.in +++ b/configure.in @@ -708,16 +708,23 @@ dnl =================================================================== EL_CONFIG_RUBY +dnl =================================================================== +dnl Check for SEE, optional even if installed. +dnl =================================================================== + +EL_CONFIG_SEE + dnl =================================================================== dnl Setup global scripting dnl =================================================================== -EL_CONFIG_DEPENDS(CONFIG_SCRIPTING, [CONFIG_GUILE CONFIG_LUA CONFIG_PERL CONFIG_PYTHON CONFIG_RUBY], [Scripting]) +EL_CONFIG_DEPENDS(CONFIG_SCRIPTING, [CONFIG_GUILE CONFIG_LUA CONFIG_PERL CONFIG_PYTHON CONFIG_RUBY CONFIG_SEE], [Scripting]) AC_SUBST(CONFIG_GUILE) AC_SUBST(CONFIG_LUA) AC_SUBST(CONFIG_PERL) AC_SUBST(CONFIG_PYTHON) AC_SUBST(CONFIG_RUBY) +AC_SUBST(CONFIG_SEE) AC_SUBST(CONFIG_SCRIPTING) diff --git a/src/scripting/Makefile b/src/scripting/Makefile index 2e3d791c..1d816002 100644 --- a/src/scripting/Makefile +++ b/src/scripting/Makefile @@ -6,6 +6,7 @@ SUBDIRS-$(CONFIG_LUA) += lua SUBDIRS-$(CONFIG_PERL) += perl SUBDIRS-$(CONFIG_PYTHON) += python SUBDIRS-$(CONFIG_RUBY) += ruby +SUBDIRS-$(CONFIG_SEE) += see OBJS = scripting.o diff --git a/src/scripting/scripting.c b/src/scripting/scripting.c index 1bf638f7..d57b749d 100644 --- a/src/scripting/scripting.c +++ b/src/scripting/scripting.c @@ -19,6 +19,7 @@ #include "scripting/perl/perl.h" #include "scripting/python/python.h" #include "scripting/ruby/ruby.h" +#include "scripting/see/see.h" static struct module *scripting_modules[] = { @@ -36,6 +37,9 @@ static struct module *scripting_modules[] = { #endif #ifdef CONFIG_RUBY &ruby_scripting_module, +#endif +#ifdef CONFIG_SEE + &see_scripting_module, #endif NULL, }; diff --git a/src/scripting/see/Makefile b/src/scripting/see/Makefile new file mode 100644 index 00000000..1afc1e3a --- /dev/null +++ b/src/scripting/see/Makefile @@ -0,0 +1,8 @@ +top_builddir=../../.. +include $(top_builddir)/Makefile.config + +INCLUDES += $(SEE_CFLAGS) + +OBJS = see.o hooks.o core.o + +include $(top_srcdir)/Makefile.lib diff --git a/src/scripting/see/core.c b/src/scripting/see/core.c new file mode 100644 index 00000000..b2030766 --- /dev/null +++ b/src/scripting/see/core.c @@ -0,0 +1,249 @@ +/* Ruby interface (scripting engine) */ +/* $Id: core.c,v 1.14 2005/06/14 12:25:21 jonas Exp $ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "elinks.h" + +#include "bfu/dialog.h" +#include "config/home.h" +#include "intl/gettext/libintl.h" +#include "main/module.h" +#include "scripting/see/core.h" +#include "session/session.h" +#include "terminal/terminal.h" +#include "terminal/window.h" +#include "util/error.h" +#include "util/file.h" +#include "util/string.h" + + +#define SEE_HOOKS_FILENAME "hooks.js" + +struct SEE_interpreter see_interpreter; +struct SEE_object *see_browser_object; + +#if 0 +/* SEE strings */ + +static struct SEE_string[] = { + { 'E', 'L', 'i', 'n', 'k', 's' }, + { "goto-url", 0, script_hook_goto_url, NULL }, + { "follow-url", 0, script_hook_follow_url, NULL }, + { "pre-format-html", 0, script_hook_pre_format_html, NULL }, + { "get-proxy", 0, script_hook_get_proxy, NULL }, + { "quit", 0, script_hook_quit, NULL }, + { 0 }, +}; +#endif + +struct string * +convert_see_string(struct string *string, struct SEE_string *source) +{ + unsigned int pos; + + if (!init_string(string)) + return NULL; + + for (pos = 0; pos < source->length; pos++) { + add_char_to_string(string, (unsigned char) source->data[pos]); + } + + return string; +} + + +/* Error reporting. */ + +void +alert_see_error(struct session *ses, unsigned char *msg) +{ + struct terminal *term; + + if (!ses) { + if (list_empty(terminals)) { + usrerror("[SEE script] %s", msg); + return; + } + + term = terminals.next; + + } else { + term = ses->tab->term; + } + + msg = stracpy(msg); + if (!msg) return; + + info_box(term, MSGBOX_NO_TEXT_INTL | MSGBOX_FREE_TEXT, + N_("SEE error"), ALIGN_LEFT, msg); +} + + +/* The ELinks module: */ + +static void +elinks_see_write(struct SEE_interpreter *see, struct SEE_object *self, + struct SEE_object *thisobj, int argc, struct SEE_value **argv, + struct SEE_value *res) +{ + struct SEE_value v; + struct string string; + + SEE_SET_UNDEFINED(res); + + if (!argc) return; + + SEE_ToString(see, argv[0], &v); + if (!convert_see_string(&string, v.u.string)) + return; + + if (list_empty(terminals)) { + usrerror("[SEE] ", string.source); + done_string(&string); + return; + } + + info_box(terminals.next, MSGBOX_NO_TEXT_INTL | MSGBOX_FREE_TEXT, + N_("SEE Message"), ALIGN_LEFT, string.source); +} + +#if 0 +struct object_info browser_object[] = { + "ELinks", + { /* Properties: */ + { "version", SEE_STRING, VERSION, SEE_READONLY }, + { "home", ... }, + { NULL } + }, + { /* Methods: (as name, handler, args) */ + { "write", elinks_see_write, 1 }, + { NULL } + }, +}; +#endif + +static void +init_see_environment(struct SEE_interpreter *see) +{ + unsigned char *home; + struct SEE_object *obj, *elinks; + struct SEE_value value; + struct SEE_string *name; + + /* TODO: Initialize strings. + SEE_intern_global(s_print = &S_print); + * */ + + /* Create the elinks browser object. Add it to the global space */ + elinks = SEE_Object_new(see); + SEE_SET_OBJECT(&value, elinks); + name = SEE_string_sprintf(see, "ELinks"); + SEE_OBJECT_PUT(see, see->Global, name, &value, 0); + + /* Create a string and attach as 'ELinks.version' */ + SEE_SET_STRING(&value, SEE_string_sprintf(see, VERSION)); + name = SEE_string_sprintf(see, "version"); + SEE_OBJECT_PUT(see, elinks, name, &value, SEE_ATTR_READONLY); + + /* Create a string and attach as 'ELinks.home' */ + home = elinks_home ? elinks_home : (unsigned char *) CONFDIR; + SEE_SET_STRING(&value, SEE_string_sprintf(see, home)); + name = SEE_string_sprintf(see, "home"); + SEE_OBJECT_PUT(see, elinks, name, &value, SEE_ATTR_READONLY); + + /* Create a 'write' method and attach to the browser object. */ + name = SEE_string_sprintf(see, "write"); + obj = SEE_cfunction_make(see, elinks_see_write, name, 1); + SEE_SET_OBJECT(&value, obj); + SEE_OBJECT_PUT(see, elinks, name, &value, 0); +} + +static void +see_abort_handler(struct SEE_interpreter *see, const char *msg) +{ + alert_see_error(NULL, (unsigned char *) msg); +} + +static void +see_oom_handler(struct SEE_interpreter *see) +{ + /* XXX: Ignore! */ +} + +void +init_see(struct module *module) +{ + struct SEE_interpreter *see = &see_interpreter; + unsigned char *path; + FILE *file; + + SEE_abort = see_abort_handler; + SEE_mem_exhausted_hook = see_oom_handler; + + /* Initialise an interpreter */ + SEE_interpreter_init(see); + + /* Set up the ELinks module interface. */ + init_see_environment(see); + + if (elinks_home) { + path = straconcat(elinks_home, SEE_HOOKS_FILENAME, NULL); + + } else { + path = stracpy(CONFDIR "/" SEE_HOOKS_FILENAME); + } + + if (!path) return; + + file = fopen(path, "r"); + if (file) { + struct SEE_value result; + struct SEE_input *input; + SEE_try_context_t try_context; + struct SEE_value *exception; + + /* Load ~/.elinks/hooks.js into the interpreter. */ + input = SEE_input_file(see, file, path, NULL); + SEE_TRY(see, try_context) { + SEE_Global_eval(see, input, &result); + } + + SEE_INPUT_CLOSE(input); + + exception = SEE_CAUGHT(try_context); + if (exception) { + SEE_try_context_t try_context2; + struct SEE_value value; + + fprintf(stderr, "errors encountered while reading %s:", path); + SEE_TRY(see, try_context2) { + SEE_ToString(see, exception, &value); + SEE_string_fputs(value.u.string, stderr); +#if 0 + if (ctxt.throw_file) + fprintf(stderr, " (thrown from %s:%d)\n", + ctxt.throw_file, ctxt.throw_line); +#endif + SEE_PrintTraceback(see, stderr); + } + + if (SEE_CAUGHT(try_context2)) { + fprintf(stderr, "exception thrown while " + "printing exception"); +#if 0 + if (ctxt2.throw_file) + fprintf(stderr, " at %s:%d", + ctxt2.throw_file, ctxt2.throw_line); +#endif + fprintf(stderr, "\n"); + } + } + } + + mem_free(path); +} diff --git a/src/scripting/see/core.h b/src/scripting/see/core.h new file mode 100644 index 00000000..7e799da6 --- /dev/null +++ b/src/scripting/see/core.h @@ -0,0 +1,21 @@ +/* $Id: core.h,v 1.3 2005/04/01 17:20:59 zas Exp $ */ + +#ifndef EL__SCRIPTING_SEE_CORE_H +#define EL__SCRIPTING_SEE_CORE_H + +#include + +struct module; +struct session; +struct string; + +extern struct SEE_interpreter see_interpreter; +extern struct SEE_object *see_browser_object; + +struct string *convert_see_string(struct string *string, struct SEE_string *source); +void alert_see_error(struct session *ses, unsigned char *msg); +void see_report_error(struct session *ses, int state); + +void init_see(struct module *module); + +#endif diff --git a/src/scripting/see/hooks.c b/src/scripting/see/hooks.c new file mode 100644 index 00000000..05012dfb --- /dev/null +++ b/src/scripting/see/hooks.c @@ -0,0 +1,232 @@ +/* Ruby scripting hooks */ +/* $Id: hooks.c,v 1.14 2005/06/14 12:25:21 jonas Exp $ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "elinks.h" + +#include "main/event.h" +#include "protocol/uri.h" +#include "scripting/see/core.h" +#include "scripting/see/hooks.h" +#include "session/location.h" +#include "session/session.h" +#include "util/string.h" + + +/* The events that will trigger the functions below and what they are expected + * to do is explained in doc/events.txt */ + +static struct SEE_value * +call_see_hook(struct SEE_interpreter *see, unsigned char *name, + struct SEE_value *args[], int argc, + struct SEE_value *result) +{ + struct SEE_string *hook_name = SEE_string_sprintf(see, name); + struct SEE_value hook; + + SEE_OBJECT_GET(see, see->Global, hook_name, &hook); + + if (SEE_VALUE_GET_TYPE(&hook) != SEE_OBJECT + || !SEE_OBJECT_HAS_CALL(hook.u.object)) + return NULL; + + SEE_OBJECT_CALL(see, hook.u.object, NULL, argc, args, result); +#if 0 + if (error) { + erb_report_error(NULL, error); + return EVENT_HOOK_STATUS_NEXT; + } +#endif + + return result; +} + + +static enum evhook_status +script_hook_goto_url(va_list ap, void *data) +{ + unsigned char **url = va_arg(ap, unsigned char **); + struct session *ses = va_arg(ap, struct session *); + struct SEE_interpreter *see = &see_interpreter; + struct SEE_value args_[2], *args[2] = { &args_[0], &args_[1] }; + struct SEE_value result; + + if (*url == NULL) + return EVENT_HOOK_STATUS_NEXT; + + SEE_SET_STRING(args[0], SEE_string_sprintf(see, "%s", *url)); + + if (!ses || !have_location(ses)) { + SEE_SET_UNDEFINED(args[1]); + } else { + SEE_SET_STRING(args[1], + SEE_string_sprintf(see, "%s", struri(cur_loc(ses)->vs.uri))); + } + + if (!call_see_hook(see, "goto_url", args, sizeof_array(args), &result)) + return EVENT_HOOK_STATUS_NEXT; + + switch (SEE_VALUE_GET_TYPE(&result)) { + case SEE_STRING: + { + struct string new_url; + + if (convert_see_string(&new_url, result.u.string)) + mem_free_set(url, new_url.source); + break; + } + case SEE_NULL: + break; + + default: + alert_see_error(ses, "goto_url_hook must return a string or null"); + } + + return EVENT_HOOK_STATUS_NEXT; +} + +static enum evhook_status +script_hook_follow_url(va_list ap, void *data) +{ + unsigned char **url = va_arg(ap, unsigned char **); + struct session *ses = va_arg(ap, struct session *); + struct SEE_interpreter *see = &see_interpreter; + struct SEE_value args_[1], *args[1] = { &args_[0] }; + struct SEE_value result; + + evhook_use_params(url && ses); + + if (*url == NULL) + return EVENT_HOOK_STATUS_NEXT; + + SEE_SET_STRING(args[0], SEE_string_sprintf(see, "%s", *url)); + + if (!call_see_hook(see, "follow_url", args, sizeof_array(args), &result)) + return EVENT_HOOK_STATUS_NEXT; + + switch (SEE_VALUE_GET_TYPE(&result)) { + case SEE_STRING: + { + struct string new_url; + + if (convert_see_string(&new_url, result.u.string)) + mem_free_set(url, new_url.source); + break; + } + case SEE_NULL: + break; + + default: + alert_see_error(ses, "follow_url_hook must return a string or null"); + } + + return EVENT_HOOK_STATUS_NEXT; +} + +static enum evhook_status +script_hook_pre_format_html(va_list ap, void *data) +{ + unsigned char **html = va_arg(ap, unsigned char **); + int *html_len = va_arg(ap, int *); + struct session *ses = va_arg(ap, struct session *); + unsigned char *url = va_arg(ap, unsigned char *); + struct SEE_interpreter *see = &see_interpreter; + struct SEE_value args_[2], *args[2] = { &args_[0], &args_[1] }; + struct SEE_value result; + + evhook_use_params(url && ses); + + if (*html == NULL || *html_len == 0) + return EVENT_HOOK_STATUS_NEXT; + + SEE_SET_STRING(args[0], SEE_string_sprintf(see, "%s", url)); + SEE_SET_STRING(args[1], SEE_string_sprintf(see, "%s", *html)); + + if (!call_see_hook(see, "pre_format_html", args, sizeof_array(args), &result)) + return EVENT_HOOK_STATUS_NEXT; + + switch (SEE_VALUE_GET_TYPE(&result)) { + case SEE_STRING: + { + struct string new_html; + + if (convert_see_string(&new_html, result.u.string)) { + mem_free_set(html, new_html.source); + *html_len = new_html.length; + } + break; + } + case SEE_NULL: + break; + + default: + alert_see_error(ses, "pre_format_hook must return a string or null"); + } + + return EVENT_HOOK_STATUS_NEXT; +} + +/* The function can return: + * - "PROXY:PORT" to use the specified proxy + * - "" to not use any proxy + * - nil to use the default proxies */ +static enum evhook_status +script_hook_get_proxy(va_list ap, void *data) +{ + unsigned char **new_proxy_url = va_arg(ap, unsigned char **); + unsigned char *url = va_arg(ap, unsigned char *); + struct SEE_interpreter *see = &see_interpreter; + struct SEE_value result, args_[1], *args[1] = { &args_[0] }; + + if (!new_proxy_url || !url) + return EVENT_HOOK_STATUS_NEXT; + + SEE_SET_STRING(args[0], SEE_string_sprintf(see, "%s", url)); + + if (!call_see_hook(see, "get_proxy", args, sizeof_array(args), &result)) + return EVENT_HOOK_STATUS_NEXT; + + switch (SEE_VALUE_GET_TYPE(&result)) { + case SEE_STRING: + { + struct string proxy; + + if (convert_see_string(&proxy, result.u.string)) + mem_free_set(new_proxy_url, proxy.source); + break; + } + case SEE_NULL: + break; + + default: + alert_see_error(NULL, "proxy_hook must return a string, undefined or null"); + } + + return EVENT_HOOK_STATUS_NEXT; +} + +static enum evhook_status +script_hook_quit(va_list ap, void *data) +{ + struct SEE_interpreter *see = &see_interpreter; + struct SEE_value result; + + call_see_hook(see, "quit", NULL, 0, &result); + + return EVENT_HOOK_STATUS_NEXT; +} + +struct event_hook_info see_scripting_hooks[] = { + { "goto-url", 0, script_hook_goto_url, NULL }, + { "follow-url", 0, script_hook_follow_url, NULL }, + { "pre-format-html", 0, script_hook_pre_format_html, NULL }, + { "get-proxy", 0, script_hook_get_proxy, NULL }, + { "quit", 0, script_hook_quit, NULL }, + + NULL_EVENT_HOOK_INFO, +}; diff --git a/src/scripting/see/hooks.h b/src/scripting/see/hooks.h new file mode 100644 index 00000000..b25256df --- /dev/null +++ b/src/scripting/see/hooks.h @@ -0,0 +1,10 @@ +/* $Id: hooks.h,v 1.2 2005/04/01 17:42:56 zas Exp $ */ + +#ifndef EL__SCRIPTING_SEE_HOOKS_H +#define EL__SCRIPTING_SEE_HOOKS_H + +struct event_hook_info; + +extern struct event_hook_info see_scripting_hooks[]; + +#endif diff --git a/src/scripting/see/see.c b/src/scripting/see/see.c new file mode 100644 index 00000000..e3f4578f --- /dev/null +++ b/src/scripting/see/see.c @@ -0,0 +1,23 @@ +/* Simple Ecmascript Engine (SEE) browser scripting module */ +/* $Id: see.c,v 1.2 2005/06/13 00:43:29 jonas Exp $ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "elinks.h" + +#include "main/module.h" +#include "scripting/see/core.h" +#include "scripting/see/hooks.h" + + +struct module see_scripting_module = struct_module( + /* name: */ "Simple Ecmascript Engine", + /* options: */ NULL, + /* events: */ see_scripting_hooks, + /* submodules: */ NULL, + /* data: */ NULL, + /* init: */ init_see, + /* done: */ NULL +); diff --git a/src/scripting/see/see.h b/src/scripting/see/see.h new file mode 100644 index 00000000..55d4da4f --- /dev/null +++ b/src/scripting/see/see.h @@ -0,0 +1,10 @@ +/* $Id: see.h,v 1.4 2005/04/06 22:33:42 jonas Exp $ */ + +#ifndef EL__SCRIPTING_SEE_SEE_H +#define EL__SCRIPTING_SEE_SEE_H + +struct module; + +extern struct module see_scripting_module; + +#endif