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 */