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