Line data Source code
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 7485 : is_reserved (char c)
34 : {
35 7485 : switch (c)
36 : {
37 7213 : 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 7213 : return false;
51 272 : default:
52 272 : break;
53 : }
54 272 : 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 203 : urlencode_len (const char *s)
67 : {
68 203 : size_t len = 0;
69 4884 : for (; *s != '\0'; len++, s++)
70 4681 : if (is_reserved (*s))
71 137 : len += 2;
72 203 : 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 115 : buffer_write_urlencode (struct GNUNET_Buffer *buf,
84 : const char *s)
85 : {
86 : size_t ulen;
87 :
88 115 : ulen = urlencode_len (s);
89 115 : GNUNET_assert (ulen < ulen + 1);
90 115 : GNUNET_buffer_ensure_remaining (buf,
91 : ulen + 1);
92 2919 : for (size_t i = 0; i < strlen (s); i++)
93 : {
94 2804 : if (GNUNET_YES == is_reserved (s[i]))
95 135 : GNUNET_buffer_write_fstr (buf,
96 : "%%%02X",
97 135 : s[i]);
98 : else
99 2669 : buf->mem[buf->position++] = s[i];
100 : }
101 115 : }
102 :
103 :
104 : char *
105 27 : TALER_urlencode (const char *s)
106 : {
107 27 : struct GNUNET_Buffer buf = { 0 };
108 :
109 27 : buffer_write_urlencode (&buf,
110 : s);
111 27 : 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 1242 : calculate_argument_length (va_list args)
129 : {
130 1242 : size_t len = 0;
131 : va_list ap;
132 :
133 1242 : va_copy (ap,
134 : args);
135 : while (1)
136 179 : {
137 : char *key;
138 : char *value;
139 : size_t vlen;
140 : size_t klen;
141 :
142 1421 : key = va_arg (ap,
143 : char *);
144 1421 : if (NULL == key)
145 1242 : break;
146 179 : value = va_arg (ap,
147 : char *);
148 179 : if (NULL == value)
149 91 : continue;
150 88 : vlen = urlencode_len (value);
151 88 : klen = strlen (key);
152 88 : GNUNET_assert ( (len <= len + vlen) &&
153 : (len <= len + vlen + klen) &&
154 : (len < len + vlen + klen + 2) );
155 88 : len += vlen + klen + 2;
156 : }
157 1242 : va_end (ap);
158 1242 : 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 1242 : 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 1242 : unsigned int iparam = 0;
181 :
182 : while (1)
183 179 : {
184 : char *key;
185 : char *value;
186 :
187 1421 : key = va_arg (args,
188 : char *);
189 1421 : if (NULL == key)
190 1242 : break;
191 179 : value = va_arg (args,
192 : char *);
193 179 : if (NULL == value)
194 91 : continue;
195 88 : GNUNET_buffer_write_str (buf,
196 : (0 == iparam) ? "?" : "&");
197 88 : iparam = 1;
198 88 : GNUNET_buffer_write_str (buf,
199 : key);
200 88 : GNUNET_buffer_write_str (buf,
201 : "=");
202 88 : buffer_write_urlencode (buf,
203 : value);
204 : }
205 1242 : }
206 :
207 :
208 : char *
209 1241 : TALER_url_join (const char *base_url,
210 : const char *path,
211 : ...)
212 : {
213 1241 : struct GNUNET_Buffer buf = { 0 };
214 :
215 1241 : GNUNET_assert (NULL != base_url);
216 1241 : GNUNET_assert (NULL != path);
217 1241 : if (0 == strlen (base_url))
218 : {
219 : /* base URL can't be empty */
220 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
221 : "Empty base URL specified\n");
222 0 : return NULL;
223 : }
224 1241 : if ('\0' != path[0])
225 : {
226 1241 : if ('/' != base_url[strlen (base_url) - 1])
227 : {
228 : /* Must be an actual base URL! */
229 0 : 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 0 : return NULL;
234 : }
235 1241 : if ('/' == path[0])
236 : {
237 : /* The path must be relative. */
238 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
239 : "Path `%s' is not relative\n",
240 : path);
241 0 : return NULL;
242 : }
243 : }
244 :
245 : {
246 : va_list args;
247 : size_t len;
248 :
249 1241 : va_start (args,
250 : path);
251 1241 : len = strlen (base_url) + strlen (path) + 1;
252 1241 : len += calculate_argument_length (args);
253 1241 : GNUNET_buffer_prealloc (&buf,
254 : len);
255 1241 : GNUNET_buffer_write_str (&buf,
256 : base_url);
257 1241 : GNUNET_buffer_write_str (&buf,
258 : path);
259 1241 : serialize_arguments (&buf,
260 : args);
261 1241 : va_end (args);
262 : }
263 1241 : return GNUNET_buffer_reap_str (&buf);
264 : }
265 :
266 :
267 : char *
268 1 : 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 1 : struct GNUNET_Buffer buf = { 0 };
275 1 : size_t len = 0;
276 :
277 1 : len += strlen (proto) + strlen ("://") + strlen (host);
278 1 : len += strlen (prefix) + strlen (path);
279 1 : len += calculate_argument_length (args) + 1; /* 0-terminator */
280 :
281 1 : GNUNET_buffer_prealloc (&buf,
282 : len);
283 1 : GNUNET_buffer_write_str (&buf,
284 : proto);
285 1 : GNUNET_buffer_write_str (&buf,
286 : "://");
287 1 : GNUNET_buffer_write_str (&buf,
288 : host);
289 1 : GNUNET_buffer_write_path (&buf,
290 : prefix);
291 1 : GNUNET_buffer_write_path (&buf,
292 : path);
293 1 : serialize_arguments (&buf,
294 : args);
295 1 : return GNUNET_buffer_reap_str (&buf);
296 : }
297 :
298 :
299 : char *
300 1 : 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 1 : va_start (args,
310 : path);
311 1 : result = TALER_url_absolute_raw_va (proto,
312 : host,
313 : prefix,
314 : path,
315 : args);
316 1 : va_end (args);
317 1 : return result;
318 : }
319 :
320 :
321 : bool
322 505 : TALER_url_valid_charset (const char *url)
323 : {
324 16493 : for (unsigned int i = 0; '\0' != url[i]; i++)
325 : {
326 : #define ALLOWED_CHARACTERS \
327 : "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:;&?-.,=_~%+#"
328 15988 : if (NULL == strchr (ALLOWED_CHARACTERS,
329 15988 : (int) url[i]))
330 0 : return false;
331 : #undef ALLOWED_CHARACTERS
332 : }
333 505 : return true;
334 : }
335 :
336 :
337 : bool
338 211 : TALER_is_web_url (const char *url)
339 : {
340 211 : if ( (0 != strncasecmp (url,
341 : "https://",
342 211 : strlen ("https://"))) &&
343 211 : (0 != strncasecmp (url,
344 : "http://",
345 : strlen ("http://"))) )
346 0 : return false;
347 211 : if (! TALER_url_valid_charset (url) )
348 0 : return false;
349 211 : return true;
350 : }
351 :
352 :
353 : /* end of url.c */
|