blob: 5db5932a4e086cdfa6bafe142340dff0c6906d31 [file] [log] [blame]
/*
* IPP data file parsing functions.
*
* Copyright © 2007-2019 by Apple Inc.
* Copyright © 1997-2007 by Easy Software Products.
*
* Licensed under Apache License v2.0. See the file "LICENSE" for more
* information.
*/
/*
* Include necessary headers...
*/
#include "ipp-private.h"
#include "string-private.h"
#include "debug-internal.h"
/*
* Local functions...
*/
static ipp_t *parse_collection(_ipp_file_t *f, _ipp_vars_t *v, void *user_data);
static int parse_value(_ipp_file_t *f, _ipp_vars_t *v, void *user_data, ipp_t *ipp, ipp_attribute_t **attr, int element);
static void report_error(_ipp_file_t *f, _ipp_vars_t *v, void *user_data, const char *message, ...) _CUPS_FORMAT(4, 5);
/*
* '_ippFileParse()' - Parse an IPP data file.
*/
ipp_t * /* O - IPP attributes or @code NULL@ on failure */
_ippFileParse(
_ipp_vars_t *v, /* I - Variables */
const char *filename, /* I - Name of file to parse */
void *user_data) /* I - User data pointer */
{
_ipp_file_t f; /* IPP data file information */
ipp_t *attrs = NULL; /* Active IPP message */
ipp_attribute_t *attr = NULL; /* Current attribute */
char token[1024]; /* Token string */
ipp_t *ignored = NULL; /* Ignored attributes */
DEBUG_printf(("_ippFileParse(v=%p, filename=\"%s\", user_data=%p)", (void *)v, filename, user_data));
/*
* Initialize file info...
*/
memset(&f, 0, sizeof(f));
f.filename = filename;
f.linenum = 1;
if ((f.fp = cupsFileOpen(filename, "r")) == NULL)
{
DEBUG_printf(("1_ippFileParse: Unable to open \"%s\": %s", filename, strerror(errno)));
return (0);
}
/*
* Do the callback with a NULL token to setup any initial state...
*/
(*v->tokencb)(&f, v, user_data, NULL);
/*
* Read data file, using the callback function as needed...
*/
while (_ippFileReadToken(&f, token, sizeof(token)))
{
if (!_cups_strcasecmp(token, "DEFINE") || !_cups_strcasecmp(token, "DEFINE-DEFAULT"))
{
char name[128], /* Variable name */
value[1024], /* Variable value */
temp[1024]; /* Temporary string */
attr = NULL;
if (_ippFileReadToken(&f, name, sizeof(name)) && _ippFileReadToken(&f, temp, sizeof(temp)))
{
if (_cups_strcasecmp(token, "DEFINE-DEFAULT") || !_ippVarsGet(v, name))
{
_ippVarsExpand(v, value, temp, sizeof(value));
_ippVarsSet(v, name, value);
}
}
else
{
report_error(&f, v, user_data, "Missing %s name and/or value on line %d of \"%s\".", token, f.linenum, f.filename);
break;
}
}
else if (f.attrs && !_cups_strcasecmp(token, "ATTR"))
{
/*
* Attribute definition...
*/
char syntax[128], /* Attribute syntax (value tag) */
name[128]; /* Attribute name */
ipp_tag_t value_tag; /* Value tag */
attr = NULL;
if (!_ippFileReadToken(&f, syntax, sizeof(syntax)))
{
report_error(&f, v, user_data, "Missing ATTR syntax on line %d of \"%s\".", f.linenum, f.filename);
break;
}
else if ((value_tag = ippTagValue(syntax)) < IPP_TAG_UNSUPPORTED_VALUE)
{
report_error(&f, v, user_data, "Bad ATTR syntax \"%s\" on line %d of \"%s\".", syntax, f.linenum, f.filename);
break;
}
if (!_ippFileReadToken(&f, name, sizeof(name)) || !name[0])
{
report_error(&f, v, user_data, "Missing ATTR name on line %d of \"%s\".", f.linenum, f.filename);
break;
}
if (!v->attrcb || (*v->attrcb)(&f, user_data, name))
{
/*
* Add this attribute...
*/
attrs = f.attrs;
}
else
{
/*
* Ignore this attribute...
*/
if (!ignored)
ignored = ippNew();
attrs = ignored;
}
if (value_tag < IPP_TAG_INTEGER)
{
/*
* Add out-of-band attribute - no value string needed...
*/
ippAddOutOfBand(attrs, f.group_tag, value_tag, name);
}
else
{
/*
* Add attribute with one or more values...
*/
attr = ippAddString(attrs, f.group_tag, value_tag, name, NULL, NULL);
if (!parse_value(&f, v, user_data, attrs, &attr, 0))
break;
}
}
else if (attr && !_cups_strcasecmp(token, ","))
{
/*
* Additional value...
*/
if (!parse_value(&f, v, user_data, attrs, &attr, ippGetCount(attr)))
break;
}
else
{
/*
* Something else...
*/
attr = NULL;
attrs = NULL;
if (!(*v->tokencb)(&f, v, user_data, token))
break;
}
}
/*
* Close the file and free ignored attributes, then return any attributes we
* kept...
*/
cupsFileClose(f.fp);
ippDelete(ignored);
return (f.attrs);
}
/*
* '_ippFileReadToken()' - Read a token from an IPP data file.
*/
int /* O - 1 on success, 0 on failure */
_ippFileReadToken(_ipp_file_t *f, /* I - File to read from */
char *token, /* I - Token string buffer */
size_t tokensize)/* I - Size of token string buffer */
{
int ch, /* Character from file */
quote = 0; /* Quoting character */
char *tokptr = token, /* Pointer into token buffer */
*tokend = token + tokensize - 1;/* End of token buffer */
/*
* Skip whitespace and comments...
*/
DEBUG_printf(("1_ippFileReadToken: linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
while ((ch = cupsFileGetChar(f->fp)) != EOF)
{
if (_cups_isspace(ch))
{
/*
* Whitespace...
*/
if (ch == '\n')
{
f->linenum ++;
DEBUG_printf(("1_ippFileReadToken: LF in leading whitespace, linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
}
}
else if (ch == '#')
{
/*
* Comment...
*/
DEBUG_puts("1_ippFileReadToken: Skipping comment in leading whitespace...");
while ((ch = cupsFileGetChar(f->fp)) != EOF)
{
if (ch == '\n')
break;
}
if (ch == '\n')
{
f->linenum ++;
DEBUG_printf(("1_ippFileReadToken: LF at end of comment, linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
}
else
break;
}
else
break;
}
if (ch == EOF)
{
DEBUG_puts("1_ippFileReadToken: EOF");
return (0);
}
/*
* Read a token...
*/
while (ch != EOF)
{
if (ch == '\n')
{
f->linenum ++;
DEBUG_printf(("1_ippFileReadToken: LF in token, linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
}
if (ch == quote)
{
/*
* End of quoted text...
*/
*tokptr = '\0';
DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" at closing quote.", token));
return (1);
}
else if (!quote && _cups_isspace(ch))
{
/*
* End of unquoted text...
*/
*tokptr = '\0';
DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" before whitespace.", token));
return (1);
}
else if (!quote && (ch == '\'' || ch == '\"'))
{
/*
* Start of quoted text or regular expression...
*/
if (ch == '<')
quote = '>';
else
quote = ch;
DEBUG_printf(("1_ippFileReadToken: Start of quoted string, quote=%c, pos=%ld", quote, (long)cupsFileTell(f->fp)));
}
else if (!quote && ch == '#')
{
/*
* Start of comment...
*/
cupsFileSeek(f->fp, cupsFileTell(f->fp) - 1);
*tokptr = '\0';
DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" before comment.", token));
return (1);
}
else if (!quote && (ch == '{' || ch == '}' || ch == ','))
{
/*
* Delimiter...
*/
if (tokptr > token)
{
/*
* Return the preceding token first...
*/
cupsFileSeek(f->fp, cupsFileTell(f->fp) - 1);
}
else
{
/*
* Return this delimiter by itself...
*/
*tokptr++ = (char)ch;
}
*tokptr = '\0';
DEBUG_printf(("1_ippFileReadToken: Returning \"%s\".", token));
return (1);
}
else
{
if (ch == '\\')
{
/*
* Quoted character...
*/
DEBUG_printf(("1_ippFileReadToken: Quoted character at pos=%ld", (long)cupsFileTell(f->fp)));
if ((ch = cupsFileGetChar(f->fp)) == EOF)
{
*token = '\0';
DEBUG_puts("1_ippFileReadToken: EOF");
return (0);
}
else if (ch == '\n')
{
f->linenum ++;
DEBUG_printf(("1_ippFileReadToken: quoted LF, linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
}
else if (ch == 'a')
ch = '\a';
else if (ch == 'b')
ch = '\b';
else if (ch == 'f')
ch = '\f';
else if (ch == 'n')
ch = '\n';
else if (ch == 'r')
ch = '\r';
else if (ch == 't')
ch = '\t';
else if (ch == 'v')
ch = '\v';
}
if (tokptr < tokend)
{
/*
* Add to current token...
*/
*tokptr++ = (char)ch;
}
else
{
/*
* Token too long...
*/
*tokptr = '\0';
DEBUG_printf(("1_ippFileReadToken: Too long: \"%s\".", token));
return (0);
}
}
/*
* Get the next character...
*/
ch = cupsFileGetChar(f->fp);
}
*tokptr = '\0';
DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" at EOF.", token));
return (tokptr > token);
}
/*
* 'parse_collection()' - Parse an IPP collection value.
*/
static ipp_t * /* O - Collection value or @code NULL@ on error */
parse_collection(
_ipp_file_t *f, /* I - IPP data file */
_ipp_vars_t *v, /* I - IPP variables */
void *user_data) /* I - User data pointer */
{
ipp_t *col = ippNew(); /* Collection value */
ipp_attribute_t *attr = NULL; /* Current member attribute */
char token[1024]; /* Token string */
/*
* Parse the collection value...
*/
while (_ippFileReadToken(f, token, sizeof(token)))
{
if (!_cups_strcasecmp(token, "}"))
{
/*
* End of collection value...
*/
break;
}
else if (!_cups_strcasecmp(token, "MEMBER"))
{
/*
* Member attribute definition...
*/
char syntax[128], /* Attribute syntax (value tag) */
name[128]; /* Attribute name */
ipp_tag_t value_tag; /* Value tag */
attr = NULL;
if (!_ippFileReadToken(f, syntax, sizeof(syntax)))
{
report_error(f, v, user_data, "Missing MEMBER syntax on line %d of \"%s\".", f->linenum, f->filename);
ippDelete(col);
col = NULL;
break;
}
else if ((value_tag = ippTagValue(syntax)) < IPP_TAG_UNSUPPORTED_VALUE)
{
report_error(f, v, user_data, "Bad MEMBER syntax \"%s\" on line %d of \"%s\".", syntax, f->linenum, f->filename);
ippDelete(col);
col = NULL;
break;
}
if (!_ippFileReadToken(f, name, sizeof(name)) || !name[0])
{
report_error(f, v, user_data, "Missing MEMBER name on line %d of \"%s\".", f->linenum, f->filename);
ippDelete(col);
col = NULL;
break;
}
if (value_tag < IPP_TAG_INTEGER)
{
/*
* Add out-of-band attribute - no value string needed...
*/
ippAddOutOfBand(col, IPP_TAG_ZERO, value_tag, name);
}
else
{
/*
* Add attribute with one or more values...
*/
attr = ippAddString(col, IPP_TAG_ZERO, value_tag, name, NULL, NULL);
if (!parse_value(f, v, user_data, col, &attr, 0))
{
ippDelete(col);
col = NULL;
break;
}
}
}
else if (attr && !_cups_strcasecmp(token, ","))
{
/*
* Additional value...
*/
if (!parse_value(f, v, user_data, col, &attr, ippGetCount(attr)))
{
ippDelete(col);
col = NULL;
break;
}
}
else
{
/*
* Something else...
*/
report_error(f, v, user_data, "Unknown directive \"%s\" on line %d of \"%s\".", token, f->linenum, f->filename);
ippDelete(col);
col = NULL;
attr = NULL;
break;
}
}
return (col);
}
/*
* 'parse_value()' - Parse an IPP value.
*/
static int /* O - 1 on success or 0 on error */
parse_value(_ipp_file_t *f, /* I - IPP data file */
_ipp_vars_t *v, /* I - IPP variables */
void *user_data,/* I - User data pointer */
ipp_t *ipp, /* I - IPP message */
ipp_attribute_t **attr, /* IO - IPP attribute */
int element) /* I - Element number */
{
char value[2049], /* Value string */
*valueptr, /* Pointer into value string */
temp[2049], /* Temporary string */
*tempptr; /* Pointer into temporary string */
size_t valuelen; /* Length of value */
if (!_ippFileReadToken(f, temp, sizeof(temp)))
{
report_error(f, v, user_data, "Missing value on line %d of \"%s\".", f->linenum, f->filename);
return (0);
}
_ippVarsExpand(v, value, temp, sizeof(value));
switch (ippGetValueTag(*attr))
{
case IPP_TAG_BOOLEAN :
return (ippSetBoolean(ipp, attr, element, !_cups_strcasecmp(value, "true")));
break;
case IPP_TAG_ENUM :
case IPP_TAG_INTEGER :
return (ippSetInteger(ipp, attr, element, (int)strtol(value, NULL, 0)));
break;
case IPP_TAG_DATE :
{
int year, /* Year */
month, /* Month */
day, /* Day of month */
hour, /* Hour */
minute, /* Minute */
second, /* Second */
utc_offset = 0; /* Timezone offset from UTC */
ipp_uchar_t date[11]; /* dateTime value */
if (*value == 'P')
{
/*
* Time period...
*/
time_t curtime; /* Current time in seconds */
int period = 0, /* Current period value */
saw_T = 0; /* Saw time separator */
curtime = time(NULL);
for (valueptr = value + 1; *valueptr; valueptr ++)
{
if (isdigit(*valueptr & 255))
{
period = (int)strtol(valueptr, &valueptr, 10);
if (!valueptr || period < 0)
{
report_error(f, v, user_data, "Bad dateTime value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
return (0);
}
}
if (*valueptr == 'Y')
{
curtime += 365 * 86400 * period;
period = 0;
}
else if (*valueptr == 'M')
{
if (saw_T)
curtime += 60 * period;
else
curtime += 30 * 86400 * period;
period = 0;
}
else if (*valueptr == 'D')
{
curtime += 86400 * period;
period = 0;
}
else if (*valueptr == 'H')
{
curtime += 3600 * period;
period = 0;
}
else if (*valueptr == 'S')
{
curtime += period;
period = 0;
}
else if (*valueptr == 'T')
{
saw_T = 1;
period = 0;
}
else
{
report_error(f, v, user_data, "Bad dateTime value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
return (0);
}
}
return (ippSetDate(ipp, attr, element, ippTimeToDate(curtime)));
}
else if (sscanf(value, "%d-%d-%dT%d:%d:%d%d", &year, &month, &day, &hour, &minute, &second, &utc_offset) < 6)
{
/*
* Date/time value did not parse...
*/
report_error(f, v, user_data, "Bad dateTime value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
return (0);
}
date[0] = (ipp_uchar_t)(year >> 8);
date[1] = (ipp_uchar_t)(year & 255);
date[2] = (ipp_uchar_t)month;
date[3] = (ipp_uchar_t)day;
date[4] = (ipp_uchar_t)hour;
date[5] = (ipp_uchar_t)minute;
date[6] = (ipp_uchar_t)second;
date[7] = 0;
if (utc_offset < 0)
{
utc_offset = -utc_offset;
date[8] = (ipp_uchar_t)'-';
}
else
{
date[8] = (ipp_uchar_t)'+';
}
date[9] = (ipp_uchar_t)(utc_offset / 100);
date[10] = (ipp_uchar_t)(utc_offset % 100);
return (ippSetDate(ipp, attr, element, date));
}
break;
case IPP_TAG_RESOLUTION :
{
int xres, /* X resolution */
yres; /* Y resolution */
char *ptr; /* Pointer into value */
xres = yres = (int)strtol(value, (char **)&ptr, 10);
if (ptr > value && xres > 0)
{
if (*ptr == 'x')
yres = (int)strtol(ptr + 1, (char **)&ptr, 10);
}
if (ptr <= value || xres <= 0 || yres <= 0 || !ptr || (_cups_strcasecmp(ptr, "dpi") && _cups_strcasecmp(ptr, "dpc") && _cups_strcasecmp(ptr, "dpcm") && _cups_strcasecmp(ptr, "other")))
{
report_error(f, v, user_data, "Bad resolution value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
return (0);
}
if (!_cups_strcasecmp(ptr, "dpi"))
return (ippSetResolution(ipp, attr, element, IPP_RES_PER_INCH, xres, yres));
else if (!_cups_strcasecmp(ptr, "dpc") || !_cups_strcasecmp(ptr, "dpcm"))
return (ippSetResolution(ipp, attr, element, IPP_RES_PER_CM, xres, yres));
else
return (ippSetResolution(ipp, attr, element, (ipp_res_t)0, xres, yres));
}
break;
case IPP_TAG_RANGE :
{
int lower, /* Lower value */
upper; /* Upper value */
if (sscanf(value, "%d-%d", &lower, &upper) != 2)
{
report_error(f, v, user_data, "Bad rangeOfInteger value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
return (0);
}
return (ippSetRange(ipp, attr, element, lower, upper));
}
break;
case IPP_TAG_STRING :
valuelen = strlen(value);
if (value[0] == '<' && value[strlen(value) - 1] == '>')
{
if (valuelen & 1)
{
report_error(f, v, user_data, "Bad octetString value on line %d of \"%s\".", f->linenum, f->filename);
return (0);
}
valueptr = value + 1;
tempptr = temp;
while (*valueptr && *valueptr != '>')
{
if (!isxdigit(valueptr[0] & 255) || !isxdigit(valueptr[1] & 255))
{
report_error(f, v, user_data, "Bad octetString value on line %d of \"%s\".", f->linenum, f->filename);
return (0);
}
if (valueptr[0] >= '0' && valueptr[0] <= '9')
*tempptr = (char)((valueptr[0] - '0') << 4);
else
*tempptr = (char)((tolower(valueptr[0]) - 'a' + 10) << 4);
if (valueptr[1] >= '0' && valueptr[1] <= '9')
*tempptr |= (valueptr[1] - '0');
else
*tempptr |= (tolower(valueptr[1]) - 'a' + 10);
tempptr ++;
}
return (ippSetOctetString(ipp, attr, element, temp, (int)(tempptr - temp)));
}
else
return (ippSetOctetString(ipp, attr, element, value, (int)valuelen));
break;
case IPP_TAG_TEXTLANG :
case IPP_TAG_NAMELANG :
case IPP_TAG_TEXT :
case IPP_TAG_NAME :
case IPP_TAG_KEYWORD :
case IPP_TAG_URI :
case IPP_TAG_URISCHEME :
case IPP_TAG_CHARSET :
case IPP_TAG_LANGUAGE :
case IPP_TAG_MIMETYPE :
return (ippSetString(ipp, attr, element, value));
break;
case IPP_TAG_BEGIN_COLLECTION :
{
int status; /* Add status */
ipp_t *col; /* Collection value */
if (strcmp(value, "{"))
{
report_error(f, v, user_data, "Bad collection value on line %d of \"%s\".", f->linenum, f->filename);
return (0);
}
if ((col = parse_collection(f, v, user_data)) == NULL)
return (0);
status = ippSetCollection(ipp, attr, element, col);
ippDelete(col);
return (status);
}
break;
default :
report_error(f, v, user_data, "Unsupported value on line %d of \"%s\".", f->linenum, f->filename);
return (0);
}
return (1);
}
/*
* 'report_error()' - Report an error.
*/
static void
report_error(
_ipp_file_t *f, /* I - IPP data file */
_ipp_vars_t *v, /* I - Error callback function, if any */
void *user_data, /* I - User data pointer */
const char *message, /* I - Printf-style message */
...) /* I - Additional arguments as needed */
{
char buffer[8192]; /* Formatted string */
va_list ap; /* Argument pointer */
va_start(ap, message);
vsnprintf(buffer, sizeof(buffer), message, ap);
va_end(ap);
if (v->errorcb)
(*v->errorcb)(f, user_data, buffer);
else
fprintf(stderr, "%s\n", buffer);
}