changeset 298:289a8ac515f2

qstring: turn a encoded query string into a nvlist entries Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Mon, 31 Jul 2017 21:22:20 +0300
parents d7967afe6c56
children 8c1ea2cc67a2
files .hgignore CMakeLists.txt include/jeffpc/qstring.h jeffpc.mapfile-vers qstring.c test_qstring.c tests/CMakeLists.txt tests/qstring-basic/CMakeLists.txt tests/qstring-basic/comment-body.qs tests/qstring-basic/empty.qs tests/qstring-basic/multi-char.qs tests/qstring-basic/no-value-long.qs tests/qstring-basic/no-value.qs tests/qstring-basic/one.qs tests/qstring-basic/two.qs
diffstat 14 files changed, 295 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Jul 31 20:23:43 2017 +0300
+++ b/.hgignore	Mon Jul 31 21:22:20 2017 +0300
@@ -28,6 +28,7 @@
 test_nvl
 test_nvl_pack
 test_padding
+test_qstring
 test_sexpr_parser
 test_sexpr_eval
 test_urldecode
--- a/CMakeLists.txt	Mon Jul 31 20:23:43 2017 +0300
+++ b/CMakeLists.txt	Mon Jul 31 21:22:20 2017 +0300
@@ -100,6 +100,7 @@
 	nvl_pack.c
 	nvl_unpack.c
 	padding.c
+	qstring.c
 	rand.c
 	sexpr.c
 	sexpr_eval.c
@@ -150,6 +151,7 @@
 		include/jeffpc/mem.h
 		include/jeffpc/nvl.h
 		include/jeffpc/padding.h
+		include/jeffpc/qstring.h
 		include/jeffpc/rand.h
 		include/jeffpc/refcnt.h
 		include/jeffpc/scgi.h
@@ -201,6 +203,7 @@
 #
 
 build_test_bin(sexpr_parser)
+build_test_bin(qstring)
 build_test_bin_and_run(array)
 build_test_bin_and_run(atomic-single-thread)
 build_test_bin_and_run(bswap)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/jeffpc/qstring.h	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2014-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef __JEFFPC_QSTRING_H
+#define __JEFFPC_QSTRING_H
+
+#include <string.h>
+
+#include <jeffpc/nvl.h>
+
+extern int qstring_parse_len(struct nvlist *vars, const char *qs, size_t len);
+
+static inline int qstring_parse(struct nvlist *vars, const char *qs)
+{
+	size_t len;
+
+	if (qs)
+		len = strlen(qs);
+	else
+		len = 0;
+
+	return qstring_parse_len(vars, qs, len);
+}
+
+#endif
--- a/jeffpc.mapfile-vers	Mon Jul 31 20:23:43 2017 +0300
+++ b/jeffpc.mapfile-vers	Mon Jul 31 21:22:20 2017 +0300
@@ -121,6 +121,9 @@
 		# padding
 		check_padding;
 
+		# qstring
+		qstring_parse_len;
+
 		# rand
 		rand32;
 		rand64;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qstring.c	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2014-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <jeffpc/qstring.h>
+#include <jeffpc/urldecode.h>
+#include <jeffpc/error.h>
+
+/*
+ * The query string is a sequence of name-value pairs.  Each name is
+ * separated from the value with a '=', and each pair is separated by '&'.
+ * We parse the input string, and produce a nvlist key-value pair for each
+ * of the input pairs.  We map each input pair based on these rules:
+ *
+ *	foo=bar		->	{ "foo" : "bar" }
+ *	foo=		->	{ "foo" : "" }
+ *	foo		->	{ "foo" : null }
+ *	=bar		->	{ "" : "bar" }
+ */
+
+enum qs_state {
+	QS_STATE_NAME,
+	QS_STATE_VAL,
+};
+
+static int insert(struct nvlist *nvl, const char *namestart, size_t namelen,
+		  const char *valstart, size_t vallen)
+{
+	char name[namelen + 1];
+	ssize_t newlen;
+
+	/* decode & nul-terminate the name */
+	newlen = urldecode(namestart, namelen, name);
+	if (newlen < 0)
+		return newlen;
+
+	name[newlen] = '\0';
+
+	if (!valstart) {
+		/* we want a null value */
+		return nvl_set_null(nvl, name);
+	} else {
+		/* we want a string value (possibly empty string) */
+		struct str *val;
+
+		if (!vallen)
+			val = str_dup(NULL);
+		else
+			val = urldecode_str(valstart, vallen);
+
+		if (IS_ERR(val))
+			return PTR_ERR(val);
+
+		return nvl_set_str(nvl, name, val);
+	}
+}
+
+int qstring_parse_len(struct nvlist *nvl, const char *qs, size_t len)
+{
+	const char *cur, *end;
+	const char *name;
+	const char *val;
+	size_t name_len;
+	enum qs_state state;
+
+	if (!nvl)
+		return -EINVAL;
+
+	if (!len && !qs)
+		return 0;
+
+	if (!qs)
+		return -EINVAL;
+
+	end = qs + len;
+	cur = qs;
+
+	state = QS_STATE_NAME;
+
+	name = qs;
+	name_len = 0;
+	val = NULL;
+
+	for (; end > cur; cur++) {
+		char c = *cur;
+
+		if (state == QS_STATE_NAME) {
+			if (c == '=') {
+				/* end of name */
+				name_len = cur - name;
+				val = cur + 1;
+				state = QS_STATE_VAL;
+			} else if (c == '&') {
+				/* end of name; no value */
+				insert(nvl, name, cur - name, NULL, 0);
+
+				name = cur + 1;
+				state = QS_STATE_NAME; /* no change */
+			}
+		} else if (state == QS_STATE_VAL) {
+			if (c == '&') {
+				/* end of value */
+				insert(nvl, name, name_len, val, cur - val);
+
+				name = cur + 1;
+				state = QS_STATE_NAME;
+			} else if (c == '=') {
+				/* value contains = */
+				return -EILSEQ;
+			}
+		}
+	}
+
+	/* qs ends with a name without a '=' (e.g., abc=def&ghi) */
+	if ((state == QS_STATE_NAME) && (name < end))
+		insert(nvl, name, end - name, NULL, 0);
+	/* qs ends with an empty value (e.g., abc=def&ghi=) */
+	if ((state == QS_STATE_VAL) && (val == end))
+		insert(nvl, name, name_len, val, 0);
+	/* qs ends with a value (e.g., abc=def&ghi=jkl) */
+	if ((state == QS_STATE_VAL) && (val < end))
+		insert(nvl, name, name_len, val, end - val);
+
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test_qstring.c	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2014-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include <jeffpc/qstring.h>
+#include <jeffpc/io.h>
+
+static int onefile(char *ibuf, size_t len)
+{
+	struct nvlist *vars;
+	int ret;
+
+	vars = nvl_alloc();
+	if (!vars)
+		return -ENOMEM;
+
+	ret = qstring_parse_len(vars, ibuf, len);
+	if (!ret)
+		nvl_dump_file(stderr, vars);
+
+	nvl_putref(vars);
+
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	char *in;
+	int i;
+	int result;
+
+	result = 0;
+
+	ASSERT0(putenv("UMEM_DEBUG=default,verbose"));
+
+	for (i = 1; i < argc; i++) {
+		in = read_file(argv[i]);
+		ASSERT(!IS_ERR(in));
+
+		if (onefile(in, strlen(in)))
+			result = 1;
+
+		free(in);
+	}
+
+	return result;
+}
--- a/tests/CMakeLists.txt	Mon Jul 31 20:23:43 2017 +0300
+++ b/tests/CMakeLists.txt	Mon Jul 31 21:22:20 2017 +0300
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2016 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Copyright (c) 2016-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
@@ -20,4 +20,5 @@
 # SOFTWARE.
 #
 
+add_subdirectory(qstring-basic)
 add_subdirectory(sexpr-parser)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/qstring-basic/CMakeLists.txt	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2014-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qs)
+foreach(TEST ${TESTS})
+	simple_c_test(qstring basic qstring ${TEST})
+endforeach()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/qstring-basic/comment-body.qs	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,1 @@
+i=535&d=1448913859968688486&s=Submit+Comment&a=seo&e=dsqbketcxa@example.com&u=http%3a%2f%2fwww.example.com%2f&x=seo&v=seo&c=Hello+Web+Admin%2c+I+noticed+that
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/qstring-basic/multi-char.qs	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,1 @@
+preview=1234&p=5
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/qstring-basic/no-value-long.qs	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,1 @@
+just-a-very-long-string---longer-than-most-variable-names-would-be-because-we-want-to-make-sure-that-nothing-blows-up-when-there-is-no-value-and-just-a-long-name
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/qstring-basic/no-value.qs	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,1 @@
+abc
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/qstring-basic/one.qs	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,1 @@
+a=b
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/qstring-basic/two.qs	Mon Jul 31 21:22:20 2017 +0300
@@ -0,0 +1,1 @@
+a=b&c=d
\ No newline at end of file