From 86dd720a666aca5b95c4f913c3f705d6b7a8f17d Mon Sep 17 00:00:00 2001 From: Pavel Plesov Date: Sun, 8 Aug 2010 16:46:39 +0400 Subject: [PATCH 01/11] 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 6aca33c3f7..37936566cf 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 d872a18977..9293270d17 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 a3664e0448..168f5aca60 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 e2fd987b91..170432195e 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -1707,6 +1707,81 @@ http_parse_query_test(void *ptr) 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), From 86212341c5f38a973e3190a2c5c36ea947ef7253 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 18 Oct 2010 14:34:20 -0400 Subject: [PATCH 02/11] Make evhttp_uri_parse and friends conform to memory management standards --- http.c | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/http.c b/http.c index 37936566cf..7677e493e1 100644 --- a/http.c +++ b/http.c @@ -3334,20 +3334,21 @@ bind_socket(const char *address, ev_uint16_t port, int reuse) return (fd); } -struct evhttp_uri *evhttp_uri_parse(const char *source_uri) +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)); + struct evhttp_uri *uri = mm_calloc(1, sizeof(struct evhttp_uri)); if (uri == NULL) { event_err(1, "%s: calloc", __func__); return NULL; } - readbuf = strdup(source_uri); + readbuf = mm_strdup(source_uri); if (readbuf == NULL) { event_err(1, "%s: strdup", __func__); - free(uri); + mm_free(uri); return NULL; } @@ -3358,13 +3359,13 @@ struct evhttp_uri *evhttp_uri_parse(const char *source_uri) token = strstr(readp, "://"); if (!token) { /* unsupported uri */ - free(readbuf); - free(uri); + mm_free(readbuf); + mm_free(uri); return NULL; } *token = '\0'; - uri->scheme = strdup(readp); + uri->scheme = mm_strdup(readp); readp = token; readp += 3; /* eat :// */ @@ -3375,10 +3376,10 @@ struct evhttp_uri *evhttp_uri_parse(const char *source_uri) char *fragment = strchr(query, '#'); if (fragment) { *fragment++ = '\0'; /* eat '#' */ - uri->fragment = strdup(fragment); + uri->fragment = mm_strdup(fragment); } - uri->query = strdup(query); + uri->query = mm_strdup(query); *query = '\0'; /* eat '/' */ } @@ -3391,10 +3392,10 @@ struct evhttp_uri *evhttp_uri_parse(const char *source_uri) pass = strchr(readp, ':'); if (pass) { *pass++ = '\0'; /* eat ':' */ - uri->pass = strdup(pass); + uri->pass = mm_strdup(pass); } - uri->user = strdup(readp); + uri->user = mm_strdup(readp); readp = host; } @@ -3406,9 +3407,9 @@ struct evhttp_uri *evhttp_uri_parse(const char *source_uri) } /* 5. host */ - uri->host = strdup(readp); + uri->host = mm_strdup(readp); - free(readbuf); + mm_free(readbuf); return uri; } @@ -3420,7 +3421,7 @@ void evhttp_uri_free(struct evhttp_uri *uri) #define _URI_FREE_STR(f) \ if (uri->f) { \ - free(uri->f); \ + mm_free(uri->f); \ } _URI_FREE_STR(scheme); @@ -3430,12 +3431,13 @@ void evhttp_uri_free(struct evhttp_uri *uri) _URI_FREE_STR(query); _URI_FREE_STR(fragment); - free(uri); + mm_free(uri); #undef _URI_FREE_STR } -char *evhttp_uri_join(struct evhttp_uri *uri, void *buf, size_t limit) +char * +evhttp_uri_join(struct evhttp_uri *uri, void *buf, size_t limit) { struct evbuffer *tmp = 0; unsigned char *joined = 0; From 7d45431e15dc0d8aa88fddc4fc406e19fe54d004 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 18 Oct 2010 14:38:48 -0400 Subject: [PATCH 03/11] Do not silently truncate URIs in evhttp_uri_join. Also avoid evbuffer_pullup. --- http.c | 15 +++++++-------- include/event2/http.h | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/http.c b/http.c index 7677e493e1..ba2d528b9b 100644 --- a/http.c +++ b/http.c @@ -3437,10 +3437,9 @@ void evhttp_uri_free(struct evhttp_uri *uri) } char * -evhttp_uri_join(struct evhttp_uri *uri, void *buf, size_t limit) +evhttp_uri_join(struct evhttp_uri *uri, char *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)) @@ -3482,15 +3481,15 @@ evhttp_uri_join(struct evhttp_uri *uri, void *buf, size_t limit) 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'; + if (joined_size > limit) { + /* It doesn't fit. */ + evbuffer_free(tmp); + return NULL; } + evbuffer_remove(tmp, buf, joined_size); + evbuffer_free(tmp); return (char *)buf; diff --git a/include/event2/http.h b/include/event2/http.h index 9293270d17..655ff25494 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -637,7 +637,7 @@ void evhttp_uri_free(struct evhttp_uri *uri); * @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); +char *evhttp_uri_join(struct evhttp_uri *uri, char *buf, size_t limit); #ifdef __cplusplus } From fadbfd4e6e2d9badd4b61d5f847c589ed84dd2c8 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 18 Oct 2010 14:43:54 -0400 Subject: [PATCH 04/11] Clean up error handling in uri_parse a little --- http.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/http.c b/http.c index ba2d528b9b..87dc1f51e3 100644 --- a/http.c +++ b/http.c @@ -3337,19 +3337,18 @@ bind_socket(const char *address, ev_uint16_t port, int reuse) struct evhttp_uri * evhttp_uri_parse(const char *source_uri) { - char *readbuf = 0, *readp = 0, *token = 0, *query = 0, *host = 0, *port = 0; + char *readbuf = NULL, *readp = NULL, *token = NULL, *query = NULL, *host = NULL, *port = NULL; struct evhttp_uri *uri = mm_calloc(1, sizeof(struct evhttp_uri)); if (uri == NULL) { event_err(1, "%s: calloc", __func__); - return NULL; + goto err; } readbuf = mm_strdup(source_uri); if (readbuf == NULL) { event_err(1, "%s: strdup", __func__); - mm_free(uri); - return NULL; + goto err; } readp = readbuf; @@ -3359,9 +3358,7 @@ evhttp_uri_parse(const char *source_uri) token = strstr(readp, "://"); if (!token) { /* unsupported uri */ - mm_free(readbuf); - mm_free(uri); - return NULL; + goto err; } *token = '\0'; @@ -3412,13 +3409,17 @@ evhttp_uri_parse(const char *source_uri) mm_free(readbuf); return uri; +err: + if (uri) + evhttp_uri_free(uri); + if (readbuf) + mm_free(readbuf); + return NULL; } -void evhttp_uri_free(struct evhttp_uri *uri) +void +evhttp_uri_free(struct evhttp_uri *uri) { - if (uri == NULL) - return; - #define _URI_FREE_STR(f) \ if (uri->f) { \ mm_free(uri->f); \ From eaa5f1d9edd8ae2a5f637a3a07928dfee113accc Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Oct 2010 11:26:59 -0400 Subject: [PATCH 05/11] Revise evhttp_uri_parse implementation to handle more of RFC3986 --- http.c | 354 ++++++++++++++++++++++++++++------- include/event2/http_struct.h | 8 +- test/regress_http.c | 143 ++++++++++++-- 3 files changed, 414 insertions(+), 91 deletions(-) diff --git a/http.c b/http.c index 87dc1f51e3..883c675f5c 100644 --- a/http.c +++ b/http.c @@ -2317,6 +2317,9 @@ static const char uri_chars[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; +#define CHAR_IS_UNRESERVED(c) \ + (uri_chars[(unsigned char)(c)]) + /* * Helper functions to encode/decode a string for inclusion in a URI. * The returned string must be freed by the caller. @@ -2337,7 +2340,7 @@ evhttp_uriencode(const char *uri, ev_ssize_t len, int space_as_plus) end = uri+strlen(uri); for (p = uri; p < end; p++) { - if (uri_chars[(unsigned char)(*p)]) { + if (CHAR_IS_UNRESERVED(*p)) { evbuffer_add(buf, p, 1); } else if (*p == ' ' && space_as_plus) { evbuffer_add(buf, "+", 1); @@ -3334,16 +3337,208 @@ bind_socket(const char *address, ev_uint16_t port, int reuse) return (fd); } +/* Return true of the string starting at s and ending immediately before eos + * is a valid URI scheme according to RFC3986 + */ +static int +scheme_ok(const char *s, const char *eos) +{ + /* scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ + EVUTIL_ASSERT(eos >= s); + if (s == eos) + return 0; + if (!EVUTIL_ISALPHA(*s)) + return 0; + while (++s < eos) { + if (! EVUTIL_ISALNUM(*s) && + *s != '+' && *s != '-' && *s != '.') + return 0; + } + return 1; +} + +#define SUBDELIMS "!$&'()*+,;=" + +/* Return true iff [s..eos) is a valid userinfo */ +static int +userinfo_ok(const char *s, const char *eos) +{ + while (s < eos) { + if (CHAR_IS_UNRESERVED(*s) || + strchr(SUBDELIMS, *s) || + *s == ':') + ++s; + else if (*s == '%' && s+2 < eos && + EVUTIL_ISXDIGIT(s[1]) && + EVUTIL_ISXDIGIT(s[2])) + s += 3; + else + return 0; + } + return 1; +} + +static int +regname_ok(const char *s, const char *eos) +{ + while (s && s eos || *s != '[' || *(eos-1) != ']') + return 0; + if (s[1] == 'v') { + /* IPvFuture, or junk. + "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + */ + s += 2; /* skip [v */ + --eos; + if (!EVUTIL_ISXDIGIT(*s)) /*require at least one*/ + return 0; + while (s < eos && *s != '.') { + if (EVUTIL_ISXDIGIT(*s)) + ++s; + else + return 0; + } + if (*s != '.') + return 0; + ++s; + while (s < eos) { + if (CHAR_IS_UNRESERVED(*s) || + strchr(SUBDELIMS, *s) || + *s == ':') + ++s; + else + return 0; + } + return 2; + } else { + /* IPv6, or junk */ + char buf[64]; + int n_chars = eos-s-2; + struct in6_addr in6; + if (n_chars >= 64) /* way too long */ + return 0; + memcpy(buf, s+1, n_chars); + buf[n_chars]='\0'; + return (evutil_inet_pton(AF_INET6,buf,&in6)==1) ? 1 : 0; + } +} + +static int +parse_authority(struct evhttp_uri *uri, char *s, char *eos) +{ + char *cp, *port; + EVUTIL_ASSERT(eos); + if (eos == s) { + uri->host = mm_strdup(""); + return 0; + } + + /* Optionally, we start with "userinfo@" */ + + cp = strchr(s, '@'); + if (cp && cp < eos) { + if (! userinfo_ok(s,cp)) + return -1; + *cp++ = '\0'; + uri->userinfo = mm_strdup(s); + } else { + cp = s; + } + /* Optionally, we end with ":port" */ + for (port=eos-1; port >= cp && EVUTIL_ISDIGIT(*port); --port) + ; + if (port >= cp && *port == ':') { + if ((uri->port = parse_port(port+1, eos))<0) + return -1; + eos = port; + } + /* Now, cp..eos holds the "host" port, which can be an IPv4Address, + * an IP-Literal, or a reg-name */ + EVUTIL_ASSERT(eos >= cp); + if (*cp == '[' && eos >= cp+2 && *(eos-1) == ']') { + /* IPv6address, IP-Literal, or junk. */ + if (! bracket_addr_ok(cp, eos)) + return -1; + } else { + /* Make sure the host part is ok. */ + if (! regname_ok(cp,eos)) /* Match IPv4Address or reg-name */ + return -1; + } + uri->host = mm_malloc(eos-cp+1); + memcpy(uri->host, cp, eos-cp); + uri->host[eos-cp] = '\0'; + return 0; + +} + +/* Return the character after the longest prefix of 'cp' that matches... + * *pchar / "/" if allow_qchars is false, or + * *(pchar / "/" / "?") if allow_chars is true. + */ +static char * +end_of_path(char *cp, int allow_qchars) +{ + while (*cp) { + if (CHAR_IS_UNRESERVED(*cp) || + strchr(SUBDELIMS, *cp) || + *cp == ':' || *cp == '@' || *cp == '/') + ++cp; + else if (*cp == '%' && EVUTIL_ISXDIGIT(cp[1]) && + EVUTIL_ISXDIGIT(cp[2])) + cp += 3; + else if (*cp == '?' && allow_qchars) + ++cp; + else + return cp; + } + return cp; +} + struct evhttp_uri * evhttp_uri_parse(const char *source_uri) { - char *readbuf = NULL, *readp = NULL, *token = NULL, *query = NULL, *host = NULL, *port = NULL; + char *readbuf = NULL, *readp = NULL, *token = NULL, *query = NULL; + char *path = NULL, *fragment = NULL; + int got_authority = 0; struct evhttp_uri *uri = mm_calloc(1, sizeof(struct evhttp_uri)); if (uri == NULL) { event_err(1, "%s: calloc", __func__); goto err; } + uri->port = -1; readbuf = mm_strdup(source_uri); if (readbuf == NULL) { @@ -3354,57 +3549,79 @@ evhttp_uri_parse(const char *source_uri) readp = readbuf; token = NULL; - /* 1. scheme:// */ - token = strstr(readp, "://"); - if (!token) { - /* unsupported uri */ - goto err; - } + /* We try to follow RFC3986 here as much as we can, and match + the productions - *token = '\0'; - uri->scheme = mm_strdup(readp); + URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] - readp = token; - readp += 3; /* eat :// */ + relative-ref = relative-part [ "?" query ] [ "#" fragment ] - /* 2. query */ - 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 */ - 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 = mm_strdup(pass); - } + /* 1. scheme: */ + token = strchr(readp, ':'); + if (token && scheme_ok(readp,token)) { + *token = '\0'; + uri->scheme = mm_strdup(readp); + + readp = token+1; /* eat : */ + } - uri->user = mm_strdup(readp); - readp = host; + /* 2. Optionally, "//" then an 'authority' part. */ + if (readp[0]=='/' && readp[1] == '/') { + char *authority; + readp += 2; + authority = readp; + path = strchr(readp, '/'); /*XXXX path can be empty; we can + * have a query though */ + if (!path) + path = strchr(readp, '\0'); + if (parse_authority(uri, authority, path) < 0) + goto err; + readp = path; + got_authority = 1; } - /* 4. host:port */ - port = strchr(readp, ':'); - if (port) { - *port++ = '\0'; /* eat ':' */ - uri->port = atoi(port); + /* 3. Query: path-abempty, path-absolute, path-rootless, or path-empty + */ + path = readp; + readp = end_of_path(path, 0); + + /* Query */ + if (*readp == '?') { + *readp = '\0'; + ++readp; + query = readp; + readp = end_of_path(readp, 1); + } + /* fragment */ + if (*readp == '#') { + *readp = '\0'; + ++readp; + fragment = readp; + readp = end_of_path(readp, 1); + } + if (*readp != '\0') { + goto err; } - /* 5. host */ - uri->host = mm_strdup(readp); + /* If you didn't get an authority, the path can't begin with "//" */ + if (!got_authority && path[0]=='/' && path[1]=='/') + goto err; + /* If you did get an authority, the path must begin with "/" or be + * empty. */ + if (got_authority && path[0] != '/' && path[0] != '\0') + goto err; + + if (path) + uri->path = mm_strdup(path); + else + uri->path = mm_strdup(""); + + if (query) + uri->query = mm_strdup(query); + if (fragment) + uri->fragment = mm_strdup(fragment); mm_free(readbuf); @@ -3426,8 +3643,7 @@ evhttp_uri_free(struct evhttp_uri *uri) } _URI_FREE_STR(scheme); - _URI_FREE_STR(user); - _URI_FREE_STR(pass); + _URI_FREE_STR(userinfo); _URI_FREE_STR(host); _URI_FREE_STR(query); _URI_FREE_STR(fragment); @@ -3442,40 +3658,42 @@ evhttp_uri_join(struct evhttp_uri *uri, char *buf, size_t limit) { struct evbuffer *tmp = 0; size_t joined_size = 0; + char *output = NULL; #define _URI_ADD(f) evbuffer_add(tmp, uri->f, strlen(uri->f)) - if (!uri || !uri->scheme || !buf || !limit) + + if (!uri || !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); - } - + if (uri->scheme) { + _URI_ADD(scheme); + evbuffer_add(tmp, ":", 1); + } + if (uri->host) { + evbuffer_add(tmp, "//", 2); + if (uri->userinfo) + evbuffer_add_printf(tmp,"%s@", uri->userinfo); _URI_ADD(host); + if (uri->port >= 0) + evbuffer_add_printf(tmp,":%d", uri->port); - if (uri->port > 0) - evbuffer_add_printf(tmp,":%u", uri->port); + if (uri->path && uri->path[0] != '/' && uri->path[0] != '\0') + goto err; } - if (uri->query && *uri->query) - _URI_ADD(query); + if (uri->path) + _URI_ADD(path); - if (uri->fragment && *uri->fragment) { - if (!uri->query || !*uri->query) - evbuffer_add(tmp, "/", 1); + if (uri->query) { + evbuffer_add(tmp, "?", 1); + _URI_ADD(query); + } + if (uri->fragment) { evbuffer_add(tmp, "#", 1); _URI_ADD(fragment); } @@ -3491,8 +3709,10 @@ evhttp_uri_join(struct evhttp_uri *uri, char *buf, size_t limit) } evbuffer_remove(tmp, buf, joined_size); + output = buf; +err: evbuffer_free(tmp); - return (char *)buf; + return output; #undef _URI_ADD } diff --git a/include/event2/http_struct.h b/include/event2/http_struct.h index 168f5aca60..264877990d 100644 --- a/include/event2/http_struct.h +++ b/include/event2/http_struct.h @@ -123,11 +123,11 @@ struct { */ 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 */ + char *host; /* hostname, IP address, or NULL */ + char *userinfo; /* userinfo (typically username:pass), or NULL */ int port; /* port, or zero */ - char *query; /* path + query: e.g. /path/to?param=foo, or NULL */ + char *path; /* path, or NULL */ + char *query; /* query, or NULL */ char *fragment; /* fragment or NULL */ }; diff --git a/test/regress_http.c b/test/regress_http.c index 170432195e..fee9a263a3 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -1725,15 +1725,23 @@ http_parse_uri_test(void *ptr) 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("mailto:foo@bar"); + tt_want(uri != NULL); + tt_want(uri->host == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); + tt_want(!strcmp(uri->scheme, "mailto")); + tt_want(!strcmp(uri->path, "foo@bar")); + tt_want(uri->query == NULL); + tt_want(uri->fragment == 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(strcmp(uri->path, "/") == 0); + tt_want(strcmp(uri->query, "q=test") == 0); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); tt_want(uri->fragment == NULL); TT_URI("http://www.test.com/?q=test"); evhttp_uri_free(uri); @@ -1741,45 +1749,140 @@ http_parse_uri_test(void *ptr) 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(strcmp(uri->path, "/") == 0); + tt_want(strcmp(uri->query, "q=test") == 0); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); tt_want(uri->fragment == NULL); TT_URI("ftp://www.test.com/?q=test"); evhttp_uri_free(uri); + uri = evhttp_uri_parse("ftp://[::1]:999/?q=test"); + tt_want(strcmp(uri->scheme, "ftp") == 0); + tt_want(strcmp(uri->host, "[::1]") == 0); + tt_want(strcmp(uri->path, "/") == 0); + tt_want(strcmp(uri->query, "q=test") == 0); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == 999); + tt_want(uri->fragment == NULL); + TT_URI("ftp://[::1]:999/?q=test"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("ftp://[ff00::127.0.0.1]/?q=test"); + tt_want(strcmp(uri->scheme, "ftp") == 0); + tt_want(strcmp(uri->host, "[ff00::127.0.0.1]") == 0); + tt_want(strcmp(uri->path, "/") == 0); + tt_want(strcmp(uri->query, "q=test") == 0); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); + tt_want(uri->fragment == NULL); + TT_URI("ftp://[ff00::127.0.0.1]/?q=test"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("ftp://[v99.not_anytime_soon]/?q=test"); + tt_want(strcmp(uri->scheme, "ftp") == 0); + tt_want(strcmp(uri->host, "[v99.not_anytime_soon]") == 0); + tt_want(strcmp(uri->path, "/") == 0); + tt_want(strcmp(uri->query, "q=test") == 0); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); + tt_want(uri->fragment == NULL); + TT_URI("ftp://[v99.not_anytime_soon]/?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->userinfo, "user: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->path, "/") == 0); + 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->userinfo, "user") == 0); tt_want(strcmp(uri->host, "foo.com") == 0); - tt_want(uri->port == 0); - tt_want(strcmp(uri->query, "/") == 0); + tt_want(uri->port == -1); + tt_want(strcmp(uri->path, "/") == 0); + tt_want(uri->query == NULL); 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(uri->userinfo == 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->port == -1); + tt_want(strcmp(uri->path, "/some/path/to/the/file") == 0); + tt_want(uri->query == NULL); tt_want(uri->fragment == NULL); TT_URI("file:///some/path/to/the/file"); evhttp_uri_free(uri); + + uri = evhttp_uri_parse("///some/path/to/the-file"); + tt_want(uri != NULL); + tt_want(uri->scheme == NULL); + tt_want(uri->userinfo == NULL); + tt_want(strcmp(uri->host, "") == 0); + tt_want(uri->port == -1); + tt_want(strcmp(uri->path, "/some/path/to/the-file") == 0); + tt_want(uri->query == NULL); + tt_want(uri->fragment == NULL); + TT_URI("///some/path/to/the-file"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("/s:ome/path/to/the-file?q=99#fred"); + tt_want(uri != NULL); + tt_want(uri->scheme == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->host == NULL); + tt_want(uri->port == -1); + tt_want(strcmp(uri->path, "/s:ome/path/to/the-file") == 0); + tt_want(strcmp(uri->query, "q=99") == 0); + tt_want(strcmp(uri->fragment, "fred") == 0); + TT_URI("/s:ome/path/to/the-file?q=99#fred"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("relative/path/with/co:lon"); + tt_want(uri != NULL); + tt_want(uri->scheme == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->host == NULL); + tt_want(uri->port == -1); + tt_want(strcmp(uri->path, "relative/path/with/co:lon") == 0); + tt_want(uri->query == NULL); + tt_want(uri->fragment == NULL); + TT_URI("relative/path/with/co:lon"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("bob?q=99&q2=q?33#fr?ed"); + tt_want(uri != NULL); + tt_want(uri->scheme == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->host == NULL); + tt_want(uri->port == -1); + tt_want(strcmp(uri->path, "bob") == 0); + tt_want(strcmp(uri->query, "q=99&q2=q?33") == 0); + tt_want(strcmp(uri->fragment, "fr?ed") == 0); + TT_URI("bob?q=99&q2=q?33#fr?ed"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("#fr?ed"); + tt_want(uri != NULL); + tt_want(uri->scheme == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->host == NULL); + tt_want(uri->port == -1); + tt_want(strcmp(uri->path, "") == 0); + tt_want(uri->query == NULL); + tt_want(strcmp(uri->fragment, "fr?ed") == 0); + TT_URI("#fr?ed"); + evhttp_uri_free(uri); + } static void From ad923a11f1e110b3bf03b7179cdcd15ccfcfedd6 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Oct 2010 12:33:50 -0400 Subject: [PATCH 06/11] Improvements to tinytest_macros.h First, handle cases where we have %s in a tt_want or tt_assert. Second, add tt_want_*_op that do a tt_*_op test, but do not exit the test on failure. We should push these upstream to tinytest some time. --- test/tinytest_macros.h | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/test/tinytest_macros.h b/test/tinytest_macros.h index a7fa64a824..032393ccf7 100644 --- a/test/tinytest_macros.h +++ b/test/tinytest_macros.h @@ -90,10 +90,10 @@ TT_STMT_BEGIN \ if (!(b)) { \ _tinytest_set_test_failed(); \ - TT_GRIPE((msg)); \ + TT_GRIPE(("%s",msg)); \ fail; \ } else { \ - TT_BLATHER((msg)); \ + TT_BLATHER(("%s",msg)); \ } \ TT_STMT_END @@ -111,7 +111,7 @@ #define tt_assert(b) tt_assert_msg((b), "assert("#b")") #define tt_assert_test_fmt_type(a,b,str_test,type,test,printf_type,printf_fmt, \ - setup_block,cleanup_block) \ + setup_block,cleanup_block,die_on_fail) \ TT_STMT_BEGIN \ type _val1 = (type)(a); \ type _val2 = (type)(b); \ @@ -135,33 +135,50 @@ cleanup_block; \ if (!_tt_status) { \ _tinytest_set_test_failed(); \ - TT_EXIT_TEST_FUNCTION; \ + die_on_fail ; \ } \ } \ TT_STMT_END -#define tt_assert_test_type(a,b,str_test,type,test,fmt) \ +#define tt_assert_test_type(a,b,str_test,type,test,fmt,die_on_fail) \ tt_assert_test_fmt_type(a,b,str_test,type,test,type,fmt, \ - {_print=_value;},{}) + {_print=_value;},{},die_on_fail) /* Helper: assert that a op b, when cast to type. Format the values with * printf format fmt on failure. */ #define tt_assert_op_type(a,op,b,type,fmt) \ - tt_assert_test_type(a,b,#a" "#op" "#b,type,(_val1 op _val2),fmt) + tt_assert_test_type(a,b,#a" "#op" "#b,type,(_val1 op _val2),fmt, \ + TT_EXIT_TEST_FUNCTION) #define tt_int_op(a,op,b) \ - tt_assert_test_type(a,b,#a" "#op" "#b,long,(_val1 op _val2),"%ld") + tt_assert_test_type(a,b,#a" "#op" "#b,long,(_val1 op _val2), \ + "%ld",TT_EXIT_TEST_FUNCTION) #define tt_uint_op(a,op,b) \ tt_assert_test_type(a,b,#a" "#op" "#b,unsigned long, \ - (_val1 op _val2),"%lu") + (_val1 op _val2),"%lu",TT_EXIT_TEST_FUNCTION) #define tt_ptr_op(a,op,b) \ tt_assert_test_type(a,b,#a" "#op" "#b,void*, \ - (_val1 op _val2),"%p") + (_val1 op _val2),"%p",TT_EXIT_TEST_FUNCTION) #define tt_str_op(a,op,b) \ tt_assert_test_type(a,b,#a" "#op" "#b,const char *, \ - (strcmp(_val1,_val2) op 0),"<%s>") + (strcmp(_val1,_val2) op 0),"<%s>",TT_EXIT_TEST_FUNCTION) + +#define tt_want_int_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,long,(_val1 op _val2),"%ld",(void)0) + +#define tt_want_uint_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,unsigned long, \ + (_val1 op _val2),"%lu",(void)0) + +#define tt_want_ptr_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,void*, \ + (_val1 op _val2),"%p",(void)0) + +#define tt_want_str_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,const char *, \ + (strcmp(_val1,_val2) op 0),"<%s>",(void)0) #endif From a5a76e689c185fb9f6f430fd8e989645b627bdb3 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Oct 2010 12:35:50 -0400 Subject: [PATCH 07/11] Add a huge pile of tests for the new URI functions, and make them pass. --- http.c | 52 +++++++++++--- test/regress_http.c | 162 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 193 insertions(+), 21 deletions(-) diff --git a/http.c b/http.c index 883c675f5c..2bf48498a8 100644 --- a/http.c +++ b/http.c @@ -3399,12 +3399,12 @@ static int parse_port(const char *s, const char *eos) { int portnum = 0; - if (s == eos) - return 0; /* The RFC allows an empty port. */ while (s < eos) { if (! EVUTIL_ISDIGIT(*s)) return -1; portnum = (portnum * 10) + (*s - '0'); + if (portnum < 0) + return -1; ++s; } return portnum; @@ -3480,7 +3480,10 @@ parse_authority(struct evhttp_uri *uri, char *s, char *eos) for (port=eos-1; port >= cp && EVUTIL_ISDIGIT(*port); --port) ; if (port >= cp && *port == ':') { - if ((uri->port = parse_port(port+1, eos))<0) + if (port+1 == eos) /* Leave port unspecified; the RFC allows a + * nil port */ + uri->port = -1; + else if ((uri->port = parse_port(port+1, eos))<0) return -1; eos = port; } @@ -3503,6 +3506,17 @@ parse_authority(struct evhttp_uri *uri, char *s, char *eos) } +static char * +end_of_authority(char *cp) +{ + while (*cp) { + if (*cp == '?' || *cp == '#' || *cp == '/') + return cp; + ++cp; + } + return cp; +} + /* Return the character after the longest prefix of 'cp' that matches... * *pchar / "/" if allow_qchars is false, or * *(pchar / "/" / "?") if allow_chars is true. @@ -3526,6 +3540,19 @@ end_of_path(char *cp, int allow_qchars) return cp; } +static int +path_matches_noscheme(const char *cp) +{ + while (*cp) { + if (*cp == ':') + return 0; + else if (*cp == '/') + return 1; + ++cp; + } + return 1; +} + struct evhttp_uri * evhttp_uri_parse(const char *source_uri) { @@ -3572,10 +3599,7 @@ evhttp_uri_parse(const char *source_uri) char *authority; readp += 2; authority = readp; - path = strchr(readp, '/'); /*XXXX path can be empty; we can - * have a query though */ - if (!path) - path = strchr(readp, '\0'); + path = end_of_authority(readp); if (parse_authority(uri, authority, path) < 0) goto err; readp = path; @@ -3605,6 +3629,8 @@ evhttp_uri_parse(const char *source_uri) goto err; } + /* These next two cases may be unreachable; I'm leaving them + * in to be defensive. */ /* If you didn't get an authority, the path can't begin with "//" */ if (!got_authority && path[0]=='/' && path[1]=='/') goto err; @@ -3612,11 +3638,15 @@ evhttp_uri_parse(const char *source_uri) * empty. */ if (got_authority && path[0] != '/' && path[0] != '\0') goto err; + /* (End of maybe-unreachable cases) */ - if (path) - uri->path = mm_strdup(path); - else - uri->path = mm_strdup(""); + /* If there was no scheme, the first part of the path (if any) must + * have no colon in it. */ + if (! uri->scheme && !path_matches_noscheme(path)) + goto err; + + EVUTIL_ASSERT(path); + uri->path = mm_strdup(path); if (query) uri->query = mm_strdup(query); diff --git a/test/regress_http.c b/test/regress_http.c index fee9a263a3..34d2781e1e 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -1717,13 +1717,66 @@ http_parse_uri_test(void *ptr) 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); \ + if (strcmp(ret,want) != 0) \ + TT_FAIL(("\"%s\" != \"%s\"",ret,want)); \ } 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(NULL, 0, 0) == NULL); + tt_want(evhttp_uri_join(NULL, url_tmp, 0) == NULL); + tt_want(evhttp_uri_join(NULL, url_tmp, sizeof(url_tmp)) == NULL); + + /* bad URIs: parsing */ +#define BAD(s) do { \ + if (evhttp_uri_parse(s) != NULL) \ + TT_FAIL(("Expected error parsing \"%s\"",s)); \ + } while(0) + BAD("http://www.test.com/ why hello"); + BAD("http://www.test.com/why-hello\x01"); + BAD("http://www.test.com/why-hello?\x01"); + BAD("http://www.test.com/why-hello#\x01"); + BAD("http://www.\x01.test.com/why-hello"); + BAD("http://www.%7test.com/why-hello"); + BAD("http://www.test.com/why-hell%7o"); + BAD("h%3ttp://www.test.com/why-hello"); + BAD("http://www.test.com/why-hello%7"); + BAD("http://www.test.com/why-hell%7o"); + BAD("http://www.test.com/foo?ba%r"); + BAD("http://www.test.com/foo#ba%r"); + BAD("99:99/foo"); + BAD("http://www.test.com:999x/"); + BAD("http://www.test.com:x/"); + BAD("http://[hello-there]/"); + BAD("http://[::1]]/"); + BAD("http://[::1/"); + BAD("http://[foob/"); + BAD("http://[/"); + BAD("http://[ffff:ffff:ffff:ffff:Ffff:ffff:ffff:" + "ffff:ffff:ffff:ffff:ffff:ffff:ffff]/"); + BAD("http://[vX.foo]/"); + BAD("http://[vX.foo]/"); + BAD("http://[v.foo]/"); + BAD("http://[v5.fo%o]/"); + BAD("http://[v5X]/"); + BAD("http://[v5]/"); + BAD("http://[]/"); + BAD("http://f\x01red@www.example.com/"); + BAD("http://f%0red@www.example.com/"); + BAD("http://www.example.com:9999999999999999999999999999999999999/"); + BAD("http://www.example.com:hihi/"); + BAD("://www.example.com/"); + + /* bad URIs: joining */ + uri = calloc(sizeof(struct evhttp_uri),1); + uri->host = (char*)"www.example.com"; + tt_want(evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)) != NULL); + /* not enough space: */ + tt_want(evhttp_uri_join(uri, url_tmp, 3) == NULL); + /* host is set, but path doesn't start with "/": */ + uri->path = (char*)"hi_mom"; tt_want(evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)) == NULL); + tt_want(evhttp_uri_join(uri, NULL, sizeof(url_tmp))==NULL); + tt_want(evhttp_uri_join(uri, url_tmp, 0)==NULL); + free(uri); uri = evhttp_uri_parse("mailto:foo@bar"); tt_want(uri != NULL); @@ -1734,16 +1787,95 @@ http_parse_uri_test(void *ptr) tt_want(!strcmp(uri->path, "foo@bar")); tt_want(uri->query == NULL); tt_want(uri->fragment == NULL); + TT_URI("mailto:foo@bar"); + evhttp_uri_free(uri); - uri = evhttp_uri_parse("http://www.test.com/?q=test"); + uri = evhttp_uri_parse("http://www.test.com/?q=t%33est"); tt_want(strcmp(uri->scheme, "http") == 0); tt_want(strcmp(uri->host, "www.test.com") == 0); tt_want(strcmp(uri->path, "/") == 0); + tt_want(strcmp(uri->query, "q=t%33est") == 0); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); + tt_want(uri->fragment == NULL); + TT_URI("http://www.test.com/?q=t%33est"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("http://%77ww.test.com"); + tt_want(strcmp(uri->scheme, "http") == 0); + tt_want(strcmp(uri->host, "%77ww.test.com") == 0); + tt_want(strcmp(uri->path, "") == 0); + tt_want(uri->query == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); + tt_want(uri->fragment == NULL); + TT_URI("http://%77ww.test.com"); + evhttp_uri_free(uri); + + 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->path, "") == 0); tt_want(strcmp(uri->query, "q=test") == 0); tt_want(uri->userinfo == NULL); tt_want(uri->port == -1); tt_want(uri->fragment == NULL); - TT_URI("http://www.test.com/?q=test"); + TT_URI("http://www.test.com?q=test"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("http://www.test.com#fragment"); + tt_want(strcmp(uri->scheme, "http") == 0); + tt_want(strcmp(uri->host, "www.test.com") == 0); + tt_want(strcmp(uri->path, "") == 0); + tt_want(uri->query == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); + tt_want_str_op(uri->fragment, ==, "fragment"); + TT_URI("http://www.test.com#fragment"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("http://8000/"); + tt_want(strcmp(uri->scheme, "http") == 0); + tt_want(strcmp(uri->host, "8000") == 0); + tt_want(strcmp(uri->path, "/") == 0); + tt_want(uri->query == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); + tt_want(uri->fragment == NULL); + TT_URI("http://8000/"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("http://:8000/"); + tt_want(strcmp(uri->scheme, "http") == 0); + tt_want(strcmp(uri->host, "") == 0); + tt_want(strcmp(uri->path, "/") == 0); + tt_want(uri->query == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == 8000); + tt_want(uri->fragment == NULL); + TT_URI("http://:8000/"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("http://www.test.com:/"); /* empty port */ + tt_want(strcmp(uri->scheme, "http") == 0); + tt_want(strcmp(uri->host, "www.test.com") == 0); + tt_want_str_op(uri->path, ==, "/"); + tt_want(uri->query == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); + tt_want(uri->fragment == NULL); + TT_URI("http://www.test.com/"); + evhttp_uri_free(uri); + + uri = evhttp_uri_parse("http://www.test.com:"); /* empty port 2 */ + tt_want(strcmp(uri->scheme, "http") == 0); + tt_want(strcmp(uri->host, "www.test.com") == 0); + tt_want(strcmp(uri->path, "") == 0); + tt_want(uri->query == NULL); + tt_want(uri->userinfo == NULL); + tt_want(uri->port == -1); + tt_want(uri->fragment == NULL); + TT_URI("http://www.test.com"); evhttp_uri_free(uri); uri = evhttp_uri_parse("ftp://www.test.com/?q=test"); @@ -1779,15 +1911,15 @@ http_parse_uri_test(void *ptr) TT_URI("ftp://[ff00::127.0.0.1]/?q=test"); evhttp_uri_free(uri); - uri = evhttp_uri_parse("ftp://[v99.not_anytime_soon]/?q=test"); + uri = evhttp_uri_parse("ftp://[v99.not_(any:time)_soon]/?q=test"); tt_want(strcmp(uri->scheme, "ftp") == 0); - tt_want(strcmp(uri->host, "[v99.not_anytime_soon]") == 0); + tt_want(strcmp(uri->host, "[v99.not_(any:time)_soon]") == 0); tt_want(strcmp(uri->path, "/") == 0); tt_want(strcmp(uri->query, "q=test") == 0); tt_want(uri->userinfo == NULL); tt_want(uri->port == -1); tt_want(uri->fragment == NULL); - TT_URI("ftp://[v99.not_anytime_soon]/?q=test"); + TT_URI("ftp://[v99.not_(any:time)_soon]/?q=test"); evhttp_uri_free(uri); uri = evhttp_uri_parse("scheme://user:pass@foo.com:42/?q=test&s=some+thing#fragment"); @@ -1812,6 +1944,17 @@ http_parse_uri_test(void *ptr) TT_URI("scheme://user@foo.com/#fragment"); evhttp_uri_free(uri); + uri = evhttp_uri_parse("scheme://%75ser@foo.com/#frag@ment"); + tt_want(strcmp(uri->scheme, "scheme") == 0); + tt_want(strcmp(uri->userinfo, "%75ser") == 0); + tt_want(strcmp(uri->host, "foo.com") == 0); + tt_want(uri->port == -1); + tt_want(strcmp(uri->path, "/") == 0); + tt_want(uri->query == NULL); + tt_want(strcmp(uri->fragment, "frag@ment") == 0); + TT_URI("scheme://%75ser@foo.com/#frag@ment"); + evhttp_uri_free(uri); + uri = evhttp_uri_parse("file:///some/path/to/the/file"); tt_want(strcmp(uri->scheme, "file") == 0); tt_want(uri->userinfo == NULL); @@ -1882,7 +2025,6 @@ http_parse_uri_test(void *ptr) tt_want(strcmp(uri->fragment, "fr?ed") == 0); TT_URI("#fr?ed"); evhttp_uri_free(uri); - } static void From 3a33462827ec1c31083c297a16aa0287c92a8bfe Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Oct 2010 13:02:18 -0400 Subject: [PATCH 08/11] Document behavior of URI parsing more thoroughly. Also, move evhttp_uri struct into http.h, since it is part of the API. --- include/event2/http.h | 65 ++++++++++++++++++++++++++++-------- include/event2/http_struct.h | 14 -------- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/include/event2/http.h b/include/event2/http.h index 655ff25494..e2c6942e3c 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -607,30 +607,69 @@ 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] + * A structure to hold a parsed URI. + */ +struct evhttp_uri { + char *scheme; /* scheme; e.g http, ftp etc */ + char *host; /* hostname, IP address, or NULL */ + char *userinfo; /* userinfo (typically username:pass), or NULL */ + int port; /* port, or zero */ + char *path; /* path, or "". */ + char *query; /* query, or NULL */ + char *fragment; /* fragment or NULL */ +}; - @param source_uri the request URI - @return uri container to hold parsed data, or NULL if there is error - @see evhttp_uri_free() +/** + * Helper function to parse a URI-Reference as specified by RFC3986. + * + * This function matches the URI-Reference production from RFC3986, + * which includes both URIs like + * + * scheme://[[userinfo]@]foo.com[:port]]/[path][?query][#fragment] + * + * and relative-refs like + * + * [path][?query][#fragment] + * + * Any optional elements portions not present in the original URI are + * left set to NULL in the resulting evhttp_uri. If no port is + * specified, the port is set to -1. + * + * Note that no decoding is performed on percent-escaped characters in + * the string; if you want to parse them, use evhttp_uridecode as + * appropriate. + * + * Note also that most URI schemes will have additional constraints that + * this function does not know about, and cannot check. For example, + * mailto://www.example.com/cgi-bin/fortune.pl is not a reasonable + * mailto url, http://www.example.com:99999/ is not a reasonable HTTP + * URL, and ftp:username@example.com is not a reasonable FTP URL. + * Nevertheless, all of these URLs conform to RFC3986, and this function + * accepts all of them as valid. + * + * @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 + * Free all memory allocated for a parsed uri. Only use this for URIs + * generated by evhttp_uri_parse. + * * @param uri container with parsed data - @see evhttp_uri_parse() + * @see evhttp_uri_parse() */ void evhttp_uri_free(struct evhttp_uri *uri); /** - * Join together the uri parts from parsed data + * Join together the uri parts from parsed data to form a URI-Reference. + * + * Note that no escaping of reserved characters is done on the members + * of the evhttp_uri, so the generated string might not be a valid URI + * unless the members of evhttp_uri are themselves valid. + * * @param uri container with parsed data * @param buf destination buffer * @param limit destination buffer size diff --git a/include/event2/http_struct.h b/include/event2/http_struct.h index 264877990d..a3664e0448 100644 --- a/include/event2/http_struct.h +++ b/include/event2/http_struct.h @@ -118,20 +118,6 @@ 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, IP address, or NULL */ - char *userinfo; /* userinfo (typically username:pass), or NULL */ - int port; /* port, or zero */ - char *path; /* path, or NULL */ - char *query; /* query, or NULL */ - char *fragment; /* fragment or NULL */ -}; - - #ifdef __cplusplus } #endif From 2075fbcff0d8a9e06e2c3287b1cf4d400394c285 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Oct 2010 13:15:48 -0400 Subject: [PATCH 09/11] Add evhttp_parse_query_str to be used with evhttp_uri_parse. The old evhttp_parse_query() doesn't work well with struct evhttp_uri.query, since it expects to get whole URIs, rather than just the query portion. --- http.c | 42 ++++++++++++++++++++++++++---------------- include/event2/http.h | 34 +++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/http.c b/http.c index 2bf48498a8..e4b8e31a3a 100644 --- a/http.c +++ b/http.c @@ -2449,30 +2449,39 @@ evhttp_uridecode(const char *uri, int decode_plus, size_t *size_out) */ int -evhttp_parse_query__checked_20(const char *uri, struct evkeyvalq *headers) +evhttp_parse_query__checked_20(const char *str, struct evkeyvalq *headers, + int is_whole_uri) { - char *line; + char *line=NULL; char *argument; char *p; + const char *query_part; int result = -1; + struct evhttp_uri *uri=NULL; TAILQ_INIT(headers); + if (is_whole_uri) { + uri = evhttp_uri_parse(str); + if (!uri) + goto error; + query_part = uri->query; + } else { + query_part = str; + } + /* No arguments - we are done */ - if (strchr(uri, '?') == NULL) - return 0; + if (!query_part || !strlen(query_part)) { + result = 0; + goto done; + } - if ((line = mm_strdup(uri)) == NULL) { + if ((line = mm_strdup(query_part)) == NULL) { event_warn("%s: strdup", __func__); - return -1; + goto error; } - argument = line; - - /* We already know that there has to be a ? */ - strsep(&argument, "?"); - - p = argument; + p = argument = line; while (p != NULL && *p != '\0') { char *key, *value, *decoded_value; argument = strsep(&p, "&"); @@ -2499,7 +2508,10 @@ evhttp_parse_query__checked_20(const char *uri, struct evkeyvalq *headers) error: evhttp_clear_headers(headers); done: - mm_free(line); + if (line) + mm_free(line); + if (uri) + evhttp_uri_free(uri); return result; } @@ -2512,11 +2524,9 @@ void evhttp_parse_query(const char *uri, struct evkeyvalq *headers); void evhttp_parse_query(const char *uri, struct evkeyvalq *headers) { - evhttp_parse_query__checked_20(uri, headers); + evhttp_parse_query__checked_20(uri, headers, 1); } - - static struct evhttp_cb * evhttp_dispatch_callback(struct httpcbq *callbacks, struct evhttp_request *req) { diff --git a/include/event2/http.h b/include/event2/http.h index e2c6942e3c..d882061c73 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -573,7 +573,7 @@ char *evhttp_uridecode(const char *uri, int decode_plus, /** Helper function to parse out arguments in a query. - Parsing a uri like + Parsing a URI like http://foo.com/?q=test&s=some+thing @@ -582,17 +582,41 @@ char *evhttp_uridecode(const char *uri, int decode_plus, The first entry is: key="q", value="test" The second entry is: key="s", value="some thing" + @deprecated This function is deprecated as of Libevent 2.0.9. Use + evhttp_uri_parse and evhttp_parse_query_str instead. + @param uri the request URI @param headers the head of the evkeyval queue @return 0 on success, -1 on failure */ #define evhttp_parse_query(uri, headers) \ - evhttp_parse_query__checked_20((uri), (headers)) + evhttp_parse_query__checked_20((uri), (headers), 1) + +/** + Helper function to parse out arguments from the query portion of an + HTTP URI. + + Parsing a query string like + + q=test&s=some+thing + + will result in two entries in the key value queue. + + The first entry is: key="q", value="test" + The second entry is: key="s", value="some thing" + + @param query_parse the query portion of the URI + @param headers the head of the evkeyval queue + @return 0 on success, -1 on failure + */ +#define evhttp_parse_query_str(query, headers) \ + evhttp_parse_query__checked_20((uri), (headers), 0) /* Do not call this function directly; it is a temporary alias introduced * to avoid changing the old signature for evhttp_parse_query */ -int evhttp_parse_query__checked_20(const char *uri, struct evkeyvalq *headers); +int evhttp_parse_query__checked_20(const char *uri, struct evkeyvalq *headers, + int is_whole_url); /** * Escape HTML character entities in a string. @@ -637,8 +661,8 @@ struct evhttp_uri { * specified, the port is set to -1. * * Note that no decoding is performed on percent-escaped characters in - * the string; if you want to parse them, use evhttp_uridecode as - * appropriate. + * the string; if you want to parse them, use evhttp_uridecode or + * evhttp_parse_query_str as appropriate. * * Note also that most URI schemes will have additional constraints that * this function does not know about, and cannot check. For example, From 45f6869c753218a72dd44660a880237aa8daa386 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 21 Oct 2010 14:41:12 -0400 Subject: [PATCH 10/11] Make evhttp_uri non-public, and give it accessor functions. --- http.c | 140 ++++++++++++++++++- include/event2/http.h | 75 ++++++++-- test/regress_http.c | 317 +++++++++++++++++++++--------------------- 3 files changed, 360 insertions(+), 172 deletions(-) diff --git a/http.c b/http.c index e4b8e31a3a..321920072b 100644 --- a/http.c +++ b/http.c @@ -2465,7 +2465,7 @@ evhttp_parse_query__checked_20(const char *str, struct evkeyvalq *headers, uri = evhttp_uri_parse(str); if (!uri) goto error; - query_part = uri->query; + query_part = evhttp_uri_get_query(uri); } else { query_part = str; } @@ -3347,6 +3347,25 @@ bind_socket(const char *address, ev_uint16_t port, int reuse) return (fd); } +struct evhttp_uri { + char *scheme; /* scheme; e.g http, ftp etc */ + char *userinfo; /* userinfo (typically username:pass), or NULL */ + char *host; /* hostname, IP address, or NULL */ + int port; /* port, or zero */ + char *path; /* path, or "". */ + char *query; /* query, or NULL */ + char *fragment; /* fragment or NULL */ +}; + +struct evhttp_uri * +evhttp_uri_new(void) +{ + struct evhttp_uri *uri = mm_calloc(sizeof(struct evhttp_uri), 1); + if (uri) + uri->port = -1; + return uri; +} + /* Return true of the string starting at s and ending immediately before eos * is a valid URI scheme according to RFC3986 */ @@ -3689,7 +3708,6 @@ evhttp_uri_free(struct evhttp_uri *uri) _URI_FREE_STR(fragment); mm_free(uri); - #undef _URI_FREE_STR } @@ -3756,3 +3774,121 @@ evhttp_uri_join(struct evhttp_uri *uri, char *buf, size_t limit) return output; #undef _URI_ADD } + +const char * +evhttp_uri_get_scheme(const struct evhttp_uri *uri) +{ + return uri->scheme; +} +const char * +evhttp_uri_get_userinfo(const struct evhttp_uri *uri) +{ + return uri->userinfo; +} +const char * +evhttp_uri_get_host(const struct evhttp_uri *uri) +{ + return uri->host; +} +int +evhttp_uri_get_port(const struct evhttp_uri *uri) +{ + return uri->port; +} +const char * +evhttp_uri_get_path(const struct evhttp_uri *uri) +{ + return uri->path; +} +const char * +evhttp_uri_get_query(const struct evhttp_uri *uri) +{ + return uri->query; +} +const char * +evhttp_uri_get_fragment(const struct evhttp_uri *uri) +{ + return uri->fragment; +} + +#define _URI_SET_STR(f) do { \ + if (uri->f) \ + mm_free(uri->f); \ + if (f) { \ + if ((uri->f = mm_strdup(f)) == NULL) { \ + event_warn("%s: strdup()", __func__); \ + return -1; \ + } \ + } else { \ + uri->f = NULL; \ + } \ + } while(0) + +int +evhttp_uri_set_scheme(struct evhttp_uri *uri, const char *scheme) +{ + if (scheme && !scheme_ok(scheme, scheme+strlen(scheme))) + return -1; + + _URI_SET_STR(scheme); + return 0; +} +int +evhttp_uri_set_userinfo(struct evhttp_uri *uri, const char *userinfo) +{ + if (userinfo && !userinfo_ok(userinfo, userinfo+strlen(userinfo))) + return -1; + _URI_SET_STR(userinfo); + return 0; +} +int +evhttp_uri_set_host(struct evhttp_uri *uri, const char *host) +{ + if (host) { + if (host[0] == '[') { + if (! bracket_addr_ok(host, host+strlen(host))) + return -1; + } else { + if (! regname_ok(host, host+strlen(host))) + return -1; + } + } + + _URI_SET_STR(host); + return 0; +} +int +evhttp_uri_set_port(struct evhttp_uri *uri, int port) +{ + if (port < -1) + return -1; + uri->port = port; + return 0; +} +#define end_of_cpath(cp,aq) ((const char*)(end_of_path(((char*)(cp)), (aq)))) + +int +evhttp_uri_set_path(struct evhttp_uri *uri, const char *path) +{ + if (path && end_of_cpath(path, 0) != path+strlen(path)) + return -1; + + _URI_SET_STR(path); + return 0; +} +int +evhttp_uri_set_query(struct evhttp_uri *uri, const char *query) +{ + if (query && end_of_cpath(query, 1) != query+strlen(query)) + return -1; + _URI_SET_STR(query); + return 0; +} +int +evhttp_uri_set_fragment(struct evhttp_uri *uri, const char *fragment) +{ + if (fragment && end_of_cpath(fragment, 1) != fragment+strlen(fragment)) + return -1; + _URI_SET_STR(fragment); + return 0; +} diff --git a/include/event2/http.h b/include/event2/http.h index d882061c73..3fdd1f9883 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -632,17 +632,70 @@ int evhttp_parse_query__checked_20(const char *uri, struct evkeyvalq *headers, char *evhttp_htmlescape(const char *html); /** - * A structure to hold a parsed URI. - */ -struct evhttp_uri { - char *scheme; /* scheme; e.g http, ftp etc */ - char *host; /* hostname, IP address, or NULL */ - char *userinfo; /* userinfo (typically username:pass), or NULL */ - int port; /* port, or zero */ - char *path; /* path, or "". */ - char *query; /* query, or NULL */ - char *fragment; /* fragment or NULL */ -}; + * A structure to hold a parsed URI or Relative-Ref conforming to RFC3986. + */ +struct evhttp_uri; + +/** + * Return a new empty evhttp_uri with no fields set. + */ +struct evhttp_uri *evhttp_uri_new(void); + +/** Return the scheme of an evhttp_uri, or NULL if there is no scheme has + * been set and the evhttp_uri contains a Relative-Ref. */ +const char *evhttp_uri_get_scheme(const struct evhttp_uri *uri); +/** + * Return the userinfo part of an evhttp_uri, or NULL if it has no userinfo + * set. + */ +const char *evhttp_uri_get_userinfo(const struct evhttp_uri *uri); +/** + * Return the host part of an evhttp_uri, or NULL if it has no host set. + * The host may either be a regular hostname (conforming to the RFC 3986 + * "regname" production), or an IPv4 address, or the empty string, or a + * bracketed IPv6 address, or a bracketed 'IP-Future' address. + * + * Note that having a NULL host means that the URI has no authority + * section, but having an empty-string host means that the URI has an + * authority section with no host part. For example, + * "mailto:user@example.com" has a host of NULL, but "file:///etc/motd" + * has a host of "". + */ +const char *evhttp_uri_get_host(const struct evhttp_uri *uri); +/** Return the port part of an evhttp_uri, or -1 if there is no port set. */ +int evhttp_uri_get_port(const struct evhttp_uri *uri); +/** Return the path part of an evhttp_uri, or NULL if it has no path set */ +const char *evhttp_uri_get_path(const struct evhttp_uri *uri); +/** Return the query part of an evhttp_uri (excluding the leading "?"), or + * NULL if it has no query set */ +const char *evhttp_uri_get_query(const struct evhttp_uri *uri); +/** Return the fragment part of an evhttp_uri (excluding the leading "#"), + * or NULL if it has no fragment set */ +const char *evhttp_uri_get_fragment(const struct evhttp_uri *uri); + +/** Set the scheme of an evhttp_uri, or clear the scheme if scheme==NULL. + * Returns 0 on success, -1 if scheme is not well-formed. */ +int evhttp_uri_set_scheme(struct evhttp_uri *uri, const char *scheme); +/** Set the userinfo of an evhttp_uri, or clear the userinfo if userinfo==NULL. + * Returns 0 on success, -1 if userinfo is not well-formed. */ +int evhttp_uri_set_userinfo(struct evhttp_uri *uri, const char *userinfo); +/** Set the host of an evhttp_uri, or clear the host if host==NULL. + * Returns 0 on success, -1 if host is not well-formed. */ +int evhttp_uri_set_host(struct evhttp_uri *uri, const char *host); +/** Set the port of an evhttp_uri, or clear the port if port==-1. + * Returns 0 on success, -1 if port is not well-formed. */ +int evhttp_uri_set_port(struct evhttp_uri *uri, int port); +/** Set the path of an evhttp_uri, or clear the path if path==NULL. + * Returns 0 on success, -1 if path is not well-formed. */ +int evhttp_uri_set_path(struct evhttp_uri *uri, const char *path); +/** Set the query of an evhttp_uri, or clear the query if query==NULL. + * The query should not include a leading "?". + * Returns 0 on success, -1 if query is not well-formed. */ +int evhttp_uri_set_query(struct evhttp_uri *uri, const char *query); +/** Set the fragment of an evhttp_uri, or clear the fragment if fragment==NULL. + * The fragment should not include a leading "#". + * Returns 0 on success, -1 if fragment is not well-formed. */ +int evhttp_uri_set_fragment(struct evhttp_uri *uri, const char *fragment); /** * Helper function to parse a URI-Reference as specified by RFC3986. diff --git a/test/regress_http.c b/test/regress_http.c index 34d2781e1e..f90e98c38e 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -1766,263 +1766,262 @@ http_parse_uri_test(void *ptr) BAD("://www.example.com/"); /* bad URIs: joining */ - uri = calloc(sizeof(struct evhttp_uri),1); - uri->host = (char*)"www.example.com"; + uri = evhttp_uri_new(); + tt_want(0==evhttp_uri_set_host(uri, "www.example.com")); tt_want(evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)) != NULL); /* not enough space: */ tt_want(evhttp_uri_join(uri, url_tmp, 3) == NULL); /* host is set, but path doesn't start with "/": */ - uri->path = (char*)"hi_mom"; + tt_want(0==evhttp_uri_set_path(uri, "hi_mom")); tt_want(evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)) == NULL); tt_want(evhttp_uri_join(uri, NULL, sizeof(url_tmp))==NULL); tt_want(evhttp_uri_join(uri, url_tmp, 0)==NULL); - free(uri); - + evhttp_uri_free(uri); uri = evhttp_uri_parse("mailto:foo@bar"); tt_want(uri != NULL); - tt_want(uri->host == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(!strcmp(uri->scheme, "mailto")); - tt_want(!strcmp(uri->path, "foo@bar")); - tt_want(uri->query == NULL); - tt_want(uri->fragment == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(!strcmp(evhttp_uri_get_scheme(uri), "mailto")); + tt_want(!strcmp(evhttp_uri_get_path(uri), "foo@bar")); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("mailto:foo@bar"); evhttp_uri_free(uri); uri = evhttp_uri_parse("http://www.test.com/?q=t%33est"); - tt_want(strcmp(uri->scheme, "http") == 0); - tt_want(strcmp(uri->host, "www.test.com") == 0); - tt_want(strcmp(uri->path, "/") == 0); - tt_want(strcmp(uri->query, "q=t%33est") == 0); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=t%33est") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("http://www.test.com/?q=t%33est"); evhttp_uri_free(uri); uri = evhttp_uri_parse("http://%77ww.test.com"); - tt_want(strcmp(uri->scheme, "http") == 0); - tt_want(strcmp(uri->host, "%77ww.test.com") == 0); - tt_want(strcmp(uri->path, "") == 0); - tt_want(uri->query == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "%77ww.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("http://%77ww.test.com"); evhttp_uri_free(uri); 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->path, "") == 0); - tt_want(strcmp(uri->query, "q=test") == 0); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("http://www.test.com?q=test"); evhttp_uri_free(uri); uri = evhttp_uri_parse("http://www.test.com#fragment"); - tt_want(strcmp(uri->scheme, "http") == 0); - tt_want(strcmp(uri->host, "www.test.com") == 0); - tt_want(strcmp(uri->path, "") == 0); - tt_want(uri->query == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want_str_op(uri->fragment, ==, "fragment"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want_str_op(evhttp_uri_get_fragment(uri), ==, "fragment"); TT_URI("http://www.test.com#fragment"); evhttp_uri_free(uri); uri = evhttp_uri_parse("http://8000/"); - tt_want(strcmp(uri->scheme, "http") == 0); - tt_want(strcmp(uri->host, "8000") == 0); - tt_want(strcmp(uri->path, "/") == 0); - tt_want(uri->query == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "8000") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("http://8000/"); evhttp_uri_free(uri); uri = evhttp_uri_parse("http://:8000/"); - tt_want(strcmp(uri->scheme, "http") == 0); - tt_want(strcmp(uri->host, "") == 0); - tt_want(strcmp(uri->path, "/") == 0); - tt_want(uri->query == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == 8000); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == 8000); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("http://:8000/"); evhttp_uri_free(uri); uri = evhttp_uri_parse("http://www.test.com:/"); /* empty port */ - tt_want(strcmp(uri->scheme, "http") == 0); - tt_want(strcmp(uri->host, "www.test.com") == 0); - tt_want_str_op(uri->path, ==, "/"); - tt_want(uri->query == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want_str_op(evhttp_uri_get_path(uri), ==, "/"); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("http://www.test.com/"); evhttp_uri_free(uri); uri = evhttp_uri_parse("http://www.test.com:"); /* empty port 2 */ - tt_want(strcmp(uri->scheme, "http") == 0); - tt_want(strcmp(uri->host, "www.test.com") == 0); - tt_want(strcmp(uri->path, "") == 0); - tt_want(uri->query == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("http://www.test.com"); 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->path, "/") == 0); - tt_want(strcmp(uri->query, "q=test") == 0); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("ftp://www.test.com/?q=test"); evhttp_uri_free(uri); uri = evhttp_uri_parse("ftp://[::1]:999/?q=test"); - tt_want(strcmp(uri->scheme, "ftp") == 0); - tt_want(strcmp(uri->host, "[::1]") == 0); - tt_want(strcmp(uri->path, "/") == 0); - tt_want(strcmp(uri->query, "q=test") == 0); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == 999); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "[::1]") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == 999); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("ftp://[::1]:999/?q=test"); evhttp_uri_free(uri); uri = evhttp_uri_parse("ftp://[ff00::127.0.0.1]/?q=test"); - tt_want(strcmp(uri->scheme, "ftp") == 0); - tt_want(strcmp(uri->host, "[ff00::127.0.0.1]") == 0); - tt_want(strcmp(uri->path, "/") == 0); - tt_want(strcmp(uri->query, "q=test") == 0); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "[ff00::127.0.0.1]") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("ftp://[ff00::127.0.0.1]/?q=test"); evhttp_uri_free(uri); uri = evhttp_uri_parse("ftp://[v99.not_(any:time)_soon]/?q=test"); - tt_want(strcmp(uri->scheme, "ftp") == 0); - tt_want(strcmp(uri->host, "[v99.not_(any:time)_soon]") == 0); - tt_want(strcmp(uri->path, "/") == 0); - tt_want(strcmp(uri->query, "q=test") == 0); - tt_want(uri->userinfo == NULL); - tt_want(uri->port == -1); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "[v99.not_(any:time)_soon]") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("ftp://[v99.not_(any:time)_soon]/?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->userinfo, "user:pass") == 0); - tt_want(strcmp(uri->host, "foo.com") == 0); - tt_want(uri->port == 42); - tt_want(strcmp(uri->path, "/") == 0); - tt_want(strcmp(uri->query, "q=test&s=some+thing") == 0); - tt_want(strcmp(uri->fragment, "fragment") == 0); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "scheme") == 0); + tt_want(strcmp(evhttp_uri_get_userinfo(uri), "user:pass") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "foo.com") == 0); + tt_want(evhttp_uri_get_port(uri) == 42); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test&s=some+thing") == 0); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "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->userinfo, "user") == 0); - tt_want(strcmp(uri->host, "foo.com") == 0); - tt_want(uri->port == -1); - tt_want(strcmp(uri->path, "/") == 0); - tt_want(uri->query == NULL); - tt_want(strcmp(uri->fragment, "fragment") == 0); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "scheme") == 0); + tt_want(strcmp(evhttp_uri_get_userinfo(uri), "user") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "foo.com") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fragment") == 0); TT_URI("scheme://user@foo.com/#fragment"); evhttp_uri_free(uri); uri = evhttp_uri_parse("scheme://%75ser@foo.com/#frag@ment"); - tt_want(strcmp(uri->scheme, "scheme") == 0); - tt_want(strcmp(uri->userinfo, "%75ser") == 0); - tt_want(strcmp(uri->host, "foo.com") == 0); - tt_want(uri->port == -1); - tt_want(strcmp(uri->path, "/") == 0); - tt_want(uri->query == NULL); - tt_want(strcmp(uri->fragment, "frag@ment") == 0); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "scheme") == 0); + tt_want(strcmp(evhttp_uri_get_userinfo(uri), "%75ser") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "foo.com") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "frag@ment") == 0); TT_URI("scheme://%75ser@foo.com/#frag@ment"); evhttp_uri_free(uri); uri = evhttp_uri_parse("file:///some/path/to/the/file"); - tt_want(strcmp(uri->scheme, "file") == 0); - tt_want(uri->userinfo == NULL); - tt_want(strcmp(uri->host, "") == 0); - tt_want(uri->port == -1); - tt_want(strcmp(uri->path, "/some/path/to/the/file") == 0); - tt_want(uri->query == NULL); - tt_want(uri->fragment == NULL); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "file") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_host(uri), "") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/some/path/to/the/file") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("file:///some/path/to/the/file"); evhttp_uri_free(uri); uri = evhttp_uri_parse("///some/path/to/the-file"); tt_want(uri != NULL); - tt_want(uri->scheme == NULL); - tt_want(uri->userinfo == NULL); - tt_want(strcmp(uri->host, "") == 0); - tt_want(uri->port == -1); - tt_want(strcmp(uri->path, "/some/path/to/the-file") == 0); - tt_want(uri->query == NULL); - tt_want(uri->fragment == NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_host(uri), "") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/some/path/to/the-file") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("///some/path/to/the-file"); evhttp_uri_free(uri); uri = evhttp_uri_parse("/s:ome/path/to/the-file?q=99#fred"); tt_want(uri != NULL); - tt_want(uri->scheme == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->host == NULL); - tt_want(uri->port == -1); - tt_want(strcmp(uri->path, "/s:ome/path/to/the-file") == 0); - tt_want(strcmp(uri->query, "q=99") == 0); - tt_want(strcmp(uri->fragment, "fred") == 0); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/s:ome/path/to/the-file") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=99") == 0); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fred") == 0); TT_URI("/s:ome/path/to/the-file?q=99#fred"); evhttp_uri_free(uri); uri = evhttp_uri_parse("relative/path/with/co:lon"); tt_want(uri != NULL); - tt_want(uri->scheme == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->host == NULL); - tt_want(uri->port == -1); - tt_want(strcmp(uri->path, "relative/path/with/co:lon") == 0); - tt_want(uri->query == NULL); - tt_want(uri->fragment == NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "relative/path/with/co:lon") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); TT_URI("relative/path/with/co:lon"); evhttp_uri_free(uri); uri = evhttp_uri_parse("bob?q=99&q2=q?33#fr?ed"); tt_want(uri != NULL); - tt_want(uri->scheme == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->host == NULL); - tt_want(uri->port == -1); - tt_want(strcmp(uri->path, "bob") == 0); - tt_want(strcmp(uri->query, "q=99&q2=q?33") == 0); - tt_want(strcmp(uri->fragment, "fr?ed") == 0); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "bob") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=99&q2=q?33") == 0); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fr?ed") == 0); TT_URI("bob?q=99&q2=q?33#fr?ed"); evhttp_uri_free(uri); uri = evhttp_uri_parse("#fr?ed"); tt_want(uri != NULL); - tt_want(uri->scheme == NULL); - tt_want(uri->userinfo == NULL); - tt_want(uri->host == NULL); - tt_want(uri->port == -1); - tt_want(strcmp(uri->path, "") == 0); - tt_want(uri->query == NULL); - tt_want(strcmp(uri->fragment, "fr?ed") == 0); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fr?ed") == 0); TT_URI("#fr?ed"); evhttp_uri_free(uri); } From bc98f5e6bad6a8502895d992cc3413b118969e01 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 21 Oct 2010 14:53:21 -0400 Subject: [PATCH 11/11] Unit tests for evhttp_uri_set* --- test/regress_http.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/regress_http.c b/test/regress_http.c index f90e98c38e..95b2763c20 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -1789,6 +1789,46 @@ http_parse_uri_test(void *ptr) TT_URI("mailto:foo@bar"); evhttp_uri_free(uri); + uri = evhttp_uri_new(); + /* Bad URI usage: setting invalid values */ + tt_want(-1 == evhttp_uri_set_scheme(uri,"")); + tt_want(-1 == evhttp_uri_set_scheme(uri,"33")); + tt_want(-1 == evhttp_uri_set_scheme(uri,"hi!")); + tt_want(-1 == evhttp_uri_set_userinfo(uri,"hello@")); + tt_want(-1 == evhttp_uri_set_host(uri,"[1.2.3.4]")); + tt_want(-1 == evhttp_uri_set_host(uri,"[")); + tt_want(-1 == evhttp_uri_set_host(uri,"www.[foo].com")); + tt_want(-1 == evhttp_uri_set_port(uri,-3)); + tt_want(-1 == evhttp_uri_set_path(uri,"hello?world")); + tt_want(-1 == evhttp_uri_set_query(uri,"hello#world")); + tt_want(-1 == evhttp_uri_set_fragment(uri,"hello#world")); + /* Valid URI usage: setting valid values */ + tt_want(0 == evhttp_uri_set_scheme(uri,"http")); + tt_want(0 == evhttp_uri_set_scheme(uri,NULL)); + tt_want(0 == evhttp_uri_set_userinfo(uri,"username:pass")); + tt_want(0 == evhttp_uri_set_userinfo(uri,NULL)); + tt_want(0 == evhttp_uri_set_host(uri,"www.example.com")); + tt_want(0 == evhttp_uri_set_host(uri,"1.2.3.4")); + tt_want(0 == evhttp_uri_set_host(uri,"[1:2:3:4::]")); + tt_want(0 == evhttp_uri_set_host(uri,"[v7.wobblewobble]")); + tt_want(0 == evhttp_uri_set_host(uri,NULL)); + tt_want(0 == evhttp_uri_set_host(uri,"")); + tt_want(0 == evhttp_uri_set_port(uri, -1)); + tt_want(0 == evhttp_uri_set_port(uri, 80)); + tt_want(0 == evhttp_uri_set_port(uri, 65535)); + tt_want(0 == evhttp_uri_set_path(uri, "")); + tt_want(0 == evhttp_uri_set_path(uri, "/documents/public/index.html")); + tt_want(0 == evhttp_uri_set_path(uri, NULL)); + tt_want(0 == evhttp_uri_set_query(uri, "key=val&key2=val2")); + tt_want(0 == evhttp_uri_set_query(uri, "keyvalblarg")); + tt_want(0 == evhttp_uri_set_query(uri, "")); + tt_want(0 == evhttp_uri_set_query(uri, NULL)); + tt_want(0 == evhttp_uri_set_fragment(uri, "")); + tt_want(0 == evhttp_uri_set_fragment(uri, "here?i?am")); + tt_want(0 == evhttp_uri_set_fragment(uri, NULL)); + evhttp_uri_free(uri); + + /* Valid parsing */ uri = evhttp_uri_parse("http://www.test.com/?q=t%33est"); tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0);