diff options
-rw-r--r-- | src/include/taler_util.h | 59 | ||||
-rw-r--r-- | src/util/Makefile.am | 12 | ||||
-rw-r--r-- | src/util/util.c | 300 |
3 files changed, 369 insertions, 2 deletions
diff --git a/src/include/taler_util.h b/src/include/taler_util.h index 84d4f5dee..407521c6e 100644 --- a/src/include/taler_util.h +++ b/src/include/taler_util.h @@ -22,6 +22,7 @@ #define TALER_UTIL_H #include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> #include "taler_amount_lib.h" #include "taler_crypto_lib.h" @@ -134,4 +135,62 @@ const struct GNUNET_OS_ProjectData * TALER_project_data_default (void); +/** + * URL-encode a string according to rfc3986. + * + * @param s string to encode + * @returns the urlencoded string, the caller must free it with GNUNET_free + */ +char * +TALER_urlencode (const char *s); + + +/** + * Make an absolute URL with query parameters. + * + * @param base_url absolute base URL to use + * @param path path of the url + * @param ... NULL-terminated key-value pairs (char *) for query parameters, + * only the value will be url-encoded + * @returns the URL, must be freed with #GNUNET_free + */ +char * +TALER_url_join (const char *base_url, + const char *path, + ...); + + +/** + * Make an absolute URL for the given parameters. + * + * @param proto protocol for the URL (typically https) + * @param host hostname for the URL + * @param prefix prefix for the URL + * @param path path for the URL + * @param ... NULL-terminated key-value pairs (char *) for query parameters, + * the value will be url-encoded + * @returns the URL, must be freed with #GNUNET_free + */ +char * +TALER_url_absolute_raw (const char *proto, + const char *host, + const char *prefix, + const char *path, + ...); + + +/** + * Make an absolute URL for a given MHD connection. + * + * @param path path of the url + * @param ... NULL-terminated key-value pairs (char *) for query parameters, + * the value will be url-encoded + * @returns the URL, must be freed with #GNUNET_free + */ +char * +TALER_url_absolute_mhd (struct MHD_Connection *connection, + const char *path, + ...); + + #endif diff --git a/src/util/Makefile.am b/src/util/Makefile.am index c05f756f2..4f9a6367d 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -57,11 +57,13 @@ libtalerutil_la_LDFLAGS = \ TESTS = \ test_amount \ - test_crypto + test_crypto \ + test_url check_PROGRAMS = \ test_amount \ - test_crypto + test_crypto \ + test_url test_amount_SOURCES = \ @@ -75,3 +77,9 @@ test_crypto_SOURCES = \ test_crypto_LDADD = \ -lgnunetutil \ libtalerutil.la + +test_url_SOURCES = \ + test_url.c +test_url_LDADD = \ + -lgnunetutil \ + libtalerutil.la diff --git a/src/util/util.c b/src/util/util.c index 8976b0a3d..00b67e25e 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -159,4 +159,304 @@ TALER_getopt_get_amount (char shortName, } +/** + * Check if a character is reserved and should + * be urlencoded. + * + * @param c character to look at + * @return #GNUNET_YES if @a c needs to be urlencoded, + * #GNUNET_NO otherwise + */ +static bool +is_reserved(char c) +{ + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '-': case '.': case '_': case '~': + return GNUNET_NO; + default: + break; + } + return GNUNET_YES; +} + + +/** + * URL-encode a string according to rfc3986. + * + * @param s string to encode + * @returns the urlencoded string, the caller must free it with GNUNET_free + */ +char * +TALER_urlencode (const char *s) +{ + unsigned int new_size; + unsigned int i; + unsigned int t; + char *out; + + new_size = strlen (s); + for (i = 0; i < strlen (s); i++) + if (GNUNET_YES == is_reserved (s[i])) + new_size += 2; + out = GNUNET_malloc (new_size + 1); + for (i = 0, t = 0; i < strlen (s); i++, t++) + { + if (GNUNET_YES == is_reserved (s[i])) + { + snprintf(&out[t], 4, "%%%02X", s[i]); + t += 2; + continue; + } + out[t] = s[i]; + } + return out; +} + + +/** + * Grow a string in a buffer with the given size. + * The buffer is re-allocated if necessary. + * + * @param s string buffer + * @param p the string to append + * @param n pointer to the allocated size of n + * @returns pointer to the resulting buffer, + * might differ from @a s (!!) + */ +static char * +grow_string (char *s, const char *p, size_t *n) +{ + for (; strlen (s) + strlen (p) >= *n; *n *= 2); + s = GNUNET_realloc (s, *n); + GNUNET_assert (NULL != s); + strncat (s, p, *n); + return s; +} + + +/** + * Grow a string in a buffer with the given size. + * The buffer is re-allocated if necessary. + * + * Ensures that slashes are removed or added when joining paths. + * + * @param s string buffer + * @param p the string to append + * @param n pointer to the allocated size of n + * @returns pointer to the resulting buffer, + * might differ from @a s (!!) + */ +static char * +grow_string_path (char *s, const char *p, size_t *n) +{ + char a = (0 == strlen (s)) ? '\0' : s[strlen (s) - 1]; + char b = (0 == strlen (p)) ? '\0' : p[0]; + + if ( (a == '/') && (b == '/')) + { + p++; + } + else if ( (a != '/') && (b != '/')) + { + if (NULL == (s = grow_string (s, "/", n))) + return NULL; + } + return grow_string (s, p, n); +} + + +/** + * Make an absolute URL with query parameters. + * + * @param base_url absolute base URL to use + * @param path path of the url + * @param ... NULL-terminated key-value pairs (char *) for query parameters, + * the value will be url-encoded + * @returns the URL, must be freed with #GNUNET_free + */ +char * +TALER_url_join (const char *base_url, + const char *path, + ...) +{ + size_t n = 256; + char *res = GNUNET_malloc (n); + unsigned int iparam = 0; + char *enc; + va_list args; + + GNUNET_assert (NULL != res); + + grow_string (res, base_url, &n); + + grow_string_path (res, path, &n); + + va_start (args, path); + + while (1) { + char *key; + char *value; + key = va_arg (args, char *); + if (NULL == key) + break; + value = va_arg (args, char *); + if (NULL == value) + continue; + grow_string (res, (0 == iparam) ? "?" : "&", &n); + iparam++; + grow_string (res, key, &n); + grow_string (res, "=", &n); + enc = TALER_urlencode (value); + grow_string (res, enc, &n); + GNUNET_free (enc); + } + + va_end (args); + + return res; +} + + +/** + * Make an absolute URL for the given parameters. + * + * @param proto protocol for the URL (typically https) + * @param host hostname for the URL + * @param prefix prefix for the URL + * @param path path for the URL + * @param args NULL-terminated key-value pairs (char *) for query parameters, + * the value will be url-encoded + * @returns the URL, must be freed with #GNUNET_free + */ +char * +url_absolute_raw_va (const char *proto, + const char *host, + const char *prefix, + const char *path, + va_list args) +{ + size_t n = 256; + char *res = GNUNET_malloc (n); + char *enc; + unsigned int iparam = 0; + + grow_string (res, proto, &n); + grow_string (res, "://", &n); + grow_string (res, host, &n); + + grow_string_path (res, prefix, &n); + + grow_string_path (res, path, &n); + + while (1) { + char *key; + char *value; + key = va_arg (args, char *); + if (NULL == key) + break; + value = va_arg (args, char *); + if (NULL == value) + continue; + grow_string (res, (0 == iparam) ? "?" : "&", &n); + iparam++; + grow_string (res, key, &n); + grow_string (res, "=", &n); + enc = TALER_urlencode (value); + grow_string (res, enc, &n); + GNUNET_free (enc); + } + + return res; +} + + +/** + * Make an absolute URL for the given parameters. + * + * @param proto protocol for the URL (typically https) + * @param host hostname for the URL + * @param prefix prefix for the URL + * @param path path for the URL + * @param args NULL-terminated key-value pairs (char *) for query parameters, + * the value will be url-encoded + * @returns the URL, must be freed with #GNUNET_free + */ +char * +TALER_url_absolute_raw (const char *proto, + const char *host, + const char *prefix, + const char *path, + ...) +{ + char *result; + va_list args; + + va_start (args, path); + result = url_absolute_raw_va (proto, host, prefix, path, args); + va_end (args); + return result; +} + + +/** + * Make an absolute URL for a given MHD connection. + * + * @param path path of the url + * @param ... NULL-terminated key-value pairs (char *) for query parameters, + * the value will be url-encoded + * @returns the URL, must be freed with #GNUNET_free + */ +char * +TALER_url_absolute_mhd (struct MHD_Connection *connection, + const char *path, + ...) +{ + /* By default we assume we're running under HTTPS */ + const char *proto = "https"; + const char *forwarded_proto = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "X-Forwarded-Proto"); + const char *host; + const char *forwarded_host; + const char *prefix; + va_list args; + char *result; + + if (NULL != forwarded_proto) + proto = forwarded_proto; + + host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "Host"); + forwarded_host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "X-Forwarded-Host"); + + prefix = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "X-Forwarded-Prefix"); + if (NULL == prefix) + prefix = ""; + + if (NULL != forwarded_host) + host = forwarded_host; + + if (NULL == host) + { + /* Should never happen, at last the host header should be defined */ + GNUNET_break (0); + return NULL; + } + + va_start (args, path); + result = TALER_url_absolute_raw (proto, host, prefix, path, args); + va_end (args); + return result; +} + + /* end of util.c */ |