diff -purN bb.old/editors/awk.c bb.dev/editors/awk.c
--- bb.old/editors/awk.c	2007-05-20 04:17:05.002197784 +0200
+++ bb.dev/editors/awk.c	2007-05-20 22:40:48.183743936 +0200
@@ -30,6 +30,11 @@
 /* these flags are static, don't change them when value is changed */
 #define	VF_DONTTOUCH (VF_ARRAY | VF_SPECIAL | VF_WALK | VF_CHILD | VF_DIRTY)
 
+#ifdef CONFIG_AWX
+#define fputs(s, stream) fputs_hook(s, stream)
+static inline int fputs_hook (__const char *__restrict __s, FILE *__restrict __stream);
+#endif
+
 /* Variable */
 typedef struct var_s {
 	unsigned short type;		/* flags */
@@ -50,10 +55,15 @@ typedef struct chain_s {
 	char *programname;
 } chain;
 
+typedef var *(*awk_cfunc)(var *res, var *args, int nargs);
 /* Function */
 typedef struct func_s {
 	unsigned short nargs;
-	struct chain_s body;
+	enum { AWKFUNC, CFUNC } type;
+	union {
+		awk_cfunc cfunc;
+		struct chain_s body;
+	} x;
 } func;
 
 /* I/O stream */
@@ -1312,7 +1322,8 @@ static void parse_program(char *p)
 			next_token(TC_FUNCTION);
 			pos++;
 			f = newfunc(t.string);
-			f->body.first = NULL;
+			f->type = AWKFUNC;
+			f->x.body.first = NULL;
 			f->nargs = 0;
 			while (next_token(TC_VARIABLE | TC_SEQTERM) & TC_VARIABLE) {
 				v = findvar(ahash, t.string);
@@ -1321,7 +1332,7 @@ static void parse_program(char *p)
 				if (next_token(TC_COMMA | TC_SEQTERM) & TC_SEQTERM)
 					break;
 			}
-			seq = &(f->body);
+			seq = &(f->x.body);
 			chain_group();
 			clear_array(ahash);
 
@@ -2260,7 +2271,8 @@ static var *evaluate(node *op, var *res)
 			break;
 
 		case XC( OC_FUNC ):
-			if (! op->r.f->body.first)
+			if ((op->r.f->type == AWKFUNC) &&
+				!op->r.f->x.body.first)
 				runtime_error(EMSG_UNDEF_FUNC);
 
 			X.v = R.v = nvalloc(op->r.f->nargs+1);
@@ -2277,7 +2289,11 @@ static var *evaluate(node *op, var *res)
 			fnargs = X.v;
 
 			L.s = programname;
-			res = evaluate(op->r.f->body.first, res);
+			if (op->r.f->type == AWKFUNC)
+				res = evaluate(op->r.f->x.body.first, res);
+			else if (op->r.f->type == CFUNC)
+				res = op->r.f->x.cfunc(res, fnargs, op->r.f->nargs);
+
 			programname = L.s;
 
 			nvfree(fnargs);
@@ -2637,6 +2653,11 @@ static rstream *next_input_file(void)
 	return &rsm;
 }
 
+#ifdef CONFIG_AWX
+static int is_awx = 0;
+#include "awx.c"
+#endif
+
 int awk_main(int argc, char **argv)
 {
 	int i, j, flen;
@@ -2693,6 +2714,10 @@ int awk_main(int argc, char **argv)
 		free(s);
 	}
 
+#ifdef CONFIG_AWX
+	do_awx(argc, argv);
+#endif
+
 	programname = NULL;
 	while((c = getopt(argc, argv, "F:v:f:W:")) != EOF) {
 		switch (c) {
diff -purN bb.old/editors/awx.c bb.dev/editors/awx.c
--- bb.old/editors/awx.c	1970-01-01 01:00:00.000000000 +0100
+++ bb.dev/editors/awx.c	2007-05-20 23:07:21.338547592 +0200
@@ -0,0 +1,628 @@
+/*
+ * awk web extension
+ *
+ * Copyright (C) 2007 by Felix Fietkau <nbd@openwrt.org>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include <cgi.h>
+#include <glob.h>
+#include "awx_parser.h"
+
+#define LINE_BUF 2048
+#define HASH_MAX	1536
+#define TR_START	"@TR<<"
+#define TR_END		">>"
+#define MAX_TR	32
+
+#undef fputs
+
+static xhash *lstr = NULL;
+static xhash *formvar = NULL;
+static int lang_inuse = 0;
+
+/* look up a translation symbol from the hash */
+static inline char *translate_lookup(char *str)
+{
+	char *name, *def, *p;
+	hash_item *hi;
+	var *v;
+
+	def = name = str;
+	if (((p = strchr(str, '|')) != NULL)
+		|| ((p = strchr(str, '#')) != NULL)) {
+		def = p + 1;
+		*p = 0;
+	}
+	
+	hi = hash_search(lstr, name);
+	if (!hi)
+		return def;
+	
+	v = &hi->data.v;
+
+	return getvar_s(v);
+}
+
+/* look for translation markers in the line and return the translated string */
+static char *translate_line(char *line)
+{
+	char *tok[MAX_TR * 3];
+	char *l, *p, *p2, *res;
+	int len = 0, _pos = 0, i, tr_abort = 0;
+	static char *backlog = NULL;
+
+	if (backlog && line) {
+		backlog = xrealloc(backlog, strlen(backlog) + strlen(line) + 1);
+		sprintf(backlog + strlen(backlog), line);
+		l = backlog;
+	} else {
+		l = line;
+	}
+
+	while (l != NULL) {
+		if ((p = strstr(l, TR_START)) == NULL) {
+			len += strlen((tok[_pos++] = l));
+			break;
+		}
+
+		p2 = strstr(p, TR_END);
+		if (p2 == NULL) {
+			p2 = backlog;
+			backlog = xstrdup(l);
+			if (p2)
+				free(p2);
+			tr_abort = 1;
+			break;
+		}
+
+		*p = 0;
+		*p2 = 0;
+		len += strlen((tok[_pos++] = l));
+		len += strlen((tok[_pos++] = translate_lookup(p + strlen(TR_START))));
+
+		l = p2;
+		l += strlen(TR_END);
+	}
+	len++;
+
+	p = xmalloc(len + 1);
+	*p = 0;
+	res = p;
+	for (i = 0; i < _pos; i++) {
+		strcat(p, tok[i]);
+		p += strlen(tok[i]);
+	}
+	if (!tr_abort && backlog) {
+		free(backlog);
+		backlog = NULL;
+	}
+	
+	return res;
+}
+
+/* hook for intercepting awk's use of puts. used for running all printed strings
+ * through the translation system */
+static inline int fputs_hook (__const char *__restrict __s, FILE *__restrict __stream)
+{
+	if (lang_inuse && (__stream == stdout)) {
+		int ret;
+		char *str;
+	
+		str = translate_line((char *) __s);
+		ret = fputs(str, __stream);
+		free(str);
+
+		return ret;
+	}
+
+	return fputs(__s, __stream);
+}
+
+static var *init_lang(var *res, var *args, int nargs)
+{
+	if (!lstr)
+		lstr = hash_init();
+
+	lang_inuse = 1;
+	return res;
+}
+
+
+/* load and parse language file */
+static void load_lang_file(char *file)
+{
+	FILE *f;
+	char *b, *name, *value;
+	char buf1[LINE_BUF];
+
+	if ((f = fopen(file, "r")) == NULL)
+		return;
+
+	while (!feof(f) && (fgets(buf1, LINE_BUF - 1, f) != NULL)) {
+		b = buf1;
+		if (*b == '#')
+			continue; /* skip comments */
+
+		while (isspace(*b))
+			b++; /* skip leading spaces */
+		if (!*b)
+			continue;
+		
+		name = b;
+		if ((b = strstr(name, "=>")) == NULL)
+			continue; /* separator not found */
+
+		value = b + 2;
+		if (!*value)
+			continue;
+		
+		*b = 0;
+		for (b--; isspace(*b); b--)
+			*b = 0; /* remove trailing spaces */
+		
+		while (isspace(*value))
+			value++; /* skip leading spaces */
+
+		for (b = value + strlen(value) - 1; isspace(*b); b--)
+			*b = 0; /* remove trailing spaces */
+		
+		if (!*value)
+			continue;
+
+		setvar_s(findvar(lstr,name), value);
+	}
+
+	fclose(f);
+}
+
+static var *load_lang(var *res, var *args, int nargs)
+{
+	const char *langfmt = "/usr/lib/webif/lang/%s.txt";
+	char lbuf[LINE_BUF];
+	char *lang;
+
+	if (!lang_inuse)
+		init_lang(res, args, nargs);
+	
+	lang = getvar_s(args);
+	if (!lang || !strcmp(lang, ""))
+		return res;
+
+	sprintf(lbuf, langfmt, lang);
+	load_lang_file(lbuf);
+
+	return res;	
+}
+		
+/* read the contents of an entire file */
+static char *get_file(char *fname)
+{
+	FILE *F;
+	char *s = NULL;
+	int i, j, flen;
+
+	F = fopen(fname, "r");
+	if (!F) {
+		return NULL;
+	}
+
+	if (fseek(F, 0, SEEK_END) == 0) {
+		flen = ftell(F);
+		s = (char *)xmalloc(flen+4);
+		fseek(F, 0, SEEK_SET);
+		i = 1 + fread(s+1, 1, flen, F);
+	} else {
+		for (i=j=1; j>0; i+=j) {
+			s = (char *)xrealloc(s, i+4096);
+			j = fread(s+i, 1, 4094, F);
+		}
+	}
+
+	s[i] = '\0';
+	fclose(F);
+	return s;
+}
+
+
+/* parse_include():
+ * 
+ * taken from parse_program from awk.c
+ * END{} is not parsed here, and BEGIN{} is executed immediately
+ */
+static void parse_include(char *p)
+{
+	uint32_t tclass;
+	chain *initseq = NULL;
+	chain tmp;
+	func *f;
+	var *v, *tv;
+
+	tv = nvalloc(1);
+	memset(&tmp, 0, sizeof(tmp));
+	pos = p;
+	t.lineno = 1;
+	while ((tclass = next_token(TC_EOF | TC_OPSEQ |
+				TC_OPTERM | TC_BEGIN | TC_FUNCDECL)) != TC_EOF) {
+		if (tclass & TC_OPTERM)
+			continue;
+
+		seq = &tmp;
+		if (tclass & TC_BEGIN) {
+			initseq = xzalloc(sizeof(chain));
+			seq = initseq;
+			chain_group();
+		} else if (tclass & TC_FUNCDECL) {
+			next_token(TC_FUNCTION);
+			pos++;
+			f = newfunc(t.string);
+			f->type = AWKFUNC;
+			f->x.body.first = NULL;
+			f->nargs = 0;
+			while (next_token(TC_VARIABLE | TC_SEQTERM) & TC_VARIABLE) {
+				v = findvar(ahash, t.string);
+				v->x.aidx = (f->nargs)++;
+
+				if (next_token(TC_COMMA | TC_SEQTERM) & TC_SEQTERM)
+					break;
+			}
+			seq = &(f->x.body);
+			chain_group();
+			clear_array(ahash);
+		}
+	}
+	if (initseq && initseq->first)
+		tv = evaluate(initseq->first, tv);
+	nvfree(tv);
+}
+
+
+/* include an awk file and run its BEGIN{} section */
+static xhash *includes = NULL;
+static void include_file(char *filename)
+{
+	char *s;
+	var *v;
+
+	if (!includes)
+		includes = hash_init();
+	
+	/* find out if the file has been included already */
+	v = findvar(includes, filename);
+	if (istrue(v))
+		return;
+	setvar_s(v, "1");
+
+	/* read include file */
+	s = get_file(filename);
+	if (!s) {
+		fprintf(stderr, "Could not open file.\n");
+		return;
+	}
+	parse_include(s+1);
+	free(s);
+}
+
+static var *include(var *res, var *args, int nargs)
+{
+	char *s;
+
+	s = getvar_s(args);
+	if (s && (strlen(s) > 0))
+		include_file(s);
+
+	return res;
+}
+
+/* parse an awk expression */
+static var *parse_awk(char *str, var *tv)
+{
+	chain body;
+	node *n;
+
+	memset(&body, 0, sizeof(body));
+	pos = str;
+	seq = &body;
+	
+	/* end of expression, assume that there's going to be a free byte
+	 * at the end of the string that can be used for the ')' */
+	strcat(str + strlen(str), "}");
+	n = parse_expr(TC_GRPTERM);
+	if (!n)
+		return NULL;
+
+	return evaluate(n, tv);
+}
+
+static inline void print_translate(char *s)
+{
+	char *str = s;
+	if (lang_inuse)
+		str = translate_line(s);
+	fputs(str, stdout);
+	fflush(stdout);
+	if (lang_inuse)
+		free(str);
+}
+
+static void render_element(struct template_cb *tcb, struct template_element *e)
+{
+	var *v;
+	char *s, *s2;
+	int i;
+	
+	if (!e || !e->var)
+		return;
+	lineno = e->line;
+	switch (e->t) {
+		case T_TEXT:
+			s = strdup(e->var);
+			print_translate(s);
+			free(s);
+			break;
+		case T_CODE:
+			s = strdup(e->var);
+			v = nvalloc(1);
+			s2 = strdup(getvar_s(parse_awk(s, v)));
+			nvfree(v);
+			print_translate(s);
+			free(s);
+			free(s2);
+			break;
+		case T_IF:
+			s = strdup(e->var);
+			v = nvalloc(1);
+			i = istrue(parse_awk(s, v));
+			nvfree(v);
+			free(s);
+
+			if (i)
+				execute_template(tcb, e->sub);
+			else if (e->sub2)
+				execute_template(tcb, e->sub2);
+			break;
+		case T_FOR: {
+				v = newvar(e->var);
+				hashwalk_init(v, iamarray(findvar(vhash, e->in)));
+				while (hashwalk_next(v)) {
+					execute_template(tcb, e->sub);
+				}
+				clrvar(v);
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+/* awk method render(), which opens a template file and processes all awk ssi calls */
+static void render_file(char *filename)
+{
+	struct template_cb tcb;
+	struct template_element *e;
+	FILE *f;
+	char *oldprg;
+	int oldlnr;
+	
+	oldlnr = lineno;
+	oldprg = programname;
+	programname = filename;
+			
+	f = fopen(filename, "r");
+	if (!f)
+		return;
+	
+	memset(&tcb, 0, sizeof(tcb));
+	tcb.handle_element = render_element;
+	e = parse_template(&tcb, f);
+	execute_template(&tcb, e);
+	free_template(&tcb, e);
+	fclose(f);
+	programname = oldprg;
+	lineno = oldlnr;
+}
+
+static var *render(var *res, var *args, int nargs)
+{
+	char *s;
+
+	s = getvar_s(args);
+	if (!s)
+		return res;
+
+	render_file(s);
+	
+	return res;
+}
+		
+/* Call render, but only if this function hasn't been called already */
+static int layout_rendered = 0;
+static var *render_layout(var *res, var *args, int nargs)
+{
+	if (layout_rendered)
+		return res;
+	layout_rendered = 1;
+	return render(res, args, nargs);
+}
+
+/* registers a global c function for the awk interpreter */
+static void register_cfunc(char *name, awk_cfunc cfunc, int nargs)
+{
+	func *f;
+
+	f = newfunc(name);
+	f->type = CFUNC;
+	f->x.cfunc = cfunc;
+	f->nargs = nargs;
+}
+
+static void putvar(vartype type, char *name, char *value)
+{
+	if (type != FORM_VAR)
+		return;
+
+	setvar_u(findvar(formvar, name), value);
+}
+
+static char *cgi_getvar(char *name)
+{
+	if (!formvar) {
+		formvar = hash_init();
+		cgi_init(putvar);
+	}
+
+	if (!formvar || !name)
+		return NULL;
+	
+	return getvar_s(findvar(formvar, name));
+}
+
+/* function call for accessing cgi form variables */
+static var *getvar(var *res, var *args, int nargs)
+{
+	char *s;
+	char *svar;
+
+	s = getvar_s(args);
+	if (!s)
+		return res;
+	
+	svar = cgi_getvar(s);
+	if (!svar)
+		return res;
+
+	setvar_u(res, svar);
+
+	return res;
+}
+
+/* call an awk function without arguments by string reference */
+static var *call(var *res, var *args, int nargs)
+{
+	char *s = getvar_s(args);
+	func *f;
+
+	if (!s)
+		goto done;
+	
+	f = newfunc(s);
+	if (f && f->type == AWKFUNC && f->x.body.first)
+		return evaluate(f->x.body.first, res);
+
+done:
+	return res;
+}
+
+
+static int run_awxscript(char *name)
+{
+	var tv, *layout, *action;
+	char *tmp, *s = NULL;
+
+	zero_out_var(&tv);
+	programname = name;
+
+	/* read the main controller source */
+	s = get_file(programname);
+	if (!s) {
+		fprintf(stderr, "Could not open file\n");
+		return 1;
+	}
+	parse_program(s+1);
+	free(s);
+
+
+	/* set some defaults for ACTION and LAYOUT, which will have special meaning */
+	layout = newvar("LAYOUT");
+	setvar_s(layout, "views/layout.ahtml");
+
+	/* run the BEGIN {} block */
+	evaluate(beginseq.first, &tv);
+
+	action = newvar("ACTION");
+	if (!(strlen(getvar_s(action)) > 0)) {
+		tmp = cgi_getvar("action");
+		if (!tmp || (strlen(tmp) <= 0))
+			tmp = strdup("default");
+
+		setvar_p(action, tmp);
+	}
+	
+	/* call the action (precedence: begin block override > cgi parameter > "default") */
+	tmp = xmalloc(strlen(getvar_s(action)) + 7);
+	sprintf(tmp, "handle_%s", getvar_s(action));
+	setvar_s(action, tmp);
+	call(&tv, action, 1);
+	free(tmp);
+
+	/* render the selected layout, will do nothing if render_layout has been called from awk */
+	render_layout(&tv, layout, 1);
+
+	return 0;
+}
+
+
+/* main awx processing function. called from awk_main() */
+static int do_awx(int argc, char **argv)
+{
+	int ret = -1;
+	var tv;
+	int i, c;
+	char **args = argv;
+	
+	zero_out_var(&tv);
+
+	/* register awk C callbacks */
+	register_cfunc("getvar", getvar, 1);
+	register_cfunc("render", render, 1);
+	register_cfunc("render_layout", render_layout, 1);
+	register_cfunc("call", call, 1);
+	register_cfunc("include", include, 1);
+	register_cfunc("init_lang", init_lang, 1);
+	register_cfunc("load_lang", load_lang, 1);
+
+	if (!is_awx)
+		return 0;
+
+	/* fill in ARGV array */
+	setvar_i(V[ARGC], argc + 1);
+	setari_u(V[ARGV], 0, "awx");
+	i = 0;
+	while (*args)
+		setari_u(V[ARGV], ++i, *args++);
+	
+	while((c = getopt(argc, argv, "i:f:")) != EOF) {
+		switch(c) {
+			case 'i':
+				programname = optarg;
+				include_file(optarg);
+				break;
+			case 'f':
+				ret = 0;
+				programname = optarg;
+				render_file(optarg);
+				goto done;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		fprintf(stderr, "Invalid argument.\n");
+		goto done;
+	}
+
+	ret = run_awxscript(*argv);
+
+done:
+	exit(ret);
+}
+
+/* entry point for awx applet */
+int awx_main(int argc, char **argv)
+{
+	is_awx = 1;
+	return awk_main(argc, argv);
+}
+
diff -purN bb.old/editors/awx_parser.h bb.dev/editors/awx_parser.h
--- bb.old/editors/awx_parser.h	1970-01-01 01:00:00.000000000 +0100
+++ bb.dev/editors/awx_parser.h	2007-05-20 22:30:31.380512280 +0200
@@ -0,0 +1,38 @@
+#ifndef __TEMPLATE_PARSER_H
+#define __TEMPLATE_PARSER_H
+
+enum type {
+	T_TEXT,
+	T_FOR,
+	T_IF,
+	T_CODE
+};
+
+struct template_element;
+struct template_cb;
+
+struct template_cb {
+	void *(*prepare_code)(struct template_element *);
+	void (*handle_element)(struct template_cb *, struct template_element *);
+	void (*free_code)(struct template_element *);
+};
+
+struct template_element {
+	enum type t;
+	char *var;
+	char *in;
+	int line;
+	void *priv;
+	struct template_element *parent;
+	struct template_element *sub;
+	struct template_element *sub2;
+	struct template_element *prev;
+	struct template_element *next;
+};
+
+
+struct template_element *parse_template(struct template_cb *cb, FILE *in);
+void execute_template(struct template_cb *cb, struct template_element *e);
+void free_template(struct template_cb *cb, struct template_element *e);
+
+#endif
diff -purN bb.old/editors/awx_parser.l bb.dev/editors/awx_parser.l
--- bb.old/editors/awx_parser.l	1970-01-01 01:00:00.000000000 +0100
+++ bb.dev/editors/awx_parser.l	2007-05-20 22:30:31.380512280 +0200
@@ -0,0 +1,302 @@
+%{
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "busybox.h"
+#include "awx_parser.h"
+
+enum {
+	S_INIT,
+	S_TEXT,
+	S_CODE,
+	S_IF_START,
+	S_FOR_START,
+	S_FOR_IN,
+	S_END,
+	S_ELSE,
+	S_EOF
+}; 
+int state;
+
+#undef DEBUG
+#ifdef DEBUG
+char *statestr[] = {
+	[S_INIT] = "S_INIT",
+	[S_TEXT] = "S_TEXT",
+	[S_CODE] = "S_CODE",
+	[S_IF_START] = "S_IF_START",
+	[S_FOR_START] = "S_FOR_START",
+	[S_FOR_IN] = "S_FOR_IN",
+	[S_EOF] = "S_EOF"
+};
+
+char *typestr[] = {
+	[T_TEXT] = "T_TEXT",
+	[T_FOR] = "T_FOR",
+	[T_IF] = "T_IF",
+	[T_CODE] = "T_CODE"
+};
+#endif
+
+static struct template_cb *parse_cb;
+static struct template_element *cur, *head;
+static char *textbuf;
+static unsigned int buflen;
+static unsigned int buf_offset;
+static int _lnr = 0;
+
+static void buf_realloc(void)
+{
+	buflen *= 2;
+	textbuf = xrealloc(textbuf, buflen);
+}
+
+static void parse_error(char *str)
+{
+	fprintf(stderr, "Parse error%s%s\n", (str ? ": " : "!"), (str ?: ""));
+	exit(255);
+}
+
+
+static struct template_element *new_template_element(struct template_element *parent)
+{
+	struct template_element *ptr;
+	
+	ptr = xzalloc(sizeof(*ptr));
+	ptr->parent = parent;
+	return ptr;
+}
+
+static inline void next_template_element(void)
+{
+	cur->next = new_template_element(cur->parent);
+	cur->next->prev = cur;
+	cur = cur->next;
+}
+
+static void addtext(char *text)
+{
+	while(buf_offset + strlen(text) + 1 > buflen)
+		buf_realloc();
+
+	buf_offset += sprintf(&textbuf[buf_offset], "%s", text);
+}
+
+static void set_state(int newstate)
+{
+	char *ptr;
+
+#ifdef DEBUG
+	static int _rec = 0;
+	fprintf(stderr, "DEBUG(%d): %s => %s: %s\n", _rec, statestr[state], statestr[newstate], textbuf);
+#endif
+	ptr = xstrdup(textbuf);
+	if (state == S_FOR_IN)
+		cur->in = ptr;
+	else
+		cur->var = ptr;
+
+	if (parse_cb && (cur->t == T_CODE) && parse_cb->prepare_code)
+		parse_cb->prepare_code(cur);
+
+	buf_offset = 0;
+	*textbuf = 0;
+
+	switch(newstate) {
+#if 0
+		case S_EOF:
+			if (cur->parent)
+				parse_error();
+			break;
+#endif
+		case S_FOR_START:
+			if (ptr || !cur->prev)
+				next_template_element();
+			cur->t = T_FOR;
+			break;
+		case S_IF_START:
+			if (ptr || !cur->prev)
+				next_template_element();
+			cur->t = T_IF;
+			break;
+		case S_ELSE:
+			cur = cur->parent;
+			if (!cur)
+				parse_error("'@else' without parent element");
+			cur->sub2 = new_template_element(cur);
+			cur = cur->sub2;
+			newstate = S_TEXT;
+			break;
+		case S_END:
+#ifdef DEBUG
+			_rec--;
+#endif
+			cur = cur->parent;
+			if (!cur) 
+				parse_error("'@end' without parent element");
+
+			next_template_element();
+			cur->t = T_TEXT;
+			newstate = S_TEXT;
+			break;
+		case S_TEXT:
+			switch (cur->t) {
+				case T_CODE:
+					next_template_element();
+					break;
+				case T_IF:
+				case T_FOR:
+#ifdef DEBUG
+					_rec++;
+#endif
+					cur->sub = new_template_element(cur);
+					cur = cur->sub;
+					break;
+				default:
+					break;
+			}
+			cur->t = T_TEXT;
+			break;
+		case S_CODE:
+			if (ptr || !cur->prev)
+				next_template_element();
+			cur->t = T_CODE;
+			break;
+		default:
+			break;
+	}
+	cur->line = _lnr;
+	state = newstate;
+}
+
+%}
+
+%%
+"<%"[ \n\t]*"@if"[ \n\t]+ {
+	if (state == S_TEXT) 
+		set_state(S_IF_START);
+	else
+		REJECT;
+}
+
+"<%"[ \n\t]*"@for"[ \n\t]+ {
+	if (state == S_TEXT) 
+		set_state(S_FOR_START);
+	else
+		REJECT;
+}
+
+[ \n\t]+"in"[ \n\t]+ {
+	if (state == S_FOR_START)
+		set_state(S_FOR_IN);
+	else
+		REJECT;
+}
+
+"<%"[ \n\t]*"@end"[ \n\t]*%> {
+	if (state != S_TEXT)
+		REJECT;
+	set_state(S_END);
+}
+
+"<%"[ \n\t]*"@else"[ \n\t]*%> {
+	if (state != S_TEXT)
+		REJECT;
+	set_state(S_ELSE);
+}
+
+"<%" {
+	if (state != S_TEXT) 
+		parse_error("'<%' cannot be nested");
+	set_state(S_CODE);
+}
+
+[ \n\t]"%>" {
+	if (state == S_TEXT)
+		REJECT;
+	set_state(S_TEXT);
+}
+
+\n {
+	_lnr++;
+	if (state == S_TEXT)
+		addtext(yytext);
+}
+.	{
+	addtext(yytext);
+}
+
+
+%%
+
+
+void execute_template(struct template_cb *cb, struct template_element *e)
+{
+	static int rec = 0;
+
+	while (e) {
+#ifdef DEBUG
+		fprintf(stderr, "DEBUG: execute(%d)\t%s\n", rec, typestr[e->t]);
+#endif
+		rec++;
+		if (cb->handle_element)
+			cb->handle_element(cb, e);
+		rec--;
+		e = e->next;
+	}
+}
+
+int yywrap()
+{
+	set_state(S_EOF);
+	return 1;
+}
+
+struct template_element *parse_template(struct template_cb *cb, FILE *in)
+{
+	_lnr = 1;
+	buf_offset = 0;
+	state = S_TEXT;
+	parse_cb = cb;
+	
+	buflen = 4096;
+	textbuf = xzalloc(buflen);
+
+	head = xzalloc(sizeof(*head));
+	head->t = T_TEXT;
+	cur = head;
+
+	yyin = in;
+	yylex();
+
+	return head;
+}
+
+void free_template(struct template_cb *cb, struct template_element *e)
+{
+	struct template_element *next;
+
+	if (!e)
+		return;
+		
+	switch (e->t) {
+		case T_CODE:
+			if (cb->free_code)
+				cb->free_code(e);
+			break;
+		case T_FOR:
+		case T_IF:
+			free_template(cb, e->sub);
+			break;
+		default:
+			break;
+	}
+	if (e->var)
+		free(e->var);
+	if (e->in)
+		free(e->in);
+		
+	next = e->next;
+	free(e);
+	return free_template(cb, next);
+}
diff -purN bb.old/editors/Config.in bb.dev/editors/Config.in
--- bb.old/editors/Config.in	2007-05-20 04:17:05.003197632 +0200
+++ bb.dev/editors/Config.in	2007-05-20 22:30:31.380512280 +0200
@@ -12,6 +12,13 @@ config AWK
 	  Awk is used as a pattern scanning and processing language.  This is
 	  the BusyBox implementation of that programming language.
 
+config AWX
+	bool "Enable awx (awk web extension)"
+	default n
+	depends on AWK
+	help
+	  awx - awk web extension
+
 config FEATURE_AWK_MATH
 	bool "Enable math functions (requires libm)"
 	default y
diff -purN bb.old/editors/Kbuild bb.dev/editors/Kbuild
--- bb.old/editors/Kbuild	2007-03-18 17:59:37.000000000 +0100
+++ bb.dev/editors/Kbuild	2007-05-20 22:30:31.381512128 +0200
@@ -10,3 +10,12 @@ lib-$(CONFIG_ED)        += ed.o
 lib-$(CONFIG_PATCH)     += patch.o
 lib-$(CONFIG_SED)       += sed.o
 lib-$(CONFIG_VI)        += vi.o
+lib-$(CONFIG_AWX)		+= awx_parser.o
+
+editors/awx_parser.c: editors/awx_parser.l editors/awx_parser.h
+	@flex $<
+	@mv lex.yy.c $@
+
+editors/awx_parser.o: editors/awx_parser.c FORCE
+	$(call cmd,force_checksrc)
+	$(call if_changed_rule,cc_o_c)
diff -purN bb.old/include/applets.h bb.dev/include/applets.h
--- bb.old/include/applets.h	2007-05-20 04:17:05.003197632 +0200
+++ bb.dev/include/applets.h	2007-05-20 22:30:31.381512128 +0200
@@ -60,6 +60,7 @@ USE_ARP(APPLET(arp, _BB_DIR_SBIN, _BB_SU
 USE_ARPING(APPLET(arping, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_ASH(APPLET_NOUSAGE(ash, ash, _BB_DIR_BIN, _BB_SUID_NEVER))
 USE_AWK(APPLET(awk, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_AWX(APPLET_NOUSAGE(awx, awx, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) 
 USE_BASENAME(APPLET(basename, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_BBCONFIG(APPLET(bbconfig, _BB_DIR_BIN, _BB_SUID_NEVER))
 //USE_BBSH(APPLET(bbsh, _BB_DIR_BIN, _BB_SUID_NEVER))
diff -purN bb.old/include/cgi.h bb.dev/include/cgi.h
--- bb.old/include/cgi.h	1970-01-01 01:00:00.000000000 +0100
+++ bb.dev/include/cgi.h	2007-05-20 22:30:31.381512128 +0200
@@ -0,0 +1,8 @@
+#ifndef CGI_H
+#define CGI_H
+
+typedef enum { FORM_VAR, COOKIE_VAR } vartype;
+typedef void (*var_handler) (vartype, char *, char *);
+int cgi_init(var_handler);
+
+#endif
diff -purN bb.old/libbb/cgi.c bb.dev/libbb/cgi.c
--- bb.old/libbb/cgi.c	1970-01-01 01:00:00.000000000 +0100
+++ bb.dev/libbb/cgi.c	2007-05-20 22:30:31.382511976 +0200
@@ -0,0 +1,457 @@
+/* --------------------------------------------------------------------------
+ * functions for processing cgi form data
+ * Copyright (C) 2007 by Felix Fietkau <nbd@openwrt.org>
+ *
+ * parts taken from the core of haserl.cgi - a poor-man's php for embedded/lightweight environments
+ * $Id: haserl.c,v 1.13 2004/11/10 17:59:35 nangel Exp $ 
+ * Copyright (c) 2003,2004    Nathan Angelacos (nangel@users.sourceforge.net)
+ *
+ * This program 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * -----
+ * The x2c() and unescape_url() routines were taken from  
+ *  http://www.jmarshall.com/easy/cgi/getcgi.c.txt 
+ * 
+ * The comments in that text file state:
+ *
+ ***  Written in 1996 by James Marshall, james@jmarshall.com, except 
+ ***  that the x2c() and unescape_url() routines were lifted directly 
+ ***  from NCSA's sample program util.c, packaged with their HTTPD. 
+ ***     For the latest, see http://www.jmarshall.com/easy/cgi/ 
+ * -----
+ *
+ ------------------------------------------------------------------------- */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cgi.h>
+
+#ifndef MAX_UPLOAD_KB
+#define MAX_UPLOAD_KB 2048
+#endif
+#define TEMPDIR "/tmp"
+
+static int global_upload_size = 0;
+static int ReadMimeEncodedInput(char *qs);
+static var_handler do_putvar = NULL;
+
+/*
+ * Convert 2 char hex string into char it represents
+ * (from http://www.jmarshall.com/easy/cgi)
+ */
+static char x2c (char *what) {
+	char digit;
+	
+	digit=(what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
+	digit *=16;
+	digit+=(what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
+
+	return digit;
+}
+
+/*
+ * unsescape %xx to the characters they represent 
+ */
+
+static void unescape_url (char *url) {
+	int  i,j;
+
+	for (i=0, j=0; url[j]; ++i, ++j) {
+		if ((url[i] = url[j]) == '%') {
+			url[i] = x2c(&url[j+1]);
+			j+=2;	
+		}
+	}
+	url[i]='\0';
+}
+
+static inline void put_var(vartype type, char *var)
+{
+	char *val;
+	
+	if (!do_putvar)
+		return;
+
+	val = strchr(var, '=');
+	if (!val)
+		return;
+	
+	*val = 0;
+	val++;
+	do_putvar(type, var, val);
+	
+	return;
+}
+
+
+/* CookieVars ()
+ * if HTTP_COOKIE is passed as an environment variable,
+ * attempt to parse its values into environment variables
+ */
+static void CookieVars (void)
+{
+	char *qs;
+	char *token;
+
+	if (getenv("HTTP_COOKIE") != NULL)
+		qs=strdup(getenv("HTTP_COOKIE"));
+	else
+		return;
+	
+	/** split on; to extract name value pairs */
+	token=strtok(qs, ";");
+	while (token) {
+		// skip leading spaces 
+		while ( token[0] == ' ' ) 
+			token++;
+		
+		put_var(COOKIE_VAR, token);
+		
+		token = strtok(NULL, ";");
+	}
+	free (qs);
+}
+
+/* 
+ * Read cgi variables from query string, and put in environment
+ */
+static int ReadCGIQueryString (void) 
+{
+	char *qs;
+	char *token;
+	int i;
+
+	if (getenv("QUERY_STRING") != NULL)
+		qs=strdup(getenv("QUERY_STRING"));
+	else
+		return 0;
+	
+	/* change plusses into spaces */
+	for (i=0; qs[i]; i++ ) { if (qs[i] == '+' ) { qs[i] = ' ';} };
+
+	/** split on & and ; to extract name value pairs */
+	
+	token=strtok(qs, "&;");
+	while (token) {
+		unescape_url(token);
+		put_var(FORM_VAR, token);
+		token=strtok(NULL, "&;");
+	}
+	free(qs);
+
+	return 0;
+}
+
+
+/* 
+ * Read cgi variables from stdin (for POST queries)
+ * (oh... and if its mime-encoded file upload, we save the
+ * file to /tmp; and return the name of the tmp file
+ * the cgi script is responsible for disposing of the tmp file
+ */
+
+static int ReadCGIPOSTValues (void) {
+	char *qs;
+	int content_length;
+	int i;
+	char *token;
+
+
+ 	if (getenv("CONTENT_LENGTH") == NULL) 
+		return(-1);
+	else
+		content_length = atoi(getenv("CONTENT_LENGTH"));
+	
+	/* protect ourselves from 20GB file uploads */
+	if (content_length > MAX_UPLOAD_KB * 1024 ) {
+		/* But we need to finish reading the content */
+	 	while (	fread( &i, sizeof(int), 1, stdin) == 1 );
+		return -1;
+	}
+ 
+ 	if (!(qs=malloc(content_length+1)))
+		return -1;
+
+	/* set the buffer to null, so that a browser messing with less 
+  	   data than content_length won't buffer underrun us */
+	memset(qs, 0 ,content_length+1);
+
+	if ((!fread(qs,content_length,1,stdin) &&
+		(content_length > 0) 
+		&& !feof(stdin))) {
+			
+		free(qs);
+		return -1;
+	}
+
+	if (getenv("CONTENT_TYPE") && (strncasecmp(getenv("CONTENT_TYPE"), "multipart/form-data", 19) == 0)) {
+		/* This is a mime request, we need to go to the mime handler */
+		i=ReadMimeEncodedInput(qs);
+		free(qs);
+			
+		return i;
+	}
+
+	/* change plusses into spaces */
+	for (i=0; qs[i]; i++ ) { if (qs[i] == '+' ) { qs[i] = ' ';} };
+
+	/** split on & and ; to extract name value pairs */
+	token=strtok(qs, "&;");
+	while (token) {
+		unescape_url(token);
+		put_var(FORM_VAR, token);
+		token=strtok(NULL, "&;");
+	}
+	
+	free(qs);
+	
+	return 0;
+}
+
+/*
+ *  LineToStr - Scans char and replaces the first "\n" with a "\0";
+ *  If it finds a "\r", it does that to; (fix DOS brokennes) returns
+ *  the length of the string;
+ */
+static int LineToStr (char *string, size_t max) {
+	size_t offset=0;
+
+	while ((offset < max) && (string[offset] != '\n') && (string[offset] != '\r'))
+		offset++;
+
+	if (string[offset] == '\r') {
+		string[offset]='\0';
+		offset++;
+	}
+	if (string[offset] == '\n') {
+		string[offset]='\0';
+		offset++;
+	}
+
+	return offset;
+}
+
+
+/*
+ * ReadMimeEncodedInput - handles things that are mime encoded
+ * takes a pointer to the input; returns 0 on success
+ */
+
+static int ReadMimeEncodedInput(char *qs) 
+{
+	char 	*boundary;
+	char	*ct;
+	int	i;
+	int	datastart;
+ 	size_t	cl;
+	size_t	offset;
+	char    *envname;
+	char	*filename;
+	char 	*ptr;
+	int	line;
+	char 	tmpname[] = TEMPDIR "/XXXXXX";
+	int	fd;
+	/* we should only get here if the content type was set. Segfaults happen
+	   if Content_Type is null */
+
+	if (getenv("CONTENT_LENGTH") == NULL)
+		/* No content length?! */
+		return(-1);
+
+	cl=atoi(getenv("CONTENT_LENGTH"));
+	
+	/* we do this 'cause we can't mess with the real env. variable - it would
+	 * overwrite the environment - I tried.
+	 */
+	i=strlen(getenv("CONTENT_TYPE"))+1;
+	ct=malloc(i);
+	if (ct)
+		memcpy(ct, getenv("CONTENT_TYPE"), i);
+	else
+		return(-1);
+	
+	i=(int) NULL;
+ 	if (ct != NULL) {
+		while (i < strlen(ct) && (strncmp("boundary=", &ct[i], 9) != 0)) 
+			i++;
+	}
+	if (i == strlen(ct)) {
+		/* no boundary informaiton found */
+		free(ct);
+		return -1;
+	}
+	boundary=&ct[i+7];
+	/* add two leading -- to the boundary */
+	boundary[0]='-';
+	boundary[1]='-';
+	
+	/* begin the big loop.  Look for:
+		--boundary
+		Content-Disposition: form-data;  name="......." 
+		....
+		<blank line>
+		content
+		--boundary
+		Content-Disposition: form-data; name="....." filename="....."
+		...
+		<blank line>
+		--boundary--
+		eof
+	*/
+
+	offset=0;
+	while (offset < cl) {
+		/* first look for boundary */
+		while ((offset < cl) && (memcmp(&qs[offset], boundary, strlen(boundary)))) 
+			offset++;
+
+		/* if we got here and we ran off the end, its an error 		*/
+		if (offset >= cl) { 
+			free(ct);
+			return -1;
+		}
+
+		/* if the two characters following the boundary are --, 	*/ 
+		/* then we are at the end, exit 				*/
+		if (memcmp(&qs[offset+strlen(boundary)], "--", 2) == 0) {
+			offset+=2;
+			break;
+		}
+		/* find where the offset should be */
+		line=LineToStr(&qs[offset], cl-offset);
+		offset+=line;
+				
+		/* Now we're going to look for content-disposition		*/ 
+		line=LineToStr(&qs[offset], cl-offset);
+		if (strncasecmp(&qs[offset], "Content-Disposition", 19) != 0) {
+			/* hmm... content disposition was not where we expected it */
+			free(ct);
+			return -1;
+		}
+		/* Found it, so let's go find "name="				*/
+	 	if (!(envname=strstr(&qs[offset], "name="))) {
+			/* now name= is missing?!  				*/
+			free(ct);
+			return(-1);
+		} else
+			envname+=6;
+
+		/* is there a filename tag? 					*/
+		if ((filename=strstr(&qs[offset], "filename="))!= NULL)
+			filename+=10;
+		else
+			filename=NULL;
+		
+		/* make envname and filename ASCIIZ				*/
+		for (i=0; (envname[i] != '"') && (envname[i] != '\0'); i++);
+		
+		envname[i] = '\0';
+		if (filename) {
+			for (i=0; (filename[i] != '"') && (filename[i] != '\0'); i++);
+			filename[i] = '\0';
+		}
+		offset+=line;
+		
+		/* Ok, by some miracle, we have the name; let's skip till we	*/
+		/* come to a blank line						*/
+		line=LineToStr(&qs[offset], cl-offset);
+		while (strlen(&qs[offset]) > 1) {
+			offset+=line;
+			line=LineToStr(&qs[offset], cl-offset);
+		}
+		offset+=line;
+		datastart=offset;
+		/* And we go back to looking for a boundary */
+		while ((offset < cl) && (memcmp(&qs[offset], boundary, strlen(boundary))))
+			offset++;
+
+		/* strip [cr] lf */
+		if ((qs[offset-1] == '\n') && (qs[offset-2] == '\r'))
+			offset-=2; 
+		else
+			offset-=1;
+
+		qs[offset]=0;
+
+		/* ok, at this point, we know where the name is, and we know    */
+		/* where the content is... we have to do one of two things      */
+		/* based on whether its a file or not				*/
+		if (filename==NULL) { /* its not a file, so its easy		*/
+			/* just jam the content after the name		*/
+			memcpy(&envname[strlen(envname)+1], &qs[datastart], offset-datastart+1);
+			envname[strlen(envname)]='=';
+			put_var(FORM_VAR, envname);
+		} else {	/* handle the fileupload case		*/
+			if (offset-datastart) {  /* only if they uploaded */
+				if ( global_upload_size == 0 ) {
+					return -1;
+				}
+				/*  stuff in the filename */
+				ptr= calloc ( sizeof (char), strlen(envname)+strlen(filename)+2+5 );
+				sprintf (ptr, "%s_name=%s", envname, filename);
+				put_var(FORM_VAR, ptr);
+				free(ptr);
+						
+				fd=mkstemp(tmpname);
+				
+				if (fd == -1)
+					return(-1);
+
+				write(fd, &qs[datastart], offset-datastart);
+				close(fd);
+				ptr= calloc (sizeof(char), strlen(envname)+strlen(tmpname)+2);
+				sprintf (ptr, "%s=%s", envname, tmpname);
+				put_var(FORM_VAR, ptr);
+				free(ptr);
+			}
+		}
+	}
+	free(ct);
+	return 0;
+}
+
+	
+/*-------------------------------------------------------------------------
+ *
+ * Main 
+ *
+ *------------------------------------------------------------------------*/
+
+int cgi_init(var_handler putvar_handler)
+{
+	int	retval = 0;
+	
+	do_putvar = putvar_handler;
+
+	/* Read the current environment into our chain */
+	CookieVars();
+	if (getenv("REQUEST_METHOD")) {
+		if (strcasecmp(getenv("REQUEST_METHOD"), "GET") == 0)
+			retval = ReadCGIQueryString();
+
+		if (strcasecmp(getenv("REQUEST_METHOD"), "POST") == 0)
+			retval = ReadCGIPOSTValues();
+	}
+
+	return retval;
+} 
diff -purN bb.old/libbb/Kbuild bb.dev/libbb/Kbuild
--- bb.old/libbb/Kbuild	2007-05-20 04:17:05.004197480 +0200
+++ bb.dev/libbb/Kbuild	2007-05-20 22:30:31.382511976 +0200
@@ -118,3 +118,6 @@ lib-$(CONFIG_GREP) += xregcomp.o
 lib-$(CONFIG_MDEV) += xregcomp.o
 lib-$(CONFIG_LESS) += xregcomp.o
 lib-$(CONFIG_DEVFSD) += xregcomp.o
+
+lib-$(CONFIG_AWX) += cgi.o
+
