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:p...@host:port */ + char *host = strchr(readp, '@'); + if (host) { + /* got user:p...@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[:pa...@]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:p...@foo.com: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:p...@foo.com: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://u...@foo.com/#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://u...@foo.com/#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 majord...@freehaven.net with unsubscribe libevent-users in the body.