1 /*
2 * IPP data file parsing functions.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright © 2007-2019 by Apple Inc.
6 * Copyright © 1997-2007 by Easy Software Products.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more
9 * information.
10 */
11
12 /*
13 * Include necessary headers...
14 */
15
16 #include "ipp-private.h"
17 #include "string-private.h"
18 #include "debug-internal.h"
19
20
21 /*
22 * Local functions...
23 */
24
25 static ipp_t *parse_collection(_ipp_file_t *f, _ipp_vars_t *v, void *user_data);
26 static int parse_value(_ipp_file_t *f, _ipp_vars_t *v, void *user_data, ipp_t *ipp, ipp_attribute_t **attr, int element);
27 static void report_error(_ipp_file_t *f, _ipp_vars_t *v, void *user_data, const char *message, ...) _CUPS_FORMAT(4, 5);
28
29
30 /*
31 * '_ippFileParse()' - Parse an IPP data file.
32 */
33
34 ipp_t * /* O - IPP attributes or @code NULL@ on failure */
_ippFileParse(_ipp_vars_t * v,const char * filename,void * user_data)35 _ippFileParse(
36 _ipp_vars_t *v, /* I - Variables */
37 const char *filename, /* I - Name of file to parse */
38 void *user_data) /* I - User data pointer */
39 {
40 _ipp_file_t f; /* IPP data file information */
41 ipp_t *attrs = NULL; /* Active IPP message */
42 ipp_attribute_t *attr = NULL; /* Current attribute */
43 char token[1024]; /* Token string */
44 ipp_t *ignored = NULL; /* Ignored attributes */
45
46
47 DEBUG_printf(("_ippFileParse(v=%p, filename=\"%s\", user_data=%p)", (void *)v, filename, user_data));
48
49 /*
50 * Initialize file info...
51 */
52
53 memset(&f, 0, sizeof(f));
54 f.filename = filename;
55 f.linenum = 1;
56
57 if ((f.fp = cupsFileOpen(filename, "r")) == NULL)
58 {
59 DEBUG_printf(("1_ippFileParse: Unable to open \"%s\": %s", filename, strerror(errno)));
60 return (0);
61 }
62
63 /*
64 * Do the callback with a NULL token to setup any initial state...
65 */
66
67 (*v->tokencb)(&f, v, user_data, NULL);
68
69 /*
70 * Read data file, using the callback function as needed...
71 */
72
73 while (_ippFileReadToken(&f, token, sizeof(token)))
74 {
75 if (!_cups_strcasecmp(token, "DEFINE") || !_cups_strcasecmp(token, "DEFINE-DEFAULT"))
76 {
77 char name[128], /* Variable name */
78 value[1024], /* Variable value */
79 temp[1024]; /* Temporary string */
80
81 attr = NULL;
82
83 if (_ippFileReadToken(&f, name, sizeof(name)) && _ippFileReadToken(&f, temp, sizeof(temp)))
84 {
85 if (_cups_strcasecmp(token, "DEFINE-DEFAULT") || !_ippVarsGet(v, name))
86 {
87 _ippVarsExpand(v, value, temp, sizeof(value));
88 _ippVarsSet(v, name, value);
89 }
90 }
91 else
92 {
93 report_error(&f, v, user_data, "Missing %s name and/or value on line %d of \"%s\".", token, f.linenum, f.filename);
94 break;
95 }
96 }
97 else if (f.attrs && !_cups_strcasecmp(token, "ATTR"))
98 {
99 /*
100 * Attribute definition...
101 */
102
103 char syntax[128], /* Attribute syntax (value tag) */
104 name[128]; /* Attribute name */
105 ipp_tag_t value_tag; /* Value tag */
106
107 attr = NULL;
108
109 if (!_ippFileReadToken(&f, syntax, sizeof(syntax)))
110 {
111 report_error(&f, v, user_data, "Missing ATTR syntax on line %d of \"%s\".", f.linenum, f.filename);
112 break;
113 }
114 else if ((value_tag = ippTagValue(syntax)) < IPP_TAG_UNSUPPORTED_VALUE)
115 {
116 report_error(&f, v, user_data, "Bad ATTR syntax \"%s\" on line %d of \"%s\".", syntax, f.linenum, f.filename);
117 break;
118 }
119
120 if (!_ippFileReadToken(&f, name, sizeof(name)) || !name[0])
121 {
122 report_error(&f, v, user_data, "Missing ATTR name on line %d of \"%s\".", f.linenum, f.filename);
123 break;
124 }
125
126 if (!v->attrcb || (*v->attrcb)(&f, user_data, name))
127 {
128 /*
129 * Add this attribute...
130 */
131
132 attrs = f.attrs;
133 }
134 else
135 {
136 /*
137 * Ignore this attribute...
138 */
139
140 if (!ignored)
141 ignored = ippNew();
142
143 attrs = ignored;
144 }
145
146 if (value_tag < IPP_TAG_INTEGER)
147 {
148 /*
149 * Add out-of-band attribute - no value string needed...
150 */
151
152 ippAddOutOfBand(attrs, f.group_tag, value_tag, name);
153 }
154 else
155 {
156 /*
157 * Add attribute with one or more values...
158 */
159
160 attr = ippAddString(attrs, f.group_tag, value_tag, name, NULL, NULL);
161
162 if (!parse_value(&f, v, user_data, attrs, &attr, 0))
163 break;
164 }
165
166 }
167 else if (attr && !_cups_strcasecmp(token, ","))
168 {
169 /*
170 * Additional value...
171 */
172
173 if (!parse_value(&f, v, user_data, attrs, &attr, ippGetCount(attr)))
174 break;
175 }
176 else
177 {
178 /*
179 * Something else...
180 */
181
182 attr = NULL;
183 attrs = NULL;
184
185 if (!(*v->tokencb)(&f, v, user_data, token))
186 break;
187 }
188 }
189
190 /*
191 * Close the file and free ignored attributes, then return any attributes we
192 * kept...
193 */
194
195 cupsFileClose(f.fp);
196 ippDelete(ignored);
197
198 return (f.attrs);
199 }
200
201
202 /*
203 * '_ippFileReadToken()' - Read a token from an IPP data file.
204 */
205
206 int /* O - 1 on success, 0 on failure */
_ippFileReadToken(_ipp_file_t * f,char * token,size_t tokensize)207 _ippFileReadToken(_ipp_file_t *f, /* I - File to read from */
208 char *token, /* I - Token string buffer */
209 size_t tokensize)/* I - Size of token string buffer */
210 {
211 int ch, /* Character from file */
212 quote = 0; /* Quoting character */
213 char *tokptr = token, /* Pointer into token buffer */
214 *tokend = token + tokensize - 1;/* End of token buffer */
215
216
217 /*
218 * Skip whitespace and comments...
219 */
220
221 DEBUG_printf(("1_ippFileReadToken: linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
222
223 while ((ch = cupsFileGetChar(f->fp)) != EOF)
224 {
225 if (_cups_isspace(ch))
226 {
227 /*
228 * Whitespace...
229 */
230
231 if (ch == '\n')
232 {
233 f->linenum ++;
234 DEBUG_printf(("1_ippFileReadToken: LF in leading whitespace, linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
235 }
236 }
237 else if (ch == '#')
238 {
239 /*
240 * Comment...
241 */
242
243 DEBUG_puts("1_ippFileReadToken: Skipping comment in leading whitespace...");
244
245 while ((ch = cupsFileGetChar(f->fp)) != EOF)
246 {
247 if (ch == '\n')
248 break;
249 }
250
251 if (ch == '\n')
252 {
253 f->linenum ++;
254 DEBUG_printf(("1_ippFileReadToken: LF at end of comment, linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
255 }
256 else
257 break;
258 }
259 else
260 break;
261 }
262
263 if (ch == EOF)
264 {
265 DEBUG_puts("1_ippFileReadToken: EOF");
266 return (0);
267 }
268
269 /*
270 * Read a token...
271 */
272
273 while (ch != EOF)
274 {
275 if (ch == '\n')
276 {
277 f->linenum ++;
278 DEBUG_printf(("1_ippFileReadToken: LF in token, linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
279 }
280
281 if (ch == quote)
282 {
283 /*
284 * End of quoted text...
285 */
286
287 *tokptr = '\0';
288 DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" at closing quote.", token));
289 return (1);
290 }
291 else if (!quote && _cups_isspace(ch))
292 {
293 /*
294 * End of unquoted text...
295 */
296
297 *tokptr = '\0';
298 DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" before whitespace.", token));
299 return (1);
300 }
301 else if (!quote && (ch == '\'' || ch == '\"'))
302 {
303 /*
304 * Start of quoted text or regular expression...
305 */
306
307 quote = ch;
308
309 DEBUG_printf(("1_ippFileReadToken: Start of quoted string, quote=%c, pos=%ld", quote, (long)cupsFileTell(f->fp)));
310 }
311 else if (!quote && ch == '#')
312 {
313 /*
314 * Start of comment...
315 */
316
317 cupsFileSeek(f->fp, cupsFileTell(f->fp) - 1);
318 *tokptr = '\0';
319 DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" before comment.", token));
320 return (1);
321 }
322 else if (!quote && (ch == '{' || ch == '}' || ch == ','))
323 {
324 /*
325 * Delimiter...
326 */
327
328 if (tokptr > token)
329 {
330 /*
331 * Return the preceding token first...
332 */
333
334 cupsFileSeek(f->fp, cupsFileTell(f->fp) - 1);
335 }
336 else
337 {
338 /*
339 * Return this delimiter by itself...
340 */
341
342 *tokptr++ = (char)ch;
343 }
344
345 *tokptr = '\0';
346 DEBUG_printf(("1_ippFileReadToken: Returning \"%s\".", token));
347 return (1);
348 }
349 else
350 {
351 if (ch == '\\')
352 {
353 /*
354 * Quoted character...
355 */
356
357 DEBUG_printf(("1_ippFileReadToken: Quoted character at pos=%ld", (long)cupsFileTell(f->fp)));
358
359 if ((ch = cupsFileGetChar(f->fp)) == EOF)
360 {
361 *token = '\0';
362 DEBUG_puts("1_ippFileReadToken: EOF");
363 return (0);
364 }
365 else if (ch == '\n')
366 {
367 f->linenum ++;
368 DEBUG_printf(("1_ippFileReadToken: quoted LF, linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
369 }
370 else if (ch == 'a')
371 ch = '\a';
372 else if (ch == 'b')
373 ch = '\b';
374 else if (ch == 'f')
375 ch = '\f';
376 else if (ch == 'n')
377 ch = '\n';
378 else if (ch == 'r')
379 ch = '\r';
380 else if (ch == 't')
381 ch = '\t';
382 else if (ch == 'v')
383 ch = '\v';
384 }
385
386 if (tokptr < tokend)
387 {
388 /*
389 * Add to current token...
390 */
391
392 *tokptr++ = (char)ch;
393 }
394 else
395 {
396 /*
397 * Token too long...
398 */
399
400 *tokptr = '\0';
401 DEBUG_printf(("1_ippFileReadToken: Too long: \"%s\".", token));
402 return (0);
403 }
404 }
405
406 /*
407 * Get the next character...
408 */
409
410 ch = cupsFileGetChar(f->fp);
411 }
412
413 *tokptr = '\0';
414 DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" at EOF.", token));
415
416 return (tokptr > token);
417 }
418
419
420 /*
421 * 'parse_collection()' - Parse an IPP collection value.
422 */
423
424 static ipp_t * /* O - Collection value or @code NULL@ on error */
parse_collection(_ipp_file_t * f,_ipp_vars_t * v,void * user_data)425 parse_collection(
426 _ipp_file_t *f, /* I - IPP data file */
427 _ipp_vars_t *v, /* I - IPP variables */
428 void *user_data) /* I - User data pointer */
429 {
430 ipp_t *col = ippNew(); /* Collection value */
431 ipp_attribute_t *attr = NULL; /* Current member attribute */
432 char token[1024]; /* Token string */
433
434
435 /*
436 * Parse the collection value...
437 */
438
439 while (_ippFileReadToken(f, token, sizeof(token)))
440 {
441 if (!_cups_strcasecmp(token, "}"))
442 {
443 /*
444 * End of collection value...
445 */
446
447 break;
448 }
449 else if (!_cups_strcasecmp(token, "MEMBER"))
450 {
451 /*
452 * Member attribute definition...
453 */
454
455 char syntax[128], /* Attribute syntax (value tag) */
456 name[128]; /* Attribute name */
457 ipp_tag_t value_tag; /* Value tag */
458
459 attr = NULL;
460
461 if (!_ippFileReadToken(f, syntax, sizeof(syntax)))
462 {
463 report_error(f, v, user_data, "Missing MEMBER syntax on line %d of \"%s\".", f->linenum, f->filename);
464 ippDelete(col);
465 col = NULL;
466 break;
467 }
468 else if ((value_tag = ippTagValue(syntax)) < IPP_TAG_UNSUPPORTED_VALUE)
469 {
470 report_error(f, v, user_data, "Bad MEMBER syntax \"%s\" on line %d of \"%s\".", syntax, f->linenum, f->filename);
471 ippDelete(col);
472 col = NULL;
473 break;
474 }
475
476 if (!_ippFileReadToken(f, name, sizeof(name)) || !name[0])
477 {
478 report_error(f, v, user_data, "Missing MEMBER name on line %d of \"%s\".", f->linenum, f->filename);
479 ippDelete(col);
480 col = NULL;
481 break;
482 }
483
484 if (value_tag < IPP_TAG_INTEGER)
485 {
486 /*
487 * Add out-of-band attribute - no value string needed...
488 */
489
490 ippAddOutOfBand(col, IPP_TAG_ZERO, value_tag, name);
491 }
492 else
493 {
494 /*
495 * Add attribute with one or more values...
496 */
497
498 attr = ippAddString(col, IPP_TAG_ZERO, value_tag, name, NULL, NULL);
499
500 if (!parse_value(f, v, user_data, col, &attr, 0))
501 {
502 ippDelete(col);
503 col = NULL;
504 break;
505 }
506 }
507
508 }
509 else if (attr && !_cups_strcasecmp(token, ","))
510 {
511 /*
512 * Additional value...
513 */
514
515 if (!parse_value(f, v, user_data, col, &attr, ippGetCount(attr)))
516 {
517 ippDelete(col);
518 col = NULL;
519 break;
520 }
521 }
522 else
523 {
524 /*
525 * Something else...
526 */
527
528 report_error(f, v, user_data, "Unknown directive \"%s\" on line %d of \"%s\".", token, f->linenum, f->filename);
529 ippDelete(col);
530 col = NULL;
531 attr = NULL;
532 break;
533
534 }
535 }
536
537 return (col);
538 }
539
540
541 /*
542 * 'parse_value()' - Parse an IPP value.
543 */
544
545 static int /* O - 1 on success or 0 on error */
parse_value(_ipp_file_t * f,_ipp_vars_t * v,void * user_data,ipp_t * ipp,ipp_attribute_t ** attr,int element)546 parse_value(_ipp_file_t *f, /* I - IPP data file */
547 _ipp_vars_t *v, /* I - IPP variables */
548 void *user_data,/* I - User data pointer */
549 ipp_t *ipp, /* I - IPP message */
550 ipp_attribute_t **attr, /* IO - IPP attribute */
551 int element) /* I - Element number */
552 {
553 char value[2049], /* Value string */
554 *valueptr, /* Pointer into value string */
555 temp[2049], /* Temporary string */
556 *tempptr; /* Pointer into temporary string */
557 size_t valuelen; /* Length of value */
558
559
560 if (!_ippFileReadToken(f, temp, sizeof(temp)))
561 {
562 report_error(f, v, user_data, "Missing value on line %d of \"%s\".", f->linenum, f->filename);
563 return (0);
564 }
565
566 _ippVarsExpand(v, value, temp, sizeof(value));
567
568 switch (ippGetValueTag(*attr))
569 {
570 case IPP_TAG_BOOLEAN :
571 return (ippSetBoolean(ipp, attr, element, !_cups_strcasecmp(value, "true")));
572
573 case IPP_TAG_ENUM :
574 case IPP_TAG_INTEGER :
575 return (ippSetInteger(ipp, attr, element, (int)strtol(value, NULL, 0)));
576
577 case IPP_TAG_DATE :
578 {
579 int year, /* Year */
580 month, /* Month */
581 day, /* Day of month */
582 hour, /* Hour */
583 minute, /* Minute */
584 second, /* Second */
585 utc_offset = 0; /* Timezone offset from UTC */
586 ipp_uchar_t date[11]; /* dateTime value */
587
588 if (*value == 'P')
589 {
590 /*
591 * Time period...
592 */
593
594 time_t curtime; /* Current time in seconds */
595 int period = 0, /* Current period value */
596 saw_T = 0; /* Saw time separator */
597
598 curtime = time(NULL);
599
600 for (valueptr = value + 1; *valueptr; valueptr ++)
601 {
602 if (isdigit(*valueptr & 255))
603 {
604 period = (int)strtol(valueptr, &valueptr, 10);
605
606 if (!valueptr || period < 0)
607 {
608 report_error(f, v, user_data, "Bad dateTime value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
609 return (0);
610 }
611 }
612
613 if (*valueptr == 'Y')
614 {
615 curtime += 365 * 86400 * period;
616 period = 0;
617 }
618 else if (*valueptr == 'M')
619 {
620 if (saw_T)
621 curtime += 60 * period;
622 else
623 curtime += 30 * 86400 * period;
624
625 period = 0;
626 }
627 else if (*valueptr == 'D')
628 {
629 curtime += 86400 * period;
630 period = 0;
631 }
632 else if (*valueptr == 'H')
633 {
634 curtime += 3600 * period;
635 period = 0;
636 }
637 else if (*valueptr == 'S')
638 {
639 curtime += period;
640 period = 0;
641 }
642 else if (*valueptr == 'T')
643 {
644 saw_T = 1;
645 period = 0;
646 }
647 else
648 {
649 report_error(f, v, user_data, "Bad dateTime value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
650 return (0);
651 }
652 }
653
654 return (ippSetDate(ipp, attr, element, ippTimeToDate(curtime)));
655 }
656 else if (sscanf(value, "%d-%d-%dT%d:%d:%d%d", &year, &month, &day, &hour, &minute, &second, &utc_offset) < 6)
657 {
658 /*
659 * Date/time value did not parse...
660 */
661
662 report_error(f, v, user_data, "Bad dateTime value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
663 return (0);
664 }
665
666 date[0] = (ipp_uchar_t)(year >> 8);
667 date[1] = (ipp_uchar_t)(year & 255);
668 date[2] = (ipp_uchar_t)month;
669 date[3] = (ipp_uchar_t)day;
670 date[4] = (ipp_uchar_t)hour;
671 date[5] = (ipp_uchar_t)minute;
672 date[6] = (ipp_uchar_t)second;
673 date[7] = 0;
674 if (utc_offset < 0)
675 {
676 utc_offset = -utc_offset;
677 date[8] = (ipp_uchar_t)'-';
678 }
679 else
680 {
681 date[8] = (ipp_uchar_t)'+';
682 }
683
684 date[9] = (ipp_uchar_t)(utc_offset / 100);
685 date[10] = (ipp_uchar_t)(utc_offset % 100);
686
687 return (ippSetDate(ipp, attr, element, date));
688 }
689
690 case IPP_TAG_RESOLUTION :
691 {
692 int xres, /* X resolution */
693 yres; /* Y resolution */
694 char *ptr; /* Pointer into value */
695
696 xres = yres = (int)strtol(value, &ptr, 10);
697 if (ptr > value && xres > 0)
698 {
699 if (*ptr == 'x')
700 yres = (int)strtol(ptr + 1, &ptr, 10);
701 }
702
703 if (ptr <= value || xres <= 0 || yres <= 0 || !ptr || (_cups_strcasecmp(ptr, "dpi") && _cups_strcasecmp(ptr, "dpc") && _cups_strcasecmp(ptr, "dpcm") && _cups_strcasecmp(ptr, "other")))
704 {
705 report_error(f, v, user_data, "Bad resolution value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
706 return (0);
707 }
708
709 if (!_cups_strcasecmp(ptr, "dpi"))
710 return (ippSetResolution(ipp, attr, element, IPP_RES_PER_INCH, xres, yres));
711 else if (!_cups_strcasecmp(ptr, "dpc") || !_cups_strcasecmp(ptr, "dpcm"))
712 return (ippSetResolution(ipp, attr, element, IPP_RES_PER_CM, xres, yres));
713 else
714 return (ippSetResolution(ipp, attr, element, (ipp_res_t)0, xres, yres));
715 }
716
717 case IPP_TAG_RANGE :
718 {
719 int lower, /* Lower value */
720 upper; /* Upper value */
721
722 if (sscanf(value, "%d-%d", &lower, &upper) != 2)
723 {
724 report_error(f, v, user_data, "Bad rangeOfInteger value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
725 return (0);
726 }
727
728 return (ippSetRange(ipp, attr, element, lower, upper));
729 }
730
731 case IPP_TAG_STRING :
732 valuelen = strlen(value);
733
734 if (value[0] == '<' && value[strlen(value) - 1] == '>')
735 {
736 if (valuelen & 1)
737 {
738 report_error(f, v, user_data, "Bad octetString value on line %d of \"%s\".", f->linenum, f->filename);
739 return (0);
740 }
741
742 valueptr = value + 1;
743 tempptr = temp;
744
745 while (*valueptr && *valueptr != '>')
746 {
747 if (!isxdigit(valueptr[0] & 255) || !isxdigit(valueptr[1] & 255))
748 {
749 report_error(f, v, user_data, "Bad octetString value on line %d of \"%s\".", f->linenum, f->filename);
750 return (0);
751 }
752
753 if (valueptr[0] >= '0' && valueptr[0] <= '9')
754 *tempptr = (char)((valueptr[0] - '0') << 4);
755 else
756 *tempptr = (char)((tolower(valueptr[0]) - 'a' + 10) << 4);
757
758 if (valueptr[1] >= '0' && valueptr[1] <= '9')
759 *tempptr |= (valueptr[1] - '0');
760 else
761 *tempptr |= (tolower(valueptr[1]) - 'a' + 10);
762
763 tempptr ++;
764 }
765
766 return (ippSetOctetString(ipp, attr, element, temp, (int)(tempptr - temp)));
767 }
768 else
769 return (ippSetOctetString(ipp, attr, element, value, (int)valuelen));
770
771 case IPP_TAG_TEXTLANG :
772 case IPP_TAG_NAMELANG :
773 case IPP_TAG_TEXT :
774 case IPP_TAG_NAME :
775 case IPP_TAG_KEYWORD :
776 case IPP_TAG_URI :
777 case IPP_TAG_URISCHEME :
778 case IPP_TAG_CHARSET :
779 case IPP_TAG_LANGUAGE :
780 case IPP_TAG_MIMETYPE :
781 return (ippSetString(ipp, attr, element, value));
782
783 case IPP_TAG_BEGIN_COLLECTION :
784 {
785 int status; /* Add status */
786 ipp_t *col; /* Collection value */
787
788 if (strcmp(value, "{"))
789 {
790 report_error(f, v, user_data, "Bad collection value on line %d of \"%s\".", f->linenum, f->filename);
791 return (0);
792 }
793
794 if ((col = parse_collection(f, v, user_data)) == NULL)
795 return (0);
796
797 status = ippSetCollection(ipp, attr, element, col);
798 ippDelete(col);
799
800 return (status);
801 }
802
803 default :
804 report_error(f, v, user_data, "Unsupported value on line %d of \"%s\".", f->linenum, f->filename);
805 return (0);
806 }
807 }
808
809
810 /*
811 * 'report_error()' - Report an error.
812 */
813
814 static void
report_error(_ipp_file_t * f,_ipp_vars_t * v,void * user_data,const char * message,...)815 report_error(
816 _ipp_file_t *f, /* I - IPP data file */
817 _ipp_vars_t *v, /* I - Error callback function, if any */
818 void *user_data, /* I - User data pointer */
819 const char *message, /* I - Printf-style message */
820 ...) /* I - Additional arguments as needed */
821 {
822 char buffer[8192]; /* Formatted string */
823 va_list ap; /* Argument pointer */
824
825
826 va_start(ap, message);
827 vsnprintf(buffer, sizeof(buffer), message, ap);
828 va_end(ap);
829
830 if (v->errorcb)
831 (*v->errorcb)(f, user_data, buffer);
832 else
833 fprintf(stderr, "%s\n", buffer);
834 }
835