exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

url.c (9182B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2020 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file url.c
     18  * @brief URL handling utility functions
     19  * @author Florian Dold
     20  */
     21 #include "taler/taler_util.h"
     22 
     23 
     24 /**
     25  * Check if a character is reserved and should
     26  * be urlencoded.
     27  *
     28  * @param c character to look at
     29  * @return true if @a c needs to be urlencoded,
     30  *         false otherwise
     31  */
     32 static bool
     33 is_reserved (char c)
     34 {
     35   switch (c)
     36   {
     37   case '0': case '1': case '2': case '3': case '4':
     38   case '5': case '6': case '7': case '8': case '9':
     39   case 'a': case 'b': case 'c': case 'd': case 'e':
     40   case 'f': case 'g': case 'h': case 'i': case 'j':
     41   case 'k': case 'l': case 'm': case 'n': case 'o':
     42   case 'p': case 'q': case 'r': case 's': case 't':
     43   case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
     44   case 'A': case 'B': case 'C': case 'D': case 'E':
     45   case 'F': case 'G': case 'H': case 'I': case 'J':
     46   case 'K': case 'L': case 'M': case 'N': case 'O':
     47   case 'P': case 'Q': case 'R': case 'S': case 'T':
     48   case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
     49   case '-': case '.': case '_': case '~':
     50     return false;
     51   default:
     52     break;
     53   }
     54   return true;
     55 }
     56 
     57 
     58 /**
     59  * Get the length of a string after it has been
     60  * urlencoded.
     61  *
     62  * @param s the string
     63  * @returns the size of the urlencoded @a s
     64  */
     65 static size_t
     66 urlencode_len (const char *s)
     67 {
     68   size_t len = 0;
     69   for (; *s != '\0'; len++, s++)
     70     if (is_reserved (*s))
     71       len += 2;
     72   return len;
     73 }
     74 
     75 
     76 /**
     77  * URL-encode a string according to rfc3986.
     78  *
     79  * @param buf buffer to write the result to
     80  * @param s string to encode
     81  */
     82 static void
     83 buffer_write_urlencode (struct GNUNET_Buffer *buf,
     84                         const char *s)
     85 {
     86   size_t ulen;
     87 
     88   ulen = urlencode_len (s);
     89   GNUNET_assert (ulen < ulen + 1);
     90   GNUNET_buffer_ensure_remaining (buf,
     91                                   ulen + 1);
     92   for (size_t i = 0; i < strlen (s); i++)
     93   {
     94     if (GNUNET_YES == is_reserved (s[i]))
     95       GNUNET_buffer_write_fstr (buf,
     96                                 "%%%02X",
     97                                 s[i]);
     98     else
     99       buf->mem[buf->position++] = s[i];
    100   }
    101 }
    102 
    103 
    104 char *
    105 TALER_urlencode (const char *s)
    106 {
    107   struct GNUNET_Buffer buf = { 0 };
    108 
    109   buffer_write_urlencode (&buf,
    110                           s);
    111   return GNUNET_buffer_reap_str (&buf);
    112 }
    113 
    114 
    115 /**
    116  * Compute the total length of the @a args given. The args are a
    117  * NULL-terminated list of key-value pairs, where the values
    118  * must be URL-encoded.  When serializing, the pairs will be separated
    119  * via '?' or '&' and an '=' between key and value. Hence each
    120  * pair takes an extra 2 characters to encode.  This function computes
    121  * how many bytes are needed.  It must match the #serialize_arguments()
    122  * function.
    123  *
    124  * @param args NULL-terminated key-value pairs (char *) for query parameters
    125  * @return number of bytes needed (excluding 0-terminator) for the string buffer
    126  */
    127 static size_t
    128 calculate_argument_length (va_list args)
    129 {
    130   size_t len = 0;
    131   va_list ap;
    132 
    133   va_copy (ap,
    134            args);
    135   while (1)
    136   {
    137     char *key;
    138     char *value;
    139     size_t vlen;
    140     size_t klen;
    141 
    142     key = va_arg (ap,
    143                   char *);
    144     if (NULL == key)
    145       break;
    146     value = va_arg (ap,
    147                     char *);
    148     if (NULL == value)
    149       continue;
    150     vlen = urlencode_len (value);
    151     klen = strlen (key);
    152     GNUNET_assert ( (len <= len + vlen) &&
    153                     (len <= len + vlen + klen) &&
    154                     (len < len + vlen + klen + 2) );
    155     len += vlen + klen + 2;
    156   }
    157   va_end (ap);
    158   return len;
    159 }
    160 
    161 
    162 /**
    163  * Take the key-value pairs in @a args and serialize them into
    164  * @a buf, using URL encoding for the values.  If a 'value' is
    165  * given as NULL, both the key and the value are skipped. Note
    166  * that a NULL value does not terminate the list, only a NULL
    167  * key signals the end of the list of arguments.
    168  *
    169  * @param buf where to write the values
    170  * @param args NULL-terminated key-value pairs (char *) for query parameters,
    171  *        the value will be url-encoded
    172  */
    173 static void
    174 serialize_arguments (struct GNUNET_Buffer *buf,
    175                      va_list args)
    176 {
    177   /* used to indicate if we are processing the initial
    178      parameter which starts with '?' or subsequent
    179      parameters which are separated with '&' */
    180   unsigned int iparam = 0;
    181 
    182   while (1)
    183   {
    184     char *key;
    185     char *value;
    186 
    187     key = va_arg (args,
    188                   char *);
    189     if (NULL == key)
    190       break;
    191     value = va_arg (args,
    192                     char *);
    193     if (NULL == value)
    194       continue;
    195     GNUNET_buffer_write_str (buf,
    196                              (0 == iparam) ? "?" : "&");
    197     iparam = 1;
    198     GNUNET_buffer_write_str (buf,
    199                              key);
    200     GNUNET_buffer_write_str (buf,
    201                              "=");
    202     buffer_write_urlencode (buf,
    203                             value);
    204   }
    205 }
    206 
    207 
    208 char *
    209 TALER_url_join (const char *base_url,
    210                 const char *path,
    211                 ...)
    212 {
    213   struct GNUNET_Buffer buf = { 0 };
    214 
    215   GNUNET_assert (NULL != base_url);
    216   GNUNET_assert (NULL != path);
    217   if (0 == strlen (base_url))
    218   {
    219     /* base URL can't be empty */
    220     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    221                 "Empty base URL specified\n");
    222     return NULL;
    223   }
    224   if ('\0' != path[0])
    225   {
    226     if ('/' != base_url[strlen (base_url) - 1])
    227     {
    228       /* Must be an actual base URL! */
    229       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    230                   "Base URL `%s' does not end with '/', cannot join with `%s'\n",
    231                   base_url,
    232                   path);
    233       return NULL;
    234     }
    235     if ('/' == path[0])
    236     {
    237       /* The path must be relative. */
    238       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    239                   "Path `%s' is not relative\n",
    240                   path);
    241       return NULL;
    242     }
    243   }
    244 
    245   {
    246     va_list args;
    247     size_t len;
    248 
    249     va_start (args,
    250               path);
    251     len = strlen (base_url) + strlen (path) + 1;
    252     len += calculate_argument_length (args);
    253     GNUNET_buffer_prealloc (&buf,
    254                             len);
    255     GNUNET_buffer_write_str (&buf,
    256                              base_url);
    257     GNUNET_buffer_write_str (&buf,
    258                              path);
    259     serialize_arguments (&buf,
    260                          args);
    261     va_end (args);
    262   }
    263   return GNUNET_buffer_reap_str (&buf);
    264 }
    265 
    266 
    267 char *
    268 TALER_url_absolute_raw_va (const char *proto,
    269                            const char *host,
    270                            const char *prefix,
    271                            const char *path,
    272                            va_list args)
    273 {
    274   struct GNUNET_Buffer buf = { 0 };
    275   size_t len = 0;
    276 
    277   len += strlen (proto) + strlen ("://") + strlen (host);
    278   len += strlen (prefix) + strlen (path);
    279   len += calculate_argument_length (args) + 1; /* 0-terminator */
    280 
    281   GNUNET_buffer_prealloc (&buf,
    282                           len);
    283   GNUNET_buffer_write_str (&buf,
    284                            proto);
    285   GNUNET_buffer_write_str (&buf,
    286                            "://");
    287   GNUNET_buffer_write_str (&buf,
    288                            host);
    289   GNUNET_buffer_write_path (&buf,
    290                             prefix);
    291   GNUNET_buffer_write_path (&buf,
    292                             path);
    293   serialize_arguments (&buf,
    294                        args);
    295   return GNUNET_buffer_reap_str (&buf);
    296 }
    297 
    298 
    299 char *
    300 TALER_url_absolute_raw (const char *proto,
    301                         const char *host,
    302                         const char *prefix,
    303                         const char *path,
    304                         ...)
    305 {
    306   char *result;
    307   va_list args;
    308 
    309   va_start (args,
    310             path);
    311   result = TALER_url_absolute_raw_va (proto,
    312                                       host,
    313                                       prefix,
    314                                       path,
    315                                       args);
    316   va_end (args);
    317   return result;
    318 }
    319 
    320 
    321 bool
    322 TALER_url_valid_charset (const char *url)
    323 {
    324   for (unsigned int i = 0; '\0' != url[i]; i++)
    325   {
    326 #define ALLOWED_CHARACTERS \
    327         "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:;&?-.,=_~%+#"
    328     if (NULL == strchr (ALLOWED_CHARACTERS,
    329                         (int) url[i]))
    330       return false;
    331 #undef ALLOWED_CHARACTERS
    332   }
    333   return true;
    334 }
    335 
    336 
    337 bool
    338 TALER_is_web_url (const char *url)
    339 {
    340   if ( (0 != strncasecmp (url,
    341                           "https://",
    342                           strlen ("https://"))) &&
    343        (0 != strncasecmp (url,
    344                           "http://",
    345                           strlen ("http://"))) )
    346     return false;
    347   if (! TALER_url_valid_charset (url) )
    348     return false;
    349   return true;
    350 }
    351 
    352 
    353 /* end of url.c */