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