summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org>2018-12-08 10:03:40 (GMT)
committer Sergey Poznyakoff <gray@gnu.org>2018-12-08 10:03:40 (GMT)
commit602f4d93070ac0e762e0cbe3ef72ba792f9c4811 (patch) (side-by-side diff)
tree8047168c6b23de38973b494b6ec794a81faf3576
parent2a684f1cdd7723c2ded277ea2c7e66227b6f3ae1 (diff)
downloadvmod-dbrw-602f4d93070ac0e762e0cbe3ef72ba792f9c4811.tar.gz
vmod-dbrw-602f4d93070ac0e762e0cbe3ef72ba792f9c4811.tar.bz2
Implement the $(urlprefixes) built-in function.
* NEWS: Update. * README: Update. * configure.ac: Version 2.2.91 * doc/vmod-dbrw.3: Document the use of $(urlprefixes) built-in * doc/vmod-dbrw.texi: Likewise. * src/vmod_dbrw.c (parse_flags): Make sure status string is null-terminated. (do_rewrite): Expand built-in functions in $(). Support urlprefixes. On debug_level=100, produce detailed trace of expansions. * src/wordsplit.c: Pull from grecs commit 9097d529. * src/wordsplit.h: Likewise. * tests/initdb.at (rewrite): Change the url column. * tests/rewrite01.at: Use $(urlprefixes) in the SQL templates. * tests/rewrite02.at: Likewise. * tests/rewrite03.at: Likewise. * tests/rewrite04.at: Likewise. * tests/rewrite05.at: Likewise. * tests/rewrite06.at: Likewise.
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--NEWS31
-rw-r--r--README3
-rw-r--r--configure.ac2
-rw-r--r--doc/vmod-dbrw.334
-rw-r--r--doc/vmod-dbrw.texi16
-rw-r--r--src/vmod_dbrw.c127
-rw-r--r--src/wordsplit.c1646
-rw-r--r--src/wordsplit.h214
-rw-r--r--tests/initdb.at10
-rw-r--r--tests/rewrite01.at5
-rw-r--r--tests/rewrite02.at5
-rw-r--r--tests/rewrite03.at5
-rw-r--r--tests/rewrite04.at5
-rw-r--r--tests/rewrite05.at5
-rw-r--r--tests/rewrite06.at5
15 files changed, 1664 insertions, 449 deletions
diff --git a/NEWS b/NEWS
index 913b0f9..c700564 100644
--- a/NEWS
+++ b/NEWS
@@ -1,2 +1,2 @@
-vmod-dbrw -- history of user-visible changes. 2018-01-30
+vmod-dbrw -- history of user-visible changes. 2018-12-08
See the end of file for copying conditions.
@@ -5,3 +5,3 @@ Please send vmod-dbrw bug reports to <gray@gnu.org>
-Version 2.2.90 (Git)
+Version 2.2.91 (Git)
@@ -20,2 +20,29 @@ e.g.:
+* The $() functions in SQL templates
+
+The SQL templates support the use of $() constructs for invoking
+built-in functions. So far one function is implemented:
+
+ $(urlprefixes PATH)
+
+It expands to comma-separated list of properly quoted pathname
+prefixes, constructed from its argument. Optional query part is
+stripped off the argument prior to expansion. For example
+
+ $(urlprefixes "/local/user/local?a=1")
+
+expands to:
+
+ '/local/user/local','/local/user','/local'
+
+This construct is intended for use in SQL IN conditionals, for
+example:
+
+ SELECT dest,pattern,value,flags
+ FROM rewrite
+ WHERE host='$host'
+ AND url IN ($(urlprefixes $url))
+ ORDER BY length(dest),value,weight DESC
+
+
diff --git a/README b/README
index be4e408..354099f 100644
--- a/README
+++ b/README
@@ -1,3 +1,2 @@
Vmod-dbrw README
-Copyright (C) 2013-2017 Sergey Poznyakoff
See the end of file for copying conditions.
@@ -225,3 +224,3 @@ Send bug reports and suggestions to <gray@gnu.org>
-Copyright (C) 2013-2017 Sergey Poznyakoff
+Copyright (C) 2013-2018 Sergey Poznyakoff
diff --git a/configure.ac b/configure.ac
index 7a1272c..1212a37 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,3 +16,3 @@
AC_PREREQ(2.69)
-AC_INIT([vmod-dbrw], 2.2.90, [gray@gnu.org])
+AC_INIT([vmod-dbrw], 2.2.91, [gray@gnu.org])
AC_CONFIG_AUX_DIR([build-aux])
diff --git a/doc/vmod-dbrw.3 b/doc/vmod-dbrw.3
index 6f1dba8..4760b6b 100644
--- a/doc/vmod-dbrw.3
+++ b/doc/vmod-dbrw.3
@@ -1,3 +1,3 @@
.\" This file is part of Vmod-dbrw -*- nroff -*-
-.\" Copyright (C) 2013-2017 Sergey Poznyakoff
+.\" Copyright (C) 2013-2018 Sergey Poznyakoff
.\"
@@ -15,3 +15,3 @@
.\" along with vmod-dbrw. If not, see <http://www.gnu.org/licenses/>.
-.TH VMOD-DBRW 1 "January 30, 2018" "VMOD-DBRW" "User Reference"
+.TH VMOD-DBRW 1 "December 8, 2018" "VMOD-DBRW" "User Reference"
.SH NAME
@@ -136,2 +136,28 @@ of the name.
.PP
+The special construct
+.sp
+.EX
+$(urlprefixes \fIPATH\fR)
+.EE
+.sp
+expands to a comma-separated list of all possible path prefixes in
+\fIPATH\fR. Each element in the list is quoted, so the result can
+safely be used in SQL statements. For example,
+.sp
+.EX
+$(urlprefixes "/local/user/login")
+.EE
+.sp
+produces
+.sp
+.EX
+ '/local/user/login','/local/user','/local'
+.EE
+.PP
+This statement is usually used in \fBIN\fR SQL constructs, e.g.
+.sp
+.EX
+SELECT * FROM table WHERE url IN ($(urlprefixes $url))
+.EE
+.PP
The expanded query is then sent to the database server. Handling
@@ -250,3 +276,5 @@ sub vcl_recv {
{"SELECT dest,pattern,value,flags FROM rewrite
- WHERE host='$host' and '$url' like url"});
+ WHERE host='$host'
+ AND url IN ($(urlprefixes $url))
+ ORDER BY LENGTH(dest),value DESC"});
set req.http.X-Redirect-To =
diff --git a/doc/vmod-dbrw.texi b/doc/vmod-dbrw.texi
index 40fc84d..31b15d1 100644
--- a/doc/vmod-dbrw.texi
+++ b/doc/vmod-dbrw.texi
@@ -498,4 +498,4 @@ ORDER BY weight
@noindent
-Furthermore, the @code{url} column can contain a SQL wildcard pattern,
-in which case the query will look like:
+Furthermore, the @code{url} column can contain a path prefix,
+which can be matched using the @code{IN} conditional:
@@ -506,3 +506,3 @@ FROM rewrite
WHERE host='$host'
-AND '$url' like $url
+AND url IN ($(urlprefixes $url))
ORDER BY weight
@@ -511,2 +511,12 @@ ORDER BY weight
+Notice the use of the @samp{$(urlprefixes $url)}. This invokes the built-in
+@dfn{function} @code{urlprefixes}, which expands to comma-separated
+list of properly quoted pathname prefixes, constructed from its
+argument. For example, if @samp{$url} is @samp{/local/user/local?a=1},
+then the expansion of @samp{$(urlprefixes $url)} is:
+
+@example
+'/local/user/local','/local/user','/local'
+@end example
+
@node Rewrite
diff --git a/src/vmod_dbrw.c b/src/vmod_dbrw.c
index d6785d7..63d4ea1 100644
--- a/src/vmod_dbrw.c
+++ b/src/vmod_dbrw.c
@@ -191,5 +191,7 @@ parse_flags(const char *arg, int *qdisp, int *flags, char status[])
rc = 1;
- } else
+ } else {
strncpy(status, ws.ws_wordv[i] + 9,
HTTP_STATUS_LEN);
+ status[HTTP_STATUS_LEN] = 0;
+ }
} else if (strncmp(ws.ws_wordv[i], "R=", 2) == 0) {
@@ -199,5 +201,7 @@ parse_flags(const char *arg, int *qdisp, int *flags, char status[])
rc = 1;
- } else
+ } else {
strncpy(status, ws.ws_wordv[i] + 2,
HTTP_STATUS_LEN);
+ status[HTTP_STATUS_LEN] = 0;
+ }
} else {
@@ -505,2 +509,101 @@ findmatch(VRT_CTX, struct dbrw_connection *conn, char **param)
+static int
+expand_error(char **ret, char const *func, char const *msg)
+{
+ static char delim[] = ": ";
+ *ret = malloc(strlen(func) + strlen(msg) + 1);
+ if (*ret) {
+ strcat(strcat(strcpy(*ret, func), delim), msg);
+ return WRDSE_USERERR;
+ } else
+ return WRDSE_NOSPACE;
+}
+
+static int
+expand_urlprefixes(struct dbrw_connection *cp, char **argv, char **ret)
+{
+ char *arg;
+ size_t n, len, i, j;
+ char *q, *res;
+
+ if (argv[1] == NULL || argv[2] != NULL)
+ return expand_error(ret, argv[0], "bad arguments");
+
+ /* Create a copy of the argument */
+ if (cp->conf->backend->sql_escape) {
+ arg = sql_escape(cp, argv[1]);
+ } else {
+ arg = strdup(argv[1]);
+ }
+ if (!arg)
+ return WRDSE_NOSPACE;
+
+ /* Cut off eventual query */
+ i = j = strcspn(arg, "?");
+ arg[i] = 0;
+
+ /* Compute the resulting length */
+ len = i;
+ n = 1;
+ for (; i > 0; i--) {
+ if (arg[i] == '/') {
+ len += i;
+ n++;
+ }
+ }
+
+ /* Count quotes around each member */
+ len += n * 2 + n - 1;
+
+ /* Allocate the result */
+ res = malloc(len + 1);
+ if (!res) {
+ free(arg);
+ return WRDSE_NOSPACE;
+ }
+
+ /* Format the result */
+ q = res;
+ i = j;
+ while (i) {
+ if (q > res)
+ *q++ = ',';
+ *q++ = '\'';
+ memcpy(q, arg, i);
+ q += i;
+ *q++ = '\'';
+ i--;
+ while (i > 0 && arg[i] != '/')
+ i--;
+ }
+ *q = 0;
+ *ret = res;
+ free(arg);
+
+ return WRDSE_OK;
+}
+
+static struct expcom {
+ char *com;
+ int (*exp) (struct dbrw_connection *, char **, char **);
+} expcomtab[] = {
+ { "urlprefixes", expand_urlprefixes },
+ { NULL }
+};
+
+static int
+query_command_expand(char **ret, const char *cmd, size_t len, char **argv,
+ void *clos)
+{
+ struct expcom *ec;
+ static char diagmsg[] = "unknown command: ";
+
+ for (ec = expcomtab; ec->com; ec++) {
+ if (strcmp(ec->com, argv[0]) == 0)
+ return ec->exp(clos, argv, ret);
+ }
+
+ return expand_error(ret, argv[0], "unknown command");
+}
+
static char *
@@ -511,3 +614,4 @@ do_rewrite(VRT_CTX, struct dbrw_connection *cp, VCL_STRING arg)
char *res;
-
+ int wsflags;
+
if (sql_connect(cp) || cp->state != state_connected)
@@ -538,8 +642,15 @@ do_rewrite(VRT_CTX, struct dbrw_connection *cp, VCL_STRING arg)
- debug(cp->conf, 2, ("expanding query"));
+ debug(cp->conf, 2, ("expanding query {\"%s\"}", cp->conf->query));
ws.ws_env = (const char **)wsenv.ws_wordv;
- rc = wordsplit(cp->conf->query, &ws,
- WRDSF_NOCMD | WRDSF_QUOTE |
- WRDSF_NOSPLIT |
- WRDSF_ENV | WRDSF_UNDEF);
+ ws.ws_command = query_command_expand;
+ ws.ws_closure = cp;
+ wsflags = WRDSF_NOSPLIT | WRDSF_CLOSURE | WRDSF_ENV | WRDSF_UNDEF;
+
+ if (cp->conf->debug_level == 100) {
+ ws.ws_debug = dbrw_debug;
+ wsflags |= WRDSF_DEBUG | WRDSF_SHOWDBG;
+ }
+
+ rc = wordsplit(cp->conf->query, &ws, wsflags);
+
if (rc) {
diff --git a/src/wordsplit.c b/src/wordsplit.c
index f4740bf..bad59b1 100644
--- a/src/wordsplit.c
+++ b/src/wordsplit.c
@@ -1,3 +1,3 @@
/* wordsplit - a word splitter
- Copyright (C) 2009-2014 Sergey Poznyakoff
+ Copyright (C) 2009-2018 Sergey Poznyakoff
@@ -27,2 +27,4 @@
#include <stdarg.h>
+#include <pwd.h>
+#include <glob.h>
@@ -50,2 +52,8 @@
+#define ISVARBEG(c) (ISALPHA(c) || c == '_')
+#define ISVARCHR(c) (ISALNUM(c) || c == '_')
+
+#define WSP_RETURN_DELIMS(wsp) \
+ ((wsp)->ws_flags & WRDSF_RETURN_DELIMS || ((wsp)->ws_options & WRDSO_MAXWORDS))
+
#define ALLOC_INIT 128
@@ -56,3 +64,3 @@ _wsplt_alloc_die (struct wordsplit *wsp)
{
- wsp->ws_error (_("memory exhausted"));
+ wsp->ws_error ("%s", _("memory exhausted"));
abort ();
@@ -60,3 +68,3 @@ _wsplt_alloc_die (struct wordsplit *wsp)
-static void
+static void
_wsplt_error (const char *fmt, ...)
@@ -74,2 +82,11 @@ static void wordsplit_free_nodes (struct wordsplit *);
static int
+_wsplt_seterr (struct wordsplit *wsp, int ec)
+{
+ wsp->ws_errno = ec;
+ if (wsp->ws_flags & WRDSF_SHOWERR)
+ wordsplit_perror (wsp);
+ return ec;
+}
+
+static int
_wsplt_nomem (struct wordsplit *wsp)
@@ -88,2 +105,80 @@ _wsplt_nomem (struct wordsplit *wsp)
+static int wordsplit_run (const char *command, size_t length,
+ struct wordsplit *wsp,
+ int flags, int lvl);
+
+static int wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
+ int flags);
+static int wordsplit_process_list (struct wordsplit *wsp, size_t start);
+static int wordsplit_finish (struct wordsplit *wsp);
+
+static int
+_wsplt_subsplit (struct wordsplit *wsp, struct wordsplit *wss,
+ char const *str, int len,
+ int flags, int finalize)
+{
+ int rc;
+
+ wss->ws_delim = wsp->ws_delim;
+ wss->ws_debug = wsp->ws_debug;
+ wss->ws_error = wsp->ws_error;
+ wss->ws_alloc_die = wsp->ws_alloc_die;
+
+ if (!(flags & WRDSF_NOVAR))
+ {
+ wss->ws_env = wsp->ws_env;
+ wss->ws_getvar = wsp->ws_getvar;
+ flags |= wsp->ws_flags & (WRDSF_ENV | WRDSF_ENV_KV | WRDSF_GETVAR);
+ }
+ if (!(flags & WRDSF_NOCMD))
+ {
+ wss->ws_command = wsp->ws_command;
+ }
+
+ if ((flags & (WRDSF_NOVAR|WRDSF_NOCMD)) != (WRDSF_NOVAR|WRDSF_NOCMD))
+ {
+ wss->ws_closure = wsp->ws_closure;
+ flags |= wsp->ws_flags & WRDSF_CLOSURE;
+ }
+
+ wss->ws_options = wsp->ws_options;
+
+ flags |= WRDSF_DELIM
+ | WRDSF_ALLOC_DIE
+ | WRDSF_ERROR
+ | WRDSF_DEBUG
+ | (wsp->ws_flags & (WRDSF_SHOWDBG | WRDSF_SHOWERR | WRDSF_OPTIONS));
+
+ rc = wordsplit_init (wss, str, len, flags);
+ if (rc)
+ return rc;
+ wss->ws_lvl = wsp->ws_lvl + 1;
+ rc = wordsplit_process_list (wss, 0);
+ if (rc)
+ {
+ wordsplit_free_nodes (wss);
+ return rc;
+ }
+ if (finalize)
+ {
+ rc = wordsplit_finish (wss);
+ wordsplit_free_nodes (wss);
+ }
+ return rc;
+}
+
+static void
+_wsplt_seterr_sub (struct wordsplit *wsp, struct wordsplit *wss)
+{
+ if (wsp->ws_errno == WRDSE_USERERR)
+ free (wsp->ws_usererr);
+ wsp->ws_errno = wss->ws_errno;
+ if (wss->ws_errno == WRDSE_USERERR)
+ {
+ wsp->ws_usererr = wss->ws_usererr;
+ wss->ws_errno = WRDSE_EOF;
+ wss->ws_usererr = NULL;
+ }
+}
+
static void
@@ -95,2 +190,3 @@ wordsplit_init0 (struct wordsplit *wsp)
wordsplit_free_words (wsp);
+ wordsplit_clearerr (wsp);
}
@@ -104,5 +200,6 @@ wordsplit_init0 (struct wordsplit *wsp)
wsp->ws_errno = 0;
- wsp->ws_head = wsp->ws_tail = NULL;
}
+char wordsplit_c_escape_tab[] = "\\\\\"\"a\ab\bf\fn\nr\rt\tv\v";
+
static int
@@ -118,10 +215,7 @@ wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
- if (!(wsp->ws_flags & WRDSF_NOVAR)
- && !(wsp->ws_flags & (WRDSF_ENV | WRDSF_GETVAR)))
+ if (!(wsp->ws_flags & WRDSF_NOVAR))
{
- errno = EINVAL;
- wsp->ws_errno = WRDSE_USAGE;
- if (wsp->ws_flags & WRDSF_SHOWERR)
- wordsplit_perror (wsp);
- return wsp->ws_errno;
+ /* These will be initialized on first variable assignment */
+ wsp->ws_envidx = wsp->ws_envsiz = 0;
+ wsp->ws_envbuf = NULL;
}
@@ -130,7 +224,8 @@ wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
{
- errno = EINVAL;
- wsp->ws_errno = WRDSE_NOSUPP;
- if (wsp->ws_flags & WRDSF_SHOWERR)
- wordsplit_perror (wsp);
- return wsp->ws_errno;
+ if (!wsp->ws_command)
+ {
+ _wsplt_seterr (wsp, WRDSE_USAGE);
+ errno = EINVAL;
+ return wsp->ws_errno;
+ }
}
@@ -165,6 +260,38 @@ wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
+ if (!(wsp->ws_flags & WRDSF_OPTIONS))
+ wsp->ws_options = 0;
+
+ if (wsp->ws_flags & WRDSF_ESCAPE)
+ {
+ if (!wsp->ws_escape[WRDSX_WORD])
+ wsp->ws_escape[WRDSX_WORD] = "";
+ if (!wsp->ws_escape[WRDSX_QUOTE])
+ wsp->ws_escape[WRDSX_QUOTE] = "";
+ }
+ else
+ {
+ if (wsp->ws_flags & WRDSF_CESCAPES)
+ {
+ wsp->ws_escape[WRDSX_WORD] = wordsplit_c_escape_tab;
+ wsp->ws_escape[WRDSX_QUOTE] = wordsplit_c_escape_tab;
+ wsp->ws_options |= WRDSO_OESC_QUOTE | WRDSO_OESC_WORD
+ | WRDSO_XESC_QUOTE | WRDSO_XESC_WORD;
+ }
+ else
+ {
+ wsp->ws_escape[WRDSX_WORD] = "";
+ wsp->ws_escape[WRDSX_QUOTE] = "\\\\\"\"";
+ wsp->ws_options |= WRDSO_BSKEEP_QUOTE;
+ }
+ }
+
wsp->ws_endp = 0;
+ wsp->ws_wordi = 0;
+ if (wsp->ws_flags & WRDSF_REUSE)
+ wordsplit_free_nodes (wsp);
+ wsp->ws_head = wsp->ws_tail = NULL;
+
wordsplit_init0 (wsp);
-
+
return 0;
@@ -211,2 +338,3 @@ alloc_space (struct wordsplit *wsp, size_t count)
#define _WSNF_SEXP 0x20 /* is a sed expression */
+#define _WSNF_DELIM 0x40 /* node is a delimiter */
@@ -235,3 +363,3 @@ wsnode_flagstr (int flags)
{
- static char retbuf[6];
+ static char retbuf[7];
char *p = retbuf;
@@ -260,2 +388,6 @@ wsnode_flagstr (int flags)
*p++ = '-';
+ if (flags & _WSNF_DELIM)
+ *p++ = 'd';
+ else
+ *p++ = '-';
*p = 0;
@@ -340,2 +472,10 @@ wsnode_remove (struct wordsplit *wsp, struct wordsplit_node *node)
+static struct wordsplit_node *
+wsnode_tail (struct wordsplit_node *p)
+{
+ while (p && p->next)
+ p = p->next;
+ return p;
+}
+
static void
@@ -355,5 +495,6 @@ wsnode_insert (struct wordsplit *wsp, struct wordsplit_node *node,
{
+ struct wordsplit_node *tail = wsnode_tail (node);
node->prev = NULL;
- node->next = anchor;
- anchor->prev = node;
+ tail->next = anchor;
+ anchor->prev = tail;
wsp->ws_head = node;
@@ -364,2 +505,3 @@ wsnode_insert (struct wordsplit *wsp, struct wordsplit_node *node,
struct wordsplit_node *p;
+ struct wordsplit_node *tail = wsnode_tail (node);
@@ -367,6 +509,6 @@ wsnode_insert (struct wordsplit *wsp, struct wordsplit_node *node,
if (p)
- p->prev = node;
+ p->prev = tail;
else
- wsp->ws_tail = node;
- node->next = p;
+ wsp->ws_tail = tail;
+ tail->next = p;
node->prev = anchor;
@@ -417,6 +559,8 @@ wordsplit_dump_nodes (struct wordsplit *wsp)
if (p->flags & _WSNF_WORD)
- wsp->ws_debug ("%4d: %p: %#04x (%s):%s;",
+ wsp->ws_debug ("(%02d) %4d: %p: %#04x (%s):%s;",
+ wsp->ws_lvl,
n, p, p->flags, wsnode_flagstr (p->flags), p->v.word);
else
- wsp->ws_debug ("%4d: %p: %#04x (%s):%.*s;",
+ wsp->ws_debug ("(%02d) %4d: %p: %#04x (%s):%.*s;",
+ wsp->ws_lvl,
n, p, p->flags, wsnode_flagstr (p->flags),
@@ -435,2 +579,5 @@ coalesce_segment (struct wordsplit *wsp, struct wordsplit_node *node)
+ if (!(node->flags & _WSNF_JOIN))
+ return 0;
+
for (p = node; p && (p->flags & _WSNF_JOIN); p = p->next)
@@ -459,2 +606,3 @@ coalesce_segment (struct wordsplit *wsp, struct wordsplit_node *node)
{
+ node->flags |= p->flags & _WSNF_QUOTE;
wsnode_remove (wsp, p);
@@ -478,2 +626,6 @@ coalesce_segment (struct wordsplit *wsp, struct wordsplit_node *node)
+static void wordsplit_string_unquote_copy (struct wordsplit *ws, int inquote,
+ char *dst, const char *src,
+ size_t n);
+
static int
@@ -482,5 +634,2 @@ wsnode_quoteremoval (struct wordsplit *wsp)
struct wordsplit_node *p;
- void (*uqfn) (char *, const char *, size_t) =
- (wsp->ws_flags & WRDSF_CESCAPES) ?
- wordsplit_c_unquote_copy : wordsplit_sh_unquote_copy;
@@ -493,5 +642,3 @@ wsnode_quoteremoval (struct wordsplit *wsp)
if (wsp->ws_flags & WRDSF_QUOTE)
- {
- unquote = !(p->flags & _WSNF_NOEXPAND);
- }
+ unquote = !(p->flags & _WSNF_NOEXPAND);
else
@@ -512,7 +659,4 @@ wsnode_quoteremoval (struct wordsplit *wsp)
- if (wsp->ws_flags & WRDSF_ESCAPE)
- wordsplit_general_unquote_copy (p->v.word, str, slen,
- wsp->ws_escape);
- else
- uqfn (p->v.word, str, slen);
+ wordsplit_string_unquote_copy (wsp, p->flags & _WSNF_QUOTE,
+ p->v.word, str, slen);
}
@@ -537,2 +681,21 @@ wsnode_coalesce (struct wordsplit *wsp)
static int
+wsnode_tail_coalesce (struct wordsplit *wsp, struct wordsplit_node *p)
+{
+ if (p->next)
+ {
+ struct wordsplit_node *np = p;
+ while (np && np->next)
+ {
+ np->flags |= _WSNF_JOIN;
+ np = np->next;
+ }
+ if (coalesce_segment (wsp, p))
+ return 1;
+ }
+ return 0;
+}
+
+static size_t skip_delim (struct wordsplit *wsp);
+
+static int
wordsplit_finish (struct wordsplit *wsp)
@@ -541,15 +704,133 @@ wordsplit_finish (struct wordsplit *wsp)
size_t n;
+ int delim;
- n = 0;
+ /* Postprocess delimiters. It would be rather simple, if it weren't for
+ the incremental operation.
- for (p = wsp->ws_head; p; p = p->next)
- n++;
+ Nodes of type _WSNF_DELIM get inserted to the node list if either
+ WRDSF_RETURN_DELIMS flag or WRDSO_MAXWORDS option is set.
+
+ The following cases should be distinguished:
+
+ 1. If both WRDSF_SQUEEZE_DELIMS and WRDSF_RETURN_DELIMS are set, compress
+ any runs of similar delimiter nodes to a single node. The nodes are
+ 'similar' if they point to the same delimiter character.
+
+ If WRDSO_MAXWORDS option is set, stop compressing when
+ ws_wordi + 1 == ws_maxwords, and coalesce the rest of nodes into
+ a single last node.
+
+ 2. If WRDSO_MAXWORDS option is set, but WRDSF_RETURN_DELIMS is not,
+ remove any delimiter nodes. Stop operation when
+ ws_wordi + 1 == ws_maxwords, and coalesce the rest of nodes into
+ a single last node.
+
+ 3. If incremental operation is in progress, restart the loop any time
+ a delimiter node is about to be returned, unless WRDSF_RETURN_DELIMS
+ is set.
+ */
+ again:
+ delim = 0; /* Delimiter being processed (if any) */
+ n = 0; /* Number of words processed so far */
+ p = wsp->ws_head; /* Current node */
+
+ while (p)
+ {
+ struct wordsplit_node *next = p->next;
+ if (p->flags & _WSNF_DELIM)
+ {
+ if (wsp->ws_flags & WRDSF_RETURN_DELIMS)
+ {
+ if (wsp->ws_flags & WRDSF_SQUEEZE_DELIMS)
+ {
+ char const *s = wsnode_ptr (wsp, p);
+ if (delim)
+ {
+ if (delim == *s)
+ {
+ wsnode_remove (wsp, p);
+ p = next;
+ continue;
+ }
+ else
+ {
+ delim = 0;
+ n++; /* Count this node; it will be returned */
+ }
+ }
+ else
+ {
+ delim = *s;
+ p = next;
+ continue;
+ }
+ }
+ }
+ else if (wsp->ws_options & WRDSO_MAXWORDS)
+ {
+ wsnode_remove (wsp, p);
+ p = next;
+ continue;
+ }
+ }
+ else
+ {
+ if (delim)
+ {
+ /* Last node was a delimiter or a compressed run of delimiters;
+ Count it, and clear the delimiter marker */
+ n++;
+ delim = 0;
+ }
+ if (wsp->ws_options & WRDSO_MAXWORDS)
+ {
+ if (wsp->ws_wordi + n + 1 == wsp->ws_maxwords)
+ break;
+ }
+ }
+ n++;
+ if (wsp->ws_flags & WRDSF_INCREMENTAL)
+ p = NULL; /* Break the loop */
+ else
+ p = next;
+ }
+
+ if (p)
+ {
+ /* We're here if WRDSO_MAXWORDS is in effect and wsp->ws_maxwords
+ words have already been collected. Reconstruct a single final
+ node from the remaining nodes. */
+ if (wsnode_tail_coalesce (wsp, p))
+ return wsp->ws_errno;
+ n++;
+ }
+
+ if (n == 0 && (wsp->ws_flags & WRDSF_INCREMENTAL))
+ {
+ /* The loop above have eliminated all nodes. Restart the
+ processing, if there's any input left. */
+ if (wsp->ws_endp < wsp->ws_len)
+ {
+ int rc;
+ if (wsp->ws_flags & WRDSF_SHOWDBG)
+ wsp->ws_debug (_("Restarting"));
+ rc = wordsplit_process_list (wsp, skip_delim (wsp));
+ if (rc)
+ return rc;
+ }
+ else
+ {
+ wsp->ws_error = WRDSE_EOF;
+ return WRDSE_EOF;
+ }
+ goto again;
+ }
if (alloc_space (wsp, n + 1))
- return 1;
+ return wsp->ws_errno;
- for (p = wsp->ws_head; p; p = p->next)
+ while (wsp->ws_head)
{
- const char *str = wsnode_ptr (wsp, p);
- size_t slen = wsnode_len (p);
+ const char *str = wsnode_ptr (wsp, wsp->ws_head);
+ size_t slen = wsnode_len (wsp->ws_head);
char *newstr = malloc (slen + 1);
@@ -565,4 +846,9 @@ wordsplit_finish (struct wordsplit *wsp)
+ wsnode_remove (wsp, wsp->ws_head);
+
wsp->ws_wordc++;
+ wsp->ws_wordi++;
+ if (wsp->ws_flags & WRDSF_INCREMENTAL)
+ break;
}
@@ -572,3 +858,31 @@ wordsplit_finish (struct wordsplit *wsp)
+int
+wordsplit_append (wordsplit_t *wsp, int argc, char **argv)
+{
+ int rc;
+ size_t i;
+ rc = alloc_space (wsp, wsp->ws_wordc + argc + 1);
+ if (rc)
+ return rc;
+ for (i = 0; i < argc; i++)
+ {
+ char *newstr = strdup (argv[i]);
+ if (!newstr)
+ {
+ while (i > 0)
+ {
+ free (wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc + i - 1]);
+ wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc + i - 1] = NULL;
+ i--;
+ }
+ return _wsplt_nomem (wsp);
+ }
+ wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc + i] = newstr;
+ }
+ wsp->ws_wordc += i;
+ wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc] = NULL;
+ return 0;
+}
+
/* Variable expansion */
@@ -609,6 +923,6 @@ node_split_prefix (struct wordsplit *wsp,
static int
-find_closing_cbrace (const char *str, size_t i, size_t len, size_t * poff)
+find_closing_paren (const char *str, size_t i, size_t len, size_t *poff,
+ char const *paren)
{
- enum
- { st_init, st_squote, st_dquote } state = st_init;
+ enum { st_init, st_squote, st_dquote } state = st_init;
size_t level = 1;
@@ -622,14 +936,19 @@ find_closing_cbrace (const char *str, size_t i, size_t len, size_t * poff)
{
- case '{':
- level++;
- break;
-
- case '}':
- if (--level == 0)
+ default:
+ if (str[i] == paren[0])
+ {
+ level++;
+ break;
+ }
+ else if (str[i] == paren[1])
{
- *poff = i;
- return 0;
+ if (--level == 0)
+ {
+ *poff = i;
+ return 0;
+ }
+ break;
}
break;
-
+
case '"':
@@ -660,4 +979,5 @@ find_closing_cbrace (const char *str, size_t i, size_t len, size_t * poff)
-static const char *
-wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len)
+static int
+wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len,
+ char const **ret)
{
@@ -666,3 +986,3 @@ wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len)
if (!(wsp->ws_flags & WRDSF_ENV))
- return NULL;
+ return WRDSE_UNDEF;
@@ -675,3 +995,6 @@ wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len)
if (elen == len && memcmp (wsp->ws_env[i], name, elen) == 0)
- return wsp->ws_env[i + 1];
+ {
+ *ret = wsp->ws_env[i + 1];
+ return WRDSE_OK;
+ }
/* Skip the value. Break the loop if it is NULL. */
@@ -682,3 +1005,3 @@ wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len)
}
- else
+ else if (wsp->ws_env)
{
@@ -694,6 +1017,113 @@ wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len)
if (j == len && var[j] == '=')
- return var + j + 1;
+ {
+ *ret = var + j + 1;
+ return WRDSE_OK;
+ }
}
}
- return NULL;
+ return WRDSE_UNDEF;
+}
+
+static int
+wsplt_assign_var (struct wordsplit *wsp, const char *name, size_t namelen,
+ char *value)
+{
+ int n = (wsp->ws_flags & WRDSF_ENV_KV) ? 2 : 1;
+ char *v;
+
+ if (wsp->ws_envidx + n >= wsp->ws_envsiz)
+ {
+ size_t sz;
+ char **newenv;
+
+ if (!wsp->ws_envbuf)
+ {
+ if (wsp->ws_flags & WRDSF_ENV)
+ {
+ size_t i = 0, j;
+
+ if (wsp->ws_env)
+ {
+ for (; wsp->ws_env[i]; i++)
+ ;
+ }
+
+ sz = i + n + 1;
+
+ newenv = calloc (sz, sizeof(newenv[0]));
+ if (!newenv)
+ return _wsplt_nomem (wsp);
+
+ for (j = 0; j < i; j++)
+ {
+ newenv[j] = strdup (wsp->ws_env[j]);
+ if (!newenv[j])
+ {
+ for (; j > 1; j--)
+ free (newenv[j-1]);
+ free (newenv[j-1]);
+ return _wsplt_nomem (wsp);
+ }
+ }
+ newenv[j] = NULL;
+
+ wsp->ws_envbuf = newenv;
+ wsp->ws_envidx = i;
+ wsp->ws_envsiz = sz;
+ wsp->ws_env = (const char**) wsp->ws_envbuf;
+ }
+ else
+ {
+ newenv = calloc (WORDSPLIT_ENV_INIT, sizeof(newenv[0]));
+ if (!newenv)
+ return _wsplt_nomem (wsp);
+ wsp->ws_envbuf = newenv;
+ wsp->ws_envidx = 0;
+ wsp->ws_envsiz = WORDSPLIT_ENV_INIT;
+ wsp->ws_env = (const char**) wsp->ws_envbuf;
+ wsp->ws_flags |= WRDSF_ENV;
+ }
+ }
+ else
+ {
+ wsp->ws_envsiz *= 2;
+ newenv = realloc (wsp->ws_envbuf,
+ wsp->ws_envsiz * sizeof (wsp->ws_envbuf[0]));
+ if (!newenv)
+ return _wsplt_nomem (wsp);
+ wsp->ws_envbuf = newenv;
+ wsp->ws_env = (const char**) wsp->ws_envbuf;
+ }
+ }
+
+ if (wsp->ws_flags & WRDSF_ENV_KV)
+ {
+ /* A key-value pair environment */
+ char *p = malloc (namelen + 1);
+ if (!p)
+ return _wsplt_nomem (wsp);
+ memcpy (p, name, namelen);
+ p[namelen] = 0;
+
+ v = strdup (value);
+ if (!v)
+ {
+ free (p);
+ return _wsplt_nomem (wsp);
+ }
+ wsp->ws_env[wsp->ws_envidx++] = p;
+ wsp->ws_env[wsp->ws_envidx++] = v;
+ }
+ else
+ {
+ v = malloc (namelen + strlen(value) + 2);
+ if (!v)
+ return _wsplt_nomem (wsp);
+ memcpy (v, name, namelen);
+ v[namelen++] = '=';
+ strcpy(v + namelen, value);
+ wsp->ws_env[wsp->ws_envidx++] = v;
+ }
+ wsp->ws_env[wsp->ws_envidx++] = NULL;
+ return WRDSE_OK;
}
@@ -706,3 +1136,3 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
const char *defstr = NULL;
- const char *value;
+ char *value;
const char *vptr;
@@ -710,7 +1140,9 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
const char *start = str - 1;
-
- if (ISALPHA (str[0]) || str[0] == '_')
+ int rc;
+ struct wordsplit ws;
+
+ if (ISVARBEG (str[0]))
{
for (i = 1; i < len; i++)
- if (!(ISALNUM (str[i]) || str[i] == '_'))
+ if (!ISVARCHR (str[i]))
break;
@@ -723,26 +1155,32 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
for (i = 1; i < len; i++)
- if (str[i] == '}' || str[i] == ':')
- break;
- if (str[i] == ':')
{
- size_t j;
-
- defstr = str + i + 1;
- if (find_closing_cbrace (str, i + 1, len, &j))
+ if (str[i] == ':')
{
- wsp->ws_errno = WRDSE_CBRACE;
- return 1;
+ size_t j;
+
+ defstr = str + i + 1;
+ if (find_closing_paren (str, i + 1, len, &j, "{}"))
+ return _wsplt_seterr (wsp, WRDSE_CBRACE);
+ *pend = str + j;
+ break;
+ }
+ else if (str[i] == '}')
+ {
+ defstr = NULL;
+ *pend = str + i;
+ break;
+ }
+ else if (strchr ("-+?=", str[i]))
+ {
+ size_t j;
+
+ defstr = str + i;
+ if (find_closing_paren (str, i, len, &j, "{}"))
+ return _wsplt_seterr (wsp, WRDSE_CBRACE);
+ *pend = str + j;
+ break;
}
- *pend = str + j;
- }
- else if (str[i] == '}')
- {
- defstr = NULL;
- *pend = str + i;
- }
- else
- {
- wsp->ws_errno = WRDSE_CBRACE;
- return 1;
}
+ if (i == len)
+ return _wsplt_seterr (wsp, WRDSE_CBRACE);
}
@@ -770,28 +1208,140 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
- vptr = wordsplit_find_env (wsp, str, i);
- if (vptr)
+ if (defstr && strchr("-+?=", defstr[0]) == 0)
{
- value = strdup (vptr);
- if (!value)
- return _wsplt_nomem (wsp);
+ rc = WRDSE_UNDEF;
+ defstr = NULL;
}
- else if (wsp->ws_flags & WRDSF_GETVAR)
- value = wsp->ws_getvar (str, i, wsp->ws_closure);
- else if (wsp->ws_flags & WRDSF_UNDEF)
+ else
{
- wsp->ws_errno = WRDSE_UNDEF;
- if (wsp->ws_flags & WRDSF_SHOWERR)
- wordsplit_perror (wsp);
- return 1;
+ rc = wordsplit_find_env (wsp, str, i, &vptr);
+ if (rc == WRDSE_OK)
+ {
+ if (vptr)
+ {
+ value = strdup (vptr);
+ if (!value)
+ rc = WRDSE_NOSPACE;
+ }
+ else
+ rc = WRDSE_UNDEF;
+ }
+ else if (wsp->ws_flags & WRDSF_GETVAR)
+ rc = wsp->ws_getvar (&value, str, i, wsp->ws_closure);
+ else
+ rc = WRDSE_UNDEF;
+
+ if (rc == WRDSE_OK
+ && (!value || value[0] == 0)
+ && defstr && defstr[-1] == ':')
+ {
+ free (value);
+ rc = WRDSE_UNDEF;
+ }
}
- else
+
+ switch (rc)
{
- if (wsp->ws_flags & WRDSF_WARNUNDEF)
- wsp->ws_error (_("warning: undefined variable `%.*s'"), (int) i, str);
- if (wsp->ws_flags & WRDSF_KEEPUNDEF)
- value = NULL;
+ case WRDSE_OK:
+ if (defstr && *defstr == '+')
+ {
+ size_t size = *pend - ++defstr;
+
+ rc = _wsplt_subsplit (wsp, &ws, defstr, size,
+ WRDSF_NOSPLIT | WRDSF_WS | WRDSF_QUOTE |
+ (wsp->ws_flags &
+ (WRDSF_NOVAR | WRDSF_NOCMD)), 1);
+ if (rc)
+ return rc;
+ free (value);
+ value = ws.ws_wordv[0];
+ ws.ws_wordv[0] = NULL;
+ wordsplit_free (&ws);
+ }
+ break;
+
+ case WRDSE_UNDEF:
+ if (defstr)
+ {
+ size_t size;
+ if (*defstr == '-' || *defstr == '=')
+ {
+ size = *pend - ++defstr;
+
+ rc = _wsplt_subsplit (wsp, &ws, defstr, size,
+ WRDSF_NOSPLIT | WRDSF_WS | WRDSF_QUOTE |
+ (wsp->ws_flags &
+ (WRDSF_NOVAR | WRDSF_NOCMD)),
+ 1);
+ if (rc)
+ return rc;
+
+ value = ws.ws_wordv[0];
+ ws.ws_wordv[0] = NULL;
+ wordsplit_free (&ws);
+
+ if (defstr[-1] == '=')
+ wsplt_assign_var (wsp, str, i, value);
+ }
+ else
+ {
+ if (*defstr == '?')
+ {
+ size = *pend - ++defstr;
+ if (size == 0)
+ wsp->ws_error (_("%.*s: variable null or not set"),
+ (int) i, str);
+ else
+ {
+ rc = _wsplt_subsplit (wsp, &ws, defstr, size,
+ WRDSF_NOSPLIT | WRDSF_WS |
+ WRDSF_QUOTE |
+ (wsp->ws_flags &
+ (WRDSF_NOVAR | WRDSF_NOCMD)),
+ 1);
+ if (rc == 0)
+ wsp->ws_error ("%.*s: %s",
+ (int) i, str, ws.ws_wordv[0]);
+ else
+ wsp->ws_error ("%.*s: %.*s",
+ (int) i, str, (int) size, defstr);
+ wordsplit_free (&ws);
+ }
+ }
+ value = NULL;
+ }
+ }
+ else if (wsp->ws_flags & WRDSF_UNDEF)
+ {
+ _wsplt_seterr (wsp, WRDSE_UNDEF);
+ return 1;
+ }
else
- value = "";
+ {
+ if (wsp->ws_flags & WRDSF_WARNUNDEF)
+ wsp->ws_error (_("warning: undefined variable `%.*s'"),
+ (int) i, str);
+ if (wsp->ws_flags & WRDSF_KEEPUNDEF)
+ value = NULL;
+ else
+ {
+ value = strdup ("");
+ if (!value)
+ return _wsplt_nomem (wsp);
+ }
+ }
+ break;
+
+ case WRDSE_NOSPACE:
+ return _wsplt_nomem (wsp);
+
+ case WRDSE_USERERR:
+ if (wsp->ws_errno == WRDSE_USERERR)
+ free (wsp->ws_usererr);
+ wsp->ws_usererr = value;
+ /* fall through */
+ default:
+ _wsplt_seterr (wsp, rc);
+ return 1;
}
- /* FIXME: handle defstr */
+
if (value)
@@ -805,5 +1355,3 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
- newnode->v.word = strdup (value);
- if (!newnode->v.word)
- return _wsplt_nomem (wsp);
+ newnode->v.word = value;
}
@@ -811,2 +1359,3 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
{
+ free (value);
/* Empty string is a special case */
@@ -821,8 +1370,13 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
struct wordsplit ws;
- int i;
-
- ws.ws_delim = wsp->ws_delim;
- if (wordsplit (value, &ws,
- WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_DELIM | WRDSF_WS))
+ int rc;
+
+ rc = _wsplt_subsplit (wsp, &ws, value, strlen (value),
+ WRDSF_NOVAR | WRDSF_NOCMD |
+ WRDSF_QUOTE
+ | (WSP_RETURN_DELIMS (wsp) ? WRDSF_RETURN_DELIMS : 0) ,
+ 0);
+ free (value);
+ if (rc)
{
+ _wsplt_seterr_sub (wsp, &ws);
wordsplit_free (&ws);
@@ -830,15 +1384,5 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
}
- for (i = 0; i < ws.ws_wordc; i++)
- {
- if (wsnode_new (wsp, &newnode))
- return 1;
- wsnode_insert (wsp, newnode, *ptail, 0);
- *ptail = newnode;
- newnode->flags = _WSNF_WORD |
- _WSNF_NOEXPAND |
- (i + 1 < ws.ws_wordc ? (flg & ~_WSNF_JOIN) : flg);
- newnode->v.word = strdup (ws.ws_wordv[i]);
- if (!newnode->v.word)
- return _wsplt_nomem (wsp);
- }
+ wsnode_insert (wsp, ws.ws_head, *ptail, 0);
+ *ptail = ws.ws_tail;
+ ws.ws_head = ws.ws_tail = NULL;
wordsplit_free (&ws);
@@ -873,3 +1417,15 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
static int
-node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
+begin_var_p (int c)
+{
+ return c == '{' || ISVARBEG (c);
+}
+
+static int
+node_expand (struct wordsplit *wsp, struct wordsplit_node *node,
+ int (*beg_p) (int),
+ int (*ws_exp_fn) (struct wordsplit *wsp,
+ const char *str, size_t len,
+ struct wordsplit_node **ptail,
+ const char **pend,
+ int flg))
{
@@ -889,3 +1445,3 @@ node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
}
- if (*p == '$')
+ if (*p == '$' && beg_p (p[1]))
{
@@ -898,4 +1454,4 @@ node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
p++;
- if (expvar (wsp, p, slen - n, &tail, &p,
- node->flags & (_WSNF_JOIN | _WSNF_QUOTE)))
+ if (ws_exp_fn (wsp, p, slen - n, &tail, &p,
+ node->flags & (_WSNF_JOIN | _WSNF_QUOTE)))
return 1;
@@ -910,3 +1466,3 @@ node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
if (node_split_prefix (wsp, &tail, node, off, p - str,
- node->flags & _WSNF_JOIN))
+ node->flags & (_WSNF_JOIN|_WSNF_QUOTE)))
return 1;
@@ -920,4 +1476,4 @@ node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
}
-
-/* Remove NULL lists */
+
+/* Remove NULL nodes from the list */
static void
@@ -930,2 +1486,4 @@ wsnode_nullelim (struct wordsplit *wsp)
struct wordsplit_node *next = p->next;
+ if (p->flags & _WSNF_DELIM && p->prev)
+ p->prev->flags &= ~_WSNF_JOIN;
if (p->flags & _WSNF_NULL)
@@ -947,4 +1505,127 @@ wordsplit_varexp (struct wordsplit *wsp)
struct wordsplit_node *next = p->next;
+ if (!(p->flags & (_WSNF_NOEXPAND|_WSNF_DELIM)))
+ if (node_expand (wsp, p, begin_var_p, expvar))
+ return 1;
+ p = next;
+ }
+
+ wsnode_nullelim (wsp);
+ return 0;
+}
+
+static int
+begin_cmd_p (int c)
+{
+ return c == '(';
+}
+
+static int
+expcmd (struct wordsplit *wsp, const char *str, size_t len,
+ struct wordsplit_node **ptail, const char **pend, int flg)
+{
+ int rc;
+ size_t j;
+ char *value;
+ struct wordsplit_node *newnode;
+ struct wordsplit ws;
+
+ str++;
+ len--;
+
+ if (find_closing_paren (str, 0, len, &j, "()"))
+ {
+ _wsplt_seterr (wsp, WRDSE_PAREN);
+ return 1;
+ }
+
+ *pend = str + j;
+ rc = _wsplt_subsplit (wsp, &ws, str, j, WRDSF_WS | WRDSF_QUOTE, 1);
+ if (rc)
+ {
+ _wsplt_seterr_sub (wsp, &ws);
+ wordsplit_free (&ws);
+ return 1;
+ }
+ rc = wsp->ws_command (&value, str, j, ws.ws_wordv, wsp->ws_closure);
+ wordsplit_free (&ws);
+
+ if (rc == WRDSE_NOSPACE)
+ return _wsplt_nomem (wsp);
+ else if (rc)
+ {
+ if (rc == WRDSE_USERERR)
+ {
+ if (wsp->ws_errno == WRDSE_USERERR)
+ free (wsp->ws_usererr);
+ wsp->ws_usererr = value;
+ }
+ _wsplt_seterr (wsp, rc);
+ return 1;
+ }
+
+ if (value)
+ {
+ if (flg & _WSNF_QUOTE)
+ {
+ if (wsnode_new (wsp, &newnode))
+ return 1;
+ wsnode_insert (wsp, newnode, *ptail, 0);
+ *ptail = newnode;
+ newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
+ newnode->v.word = value;
+ }
+ else if (*value == 0)
+ {
+ free (value);
+ /* Empty string is a special case */
+ if (wsnode_new (wsp, &newnode))
+ return 1;
+ wsnode_insert (wsp, newnode, *ptail, 0);
+ *ptail = newnode;
+ newnode->flags = _WSNF_NULL;
+ }
+ else
+ {
+ struct wordsplit ws;
+ int rc;
+
+ rc = _wsplt_subsplit (wsp, &ws, value, strlen (value),
+ WRDSF_NOVAR | WRDSF_NOCMD
+ | WRDSF_WS | WRDSF_QUOTE
+ | (WSP_RETURN_DELIMS (wsp) ? WRDSF_RETURN_DELIMS : 0),
+ 0);
+ free (value);
+ if (rc)
+ {
+ _wsplt_seterr_sub (wsp, &ws);
+ wordsplit_free (&ws);
+ return 1;
+ }
+ wsnode_insert (wsp, ws.ws_head, *ptail, 0);
+ *ptail = ws.ws_tail;
+ ws.ws_head = ws.ws_tail = NULL;
+ wordsplit_free (&ws);
+ }
+ }
+ else
+ {
+ if (wsnode_new (wsp, &newnode))
+ return 1;
+ wsnode_insert (wsp, newnode, *ptail, 0);
+ *ptail = newnode;
+ newnode->flags = _WSNF_NULL;
+ }
+ return 0;
+}
+
+static int
+wordsplit_cmdexp (struct wordsplit *wsp)
+{
+ struct wordsplit_node *p;
+
+ for (p = wsp->ws_head; p;)
+ {
+ struct wordsplit_node *next = p->next;
if (!(p->flags & _WSNF_NOEXPAND))
- if (node_expand_vars (wsp, p))
+ if (node_expand (wsp, p, begin_cmd_p, expcmd))
return 1;
@@ -960,3 +1641,3 @@ wordsplit_varexp (struct wordsplit *wsp)
node in the list is a text reference node. */
-static void
+static int
wordsplit_trimws (struct wordsplit *wsp)
@@ -969,10 +1650,17 @@ wordsplit_trimws (struct wordsplit *wsp)
+ if (!(p->flags & _WSNF_QUOTE))
+ {
+ /* Skip leading whitespace: */
+ for (n = p->v.segm.beg; n < p->v.segm.end && ISWS (wsp->ws_input[n]);
+ n++)
+ ;
+ p->v.segm.beg = n;
+ }
+
+ while (p->next && (p->flags & _WSNF_JOIN))
+ p = p->next;
+
if (p->flags & _WSNF_QUOTE)
continue;
-
- /* Skip leading whitespace: */
- for (n = p->v.segm.beg; n < p->v.segm.end && ISWS (wsp->ws_input[n]);
- n++)
- ;
- p->v.segm.beg = n;
+
/* Trim trailing whitespace */
@@ -986,2 +1674,192 @@ wordsplit_trimws (struct wordsplit *wsp)
wsnode_nullelim (wsp);
+ return 0;
+}
+
+static int
+wordsplit_tildexpand (struct wordsplit *wsp)
+{
+ struct wordsplit_node *p;
+ char *uname = NULL;
+ size_t usize = 0;
+
+ for (p = wsp->ws_head; p; p = p->next)
+ {
+ const char *str;
+
+ if (p->flags & _WSNF_QUOTE)
+ continue;
+
+ str = wsnode_ptr (wsp, p);
+ if (str[0] == '~')
+ {
+ size_t i, size, dlen;
+ size_t slen = wsnode_len (p);
+ struct passwd *pw;
+ char *newstr;
+
+ for (i = 1; i < slen && str[i] != '/'; i++)
+ ;
+ if (i == slen)
+ continue;
+ if (i > 1)
+ {
+ if (i > usize)
+ {
+ char *p = realloc (uname, i);
+ if (!p)
+ {
+ free (uname);
+ return _wsplt_nomem (wsp);
+ }
+ uname = p;
+ usize = i;
+ }
+ --i;
+ memcpy (uname, str + 1, i);
+ uname[i] = 0;
+ pw = getpwnam (uname);
+ }
+ else
+ pw = getpwuid (getuid ());
+
+ if (!pw)
+ continue;
+
+ dlen = strlen (pw->pw_dir);
+ size = slen - i + dlen;
+ newstr = malloc (size);
+ if (!newstr)
+ {
+ free (uname);
+ return _wsplt_nomem (wsp);
+ }
+ --size;
+
+ memcpy (newstr, pw->pw_dir, dlen);
+ memcpy (newstr + dlen, str + i + 1, slen - i - 1);
+ newstr[size] = 0;
+ if (p->flags & _WSNF_WORD)
+ free (p->v.word);
+ p->v.word = newstr;
+ p->flags |= _WSNF_WORD;
+ }
+ }
+ free (uname);
+ return 0;
+}
+
+static int
+isglob (const char *s, int l)
+{
+ while (l--)
+ {
+ if (strchr ("*?[", *s++))
+ return 1;
+ }
+ return 0;
+}
+
+static int
+wordsplit_pathexpand (struct wordsplit *wsp)
+{
+ struct wordsplit_node *p, *next;
+ char *pattern = NULL;
+ size_t patsize = 0;
+ size_t slen;
+ int flags = 0;
+
+#ifdef GLOB_PERIOD
+ if (wsp->ws_options & WRDSO_DOTGLOB)
+ flags = GLOB_PERIOD;
+#endif
+
+ for (p = wsp->ws_head; p; p = next)
+ {
+ const char *str;
+
+ next = p->next;
+
+ if (p->flags & _WSNF_QUOTE)
+ continue;
+
+ str = wsnode_ptr (wsp, p);
+ slen = wsnode_len (p);
+
+ if (isglob (str, slen))
+ {
+ int i;
+ glob_t g;
+ struct wordsplit_node *prev;
+
+ if (slen + 1 > patsize)
+ {
+ char *p = realloc (pattern, slen + 1);
+ if (!p)
+ return _wsplt_nomem (wsp);
+ pattern = p;
+ patsize = slen + 1;
+ }
+ memcpy (pattern, str, slen);
+ pattern[slen] = 0;
+
+ switch (glob (pattern, flags, NULL, &g))
+ {
+ case 0:
+ break;
+
+ case GLOB_NOSPACE:
+ free (pattern);
+ return _wsplt_nomem (wsp);
+
+ case GLOB_NOMATCH:
+ if (wsp->ws_options & WRDSO_NULLGLOB)
+ {
+ wsnode_remove (wsp, p);
+ wsnode_free (p);
+ }
+ else if (wsp->ws_options & WRDSO_FAILGLOB)
+ {
+ char buf[128];
+ if (wsp->ws_errno == WRDSE_USERERR)
+ free (wsp->ws_usererr);
+ snprintf (buf, sizeof (buf), _("no files match pattern %s"),
+ pattern);
+ free (pattern);
+ wsp->ws_usererr = strdup (buf);
+ if (!wsp->ws_usererr)
+ return _wsplt_nomem (wsp);
+ else
+ return _wsplt_seterr (wsp, WRDSE_USERERR);
+ }
+ continue;
+
+ default:
+ free (pattern);
+ return _wsplt_seterr (wsp, WRDSE_GLOBERR);
+ }
+
+ prev = p;
+ for (i = 0; i < g.gl_pathc; i++)
+ {
+ struct wordsplit_node *newnode;
+ char *newstr;
+
+ if (wsnode_new (wsp, &newnode))
+ return 1;
+ newstr = strdup (g.gl_pathv[i]);
+ if (!newstr)
+ return _wsplt_nomem (wsp);
+ newnode->v.word = newstr;
+ newnode->flags |= _WSNF_WORD|_WSNF_QUOTE;
+ wsnode_insert (wsp, newnode, prev, 0);
+ prev = newnode;
+ }
+ globfree (&g);
+
+ wsnode_remove (wsp, p);
+ wsnode_free (p);
+ }
+ }
+ free (pattern);
+ return 0;
}
@@ -1021,29 +1899,20 @@ skip_sed_expr (const char *command, size_t i, size_t len)
-static size_t
-skip_delim (struct wordsplit *wsp)
+/* wsp->ws_endp points to a delimiter character. If RETURN_DELIMS
+ is true, return its value, otherwise return the index past it. */
+static inline size_t
+skip_delim_internal (struct wordsplit *wsp, int return_delims)
{
- size_t start = wsp->ws_endp;
- if (wsp->ws_flags & WRDSF_SQUEEZE_DELIMS)
- {
- if ((wsp->ws_flags & WRDSF_RETURN_DELIMS) &&
- ISDELIM (wsp, wsp->ws_input[start]))
- {
- int delim = wsp->ws_input[start];
- do
- start++;
- while (start < wsp->ws_len && delim == wsp->ws_input[start]);
- }
- else
- {
- do
- start++;
- while (start < wsp->ws_len && ISDELIM (wsp, wsp->ws_input[start]));
- }
- start--;
- }
+ return return_delims ? wsp->ws_endp : wsp->ws_endp + 1;
+}
- if (!(wsp->ws_flags & WRDSF_RETURN_DELIMS))
- start++;
+static inline size_t
+skip_delim (struct wordsplit *wsp)
+{
+ return skip_delim_internal (wsp, WSP_RETURN_DELIMS (wsp));
+}
- return start;
+static inline size_t
+skip_delim_real (struct wordsplit *wsp)
+{
+ return skip_delim_internal (wsp, wsp->ws_flags & WRDSF_RETURN_DELIMS);
}
@@ -1055,3 +1924,3 @@ skip_delim (struct wordsplit *wsp)
static int
-scan_qstring (struct wordsplit *wsp, size_t start, size_t * end)
+scan_qstring (struct wordsplit *wsp, size_t start, size_t *end)
{
@@ -1077,5 +1946,3 @@ scan_qstring (struct wordsplit *wsp, size_t start, size_t * end)
wsp->ws_endp = start;
- wsp->ws_errno = WRDSE_QUOTE;
- if (wsp->ws_flags & WRDSF_SHOWERR)
- wordsplit_perror (wsp);
+ _wsplt_seterr (wsp, WRDSE_QUOTE);
return _WRDS_ERR;
@@ -1086,3 +1953,3 @@ scan_qstring (struct wordsplit *wsp, size_t start, size_t * end)
static int
-scan_word (struct wordsplit *wsp, size_t start)
+scan_word (struct wordsplit *wsp, size_t start, int consume_all)
{
@@ -1093,3 +1960,4 @@ scan_word (struct wordsplit *wsp, size_t start)
int flags = 0;
-
+ struct wordsplit_node *np = wsp->ws_tail;
+
size_t i = start;
@@ -1110,3 +1978,3 @@ scan_word (struct wordsplit *wsp, size_t start)
}
- else if (!ISDELIM (wsp, command[i]))
+ else if (consume_all || !ISDELIM (wsp, command[i]))
{
@@ -1149,3 +2017,15 @@ scan_word (struct wordsplit *wsp, size_t start)
- if (ISDELIM (wsp, command[i]))
+ if (command[i] == '$')
+ {
+ if (!(wsp->ws_flags & WRDSF_NOVAR)
+ && command[i+1] == '{'
+ && find_closing_paren (command, i + 2, len, &i, "{}") == 0)
+ continue;
+ if (!(wsp->ws_flags & WRDSF_NOCMD)
+ && command[i+1] == '('
+ && find_closing_paren (command, i + 2, len, &i, "()") == 0)
+ continue;
+ }
+
+ if (!consume_all && ISDELIM (wsp, command[i]))
break;
@@ -1155,5 +2035,6 @@ scan_word (struct wordsplit *wsp, size_t start)
}
- else if (wsp->ws_flags & WRDSF_RETURN_DELIMS)
+ else if (WSP_RETURN_DELIMS (wsp))
{
i++;
+ flags |= _WSNF_DELIM;
}
@@ -1169,32 +2050,15 @@ scan_word (struct wordsplit *wsp, size_t start)
return _WRDS_EOF;
- return _WRDS_OK;
-}
-
-static char quote_transtab[] = "\\\\\"\"a\ab\bf\fn\nr\rt\tv\v";
-
-int
-wordsplit_c_unquote_char (int c)
-{
- char *p;
-
- for (p = quote_transtab; *p; p += 2)
+
+ if (consume_all)
{
- if (*p == c)
- return p[1];
- }
- return c;
-}
-
-int
-wordsplit_c_quote_char (int c)
-{
- char *p;
-
- for (p = quote_transtab + sizeof (quote_transtab) - 2;
- p > quote_transtab; p -= 2)
- {
- if (*p == c)
- return p[-1];
+ if (!np)
+ np = wsp->ws_head;
+ while (np)
+ {
+ np->flags |= _WSNF_QUOTE;
+ np = np->next;
+ }
}
- return -1;
+
+ return _WRDS_OK;
}
@@ -1241,3 +2105,3 @@ wordsplit_c_quoted_length (const char *str, int quote_hex, int *quote)
{
- if (wordsplit_c_quote_char (*str) != -1)
+ if (wordsplit_c_quote_char (*str))
len += 2;
@@ -1250,29 +2114,35 @@ wordsplit_c_quoted_length (const char *str, int quote_hex, int *quote)
-void
-wordsplit_general_unquote_copy (char *dst, const char *src, size_t n,
- const char *escapable)
+static int
+wsplt_unquote_char (const char *transtab, int c)
{
- int i;
-
- for (i = 0; i < n;)
+ while (*transtab && transtab[1])
{
- if (src[i] == '\\' && i < n && strchr (escapable, src[i + 1]))
- i++;
- *dst++ = src[i++];
+ if (*transtab++ == c)
+ return *transtab;
+ ++transtab;
}
- *dst = 0;
+ return 0;
}
-void
-wordsplit_sh_unquote_copy (char *dst, const char *src, size_t n)
+static int
+wsplt_quote_char (const char *transtab, int c)
{
- int i;
-
- for (i = 0; i < n;)
+ for (; *transtab && transtab[1]; transtab += 2)
{
- if (src[i] == '\\')
- i++;
- *dst++ = src[i++];
+ if (transtab[1] == c)
+ return *transtab;
}
- *dst = 0;
+ return 0;
+}
+
+int
+wordsplit_c_unquote_char (int c)
+{
+ return wsplt_unquote_char (wordsplit_c_escape_tab, c);
+}
+
+int
+wordsplit_c_quote_char (int c)
+{
+ return wsplt_quote_char (wordsplit_c_escape_tab, c);
}
@@ -1280,3 +2150,4 @@ wordsplit_sh_unquote_copy (char *dst, const char *src, size_t n)
void
-wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
+wordsplit_string_unquote_copy (struct wordsplit *ws, int inquote,
+ char *dst, const char *src, size_t n)
{
@@ -1285,2 +2156,3 @@ wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
+ inquote = !!inquote;
while (i < n)
@@ -1290,3 +2162,4 @@ wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
++i;
- if (src[i] == 'x' || src[i] == 'X')
+ if (WRDSO_ESC_TEST (ws, inquote, WRDSO_XESC)
+ && (src[i] == 'x' || src[i] == 'X'))
{
@@ -1313,3 +2186,4 @@ wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
}
- else if ((unsigned char) src[i] < 128 && ISDIGIT (src[i]))
+ else if (WRDSO_ESC_TEST (ws, inquote, WRDSO_OESC)
+ && (unsigned char) src[i] < 128 && ISDIGIT (src[i]))
{
@@ -1335,4 +2209,13 @@ wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
}
+ else if ((c = wsplt_unquote_char (ws->ws_escape[inquote], src[i])))
+ {
+ *dst++ = c;
+ ++i;
+ }
else
- *dst++ = wordsplit_c_unquote_char (src[i++]);
+ {
+ if (WRDSO_ESC_TEST (ws, inquote, WRDSO_BSKEEP))
+ *dst++ = '\\';
+ *dst++ = src[i++];
+ }
}
@@ -1370,3 +2253,3 @@ wordsplit_c_quote_copy (char *dst, const char *src, int quote_hex)
*dst++ = '\\';
- if (c != -1)
+ if (c)
*dst++ = c;
@@ -1383,2 +2266,56 @@ wordsplit_c_quote_copy (char *dst, const char *src, int quote_hex)
+
+/* This structure describes a single expansion phase */
+struct exptab
+{
+ char const *descr; /* Textual description (for debugging) */
+ int flag; /* WRDSF_ bit that controls this phase */
+ int opt; /* Entry-specific options (see EXPOPT_ flags below */
+ int (*expansion) (struct wordsplit *wsp); /* expansion function */
+};
+
+/* The following options control expansions: */
+/* Normally the exptab entry is run if its flag bit is set in struct
+ wordsplit. The EXPOPT_NEG option negates this test so that expansion
+ is performed if its associated flag bit is not set in struct wordsplit. */
+#define EXPOPT_NEG 0x01
+/* All bits in flag must be set in order for entry to match */
+#define EXPORT_ALLOF 0x02
+/* Coalesce the input list before running the expansion. */
+#define EXPOPT_COALESCE 0x04
+
+static struct exptab exptab[] = {
+ { N_("WS trimming"), WRDSF_WS, 0,
+ wordsplit_trimws },
+ { N_("command substitution"), WRDSF_NOCMD, EXPOPT_NEG|EXPOPT_COALESCE,
+ wordsplit_cmdexp },
+ { N_("coalesce list"), 0, EXPOPT_NEG|EXPOPT_COALESCE,
+ NULL },
+ { N_("tilde expansion"), WRDSF_PATHEXPAND, 0,
+ wordsplit_tildexpand },
+ { N_("variable expansion"), WRDSF_NOVAR, EXPOPT_NEG,
+ wordsplit_varexp },
+ { N_("quote removal"), 0, EXPOPT_NEG,
+ wsnode_quoteremoval },
+ { N_("coalesce list"), 0, EXPOPT_NEG|EXPOPT_COALESCE,
+ NULL },
+ { N_("path expansion"), WRDSF_PATHEXPAND, 0,
+ wordsplit_pathexpand },
+ { NULL }
+};
+
+static inline int
+exptab_matches(struct exptab *p, struct wordsplit *wsp)
+{
+ int result;
+
+ result = (wsp->ws_flags & p->flag);
+ if (p->opt & EXPORT_ALLOF)
+ result = result == p->flag;
+ if (p->opt & EXPOPT_NEG)
+ result = !result;
+
+ return result;
+}
+
static int
@@ -1386,6 +2323,14 @@ wordsplit_process_list (struct wordsplit *wsp, size_t start)
{
- if (wsp->ws_flags & WRDSF_NOSPLIT)
- {
- /* Treat entire input as a quoted argument */
- if (wordsplit_add_segm (wsp, start, wsp->ws_len, _WSNF_QUOTE))
+ struct exptab *p;
+
+ if (wsp->ws_flags & WRDSF_SHOWDBG)
+ wsp->ws_debug (_("(%02d) Input:%.*s;"),
+ wsp->ws_lvl, (int) wsp->ws_len, wsp->ws_input);
+
+ if ((wsp->ws_flags & WRDSF_NOSPLIT)
+ || ((wsp->ws_options & WRDSO_MAXWORDS)
+ && wsp->ws_wordi + 1 == wsp->ws_maxwords))
+ {
+ /* Treat entire input as a single word */
+ if (scan_word (wsp, start, 1) == _WRDS_ERR)
return wsp->ws_errno;
@@ -1396,3 +2341,3 @@ wordsplit_process_list (struct wordsplit *wsp, size_t start)
- while ((rc = scan_word (wsp, start)) == _WRDS_OK)
+ while ((rc = scan_word (wsp, start, 0)) == _WRDS_OK)
start = skip_delim (wsp);
@@ -1407,3 +2352,3 @@ wordsplit_process_list (struct wordsplit *wsp, size_t start)
{
- wsp->ws_debug ("Initial list:");
+ wsp->ws_debug ("(%02d) %s", wsp->ws_lvl, _("Initial list:"));
wordsplit_dump_nodes (wsp);
@@ -1411,48 +2356,29 @@ wordsplit_process_list (struct wordsplit *wsp, size_t start)
- if (wsp->ws_flags & WRDSF_WS)
- {
- /* Trim leading and trailing whitespace */
- wordsplit_trimws (wsp);
- if (wsp->ws_flags & WRDSF_SHOWDBG)
- {
- wsp->ws_debug ("After WS trimming:");
- wordsplit_dump_nodes (wsp);
- }
- }
-
- /* Expand variables (FIXME: & commands) */
- if (!(wsp->ws_flags & WRDSF_NOVAR))
- {
- if (wordsplit_varexp (wsp))
- {
- wordsplit_free_nodes (wsp);
- return wsp->ws_errno;
- }
- if (wsp->ws_flags & WRDSF_SHOWDBG)
- {
- wsp->ws_debug ("Expanded list:");
- wordsplit_dump_nodes (wsp);
- }
- }
-
- do
+ for (p = exptab; p->descr; p++)
{
- if (wsnode_quoteremoval (wsp))
- break;
- if (wsp->ws_flags & WRDSF_SHOWDBG)
- {
- wsp->ws_debug ("After quote removal:");
- wordsplit_dump_nodes (wsp);
- }
-
- if (wsnode_coalesce (wsp))
- break;
-
- if (wsp->ws_flags & WRDSF_SHOWDBG)
+ if (exptab_matches(p, wsp))
{
- wsp->ws_debug ("Coalesced list:");
- wordsplit_dump_nodes (wsp);
+ if (p->opt & EXPOPT_COALESCE)
+ {
+ if (wsnode_coalesce (wsp))
+ break;
+ if (wsp->ws_flags & WRDSF_SHOWDBG)
+ {
+ wsp->ws_debug ("(%02d) %s", wsp->ws_lvl,
+ _("Coalesced list:"));
+ wordsplit_dump_nodes (wsp);
+ }
+ }
+ if (p->expansion)
+ {
+ if (p->expansion (wsp))
+ break;
+ if (wsp->ws_flags & WRDSF_SHOWDBG)
+ {
+ wsp->ws_debug ("(%02d) %s", wsp->ws_lvl, _(p->descr));
+ wordsplit_dump_nodes (wsp);
+ }
+ }
}
}
- while (0);
return wsp->ws_errno;
@@ -1460,5 +2386,5 @@ wordsplit_process_list (struct wordsplit *wsp, size_t start)
-int
-wordsplit_len (const char *command, size_t length, struct wordsplit *wsp,
- int flags)
+static int
+wordsplit_run (const char *command, size_t length, struct wordsplit *wsp,
+ int flags, int lvl)
{
@@ -1466,4 +2392,2 @@ wordsplit_len (const char *command, size_t length, struct wordsplit *wsp,
size_t start;
- const char *cmdptr;
- size_t cmdlen;
@@ -1472,15 +2396,11 @@ wordsplit_len (const char *command, size_t length, struct wordsplit *wsp,
if (!(flags & WRDSF_INCREMENTAL))
- return EINVAL;
+ return _wsplt_seterr (wsp, WRDSE_USAGE);
+
+ if (wsp->ws_head)
+ return wordsplit_finish (wsp);
- start = skip_delim (wsp);
+ start = skip_delim_real (wsp);
if (wsp->ws_endp == wsp->ws_len)
- {
- wsp->ws_errno = WRDSE_NOINPUT;
- if (wsp->ws_flags & WRDSF_SHOWERR)
- wordsplit_perror (wsp);
- return wsp->ws_errno;
- }
+ return _wsplt_seterr (wsp, WRDSE_NOINPUT);
- cmdptr = wsp->ws_input + wsp->ws_endp;
- cmdlen = wsp->ws_len - wsp->ws_endp;
wsp->ws_flags |= WRDSF_REUSE;
@@ -1490,38 +2410,20 @@ wordsplit_len (const char *command, size_t length, struct wordsplit *wsp,
{
- cmdptr = command;
- cmdlen = length;
start = 0;
- rc = wordsplit_init (wsp, cmdptr, cmdlen, flags);
+ rc = wordsplit_init (wsp, command, length, flags);
if (rc)
return rc;
+ wsp->ws_lvl = lvl;
}
- if (wsp->ws_flags & WRDSF_SHOWDBG)
- wsp->ws_debug ("Input:%.*s;", (int) cmdlen, cmdptr);
-
rc = wordsplit_process_list (wsp, start);
- if (rc == 0 && (flags & WRDSF_INCREMENTAL))
- {
- while (!wsp->ws_head && wsp->ws_endp < wsp->ws_len)
- {
- start = skip_delim (wsp);
- if (wsp->ws_flags & WRDSF_SHOWDBG)
- {
- cmdptr = wsp->ws_input + wsp->ws_endp;
- cmdlen = wsp->ws_len - wsp->ws_endp;
- wsp->ws_debug ("Restart:%.*s;", (int) cmdlen, cmdptr);
- }
- rc = wordsplit_process_list (wsp, start);
- if (rc)
- break;
- }
- }
if (rc)
- {
- wordsplit_free_nodes (wsp);
- return rc;
- }
- wordsplit_finish (wsp);
- wordsplit_free_nodes (wsp);
- return wsp->ws_errno;
+ return rc;
+ return wordsplit_finish (wsp);
+}
+
+int
+wordsplit_len (const char *command, size_t length, struct wordsplit *wsp,
+ int flags)
+{
+ return wordsplit_run (command, length, wsp, flags, 0);
}
@@ -1531,4 +2433,3 @@ wordsplit (const char *command, struct wordsplit *ws, int flags)
{
- return wordsplit_len (command, command ? strlen (command) : 0, ws,
- flags);
+ return wordsplit_len (command, command ? strlen (command) : 0, ws, flags);
}
@@ -1553,4 +2454,31 @@ wordsplit_free_words (struct wordsplit *ws)
void
+wordsplit_free_envbuf (struct wordsplit *ws)
+{
+ if (ws->ws_flags & WRDSF_NOCMD)
+ return;
+ if (ws->ws_envbuf)
+ {
+ size_t i;
+
+ for (i = 0; ws->ws_envbuf[i]; i++)
+ free (ws->ws_envbuf[i]);
+ free (ws->ws_envbuf);
+ ws->ws_envidx = ws->ws_envsiz = 0;
+ ws->ws_envbuf = NULL;
+ }
+}
+
+void
+wordsplit_clearerr (struct wordsplit *ws)
+{
+ if (ws->ws_errno == WRDSE_USERERR)
+ free (ws->ws_usererr);
+ ws->ws_usererr = NULL;
+ ws->ws_errno = WRDSE_OK;
+}
+
+void
wordsplit_free (struct wordsplit *ws)
{
+ wordsplit_free_nodes (ws);
wordsplit_free_words (ws);
@@ -1558,46 +2486,20 @@ wordsplit_free (struct wordsplit *ws)
ws->ws_wordv = NULL;
+ wordsplit_free_envbuf (ws);
}
-void
-wordsplit_perror (struct wordsplit *wsp)
+int
+wordsplit_get_words (struct wordsplit *ws, size_t *wordc, char ***wordv)
{
- switch (wsp->ws_errno)
- {
- case WRDSE_EOF:
- wsp->ws_error (_("no error"));
- break;
+ char **p = realloc (ws->ws_wordv,
+ (ws->ws_wordc + 1) * sizeof (ws->ws_wordv[0]));
+ if (!p)
+ return -1;
+ *wordv = p;
+ *wordc = ws->ws_wordc;
- case WRDSE_QUOTE:
- wsp->ws_error (_("missing closing %c (start near #%lu)"),
- wsp->ws_input[wsp->ws_endp],
- (unsigned long) wsp->ws_endp);
- break;
-
- case WRDSE_NOSPACE:
- wsp->ws_error (_("memory exhausted"));
- break;
-
- case WRDSE_NOSUPP:
- wsp->ws_error (_("command substitution is not yet supported"));
- break;
-
- case WRDSE_USAGE:
- wsp->ws_error (_("invalid wordsplit usage"));
- break;
-
- case WRDSE_CBRACE:
- wsp->ws_error (_("unbalanced curly brace"));
- break;
-
- case WRDSE_UNDEF:
- wsp->ws_error (_("undefined variable"));
- break;
-
- case WRDSE_NOINPUT:
- wsp->ws_error (_("input exhausted"));
- break;
+ ws->ws_wordv = NULL;
+ ws->ws_wordc = 0;
+ ws->ws_wordn = 0;
- default:
- wsp->ws_error (_("unknown error"));
- }
+ return 0;
}
@@ -1608,3 +2510,2 @@ const char *_wordsplit_errstr[] = {
N_("memory exhausted"),
- N_("command substitution is not yet supported"),
N_("invalid wordsplit usage"),
@@ -1612,3 +2513,5 @@ const char *_wordsplit_errstr[] = {
N_("undefined variable"),
- N_("input exhausted")
+ N_("input exhausted"),
+ N_("unbalanced parenthesis"),
+ N_("globbing error")
};
@@ -1620,2 +2523,4 @@ wordsplit_strerror (struct wordsplit *ws)
{
+ if (ws->ws_errno == WRDSE_USERERR)
+ return ws->ws_usererr;
if (ws->ws_errno < _wordsplit_nerrs)
@@ -1624 +2529,18 @@ wordsplit_strerror (struct wordsplit *ws)
}
+
+void
+wordsplit_perror (struct wordsplit *wsp)
+{
+ switch (wsp->ws_errno)
+ {
+ case WRDSE_QUOTE:
+ wsp->ws_error (_("missing closing %c (start near #%lu)"),
+ wsp->ws_input[wsp->ws_endp],
+ (unsigned long) wsp->ws_endp);
+ break;
+
+ default:
+ wsp->ws_error ("%s", wordsplit_strerror (wsp));
+ }
+}
+
diff --git a/src/wordsplit.h b/src/wordsplit.h
index 815227b..1a047f7 100644
--- a/src/wordsplit.h
+++ b/src/wordsplit.h
@@ -1,3 +1,3 @@
/* wordsplit - a word splitter
- Copyright (C) 2009-2014 Sergey Poznyakoff
+ Copyright (C) 2009-2018 Sergey Poznyakoff
@@ -21,31 +21,94 @@
-struct wordsplit
+typedef struct wordsplit wordsplit_t;
+
+/* Structure used to direct the splitting. Members marked with [Input]
+ can be defined before calling wordsplit(), those marked with [Output]
+ provide return values when the function returns. If neither mark is
+ used, the member is internal and must not be used by the caller.
+
+ In the comments below, the identifiers in parentheses indicate bits that
+ must be set (or unset, if starting with !) in ws_flags (if starting with
+ WRDSF_) or ws_options (if starting with WRDSO_) to initialize or use the
+ given member.
+
+ If not redefined explicitly, most of them are set to some reasonable
+ default value upon entry to wordsplit(). */
+struct wordsplit
{
- size_t ws_wordc;
- char **ws_wordv;
- size_t ws_offs;
- size_t ws_wordn;
- int ws_flags;
- const char *ws_delim;
- const char *ws_comment;
- const char *ws_escape;
- void (*ws_alloc_die) (struct wordsplit * wsp);
+ size_t ws_wordc; /* [Output] Number of words in ws_wordv. */
+ char **ws_wordv; /* [Output] Array of parsed out words. */
+ size_t ws_offs; /* [Input] (WRDSF_DOOFFS) Number of initial
+ elements in ws_wordv to fill with NULLs. */
+ size_t ws_wordn; /* Number of elements ws_wordv can accomodate. */
+ int ws_flags; /* [Input] Flags passed to wordsplit. */
+ int ws_options; /* [Input] (WRDSF_OPTIONS)
+ Additional options. */
+ size_t ws_maxwords; /* [Input] (WRDSO_MAXWORDS) Return at most that
+ many words */
+ size_t ws_wordi; /* [Output] (WRDSF_INCREMENTAL) Total number of
+ words returned so far */
+
+ const char *ws_delim; /* [Input] (WRDSF_DELIM) Word delimiters. */
+ const char *ws_comment; /* [Input] (WRDSF_COMMENT) Comment characters. */
+ const char *ws_escape[2]; /* [Input] (WRDSF_ESCAPE) Characters to be escaped
+ with backslash. */
+ void (*ws_alloc_die) (wordsplit_t *wsp);
+ /* [Input] (WRDSF_ALLOC_DIE) Function called when
+ out of memory. Must not return. */
void (*ws_error) (const char *, ...)
__attribute__ ((__format__ (__printf__, 1, 2)));
+ /* [Input] (WRDSF_ERROR) Function used for error
+ reporting */
void (*ws_debug) (const char *, ...)
__attribute__ ((__format__ (__printf__, 1, 2)));
+ /* [Input] (WRDSF_DEBUG) Function used for debug
+ output. */
+ const char **ws_env; /* [Input] (WRDSF_ENV, !WRDSF_NOVAR) Array of
+ environment variables. */
- const char **ws_env;
- const char *(*ws_getvar) (const char *, size_t, void *);
- void *ws_closure;
+ char **ws_envbuf;
+ size_t ws_envidx;
+ size_t ws_envsiz;
+
+ int (*ws_getvar) (char **ret, const char *var, size_t len, void *clos);
+ /* [Input] (WRDSF_GETVAR, !WRDSF_NOVAR) Looks up
+ the name VAR (LEN bytes long) in the table of
+ variables and if found returns in memory
+ location pointed to by RET the value of that
+ variable. Returns WRDSE_OK (0) on success,
+ and an error code (see WRDSE_* defines below)
+ on error. User-specific errors can be returned
+ by storing the error diagnostic string in RET
+ and returning WRDSE_USERERR.
+ Whatever is stored in RET, it must be allocated
+ using malloc(3). */
+ void *ws_closure; /* [Input] (WRDSF_CLOSURE) Passed as the CLOS
+ argument to ws_getvar and ws_command. */
+ int (*ws_command) (char **ret, const char *cmd, size_t len, char **argv,
+ void *clos);
+ /* [Input] (!WRDSF_NOCMD) Returns in the memory
+ location pointed to by RET the expansion of
+ the command CMD (LEN bytes long). On input,
+ ARGV contains CMD split out to words.
- const char *ws_input;
- size_t ws_len;
- size_t ws_endp;
- int ws_errno;
+ See ws_getvar for a discussion of possible
+ return values. */
+
+ const char *ws_input; /* Input string (the S argument to wordsplit. */
+ size_t ws_len; /* Length of ws_input. */
+ size_t ws_endp; /* Points past the last processed byte in
+ ws_input. */
+ int ws_errno; /* [Output] Error code, if an error occurred. */
+ char *ws_usererr; /* Points to textual description of
+ the error, if ws_errno is WRDSE_USERERR. Must
+ be allocated with malloc(3). */
struct wordsplit_node *ws_head, *ws_tail;
+ /* Doubly-linked list of parsed out nodes. */
+ int ws_lvl; /* Invocation nesting level. */
};
-/* Wordsplit flags. Only 2 bits of a 32-bit word remain unused.
- It is getting crowded... */
+/* Initial size for ws_env, if allocated automatically */
+#define WORDSPLIT_ENV_INIT 16
+
+/* Wordsplit flags. */
/* Append the words found to the array resulting from a previous
@@ -53,6 +116,6 @@ struct wordsplit
#define WRDSF_APPEND 0x00000001
-/* Insert we_offs initial NULLs in the array ws_wordv.
+/* Insert ws_offs initial NULLs in the array ws_wordv.
(These are not counted in the returned ws_wordc.) */
#define WRDSF_DOOFFS 0x00000002
-/* Don't do command substitution. Reserved for future use. */
+/* Don't do command substitution. */
#define WRDSF_NOCMD 0x00000004
@@ -64,6 +127,4 @@ struct wordsplit
#define WRDSF_SHOWERR 0x00000010
-/* Consider it an error if an undefined shell variable
- is expanded. */
+/* Consider it an error if an undefined variable is expanded. */
#define WRDSF_UNDEF 0x00000020
-
/* Don't do variable expansion. */
@@ -78,3 +139,3 @@ struct wordsplit
#define WRDSF_DQUOTE 0x00000400
-/* Handle quotes and escape directives */
+/* Handle single and double quotes */
#define WRDSF_QUOTE (WRDSF_SQUOTE|WRDSF_DQUOTE)
@@ -106,3 +167,3 @@ struct wordsplit
/* Keep undefined variables in place, instead of expanding them to
- empty string */
+ empty strings. */
#define WRDSF_KEEPUNDEF 0x00800000
@@ -112,3 +173,2 @@ struct wordsplit
#define WRDSF_CESCAPES 0x02000000
-
/* ws_closure is set */
@@ -118,8 +178,10 @@ struct wordsplit
#define WRDSF_ENV_KV 0x08000000
-
/* ws_escape is set */
#define WRDSF_ESCAPE 0x10000000
-
/* Incremental mode */
#define WRDSF_INCREMENTAL 0x20000000
+/* Perform pathname and tilde expansion */
+#define WRDSF_PATHEXPAND 0x40000000
+/* ws_options is initialized */
+#define WRDSF_OPTIONS 0x80000000
@@ -127,18 +189,72 @@ struct wordsplit
(WRDSF_NOVAR | WRDSF_NOCMD | \
- WRDSF_QUOTE | WRDSF_SQUEEZED_ELIMS | WRDSF_CESCAPES)
+ WRDSF_QUOTE | WRDSF_SQUEEZE_DELIMS | WRDSF_CESCAPES)
+
+/* Remove the word that produces empty string after path expansion */
+#define WRDSO_NULLGLOB 0x00000001
+/* Print error message if path expansion produces empty string */
+#define WRDSO_FAILGLOB 0x00000002
+/* Allow a leading period to be matched by metacharacters. */
+#define WRDSO_DOTGLOB 0x00000004
+#if 0 /* Unused value */
+#define WRDSO_ARGV 0x00000008
+/* Keep backslash in unrecognized escape sequences in words */
+#endif
+#define WRDSO_BSKEEP_WORD 0x00000010
+/* Handle octal escapes in words */
+#define WRDSO_OESC_WORD 0x00000020
+/* Handle hex escapes in words */
+#define WRDSO_XESC_WORD 0x00000040
-#define WRDSE_EOF 0
+/* ws_maxwords field is initialized */
+#define WRDSO_MAXWORDS 0x00000080
+
+/* Keep backslash in unrecognized escape sequences in quoted strings */
+#define WRDSO_BSKEEP_QUOTE 0x00000100
+/* Handle octal escapes in quoted strings */
+#define WRDSO_OESC_QUOTE 0x00000200
+/* Handle hex escapes in quoted strings */
+#define WRDSO_XESC_QUOTE 0x00000400
+
+#define WRDSO_BSKEEP WRDSO_BSKEEP_WORD
+#define WRDSO_OESC WRDSO_OESC_WORD
+#define WRDSO_XESC WRDSO_XESC_WORD
+
+/* Indices into ws_escape */
+#define WRDSX_WORD 0
+#define WRDSX_QUOTE 1
+
+/* Set escape option F in WS for words (Q==0) or quoted strings (Q==1) */
+#define WRDSO_ESC_SET(ws,q,f) ((ws)->ws_options |= ((f) << 4*(q)))
+/* Test WS for escape option F for words (Q==0) or quoted strings (Q==1) */
+#define WRDSO_ESC_TEST(ws,q,f) ((ws)->ws_options & ((f) << 4*(q)))
+
+#define WRDSE_OK 0
+#define WRDSE_EOF WRDSE_OK
#define WRDSE_QUOTE 1
#define WRDSE_NOSPACE 2
-#define WRDSE_NOSUPP 3
-#define WRDSE_USAGE 4
-#define WRDSE_CBRACE 5
-#define WRDSE_UNDEF 6
-#define WRDSE_NOINPUT 7
-
-int wordsplit (const char *s, struct wordsplit *p, int flags);
-int wordsplit_len (const char *s, size_t len,
- struct wordsplit *p, int flags);
-void wordsplit_free (struct wordsplit *p);
-void wordsplit_free_words (struct wordsplit *ws);
+#define WRDSE_USAGE 3
+#define WRDSE_CBRACE 4
+#define WRDSE_UNDEF 5
+#define WRDSE_NOINPUT 6
+#define WRDSE_PAREN 7
+#define WRDSE_GLOBERR 8
+#define WRDSE_USERERR 9
+
+int wordsplit (const char *s, wordsplit_t *ws, int flags);
+int wordsplit_len (const char *s, size_t len, wordsplit_t *ws, int flags);
+void wordsplit_free (wordsplit_t *ws);
+void wordsplit_free_words (wordsplit_t *ws);
+void wordsplit_free_envbuf (wordsplit_t *ws);
+int wordsplit_get_words (wordsplit_t *ws, size_t *wordc, char ***wordv);
+
+static inline void wordsplit_getwords (wordsplit_t *ws, size_t *wordc, char ***wordv)
+ __attribute__ ((deprecated));
+
+static inline void
+wordsplit_getwords (wordsplit_t *ws, size_t *wordc, char ***wordv)
+{
+ wordsplit_get_words (ws, wordc, wordv);
+}
+
+int wordsplit_append (wordsplit_t *wsp, int argc, char **argv);
@@ -146,13 +262,9 @@ int wordsplit_c_unquote_char (int c);
int wordsplit_c_quote_char (int c);
-size_t wordsplit_c_quoted_length (const char *str, int quote_hex,
- int *quote);
-void wordsplit_general_unquote_copy (char *dst, const char *src, size_t n,
- const char *escapable);
-void wordsplit_sh_unquote_copy (char *dst, const char *src, size_t n);
-void wordsplit_c_unquote_copy (char *dst, const char *src, size_t n);
+size_t wordsplit_c_quoted_length (const char *str, int quote_hex, int *quote);
void wordsplit_c_quote_copy (char *dst, const char *src, int quote_hex);
-void wordsplit_perror (struct wordsplit *ws);
-const char *wordsplit_strerror (struct wordsplit *ws);
+void wordsplit_perror (wordsplit_t *ws);
+const char *wordsplit_strerror (wordsplit_t *ws);
+void wordsplit_clearerr (wordsplit_t *ws);
diff --git a/tests/initdb.at b/tests/initdb.at
index 25e43f7..0355e3b 100644
--- a/tests/initdb.at
+++ b/tests/initdb.at
@@ -1,3 +1,3 @@
# This file is part of vmod-dbrw -*- autotest -*-
-# Copyright (C) 2013-2014 Sergey Poznyakoff
+# Copyright (C) 2013-2018 Sergey Poznyakoff
#
@@ -47,6 +47,6 @@ INSERT INTO rewrite VALUES
('en.example.net','/local','http://uno.example.com/remote',NULL,NULL,NULL),
-('en.example.net','/local/%','http://dos.example.com/$[]1','$url','/local/(.*)',NULL),
-('en.example.net','/local2/%','http://to.example.net/$[]1$[]2','$url','/local2/([[^\\?]]*)(\\?.*)?',NULL),
-('to.example.net','/local/%','http://dos.example.net/$[]1','$url','/local/(.*)','QSA'),
-('tre.example.net','/local/%','http://dos.example.net/$[]1?i=10','$url','/local/(.*)','QSA,R=302');
+('en.example.net','/local','http://dos.example.com/$[]1','$url','/local/(.*)',NULL),
+('en.example.net','/local2','http://to.example.net/$[]1$[]2','$url','/local2/([[^\\?]]*)(\\?.*)?',NULL),
+('to.example.net','/local','http://dos.example.net/$[]1','$url','/local/(.*)','QSA'),
+('tre.example.net','/local','http://dos.example.net/$[]1?i=10','$url','/local/(.*)','QSA,R=302');
diff --git a/tests/rewrite01.at b/tests/rewrite01.at
index 14232f3..d004b4c 100644
--- a/tests/rewrite01.at
+++ b/tests/rewrite01.at
@@ -1,3 +1,3 @@
# This file is part of vmod-dbrw -*- autotest -*-
-# Copyright (C) 2013-2014 Sergey Poznyakoff
+# Copyright (C) 2013-2018 Sergey Poznyakoff
#
@@ -25,3 +25,4 @@ AT_VCL([SELECT dest,pattern,value
FROM rewrite
- WHERE host='$host' AND '$url' LIKE url],
+ WHERE host='$host' AND url IN ($(urlprefixes $url))
+ ORDER BY LENGTH(dest),value DESC],
[txreq -url /local -hdr "Host:en.example.net"
diff --git a/tests/rewrite02.at b/tests/rewrite02.at
index 1ea4173..794bf88 100644
--- a/tests/rewrite02.at
+++ b/tests/rewrite02.at
@@ -1,3 +1,3 @@
# This file is part of vmod-dbrw -*- autotest -*-
-# Copyright (C) 2013-2014 Sergey Poznyakoff
+# Copyright (C) 2013-2018 Sergey Poznyakoff
#
@@ -25,3 +25,4 @@ AT_VCL([SELECT dest,pattern,value
FROM rewrite
- WHERE host='$host' AND '$url' LIKE url],
+ WHERE host='$host' AND url IN ($(urlprefixes $url))
+ ORDER BY LENGTH(dest),value DESC],
[txreq -url /local/foo/bar -hdr "Host:en.example.net"
diff --git a/tests/rewrite03.at b/tests/rewrite03.at
index 6433f7d..7bcd18c 100644
--- a/tests/rewrite03.at
+++ b/tests/rewrite03.at
@@ -1,3 +1,3 @@
# This file is part of vmod-dbrw -*- autotest -*-
-# Copyright (C) 2013-2014 Sergey Poznyakoff
+# Copyright (C) 2013-2018 Sergey Poznyakoff
#
@@ -25,3 +25,4 @@ AT_VCL([SELECT dest,pattern,value
FROM rewrite
- WHERE host='$host' AND '$url' LIKE url],
+ WHERE host='$host' AND url IN ($(urlprefixes $url))
+ ORDER BY LENGTH(dest),value DESC],
[txreq -url /local2/foo/bar -hdr "Host:en.example.net"
diff --git a/tests/rewrite04.at b/tests/rewrite04.at
index 4715c6f..033224e 100644
--- a/tests/rewrite04.at
+++ b/tests/rewrite04.at
@@ -1,3 +1,3 @@
# This file is part of vmod-dbrw -*- autotest -*-
-# Copyright (C) 2013-2014 Sergey Poznyakoff
+# Copyright (C) 2013-2018 Sergey Poznyakoff
#
@@ -25,3 +25,4 @@ AT_VCL([SELECT dest,pattern,value
FROM rewrite
- WHERE host='$host' AND '$url' LIKE url],
+ WHERE host='$host' AND url IN ($(urlprefixes $url))
+ ORDER BY LENGTH(dest),value DESC],
[ txreq -url /local2/foo/bar?x&y&z -hdr "Host:en.example.net"
diff --git a/tests/rewrite05.at b/tests/rewrite05.at
index 5927ba3..df3d062 100644
--- a/tests/rewrite05.at
+++ b/tests/rewrite05.at
@@ -1,3 +1,3 @@
# This file is part of vmod-dbrw -*- autotest -*-
-# Copyright (C) 2013-2014 Sergey Poznyakoff
+# Copyright (C) 2013-2018 Sergey Poznyakoff
#
@@ -25,3 +25,4 @@ AT_VCL([SELECT dest,pattern,value
FROM rewrite
- WHERE host='$host' AND '$url' LIKE url],
+ WHERE host='$host' AND url IN ($(urlprefixes $url))
+ ORDER BY LENGTH(dest),value DESC],
[txreq -url /local/foo/bar?x&y&z -hdr "Host:to.example.net"
diff --git a/tests/rewrite06.at b/tests/rewrite06.at
index 39666b7..0f66846 100644
--- a/tests/rewrite06.at
+++ b/tests/rewrite06.at
@@ -1,3 +1,3 @@
# This file is part of vmod-dbrw -*- autotest -*-
-# Copyright (C) 2013-2014 Sergey Poznyakoff
+# Copyright (C) 2013-2018 Sergey Poznyakoff
#
@@ -25,3 +25,4 @@ AT_VCL([SELECT dest,pattern,value,flags
FROM rewrite
- WHERE host='$host' AND '$url' LIKE url],
+ WHERE host='$host' AND url IN ($(urlprefixes $url))
+ ORDER BY LENGTH(dest),value DESC],
[txreq -url /local/foo/bar?x&y&z -hdr "Host:tre.example.net"

Return to:

Send suggestions and report system problems to the System administrator.