aboutsummaryrefslogtreecommitdiff
path: root/src/util/payto.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/payto.c')
-rw-r--r--src/util/payto.c398
1 files changed, 398 insertions, 0 deletions
diff --git a/src/util/payto.c b/src/util/payto.c
index 54072d667..7819184e8 100644
--- a/src/util/payto.c
+++ b/src/util/payto.c
@@ -128,3 +128,401 @@ TALER_xtalerbank_account_from_payto (const char *payto)
128 return GNUNET_strndup (beg, 128 return GNUNET_strndup (beg,
129 end - beg); 129 end - beg);
130} 130}
131
132
133/* Country table taken from GNU gettext */
134
135/**
136 * Entry in the country table.
137 */
138struct CountryTableEntry
139{
140 /**
141 * 2-Character international country code.
142 */
143 const char *code;
144
145 /**
146 * Long English name of the country.
147 */
148 const char *english;
149};
150
151
152/* Keep the following table in sync with gettext.
153 WARNING: the entries should stay sorted according to the code */
154/**
155 * List of country codes.
156 */
157static const struct CountryTableEntry country_table[] = {
158 { "AE", "U.A.E." },
159 { "AF", "Afghanistan" },
160 { "AL", "Albania" },
161 { "AM", "Armenia" },
162 { "AN", "Netherlands Antilles" },
163 { "AR", "Argentina" },
164 { "AT", "Austria" },
165 { "AU", "Australia" },
166 { "AZ", "Azerbaijan" },
167 { "BA", "Bosnia and Herzegovina" },
168 { "BD", "Bangladesh" },
169 { "BE", "Belgium" },
170 { "BG", "Bulgaria" },
171 { "BH", "Bahrain" },
172 { "BN", "Brunei Darussalam" },
173 { "BO", "Bolivia" },
174 { "BR", "Brazil" },
175 { "BT", "Bhutan" },
176 { "BY", "Belarus" },
177 { "BZ", "Belize" },
178 { "CA", "Canada" },
179 { "CG", "Congo" },
180 { "CH", "Switzerland" },
181 { "CI", "Cote d'Ivoire" },
182 { "CL", "Chile" },
183 { "CM", "Cameroon" },
184 { "CN", "People's Republic of China" },
185 { "CO", "Colombia" },
186 { "CR", "Costa Rica" },
187 { "CS", "Serbia and Montenegro" },
188 { "CZ", "Czech Republic" },
189 { "DE", "Germany" },
190 { "DK", "Denmark" },
191 { "DO", "Dominican Republic" },
192 { "DZ", "Algeria" },
193 { "EC", "Ecuador" },
194 { "EE", "Estonia" },
195 { "EG", "Egypt" },
196 { "ER", "Eritrea" },
197 { "ES", "Spain" },
198 { "ET", "Ethiopia" },
199 { "FI", "Finland" },
200 { "FO", "Faroe Islands" },
201 { "FR", "France" },
202 { "GB", "United Kingdom" },
203 { "GD", "Caribbean" },
204 { "GE", "Georgia" },
205 { "GL", "Greenland" },
206 { "GR", "Greece" },
207 { "GT", "Guatemala" },
208 { "HK", "Hong Kong" },
209 { "HK", "Hong Kong S.A.R." },
210 { "HN", "Honduras" },
211 { "HR", "Croatia" },
212 { "HT", "Haiti" },
213 { "HU", "Hungary" },
214 { "ID", "Indonesia" },
215 { "IE", "Ireland" },
216 { "IL", "Israel" },
217 { "IN", "India" },
218 { "IQ", "Iraq" },
219 { "IR", "Iran" },
220 { "IS", "Iceland" },
221 { "IT", "Italy" },
222 { "JM", "Jamaica" },
223 { "JO", "Jordan" },
224 { "JP", "Japan" },
225 { "KE", "Kenya" },
226 { "KG", "Kyrgyzstan" },
227 { "KH", "Cambodia" },
228 { "KR", "South Korea" },
229 { "KW", "Kuwait" },
230 { "KZ", "Kazakhstan" },
231 { "LA", "Laos" },
232 { "LB", "Lebanon" },
233 { "LI", "Liechtenstein" },
234 { "LK", "Sri Lanka" },
235 { "LT", "Lithuania" },
236 { "LU", "Luxembourg" },
237 { "LV", "Latvia" },
238 { "LY", "Libya" },
239 { "MA", "Morocco" },
240 { "MC", "Principality of Monaco" },
241 { "MD", "Moldava" },
242 { "MD", "Moldova" },
243 { "ME", "Montenegro" },
244 { "MK", "Former Yugoslav Republic of Macedonia" },
245 { "ML", "Mali" },
246 { "MM", "Myanmar" },
247 { "MN", "Mongolia" },
248 { "MO", "Macau S.A.R." },
249 { "MT", "Malta" },
250 { "MV", "Maldives" },
251 { "MX", "Mexico" },
252 { "MY", "Malaysia" },
253 { "NG", "Nigeria" },
254 { "NI", "Nicaragua" },
255 { "NL", "Netherlands" },
256 { "NO", "Norway" },
257 { "NP", "Nepal" },
258 { "NZ", "New Zealand" },
259 { "OM", "Oman" },
260 { "PA", "Panama" },
261 { "PE", "Peru" },
262 { "PH", "Philippines" },
263 { "PK", "Islamic Republic of Pakistan" },
264 { "PL", "Poland" },
265 { "PR", "Puerto Rico" },
266 { "PT", "Portugal" },
267 { "PY", "Paraguay" },
268 { "QA", "Qatar" },
269 { "RE", "Reunion" },
270 { "RO", "Romania" },
271 { "RS", "Serbia" },
272 { "RU", "Russia" },
273 { "RW", "Rwanda" },
274 { "SA", "Saudi Arabia" },
275 { "SE", "Sweden" },
276 { "SG", "Singapore" },
277 { "SI", "Slovenia" },
278 { "SK", "Slovak" },
279 { "SN", "Senegal" },
280 { "SO", "Somalia" },
281 { "SR", "Suriname" },
282 { "SV", "El Salvador" },
283 { "SY", "Syria" },
284 { "TH", "Thailand" },
285 { "TJ", "Tajikistan" },
286 { "TM", "Turkmenistan" },
287 { "TN", "Tunisia" },
288 { "TR", "Turkey" },
289 { "TT", "Trinidad and Tobago" },
290 { "TW", "Taiwan" },
291 { "TZ", "Tanzania" },
292 { "UA", "Ukraine" },
293 { "US", "United States" },
294 { "UY", "Uruguay" },
295 { "VA", "Vatican" },
296 { "VE", "Venezuela" },
297 { "VN", "Viet Nam" },
298 { "YE", "Yemen" },
299 { "ZA", "South Africa" },
300 { "ZW", "Zimbabwe" }
301};
302
303
304/**
305 * Country code comparator function, for binary search with bsearch().
306 *
307 * @param ptr1 pointer to a `struct table_entry`
308 * @param ptr2 pointer to a `struct table_entry`
309 * @return result of memcmp()'ing the 2-digit country codes of the entries
310 */
311static int
312cmp_country_code (const void *ptr1,
313 const void *ptr2)
314{
315 const struct CountryTableEntry *cc1 = ptr1;
316 const struct CountryTableEntry *cc2 = ptr2;
317
318 return memcmp (cc1->code,
319 cc2->code,
320 2);
321}
322
323
324/**
325 * Validates given IBAN according to the European Banking Standards. See:
326 * http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf
327 *
328 * @param iban the IBAN number to validate
329 * @return NULL if correctly formatted; error message if not
330 */
331static char *
332validate_iban (const char *iban)
333{
334 char cc[2];
335 char ibancpy[35];
336 struct CountryTableEntry cc_entry;
337 unsigned int len;
338 char *nbuf;
339 unsigned long long dividend;
340 unsigned long long remainder;
341 unsigned int i;
342 unsigned int j;
343
344 len = strlen (iban);
345 if (len > 34)
346 return GNUNET_strdup ("IBAN number too long to be valid");
347 memcpy (cc, iban, 2);
348 memcpy (ibancpy, iban + 4, len - 4);
349 memcpy (ibancpy + len - 4, iban, 4);
350 ibancpy[len] = '\0';
351 cc_entry.code = cc;
352 cc_entry.english = NULL;
353 if (NULL ==
354 bsearch (&cc_entry,
355 country_table,
356 sizeof (country_table) / sizeof (struct CountryTableEntry),
357 sizeof (struct CountryTableEntry),
358 &cmp_country_code))
359 {
360 char *msg;
361
362 GNUNET_asprintf (&msg,
363 "Country code `%c%c' not supported\n",
364 cc[0],
365 cc[1]);
366 return msg;
367 }
368 nbuf = GNUNET_malloc ((len * 2) + 1);
369 for (i = 0, j = 0; i < len; i++)
370 {
371 if (isalpha ((unsigned char) ibancpy[i]))
372 {
373 if (2 != snprintf (&nbuf[j],
374 3,
375 "%2u",
376 (ibancpy[i] - 'A' + 10)))
377 {
378 GNUNET_break (0);
379 return GNUNET_strdup ("internal invariant violation");
380 }
381 j += 2;
382 continue;
383 }
384 nbuf[j] = ibancpy[i];
385 j++;
386 }
387 for (j = 0; '\0' != nbuf[j]; j++)
388 {
389 if (! isdigit ( (unsigned char) nbuf[j]))
390 {
391 char *msg;
392
393 GNUNET_asprintf (&msg,
394 "digit expected at `%s'",
395 &nbuf[j]);
396 GNUNET_free (nbuf);
397 return msg;
398 }
399 }
400 GNUNET_assert (sizeof(dividend) >= 8);
401 remainder = 0;
402 for (unsigned int i = 0; i<j; i += 16)
403 {
404 int nread;
405
406 if (1 !=
407 sscanf (&nbuf[i],
408 "%16llu %n",
409 &dividend,
410 &nread))
411 {
412 char *msg;
413
414 GNUNET_asprintf (&msg,
415 "wrong input for checksum calculation at `%s'",
416 &nbuf[i]);
417 GNUNET_free (nbuf);
418 return msg;
419 }
420 if (0 != remainder)
421 dividend += remainder * (pow (10, nread));
422 remainder = dividend % 97;
423 }
424 GNUNET_free (nbuf);
425 if (1 != remainder)
426 return GNUNET_strdup ("IBAN checksum is wrong");
427 return NULL;
428}
429
430
431/**
432 * Validate payto://iban/ account URL (only account information,
433 * wire subject and amount are ignored).
434 *
435 * @param account_url URL to parse
436 * @return NULL on success, otherwise an error message
437 * to be freed by the caller
438 */
439static char *
440validate_payto_iban (const char *account_url)
441{
442 const char *iban;
443 const char *q;
444 char *result;
445 char *err;
446
447#define IBAN_PREFIX "payto://iban/"
448 if (0 != strncasecmp (account_url,
449 IBAN_PREFIX,
450 strlen (IBAN_PREFIX)))
451 return NULL; /* not an IBAN */
452
453 iban = strrchr (account_url, '/') + 1;
454#undef IBAN_PREFIX
455 q = strchr (iban,
456 '?');
457 if (NULL != q)
458 {
459 result = GNUNET_strndup (iban,
460 q - iban);
461 }
462 else
463 {
464 result = GNUNET_strdup (iban);
465 }
466 if (NULL !=
467 (err = validate_iban (result)))
468 {
469 GNUNET_free (result);
470 return err;
471 }
472 GNUNET_free (result);
473 return NULL;
474}
475
476
477/**
478 * Check that a payto:// URI is well-formed.
479 *
480 * @param payto_uri the URL to check
481 * @return NULL on success, otherwise an error
482 * message to be freed by the caller!
483 */
484char *
485TALER_payto_validate (const char *payto_uri)
486{
487 char *ret;
488 const char *start;
489 const char *end;
490
491 if (0 != strncasecmp (payto_uri,
492 PAYTO,
493 strlen (PAYTO)))
494 return GNUNET_strdup ("invalid prefix");
495 for (unsigned int i = 0; '\0' != payto_uri[i]; i++)
496 {
497 /* This is more strict than RFC 8905, alas we do not need to support messages/instructions/etc.,
498 and it is generally better to start with a narrow whitelist; we can be more permissive later ...*/
499#define ALLOWED_CHARACTERS \
500 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:&?-.,="
501 if (NULL == strchr (ALLOWED_CHARACTERS,
502 (int) payto_uri[i]))
503 {
504 char *ret;
505
506 GNUNET_asprintf (&ret,
507 "Encountered invalid character `%c' at offset %u in payto URI `%s'",
508 payto_uri[i],
509 i,
510 payto_uri);
511 return ret;
512 }
513#undef ALLOWED_CHARACTERS
514 }
515
516 start = &payto_uri[strlen (PAYTO)];
517 end = strchr (start,
518 (unsigned char) '/');
519 if (NULL == end)
520 return GNUNET_strdup ("missing '/' in payload");
521
522 if (NULL != (ret = validate_payto_iban (payto_uri)))
523 return ret; /* got a definitive answer */
524
525 /* Insert other bank account validation methods here later! */
526
527 return NULL;
528}