1 /** \ingroup header
2  * \file lib/headerfmt.c
3  */
4 
5 #include "system.h"
6 
7 #include <stdarg.h>
8 #include <rpm/header.h>
9 #include <rpm/rpmtag.h>
10 #include <rpm/rpmstring.h>
11 #include <rpm/rpmpgp.h>
12 #include "lib/misc.h"		/* format function protos */
13 
14 #include "debug.h"
15 
16 #define PARSER_BEGIN 	0
17 #define PARSER_IN_ARRAY 1
18 #define PARSER_IN_EXPR  2
19 
20 /** \ingroup header
21  */
22 typedef struct sprintfTag_s * sprintfTag;
23 struct sprintfTag_s {
24     headerFmt fmt;
25     rpmTagVal tag;
26     int justOne;
27     char * format;
28     char * type;
29 };
30 
31 typedef enum {
32     PTOK_NONE = 0,
33     PTOK_TAG,
34     PTOK_ARRAY,
35     PTOK_STRING,
36     PTOK_COND
37 } ptokType;
38 
39 /** \ingroup header
40  */
41 typedef struct sprintfToken_s * sprintfToken;
42 struct sprintfToken_s {
43     ptokType type;
44     union {
45 	struct sprintfTag_s tag;	/*!< PTOK_TAG */
46 	struct {
47 	    sprintfToken format;
48 	    int i;
49 	    int numTokens;
50 	} array;			/*!< PTOK_ARRAY */
51 	struct {
52 	    char * string;
53 	    int len;
54 	} string;			/*!< PTOK_STRING */
55 	struct {
56 	    sprintfToken ifFormat;
57 	    int numIfTokens;
58 	    sprintfToken elseFormat;
59 	    int numElseTokens;
60 	    struct sprintfTag_s tag;
61 	} cond;				/*!< PTOK_COND */
62     } u;
63 };
64 
65 #define HASHTYPE tagCache
66 #define HTKEYTYPE rpmTagVal
67 #define HTDATATYPE rpmtd
68 #include "lib/rpmhash.H"
69 #include "lib/rpmhash.C"
70 #undef HASHTYPE
71 #undef HTKEYTYPE
72 #undef HTDATATYPE
73 
74 /**
75  */
76 typedef struct headerSprintfArgs_s {
77     Header h;
78     char * fmt;
79     const char * errmsg;
80     tagCache cache;
81     sprintfToken format;
82     HeaderIterator hi;
83     char * val;
84     size_t vallen;
85     size_t alloced;
86     int numTokens;
87     int i;
88     headerGetFlags hgflags;
89 } * headerSprintfArgs;
90 
91 
escapedChar(const char ch)92 static char escapedChar(const char ch)
93 {
94     switch (ch) {
95     case 'a': 	return '\a';
96     case 'b': 	return '\b';
97     case 'f': 	return '\f';
98     case 'n': 	return '\n';
99     case 'r': 	return '\r';
100     case 't': 	return '\t';
101     case 'v': 	return '\v';
102     default:	return ch;
103     }
104 }
105 
106 /**
107  * Destroy headerSprintf format array.
108  * @param format	sprintf format array
109  * @param num		number of elements
110  * @return		NULL always
111  */
112 static sprintfToken
freeFormat(sprintfToken format,int num)113 freeFormat( sprintfToken format, int num)
114 {
115     int i;
116 
117     if (format == NULL) return NULL;
118 
119     for (i = 0; i < num; i++) {
120 	switch (format[i].type) {
121 	case PTOK_ARRAY:
122 	    format[i].u.array.format =
123 		freeFormat(format[i].u.array.format,
124 			format[i].u.array.numTokens);
125 	    break;
126 	case PTOK_COND:
127 	    format[i].u.cond.ifFormat =
128 		freeFormat(format[i].u.cond.ifFormat,
129 			format[i].u.cond.numIfTokens);
130 	    format[i].u.cond.elseFormat =
131 		freeFormat(format[i].u.cond.elseFormat,
132 			format[i].u.cond.numElseTokens);
133 	    break;
134 	case PTOK_NONE:
135 	case PTOK_TAG:
136 	case PTOK_STRING:
137 	default:
138 	    break;
139 	}
140     }
141     free(format);
142     return NULL;
143 }
144 
145 /**
146  * Initialize an hsa iteration.
147  * @param hsa		headerSprintf args
148  */
hsaInit(headerSprintfArgs hsa)149 static void hsaInit(headerSprintfArgs hsa)
150 {
151     sprintfTag tag =
152 	(hsa->format->type == PTOK_TAG
153 	    ? &hsa->format->u.tag :
154 	(hsa->format->type == PTOK_ARRAY
155 	    ? &hsa->format->u.array.format->u.tag :
156 	NULL));
157 
158     hsa->i = 0;
159     if (tag != NULL && tag->tag == -2)
160 	hsa->hi = headerInitIterator(hsa->h);
161     /* Normally with bells and whistles enabled, but raw dump on iteration. */
162     hsa->hgflags = (hsa->hi == NULL) ? HEADERGET_EXT : HEADERGET_RAW;
163 }
164 
165 /**
166  * Return next hsa iteration item.
167  * @param hsa		headerSprintf args
168  * @return		next sprintfToken (or NULL)
169  */
hsaNext(headerSprintfArgs hsa)170 static sprintfToken hsaNext(headerSprintfArgs hsa)
171 {
172     sprintfToken fmt = NULL;
173     sprintfTag tag =
174 	(hsa->format->type == PTOK_TAG
175 	    ? &hsa->format->u.tag :
176 	(hsa->format->type == PTOK_ARRAY
177 	    ? &hsa->format->u.array.format->u.tag :
178 	NULL));
179 
180     if (hsa->i >= 0 && hsa->i < hsa->numTokens) {
181 	fmt = hsa->format + hsa->i;
182 	if (hsa->hi == NULL) {
183 	    hsa->i++;
184 	} else {
185 	    tag->tag = headerNextTag(hsa->hi);
186 	    if (tag->tag == RPMTAG_NOT_FOUND)
187 		fmt = NULL;
188 	}
189     }
190 
191     return fmt;
192 }
193 
194 /**
195  * Finish an hsa iteration.
196  * @param hsa		headerSprintf args
197  */
hsaFini(headerSprintfArgs hsa)198 static void hsaFini(headerSprintfArgs hsa)
199 {
200     hsa->hi = headerFreeIterator(hsa->hi);
201     hsa->i = 0;
202 }
203 
204 /**
205  * Reserve sufficient buffer space for next output value.
206  * @param hsa		headerSprintf args
207  * @param need		no. of bytes to reserve
208  * @return		pointer to reserved space
209  */
hsaReserve(headerSprintfArgs hsa,size_t need)210 static char * hsaReserve(headerSprintfArgs hsa, size_t need)
211 {
212     if ((hsa->vallen + need) >= hsa->alloced) {
213 	if (hsa->alloced <= need)
214 	    hsa->alloced += need;
215 	hsa->alloced <<= 1;
216 	hsa->val = xrealloc(hsa->val, hsa->alloced+1);
217     }
218     return hsa->val + hsa->vallen;
219 }
220 
221 RPM_GNUC_PRINTF(2, 3)
hsaError(headerSprintfArgs hsa,const char * fmt,...)222 static void hsaError(headerSprintfArgs hsa, const char *fmt, ...)
223 {
224     /* Use thread local buffer as headerFormat() errmsg arg is const */
225     static __thread char *errbuf = NULL;
226 
227     if (fmt == NULL) {
228 	hsa->errmsg = NULL;
229     } else {
230 	va_list ap;
231 
232 	free(errbuf);
233 	va_start(ap, fmt);
234 	rvasprintf(&errbuf, fmt, ap);
235 	va_end(ap);
236 	hsa->errmsg = errbuf;
237     }
238 }
239 
240 /**
241  * Search tags for a name.
242  * @param hsa		headerSprintf args
243  * @param token		parsed fields
244  * @param name		name to find
245  * @return		0 on success, 1 on not found
246  */
findTag(headerSprintfArgs hsa,sprintfToken token,const char * name)247 static int findTag(headerSprintfArgs hsa, sprintfToken token, const char * name)
248 {
249     const char *tagname = name;
250     sprintfTag stag = (token->type == PTOK_COND
251 	? &token->u.cond.tag : &token->u.tag);
252 
253     stag->fmt = NULL;
254     stag->tag = RPMTAG_NOT_FOUND;
255 
256     if (!rstreq(tagname, "*")) {
257 	if (rstreqn("RPMTAG_", tagname, sizeof("RPMTAG_")-1)) {
258 	    tagname += sizeof("RPMTAG");
259 	}
260 
261 	/* Search tag names. */
262 	stag->tag = rpmTagGetValue(tagname);
263 	if (stag->tag == RPMTAG_NOT_FOUND) return 1;
264 
265     } else stag->tag = -2;
266 
267     /* Search extensions for specific format. */
268     if (stag->type != NULL)
269 	stag->fmt = rpmHeaderFormatByName(stag->type);
270 
271     return stag->fmt ? 0 : 1;
272 }
273 
274 /* forward ref */
275 /**
276  * Parse an expression.
277  * @param hsa		headerSprintf args
278  * @param token		token
279  * @param str		string
280  * @param[out] *endPtr
281  * @return		0 on success
282  */
283 static int parseExpression(headerSprintfArgs hsa, sprintfToken token,
284 		char * str,char ** endPtr);
285 
286 /**
287  * Parse a headerSprintf term.
288  * @param hsa		headerSprintf args
289  * @param str
290  * @retval *formatPtr
291  * @retval *numTokensPtr
292  * @retval *endPtr
293  * @param state
294  * @return		0 on success
295  */
parseFormat(headerSprintfArgs hsa,char * str,sprintfToken * formatPtr,int * numTokensPtr,char ** endPtr,int state)296 static int parseFormat(headerSprintfArgs hsa, char * str,
297 	sprintfToken * formatPtr,int * numTokensPtr,
298 	char ** endPtr, int state)
299 {
300     char * chptr, * start, * next, * dst;
301     sprintfToken format;
302     sprintfToken token;
303     int numTokens;
304     int done = 0;
305 
306     /* upper limit on number of individual formats */
307     numTokens = 0;
308     if (str != NULL)
309     for (chptr = str; *chptr != '\0'; chptr++)
310 	if (*chptr == '%' || *chptr == '[') numTokens++;
311     numTokens = numTokens * 2 + 1;
312 
313     format = xcalloc(numTokens, sizeof(*format));
314     if (endPtr) *endPtr = NULL;
315 
316     dst = start = str;
317     numTokens = 0;
318     token = NULL;
319     if (start != NULL)
320     while (*start != '\0') {
321 	switch (*start) {
322 	case '%':
323 	    /* handle %% */
324 	    if (*(start + 1) == '%') {
325 		if (token == NULL || token->type != PTOK_STRING) {
326 		    token = format + numTokens++;
327 		    token->type = PTOK_STRING;
328 		    dst = token->u.string.string = start;
329 		}
330 		start++;
331 		*dst++ = *start++;
332 		break;
333 	    }
334 
335 	    token = format + numTokens++;
336 	    *dst++ = '\0';
337 	    start++;
338 
339 	    if (*start == '|') {
340 		char * newEnd;
341 
342 		start++;
343 		if (parseExpression(hsa, token, start, &newEnd)) {
344 		    goto errxit;
345 		}
346 		start = newEnd;
347 		break;
348 	    }
349 
350 	    token->u.tag.format = start;
351 	    token->u.tag.justOne = 0;
352 
353 	    chptr = start;
354 	    while (*chptr && *chptr != '{' && *chptr != '%') {
355 		if (!risdigit(*chptr) && *chptr != '-') {
356 		    hsaError(hsa, _("invalid field width"));
357 		    goto errxit;
358 		}
359 		chptr++;
360 	    }
361 	    if (!*chptr || *chptr == '%') {
362 		hsaError(hsa, _("missing { after %%"));
363 		goto errxit;
364 	    }
365 
366 	    *chptr++ = '\0';
367 
368 	    while (start < chptr) {
369 		start++;
370 	    }
371 
372 	    if (*start == '=') {
373 		token->u.tag.justOne = 1;
374 		start++;
375 	    } else if (*start == '#') {
376 		token->u.tag.justOne = 1;
377 		token->u.tag.type = "arraysize";
378 		start++;
379 	    }
380 
381 	    dst = next = start;
382 	    while (*next && *next != '}') next++;
383 	    if (!*next) {
384 		hsaError(hsa, _("missing } after %%{"));
385 		goto errxit;
386 	    }
387 	    *next++ = '\0';
388 
389 	    chptr = start;
390 	    while (*chptr && *chptr != ':') chptr++;
391 
392 	    if (*chptr != '\0') {
393 		*chptr++ = '\0';
394 		if (!*chptr) {
395 		    hsaError(hsa, _("empty tag format"));
396 		    goto errxit;
397 		}
398 		token->u.tag.type = chptr;
399 	    }
400 	    /* default to string conversion if no formats found by now */
401 	    if (!token->u.tag.type) {
402 		token->u.tag.type = "string";
403 	    }
404 
405 	    if (!*start) {
406 		hsaError(hsa, _("empty tag name"));
407 		goto errxit;
408 	    }
409 
410 	    token->type = PTOK_TAG;
411 
412 	    if (findTag(hsa, token, start)) {
413 		hsaError(hsa, _("unknown tag: \"%s\""), start);
414 		goto errxit;
415 	    }
416 
417 	    start = next;
418 	    break;
419 
420 	case '[':
421 	    *dst++ = '\0';
422 	    *start++ = '\0';
423 	    token = format + numTokens++;
424 
425 	    if (parseFormat(hsa, start,
426 			    &token->u.array.format,
427 			    &token->u.array.numTokens,
428 			    &start, PARSER_IN_ARRAY)) {
429 		goto errxit;
430 	    }
431 
432 	    if (!start) {
433 		hsaError(hsa, _("] expected at end of array"));
434 		goto errxit;
435 	    }
436 
437 	    dst = start;
438 
439 	    token->type = PTOK_ARRAY;
440 
441 	    break;
442 
443 	case ']':
444 	    if (state != PARSER_IN_ARRAY) {
445 		hsaError(hsa, _("unexpected ]"));
446 		goto errxit;
447 	    }
448 	    *start++ = '\0';
449 	    if (endPtr) *endPtr = start;
450 	    done = 1;
451 	    break;
452 
453 	case '}':
454 	    if (state != PARSER_IN_EXPR) {
455 		hsaError(hsa, _("unexpected }"));
456 		goto errxit;
457 	    }
458 	    *start++ = '\0';
459 	    if (endPtr) *endPtr = start;
460 	    done = 1;
461 	    break;
462 
463 	default:
464 	    if (token == NULL || token->type != PTOK_STRING) {
465 		token = format + numTokens++;
466 		token->type = PTOK_STRING;
467 		dst = token->u.string.string = start;
468 	    }
469 
470 	    if (*start == '\\') {
471 		start++;
472 		if (*start == '\0') {
473 		    hsaError(hsa, _("escaped char expected after \\"));
474 		    goto errxit;
475 		}
476 		*dst++ = escapedChar(*start++);
477 	    } else {
478 		*dst++ = *start++;
479 	    }
480 	    break;
481 	}
482 	if (done)
483 	    break;
484     }
485 
486     if (dst != NULL)
487         *dst = '\0';
488 
489     for (int i = 0; i < numTokens; i++) {
490 	token = format + i;
491 	if (token->type == PTOK_STRING)
492 	    token->u.string.len = strlen(token->u.string.string);
493     }
494 
495     *numTokensPtr = numTokens;
496     *formatPtr = format;
497     return 0;
498 
499 errxit:
500     freeFormat(format, numTokens);
501     return 1;
502 }
503 
parseExpression(headerSprintfArgs hsa,sprintfToken token,char * str,char ** endPtr)504 static int parseExpression(headerSprintfArgs hsa, sprintfToken token,
505 		char * str, char ** endPtr)
506 {
507     char * chptr;
508     char * end;
509 
510     hsaError(hsa, NULL);
511     chptr = str;
512     while (*chptr && *chptr != '?') chptr++;
513 
514     if (*chptr != '?') {
515 	hsaError(hsa, _("? expected in expression"));
516 	return 1;
517     }
518 
519     *chptr++ = '\0';;
520 
521     if (*chptr != '{') {
522 	hsaError(hsa, _("{ expected after ? in expression"));
523 	return 1;
524     }
525 
526     chptr++;
527 
528     if (parseFormat(hsa, chptr, &token->u.cond.ifFormat,
529 		    &token->u.cond.numIfTokens, &end, PARSER_IN_EXPR))
530 	return 1;
531 
532     /* XXX fix segfault on "rpm -q rpm --qf='%|NAME?{%}:{NAME}|\n'"*/
533     if (!(end && *end)) {
534 	hsaError(hsa, _("} expected in expression"));
535 	token->u.cond.ifFormat =
536 		freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
537 	return 1;
538     }
539 
540     chptr = end;
541     if (*chptr != ':' && *chptr != '|') {
542 	hsaError(hsa, _(": expected following ? subexpression"));
543 	token->u.cond.ifFormat =
544 		freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
545 	return 1;
546     }
547 
548     if (*chptr == '|') {
549 	if (parseFormat(hsa, NULL, &token->u.cond.elseFormat,
550 		&token->u.cond.numElseTokens, &end, PARSER_IN_EXPR))
551 	{
552 	    token->u.cond.ifFormat =
553 		freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
554 	    return 1;
555 	}
556     } else {
557 	chptr++;
558 
559 	if (*chptr != '{') {
560 	    hsaError(hsa, _("{ expected after : in expression"));
561 	    token->u.cond.ifFormat =
562 		freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
563 	    return 1;
564 	}
565 
566 	chptr++;
567 
568 	if (parseFormat(hsa, chptr, &token->u.cond.elseFormat,
569 			&token->u.cond.numElseTokens, &end, PARSER_IN_EXPR))
570 	    return 1;
571 
572 	/* XXX fix segfault on "rpm -q rpm --qf='%|NAME?{a}:{%}|{NAME}\n'" */
573 	if (!(end && *end)) {
574 	    hsaError(hsa, _("} expected in expression"));
575 	    token->u.cond.ifFormat =
576 		freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
577 	    return 1;
578 	}
579 
580 	chptr = end;
581 	if (*chptr != '|') {
582 	    hsaError(hsa, _("| expected at end of expression"));
583 	    token->u.cond.ifFormat =
584 		freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
585 	    token->u.cond.elseFormat =
586 		freeFormat(token->u.cond.elseFormat, token->u.cond.numElseTokens);
587 	    return 1;
588 	}
589     }
590 
591     chptr++;
592 
593     *endPtr = chptr;
594 
595     token->type = PTOK_COND;
596 
597     (void) findTag(hsa, token, str);
598 
599     return 0;
600 }
601 
getCached(tagCache cache,rpmTagVal tag)602 static rpmtd getCached(tagCache cache, rpmTagVal tag)
603 {
604     rpmtd *res = NULL;
605     return tagCacheGetEntry(cache, tag, &res, NULL, NULL) ? res[0] : NULL;
606 }
607 
608 /**
609  * Do headerGet() just once for given tag, cache results.
610  * @param hsa		headerSprintf args
611  * @param tag
612  * @retval *typeptr
613  * @retval *data
614  * @retval *countptr
615  * @return		1 on success, 0 on failure
616  */
getData(headerSprintfArgs hsa,rpmTagVal tag)617 static rpmtd getData(headerSprintfArgs hsa, rpmTagVal tag)
618 {
619     rpmtd td = NULL;
620 
621     if (!(td = getCached(hsa->cache, tag))) {
622 	td = rpmtdNew();
623 	if (!headerGet(hsa->h, tag, td, hsa->hgflags)) {
624 	    rpmtdFree(td);
625 	    return NULL;
626 	}
627 	tagCacheAddEntry(hsa->cache, tag, td);
628     }
629 
630     return td;
631 }
632 
633 /**
634  * formatValue
635  * @param hsa		headerSprintf args
636  * @param tag
637  * @param element
638  * @return		end of formatted string (NULL on error)
639  */
formatValue(headerSprintfArgs hsa,sprintfTag tag,int element)640 static char * formatValue(headerSprintfArgs hsa, sprintfTag tag, int element)
641 {
642     char * val = NULL;
643     size_t need = 0;
644     char * t, * te;
645     rpmtd td;
646 
647     if ((td = getData(hsa, tag->tag)) && td->count > element) {
648 	td->ix = element; /* Ick, use iterators instead */
649 	val = rpmHeaderFormatCall(tag->fmt, td);
650     } else {
651 	val = xstrdup("(none)");
652     }
653 
654     /* Handle field width + justification formatting if specified */
655     if (tag->format && *tag->format) {
656 	char *tval = NULL;
657 	/* user string + extra for '%', format char and trailing '\0' */
658 	char fmtbuf[strlen(tag->format) + 3];
659 
660 	sprintf(fmtbuf, "%%%ss", tag->format);
661 	rasprintf(&tval, fmtbuf, val);
662 	free(val);
663 	val = tval;
664     }
665 
666     need = strlen(val);
667 
668     if (val && need > 0) {
669 	t = hsaReserve(hsa, need);
670 	te = stpcpy(t, val);
671 	hsa->vallen += (te - t);
672     }
673     free(val);
674 
675     return (hsa->val + hsa->vallen);
676 }
677 
678 /**
679  * Format a single headerSprintf item.
680  * @param hsa		headerSprintf args
681  * @param token
682  * @param element
683  * @return		end of formatted string (NULL on error)
684  */
singleSprintf(headerSprintfArgs hsa,sprintfToken token,int element)685 static char * singleSprintf(headerSprintfArgs hsa, sprintfToken token,
686 		int element)
687 {
688     char * t, * te;
689     int i, j, found;
690     rpm_count_t count, numElements;
691     sprintfToken spft;
692     int condNumFormats;
693     size_t need;
694 
695     /* we assume the token and header have been validated already! */
696 
697     switch (token->type) {
698     case PTOK_NONE:
699 	break;
700 
701     case PTOK_STRING:
702 	need = token->u.string.len;
703 	if (need == 0) break;
704 	t = hsaReserve(hsa, need);
705 	te = stpcpy(t, token->u.string.string);
706 	hsa->vallen += (te - t);
707 	break;
708 
709     case PTOK_TAG:
710 	t = hsa->val + hsa->vallen;
711 	te = formatValue(hsa, &token->u.tag,
712 			(token->u.tag.justOne ? 0 : element));
713 	if (te == NULL)
714 	    return NULL;
715 	break;
716 
717     case PTOK_COND:
718 	if (getData(hsa, token->u.cond.tag.tag) ||
719 		      headerIsEntry(hsa->h, token->u.cond.tag.tag)) {
720 	    spft = token->u.cond.ifFormat;
721 	    condNumFormats = token->u.cond.numIfTokens;
722 	} else {
723 	    spft = token->u.cond.elseFormat;
724 	    condNumFormats = token->u.cond.numElseTokens;
725 	}
726 
727 	need = condNumFormats * 20;
728 	if (spft == NULL || need == 0) break;
729 
730 	t = hsaReserve(hsa, need);
731 	for (i = 0; i < condNumFormats; i++, spft++) {
732 	    te = singleSprintf(hsa, spft, element);
733 	    if (te == NULL)
734 		return NULL;
735 	}
736 	break;
737 
738     case PTOK_ARRAY:
739 	numElements = 0;
740 	found = 0;
741 	spft = token->u.array.format;
742 	for (i = 0; i < token->u.array.numTokens; i++, spft++)
743 	{
744 	    rpmtd td = NULL;
745 	    if (spft->type != PTOK_TAG ||
746 		spft->u.tag.justOne) continue;
747 
748 	    if (!(td = getData(hsa, spft->u.tag.tag))) {
749 		continue;
750 	    }
751 
752 	    found = 1;
753 	    count = rpmtdCount(td);
754 
755 	    if (numElements > 0 && count != numElements) {
756 		hsaError(hsa,
757 			_("array iterator used with different sized arrays"));
758 		return NULL;
759 	    }
760 	    if (count > numElements)
761 		numElements = count;
762 	}
763 
764 	if (found) {
765 	    int isxml;
766 
767 	    need = numElements * token->u.array.numTokens * 10;
768 	    if (need == 0) break;
769 
770 	    spft = token->u.array.format;
771 	    isxml = (spft->type == PTOK_TAG && spft->u.tag.type != NULL &&
772 		    rstreq(spft->u.tag.type, "xml"));
773 
774 	    if (isxml) {
775 		const char * tagN = rpmTagGetName(spft->u.tag.tag);
776 
777 		need = sizeof("  <rpmTag name=\"\">\n") - 1;
778 		if (tagN != NULL)
779 		    need += strlen(tagN);
780 		t = hsaReserve(hsa, need);
781 		te = stpcpy(t, "  <rpmTag name=\"");
782 		if (tagN != NULL)
783 		    te = stpcpy(te, tagN);
784 		te = stpcpy(te, "\">\n");
785 		hsa->vallen += (te - t);
786 	    }
787 
788 	    t = hsaReserve(hsa, need);
789 	    for (j = 0; j < numElements; j++) {
790 		spft = token->u.array.format;
791 		for (i = 0; i < token->u.array.numTokens; i++, spft++) {
792 		    te = singleSprintf(hsa, spft, j);
793 		    if (te == NULL)
794 			return NULL;
795 		}
796 	    }
797 
798 	    if (isxml) {
799 		need = sizeof("  </rpmTag>\n") - 1;
800 		t = hsaReserve(hsa, need);
801 		te = stpcpy(t, "  </rpmTag>\n");
802 		hsa->vallen += (te - t);
803 	    }
804 
805 	}
806 	break;
807     }
808 
809     return (hsa->val + hsa->vallen);
810 }
811 
tagCmp(rpmTagVal a,rpmTagVal b)812 static int tagCmp(rpmTagVal a, rpmTagVal b)
813 {
814     return (a != b);
815 }
816 
tagId(rpmTagVal tag)817 static unsigned int tagId(rpmTagVal tag)
818 {
819     return tag;
820 }
821 
headerFormat(Header h,const char * fmt,errmsg_t * errmsg)822 char * headerFormat(Header h, const char * fmt, errmsg_t * errmsg)
823 {
824     struct headerSprintfArgs_s hsa;
825     sprintfToken nextfmt;
826     sprintfTag tag;
827     char * t, * te;
828     int isxml;
829     size_t need;
830 
831     memset(&hsa, 0, sizeof(hsa));
832     hsa.h = headerLink(h);
833     hsa.fmt = xstrdup(fmt);
834     hsa.errmsg = NULL;
835 
836     if (parseFormat(&hsa, hsa.fmt, &hsa.format, &hsa.numTokens, NULL, PARSER_BEGIN))
837 	goto exit;
838 
839     hsa.cache = tagCacheCreate(128, tagId, tagCmp, NULL, rpmtdFree);
840     hsa.val = xstrdup("");
841 
842     tag =
843 	(hsa.format->type == PTOK_TAG
844 	    ? &hsa.format->u.tag :
845 	(hsa.format->type == PTOK_ARRAY
846 	    ? &hsa.format->u.array.format->u.tag :
847 	NULL));
848     isxml = (tag != NULL && tag->tag == -2 && tag->type != NULL && rstreq(tag->type, "xml"));
849 
850     if (isxml) {
851 	need = sizeof("<rpmHeader>\n") - 1;
852 	t = hsaReserve(&hsa, need);
853 	te = stpcpy(t, "<rpmHeader>\n");
854 	hsa.vallen += (te - t);
855     }
856 
857     hsaInit(&hsa);
858     while ((nextfmt = hsaNext(&hsa)) != NULL) {
859 	te = singleSprintf(&hsa, nextfmt, 0);
860 	if (te == NULL) {
861 	    hsa.val = _free(hsa.val);
862 	    break;
863 	}
864     }
865     hsaFini(&hsa);
866 
867     if (isxml) {
868 	need = sizeof("</rpmHeader>\n") - 1;
869 	t = hsaReserve(&hsa, need);
870 	te = stpcpy(t, "</rpmHeader>\n");
871 	hsa.vallen += (te - t);
872     }
873 
874     if (hsa.val != NULL && hsa.vallen < hsa.alloced)
875 	hsa.val = xrealloc(hsa.val, hsa.vallen+1);
876 
877     hsa.cache = tagCacheFree(hsa.cache);
878     hsa.format = freeFormat(hsa.format, hsa.numTokens);
879 
880 exit:
881     if (errmsg)
882 	*errmsg = hsa.errmsg;
883     hsa.h = headerFree(hsa.h);
884     hsa.fmt = _free(hsa.fmt);
885     return hsa.val;
886 }
887 
888