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