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