From 86dd720a666aca5b95c4f913c3f705d6b7a8f17d Mon Sep 17 00:00:00 2001 From: Pavel Plesov Date: Sun, 8 Aug 2010 16:46:39 +0400 Subject: [PATCH] Introduce absolute URI parsing helpers. See evhttp_uri_parse(), evhttp_uri_free() and evhttp_uri_join() for details. --- http.c | 160 +++++++++++++++++++++++++++++++++++ include/event2/http.h | 32 +++++++ include/event2/http_struct.h | 14 +++ test/regress_http.c | 76 +++++++++++++++++ 4 files changed, 282 insertions(+) diff --git a/http.c b/http.c index 6aca33c3..37936566 100644 --- a/http.c +++ b/http.c @@ -3334,3 +3334,163 @@ bind_socket(const char *address, ev_uint16_t port, int reuse) return (fd); } +struct evhttp_uri *evhttp_uri_parse(const char *source_uri) +{ + char *readbuf = 0, *readp = 0, *token = 0, *query = 0, *host = 0, *port = 0; + + struct evhttp_uri *uri = calloc(1, sizeof(*uri)); + if (uri == NULL) { + event_err(1, "%s: calloc", __func__); + return NULL; + } + + readbuf = strdup(source_uri); + if (readbuf == NULL) { + event_err(1, "%s: strdup", __func__); + free(uri); + return NULL; + } + + readp = readbuf; + token = NULL; + + /* 1. scheme:// */ + token = strstr(readp, "://"); + if (!token) { + /* unsupported uri */ + free(readbuf); + free(uri); + return NULL; + } + + *token = '\0'; + uri->scheme = strdup(readp); + + readp = token; + readp += 3; /* eat :// */ + + /* 2. query */ + query = strchr(readp, '/'); + if (query) { + char *fragment = strchr(query, '#'); + if (fragment) { + *fragment++ = '\0'; /* eat '#' */ + uri->fragment = strdup(fragment); + } + + uri->query = strdup(query); + *query = '\0'; /* eat '/' */ + } + + /* 3. user:pass@host:port */ + host = strchr(readp, '@'); + if (host) { + char *pass = 0; + /* got user:pass@host:port */ + *host++ = '\0'; /* eat @ */; + pass = strchr(readp, ':'); + if (pass) { + *pass++ = '\0'; /* eat ':' */ + uri->pass = strdup(pass); + } + + uri->user = strdup(readp); + readp = host; + } + + /* 4. host:port */ + port = strchr(readp, ':'); + if (port) { + *port++ = '\0'; /* eat ':' */ + uri->port = atoi(port); + } + + /* 5. host */ + uri->host = strdup(readp); + + free(readbuf); + + return uri; +} + +void evhttp_uri_free(struct evhttp_uri *uri) +{ + if (uri == NULL) + return; + +#define _URI_FREE_STR(f) \ + if (uri->f) { \ + free(uri->f); \ + } + + _URI_FREE_STR(scheme); + _URI_FREE_STR(user); + _URI_FREE_STR(pass); + _URI_FREE_STR(host); + _URI_FREE_STR(query); + _URI_FREE_STR(fragment); + + free(uri); + +#undef _URI_FREE_STR +} + +char *evhttp_uri_join(struct evhttp_uri *uri, void *buf, size_t limit) +{ + struct evbuffer *tmp = 0; + unsigned char *joined = 0; + size_t joined_size = 0; + +#define _URI_ADD(f) evbuffer_add(tmp, uri->f, strlen(uri->f)) + if (!uri || !uri->scheme || !buf || !limit) + return NULL; + + 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 */ + + joined = evbuffer_pullup(tmp, -1); + 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 d872a189..9293270d 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -607,6 +607,38 @@ int evhttp_parse_query__checked_20(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 + @return uri container to hold parsed data, or NULL if there is error + @see evhttp_uri_free() + */ +struct evhttp_uri *evhttp_uri_parse(const char *source_uri); + +/** + * Free the memory allocated for the uri and parsed data + * @param uri container with parsed data + @see evhttp_uri_parse() + */ +void evhttp_uri_free(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 + @see evhttp_uri_parse() + */ +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 a3664e04..168f5aca 100644 --- a/include/event2/http_struct.h +++ b/include/event2/http_struct.h @@ -118,6 +118,20 @@ 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 e2fd987b..17043219 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -1707,6 +1707,81 @@ end: evhttp_clear_headers(&headers); } +static void +http_parse_uri_test(void *ptr) +{ + struct evhttp_uri *uri = NULL; + 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); + + tt_want(evhttp_uri_parse("mailto:foo@bar") == NULL); + + uri = evhttp_uri_parse("http://www.test.com/?q=test"); + 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_free(uri); + + uri = evhttp_uri_parse("ftp://www.test.com/?q=test"); + 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_free(uri); + + uri = evhttp_uri_parse("scheme://user:pass@foo.com:42/?q=test&s=some+thing#fragment"); + 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@foo.com:42/?q=test&s=some+thing#fragment"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("scheme://user@foo.com/#fragment"); + 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@foo.com/#fragment"); + evhttp_uri_free(uri); + uri = evhttp_uri_parse("file:///some/path/to/the/file"); + tt_want(strcmp(uri->scheme, "file") == 0); + tt_want(uri->user == NULL); + tt_want(uri->pass == NULL); + tt_want(strcmp(uri->host, "") == 0); + tt_want(uri->port == 0); + tt_want(strcmp(uri->query, "/some/path/to/the/file") == 0); + tt_want(uri->fragment == NULL); + TT_URI("file:///some/path/to/the/file"); + evhttp_uri_free(uri); +} + static void http_uriencode_test(void *ptr) { @@ -2801,6 +2876,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 }, { "uriencode", http_uriencode_test, 0, NULL, NULL }, HTTP_LEGACY(basic), HTTP_LEGACY(cancel), -- 2.50.0