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 : bool
25 7765 : TALER_url_is_reserved (char c)
26 : {
27 7765 : switch (c)
28 : {
29 7493 : 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 7493 : return false;
43 272 : default:
44 272 : break;
45 : }
46 272 : 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 223 : urlencode_len (const char *s)
59 : {
60 223 : size_t len = 0;
61 5044 : for (; *s != '\0'; len++, s++)
62 4821 : if (TALER_url_is_reserved (*s))
63 137 : len += 2;
64 223 : 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 135 : buffer_write_urlencode (struct GNUNET_Buffer *buf,
76 : const char *s)
77 : {
78 : size_t ulen;
79 :
80 135 : ulen = urlencode_len (s);
81 135 : GNUNET_assert (ulen < ulen + 1);
82 135 : GNUNET_buffer_ensure_remaining (buf,
83 : ulen + 1);
84 3079 : for (size_t i = 0; i < strlen (s); i++)
85 : {
86 2944 : if (TALER_url_is_reserved (s[i]))
87 135 : GNUNET_buffer_write_fstr (buf,
88 : "%%%02X",
89 135 : (unsigned char) s[i]);
90 : else
91 2809 : buf->mem[buf->position++] = s[i];
92 : }
93 135 : }
94 :
95 :
96 : char *
97 47 : TALER_urlencode (const char *s)
98 : {
99 47 : struct GNUNET_Buffer buf = { 0 };
100 :
101 47 : buffer_write_urlencode (&buf,
102 : s);
103 47 : 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 1245 : calculate_argument_length (va_list args)
121 : {
122 1245 : size_t len = 0;
123 : va_list ap;
124 :
125 1245 : va_copy (ap,
126 : args);
127 : while (1)
128 179 : {
129 : char *key;
130 : char *value;
131 : size_t vlen;
132 : size_t klen;
133 :
134 1424 : key = va_arg (ap,
135 : char *);
136 1424 : if (NULL == key)
137 1245 : break;
138 179 : value = va_arg (ap,
139 : char *);
140 179 : if (NULL == value)
141 91 : continue;
142 88 : vlen = urlencode_len (value);
143 88 : klen = strlen (key);
144 88 : GNUNET_assert ( (len <= len + vlen) &&
145 : (len <= len + vlen + klen) &&
146 : (len < len + vlen + klen + 2) );
147 88 : len += vlen + klen + 2;
148 : }
149 1245 : va_end (ap);
150 1245 : 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 1245 : 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 1245 : unsigned int iparam = 0;
173 :
174 : while (1)
175 179 : {
176 : char *key;
177 : char *value;
178 :
179 1424 : key = va_arg (args,
180 : char *);
181 1424 : if (NULL == key)
182 1245 : break;
183 179 : value = va_arg (args,
184 : char *);
185 179 : if (NULL == value)
186 91 : continue;
187 88 : GNUNET_buffer_write_str (buf,
188 : (0 == iparam) ? "?" : "&");
189 88 : iparam = 1;
190 88 : GNUNET_buffer_write_str (buf,
191 : key);
192 88 : GNUNET_buffer_write_str (buf,
193 : "=");
194 88 : buffer_write_urlencode (buf,
195 : value);
196 : }
197 1245 : }
198 :
199 :
200 : char *
201 1244 : TALER_url_join (const char *base_url,
202 : const char *path,
203 : ...)
204 : {
205 1244 : struct GNUNET_Buffer buf = { 0 };
206 :
207 1244 : GNUNET_assert (NULL != base_url);
208 1244 : GNUNET_assert (NULL != path);
209 1244 : if (0 == strlen (base_url))
210 : {
211 : /* base URL can't be empty */
212 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
213 : "Empty base URL specified\n");
214 0 : return NULL;
215 : }
216 1244 : if ('\0' != path[0])
217 : {
218 1244 : if ('/' != base_url[strlen (base_url) - 1])
219 : {
220 : /* Must be an actual base URL! */
221 0 : 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 0 : return NULL;
226 : }
227 1244 : if ('/' == path[0])
228 : {
229 : /* The path must be relative. */
230 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
231 : "Path `%s' is not relative\n",
232 : path);
233 0 : return NULL;
234 : }
235 : }
236 :
237 : {
238 : va_list args;
239 : size_t len;
240 :
241 1244 : va_start (args,
242 : path);
243 1244 : len = strlen (base_url) + strlen (path) + 1;
244 1244 : len += calculate_argument_length (args);
245 1244 : GNUNET_buffer_prealloc (&buf,
246 : len);
247 1244 : GNUNET_buffer_write_str (&buf,
248 : base_url);
249 1244 : GNUNET_buffer_write_str (&buf,
250 : path);
251 1244 : serialize_arguments (&buf,
252 : args);
253 1244 : va_end (args);
254 : }
255 1244 : return GNUNET_buffer_reap_str (&buf);
256 : }
257 :
258 :
259 : char *
260 1 : 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 1 : struct GNUNET_Buffer buf = { 0 };
267 1 : size_t len = 0;
268 :
269 1 : len += strlen (proto) + strlen ("://") + strlen (host);
270 1 : len += strlen (prefix) + strlen (path);
271 1 : len += calculate_argument_length (args) + 1; /* 0-terminator */
272 :
273 1 : GNUNET_buffer_prealloc (&buf,
274 : len);
275 1 : GNUNET_buffer_write_str (&buf,
276 : proto);
277 1 : GNUNET_buffer_write_str (&buf,
278 : "://");
279 1 : GNUNET_buffer_write_str (&buf,
280 : host);
281 1 : GNUNET_buffer_write_path (&buf,
282 : prefix);
283 1 : GNUNET_buffer_write_path (&buf,
284 : path);
285 1 : serialize_arguments (&buf,
286 : args);
287 1 : return GNUNET_buffer_reap_str (&buf);
288 : }
289 :
290 :
291 : char *
292 1 : 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 1 : va_start (args,
302 : path);
303 1 : result = TALER_url_absolute_raw_va (proto,
304 : host,
305 : prefix,
306 : path,
307 : args);
308 1 : va_end (args);
309 1 : return result;
310 : }
311 :
312 :
313 : bool
314 574 : TALER_url_valid_charset (const char *url)
315 : {
316 20176 : for (unsigned int i = 0; '\0' != url[i]; i++)
317 : {
318 : #define ALLOWED_CHARACTERS \
319 : "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:;&?-.,=_~%+#"
320 19602 : if (NULL == strchr (ALLOWED_CHARACTERS,
321 19602 : (int) url[i]))
322 0 : return false;
323 : #undef ALLOWED_CHARACTERS
324 : }
325 574 : return true;
326 : }
327 :
328 :
329 : bool
330 280 : TALER_is_web_url (const char *url)
331 : {
332 280 : if ( (0 != strncasecmp (url,
333 : "https://",
334 280 : strlen ("https://"))) &&
335 280 : (0 != strncasecmp (url,
336 : "http://",
337 : strlen ("http://"))) )
338 0 : return false;
339 280 : if (! TALER_url_valid_charset (url) )
340 0 : return false;
341 280 : return true;
342 : }
343 :
344 :
345 : /* end of url.c */
|