/* * 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); }