mirror of
https://git.code.sf.net/p/zint/code
synced 2026-05-01 11:45:29 +00:00
2434 lines
93 KiB
C
2434 lines
93 KiB
C
/* gs1.c - Verifies GS1 data */
|
|
/*
|
|
libzint - the open source barcode library
|
|
Copyright (C) 2009-2026 Robin Stuart <rstuart114@gmail.com>
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
3. Neither the name of the project nor the names of its contributors
|
|
may be used to endorse or promote products derived from this software
|
|
without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
*/
|
|
/* SPDX-License-Identifier: BSD-3-Clause */
|
|
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#ifdef ZINT_HAVE_GS1SE
|
|
#include <gs1encoders.h>
|
|
#endif
|
|
#include "common.h"
|
|
#include "gs1.h"
|
|
|
|
/* gs1_lint() validators and checkers */
|
|
|
|
/* Set `err_msg`, returning 0 for convenience */
|
|
static int gs1_err_msg_cpy_nochk(char err_msg[50], const char *msg) {
|
|
const int length = (int) strlen(msg);
|
|
|
|
assert(length < 50);
|
|
memcpy(err_msg, msg, length + 1); /* Include terminating NUL */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* sprintf into `err_msg`, returning 0 for convenience */
|
|
ZINT_FORMAT_PRINTF(2, 3) static int gs1_err_msg_printf_nochk(char err_msg[50], const char *fmt, ...) {
|
|
va_list ap;
|
|
int size;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
size = vsprintf(err_msg, fmt, ap);
|
|
|
|
(void)size;
|
|
assert(size >= 0);
|
|
assert(size < 50);
|
|
|
|
va_end(ap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Validate numeric */
|
|
static int gs1_numeric(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50]) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
|
|
for (; d < de; d++) {
|
|
if (!z_isdigit(*d)) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Non-numeric character '%c'", *d);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* GS1 General Specifications Release 26.0 Table 7-18 GS1 AI encodable character reference values.
|
|
Also used to determine if character in set 82 - a value of 82 means not in */
|
|
static const char gs1_c82[] = {
|
|
/* ! " # $ % & ' ( ) * */
|
|
0, 1, 82, 82, 2, 3, 4, 5, 6, 7,
|
|
/* + , - . / 0 1 2 3 4 */
|
|
8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
|
/* 5 6 7 8 9 : ; < = > */
|
|
18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
|
/* ? @ A B C D E F G H */
|
|
28, 82, 29, 30, 31, 32, 33, 34, 35, 36,
|
|
/* I J K L M N O P Q R */
|
|
37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
|
|
/* S T U V W X Y Z [ \ */
|
|
47, 48, 49, 50, 51, 52, 53, 54, 82, 82,
|
|
/* ] ^ _ ` a b c d e f */
|
|
82, 82, 55, 82, 56, 57, 58, 59, 60, 61,
|
|
/* g h i j k l m n o p */
|
|
62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
|
|
/* q r s t u v w x y z */
|
|
72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
|
|
};
|
|
|
|
/* Validate of character set 82
|
|
(GS1 General Specifications Release 26.0 Table 7-20 GS1 AI encodable character set 82) */
|
|
static int gs1_cset82(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50]) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
|
|
for (; d < de; d++) {
|
|
if (*d < '!' || *d > 'z' || gs1_c82[*d - '!'] == 82) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid CSET 82 character '%c'", *d);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Validate of character set 39
|
|
(GS1 General Specifications Release 26.0 Table 7-21 GS1 AI encodable character set 39) */
|
|
static int gs1_cset39(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50]) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
|
|
for (; d < de; d++) {
|
|
/* 0-9, A-Z and "#", "-", "/" */
|
|
if ((*d < '0' && *d != '#' && *d != '-' && *d != '/') || (*d > '9' && *d < 'A') || *d > 'Z') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid CSET 39 character '%c'", *d);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Validate of character set 64 (GSCN 21-307 Figure 7.11-3) */
|
|
static int gs1_cset64(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50]) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
|
|
for (; d < de; d++) {
|
|
/* 0-9, A-Z, a-z and "-", "_" */
|
|
if ((*d < '0' && *d != '-') || (*d > '9' && *d < 'A') || (*d > 'Z' && *d < 'a' && *d != '_')
|
|
|| *d > 'z') {
|
|
/* One or two "="s can be used as padding to mod 3 length */
|
|
if (*d == '=' && (d + 1 == de || (d + 2 == de && *(d + 1) == '=')) && data_len % 3 == 0) {
|
|
break;
|
|
}
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid CSET 64 character '%c'", *d);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check a check digit
|
|
(GS1 General Specifications Release 26.0 7.9.1 Standard check digit calculations for GS1 data structures) */
|
|
static int gs1_csum(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len) - 1; /* Note less last character */
|
|
int checksum = 0;
|
|
int factor = (min & 1) ? 1 : 3;
|
|
|
|
for (; d < de; d++) {
|
|
checksum += (*d - '0') * factor;
|
|
factor ^= 0x02; /* Toggles 1 and 3 */
|
|
}
|
|
checksum = 10 - checksum % 10;
|
|
if (checksum == 10) {
|
|
checksum = 0;
|
|
}
|
|
if (checksum != *d - '0') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Bad checksum '%c', expected '%c'", *d, checksum + '0');
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check alphanumeric check characters
|
|
(GS1 General Specifications Release 26.0 7.9.5 Check character calculation (for alphanumeric keys)) */
|
|
static int gs1_csumalpha(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
/* Do this check separately for backward compatibility */
|
|
if (data_len && data_len < 2) {
|
|
*p_err_no = 4;
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
static const char c32[] = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
static const char weights[] = {
|
|
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83
|
|
};
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len) - 2; /* Note less last 2 characters */
|
|
int checksum = 0, c1, c2;
|
|
|
|
for (; d < de; d++) {
|
|
checksum += gs1_c82[*d - '!'] * weights[de - 1 - d];
|
|
}
|
|
checksum %= 1021;
|
|
c1 = c32[checksum >> 5];
|
|
c2 = c32[checksum & 0x1F];
|
|
|
|
if (de[0] != c1 || de[1] != c2) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = (de - data) + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Bad checksum '%.2s', expected '%c%c'", de, c1, c2);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#define GS1_GCP_MIN_LENGTH 4 /* Minimum length of GS1 Company Prefix */
|
|
|
|
/* Check for a GS1 Prefix (GS1 General Specifications Release 26.0 1.2.3.3 GS1 Company Prefix) */
|
|
static int gs1_gcppos1(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
/* Do this check separately for backward compatibility */
|
|
if (data_len && data_len < 2) {
|
|
*p_err_no = 4;
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
int i;
|
|
|
|
if (data_len < GS1_GCP_MIN_LENGTH) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "GS1 Company Prefix length %d too short (minimum 4)", data_len);
|
|
}
|
|
|
|
data += offset;
|
|
|
|
for (i = 0; i < GS1_GCP_MIN_LENGTH; i++) {
|
|
if (!z_isdigit(data[i])) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + i + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Non-numeric company prefix '%c'", data[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for a GS1 Prefix at offset 1 (2nd position) */
|
|
static int gs1_gcppos2(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
return gs1_gcppos1(data, data_len, offset + 1, min - 1, max - 1, p_err_no, p_err_posn, err_msg, length_only);
|
|
}
|
|
|
|
/* Note following date/time checkers (!length_only) assume data all digits, i.e. `numeric()` has succeeded */
|
|
|
|
/* Check for a date YYYYMMDD with zero day allowed */
|
|
static int gs1_yyyymmd0(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
static const char days_in_month[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 8)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
int month, day;
|
|
|
|
month = z_to_int(data + offset + 4, 2);
|
|
if (month == 0 || month > 12) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 4 + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid month '%.2s'", data + offset + 4);
|
|
}
|
|
|
|
day = z_to_int(data + offset + 6, 2);
|
|
if (day && day > days_in_month[month]) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 6 + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid day '%.2s'", data + offset + 6);
|
|
}
|
|
/* Leap year check */
|
|
if (month == 2 && day == 29) {
|
|
const int year = z_to_int(data + offset, 4);
|
|
if ((year & 3) || (year % 100 == 0 && year % 400 != 0)) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 6 + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid day '%.2s'", data + offset + 6);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for a date YYYYMMDD. Zero day NOT allowed */
|
|
static int gs1_yyyymmdd(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
if (!gs1_yyyymmd0(data, data_len, offset, min, max, p_err_no, p_err_posn, err_msg, length_only)) {
|
|
return 0;
|
|
}
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (!length_only && data_len) {
|
|
const int day = z_to_int(data + offset + 6, 2);
|
|
if (day == 0) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 6 + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid day '%.2s'", data + offset + 6);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for a date YYMMDD with zero day allowed */
|
|
static int gs1_yymmd0(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 6)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
/* For leap year detection, only matters if 00 represents century divisible by 400 or not */
|
|
/* Following good until 2050 when 00 will mean 2100
|
|
(GS1 General Specifications Release 26.0 7.12 Determination of century in dates) */
|
|
unsigned char buf[8] = { '2', '0' };
|
|
|
|
memcpy(buf + 2, data + offset, 6);
|
|
if (!gs1_yyyymmd0(buf, 8, 0, min, max, p_err_no, p_err_posn, err_msg, length_only)) {
|
|
*p_err_posn += offset - 2;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for a date YYMMDD. Zero day NOT allowed */
|
|
static int gs1_yymmdd(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
if (!gs1_yymmd0(data, data_len, offset, min, max, p_err_no, p_err_posn, err_msg, length_only)) {
|
|
return 0;
|
|
}
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (!length_only && data_len) {
|
|
const int day = z_to_int(data + offset + 4, 2);
|
|
if (day == 0) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 4 + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid day '%.2s'", data + offset + 4);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for a time HHMI */
|
|
static int gs1_hhmi(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 4)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
int hour, mins;
|
|
|
|
hour = z_to_int(data + offset, 2);
|
|
if (hour > 23) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid hour of day '%.2s'", data + offset);
|
|
}
|
|
mins = z_to_int(data + offset + 2, 2);
|
|
if (mins > 59) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 2 + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid minutes in the hour '%.2s'", data + offset + 2);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for a time HH (hours) */
|
|
static int gs1_hh(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 2)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const int hour = z_to_int(data + offset, 2);
|
|
if (hour > 23) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid hour of day '%.2s'", data + offset);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for a time MI (minutes) */
|
|
static int gs1_mi(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 2)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const int mins = z_to_int(data + offset, 2);
|
|
if (mins > 59) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid minutes in the hour '%.2s'", data + offset);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for a time SS (seconds) */
|
|
static int gs1_ss(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 2)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const int secs = z_to_int(data + offset, 2);
|
|
if (secs > 59) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid seconds in the minute '%.2s'", data + offset);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Generated by "php backend/tools/gen_iso3166_h.php > backend/iso3166.h" */
|
|
#include "iso3166.h"
|
|
|
|
/* Check for an ISO 3166-1 numeric country code */
|
|
static int gs1_iso3166(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 3)) {
|
|
if (offset) {
|
|
/* For backward compatibility only warn if not first */
|
|
*p_err_no = 4;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
if (!iso3166_numeric(z_to_int(data + offset, 3))) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Unknown country code '%.3s'", data + offset);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for an ISO 3166-1 numeric country code allowing "999" */
|
|
static int gs1_iso3166999(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 3)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const int cc = z_to_int(data + offset, 3);
|
|
if (cc != 999 && !iso3166_numeric(cc)) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Unknown country code '%.3s'", data + offset);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for an ISO 3166-1 alpha2 country code */
|
|
static int gs1_iso3166alpha2(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 2)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
if (!iso3166_alpha2((const char *) (data + offset))) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Unknown country code '%.2s'", data + offset);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Generated by "php backend/tools/gen_iso4217_h.php > backend/iso4217.h" */
|
|
#include "iso4217.h"
|
|
|
|
/* Check for an ISO 4217 currency code */
|
|
static int gs1_iso4217(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 3)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
if (!iso4217_numeric(z_to_int(data + offset, 3))) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Unknown currency code '%.3s'", data + offset);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for percent encoded */
|
|
static int gs1_pcenc(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
static const char hex_chars[] = "0123456789ABCDEFabcdef";
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
|
|
for (; d < de; d++) {
|
|
if (*d == '%') {
|
|
if (de - d < 3) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Invalid % escape");
|
|
}
|
|
if (strchr(hex_chars, *(++d)) == NULL || strchr(hex_chars, *(++d)) == NULL) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Invalid character for percent encoding");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for yes/no (1/0) indicator */
|
|
static int gs1_yesno(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
if (data[offset] != '0' && data[offset] != '1') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Neither 0 nor 1 for yes or no");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for importer index
|
|
(GS1 General Specifications Release 26.0 3.8.18 GS1 UIC with Extension 1 and Importer index: AI (7040)) */
|
|
static int gs1_importeridx(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
|
|
/* 0-9, A-Z, a-z and "-", "_" */
|
|
if ((*d < '0' && *d != '-') || (*d > '9' && *d < 'A') || (*d > 'Z' && *d < 'a' && *d != '_') || *d > 'z') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid importer index '%c'", *d);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check non-zero */
|
|
static int gs1_nonzero(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const int val = z_to_int(data + offset, data_len > max ? max : data_len);
|
|
|
|
if (val == 0) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Zero not permitted");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check winding direction (0/1/9) (GS1 General Specifications Release 26.0 3.9.1 Roll products) */
|
|
static int gs1_winding(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
if (data[offset] != '0' && data[offset] != '1' && data[offset] != '9') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid winding direction '%c'", data[offset]);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check zero */
|
|
static int gs1_zero(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
if (data[offset] != '0') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Zero is required");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check piece of a trade item
|
|
(GS1 General Specifications Release 26.0 3.9.6 Identification of an individual trade item piece
|
|
and 3.9.18 Identification of pieces of a trade item (ITIP) contained in a logistic unit) */
|
|
static int gs1_pieceoftotal(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 4)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
int pieces, total;
|
|
|
|
pieces = z_to_int(data + offset, 2);
|
|
if (pieces == 0) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Piece number cannot be zero");
|
|
}
|
|
total = z_to_int(data + offset + 2, 2);
|
|
if (total == 0) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Total number cannot be zero");
|
|
}
|
|
if (pieces > total) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Piece number '%.2s' exceeds total '%.2s'",
|
|
data + offset, data + offset + 2);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check IBAN (ISO 13616) */
|
|
static int gs1_iban(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
/* Do this check separately for backward compatibility */
|
|
if (data_len && data_len < 5) {
|
|
*p_err_no = 4;
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
int checksum = 0;
|
|
int given_checksum;
|
|
|
|
/* 1st 2 chars alphabetic country code */
|
|
if (!z_isupper(d[0]) || !z_isupper(d[1])) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Non-alphabetic IBAN country code '%.2s'", d);
|
|
}
|
|
if (!iso3166_alpha2((const char *) d)) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid IBAN country code '%.2s'", d);
|
|
}
|
|
d += 2;
|
|
/* 2nd 2 chars numeric checksum */
|
|
if (!z_isdigit(d[0]) || !z_isdigit(d[1])) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Non-numeric IBAN checksum '%.2s'", d);
|
|
}
|
|
given_checksum = z_to_int(d, 2);
|
|
d += 2;
|
|
for (; d < de; d++) {
|
|
/* 0-9, A-Z */
|
|
if (*d < '0' || (*d > '9' && *d < 'A') || *d > 'Z') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid IBAN character '%c'", *d);
|
|
}
|
|
if (*d >= 'A') {
|
|
checksum = checksum * 100 + *d - 'A' + 10;
|
|
} else {
|
|
checksum = checksum * 10 + *d - '0';
|
|
}
|
|
checksum %= 97;
|
|
}
|
|
|
|
/* Add in country code */
|
|
checksum = (((checksum * 100) % 97) + (data[offset] - 'A' + 10)) * 100 + data[offset + 1] - 'A' + 10;
|
|
checksum %= 97;
|
|
|
|
checksum *= 100; /* Allow for checksum "00" */
|
|
checksum %= 97;
|
|
|
|
checksum = 98 - checksum;
|
|
|
|
if (checksum != given_checksum) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 2 + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Bad IBAN checksum '%.2s', expected '%02d'",
|
|
data + offset + 2, checksum);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check CPID does not begin with zero */
|
|
static int gs1_nozeroprefix(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
/* GS1 General Specifications Release 26.0 3.9.11 "The C/P serial number SHALL NOT begin with a "0" digit,
|
|
unless the entire serial number consists of the single digit '0'." */
|
|
if (data[0] == '0' && data_len != 1) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Zero prefix is not permitted");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Helper to parse coupon Variable Length Indicator (VLI) and associated field. If `vli_nine` set
|
|
* then a VLI of '9' means no field present */
|
|
static const unsigned char *gs1_coupon_vli(const unsigned char *data, const int data_len, const unsigned char *d,
|
|
const char *name, const int vli_offset, const int vli_min, const int vli_max, const int vli_nine,
|
|
int *p_err_no, int *p_err_posn, char err_msg[50]) {
|
|
const unsigned char *de;
|
|
int vli;
|
|
|
|
if (d - data + 1 > data_len) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
(void) gs1_err_msg_printf_nochk(err_msg, "%s VLI missing", name);
|
|
return NULL;
|
|
}
|
|
vli = z_to_int(d, 1);
|
|
if ((vli < vli_min || vli > vli_max) && (vli != 9 || !vli_nine)) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
if (vli < 0) {
|
|
(void) gs1_err_msg_printf_nochk(err_msg, "Non-numeric %s VLI '%c'", name, *d);
|
|
} else {
|
|
(void) gs1_err_msg_printf_nochk(err_msg, "Invalid %s VLI '%c'", name, *d);
|
|
}
|
|
return NULL;
|
|
}
|
|
d++;
|
|
if (vli != 9 || !vli_nine) {
|
|
if (d - data + vli + vli_offset > data_len) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
(void) gs1_err_msg_printf_nochk(err_msg, "%s incomplete", name);
|
|
return NULL;
|
|
}
|
|
de = d + vli + vli_offset;
|
|
for (; d < de; d++) {
|
|
if (!z_isdigit(*d)) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
(void) gs1_err_msg_printf_nochk(err_msg, "Non-numeric %s '%c'", name, *d);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
/* Helper to parse coupon value field (numeric) */
|
|
static const unsigned char *gs1_coupon_val(const unsigned char *data, const int data_len, const unsigned char *d,
|
|
const char *name, const int val_len, int *p_val, int *p_err_no, int *p_err_posn, char err_msg[50]) {
|
|
int val;
|
|
|
|
if (d - data + val_len > data_len) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
(void) gs1_err_msg_printf_nochk(err_msg, "%s incomplete", name);
|
|
return NULL;
|
|
}
|
|
val = z_to_int(d, val_len);
|
|
if (val < 0) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
(void) gs1_err_msg_printf_nochk(err_msg, "Non-numeric %s", name);
|
|
return NULL;
|
|
}
|
|
d += val_len;
|
|
|
|
if (p_val) {
|
|
*p_val = val;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
/* Check North American Coupon Code */
|
|
/* Note all fields including optional must be numeric so type could be N..70 */
|
|
static int gs1_couponcode(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
/* Minimum possible required fields length = 21
|
|
* (from "North American Coupon Application Guideline Using GS1 DataBar Expanded Symbols R2.0 (Feb 13 2015)")
|
|
* VLI - Variable Length Indicator; GCP - GS1 Company Prefix; OC - Offer Code; SV - Save Value;
|
|
* PPR - Primary Purchase Requirement; PPFC - Primary Purchase Family Code */
|
|
const int min_req_len = 1 /*GCP VLI*/ + 6 /*GCP*/ + 6 /*OC*/ + 1 /*SV VLI*/ + 1 /*SV*/
|
|
+ 1 /*PPR VLI*/ + 1 /*PPR*/ + 1 /*PPR Code*/ + 3 /*PPFC*/;
|
|
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
/* Do separately for backward compatibility */
|
|
if (data_len && data_len < min_req_len) {
|
|
*p_err_no = 4;
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
int val;
|
|
|
|
data_len += offset;
|
|
|
|
/* Required fields */
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "Primary GS1 Co. Prefix", 6, 0, 6, 0, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Offer Code", 6, NULL, p_err_no, p_err_posn, err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "Save Value", 0, 1, 5, 0, p_err_no, p_err_posn, err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "Primary Purch. Req.", 0, 1, 5, 0, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Primary Purch. Req. Code", 1, &val, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (val > 5 && val < 9) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid Primary Purch. Req. Code '%c'", *(d - 1));
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Primary Purch. Family Code", 3, NULL, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
|
|
/* Optional fields */
|
|
while (d - data < data_len) {
|
|
const int data_field = z_to_int(d, 1);
|
|
d++;
|
|
|
|
if (data_field == 1) {
|
|
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Add. Purch. Rules Code", 1, &val, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (val > 3) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid Add. Purch. Rules Code '%c'", *(d - 1));
|
|
}
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "2nd Purch. Req.", 0, 1, 5, 0, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "2nd Purch. Req. Code", 1, &val, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (val > 4 && val < 9) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid 2nd Purch. Req. Code '%c'", *(d - 1));
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "2nd Purch. Family Code", 3, NULL, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "2nd Purch. GS1 Co. Prefix", 6, 0, 6, 1, p_err_no,
|
|
p_err_posn, err_msg))) {
|
|
return 0;
|
|
}
|
|
|
|
} else if (data_field == 2) {
|
|
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "3rd Purch. Req.", 0, 1, 5, 0, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "3rd Purch. Req. Code", 1, &val, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (val > 4 && val < 9) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid 3rd Purch. Req. Code '%c'", *(d - 1));
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "3rd Purch. Family Code", 3, NULL, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "3rd Purch. GS1 Co. Prefix", 6, 0, 6, 1, p_err_no,
|
|
p_err_posn, err_msg))) {
|
|
return 0;
|
|
}
|
|
|
|
} else if (data_field == 3) {
|
|
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Expiration Date", 6, NULL, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!gs1_yymmd0(data, data_len, d - 6 - data, 6, 6, p_err_no, p_err_posn, err_msg, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
} else if (data_field == 4) {
|
|
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Start Date", 6, NULL, p_err_no, p_err_posn, err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!gs1_yymmd0(data, data_len, d - 6 - data, 6, 6, p_err_no, p_err_posn, err_msg, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
} else if (data_field == 5) {
|
|
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "Serial Number", 6, 0, 9, 0, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
|
|
} else if (data_field == 6) {
|
|
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "Retailer ID", 6, 1, 7, 0, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
|
|
} else if (data_field == 9) {
|
|
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Save Value Code", 1, &val, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if ((val > 2 && val < 5) || val > 6) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid Save Value Code '%c'", *(d - 1));
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Save Value Applies To", 1, &val, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (val > 2) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid Save Value Applies To '%c'", *(d - 1));
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Store Coupon Flag", 1, NULL, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Don't Multiply Flag", 1, &val, p_err_no, p_err_posn,
|
|
err_msg))) {
|
|
return 0;
|
|
}
|
|
if (val > 1) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid Don't Multiply Flag '%c'", *(d - 1));
|
|
}
|
|
|
|
} else {
|
|
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
if (data_field < 0) {
|
|
(void) gs1_err_msg_printf_nochk(err_msg, "Non-numeric Data Field '%c'", *(d - 1));
|
|
} else {
|
|
(void) gs1_err_msg_printf_nochk(err_msg, "Invalid Data Field '%c'", *(d - 1));
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check North American Positive Offer File */
|
|
/* Note max is currently set at 36 numeric digits with remaining 34 characters reserved */
|
|
static int gs1_couponposoffer(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
/* Minimum possible length = 21
|
|
* (from "GS1 AI (8112) Coupon Data Specifications Release 1.0 (March 2020)")
|
|
* CFMT - Coupon Format; CFID - Coupon Funder ID; VLI - Variable Length Indicator;
|
|
* OC - Offer Code; SN - Serial Number */
|
|
const int min_len = 1 /*CFMT*/ + 1 /*CFID VLI*/ + 6 /*CFID*/ + 6 /*OC*/ + 1 /*SN VLI*/ + 6 /*SN*/;
|
|
const int max_len = 36;
|
|
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
/* Do separately for backward compatibility */
|
|
if (data_len && (data_len < min_len || data_len > max_len)) {
|
|
*p_err_no = 4;
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
int val;
|
|
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Coupon Format", 1, &val, p_err_no, p_err_posn, err_msg))) {
|
|
return 0;
|
|
}
|
|
if (val != 0 && val != 1) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Coupon Format must be 0 or 1");
|
|
}
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "Coupon Funder ID", 6, 0, 6, 0, p_err_no, p_err_posn, err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_val(data, data_len, d, "Offer Code", 6, NULL, p_err_no, p_err_posn, err_msg))) {
|
|
return 0;
|
|
}
|
|
if (!(d = gs1_coupon_vli(data, data_len, d, "Serial Number", 6, 0, 9, 0, p_err_no, p_err_posn, err_msg))) {
|
|
return 0;
|
|
}
|
|
if (d - data != data_len) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Reserved trailing characters");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check WSG 84 latitude */
|
|
static int gs1_latitude(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 10)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
uint64_t lat = 0;
|
|
|
|
for (; d < de; d++) {
|
|
lat *= 10;
|
|
lat += *d - '0';
|
|
}
|
|
if (lat > 1800000000) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Invalid latitude");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check WSG 84 longitude */
|
|
static int gs1_longitude(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 10)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
uint64_t lng = 0;
|
|
|
|
for (; d < de; d++) {
|
|
lng *= 10;
|
|
lng += *d - '0';
|
|
}
|
|
if (lng > 3600000000) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - 1 - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Invalid longitude");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check AIDC media type (GSCN 22-345 Figure 3.8.22-2) */
|
|
static int gs1_mediatype(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min || (data_len && data_len < 2)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
unsigned int val = 0;
|
|
|
|
for (; d < de; d++) {
|
|
val *= 10;
|
|
val += *d - '0';
|
|
}
|
|
if (val == 0 || (val > 10 && val < 80)) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Invalid AIDC media type");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check negative temperature indicator (GSCN 22-353) */
|
|
static int gs1_hyphen(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
|
|
for (; d < de; d++) {
|
|
if (*d != '-') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Invalid temperature indicator (hyphen only)");
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for an ISO/IEC 5128 code for the representation of human sexes (GSCN 22-246) */
|
|
static int gs1_iso5218(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
/* 0 = Not known, 1 = Male, 2 = Female, 9 = Not applicable */
|
|
if (data[offset] != '0' && data[offset] != '1' && data[offset] != '2' && data[offset] != '9') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Invalid biological sex code (0, 1, 2 or 9 only)");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Validate sequence indicator, slash-separated (GSCN 22-246) */
|
|
static int gs1_posinseqslash(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
const unsigned char *slash = NULL;
|
|
int pos, tot;
|
|
|
|
for (; d < de; d++) {
|
|
if (!z_isdigit(*d)) {
|
|
if (*d != '/') {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid character '%c' in sequence", *d);
|
|
}
|
|
if (slash) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Single sequence separator ('/') only");
|
|
}
|
|
if (d == data + offset || d + 1 == de) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = d - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Sequence separator '/' cannot start or end");
|
|
}
|
|
slash = d;
|
|
}
|
|
}
|
|
if (!slash) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "No sequence separator ('/')");
|
|
}
|
|
pos = z_to_int(data + offset, slash - (data + offset));
|
|
if (pos == 0) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Sequence position cannot be zero");
|
|
}
|
|
tot = z_to_int(slash + 1, de - (slash + 1));
|
|
if (tot == 0) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = slash + 1 - data + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Sequence total cannot be zero");
|
|
}
|
|
if (pos > tot) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "Sequence position greater than total");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check that input contains non-digit (GSCN 21-283) */
|
|
static int gs1_hasnondigit(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
const unsigned char *d = data + offset;
|
|
const unsigned char *const de = d + (data_len > max ? max : data_len);
|
|
|
|
for (; d < de && z_isdigit(*d); d++);
|
|
|
|
if (d == de) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_cpy_nochk(err_msg, "A non-digit character is required");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Check for package type (GSCN 23-272) */
|
|
static int gs1_packagetype(const unsigned char *data, int data_len, int offset, int min, int max, int *p_err_no,
|
|
int *p_err_posn, char err_msg[50], const int length_only) {
|
|
|
|
/* Package type codes https://navigator.gs1.org/edi/codelist-details?name=PackageTypeCode */
|
|
static const char packagetypes2[381][2] = {
|
|
{'1','A'}, {'1','B'}, {'1','D'}, {'1','F'}, {'1','G'}, {'1','W'}, {'2','C'}, {'3','A'}, {'3','H'}, {'4','3'},
|
|
{'4','4'}, {'4','A'}, {'4','B'}, {'4','C'}, {'4','D'}, {'4','F'}, {'4','G'}, {'4','H'}, {'5','H'}, {'5','L'},
|
|
{'5','M'}, {'6','H'}, {'6','P'}, {'7','A'}, {'7','B'}, {'8','A'}, {'8','B'}, {'8','C'}, {'A','A'}, {'A','B'},
|
|
{'A','C'}, {'A','D'}, {'A','F'}, {'A','G'}, {'A','H'}, {'A','I'}, {'A','J'}, {'A','L'}, {'A','M'}, {'A','P'},
|
|
{'A','T'}, {'A','V'}, {'B','4'}, {'B','B'}, {'B','C'}, {'B','D'}, {'B','E'}, {'B','F'}, {'B','G'}, {'B','H'},
|
|
{'B','I'}, {'B','J'}, {'B','K'}, {'B','L'}, {'B','M'}, {'B','N'}, {'B','O'}, {'B','P'}, {'B','Q'}, {'B','R'},
|
|
{'B','S'}, {'B','T'}, {'B','U'}, {'B','V'}, {'B','W'}, {'B','X'}, {'B','Y'}, {'B','Z'}, {'C','A'}, {'C','B'},
|
|
{'C','C'}, {'C','D'}, {'C','E'}, {'C','F'}, {'C','G'}, {'C','H'}, {'C','I'}, {'C','J'}, {'C','K'}, {'C','L'},
|
|
{'C','M'}, {'C','N'}, {'C','O'}, {'C','P'}, {'C','Q'}, {'C','R'}, {'C','S'}, {'C','T'}, {'C','U'}, {'C','V'},
|
|
{'C','W'}, {'C','X'}, {'C','Y'}, {'C','Z'}, {'D','A'}, {'D','B'}, {'D','C'}, {'D','G'}, {'D','H'}, {'D','I'},
|
|
{'D','J'}, {'D','K'}, {'D','L'}, {'D','M'}, {'D','N'}, {'D','P'}, {'D','R'}, {'D','S'}, {'D','T'}, {'D','U'},
|
|
{'D','V'}, {'D','W'}, {'D','X'}, {'D','Y'}, {'E','1'}, {'E','2'}, {'E','3'}, {'E','C'}, {'E','D'}, {'E','E'},
|
|
{'E','F'}, {'E','G'}, {'E','H'}, {'E','I'}, {'E','N'}, {'F','B'}, {'F','C'}, {'F','D'}, {'F','E'}, {'F','I'},
|
|
{'F','L'}, {'F','O'}, {'F','P'}, {'F','R'}, {'F','T'}, {'F','W'}, {'F','X'}, {'G','B'}, {'G','I'}, {'G','L'},
|
|
{'G','R'}, {'G','U'}, {'G','Y'}, {'G','Z'}, {'H','A'}, {'H','B'}, {'H','C'}, {'H','G'}, {'H','N'}, {'H','R'},
|
|
{'I','A'}, {'I','B'}, {'I','C'}, {'I','D'}, {'I','E'}, {'I','F'}, {'I','G'}, {'I','H'}, {'I','K'}, {'I','L'},
|
|
{'I','N'}, {'I','Z'}, {'J','B'}, {'J','C'}, {'J','G'}, {'J','R'}, {'J','T'}, {'J','Y'}, {'K','G'}, {'K','I'},
|
|
{'L','E'}, {'L','G'}, {'L','T'}, {'L','U'}, {'L','V'}, {'L','Z'}, {'M','A'}, {'M','B'}, {'M','C'}, {'M','E'},
|
|
{'M','R'}, {'M','S'}, {'M','T'}, {'M','W'}, {'M','X'}, {'N','A'}, {'N','E'}, {'N','F'}, {'N','G'}, {'N','S'},
|
|
{'N','T'}, {'N','U'}, {'N','V'}, {'O','A'}, {'O','B'}, {'O','C'}, {'O','D'}, {'O','E'}, {'O','F'}, {'O','K'},
|
|
{'O','T'}, {'O','U'}, {'P','2'}, {'P','A'}, {'P','B'}, {'P','C'}, {'P','D'}, {'P','E'}, {'P','F'}, {'P','G'},
|
|
{'P','H'}, {'P','I'}, {'P','J'}, {'P','K'}, {'P','L'}, {'P','N'}, {'P','O'}, {'P','P'}, {'P','R'}, {'P','T'},
|
|
{'P','U'}, {'P','V'}, {'P','X'}, {'P','Y'}, {'P','Z'}, {'Q','A'}, {'Q','B'}, {'Q','C'}, {'Q','D'}, {'Q','F'},
|
|
{'Q','G'}, {'Q','H'}, {'Q','J'}, {'Q','K'}, {'Q','L'}, {'Q','M'}, {'Q','N'}, {'Q','P'}, {'Q','Q'}, {'Q','R'},
|
|
{'Q','S'}, {'R','D'}, {'R','G'}, {'R','J'}, {'R','K'}, {'R','L'}, {'R','O'}, {'R','T'}, {'R','Z'}, {'S','1'},
|
|
{'S','A'}, {'S','B'}, {'S','C'}, {'S','D'}, {'S','E'}, {'S','H'}, {'S','I'}, {'S','K'}, {'S','L'}, {'S','M'},
|
|
{'S','O'}, {'S','P'}, {'S','S'}, {'S','T'}, {'S','U'}, {'S','V'}, {'S','W'}, {'S','X'}, {'S','Y'}, {'S','Z'},
|
|
{'T','1'}, {'T','B'}, {'T','C'}, {'T','D'}, {'T','E'}, {'T','G'}, {'T','I'}, {'T','K'}, {'T','L'}, {'T','N'},
|
|
{'T','O'}, {'T','R'}, {'T','S'}, {'T','T'}, {'T','U'}, {'T','V'}, {'T','W'}, {'T','Y'}, {'T','Z'}, {'U','C'},
|
|
{'U','N'}, {'V','A'}, {'V','G'}, {'V','I'}, {'V','K'}, {'V','L'}, {'V','N'}, {'V','O'}, {'V','P'}, {'V','Q'},
|
|
{'V','R'}, {'V','S'}, {'V','Y'}, {'W','A'}, {'W','B'}, {'W','C'}, {'W','D'}, {'W','F'}, {'W','G'}, {'W','H'},
|
|
{'W','J'}, {'W','K'}, {'W','L'}, {'W','M'}, {'W','N'}, {'W','P'}, {'W','Q'}, {'W','R'}, {'W','S'}, {'W','T'},
|
|
{'W','U'}, {'W','V'}, {'W','W'}, {'W','X'}, {'W','Y'}, {'W','Z'}, {'X','3'}, {'X','A'}, {'X','B'}, {'X','C'},
|
|
{'X','D'}, {'X','F'}, {'X','G'}, {'X','H'}, {'X','J'}, {'X','K'}, {'Y','A'}, {'Y','B'}, {'Y','C'}, {'Y','D'},
|
|
{'Y','F'}, {'Y','G'}, {'Y','H'}, {'Y','J'}, {'Y','K'}, {'Y','L'}, {'Y','M'}, {'Y','N'}, {'Y','P'}, {'Y','Q'},
|
|
{'Y','R'}, {'Y','S'}, {'Y','T'}, {'Y','V'}, {'Y','W'}, {'Y','X'}, {'Y','Y'}, {'Y','Z'}, {'Z','A'}, {'Z','B'},
|
|
{'Z','C'}, {'Z','D'}, {'Z','F'}, {'Z','G'}, {'Z','H'}, {'Z','J'}, {'Z','K'}, {'Z','L'}, {'Z','M'}, {'Z','N'},
|
|
{'Z','P'}, {'Z','Q'}, {'Z','R'}, {'Z','S'}, {'Z','T'}, {'Z','U'}, {'Z','V'}, {'Z','W'}, {'Z','X'}, {'Z','Y'},
|
|
{'Z','Z'},
|
|
};
|
|
static const char packagetypes3[48][3] = {
|
|
{'2','0','0'}, {'2','0','1'}, {'2','0','2'}, {'2','0','3'}, {'2','0','4'},
|
|
{'2','0','5'}, {'2','0','6'}, {'2','1','0'}, {'2','1','1'}, {'2','1','2'},
|
|
{'A','P','E'}, {'B','G','E'}, {'B','M','E'}, {'B','R','I'}, {'C','B','L'},
|
|
{'C','C','E'}, {'D','P','E'}, {'F','O','B'}, {'F','P','E'}, {'L','A','B'},
|
|
{'M','P','E'}, {'O','P','E'}, {'P','A','E'}, {'P','L','P'}, {'P','O','P'},
|
|
{'P','P','E'}, {'P','U','E'}, {'R','B','1'}, {'R','B','2'}, {'R','B','3'},
|
|
{'R','C','B'}, {'S','E','C'}, {'S','T','L'}, {'T','E','V'}, {'T','H','E'},
|
|
{'T','R','E'}, {'T','T','E'}, {'T','W','E'}, {'U','U','E'}, {'W','R','P'},
|
|
{'X','1','1'}, {'X','1','2'}, {'X','1','5'}, {'X','1','6'}, {'X','1','7'},
|
|
{'X','1','8'}, {'X','1','9'}, {'X','2','0'},
|
|
};
|
|
|
|
(void)max;
|
|
|
|
data_len = data_len < offset ? 0 : data_len - offset;
|
|
|
|
if (data_len < min) {
|
|
return 0;
|
|
}
|
|
|
|
if (!length_only && data_len) {
|
|
/* Adapted from GS1 Syntax Dictionary and Linters
|
|
https://github.com/gs1/gs1-syntax-dictionary/blob/main/src/lint_packagetype.c */
|
|
/* SPDX-License-Identifier: Apache-2.0 */
|
|
const char *const d = (const char *const) (data + offset);
|
|
int valid = 0;
|
|
|
|
assert(2 /*single 8/9*/ + ARRAY_SIZE(packagetypes2) + ARRAY_SIZE(packagetypes3) == 431);
|
|
|
|
if (data_len == 1) {
|
|
valid = *d == '8' || *d == '9';
|
|
} else if (data_len == 2) {
|
|
int s = 0;
|
|
int e = ARRAY_SIZE(packagetypes2);
|
|
|
|
while (s < e) {
|
|
const int m = s + (e - s) / 2;
|
|
const int cmp = memcmp(packagetypes2[m], d, 2);
|
|
if (cmp < 0) {
|
|
s = m + 1;
|
|
} else if (cmp > 0) {
|
|
e = m;
|
|
} else {
|
|
valid = 1;
|
|
break;
|
|
}
|
|
}
|
|
} else if (data_len == 3) {
|
|
int s = 0;
|
|
int e = ARRAY_SIZE(packagetypes3);
|
|
|
|
while (s < e) {
|
|
const int m = s + (e - s) / 2;
|
|
const int cmp = memcmp(packagetypes3[m], d, 3);
|
|
if (cmp < 0) {
|
|
s = m + 1;
|
|
} else if (cmp > 0) {
|
|
e = m;
|
|
} else {
|
|
valid = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!valid) {
|
|
*p_err_no = 3;
|
|
*p_err_posn = offset + 1;
|
|
return gs1_err_msg_printf_nochk(err_msg, "Invalid package type '%.*s'", data_len, d);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Helper to say if AI pointed to by `source` has predefined length according to GS1 General Specifications 26.0
|
|
Figure 7-6 "Element strings with predefined length using GS1 Application Identifiers" */
|
|
static int gs1_predefined_len(const unsigned char source[]) {
|
|
const int ai2 = z_to_int(source, 2); /* First 2 digits */
|
|
|
|
/* NOTE: previously allowed legacy 23, removed 2026-02-26 */
|
|
return (ai2 >= 0 && ai2 <= 4) || (ai2 >= 11 && ai2 <= 20) || (ai2 >= 31 && ai2 <= 36) || ai2 == 41;
|
|
}
|
|
|
|
/* Generated by "php backend/tools/gen_gs1_linter.php > backend/gs1_lint.h" */
|
|
#include "gs1_lint.h"
|
|
|
|
#ifdef ZINT_TEST /* Wrapper for direct testing */
|
|
INTERNAL int zint_test_gs1_lint_parse_raw_caret(const unsigned char source[], const int length,
|
|
const int ai_max, int *ai_vals, int *ai_locs, int *data_locs, int *data_lens, int *p_ai_count,
|
|
int *p_err_no, int *p_err_posn) {
|
|
return gs1_lint_parse_raw_caret(source, length,
|
|
ai_max, ai_vals, ai_locs, data_locs, data_lens, p_ai_count, p_err_no, p_err_posn);
|
|
}
|
|
#endif
|
|
|
|
/* Whether starts with Digital Link URI */
|
|
static int gs1_is_digital_link(const unsigned char *source, const int length) {
|
|
return length >= 7 && (memcmp(source, "http://", 7) == 0 || memcmp(source, "HTTP://", 7) == 0
|
|
|| (length >= 8 && (memcmp(source, "https://", 8) == 0
|
|
|| memcmp(source, "HTTPS://", 8) == 0)));
|
|
}
|
|
|
|
/* If built with GS1 Syntax Engine library */
|
|
#ifdef ZINT_HAVE_GS1SE
|
|
|
|
/* Helper to set `errtxt` on internal error and exit, freeing `ctx` */
|
|
static int gs1se_internal_errtxt(struct zint_symbol *symbol, const int err_id, gs1_encoder *ctx) {
|
|
const char *errmsg = gs1_encoder_getErrMsg(ctx);
|
|
z_errtxtf(0, symbol, err_id, "Internal error using GS1 Syntax Engine: %.80s", *errmsg ? errmsg : "unknown");
|
|
gs1_encoder_free(ctx);
|
|
return ZINT_ERROR_ENCODING_PROBLEM;
|
|
}
|
|
|
|
/* Helper to replace GSs with carets */
|
|
static void gs1se_gs_caret_sub(const unsigned char *src, const int length, unsigned char *dst) {
|
|
int i;
|
|
for (i = 0; i < length; i++) {
|
|
dst[i] = src[i] == '\x1D' ? '^' : src[i];
|
|
}
|
|
dst[length] = '\0';
|
|
}
|
|
|
|
/* Use GS1 Syntax Engine to verify */
|
|
static int gs1se_verify(struct zint_symbol *symbol, const unsigned char source[], const int length,
|
|
unsigned char reduced[], int *p_reduced_length) {
|
|
int i, j;
|
|
const int primary_len = z_is_composite(symbol->symbology) ? (int) strlen(symbol->primary) : 0;
|
|
const int gs1parens_mode = symbol->input_mode & GS1PARENS_MODE;
|
|
const int gs1raw_mode = !!(symbol->input_mode & GS1RAW_MODE);
|
|
const int gs1_caret = source[0] == '^';
|
|
const int is_digital_link = !primary_len && !gs1_caret && gs1_is_digital_link(source, length);
|
|
const int parens_cnt = !gs1parens_mode && !gs1raw_mode && !gs1_caret ? z_chr_cnt(source, length, '(') : 0;
|
|
|
|
int local_length = length;
|
|
const unsigned char *local_source = source;
|
|
const unsigned char *local_source2 = source;
|
|
unsigned char *local_source_buf = (unsigned char *) z_alloca(ARRAY_SIZE(symbol->primary) + 4 + length + 1);
|
|
unsigned char *local_source2_buf = (unsigned char *) z_alloca(ARRAY_SIZE(symbol->primary) * 2 + length
|
|
+ parens_cnt + 1);
|
|
int linear_len = 0;
|
|
char msgBuf[120];
|
|
gs1_encoder_init_status_t status = GS1_ENCODERS_INIT_SUCCESS;
|
|
gs1_encoder_init_opts_t opts = {
|
|
/* NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) suppress clang-tidy-21 warning OR-ing enums */
|
|
sizeof(gs1_encoder_init_opts_t), gs1_encoder_iNO_SYNDICT | gs1_encoder_iQUIET, &status, msgBuf, sizeof(msgBuf)
|
|
};
|
|
gs1_encoder *ctx;
|
|
int gs1se_ret;
|
|
|
|
if (length < 2 + gs1_caret) {
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 802, "Data does not start with an AI");
|
|
}
|
|
|
|
/* Only matrix symbols can encode Digital Link URIs */
|
|
if (is_digital_link && !z_is_fixed_ratio(symbol->symbology)) {
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 264, "Data does not start with an AI");
|
|
}
|
|
|
|
/* Need "linear|composite" format to check required AIs */
|
|
if (primary_len) {
|
|
if (symbol->symbology == BARCODE_GS1_128_CC || symbol->symbology == BARCODE_DBAR_EXP_CC
|
|
|| symbol->symbology == BARCODE_DBAR_EXPSTK_CC) {
|
|
/* These take GS1 data in the linear part */
|
|
if (gs1raw_mode) {
|
|
/* Need to add initial carets */
|
|
local_source_buf[0] = '^';
|
|
local_length = 1;
|
|
/* Linear GSs */
|
|
if (symbol->primary[0] == '\x1D') { /* Allow initial GS */
|
|
gs1se_gs_caret_sub(ZCUCP(symbol->primary + 1), primary_len - 1, local_source_buf + local_length);
|
|
local_length += primary_len - 1;
|
|
} else {
|
|
gs1se_gs_caret_sub(ZCUCP(symbol->primary), primary_len, local_source_buf + local_length);
|
|
local_length += primary_len;
|
|
}
|
|
local_source_buf[local_length++] = '|';
|
|
local_source_buf[local_length++] = '^';
|
|
/* CC GSs */
|
|
if (source[0] == '\x1D') { /* Allow initial GS */
|
|
gs1se_gs_caret_sub(source + 1, length - 1, local_source_buf + local_length);
|
|
local_length += length - 1;
|
|
} else {
|
|
gs1se_gs_caret_sub(source, length, local_source_buf + local_length);
|
|
local_length += length;
|
|
}
|
|
linear_len = primary_len - (symbol->primary[0] == '\x1D'); /* Exclude initial GS if any */
|
|
} else {
|
|
memcpy(local_source_buf, symbol->primary, primary_len);
|
|
local_source_buf[primary_len] = '|';
|
|
memcpy(local_source_buf + primary_len + 1, source, length + 1); /* Include terminating NUL */
|
|
local_length += primary_len + 1;
|
|
linear_len = primary_len - 1; /* Only actual linear data counts - exclude initial caret */
|
|
}
|
|
} else {
|
|
/* Just use dummy "01" linear */
|
|
if (gs1_caret || gs1raw_mode) {
|
|
memcpy(local_source_buf, "^0112345678901231|^", 18 + gs1raw_mode);
|
|
if (gs1_caret) {
|
|
memcpy(local_source_buf + 18, source, length + 1); /* Include terminating NUL */
|
|
} else {
|
|
gs1se_gs_caret_sub(source, length, local_source_buf + 19);
|
|
}
|
|
local_length += 18 + gs1raw_mode;
|
|
} else {
|
|
memcpy(local_source_buf, gs1parens_mode ? "(01)12345678901231|" : "[01]12345678901231|", 19);
|
|
memcpy(local_source_buf + 19, source, length + 1); /* Include terminating NUL */
|
|
local_length += 19;
|
|
}
|
|
linear_len = 16; /* Only actual linear data "0112345678901231" counts */
|
|
}
|
|
local_source = local_source_buf;
|
|
local_source2 = local_source_buf;
|
|
} else if (gs1raw_mode && !is_digital_link) {
|
|
if (symbol->symbology == BARCODE_GS1_128 || symbol->symbology == BARCODE_DBAR_EXP
|
|
|| symbol->symbology == BARCODE_DBAR_EXPSTK || z_is_fixed_ratio(symbol->symbology)
|
|
|| symbol->symbology == BARCODE_CODABLOCKF) { /* Codablock-F will be GS1-enabled in the future */
|
|
/* Prefix caret */
|
|
local_source_buf[0] = '^';
|
|
if (source[0] == '\x1D') { /* Allow initial GS */
|
|
gs1se_gs_caret_sub(source + 1, length - 1, local_source_buf + 1);
|
|
} else {
|
|
gs1se_gs_caret_sub(source, length, local_source_buf + 1);
|
|
local_length++;
|
|
}
|
|
} else {
|
|
/* Just copy over */
|
|
memcpy(local_source_buf, source, length + 1); /* Include terminating NULL */
|
|
}
|
|
local_source = local_source_buf;
|
|
local_source2 = local_source_buf;
|
|
}
|
|
|
|
if (!is_digital_link && !gs1raw_mode && !gs1_caret) {
|
|
assert(local_length > 0);
|
|
/* Convert to GS1 Syntax Engine parenthesis mode */
|
|
if (!gs1parens_mode) {
|
|
/* Replace '[' and ']' with '(' and ')' & escape any data opening parentheses (not closing parentheses) */
|
|
int bracket_level = 0; /* Non-compliant closing square brackets may be in text */
|
|
for (i = 0, j = 0; i < local_length; i++) {
|
|
if (local_source[i] == '[') {
|
|
local_source2_buf[j++] = '(';
|
|
bracket_level++;
|
|
} else if (local_source[i] == ']' && bracket_level) {
|
|
local_source2_buf[j++] = ')';
|
|
bracket_level--;
|
|
} else {
|
|
if (local_source[i] == '(') {
|
|
local_source2_buf[j++] = '\\';
|
|
}
|
|
local_source2_buf[j++] = local_source[i];
|
|
}
|
|
}
|
|
local_source2_buf[j] = '\0';
|
|
local_source2 = local_source2_buf;
|
|
/* Unescape any backslashed closing parentheses */
|
|
} else if (strchr(ZCCP(local_source), '\\') != NULL) {
|
|
for (i = 0, j = 0; i < local_length; i++) {
|
|
if (local_source[i] == '\\' && i + 1 < local_length && local_source[i + 1] == ')') {
|
|
i++;
|
|
}
|
|
local_source2_buf[j++] = local_source[i];
|
|
}
|
|
local_source2_buf[j] = '\0';
|
|
local_source2 = local_source2_buf;
|
|
}
|
|
}
|
|
|
|
if (!(ctx = gs1_encoder_init_ex(NULL /*mem*/, &opts))) {
|
|
const int error_number = status == GS1_ENCODERS_INIT_FAILED_NO_MEM
|
|
? ZINT_ERROR_MEMORY : ZINT_ERROR_ENCODING_PROBLEM;
|
|
return z_errtxtf(error_number, symbol, 266, "Failed to initialize GS1 Syntax Engine: %.80s",
|
|
*opts.msgBuf ? opts.msgBuf : "unknown error");
|
|
}
|
|
/* Do not check for required checks for GS1-128 as may be spread across multiple barcodes - ticket #348
|
|
and https://github.com/gs1/gs1-syntax-dictionary/issues/24 */
|
|
if (symbol->symbology == BARCODE_GS1_128) {
|
|
if (!gs1_encoder_setValidationEnabled(ctx, gs1_encoder_vREQUISITE_AIS, false)) {
|
|
return gs1se_internal_errtxt(symbol, 0 /*err_id*/, ctx);
|
|
}
|
|
}
|
|
|
|
if (is_digital_link || gs1raw_mode || gs1_caret) {
|
|
/* Required for `gs1_encoder_getScanData()` */
|
|
gs1_encoder_setSym(ctx, primary_len ? gs1_encoder_sGS1_128_CCA : gs1_encoder_sDM);
|
|
gs1se_ret = gs1_encoder_setDataStr(ctx, ZCCP(local_source2));
|
|
} else {
|
|
gs1se_ret = gs1_encoder_setAIdataStr(ctx, ZCCP(local_source2));
|
|
}
|
|
if (!gs1se_ret) {
|
|
const char *errmsg = gs1_encoder_getErrMsg(ctx);
|
|
const int errmsg_len = (int) strlen(errmsg);
|
|
const char *errmarkup = gs1_encoder_getErrMarkup(ctx);
|
|
int errmarkup_len = (int) strlen(errmarkup);
|
|
int errtxt_set = 0;
|
|
if (errmsg_len && errmarkup_len && errmsg_len + 1 + errmarkup_len < ARRAY_SIZE(symbol->errtxt)) {
|
|
char *local_errmarkup = (char *) z_alloca(errmarkup_len * 4 + 1);
|
|
z_debug_print_escape(ZCUCP(errmarkup), errmarkup_len, local_errmarkup);
|
|
errmarkup_len = (int) strlen(local_errmarkup);
|
|
if (errmsg_len + 1 + errmarkup_len < ARRAY_SIZE(symbol->errtxt)) {
|
|
ZEXT z_errtxtf(0, symbol, 267, "%1$s %2$s", errmsg, local_errmarkup);
|
|
errtxt_set = 1;
|
|
}
|
|
}
|
|
if (!errtxt_set) {
|
|
z_errtxt(0, symbol, 268, *errmsg ? errmsg : "Unknown error using GS1 Syntax Engine");
|
|
}
|
|
gs1_encoder_free(ctx);
|
|
return ZINT_ERROR_INVALID_DATA;
|
|
}
|
|
|
|
/* If Digital Link or raw/caret mode, set `reduced` */
|
|
if (is_digital_link || gs1raw_mode || gs1_caret) {
|
|
const char *scan_data;
|
|
int reduced_length;
|
|
|
|
if (!(scan_data = gs1_encoder_getScanData(ctx))) { /* Shouldn't happen */
|
|
return gs1se_internal_errtxt(symbol, 269 /*err_id*/, ctx);
|
|
}
|
|
reduced_length = (int) strlen(scan_data);
|
|
|
|
/* Skip over Symbology Identifier */
|
|
if (reduced_length >= 3 && scan_data[0] == ']') {
|
|
scan_data += 3;
|
|
reduced_length -= 3;
|
|
}
|
|
/* Skip over any linear stuff */
|
|
if (linear_len) {
|
|
scan_data += linear_len;
|
|
reduced_length -= linear_len;
|
|
/* Need to check if the last linear AI was non-predefined length, when a GS is added */
|
|
if (*scan_data == '\x1D') { /* Skip */
|
|
scan_data++;
|
|
reduced_length--;
|
|
}
|
|
}
|
|
assert(reduced_length <= length);
|
|
|
|
memcpy(reduced, scan_data, reduced_length + 1); /* Include terminating NUL */
|
|
reduced[reduced_length] = '\0';
|
|
*p_reduced_length = reduced_length;
|
|
} else {
|
|
*p_reduced_length = 0; /* `reduced` not set */
|
|
}
|
|
|
|
gs1_encoder_free(ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* ZINT_HAVE_GS1SE */
|
|
|
|
/* Check for valid AI values and data lengths according to GS1 General Specifications Release 26, January 2026 */
|
|
static int gs1_run_lint(struct zint_symbol *symbol, const unsigned char source[], const int ai_count,
|
|
int *ai_vals, int *ai_locs, int *data_locs, int *data_lens) {
|
|
int i;
|
|
int error_number = 0;
|
|
|
|
for (i = 0; i < ai_count; i++) {
|
|
int err_no, err_posn;
|
|
char err_msg[50];
|
|
if (!gs1_lint(ai_vals[i], source + data_locs[i], data_lens[i], &err_no, &err_posn,
|
|
err_msg)) {
|
|
if (err_no == 1) {
|
|
ZEXT z_errtxtf(0, symbol, 260, "Invalid AI (%1$02d) at position %2$d",
|
|
ai_vals[i], ai_locs[i] + 1);
|
|
} else if (err_no == 2 || err_no == 4) { /* 4 is backward-incompatible bad length */
|
|
ZEXT z_errtxtf(0, symbol, 259, "Invalid data length for AI (%1$02d) at position %2$d",
|
|
ai_vals[i], ai_locs[i] + 1);
|
|
} else {
|
|
ZEXT z_errtxtf(0, symbol, 261, "AI (%1$02d) data position %2$d: %3$s", ai_vals[i], err_posn, err_msg);
|
|
}
|
|
/* For backward compatibility only error on unknown AI or bad length */
|
|
if (err_no == 1 || err_no == 2) {
|
|
return ZINT_ERROR_INVALID_DATA;
|
|
}
|
|
error_number = ZINT_WARN_NONCOMPLIANT;
|
|
}
|
|
}
|
|
return error_number;
|
|
}
|
|
|
|
/* Helper to convert parsed AIs to HRT */
|
|
static int gs1_hrt_conv_parsed(struct zint_symbol *symbol, const unsigned char source[], const int length,
|
|
const int ai_count, const int *ai_vals, const int *ai_locs, const int *data_locs, const int *data_lens,
|
|
const int no_errtxt) {
|
|
int i, j;
|
|
int warn_number = 0;
|
|
const int text_size = ARRAY_SIZE(symbol->text);
|
|
#ifdef NDEBUG
|
|
(void)length;
|
|
#endif
|
|
|
|
for (i = 0, j = 0; i < ai_count && j < text_size; i++) {
|
|
const int ai_len = 2 + (ai_vals[i] >= 100) + (ai_vals[i] >= 1000);
|
|
if (j + 1 + ai_len + 1 + data_lens[i] >= text_size) {
|
|
warn_number = ZINT_WARN_HRT_TRUNCATED;
|
|
break;
|
|
}
|
|
symbol->text[j++] = '(';
|
|
memcpy(symbol->text + j, source + ai_locs[i], ai_len);
|
|
j += ai_len;
|
|
symbol->text[j++] = ')';
|
|
assert(data_locs[i] + data_lens[i] <= length);
|
|
memcpy(symbol->text + j, source + data_locs[i], data_lens[i]);
|
|
j += data_lens[i];
|
|
}
|
|
|
|
if (j == text_size) {
|
|
warn_number = ZINT_WARN_HRT_TRUNCATED;
|
|
j--;
|
|
}
|
|
symbol->text_length = j;
|
|
symbol->text[j] = '\0';
|
|
|
|
if (warn_number && !no_errtxt) {
|
|
z_errtxt(0, symbol, 799, "Human Readable Text truncated");
|
|
}
|
|
return warn_number;
|
|
|
|
}
|
|
|
|
#ifdef ZINT_TEST /* Wrapper for direct testing */
|
|
INTERNAL int zint_test_gs1_hrt_conv_parsed(struct zint_symbol *symbol, const unsigned char source[], const int length,
|
|
const int ai_count, const int *ai_vals, const int *ai_locs, const int *data_locs, const int *data_lens,
|
|
const int no_errtxt) {
|
|
return gs1_hrt_conv_parsed(symbol, source, length, ai_count, ai_vals, ai_locs, data_locs, data_lens,
|
|
no_errtxt);
|
|
}
|
|
#endif
|
|
|
|
/* Helper to set HRT when have GS1PARENS_MODE - truncates if too long for HRT buffer */
|
|
static int gs1_hrt_cpy(struct zint_symbol *symbol, const unsigned char source[], const int length,
|
|
const int no_errtxt) {
|
|
int warn_number;
|
|
const int text_size = ARRAY_SIZE(symbol->text);
|
|
|
|
assert(symbol->input_mode & GS1PARENS_MODE);
|
|
assert(source[0] == '(');
|
|
|
|
if (length < text_size) {
|
|
memcpy(symbol->text, source, length + 1); /* Include terminating NUL */
|
|
symbol->text_length = length;
|
|
warn_number = 0;
|
|
} else {
|
|
/* Find last full AI - need to scan forward to track escaped opening parens */
|
|
int i, last_opening_bracket = 0;
|
|
for (i = 0; i < text_size; i++) {
|
|
if (source[i] == '\\' && i + 1 < length && (source[i + 1] == '\\' || source[i + 1] == '(')) {
|
|
i++;
|
|
} else if (source[i] == '(') {
|
|
last_opening_bracket = i;
|
|
}
|
|
}
|
|
if (text_size < length && source[text_size] == '(') {
|
|
/* Finished at end of AI data */
|
|
memcpy(symbol->text, source, text_size - 1);
|
|
symbol->text_length = text_size - 1;
|
|
symbol->text[text_size - 1] = '\0';
|
|
} else {
|
|
/* Use last full AI + data */
|
|
memcpy(symbol->text, source, last_opening_bracket);
|
|
symbol->text_length = last_opening_bracket;
|
|
symbol->text[last_opening_bracket] = '\0';
|
|
}
|
|
warn_number = ZINT_WARN_HRT_TRUNCATED;
|
|
if (!no_errtxt) {
|
|
z_errtxt(0, symbol, 801, "Human Readable Text truncated");
|
|
}
|
|
}
|
|
return warn_number;
|
|
}
|
|
|
|
#ifdef ZINT_TEST /* Wrapper for direct testing */
|
|
INTERNAL int zint_test_gs1_hrt_cpy(struct zint_symbol *symbol, const unsigned char source[], const int length,
|
|
const int no_errtxt) {
|
|
return gs1_hrt_cpy(symbol, source, length, no_errtxt);
|
|
}
|
|
#endif
|
|
|
|
/* Helper to set HRT when don't have GS1PARENS_MODE - substitutes square brackets with parentheses
|
|
& truncates if too long for HRT buffer */
|
|
static int gs1_hrt_conv_brackets(struct zint_symbol *symbol, const unsigned char source[], const int length,
|
|
const int no_errtxt) {
|
|
int i;
|
|
int warn_number = 0;
|
|
int bracket_level = 0; /* Non-compliant closing square brackets may be in text */
|
|
int last_opening_bracket = 0;
|
|
const int text_size = ARRAY_SIZE(symbol->text);
|
|
const int max_len = length > text_size ? text_size : length;
|
|
|
|
assert((symbol->input_mode & GS1PARENS_MODE) == 0);
|
|
assert(source[0] == '[');
|
|
|
|
for (i = 0; i < max_len; i++) {
|
|
if (source[i] == '[') {
|
|
symbol->text[i] = '(';
|
|
bracket_level++;
|
|
last_opening_bracket = i;
|
|
} else if (source[i] == ']' && bracket_level) {
|
|
symbol->text[i] = ')';
|
|
bracket_level--;
|
|
} else {
|
|
symbol->text[i] = source[i];
|
|
}
|
|
}
|
|
if (i == text_size) {
|
|
i--;
|
|
if (source[i] != '[') {
|
|
i = last_opening_bracket;
|
|
}
|
|
warn_number = ZINT_WARN_HRT_TRUNCATED;
|
|
}
|
|
symbol->text_length = i;
|
|
symbol->text[i] = '\0';
|
|
|
|
if (warn_number && !no_errtxt) {
|
|
z_errtxt(0, symbol, 798, "Human Readable Text truncated");
|
|
}
|
|
return warn_number;
|
|
}
|
|
|
|
#ifdef ZINT_TEST /* Wrapper for direct testing */
|
|
INTERNAL int zint_test_gs1_hrt_conv_brackets(struct zint_symbol *symbol, const unsigned char source[],
|
|
const int length, const int no_errtxt) {
|
|
return gs1_hrt_conv_brackets(symbol, source, length, no_errtxt);
|
|
}
|
|
#endif
|
|
|
|
#define GS1_PARENS_PLACEHOLDER_MASK 0x20 /* Mask `(` or `)` by this if escaped (get BS (\x08) or HT (\x09)) */
|
|
|
|
/* Helper to re-instate escaped parentheses */
|
|
static void gs1_reinstate_escaped_parens(unsigned char source[], const int length) {
|
|
int i;
|
|
for (i = 0; i < length; i++) {
|
|
if (source[i] < '\x1D') {
|
|
source[i] |= GS1_PARENS_PLACEHOLDER_MASK;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Verify a GS1 input string */
|
|
INTERNAL int zint_gs1_verify(struct zint_symbol *symbol, const unsigned char source[], const int length,
|
|
unsigned char reduced[], int *p_reduced_length, const int set_hrt) {
|
|
int i, j;
|
|
int error_number = 0, warn_number = 0;
|
|
int bracket_level = 0;
|
|
int ai_latch;
|
|
int local_length = length;
|
|
const unsigned char *local_source = source;
|
|
unsigned char *local_source_buf = (unsigned char *) z_alloca(length + 1);
|
|
int *ai_vals, *ai_locs, *data_locs, *data_lens;
|
|
|
|
const int gs1parens_mode = symbol->input_mode & GS1PARENS_MODE;
|
|
const char obracket = gs1parens_mode ? '(' : '[';
|
|
const char cbracket = gs1parens_mode ? ')' : ']';
|
|
int done_gs1se = 0;
|
|
int is_digital_link = 0; /* Is Digital Link? */
|
|
const int gs1raw_mode = symbol->input_mode & GS1RAW_MODE;
|
|
const int gs1nocheck_mode = symbol->input_mode & GS1NOCHECK_MODE;
|
|
|
|
/* Note: Digital Link URIs only validated if have GS1 Syntax Engine and GS1SYNTAXENGINE_MODE set */
|
|
|
|
*p_reduced_length = 0;
|
|
|
|
#ifdef ZINT_HAVE_GS1SE
|
|
if ((symbol->input_mode & GS1SYNTAXENGINE_MODE) && !gs1nocheck_mode) {
|
|
/* Strict verification */
|
|
if ((error_number = gs1se_verify(symbol, source, length, reduced, p_reduced_length))) {
|
|
return error_number;
|
|
}
|
|
done_gs1se = 1;
|
|
/* `p_reduced_length` will be set if Digital Link or raw/caret */
|
|
}
|
|
#endif
|
|
|
|
if (!done_gs1se) {
|
|
/* Detect control and extended ASCII characters */
|
|
for (i = 0; i < length; i++) {
|
|
if (!z_isascii(source[i])) {
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 250,
|
|
"Extended ASCII characters are not supported by GS1");
|
|
}
|
|
if (source[i] == '\0') {
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 262, "NUL characters not permitted in GS1 mode");
|
|
}
|
|
if (source[i] < 32 && (!gs1raw_mode || source[i] != '\x1D')) {
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 251, "Control characters are not supported by GS1");
|
|
}
|
|
if (source[i] == 127) {
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 263, "DEL characters are not supported by GS1");
|
|
}
|
|
}
|
|
|
|
is_digital_link = gs1_is_digital_link(source, length);
|
|
|
|
if (source[0] != obracket && !is_digital_link && (gs1raw_mode || source[0] == '^')) {
|
|
int ai_count;
|
|
int err_no, err_posn;
|
|
const int ai_max = (length + 2) / 3 + 1; /* Min AI 2, min data 1 */
|
|
|
|
ai_vals = (int *) z_alloca(sizeof(int) * ai_max);
|
|
ai_locs = (int *) z_alloca(sizeof(int) * ai_max);
|
|
data_locs = (int *) z_alloca(sizeof(int) * ai_max);
|
|
data_lens = (int *) z_alloca(sizeof(int) * ai_max);
|
|
if (!gs1_lint_parse_raw_caret(source, length, ai_max, ai_vals, ai_locs, data_locs, data_lens, &ai_count,
|
|
&err_no, &err_posn)) {
|
|
/* Both raw & caret modes require valid AIs and underlong data lengths to work, so check regardless of
|
|
GS1NOCHECK_MODE */
|
|
if (err_no == 1) {
|
|
if (ai_vals[ai_count] != -1) {
|
|
ZEXT z_errtxtf(0, symbol, 856, "Invalid AI (%1$02d) at position %2$d", ai_vals[ai_count],
|
|
err_posn);
|
|
} else {
|
|
z_errtxtf(0, symbol, 857, "Invalid AI at position %d", err_posn);
|
|
}
|
|
} else {
|
|
assert(err_no == 2);
|
|
if (data_lens[ai_count] == 0) {
|
|
ZEXT z_errtxtf(0, symbol, 858, "Empty data field for AI (%1$02d) at position %2$d",
|
|
ai_vals[ai_count], err_posn);
|
|
} else {
|
|
ZEXT z_errtxtf(0, symbol, 859, "Invalid data length for AI (%1$02d) at position %2$d",
|
|
ai_vals[ai_count], err_posn);
|
|
}
|
|
}
|
|
return ZINT_ERROR_INVALID_DATA;
|
|
}
|
|
assert(ai_count < ai_max); /* Suppress clang-tidy-22 clang-analyzer-security.ArrayBound */
|
|
if (!gs1nocheck_mode) {
|
|
/* Do lint to check that data values are ok */
|
|
error_number = gs1_run_lint(symbol, source, ai_count, ai_vals, ai_locs, data_locs, data_lens);
|
|
if (error_number >= ZINT_ERROR) {
|
|
return error_number;
|
|
}
|
|
}
|
|
|
|
if (set_hrt) {
|
|
warn_number = gs1_hrt_conv_parsed(symbol, source, length, ai_count, ai_vals, ai_locs, data_locs,
|
|
data_lens, error_number != 0 /*no_errtxt*/);
|
|
}
|
|
|
|
/* Set reduced - need to set it via `ai_vals` etc. so as to lose any superfluous carets/GSs */
|
|
for (i = 0, j = 0; i < ai_count; i++) {
|
|
const int ai_len = 2 + (ai_vals[i] >= 100) + (ai_vals[i] >= 1000);
|
|
memcpy(reduced + j, source + ai_locs[i], ai_len);
|
|
j += ai_len;
|
|
memcpy(reduced + j, source + data_locs[i], data_lens[i]);
|
|
j += data_lens[i];
|
|
if (i + 1 != ai_count && !gs1_predefined_len(source + ai_locs[i])) {
|
|
reduced[j++] = '\x1D';
|
|
}
|
|
}
|
|
reduced[j] = '\0';
|
|
*p_reduced_length = j;
|
|
return error_number ? error_number : warn_number;
|
|
}
|
|
if (source[0] != obracket) {
|
|
if (!z_is_fixed_ratio(symbol->symbology)) { /* Only matrix symbols can encode Digital Link URIs */
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 252, "Data does not start with an AI");
|
|
}
|
|
if (!is_digital_link) {
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 855,
|
|
"Data does not start with an AI or Digital Link URI");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_digital_link || (done_gs1se && *p_reduced_length)) {
|
|
if (!done_gs1se) {
|
|
/* Just copy over Digital Link URI - no verification */
|
|
memcpy(reduced, source, length + 1); /* Include terminating NUL */
|
|
*p_reduced_length = length;
|
|
if (set_hrt) {
|
|
error_number = z_hrt_cpy_iso8859_1(symbol, source, length);
|
|
}
|
|
}
|
|
return error_number;
|
|
}
|
|
|
|
if (gs1parens_mode) {
|
|
/* Check for escaped parentheses */
|
|
for (i = 0; i < length; i++) {
|
|
if (source[i] == '\\' && i + 1 < length && (source[i + 1] == '(' || source[i + 1] == ')')) {
|
|
break;
|
|
}
|
|
}
|
|
if (i != length) {
|
|
assert(length > 0); /* Suppress clang-tidy-21 clang-analyzer-security.ArrayBound */
|
|
local_source = local_source_buf;
|
|
/* Replace with control-char placeholders */
|
|
for (i = 0, j = 0; i < length; i++) {
|
|
if (source[i] == '\\' && i + 1 < length && (source[i + 1] == '(' || source[i + 1] == ')')) {
|
|
local_source_buf[j++] = source[++i] & ~GS1_PARENS_PLACEHOLDER_MASK; /* BS (\x08) or HT (\x09) */
|
|
} else {
|
|
local_source_buf[j++] = source[i];
|
|
}
|
|
}
|
|
local_source_buf[j] = '\0';
|
|
local_length = j;
|
|
}
|
|
}
|
|
|
|
if (!done_gs1se) {
|
|
/* Verify AIs */
|
|
int max_ai_length = 0, min_ai_length = 5;
|
|
int max_bracket_level = 0;
|
|
int ai_length = 0;
|
|
int max_ai_pos = 0, min_ai_pos = 0; /* Suppress gcc 14 "-Wmaybe-uninitialized" false positives */
|
|
int ai_zero_len_no_data = 0, ai_single_digit = 0, ai_nonnumeric = 0;
|
|
int ai_nonnumeric_pos = 0; /* Suppress gcc 14 "-Wmaybe-uninitialized" false positive */
|
|
const int ai_max = z_chr_cnt(local_source, local_length, obracket) + 1; /* Plus 1 so non-zero */
|
|
|
|
ai_vals = (int *) z_alloca(sizeof(int) * ai_max);
|
|
ai_locs = (int *) z_alloca(sizeof(int) * ai_max);
|
|
data_locs = (int *) z_alloca(sizeof(int) * ai_max);
|
|
data_lens = (int *) z_alloca(sizeof(int) * ai_max);
|
|
|
|
/* Check the balance of the brackets & AI lengths */
|
|
ai_latch = 0;
|
|
for (i = 0; i < local_length; i++) {
|
|
if (local_source[i] == obracket) {
|
|
bracket_level++;
|
|
if (bracket_level > max_bracket_level) {
|
|
max_bracket_level = bracket_level;
|
|
}
|
|
ai_latch = 1;
|
|
} else if (local_source[i] == cbracket && bracket_level) {
|
|
bracket_level--;
|
|
if (ai_length > max_ai_length) {
|
|
max_ai_length = ai_length;
|
|
max_ai_pos = i - ai_length;
|
|
}
|
|
if (ai_length < min_ai_length) {
|
|
min_ai_length = ai_length;
|
|
min_ai_pos = i - ai_length;
|
|
}
|
|
/* Check zero-length AI has data */
|
|
if (ai_length == 0 && (i + 1 == local_length || local_source[i + 1] == obracket)) {
|
|
ai_zero_len_no_data = 1;
|
|
} else if (ai_length == 1) {
|
|
ai_single_digit = 1;
|
|
}
|
|
ai_length = 0;
|
|
ai_latch = 0;
|
|
} else if (ai_latch) {
|
|
ai_length++;
|
|
if (!z_isdigit(local_source[i])) {
|
|
ai_nonnumeric = 1;
|
|
ai_nonnumeric_pos = i - ai_length + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bracket_level != 0) {
|
|
/* Not all brackets are closed */
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 253, "Malformed AI in input (brackets don\'t match)");
|
|
}
|
|
|
|
if (max_bracket_level > 1) {
|
|
/* Nested brackets */
|
|
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 254, "Found nested brackets in input");
|
|
}
|
|
|
|
if (max_ai_length > 4) {
|
|
/* AI is too long */
|
|
return z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 255,
|
|
"Invalid AI at position %d in input (AI too long)", max_ai_pos);
|
|
}
|
|
|
|
if (min_ai_length <= 1) {
|
|
/* Allow too short AI if GS1NOCHECK_MODE and no single-digit AIs and all zero-length AIs have some data
|
|
- permits dummy "[]" workaround for ticket #204 data with no valid AI */
|
|
if (!gs1nocheck_mode || ai_single_digit || ai_zero_len_no_data) {
|
|
/* AI is too short */
|
|
return z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 256,
|
|
"Invalid AI at position %d in input (AI too short)", min_ai_pos);
|
|
}
|
|
}
|
|
|
|
if (ai_nonnumeric) {
|
|
/* Non-numeric data in AI */
|
|
return z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 257,
|
|
"Invalid AI at position %d in input (non-numeric characters in AI)", ai_nonnumeric_pos);
|
|
}
|
|
|
|
if (!gs1nocheck_mode) {
|
|
const unsigned char *local_source2 = local_source;
|
|
unsigned char *local_source2_buf = (unsigned char *) z_alloca(local_length + 1);
|
|
int ai_count = 0;
|
|
for (i = 1; i < local_length; i++) {
|
|
if (local_source[i - 1] == obracket) {
|
|
ai_locs[ai_count] = i - 1;
|
|
for (j = 1; local_source[i + j] != cbracket; j++);
|
|
ai_vals[ai_count] = z_to_int(local_source + i, j);
|
|
ai_count++;
|
|
i += j;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ai_count; i++) {
|
|
data_locs[i] = ai_locs[i] + 2 /*brackets*/ + 2 + (ai_vals[i] >= 100) + (ai_vals[i] >= 1000);
|
|
data_lens[i] = 0;
|
|
while (data_locs[i] + data_lens[i] < local_length
|
|
&& local_source[data_locs[i] + data_lens[i]] != obracket) {
|
|
data_lens[i]++;
|
|
}
|
|
if (data_lens[i] == 0) {
|
|
/* No data for given AI */
|
|
return ZEXT z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 258,
|
|
"Empty data field for AI (%1$02d) at position %2$d",
|
|
ai_vals[i], ai_locs[i] + 1);
|
|
}
|
|
}
|
|
|
|
if (local_length != length) {
|
|
/* Temporarily re-instate escaped parentheses before linting */
|
|
local_source2 = local_source2_buf;
|
|
memcpy(local_source2_buf, local_source, local_length + 1); /* Include terminating NUL */
|
|
gs1_reinstate_escaped_parens(local_source2_buf, local_length);
|
|
}
|
|
|
|
/* Check AI/data */
|
|
error_number = gs1_run_lint(symbol, local_source2, ai_count, ai_vals, ai_locs, data_locs, data_lens);
|
|
if (error_number >= ZINT_ERROR) {
|
|
return error_number;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!*p_reduced_length) {
|
|
/* Resolve AI data - put resulting string in `reduced` */
|
|
j = 0;
|
|
ai_latch = 1;
|
|
for (i = 0; i < local_length; i++) {
|
|
if (local_source[i] == obracket) {
|
|
bracket_level++;
|
|
/* Start of an AI string */
|
|
if (ai_latch == 0) {
|
|
reduced[j++] = '\x1D';
|
|
}
|
|
if (i + 1 != local_length) {
|
|
ai_latch = gs1_predefined_len(local_source + i + 1);
|
|
}
|
|
} else if (local_source[i] == cbracket && bracket_level) {
|
|
bracket_level--;
|
|
} else {
|
|
reduced[j++] = local_source[i];
|
|
}
|
|
}
|
|
reduced[j] = '\0';
|
|
*p_reduced_length = j;
|
|
|
|
if (local_length != length) {
|
|
gs1_reinstate_escaped_parens(reduced, *p_reduced_length);
|
|
}
|
|
if (set_hrt) {
|
|
if (gs1parens_mode) {
|
|
warn_number = gs1_hrt_cpy(symbol, source, length, error_number != 0 /*no_errtxt*/);
|
|
} else {
|
|
warn_number = gs1_hrt_conv_brackets(symbol, source, length, error_number != 0 /*no_errtxt*/);
|
|
}
|
|
}
|
|
}
|
|
/* The character '\x1D' (GS) in the reduced string refers to the FNC1 character */
|
|
|
|
return error_number ? error_number : warn_number;
|
|
}
|
|
|
|
/* Helper to return standard GS1 check digit (GS1 General Specifications 7.9.1) */
|
|
INTERNAL char zint_gs1_check_digit(const unsigned char source[], const int length) {
|
|
int i;
|
|
int count = 0;
|
|
int factor = length & 1 ? 3 : 1;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
count += factor * z_ctoi(source[i]);
|
|
factor ^= 0x02; /* Toggles 1 and 3 */
|
|
}
|
|
|
|
return z_itoc((10 - (count % 10)) % 10);
|
|
}
|
|
|
|
/* Helper to expose `iso3166_alpha2()` */
|
|
INTERNAL int zint_gs1_iso3166_alpha2(const unsigned char *cc) {
|
|
return iso3166_alpha2((const char *) cc);
|
|
}
|
|
|
|
/* vim: set ts=4 sw=4 et : */
|