LCOV - code coverage report
Current view: top level - util - validators.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 85.3 % 68 58
Test Date: 2026-09-11 17:45:24 Functions: 100.0 % 3 3

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2025 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Lesser 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 src/util/validators.c
      18              :  * @brief Input validators
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "platform.h"
      22              : #include <gnunet/gnunet_util_lib.h>
      23              : #include <gnunet/gnunet_db_lib.h>
      24              : #include <taler/taler_json_lib.h>
      25              : #include "taler/taler_merchant_util.h"
      26              : #include <regex.h>
      27              : 
      28              : bool
      29          119 : TALER_MERCHANT_image_data_url_valid (const char *image_data_url)
      30              : {
      31          119 :   if (0 == strcmp (image_data_url,
      32              :                    ""))
      33           65 :     return true;
      34           54 :   if (0 != strncasecmp ("data:image/",
      35              :                         image_data_url,
      36              :                         strlen ("data:image/")))
      37              :   {
      38            0 :     GNUNET_break_op (0);
      39            0 :     return false;
      40              :   }
      41           54 :   if (NULL == strstr (image_data_url,
      42              :                       ";base64,"))
      43              :   {
      44            0 :     GNUNET_break_op (0);
      45            0 :     return false;
      46              :   }
      47           54 :   if (! TALER_url_valid_charset (image_data_url))
      48              :   {
      49            0 :     GNUNET_break_op (0);
      50            0 :     return false;
      51              :   }
      52           54 :   return true;
      53              : }
      54              : 
      55              : 
      56              : bool
      57           38 : TALER_MERCHANT_email_valid (const char *email)
      58              : {
      59              :   regex_t regex;
      60              :   bool is_valid;
      61              : 
      62              :   /*
      63              :    * Email regex pattern supporting:
      64              :    *
      65              :    * Local part (before @):
      66              :    * - Dot-atom: alphanumeric, dots, hyphens, underscores
      67              :    *   (no leading/trailing dots, no consecutive dots)
      68              :    * - Quoted-string: quoted text with escaped chars inside
      69              :    *
      70              :    * Domain part (after @):
      71              :    * - Domain labels: alphanumeric and hyphens
      72              :    *   (no leading/trailing hyphens per label)
      73              :    * - IP literals: [IPv4] or [IPv6:...]
      74              :    *
      75              :    * Pattern breakdown:
      76              :    * Local part:
      77              :    *   ([a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+
      78              :    *    (\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*)
      79              :    *   = dot-atom (atext chars, dots allowed between parts)
      80              :    *
      81              :    *   |"([^"\\]|\\.)*"
      82              :    *   = quoted-string (anything in quotes with escaping)
      83              :    *
      84              :    * Domain part:
      85              :    *   ([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?
      86              :    *    (\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)
      87              :    *   = domain labels (63 chars max, hyphens in middle)
      88              :    *
      89              :    *   |\[([0-9]{1,3}\.){3}[0-9]{1,3}\]
      90              :    *   = IPv4 literal
      91              :    *
      92              :    *   |\[IPv6:[0-9a-fA-F:]+\]
      93              :    *   = IPv6 literal
      94              :    */
      95           38 :   const char *pattern =
      96              :     "^("
      97              :     /* Local part: dot-atom-text or quoted-string */
      98              :     "([a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\\.)?)*[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+"
      99              :     "|"
     100              :     "\"([^\"\\\\]|\\\\.)*\""
     101              :     ")"
     102              :     "@"
     103              :     "("
     104              :     /* Domain: domain labels (with at least one dot) or IP literal */
     105              :     "([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)"
     106              :     "|"
     107              :     "\\[((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}"
     108              :     "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\]"
     109              :     "|"
     110              :     "\\[IPv6:[0-9a-fA-F:]*[0-9a-fA-F]\\]"
     111              :     ")$";
     112              : 
     113           38 :   if ('\0' == email[0])
     114            1 :     return false;
     115              : 
     116              :   /* Maximum email length per RFC 5321 */
     117           37 :   if (strlen (email) > 254)
     118            0 :     return false;
     119              : 
     120           37 :   GNUNET_assert (0 ==
     121              :                  regcomp (&regex,
     122              :                           pattern,
     123              :                           REG_EXTENDED | REG_NOSUB));
     124           37 :   is_valid = (0 ==
     125           37 :               regexec (&regex,
     126              :                        email,
     127              :                        0,
     128              :                        NULL,
     129              :                        0));
     130           37 :   regfree (&regex);
     131           37 :   return is_valid;
     132              : }
     133              : 
     134              : 
     135              : char *
     136           47 : TALER_MERCHANT_phone_validate_normalize (const char *phone,
     137              :                                          bool allow_letters)
     138              : {
     139           47 :   if ('\0' == phone[0])
     140            1 :     return NULL;
     141              : 
     142              :   /* Maximum phone length (reasonable practical limit) */
     143           46 :   if (strlen (phone) > 30)
     144            0 :     return NULL;
     145              : 
     146              :   {
     147              :     regex_t regex;
     148              :     int ret;
     149              : 
     150              :     /*
     151              :      * Phone number regex pattern with +CC prefix requirement:
     152              :      *
     153              :      * Supports:
     154              :      * - Country codes (1-3 digits after +)
     155              :      * - Variable length national numbers
     156              :      * - Spaces, hyphens, and dots as separators
     157              :      * - Parentheses for area codes
     158              :      * - Optional letters representing digits (2-9) if allow_letters is true
     159              :      *
     160              :      * Examples:
     161              :      *   +1-202-555-0173
     162              :      *   +33 1 42 68 53 00
     163              :      *   +44.20.7946.0958
     164              :      *   +1 (202) 555-0173
     165              :      *   +886 2 2345 6789
     166              :      *   +1-800-CALL-NOW (if allow_letters is true)
     167              :      *
     168              :      * Pattern breakdown:
     169              :      * ^\+[0-9]{1,3}
     170              :      *   = Plus sign followed by 1-3 digit country code
     171              :      *
     172              :      * [-. ]?
     173              :      *   = Optional separator after country code
     174              :      *
     175              :      * (\([0-9]{1,4}\)[-. ]?)?
     176              :      *   = Optional parenthesized area code with separator
     177              :      *
     178              :      * [0-9A-Z]
     179              :      *   = Start with digit or letter
     180              :      *
     181              :      * ([-. ]?[0-9A-Z])*
     182              :      *   = Digit/letter groups with optional separators
     183              :      *
     184              :      * $
     185              :      *   = End of string
     186              :      */
     187           46 :     const char *pattern_digits =
     188              :       "^\\+[0-9]{1,3}"                     /* Plus and country code (1-3 digits) */
     189              :       "[-. ]?"                             /* Optional single separator */
     190              :       "("                                  /* Optional area code group */
     191              :       "\\([0-9]{1,4}\\)"                   /* Area code in parens */
     192              :       "[-. ]?"                             /* Optional separator after parens */
     193              :       ")?"
     194              :       "[0-9]"                              /* Start national number with digit */
     195              :       "("                                  /* National number: alternating digits and separators */
     196              :       "[-. ]?[0-9]"                        /* Separator optionally followed by digit */
     197              :       ")*"
     198              :       "$";
     199              : 
     200           46 :     const char *pattern_with_letters =
     201              :       "^\\+[0-9]{1,3}"                     /* Plus and country code (1-3 digits) */
     202              :       "[-. ]?"                             /* Optional single separator */
     203              :       "("                                  /* Optional area code group */
     204              :       "\\([0-9]{1,4}\\)"                   /* Area code in parens */
     205              :       "[-. ]?"                             /* Optional separator after parens */
     206              :       ")?"
     207              :       "[0-9A-Z]"                           /* Start national number with digit or letter */
     208              :       "("                                  /* National number: alternating digits/letters and separators */
     209              :       "[-. ]?[0-9A-Z]"                     /* Separator optionally followed by digit or letter */
     210              :       ")*"
     211              :       "$";
     212              : 
     213           46 :     const char *pattern = allow_letters
     214              :       ? pattern_with_letters
     215           46 :       : pattern_digits;
     216              : 
     217           46 :     GNUNET_assert (0 ==
     218              :                    regcomp (&regex,
     219              :                             pattern,
     220              :                             REG_EXTENDED | REG_NOSUB | REG_ICASE));
     221           46 :     ret = regexec (&regex,
     222              :                    phone, 0,
     223              :                    NULL, 0);
     224           46 :     regfree (&regex);
     225           46 :     if (0 != ret)
     226           16 :       return NULL; /* invalid number */
     227              :   }
     228              : 
     229              :   /* Phone is valid - normalize it */
     230              :   {
     231              :     char *normalized;
     232              :     char *out;
     233              : 
     234           30 :     normalized = GNUNET_malloc (strlen (phone) + 1);
     235           30 :     out = normalized;
     236           30 :     *out++ = '+';  /* Start with plus sign */
     237              : 
     238           30 :     for (const char *in = phone;
     239          441 :          '\0' != *in;
     240          411 :          in++)
     241              :     {
     242          411 :       if (isdigit ((unsigned char) *in))
     243              :       {
     244              :         /* Copy digit as-is */
     245          269 :         *out++ = *in;
     246              :       }
     247          142 :       else if (allow_letters && isalpha ((unsigned char) *in))
     248              :       {
     249              :         /* Convert letter to corresponding digit (A-Z maps to 2-9) */
     250           44 :         char upper = toupper ((unsigned char) *in);
     251              :         /* T9 keypad mapping:
     252              :          * 2: ABC
     253              :          * 3: DEF
     254              :          * 4: GHI
     255              :          * 5: JKL
     256              :          * 6: MNO
     257              :          * 7: PQRS
     258              :          * 8: TUV
     259              :          * 9: WXYZ
     260              :          */
     261              :         char digit;
     262              : 
     263           44 :         if (upper >= 'A' && upper <= 'C')
     264            8 :           digit = '2';
     265           36 :         else if (upper >= 'D' && upper <= 'F')
     266            7 :           digit = '3';
     267           29 :         else if (upper >= 'G' && upper <= 'I')
     268            4 :           digit = '4';
     269           25 :         else if (upper >= 'J' && upper <= 'L')
     270            8 :           digit = '5';
     271           17 :         else if (upper >= 'M' && upper <= 'O')
     272            7 :           digit = '6';
     273           10 :         else if (upper >= 'P' && upper <= 'S')
     274            4 :           digit = '7';
     275            6 :         else if (upper >= 'T' && upper <= 'V')
     276            0 :           digit = '8';
     277            6 :         else if (upper >= 'W' && upper <= 'Z')
     278            6 :           digit = '9';
     279              :         else
     280            0 :           digit = '0';  /* Fallback (shouldn't happen) */
     281           44 :         *out++ = digit;
     282              :       }
     283              :       /* Skip separators, parentheses, and spaces */
     284              :     }
     285           30 :     *out = '\0'; /* redundant, but helps analyzers... */
     286           30 :     return normalized;
     287              :   }
     288              : }
        

Generated by: LCOV version 2.0-1