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