LCOV - code coverage report
Current view: top level - contrib/isn - isn.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 582 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 45 0
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * isn.c
       4              :  *        PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC)
       5              :  *
       6              :  * Author:      German Mendez Bravo (Kronuz)
       7              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8              :  *
       9              :  * IDENTIFICATION
      10              :  *        contrib/isn/isn.c
      11              :  *
      12              :  *-------------------------------------------------------------------------
      13              :  */
      14              : 
      15              : #include "postgres.h"
      16              : 
      17              : #include "EAN13.h"
      18              : #include "ISBN.h"
      19              : #include "ISMN.h"
      20              : #include "ISSN.h"
      21              : #include "UPC.h"
      22              : #include "fmgr.h"
      23              : #include "isn.h"
      24              : #include "utils/guc.h"
      25              : 
      26            0 : PG_MODULE_MAGIC_EXT(
      27              :                                         .name = "isn",
      28              :                                         .version = PG_VERSION
      29              : );
      30              : 
      31              : #ifdef USE_ASSERT_CHECKING
      32              : #define ISN_DEBUG 1
      33              : #else
      34              : #define ISN_DEBUG 0
      35              : #endif
      36              : 
      37              : #define MAXEAN13LEN 18
      38              : 
      39              : enum isn_type
      40              : {
      41              :         INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC
      42              : };
      43              : 
      44              : static const char *const isn_names[] = {"EAN13/UPC/ISxN", "EAN13/UPC/ISxN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC"};
      45              : 
      46              : /* GUC value */
      47              : static bool g_weak = false;
      48              : 
      49              : 
      50              : /***********************************************************************
      51              :  **
      52              :  **             Routines for EAN13/UPC/ISxNs.
      53              :  **
      54              :  ** Note:
      55              :  **  In this code, a normalized string is one that is known to be a valid
      56              :  **  ISxN number containing only digits and hyphens and with enough space
      57              :  **  to hold the full 13 digits plus the maximum of four hyphens.
      58              :  ***********************************************************************/
      59              : 
      60              : /*----------------------------------------------------------
      61              :  * Debugging routines.
      62              :  *---------------------------------------------------------*/
      63              : 
      64              : /*
      65              :  * Check if the table and its index is correct (just for debugging)
      66              :  */
      67              : pg_attribute_unused()
      68              : static bool
      69            0 : check_table(const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
      70              : {
      71            0 :         const char *aux1,
      72              :                            *aux2;
      73            0 :         int                     a,
      74              :                                 b,
      75            0 :                                 x = 0,
      76            0 :                                 y = -1,
      77            0 :                                 i = 0,
      78              :                                 j,
      79            0 :                                 init = 0;
      80              : 
      81            0 :         if (TABLE == NULL || TABLE_index == NULL)
      82            0 :                 return true;
      83              : 
      84            0 :         while (TABLE[i][0] && TABLE[i][1])
      85              :         {
      86            0 :                 aux1 = TABLE[i][0];
      87            0 :                 aux2 = TABLE[i][1];
      88              : 
      89              :                 /* must always start with a digit: */
      90            0 :                 if (!isdigit((unsigned char) *aux1) || !isdigit((unsigned char) *aux2))
      91            0 :                         goto invalidtable;
      92            0 :                 a = *aux1 - '0';
      93            0 :                 b = *aux2 - '0';
      94              : 
      95              :                 /* must always have the same format and length: */
      96            0 :                 while (*aux1 && *aux2)
      97              :                 {
      98            0 :                         if (!(isdigit((unsigned char) *aux1) &&
      99            0 :                                   isdigit((unsigned char) *aux2)) &&
     100            0 :                                 (*aux1 != *aux2 || *aux1 != '-'))
     101            0 :                                 goto invalidtable;
     102            0 :                         aux1++;
     103            0 :                         aux2++;
     104              :                 }
     105            0 :                 if (*aux1 != *aux2)
     106            0 :                         goto invalidtable;
     107              : 
     108              :                 /* found a new range */
     109            0 :                 if (a > y)
     110              :                 {
     111              :                         /* check current range in the index: */
     112            0 :                         for (j = x; j <= y; j++)
     113              :                         {
     114            0 :                                 if (TABLE_index[j][0] != init)
     115            0 :                                         goto invalidindex;
     116            0 :                                 if (TABLE_index[j][1] != i - init)
     117            0 :                                         goto invalidindex;
     118            0 :                         }
     119            0 :                         init = i;
     120            0 :                         x = a;
     121            0 :                 }
     122              : 
     123              :                 /* Always get the new limit */
     124            0 :                 y = b;
     125            0 :                 if (y < x)
     126            0 :                         goto invalidtable;
     127            0 :                 i++;
     128              :         }
     129              : 
     130            0 :         return true;
     131              : 
     132              : invalidtable:
     133            0 :         elog(DEBUG1, "invalid table near {\"%s\", \"%s\"} (pos: %d)",
     134              :                  TABLE[i][0], TABLE[i][1], i);
     135            0 :         return false;
     136              : 
     137              : invalidindex:
     138            0 :         elog(DEBUG1, "index %d is invalid", j);
     139            0 :         return false;
     140            0 : }
     141              : 
     142              : /*----------------------------------------------------------
     143              :  * Formatting and conversion routines.
     144              :  *---------------------------------------------------------*/
     145              : 
     146              : static unsigned
     147            0 : dehyphenate(char *bufO, char *bufI)
     148              : {
     149            0 :         unsigned        ret = 0;
     150              : 
     151            0 :         while (*bufI)
     152              :         {
     153            0 :                 if (isdigit((unsigned char) *bufI))
     154              :                 {
     155            0 :                         *bufO++ = *bufI;
     156            0 :                         ret++;
     157            0 :                 }
     158            0 :                 bufI++;
     159              :         }
     160            0 :         *bufO = '\0';
     161            0 :         return ret;
     162            0 : }
     163              : 
     164              : /*
     165              :  * hyphenate --- Try to hyphenate, in-place, the string starting at bufI
     166              :  *                                into bufO using the given hyphenation range TABLE.
     167              :  *                                Assumes the input string to be used is of only digits.
     168              :  *
     169              :  * Returns the number of characters actually hyphenated.
     170              :  */
     171              : static unsigned
     172            0 : hyphenate(char *bufO, char *bufI, const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
     173              : {
     174            0 :         unsigned        ret = 0;
     175            0 :         const char *ean_aux1,
     176              :                            *ean_aux2,
     177              :                            *ean_p;
     178            0 :         char       *firstdig,
     179              :                            *aux1,
     180              :                            *aux2;
     181            0 :         unsigned        search,
     182              :                                 upper,
     183              :                                 lower,
     184              :                                 step;
     185            0 :         bool            ean_in1,
     186              :                                 ean_in2;
     187              : 
     188              :         /* just compress the string if no further hyphenation is required */
     189            0 :         if (TABLE == NULL || TABLE_index == NULL)
     190              :         {
     191            0 :                 while (*bufI)
     192              :                 {
     193            0 :                         *bufO++ = *bufI++;
     194            0 :                         ret++;
     195              :                 }
     196            0 :                 *bufO = '\0';
     197            0 :                 return (ret + 1);
     198              :         }
     199              : 
     200              :         /* add remaining hyphenations */
     201              : 
     202            0 :         search = *bufI - '0';
     203            0 :         upper = lower = TABLE_index[search][0];
     204            0 :         upper += TABLE_index[search][1];
     205            0 :         lower--;
     206              : 
     207            0 :         step = (upper - lower) / 2;
     208            0 :         if (step == 0)
     209            0 :                 return 0;
     210            0 :         search = lower + step;
     211              : 
     212            0 :         firstdig = bufI;
     213            0 :         ean_in1 = ean_in2 = false;
     214            0 :         ean_aux1 = TABLE[search][0];
     215            0 :         ean_aux2 = TABLE[search][1];
     216            0 :         do
     217              :         {
     218            0 :                 if ((ean_in1 || *firstdig >= *ean_aux1) && (ean_in2 || *firstdig <= *ean_aux2))
     219              :                 {
     220            0 :                         if (*firstdig > *ean_aux1)
     221            0 :                                 ean_in1 = true;
     222            0 :                         if (*firstdig < *ean_aux2)
     223            0 :                                 ean_in2 = true;
     224            0 :                         if (ean_in1 && ean_in2)
     225            0 :                                 break;
     226              : 
     227            0 :                         firstdig++, ean_aux1++, ean_aux2++;
     228            0 :                         if (!(*ean_aux1 && *ean_aux2 && *firstdig))
     229            0 :                                 break;
     230            0 :                         if (!isdigit((unsigned char) *ean_aux1))
     231            0 :                                 ean_aux1++, ean_aux2++;
     232            0 :                 }
     233              :                 else
     234              :                 {
     235              :                         /*
     236              :                          * check in what direction we should go and move the pointer
     237              :                          * accordingly
     238              :                          */
     239            0 :                         if (*firstdig < *ean_aux1 && !ean_in1)
     240            0 :                                 upper = search;
     241              :                         else
     242            0 :                                 lower = search;
     243              : 
     244            0 :                         step = (upper - lower) / 2;
     245            0 :                         search = lower + step;
     246              : 
     247              :                         /* Initialize stuff again: */
     248            0 :                         firstdig = bufI;
     249            0 :                         ean_in1 = ean_in2 = false;
     250            0 :                         ean_aux1 = TABLE[search][0];
     251            0 :                         ean_aux2 = TABLE[search][1];
     252              :                 }
     253            0 :         } while (step);
     254              : 
     255            0 :         if (step)
     256              :         {
     257            0 :                 aux1 = bufO;
     258            0 :                 aux2 = bufI;
     259            0 :                 ean_p = TABLE[search][0];
     260            0 :                 while (*ean_p && *aux2)
     261              :                 {
     262            0 :                         if (*ean_p++ != '-')
     263            0 :                                 *aux1++ = *aux2++;
     264              :                         else
     265            0 :                                 *aux1++ = '-';
     266            0 :                         ret++;
     267              :                 }
     268            0 :                 *aux1++ = '-';
     269            0 :                 *aux1 = *aux2;                  /* add a lookahead char */
     270            0 :                 return (ret + 1);
     271              :         }
     272            0 :         return ret;
     273            0 : }
     274              : 
     275              : /*
     276              :  * weight_checkdig -- Receives a buffer with a normalized ISxN string number,
     277              :  *                                         and the length to weight.
     278              :  *
     279              :  * Returns the weight of the number (the check digit value, 0-10)
     280              :  */
     281              : static unsigned
     282            0 : weight_checkdig(char *isn, unsigned size)
     283              : {
     284            0 :         unsigned        weight = 0;
     285              : 
     286            0 :         while (*isn && size > 1)
     287              :         {
     288            0 :                 if (isdigit((unsigned char) *isn))
     289              :                 {
     290            0 :                         weight += size-- * (*isn - '0');
     291            0 :                 }
     292            0 :                 isn++;
     293              :         }
     294            0 :         weight = weight % 11;
     295            0 :         if (weight != 0)
     296            0 :                 weight = 11 - weight;
     297            0 :         return weight;
     298            0 : }
     299              : 
     300              : 
     301              : /*
     302              :  * checkdig --- Receives a buffer with a normalized ISxN string number,
     303              :  *                               and the length to check.
     304              :  *
     305              :  * Returns the check digit value (0-9)
     306              :  */
     307              : static unsigned
     308            0 : checkdig(char *num, unsigned size)
     309              : {
     310            0 :         unsigned        check = 0,
     311            0 :                                 check3 = 0;
     312            0 :         unsigned        pos = 0;
     313              : 
     314            0 :         if (*num == 'M')
     315              :         {                                                       /* ISMN start with 'M' */
     316            0 :                 check3 = 3;
     317            0 :                 pos = 1;
     318            0 :         }
     319            0 :         while (*num && size > 1)
     320              :         {
     321            0 :                 if (isdigit((unsigned char) *num))
     322              :                 {
     323            0 :                         if (pos++ % 2)
     324            0 :                                 check3 += *num - '0';
     325              :                         else
     326            0 :                                 check += *num - '0';
     327            0 :                         size--;
     328            0 :                 }
     329            0 :                 num++;
     330              :         }
     331            0 :         check = (check + 3 * check3) % 10;
     332            0 :         if (check != 0)
     333            0 :                 check = 10 - check;
     334            0 :         return check;
     335            0 : }
     336              : 
     337              : /*
     338              :  * ean2isn --- Try to convert an ean13 number to a UPC/ISxN number.
     339              :  *                         This doesn't verify for a valid check digit.
     340              :  *
     341              :  * If errorOK is false, ereport a useful error message if the ean13 is bad.
     342              :  * If errorOK is true, just return "false" for bad input.
     343              :  */
     344              : static bool
     345            0 : ean2isn(ean13 ean, bool errorOK, ean13 *result, enum isn_type accept)
     346              : {
     347            0 :         enum isn_type type = INVALID;
     348              : 
     349            0 :         char            buf[MAXEAN13LEN + 1];
     350            0 :         char       *aux;
     351            0 :         unsigned        digval;
     352            0 :         unsigned        search;
     353            0 :         ean13           ret = ean;
     354              : 
     355            0 :         ean >>= 1;
     356              :         /* verify it's in the EAN13 range */
     357            0 :         if (ean > UINT64CONST(9999999999999))
     358            0 :                 goto eantoobig;
     359              : 
     360              :         /* convert the number */
     361            0 :         search = 0;
     362            0 :         aux = buf + 13;
     363            0 :         *aux = '\0';                            /* terminate string; aux points to last digit */
     364            0 :         do
     365              :         {
     366            0 :                 digval = (unsigned) (ean % 10); /* get the decimal value */
     367            0 :                 ean /= 10;                              /* get next digit */
     368            0 :                 *--aux = (char) (digval + '0'); /* convert to ascii and store */
     369            0 :         } while (ean && search++ < 12);
     370            0 :         while (search++ < 12)
     371            0 :                 *--aux = '0';                   /* fill the remaining EAN13 with '0' */
     372              : 
     373              :         /* find out the data type: */
     374            0 :         if (strncmp("978", buf, 3) == 0)
     375              :         {                                                       /* ISBN */
     376            0 :                 type = ISBN;
     377            0 :         }
     378            0 :         else if (strncmp("977", buf, 3) == 0)
     379              :         {                                                       /* ISSN */
     380            0 :                 type = ISSN;
     381            0 :         }
     382            0 :         else if (strncmp("9790", buf, 4) == 0)
     383              :         {                                                       /* ISMN */
     384            0 :                 type = ISMN;
     385            0 :         }
     386            0 :         else if (strncmp("979", buf, 3) == 0)
     387              :         {                                                       /* ISBN-13 */
     388            0 :                 type = ISBN;
     389            0 :         }
     390            0 :         else if (*buf == '0')
     391              :         {                                                       /* UPC */
     392            0 :                 type = UPC;
     393            0 :         }
     394              :         else
     395              :         {
     396            0 :                 type = EAN13;
     397              :         }
     398            0 :         if (accept != ANY && accept != EAN13 && accept != type)
     399            0 :                 goto eanwrongtype;
     400              : 
     401            0 :         *result = ret;
     402            0 :         return true;
     403              : 
     404              : eanwrongtype:
     405            0 :         if (!errorOK)
     406              :         {
     407            0 :                 if (type != EAN13)
     408              :                 {
     409            0 :                         ereport(ERROR,
     410              :                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     411              :                                          errmsg("cannot cast EAN13(%s) to %s for number: \"%s\"",
     412              :                                                         isn_names[type], isn_names[accept], buf)));
     413            0 :                 }
     414              :                 else
     415              :                 {
     416            0 :                         ereport(ERROR,
     417              :                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     418              :                                          errmsg("cannot cast %s to %s for number: \"%s\"",
     419              :                                                         isn_names[type], isn_names[accept], buf)));
     420              :                 }
     421            0 :         }
     422            0 :         return false;
     423              : 
     424              : eantoobig:
     425            0 :         if (!errorOK)
     426            0 :                 ereport(ERROR,
     427              :                                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     428              :                                  errmsg("value \"%" PRIu64 "\" is out of range for %s type",
     429              :                                                 ean, isn_names[type])));
     430            0 :         return false;
     431            0 : }
     432              : 
     433              : /*
     434              :  * ean2UPC/ISxN --- Convert in-place a normalized EAN13 string to the corresponding
     435              :  *                                      UPC/ISxN string number. Assumes the input string is normalized.
     436              :  */
     437              : static inline void
     438            0 : ean2ISBN(char *isn)
     439              : {
     440            0 :         char       *aux;
     441            0 :         unsigned        check;
     442              : 
     443              :         /*
     444              :          * The number should come in this format: 978-0-000-00000-0 or may be an
     445              :          * ISBN-13 number, 979-..., which does not have a short representation. Do
     446              :          * the short output version if possible.
     447              :          */
     448            0 :         if (strncmp("978-", isn, 4) == 0)
     449              :         {
     450              :                 /* Strip the first part and calculate the new check digit */
     451            0 :                 hyphenate(isn, isn + 4, NULL, NULL);
     452            0 :                 check = weight_checkdig(isn, 10);
     453            0 :                 aux = strchr(isn, '\0');
     454            0 :                 while (!isdigit((unsigned char) *--aux));
     455            0 :                 if (check == 10)
     456            0 :                         *aux = 'X';
     457              :                 else
     458            0 :                         *aux = check + '0';
     459            0 :         }
     460            0 : }
     461              : 
     462              : static inline void
     463            0 : ean2ISMN(char *isn)
     464              : {
     465              :         /* the number should come in this format: 979-0-000-00000-0 */
     466              :         /* Just strip the first part and change the first digit ('0') to 'M' */
     467            0 :         hyphenate(isn, isn + 4, NULL, NULL);
     468            0 :         isn[0] = 'M';
     469            0 : }
     470              : 
     471              : static inline void
     472            0 : ean2ISSN(char *isn)
     473              : {
     474            0 :         unsigned        check;
     475              : 
     476              :         /* the number should come in this format: 977-0000-000-00-0 */
     477              :         /* Strip the first part, crop, and calculate the new check digit */
     478            0 :         hyphenate(isn, isn + 4, NULL, NULL);
     479            0 :         check = weight_checkdig(isn, 8);
     480            0 :         if (check == 10)
     481            0 :                 isn[8] = 'X';
     482              :         else
     483            0 :                 isn[8] = check + '0';
     484            0 :         isn[9] = '\0';
     485            0 : }
     486              : 
     487              : static inline void
     488            0 : ean2UPC(char *isn)
     489              : {
     490              :         /* the number should come in this format: 000-000000000-0 */
     491              :         /* Strip the first part, crop, and dehyphenate */
     492            0 :         dehyphenate(isn, isn + 1);
     493            0 :         isn[12] = '\0';
     494            0 : }
     495              : 
     496              : /*
     497              :  * ean2* --- Converts a string of digits into an ean13 number.
     498              :  *                        Assumes the input string is a string with only digits
     499              :  *                        on it, and that it's within the range of ean13.
     500              :  *
     501              :  * Returns the ean13 value of the string.
     502              :  */
     503              : static ean13
     504            0 : str2ean(const char *num)
     505              : {
     506            0 :         ean13           ean = 0;                /* current ean */
     507              : 
     508            0 :         while (*num)
     509              :         {
     510            0 :                 if (isdigit((unsigned char) *num))
     511            0 :                         ean = 10 * ean + (*num - '0');
     512            0 :                 num++;
     513              :         }
     514            0 :         return (ean << 1);                        /* also give room to a flag */
     515            0 : }
     516              : 
     517              : /*
     518              :  * ean2string --- Try to convert an ean13 number to a hyphenated string.
     519              :  *                                Assumes there's enough space in result to hold
     520              :  *                                the string (maximum MAXEAN13LEN+1 bytes)
     521              :  *                                This doesn't verify for a valid check digit.
     522              :  *
     523              :  * If shortType is true, the returned string is in the old ISxN short format.
     524              :  * If errorOK is false, ereport a useful error message if the string is bad.
     525              :  * If errorOK is true, just return "false" for bad input.
     526              :  */
     527              : static bool
     528            0 : ean2string(ean13 ean, bool errorOK, char *result, bool shortType)
     529              : {
     530            0 :         const char *(*TABLE)[2];
     531            0 :         const unsigned (*TABLE_index)[2];
     532            0 :         enum isn_type type = INVALID;
     533              : 
     534            0 :         char       *aux;
     535            0 :         unsigned        digval;
     536            0 :         unsigned        search;
     537            0 :         char            valid = '\0';   /* was the number initially written with a
     538              :                                                                  * valid check digit? */
     539              : 
     540            0 :         TABLE_index = ISBN_index;
     541              : 
     542            0 :         if ((ean & 1) != 0)
     543            0 :                 valid = '!';
     544            0 :         ean >>= 1;
     545              :         /* verify it's in the EAN13 range */
     546            0 :         if (ean > UINT64CONST(9999999999999))
     547            0 :                 goto eantoobig;
     548              : 
     549              :         /* convert the number */
     550            0 :         search = 0;
     551            0 :         aux = result + MAXEAN13LEN;
     552            0 :         *aux = '\0';                            /* terminate string; aux points to last digit */
     553            0 :         *--aux = valid;                         /* append '!' for numbers with invalid but
     554              :                                                                  * corrected check digit */
     555            0 :         do
     556              :         {
     557            0 :                 digval = (unsigned) (ean % 10); /* get the decimal value */
     558            0 :                 ean /= 10;                              /* get next digit */
     559            0 :                 *--aux = (char) (digval + '0'); /* convert to ascii and store */
     560            0 :                 if (search == 0)
     561            0 :                         *--aux = '-';           /* the check digit is always there */
     562            0 :         } while (ean && search++ < 13);
     563            0 :         while (search++ < 13)
     564            0 :                 *--aux = '0';                   /* fill the remaining EAN13 with '0' */
     565              : 
     566              :         /* The string should be in this form: ???DDDDDDDDDDDD-D" */
     567            0 :         search = hyphenate(result, result + 3, EAN13_range, EAN13_index);
     568              : 
     569              :         /* verify it's a logically valid EAN13 */
     570            0 :         if (search == 0)
     571              :         {
     572            0 :                 search = hyphenate(result, result + 3, NULL, NULL);
     573            0 :                 goto okay;
     574              :         }
     575              : 
     576              :         /* find out what type of hyphenation is needed: */
     577            0 :         if (strncmp("978-", result, search) == 0)
     578              :         {                                                       /* ISBN -13 978-range */
     579              :                 /* The string should be in this form: 978-??000000000-0" */
     580            0 :                 type = ISBN;
     581            0 :                 TABLE = ISBN_range;
     582            0 :                 TABLE_index = ISBN_index;
     583            0 :         }
     584            0 :         else if (strncmp("977-", result, search) == 0)
     585              :         {                                                       /* ISSN */
     586              :                 /* The string should be in this form: 977-??000000000-0" */
     587            0 :                 type = ISSN;
     588            0 :                 TABLE = ISSN_range;
     589            0 :                 TABLE_index = ISSN_index;
     590            0 :         }
     591            0 :         else if (strncmp("979-0", result, search + 1) == 0)
     592              :         {                                                       /* ISMN */
     593              :                 /* The string should be in this form: 979-0?000000000-0" */
     594            0 :                 type = ISMN;
     595            0 :                 TABLE = ISMN_range;
     596            0 :                 TABLE_index = ISMN_index;
     597            0 :         }
     598            0 :         else if (strncmp("979-", result, search) == 0)
     599              :         {                                                       /* ISBN-13 979-range */
     600              :                 /* The string should be in this form: 979-??000000000-0" */
     601            0 :                 type = ISBN;
     602            0 :                 TABLE = ISBN_range_new;
     603            0 :                 TABLE_index = ISBN_index_new;
     604            0 :         }
     605            0 :         else if (*result == '0')
     606              :         {                                                       /* UPC */
     607              :                 /* The string should be in this form: 000-00000000000-0" */
     608            0 :                 type = UPC;
     609            0 :                 TABLE = UPC_range;
     610            0 :                 TABLE_index = UPC_index;
     611            0 :         }
     612              :         else
     613              :         {
     614            0 :                 type = EAN13;
     615            0 :                 TABLE = NULL;
     616            0 :                 TABLE_index = NULL;
     617              :         }
     618              : 
     619              :         /* verify it's a logically valid EAN13/UPC/ISxN */
     620            0 :         digval = search;
     621            0 :         search = hyphenate(result + digval, result + digval + 2, TABLE, TABLE_index);
     622              : 
     623              :         /* verify it's a valid EAN13 */
     624            0 :         if (search == 0)
     625              :         {
     626            0 :                 search = hyphenate(result + digval, result + digval + 2, NULL, NULL);
     627            0 :                 goto okay;
     628              :         }
     629              : 
     630              : okay:
     631              :         /* convert to the old short type: */
     632            0 :         if (shortType)
     633            0 :                 switch (type)
     634              :                 {
     635              :                         case ISBN:
     636            0 :                                 ean2ISBN(result);
     637            0 :                                 break;
     638              :                         case ISMN:
     639            0 :                                 ean2ISMN(result);
     640            0 :                                 break;
     641              :                         case ISSN:
     642            0 :                                 ean2ISSN(result);
     643            0 :                                 break;
     644              :                         case UPC:
     645            0 :                                 ean2UPC(result);
     646            0 :                                 break;
     647              :                         default:
     648            0 :                                 break;
     649            0 :                 }
     650            0 :         return true;
     651              : 
     652              : eantoobig:
     653            0 :         if (!errorOK)
     654            0 :                 ereport(ERROR,
     655              :                                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     656              :                                  errmsg("value \"%" PRIu64 "\" is out of range for %s type",
     657              :                                                 ean, isn_names[type])));
     658            0 :         return false;
     659            0 : }
     660              : 
     661              : /*
     662              :  * string2ean --- try to parse a string into an ean13.
     663              :  *
     664              :  * ereturn false with a useful error message if the string is bad.
     665              :  * Otherwise return true.
     666              :  *
     667              :  * if the input string ends with '!' it will always be treated as invalid
     668              :  * (even if the check digit is valid)
     669              :  */
     670              : static bool
     671            0 : string2ean(const char *str, struct Node *escontext, ean13 *result,
     672              :                    enum isn_type accept)
     673              : {
     674            0 :         bool            digit,
     675              :                                 last;
     676            0 :         char            buf[17] = "                ";
     677            0 :         char       *aux1 = buf + 3; /* leave space for the first part, in case
     678              :                                                                  * it's needed */
     679            0 :         const char *aux2 = str;
     680            0 :         enum isn_type type = INVALID;
     681            0 :         unsigned        check = 0,
     682            0 :                                 rcheck = (unsigned) -1;
     683            0 :         unsigned        length = 0;
     684            0 :         bool            magic = false,
     685            0 :                                 valid = true;
     686              : 
     687              :         /* recognize and validate the number: */
     688            0 :         while (*aux2 && length <= 13)
     689              :         {
     690            0 :                 last = (*(aux2 + 1) == '!' || *(aux2 + 1) == '\0'); /* is the last character */
     691            0 :                 digit = (isdigit((unsigned char) *aux2) != 0);  /* is current character
     692              :                                                                                                                  * a digit? */
     693            0 :                 if (*aux2 == '?' && last)       /* automagically calculate check digit if
     694              :                                                                          * it's '?' */
     695            0 :                         magic = digit = true;
     696            0 :                 if (length == 0 && (*aux2 == 'M' || *aux2 == 'm'))
     697              :                 {
     698              :                         /* only ISMN can be here */
     699            0 :                         if (type != INVALID)
     700            0 :                                 goto eaninvalid;
     701            0 :                         type = ISMN;
     702            0 :                         *aux1++ = 'M';
     703            0 :                         length++;
     704            0 :                 }
     705            0 :                 else if (length == 7 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
     706              :                 {
     707              :                         /* only ISSN can be here */
     708            0 :                         if (type != INVALID)
     709            0 :                                 goto eaninvalid;
     710            0 :                         type = ISSN;
     711            0 :                         *aux1++ = pg_ascii_toupper((unsigned char) *aux2);
     712            0 :                         length++;
     713            0 :                 }
     714            0 :                 else if (length == 9 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
     715              :                 {
     716              :                         /* only ISBN and ISMN can be here */
     717            0 :                         if (type != INVALID && type != ISMN)
     718            0 :                                 goto eaninvalid;
     719            0 :                         if (type == INVALID)
     720            0 :                                 type = ISBN;    /* ISMN must start with 'M' */
     721            0 :                         *aux1++ = pg_ascii_toupper((unsigned char) *aux2);
     722            0 :                         length++;
     723            0 :                 }
     724            0 :                 else if (length == 11 && digit && last)
     725              :                 {
     726              :                         /* only UPC can be here */
     727            0 :                         if (type != INVALID)
     728            0 :                                 goto eaninvalid;
     729            0 :                         type = UPC;
     730            0 :                         *aux1++ = *aux2;
     731            0 :                         length++;
     732            0 :                 }
     733            0 :                 else if (*aux2 == '-' || *aux2 == ' ')
     734              :                 {
     735              :                         /* skip, we could validate but I think it's worthless */
     736            0 :                 }
     737            0 :                 else if (*aux2 == '!' && *(aux2 + 1) == '\0')
     738              :                 {
     739              :                         /* the invalid check digit suffix was found, set it */
     740            0 :                         if (!magic)
     741            0 :                                 valid = false;
     742            0 :                         magic = true;
     743            0 :                 }
     744            0 :                 else if (!digit)
     745              :                 {
     746            0 :                         goto eaninvalid;
     747              :                 }
     748              :                 else
     749              :                 {
     750            0 :                         *aux1++ = *aux2;
     751            0 :                         if (++length > 13)
     752            0 :                                 goto eantoobig;
     753              :                 }
     754            0 :                 aux2++;
     755              :         }
     756            0 :         *aux1 = '\0';                           /* terminate the string */
     757              : 
     758              :         /* find the current check digit value */
     759            0 :         if (length == 13)
     760              :         {
     761              :                 /* only EAN13 can be here */
     762            0 :                 if (type != INVALID)
     763            0 :                         goto eaninvalid;
     764            0 :                 type = EAN13;
     765            0 :                 check = buf[15] - '0';
     766            0 :         }
     767            0 :         else if (length == 12)
     768              :         {
     769              :                 /* only UPC can be here */
     770            0 :                 if (type != UPC)
     771            0 :                         goto eaninvalid;
     772            0 :                 check = buf[14] - '0';
     773            0 :         }
     774            0 :         else if (length == 10)
     775              :         {
     776            0 :                 if (type != ISBN && type != ISMN)
     777            0 :                         goto eaninvalid;
     778            0 :                 if (buf[12] == 'X')
     779            0 :                         check = 10;
     780              :                 else
     781            0 :                         check = buf[12] - '0';
     782            0 :         }
     783            0 :         else if (length == 8)
     784              :         {
     785            0 :                 if (type != INVALID && type != ISSN)
     786            0 :                         goto eaninvalid;
     787            0 :                 type = ISSN;
     788            0 :                 if (buf[10] == 'X')
     789            0 :                         check = 10;
     790              :                 else
     791            0 :                         check = buf[10] - '0';
     792            0 :         }
     793              :         else
     794            0 :                 goto eaninvalid;
     795              : 
     796            0 :         if (type == INVALID)
     797            0 :                 goto eaninvalid;
     798              : 
     799              :         /* obtain the real check digit value, validate, and convert to ean13: */
     800            0 :         if (accept == EAN13 && type != accept)
     801            0 :                 goto eanwrongtype;
     802            0 :         if (accept != ANY && type != EAN13 && type != accept)
     803            0 :                 goto eanwrongtype;
     804            0 :         switch (type)
     805              :         {
     806              :                 case EAN13:
     807            0 :                         valid = (valid && ((rcheck = checkdig(buf + 3, 13)) == check || magic));
     808              :                         /* now get the subtype of EAN13: */
     809            0 :                         if (buf[3] == '0')
     810            0 :                                 type = UPC;
     811            0 :                         else if (strncmp("977", buf + 3, 3) == 0)
     812            0 :                                 type = ISSN;
     813            0 :                         else if (strncmp("978", buf + 3, 3) == 0)
     814            0 :                                 type = ISBN;
     815            0 :                         else if (strncmp("9790", buf + 3, 4) == 0)
     816            0 :                                 type = ISMN;
     817            0 :                         else if (strncmp("979", buf + 3, 3) == 0)
     818            0 :                                 type = ISBN;
     819            0 :                         if (accept != EAN13 && accept != ANY && type != accept)
     820            0 :                                 goto eanwrongtype;
     821            0 :                         break;
     822              :                 case ISMN:
     823            0 :                         memcpy(buf, "9790", 4); /* this isn't for sure yet, for now ISMN
     824              :                                                                          * it's only 9790 */
     825            0 :                         valid = (valid && ((rcheck = checkdig(buf, 13)) == check || magic));
     826            0 :                         break;
     827              :                 case ISBN:
     828            0 :                         memcpy(buf, "978", 3);
     829            0 :                         valid = (valid && ((rcheck = weight_checkdig(buf + 3, 10)) == check || magic));
     830            0 :                         break;
     831              :                 case ISSN:
     832            0 :                         memcpy(buf + 10, "00", 2);    /* append 00 as the normal issue
     833              :                                                                                  * publication code */
     834            0 :                         memcpy(buf, "977", 3);
     835            0 :                         valid = (valid && ((rcheck = weight_checkdig(buf + 3, 8)) == check || magic));
     836            0 :                         break;
     837              :                 case UPC:
     838            0 :                         buf[2] = '0';
     839            0 :                         valid = (valid && ((rcheck = checkdig(buf + 2, 13)) == check || magic));
     840              :                 default:
     841            0 :                         break;
     842              :         }
     843              : 
     844              :         /* fix the check digit: */
     845            0 :         for (aux1 = buf; *aux1 && *aux1 <= ' '; aux1++);
     846            0 :         aux1[12] = checkdig(aux1, 13) + '0';
     847            0 :         aux1[13] = '\0';
     848              : 
     849            0 :         if (!valid && !magic)
     850            0 :                 goto eanbadcheck;
     851              : 
     852            0 :         *result = str2ean(aux1);
     853            0 :         *result |= valid ? 0 : 1;
     854            0 :         return true;
     855              : 
     856              : eanbadcheck:
     857            0 :         if (g_weak)
     858              :         {                                                       /* weak input mode is activated: */
     859              :                 /* set the "invalid-check-digit-on-input" flag */
     860            0 :                 *result = str2ean(aux1);
     861            0 :                 *result |= 1;
     862            0 :                 return true;
     863              :         }
     864              : 
     865            0 :         if (rcheck == (unsigned) -1)
     866              :         {
     867            0 :                 ereturn(escontext, false,
     868              :                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     869              :                                  errmsg("invalid %s number: \"%s\"",
     870              :                                                 isn_names[accept], str)));
     871            0 :         }
     872              :         else
     873              :         {
     874            0 :                 ereturn(escontext, false,
     875              :                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     876              :                                  errmsg("invalid check digit for %s number: \"%s\", should be %c",
     877              :                                                 isn_names[accept], str, (rcheck == 10) ? ('X') : (rcheck + '0'))));
     878              :         }
     879              : 
     880              : eaninvalid:
     881            0 :         ereturn(escontext, false,
     882              :                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     883              :                          errmsg("invalid input syntax for %s number: \"%s\"",
     884              :                                         isn_names[accept], str)));
     885              : 
     886              : eanwrongtype:
     887            0 :         ereturn(escontext, false,
     888              :                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     889              :                          errmsg("cannot cast %s to %s for number: \"%s\"",
     890              :                                         isn_names[type], isn_names[accept], str)));
     891              : 
     892              : eantoobig:
     893            0 :         ereturn(escontext, false,
     894              :                         (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     895              :                          errmsg("value \"%s\" is out of range for %s type",
     896              :                                         str, isn_names[accept])));
     897            0 : }
     898              : 
     899              : /*----------------------------------------------------------
     900              :  * Exported routines.
     901              :  *---------------------------------------------------------*/
     902              : 
     903              : void
     904            0 : _PG_init(void)
     905              : {
     906              :         if (ISN_DEBUG)
     907              :         {
     908            0 :                 if (!check_table(EAN13_range, EAN13_index))
     909            0 :                         elog(ERROR, "EAN13 failed check");
     910            0 :                 if (!check_table(ISBN_range, ISBN_index))
     911            0 :                         elog(ERROR, "ISBN failed check");
     912            0 :                 if (!check_table(ISMN_range, ISMN_index))
     913            0 :                         elog(ERROR, "ISMN failed check");
     914            0 :                 if (!check_table(ISSN_range, ISSN_index))
     915            0 :                         elog(ERROR, "ISSN failed check");
     916            0 :                 if (!check_table(UPC_range, UPC_index))
     917            0 :                         elog(ERROR, "UPC failed check");
     918              :         }
     919              : 
     920              :         /* Define a GUC variable for weak mode. */
     921            0 :         DefineCustomBoolVariable("isn.weak",
     922              :                                                          "Accept input with invalid ISN check digits.",
     923              :                                                          NULL,
     924              :                                                          &g_weak,
     925              :                                                          false,
     926              :                                                          PGC_USERSET,
     927              :                                                          0,
     928              :                                                          NULL,
     929              :                                                          NULL,
     930              :                                                          NULL);
     931              : 
     932            0 :         MarkGUCPrefixReserved("isn");
     933            0 : }
     934              : 
     935              : /* isn_out
     936              :  */
     937            0 : PG_FUNCTION_INFO_V1(isn_out);
     938              : Datum
     939            0 : isn_out(PG_FUNCTION_ARGS)
     940              : {
     941            0 :         ean13           val = PG_GETARG_EAN13(0);
     942            0 :         char       *result;
     943            0 :         char            buf[MAXEAN13LEN + 1];
     944              : 
     945            0 :         (void) ean2string(val, false, buf, true);
     946              : 
     947            0 :         result = pstrdup(buf);
     948            0 :         PG_RETURN_CSTRING(result);
     949            0 : }
     950              : 
     951              : /* ean13_out
     952              :  */
     953            0 : PG_FUNCTION_INFO_V1(ean13_out);
     954              : Datum
     955            0 : ean13_out(PG_FUNCTION_ARGS)
     956              : {
     957            0 :         ean13           val = PG_GETARG_EAN13(0);
     958            0 :         char       *result;
     959            0 :         char            buf[MAXEAN13LEN + 1];
     960              : 
     961            0 :         (void) ean2string(val, false, buf, false);
     962              : 
     963            0 :         result = pstrdup(buf);
     964            0 :         PG_RETURN_CSTRING(result);
     965            0 : }
     966              : 
     967              : /* ean13_in
     968              :  */
     969            0 : PG_FUNCTION_INFO_V1(ean13_in);
     970              : Datum
     971            0 : ean13_in(PG_FUNCTION_ARGS)
     972              : {
     973            0 :         const char *str = PG_GETARG_CSTRING(0);
     974            0 :         ean13           result;
     975              : 
     976            0 :         if (!string2ean(str, fcinfo->context, &result, EAN13))
     977            0 :                 PG_RETURN_NULL();
     978            0 :         PG_RETURN_EAN13(result);
     979            0 : }
     980              : 
     981              : /* isbn_in
     982              :  */
     983            0 : PG_FUNCTION_INFO_V1(isbn_in);
     984              : Datum
     985            0 : isbn_in(PG_FUNCTION_ARGS)
     986              : {
     987            0 :         const char *str = PG_GETARG_CSTRING(0);
     988            0 :         ean13           result;
     989              : 
     990            0 :         if (!string2ean(str, fcinfo->context, &result, ISBN))
     991            0 :                 PG_RETURN_NULL();
     992            0 :         PG_RETURN_EAN13(result);
     993            0 : }
     994              : 
     995              : /* ismn_in
     996              :  */
     997            0 : PG_FUNCTION_INFO_V1(ismn_in);
     998              : Datum
     999            0 : ismn_in(PG_FUNCTION_ARGS)
    1000              : {
    1001            0 :         const char *str = PG_GETARG_CSTRING(0);
    1002            0 :         ean13           result;
    1003              : 
    1004            0 :         if (!string2ean(str, fcinfo->context, &result, ISMN))
    1005            0 :                 PG_RETURN_NULL();
    1006            0 :         PG_RETURN_EAN13(result);
    1007            0 : }
    1008              : 
    1009              : /* issn_in
    1010              :  */
    1011            0 : PG_FUNCTION_INFO_V1(issn_in);
    1012              : Datum
    1013            0 : issn_in(PG_FUNCTION_ARGS)
    1014              : {
    1015            0 :         const char *str = PG_GETARG_CSTRING(0);
    1016            0 :         ean13           result;
    1017              : 
    1018            0 :         if (!string2ean(str, fcinfo->context, &result, ISSN))
    1019            0 :                 PG_RETURN_NULL();
    1020            0 :         PG_RETURN_EAN13(result);
    1021            0 : }
    1022              : 
    1023              : /* upc_in
    1024              :  */
    1025            0 : PG_FUNCTION_INFO_V1(upc_in);
    1026              : Datum
    1027            0 : upc_in(PG_FUNCTION_ARGS)
    1028              : {
    1029            0 :         const char *str = PG_GETARG_CSTRING(0);
    1030            0 :         ean13           result;
    1031              : 
    1032            0 :         if (!string2ean(str, fcinfo->context, &result, UPC))
    1033            0 :                 PG_RETURN_NULL();
    1034            0 :         PG_RETURN_EAN13(result);
    1035            0 : }
    1036              : 
    1037              : /* casting functions
    1038              : */
    1039            0 : PG_FUNCTION_INFO_V1(isbn_cast_from_ean13);
    1040              : Datum
    1041            0 : isbn_cast_from_ean13(PG_FUNCTION_ARGS)
    1042              : {
    1043            0 :         ean13           val = PG_GETARG_EAN13(0);
    1044            0 :         ean13           result;
    1045              : 
    1046            0 :         (void) ean2isn(val, false, &result, ISBN);
    1047              : 
    1048            0 :         PG_RETURN_EAN13(result);
    1049            0 : }
    1050              : 
    1051            0 : PG_FUNCTION_INFO_V1(ismn_cast_from_ean13);
    1052              : Datum
    1053            0 : ismn_cast_from_ean13(PG_FUNCTION_ARGS)
    1054              : {
    1055            0 :         ean13           val = PG_GETARG_EAN13(0);
    1056            0 :         ean13           result;
    1057              : 
    1058            0 :         (void) ean2isn(val, false, &result, ISMN);
    1059              : 
    1060            0 :         PG_RETURN_EAN13(result);
    1061            0 : }
    1062              : 
    1063            0 : PG_FUNCTION_INFO_V1(issn_cast_from_ean13);
    1064              : Datum
    1065            0 : issn_cast_from_ean13(PG_FUNCTION_ARGS)
    1066              : {
    1067            0 :         ean13           val = PG_GETARG_EAN13(0);
    1068            0 :         ean13           result;
    1069              : 
    1070            0 :         (void) ean2isn(val, false, &result, ISSN);
    1071              : 
    1072            0 :         PG_RETURN_EAN13(result);
    1073            0 : }
    1074              : 
    1075            0 : PG_FUNCTION_INFO_V1(upc_cast_from_ean13);
    1076              : Datum
    1077            0 : upc_cast_from_ean13(PG_FUNCTION_ARGS)
    1078              : {
    1079            0 :         ean13           val = PG_GETARG_EAN13(0);
    1080            0 :         ean13           result;
    1081              : 
    1082            0 :         (void) ean2isn(val, false, &result, UPC);
    1083              : 
    1084            0 :         PG_RETURN_EAN13(result);
    1085            0 : }
    1086              : 
    1087              : 
    1088              : /* is_valid - returns false if the "invalid-check-digit-on-input" is set
    1089              :  */
    1090            0 : PG_FUNCTION_INFO_V1(is_valid);
    1091              : Datum
    1092            0 : is_valid(PG_FUNCTION_ARGS)
    1093              : {
    1094            0 :         ean13           val = PG_GETARG_EAN13(0);
    1095              : 
    1096            0 :         PG_RETURN_BOOL((val & 1) == 0);
    1097            0 : }
    1098              : 
    1099              : /* make_valid - unsets the "invalid-check-digit-on-input" flag
    1100              :  */
    1101            0 : PG_FUNCTION_INFO_V1(make_valid);
    1102              : Datum
    1103            0 : make_valid(PG_FUNCTION_ARGS)
    1104              : {
    1105            0 :         ean13           val = PG_GETARG_EAN13(0);
    1106              : 
    1107            0 :         val &= ~((ean13) 1);
    1108            0 :         PG_RETURN_EAN13(val);
    1109            0 : }
    1110              : 
    1111              : /* this function temporarily sets weak input flag
    1112              :  * (to lose the strictness of check digit acceptance)
    1113              :  */
    1114            0 : PG_FUNCTION_INFO_V1(accept_weak_input);
    1115              : Datum
    1116            0 : accept_weak_input(PG_FUNCTION_ARGS)
    1117              : {
    1118            0 :         bool            newvalue = PG_GETARG_BOOL(0);
    1119              : 
    1120            0 :         (void) set_config_option("isn.weak", newvalue ? "on" : "off",
    1121              :                                                          PGC_USERSET, PGC_S_SESSION,
    1122              :                                                          GUC_ACTION_SET, true, 0, false);
    1123            0 :         PG_RETURN_BOOL(g_weak);
    1124            0 : }
    1125              : 
    1126            0 : PG_FUNCTION_INFO_V1(weak_input_status);
    1127              : Datum
    1128            0 : weak_input_status(PG_FUNCTION_ARGS)
    1129              : {
    1130            0 :         PG_RETURN_BOOL(g_weak);
    1131              : }
        

Generated by: LCOV version 2.3.2-1