[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[Libevent-users] [PATCH] uri parsing helpers



This patch introduces evhttp_uri_* functions to deal with URI parsing.

See evhttp_uri_parse(), evhttp_uri_clear() and evhttp_uri_join() for details.

Patch is made against Nick's master branch @ github

---
 http.c                       |  139 ++++++++++++++++++++++++++++++++++++++++++
 include/event2/http.h        |   29 +++++++++
 include/event2/http_struct.h |   13 ++++
 test/regress_http.c          |   69 +++++++++++++++++++++
 4 files changed, 250 insertions(+), 0 deletions(-)

diff --git a/http.c b/http.c
index 19d4f91..cb19fb4 100644
--- a/http.c
+++ b/http.c
@@ -3262,3 +3262,142 @@ bind_socket(const char *address, ev_uint16_t
port, int reuse)
 	return (fd);
 }

+void evhttp_uri_parse(const char *source_uri, struct evhttp_uri *uri)
+{
+	evhttp_uri_clear(uri);
+
+	char *readbuf = mm_strdup(source_uri);
+	if (readbuf == NULL) {
+		event_err(1, "%s: strdup", __func__);
+		return;
+	}
+
+	char *readp = readbuf, *token = NULL;
+
+	/* 1. scheme:// */
+	token = strstr(readp, "://");
+	if (!token) {
+		/* unsupported uri */
+		mm_free(readbuf);
+		return;
+	}
+
+	*token = '\0';
+	uri->scheme = mm_strdup(readp);
+
+	readp = token;
+	readp += 3; /* :// */
+
+	/* 2. query */
+	char *query = strchr(readp, '/');
+	if (query) {
+		char *fragment = strchr(query, '#');
+		if (fragment) {
+			*fragment++ = '\0'; /* eat '#' */
+			uri->fragment = mm_strdup(fragment);
+		}
+
+		uri->query = mm_strdup(query);
+		*query = '\0'; /* eat '/' */
+	}
+
+	/* 3. user:pass@host:port */
+	char *host = strchr(readp, '@');
+	if (host) {
+		/* got user:pass@host:port */
+		*host++ = '\0'; /* eat @ */;
+		char *pass = strchr(readp, ':');
+		if (pass) {
+			*pass++ = '\0'; /* eat ':' */
+			uri->pass = mm_strdup(pass);
+		}
+
+		uri->user = mm_strdup(readp);
+
+		readp = host;
+	}
+	/* got host:port */
+
+	char *port = strchr(readp, ':');
+	if (port) {
+		*port++ = '\0'; /* eat ':' */
+		uri->port = atoi(port);
+	}
+
+	uri->host = mm_strdup(readp);
+}
+
+void evhttp_uri_clear(struct evhttp_uri *uri)
+{
+#define _URI_CLEAR_STR(f)		\
+	if (uri->f) {			\
+		mm_free(uri->f);	\
+		uri->f = NULL;		\
+	}
+
+	_URI_CLEAR_STR(scheme);
+	_URI_CLEAR_STR(user);
+	_URI_CLEAR_STR(pass);
+	_URI_CLEAR_STR(host);
+	uri->port = 0;
+	_URI_CLEAR_STR(query);
+	_URI_CLEAR_STR(fragment);
+
+#undef _URI_CLEAR_STR
+}
+
+char *evhttp_uri_join(struct evhttp_uri *uri, void *buf, size_t limit)
+{
+#define _URI_ADD(f)	evbuffer_add(tmp, uri->f, strlen(uri->f))
+	if (!uri || !uri->scheme || !buf || !limit)
+		return NULL;
+
+	struct evbuffer *tmp = evbuffer_new();
+	if (!tmp)
+		return NULL;
+
+	_URI_ADD(scheme);
+	evbuffer_add(tmp, "://", 3);
+	if (uri->host && *uri->host) {
+		if (uri->user && *uri->user) {
+			_URI_ADD(user);
+			if (uri->pass && *uri->pass) {
+				evbuffer_add(tmp, ":", 1);
+				_URI_ADD(pass);
+			}
+			evbuffer_add(tmp, "@", 1);
+		}
+
+		_URI_ADD(host);
+
+		if (uri->port > 0)
+			evbuffer_add_printf(tmp,":%u", uri->port);
+	}
+
+	if (uri->query && *uri->query)
+		_URI_ADD(query);
+
+	if (uri->fragment && *uri->fragment) {
+		if (!uri->query || !*uri->query)
+			evbuffer_add(tmp, "/", 1);
+
+		evbuffer_add(tmp, "#", 1);
+		_URI_ADD(fragment);
+	}
+
+	evbuffer_add(tmp, "\0", 1); /* NUL */
+
+	char *joined = evbuffer_pullup(tmp, -1);
+	size_t joined_size = evbuffer_get_length(tmp);
+
+	if (joined_size < limit)
+		memcpy(buf, joined, joined_size);
+	else {
+		memcpy(buf, joined, limit-1);
+		*((char *)buf+ limit - 1) = '\0';
+	}
+	evbuffer_free(tmp);
+
+	return (char *)buf;
+#undef _URI_ADD
+}
diff --git a/include/event2/http.h b/include/event2/http.h
index cab068a..39c6b48 100644
--- a/include/event2/http.h
+++ b/include/event2/http.h
@@ -558,6 +558,35 @@ void evhttp_parse_query(const char *uri, struct
evkeyvalq *headers);
  */
 char *evhttp_htmlescape(const char *html);

+struct evhttp_uri;
+
+/**
+   Helper function to parse out uri.
+
+   Parsing a uri like
+
+      scheme://[user[:pass]@]foo.com[:port]/[path][?q=test&s=some+thing][#fragment]
+
+   @param source_uri the request URI
+   @param uri container to hold parsed data
+ */
+void evhttp_uri_parse(const char *source_uri, struct evhttp_uri *uri);
+
+/**
+ * Free the memory allocated for the parsed data, except uri itself
+ * @param uri container with parsed data
+ */
+void evhttp_uri_clear(struct evhttp_uri *uri);
+
+/**
+ * Join together the uri parts from parsed data
+ * @param uri container with parsed data
+ * @param buf destination buffer
+ * @param limit destination buffer size
+ * @return an joined uri as string or NULL on error
+ */
+char *evhttp_uri_join(struct evhttp_uri *uri, void *buf, size_t limit);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/event2/http_struct.h b/include/event2/http_struct.h
index de5d541..af117cb 100644
--- a/include/event2/http_struct.h
+++ b/include/event2/http_struct.h
@@ -118,6 +118,19 @@ struct {
 	void (*chunk_cb)(struct evhttp_request *, void *);
 };

+/**
+ * structure to hold parsed uri
+ */
+struct evhttp_uri {
+	char *scheme; /* scheme; e.g http, ftp etc */
+	char *host; /* hostname, or NULL */
+	char *user; /* usename, or NULL */
+	char *pass; /* password, or NULL */
+	int port; /* port, or zero */
+	char *query; /* path + query: e.g. /path/to?param=foo, or NULL */
+	char *fragment; /* fragment or NULL */
+};
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/test/regress_http.c b/test/regress_http.c
index 99fb4a2..27f52c0 100644
--- a/test/regress_http.c
+++ b/test/regress_http.c
@@ -1657,6 +1657,74 @@ http_parse_query_test(void *ptr)
 }

 static void
+http_parse_uri_test(void *ptr)
+{
+	struct evhttp_uri uri = { 0 };
+	char url_tmp[4096];
+
+#define TT_URI(want) do { 						\
+	char *ret = evhttp_uri_join(&uri, url_tmp, sizeof(url_tmp));	\
+	tt_want(ret != NULL);						\
+	tt_want(ret == url_tmp);					\
+	tt_want(strcmp(ret, want) == 0);				\
+	} while(0)
+
+	tt_want(evhttp_uri_join(0, 0, 0) == NULL);
+	tt_want(evhttp_uri_join(0, url_tmp, 0) == NULL);
+	tt_want(evhttp_uri_join(0, url_tmp, sizeof(url_tmp)) == NULL);
+	tt_want(evhttp_uri_join(&uri, url_tmp, sizeof(url_tmp)) == NULL);
+
+	evhttp_uri_parse("http://www.test.com/?q=test";, &uri);
+	tt_want(strcmp(uri.scheme, "http") == 0);
+	tt_want(strcmp(uri.host, "www.test.com") == 0);
+	tt_want(strcmp(uri.query, "/?q=test") == 0);
+	tt_want(uri.user == NULL);
+	tt_want(uri.pass == NULL);
+	tt_want(uri.port == 0);
+	tt_want(uri.fragment == NULL);
+	TT_URI("http://www.test.com/?q=test";);
+
+	evhttp_uri_parse("ftp://www.test.com/?q=test";, &uri);
+	tt_want(strcmp(uri.scheme, "ftp") == 0);
+	tt_want(strcmp(uri.host, "www.test.com") == 0);
+	tt_want(strcmp(uri.query, "/?q=test") == 0);
+	tt_want(uri.user == NULL);
+	tt_want(uri.pass == NULL);
+	tt_want(uri.port == 0);
+	tt_want(uri.fragment == NULL);
+	TT_URI("ftp://www.test.com/?q=test";);
+
+	evhttp_uri_parse("scheme://user:pass@xxxxxxx:42/?q=test&s=some+thing#fragment",
&uri);
+	tt_want(strcmp(uri.scheme, "scheme") == 0);
+	tt_want(strcmp(uri.user, "user") == 0);
+	tt_want(strcmp(uri.pass, "pass") == 0);
+	tt_want(strcmp(uri.host, "foo.com") == 0);
+	tt_want(uri.port == 42);
+	tt_want(strcmp(uri.query, "/?q=test&s=some+thing") == 0);
+	tt_want(strcmp(uri.fragment, "fragment") == 0);
+	TT_URI("scheme://user:pass@xxxxxxx:42/?q=test&s=some+thing#fragment");
+
+	evhttp_uri_clear(&uri);
+	tt_want(uri.scheme == NULL);
+	tt_want(uri.host == NULL);
+	tt_want(uri.user == NULL);
+	tt_want(uri.pass == NULL);
+	tt_want(uri.port == 0);
+	tt_want(uri.query == NULL);
+	tt_want(uri.fragment == NULL);
+
+	evhttp_uri_parse("scheme://user@xxxxxxx/#fragment", &uri);
+	tt_want(strcmp(uri.scheme, "scheme") == 0);
+	tt_want(strcmp(uri.user, "user") == 0);
+	tt_want(uri.pass == NULL);
+	tt_want(strcmp(uri.host, "foo.com") == 0);
+	tt_want(uri.port == 0);
+	tt_want(strcmp(uri.query, "/") == 0);
+	tt_want(strcmp(uri.fragment, "fragment") == 0);
+	TT_URI("scheme://user@xxxxxxx/#fragment");
+}
+
+static void
 http_base_test(void *ptr)
 {
 	struct event_base *base = NULL;
@@ -2648,6 +2716,7 @@ struct testcase_t http_testcases[] = {
 	{ "base", http_base_test, TT_FORK|TT_NEED_BASE, NULL, NULL },
 	{ "bad_headers", http_bad_header_test, 0, NULL, NULL },
 	{ "parse_query", http_parse_query_test, 0, NULL, NULL },
+	{ "parse_uri", http_parse_uri_test, 0, NULL, NULL },
 	HTTP_LEGACY(basic),
 	HTTP_LEGACY(cancel),
 	HTTP_LEGACY(virtual_host),
-- 
1.7.1


-- 
Pavel Plesov
***********************************************************************
To unsubscribe, send an e-mail to majordomo@xxxxxxxxxxxxx with
unsubscribe libevent-users    in the body.