]> granicus.if.org Git - libevent/commitdiff
Introduce absolute URI parsing helpers.
authorPavel Plesov <pavel.plesov@gmail.com>
Sun, 8 Aug 2010 12:46:39 +0000 (16:46 +0400)
committerNick Mathewson <nickm@torproject.org>
Mon, 18 Oct 2010 18:30:29 +0000 (14:30 -0400)
See evhttp_uri_parse(), evhttp_uri_free() and evhttp_uri_join() for details.

http.c
include/event2/http.h
include/event2/http_struct.h
test/regress_http.c

diff --git a/http.c b/http.c
index 6aca33c3f732ae7162564e2744884d989fabd8cf..37936566cfb567b1f0d2fc6fe5efd5f1b1e47dbd 100644 (file)
--- 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
+}
index d872a1897701d185121c1c9bdb7d358934733dd6..9293270d17f7fe7c0b738c52ee7d1b9de89df04d 100644 (file)
@@ -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
index a3664e0448b762e886629a4e22e0d0440de40c62..168f5aca6096c5a607d9b795a75ee222d36bffe6 100644 (file)
@@ -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
index e2fd987b9111bf5999106dced193952ed5280363..170432195e9d853504d8728267e1a0b4d4bdd066 100644 (file)
@@ -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),