]> git.ipfire.org Git - thirdparty/cups.git/blob - cups/ipp-file.c
Return kDNSServiceErr_ServiceNotRunning when Bonjour for Windows not installed.
[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 quote = ch;
307
308 DEBUG_printf(("1_ippFileReadToken: Start of quoted string, quote=%c, pos=%ld", quote, (long)cupsFileTell(f->fp)));
309 }
310 else if (!quote && ch == '#')
311 {
312 /*
313 * Start of comment...
314 */
315
316 cupsFileSeek(f->fp, cupsFileTell(f->fp) - 1);
317 *tokptr = '\0';
318 DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" before comment.", token));
319 return (1);
320 }
321 else if (!quote && (ch == '{' || ch == '}' || ch == ','))
322 {
323 /*
324 * Delimiter...
325 */
326
327 if (tokptr > token)
328 {
329 /*
330 * Return the preceding token first...
331 */
332
333 cupsFileSeek(f->fp, cupsFileTell(f->fp) - 1);
334 }
335 else
336 {
337 /*
338 * Return this delimiter by itself...
339 */
340
341 *tokptr++ = (char)ch;
342 }
343
344 *tokptr = '\0';
345 DEBUG_printf(("1_ippFileReadToken: Returning \"%s\".", token));
346 return (1);
347 }
348 else
349 {
350 if (ch == '\\')
351 {
352 /*
353 * Quoted character...
354 */
355
356 DEBUG_printf(("1_ippFileReadToken: Quoted character at pos=%ld", (long)cupsFileTell(f->fp)));
357
358 if ((ch = cupsFileGetChar(f->fp)) == EOF)
359 {
360 *token = '\0';
361 DEBUG_puts("1_ippFileReadToken: EOF");
362 return (0);
363 }
364 else if (ch == '\n')
365 {
366 f->linenum ++;
367 DEBUG_printf(("1_ippFileReadToken: quoted LF, linenum=%d, pos=%ld", f->linenum, (long)cupsFileTell(f->fp)));
368 }
369 else if (ch == 'a')
370 ch = '\a';
371 else if (ch == 'b')
372 ch = '\b';
373 else if (ch == 'f')
374 ch = '\f';
375 else if (ch == 'n')
376 ch = '\n';
377 else if (ch == 'r')
378 ch = '\r';
379 else if (ch == 't')
380 ch = '\t';
381 else if (ch == 'v')
382 ch = '\v';
383 }
384
385 if (tokptr < tokend)
386 {
387 /*
388 * Add to current token...
389 */
390
391 *tokptr++ = (char)ch;
392 }
393 else
394 {
395 /*
396 * Token too long...
397 */
398
399 *tokptr = '\0';
400 DEBUG_printf(("1_ippFileReadToken: Too long: \"%s\".", token));
401 return (0);
402 }
403 }
404
405 /*
406 * Get the next character...
407 */
408
409 ch = cupsFileGetChar(f->fp);
410 }
411
412 *tokptr = '\0';
413 DEBUG_printf(("1_ippFileReadToken: Returning \"%s\" at EOF.", token));
414
415 return (tokptr > token);
416 }
417
418
419 /*
420 * 'parse_collection()' - Parse an IPP collection value.
421 */
422
423 static ipp_t * /* O - Collection value or @code NULL@ on error */
424 parse_collection(
425 _ipp_file_t *f, /* I - IPP data file */
426 _ipp_vars_t *v, /* I - IPP variables */
427 void *user_data) /* I - User data pointer */
428 {
429 ipp_t *col = ippNew(); /* Collection value */
430 ipp_attribute_t *attr = NULL; /* Current member attribute */
431 char token[1024]; /* Token string */
432
433
434 /*
435 * Parse the collection value...
436 */
437
438 while (_ippFileReadToken(f, token, sizeof(token)))
439 {
440 if (!_cups_strcasecmp(token, "}"))
441 {
442 /*
443 * End of collection value...
444 */
445
446 break;
447 }
448 else if (!_cups_strcasecmp(token, "MEMBER"))
449 {
450 /*
451 * Member attribute definition...
452 */
453
454 char syntax[128], /* Attribute syntax (value tag) */
455 name[128]; /* Attribute name */
456 ipp_tag_t value_tag; /* Value tag */
457
458 attr = NULL;
459
460 if (!_ippFileReadToken(f, syntax, sizeof(syntax)))
461 {
462 report_error(f, v, user_data, "Missing MEMBER syntax on line %d of \"%s\".", f->linenum, f->filename);
463 ippDelete(col);
464 col = NULL;
465 break;
466 }
467 else if ((value_tag = ippTagValue(syntax)) < IPP_TAG_UNSUPPORTED_VALUE)
468 {
469 report_error(f, v, user_data, "Bad MEMBER syntax \"%s\" on line %d of \"%s\".", syntax, f->linenum, f->filename);
470 ippDelete(col);
471 col = NULL;
472 break;
473 }
474
475 if (!_ippFileReadToken(f, name, sizeof(name)) || !name[0])
476 {
477 report_error(f, v, user_data, "Missing MEMBER name on line %d of \"%s\".", f->linenum, f->filename);
478 ippDelete(col);
479 col = NULL;
480 break;
481 }
482
483 if (value_tag < IPP_TAG_INTEGER)
484 {
485 /*
486 * Add out-of-band attribute - no value string needed...
487 */
488
489 ippAddOutOfBand(col, IPP_TAG_ZERO, value_tag, name);
490 }
491 else
492 {
493 /*
494 * Add attribute with one or more values...
495 */
496
497 attr = ippAddString(col, IPP_TAG_ZERO, value_tag, name, NULL, NULL);
498
499 if (!parse_value(f, v, user_data, col, &attr, 0))
500 {
501 ippDelete(col);
502 col = NULL;
503 break;
504 }
505 }
506
507 }
508 else if (attr && !_cups_strcasecmp(token, ","))
509 {
510 /*
511 * Additional value...
512 */
513
514 if (!parse_value(f, v, user_data, col, &attr, ippGetCount(attr)))
515 {
516 ippDelete(col);
517 col = NULL;
518 break;
519 }
520 }
521 else
522 {
523 /*
524 * Something else...
525 */
526
527 report_error(f, v, user_data, "Unknown directive \"%s\" on line %d of \"%s\".", token, f->linenum, f->filename);
528 ippDelete(col);
529 col = NULL;
530 attr = NULL;
531 break;
532
533 }
534 }
535
536 return (col);
537 }
538
539
540 /*
541 * 'parse_value()' - Parse an IPP value.
542 */
543
544 static int /* O - 1 on success or 0 on error */
545 parse_value(_ipp_file_t *f, /* I - IPP data file */
546 _ipp_vars_t *v, /* I - IPP variables */
547 void *user_data,/* I - User data pointer */
548 ipp_t *ipp, /* I - IPP message */
549 ipp_attribute_t **attr, /* IO - IPP attribute */
550 int element) /* I - Element number */
551 {
552 char value[2049], /* Value string */
553 *valueptr, /* Pointer into value string */
554 temp[2049], /* Temporary string */
555 *tempptr; /* Pointer into temporary string */
556 size_t valuelen; /* Length of value */
557
558
559 if (!_ippFileReadToken(f, temp, sizeof(temp)))
560 {
561 report_error(f, v, user_data, "Missing value on line %d of \"%s\".", f->linenum, f->filename);
562 return (0);
563 }
564
565 _ippVarsExpand(v, value, temp, sizeof(value));
566
567 switch (ippGetValueTag(*attr))
568 {
569 case IPP_TAG_BOOLEAN :
570 return (ippSetBoolean(ipp, attr, element, !_cups_strcasecmp(value, "true")));
571
572 case IPP_TAG_ENUM :
573 case IPP_TAG_INTEGER :
574 return (ippSetInteger(ipp, attr, element, (int)strtol(value, NULL, 0)));
575
576 case IPP_TAG_DATE :
577 {
578 int year, /* Year */
579 month, /* Month */
580 day, /* Day of month */
581 hour, /* Hour */
582 minute, /* Minute */
583 second, /* Second */
584 utc_offset = 0; /* Timezone offset from UTC */
585 ipp_uchar_t date[11]; /* dateTime value */
586
587 if (*value == 'P')
588 {
589 /*
590 * Time period...
591 */
592
593 time_t curtime; /* Current time in seconds */
594 int period = 0, /* Current period value */
595 saw_T = 0; /* Saw time separator */
596
597 curtime = time(NULL);
598
599 for (valueptr = value + 1; *valueptr; valueptr ++)
600 {
601 if (isdigit(*valueptr & 255))
602 {
603 period = (int)strtol(valueptr, &valueptr, 10);
604
605 if (!valueptr || period < 0)
606 {
607 report_error(f, v, user_data, "Bad dateTime value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
608 return (0);
609 }
610 }
611
612 if (*valueptr == 'Y')
613 {
614 curtime += 365 * 86400 * period;
615 period = 0;
616 }
617 else if (*valueptr == 'M')
618 {
619 if (saw_T)
620 curtime += 60 * period;
621 else
622 curtime += 30 * 86400 * period;
623
624 period = 0;
625 }
626 else if (*valueptr == 'D')
627 {
628 curtime += 86400 * period;
629 period = 0;
630 }
631 else if (*valueptr == 'H')
632 {
633 curtime += 3600 * period;
634 period = 0;
635 }
636 else if (*valueptr == 'S')
637 {
638 curtime += period;
639 period = 0;
640 }
641 else if (*valueptr == 'T')
642 {
643 saw_T = 1;
644 period = 0;
645 }
646 else
647 {
648 report_error(f, v, user_data, "Bad dateTime value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
649 return (0);
650 }
651 }
652
653 return (ippSetDate(ipp, attr, element, ippTimeToDate(curtime)));
654 }
655 else if (sscanf(value, "%d-%d-%dT%d:%d:%d%d", &year, &month, &day, &hour, &minute, &second, &utc_offset) < 6)
656 {
657 /*
658 * Date/time value did not parse...
659 */
660
661 report_error(f, v, user_data, "Bad dateTime value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
662 return (0);
663 }
664
665 date[0] = (ipp_uchar_t)(year >> 8);
666 date[1] = (ipp_uchar_t)(year & 255);
667 date[2] = (ipp_uchar_t)month;
668 date[3] = (ipp_uchar_t)day;
669 date[4] = (ipp_uchar_t)hour;
670 date[5] = (ipp_uchar_t)minute;
671 date[6] = (ipp_uchar_t)second;
672 date[7] = 0;
673 if (utc_offset < 0)
674 {
675 utc_offset = -utc_offset;
676 date[8] = (ipp_uchar_t)'-';
677 }
678 else
679 {
680 date[8] = (ipp_uchar_t)'+';
681 }
682
683 date[9] = (ipp_uchar_t)(utc_offset / 100);
684 date[10] = (ipp_uchar_t)(utc_offset % 100);
685
686 return (ippSetDate(ipp, attr, element, date));
687 }
688
689 case IPP_TAG_RESOLUTION :
690 {
691 int xres, /* X resolution */
692 yres; /* Y resolution */
693 char *ptr; /* Pointer into value */
694
695 xres = yres = (int)strtol(value, (char **)&ptr, 10);
696 if (ptr > value && xres > 0)
697 {
698 if (*ptr == 'x')
699 yres = (int)strtol(ptr + 1, (char **)&ptr, 10);
700 }
701
702 if (ptr <= value || xres <= 0 || yres <= 0 || !ptr || (_cups_strcasecmp(ptr, "dpi") && _cups_strcasecmp(ptr, "dpc") && _cups_strcasecmp(ptr, "dpcm") && _cups_strcasecmp(ptr, "other")))
703 {
704 report_error(f, v, user_data, "Bad resolution value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
705 return (0);
706 }
707
708 if (!_cups_strcasecmp(ptr, "dpi"))
709 return (ippSetResolution(ipp, attr, element, IPP_RES_PER_INCH, xres, yres));
710 else if (!_cups_strcasecmp(ptr, "dpc") || !_cups_strcasecmp(ptr, "dpcm"))
711 return (ippSetResolution(ipp, attr, element, IPP_RES_PER_CM, xres, yres));
712 else
713 return (ippSetResolution(ipp, attr, element, (ipp_res_t)0, xres, yres));
714 }
715
716 case IPP_TAG_RANGE :
717 {
718 int lower, /* Lower value */
719 upper; /* Upper value */
720
721 if (sscanf(value, "%d-%d", &lower, &upper) != 2)
722 {
723 report_error(f, v, user_data, "Bad rangeOfInteger value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
724 return (0);
725 }
726
727 return (ippSetRange(ipp, attr, element, lower, upper));
728 }
729
730 case IPP_TAG_STRING :
731 valuelen = strlen(value);
732
733 if (value[0] == '<' && value[strlen(value) - 1] == '>')
734 {
735 if (valuelen & 1)
736 {
737 report_error(f, v, user_data, "Bad octetString value on line %d of \"%s\".", f->linenum, f->filename);
738 return (0);
739 }
740
741 valueptr = value + 1;
742 tempptr = temp;
743
744 while (*valueptr && *valueptr != '>')
745 {
746 if (!isxdigit(valueptr[0] & 255) || !isxdigit(valueptr[1] & 255))
747 {
748 report_error(f, v, user_data, "Bad octetString value on line %d of \"%s\".", f->linenum, f->filename);
749 return (0);
750 }
751
752 if (valueptr[0] >= '0' && valueptr[0] <= '9')
753 *tempptr = (char)((valueptr[0] - '0') << 4);
754 else
755 *tempptr = (char)((tolower(valueptr[0]) - 'a' + 10) << 4);
756
757 if (valueptr[1] >= '0' && valueptr[1] <= '9')
758 *tempptr |= (valueptr[1] - '0');
759 else
760 *tempptr |= (tolower(valueptr[1]) - 'a' + 10);
761
762 tempptr ++;
763 }
764
765 return (ippSetOctetString(ipp, attr, element, temp, (int)(tempptr - temp)));
766 }
767 else
768 return (ippSetOctetString(ipp, attr, element, value, (int)valuelen));
769
770 case IPP_TAG_TEXTLANG :
771 case IPP_TAG_NAMELANG :
772 case IPP_TAG_TEXT :
773 case IPP_TAG_NAME :
774 case IPP_TAG_KEYWORD :
775 case IPP_TAG_URI :
776 case IPP_TAG_URISCHEME :
777 case IPP_TAG_CHARSET :
778 case IPP_TAG_LANGUAGE :
779 case IPP_TAG_MIMETYPE :
780 return (ippSetString(ipp, attr, element, value));
781
782 case IPP_TAG_BEGIN_COLLECTION :
783 {
784 int status; /* Add status */
785 ipp_t *col; /* Collection value */
786
787 if (strcmp(value, "{"))
788 {
789 report_error(f, v, user_data, "Bad collection value on line %d of \"%s\".", f->linenum, f->filename);
790 return (0);
791 }
792
793 if ((col = parse_collection(f, v, user_data)) == NULL)
794 return (0);
795
796 status = ippSetCollection(ipp, attr, element, col);
797 ippDelete(col);
798
799 return (status);
800 }
801
802 default :
803 report_error(f, v, user_data, "Unsupported value on line %d of \"%s\".", f->linenum, f->filename);
804 return (0);
805 }
806 }
807
808
809 /*
810 * 'report_error()' - Report an error.
811 */
812
813 static void
814 report_error(
815 _ipp_file_t *f, /* I - IPP data file */
816 _ipp_vars_t *v, /* I - Error callback function, if any */
817 void *user_data, /* I - User data pointer */
818 const char *message, /* I - Printf-style message */
819 ...) /* I - Additional arguments as needed */
820 {
821 char buffer[8192]; /* Formatted string */
822 va_list ap; /* Argument pointer */
823
824
825 va_start(ap, message);
826 vsnprintf(buffer, sizeof(buffer), message, ap);
827 va_end(ap);
828
829 if (v->errorcb)
830 (*v->errorcb)(f, user_data, buffer);
831 else
832 fprintf(stderr, "%s\n", buffer);
833 }