xref: /freebsd/contrib/libxo/libxo/libxo.c (revision 34b867ca30479cec104fd069178df294f8ea35f1)
1 /*
2  * Copyright (c) 2014-2019, Juniper Networks, Inc.
3  * All rights reserved.
4  * This SOFTWARE is licensed under the LICENSE provided in the
5  * ../Copyright file. By downloading, installing, copying, or otherwise
6  * using the SOFTWARE, you agree to be bound by the terms of that
7  * LICENSE.
8  * Phil Shafer, July 2014
9  *
10  * This is the implementation of libxo, the formatting library that
11  * generates multiple styles of output from a single code path.
12  * Command line utilities can have their normal text output while
13  * automation tools can see XML or JSON output, and web tools can use
14  * HTML output that encodes the text output annotated with additional
15  * information.  Specialized encoders can be built that allow custom
16  * encoding including binary ones like CBOR, thrift, protobufs, etc.
17  *
18  * Full documentation is available in ./doc/libxo.txt or online at:
19  *   http://juniper.github.io/libxo/libxo-manual.html
20  *
21  * For first time readers, the core bits of code to start looking at are:
22  * - xo_do_emit() -- parse and emit a set of fields
23  * - xo_do_emit_fields -- the central function of the library
24  * - xo_do_format_field() -- handles formatting a single field
25  * - xo_transiton() -- the state machine that keeps things sane
26  * and of course the "xo_handle_t" data structure, which carries all
27  * configuration and state.
28  */
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <stdint.h>
33 #include <unistd.h>
34 #include <stddef.h>
35 #include <wchar.h>
36 #include <locale.h>
37 #include <sys/types.h>
38 #include <stdarg.h>
39 #include <string.h>
40 #include <errno.h>
41 #include <limits.h>
42 #include <ctype.h>
43 #include <wctype.h>
44 #include <getopt.h>
45 
46 #include "xo_config.h"
47 #include "xo.h"
48 #include "xo_encoder.h"
49 #include "xo_buf.h"
50 #include "xo_explicit.h"
51 
52 /*
53  * We ask wcwidth() to do an impossible job, really.  It's supposed to
54  * need to tell us the number of columns consumed to display a unicode
55  * character.  It returns that number without any sort of context, but
56  * we know they are characters whose glyph differs based on placement
57  * (end of word, middle of word, etc) and many that affect characters
58  * previously emitted.  Without content, it can't hope to tell us.
59  * But it's the only standard tool we've got, so we use it.  We would
60  * use wcswidth() but it typically just loops through adding the results
61  * of wcwidth() calls in an entirely unhelpful way.
62  *
63  * Even then, there are many poor implementations (macosx), so we have
64  * to carry our own.  We could have configure.ac test this (with
65  * something like 'assert(wcwidth(0x200d) == 0)'), but it would have
66  * to run a binary, which breaks cross-compilation.  Hmm... I could
67  * run this test at init time and make a warning for our dear user.
68  *
69  * Anyhow, it remains a best-effort sort of thing.  And it's all made
70  * more hopeless because we assume the display code doing the rendering is
71  * playing by the same rules we are.  If it display 0x200d as a square
72  * box or a funky question mark, the output will be hosed.
73  */
74 #ifdef LIBXO_WCWIDTH
75 #include "xo_wcwidth.h"
76 #else /* LIBXO_WCWIDTH */
77 #define xo_wcwidth(_x) wcwidth(_x)
78 #endif /* LIBXO_WCWIDTH */
79 
80 #ifdef HAVE_STDIO_EXT_H
81 #include <stdio_ext.h>
82 #endif /* HAVE_STDIO_EXT_H */
83 
84 /*
85  * humanize_number is a great function, unless you don't have it.  So
86  * we carry one in our pocket.
87  */
88 #ifdef HAVE_HUMANIZE_NUMBER
89 #include <libutil.h>
90 #define xo_humanize_number humanize_number
91 #else /* HAVE_HUMANIZE_NUMBER */
92 #include "xo_humanize.h"
93 #endif /* HAVE_HUMANIZE_NUMBER */
94 
95 #ifdef HAVE_GETTEXT
96 #include <libintl.h>
97 #endif /* HAVE_GETTEXT */
98 
99 /* Rather lame that we can't count on these... */
100 #ifndef FALSE
101 #define FALSE 0
102 #endif
103 #ifndef TRUE
104 #define TRUE 1
105 #endif
106 
107 /*
108  * Three styles of specifying thread-local variables are supported.
109  * configure.ac has the brains to run each possibility through the
110  * compiler and see what works; we are left to define the THREAD_LOCAL
111  * macro to the right value.  Most toolchains (clang, gcc) use
112  * "before", but some (borland) use "after" and I've heard of some
113  * (ms) that use __declspec.  Any others out there?
114  */
115 #define THREAD_LOCAL_before 1
116 #define THREAD_LOCAL_after 2
117 #define THREAD_LOCAL_declspec 3
118 
119 #ifndef HAVE_THREAD_LOCAL
120 #define THREAD_LOCAL(_x) _x
121 #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_before
122 #define THREAD_LOCAL(_x) __thread _x
123 #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_after
124 #define THREAD_LOCAL(_x) _x __thread
125 #elif HAVE_THREAD_LOCAL == THREAD_LOCAL_declspec
126 #define THREAD_LOCAL(_x) __declspec(_x)
127 #else
128 #error unknown thread-local setting
129 #endif /* HAVE_THREADS_H */
130 
131 const char xo_version[] = LIBXO_VERSION;
132 const char xo_version_extra[] = LIBXO_VERSION_EXTRA;
133 static const char xo_default_format[] = "%s";
134 
135 #ifndef UNUSED
136 #define UNUSED __attribute__ ((__unused__))
137 #endif /* UNUSED */
138 
139 #define XO_INDENT_BY 2	/* Amount to indent when pretty printing */
140 #define XO_DEPTH	128	 /* Default stack depth */
141 #define XO_MAX_ANCHOR_WIDTH (8*1024) /* Anything wider is just silly */
142 
143 #define XO_FAILURE_NAME	"failure"
144 
145 /* Flags for the stack frame */
146 typedef unsigned xo_xsf_flags_t; /* XSF_* flags */
147 #define XSF_NOT_FIRST	(1<<0)	/* Not the first element */
148 #define XSF_LIST	(1<<1)	/* Frame is a list */
149 #define XSF_INSTANCE	(1<<2)	/* Frame is an instance */
150 #define XSF_DTRT	(1<<3)	/* Save the name for DTRT mode */
151 
152 #define XSF_CONTENT	(1<<4)	/* Some content has been emitted */
153 #define XSF_EMIT	(1<<5)	/* Some field has been emitted */
154 #define XSF_EMIT_KEY	(1<<6)	/* A key has been emitted */
155 #define XSF_EMIT_LEAF_LIST (1<<7) /* A leaf-list field has been emitted */
156 
157 /* These are the flags we propagate between markers and their parents */
158 #define XSF_MARKER_FLAGS \
159  (XSF_NOT_FIRST | XSF_CONTENT | XSF_EMIT | XSF_EMIT_KEY | XSF_EMIT_LEAF_LIST )
160 
161 /*
162  * Turn the transition between two states into a number suitable for
163  * a "switch" statement.
164  */
165 #define XSS_TRANSITION(_old, _new) ((_old) << 8 | (_new))
166 
167 /*
168  * xo_stack_t: As we open and close containers and levels, we
169  * create a stack of frames to track them.  This is needed for
170  * XOF_WARN and XOF_XPATH.
171  */
172 typedef struct xo_stack_s {
173     xo_xsf_flags_t xs_flags;	/* Flags for this frame */
174     xo_state_t xs_state;	/* State for this stack frame */
175     char *xs_name;		/* Name (for XPath value) */
176     char *xs_keys;		/* XPath predicate for any key fields */
177 } xo_stack_t;
178 
179 /*
180  * libxo supports colors and effects, for those who like them.
181  * XO_COL_* ("colors") refers to fancy ansi codes, while X__EFF_*
182  * ("effects") are bits since we need to maintain state.
183  */
184 typedef uint8_t xo_color_t;
185 #define XO_COL_DEFAULT		0
186 #define XO_COL_BLACK		1
187 #define XO_COL_RED		2
188 #define XO_COL_GREEN		3
189 #define XO_COL_YELLOW		4
190 #define XO_COL_BLUE		5
191 #define XO_COL_MAGENTA		6
192 #define XO_COL_CYAN		7
193 #define XO_COL_WHITE		8
194 
195 #define XO_NUM_COLORS		9
196 
197 /*
198  * Yes, there's no blink.  We're civilized.  We like users.  Blink
199  * isn't something one does to someone you like.  Friends don't let
200  * friends use blink.  On friends.  You know what I mean.  Blink is
201  * like, well, it's like bursting into show tunes at a funeral.  It's
202  * just not done.  Not something anyone wants.  And on those rare
203  * instances where it might actually be appropriate, it's still wrong,
204  * since it's likely done by the wrong person for the wrong reason.
205  * Just like blink.  And if I implemented blink, I'd be like a funeral
206  * director who adds "Would you like us to burst into show tunes?" on
207  * the list of questions asked while making funeral arrangements.
208  * It's formalizing wrongness in the wrong way.  And we're just too
209  * civilized to do that.  Hhhmph!
210  */
211 #define XO_EFF_RESET		(1<<0)
212 #define XO_EFF_NORMAL		(1<<1)
213 #define XO_EFF_BOLD		(1<<2)
214 #define XO_EFF_UNDERLINE	(1<<3)
215 #define XO_EFF_INVERSE		(1<<4)
216 
217 #define XO_EFF_CLEAR_BITS XO_EFF_RESET /* Reset gets reset, surprisingly */
218 
219 typedef uint8_t xo_effect_t;
220 typedef struct xo_colors_s {
221     xo_effect_t xoc_effects;	/* Current effect set */
222     xo_color_t xoc_col_fg;	/* Foreground color */
223     xo_color_t xoc_col_bg;	/* Background color */
224 } xo_colors_t;
225 
226 /*
227  * xo_handle_t: this is the principle data structure for libxo.
228  * It's used as a store for state, options, content, and all manor
229  * of other information.
230  */
231 struct xo_handle_s {
232     xo_xof_flags_t xo_flags;	/* Flags (XOF_*) from the user*/
233     xo_xof_flags_t xo_iflags;	/* Internal flags (XOIF_*) */
234     xo_style_t xo_style;	/* XO_STYLE_* value */
235     unsigned short xo_indent;	/* Indent level (if pretty) */
236     unsigned short xo_indent_by; /* Indent amount (tab stop) */
237     xo_write_func_t xo_write;	/* Write callback */
238     xo_close_func_t xo_close;	/* Close callback */
239     xo_flush_func_t xo_flush;	/* Flush callback */
240     xo_formatter_t xo_formatter; /* Custom formating function */
241     xo_checkpointer_t xo_checkpointer; /* Custom formating support function */
242     void *xo_opaque;		/* Opaque data for write function */
243     xo_buffer_t xo_data;	/* Output data */
244     xo_buffer_t xo_fmt;	   	/* Work area for building format strings */
245     xo_buffer_t xo_attrs;	/* Work area for building XML attributes */
246     xo_buffer_t xo_predicate;	/* Work area for building XPath predicates */
247     xo_stack_t *xo_stack;	/* Stack pointer */
248     int xo_depth;		/* Depth of stack */
249     int xo_stack_size;		/* Size of the stack */
250     xo_info_t *xo_info;		/* Info fields for all elements */
251     int xo_info_count;		/* Number of info entries */
252     va_list xo_vap;		/* Variable arguments (stdargs) */
253     char *xo_leading_xpath;	/* A leading XPath expression */
254     mbstate_t xo_mbstate;	/* Multi-byte character conversion state */
255     ssize_t xo_anchor_offset;	/* Start of anchored text */
256     ssize_t xo_anchor_columns;	/* Number of columns since the start anchor */
257     ssize_t xo_anchor_min_width; /* Desired width of anchored text */
258     ssize_t xo_units_offset;	/* Start of units insertion point */
259     ssize_t xo_columns;	/* Columns emitted during this xo_emit call */
260 #ifndef LIBXO_TEXT_ONLY
261     xo_color_t xo_color_map_fg[XO_NUM_COLORS]; /* Foreground color mappings */
262     xo_color_t xo_color_map_bg[XO_NUM_COLORS]; /* Background color mappings */
263 #endif /* LIBXO_TEXT_ONLY */
264     xo_colors_t xo_colors;	/* Current color and effect values */
265     xo_buffer_t xo_color_buf;	/* HTML: buffer of colors and effects */
266     char *xo_version;		/* Version string */
267     int xo_errno;		/* Saved errno for "%m" */
268     char *xo_gt_domain;		/* Gettext domain, suitable for dgettext(3) */
269     xo_encoder_func_t xo_encoder; /* Encoding function */
270     void *xo_private;		/* Private data for external encoders */
271 };
272 
273 /* Flag operations */
274 #define XOF_BIT_ISSET(_flag, _bit)	(((_flag) & (_bit)) ? 1 : 0)
275 #define XOF_BIT_SET(_flag, _bit)	do { (_flag) |= (_bit); } while (0)
276 #define XOF_BIT_CLEAR(_flag, _bit)	do { (_flag) &= ~(_bit); } while (0)
277 
278 #define XOF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_flags, _bit)
279 #define XOF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_flags, _bit)
280 #define XOF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_flags, _bit)
281 
282 #define XOIF_ISSET(_xop, _bit) XOF_BIT_ISSET(_xop->xo_iflags, _bit)
283 #define XOIF_SET(_xop, _bit) XOF_BIT_SET(_xop->xo_iflags, _bit)
284 #define XOIF_CLEAR(_xop, _bit) XOF_BIT_CLEAR(_xop->xo_iflags, _bit)
285 
286 /* Internal flags */
287 #define XOIF_REORDER	XOF_BIT(0) /* Reordering fields; record field info */
288 #define XOIF_DIV_OPEN	XOF_BIT(1) /* A <div> is open */
289 #define XOIF_TOP_EMITTED XOF_BIT(2) /* The top JSON braces have been emitted */
290 #define XOIF_ANCHOR	XOF_BIT(3) /* An anchor is in place  */
291 
292 #define XOIF_UNITS_PENDING XOF_BIT(4) /* We have a units-insertion pending */
293 #define XOIF_INIT_IN_PROGRESS XOF_BIT(5) /* Init of handle is in progress */
294 #define XOIF_MADE_OUTPUT XOF_BIT(6)	 /* Have already made output */
295 
296 /*
297  * Normal printf has width and precision, which for strings operate as
298  * min and max number of columns.  But this depends on the idea that
299  * one byte means one column, which UTF-8 and multi-byte characters
300  * pitches on its ear.  It may take 40 bytes of data to populate 14
301  * columns, but we can't go off looking at 40 bytes of data without the
302  * caller's permission for fear/knowledge that we'll generate core files.
303  *
304  * So we make three values, distinguishing between "max column" and
305  * "number of bytes that we will inspect inspect safely" We call the
306  * later "size", and make the format "%[[<min>].[[<size>].<max>]]s".
307  *
308  * Under the "first do no harm" theory, we default "max" to "size".
309  * This is a reasonable assumption for folks that don't grok the
310  * MBS/WCS/UTF-8 world, and while it will be annoying, it will never
311  * be evil.
312  *
313  * For example, xo_emit("{:tag/%-14.14s}", buf) will make 14
314  * columns of output, but will never look at more than 14 bytes of the
315  * input buffer.  This is mostly compatible with printf and caller's
316  * expectations.
317  *
318  * In contrast xo_emit("{:tag/%-14..14s}", buf) will look at however
319  * many bytes (or until a NUL is seen) are needed to fill 14 columns
320  * of output.  xo_emit("{:tag/%-14.*.14s}", xx, buf) will look at up
321  * to xx bytes (or until a NUL is seen) in order to fill 14 columns
322  * of output.
323  *
324  * It's fairly amazing how a good idea (handle all languages of the
325  * world) blows such a big hole in the bottom of the fairly weak boat
326  * that is C string handling.  The simplicity and completenesss are
327  * sunk in ways we haven't even begun to understand.
328  */
329 #define XF_WIDTH_MIN	0	/* Minimal width */
330 #define XF_WIDTH_SIZE	1	/* Maximum number of bytes to examine */
331 #define XF_WIDTH_MAX	2	/* Maximum width */
332 #define XF_WIDTH_NUM	3	/* Numeric fields in printf (min.size.max) */
333 
334 /* Input and output string encodings */
335 #define XF_ENC_WIDE	1	/* Wide characters (wchar_t) */
336 #define XF_ENC_UTF8	2	/* UTF-8 */
337 #define XF_ENC_LOCALE	3	/* Current locale */
338 
339 /*
340  * A place to parse printf-style format flags for each field
341  */
342 typedef struct xo_format_s {
343     unsigned char xf_fc;	/* Format character */
344     unsigned char xf_enc;	/* Encoding of the string (XF_ENC_*) */
345     unsigned char xf_skip;	/* Skip this field */
346     unsigned char xf_lflag;	/* 'l' (long) */
347     unsigned char xf_hflag;;	/* 'h' (half) */
348     unsigned char xf_jflag;	/* 'j' (intmax_t) */
349     unsigned char xf_tflag;	/* 't' (ptrdiff_t) */
350     unsigned char xf_zflag;	/* 'z' (size_t) */
351     unsigned char xf_qflag;	/* 'q' (quad_t) */
352     unsigned char xf_seen_minus; /* Seen a minus */
353     int xf_leading_zero;	/* Seen a leading zero (zero fill)  */
354     unsigned xf_dots;		/* Seen one or more '.'s */
355     int xf_width[XF_WIDTH_NUM]; /* Width/precision/size numeric fields */
356     unsigned xf_stars;		/* Seen one or more '*'s */
357     unsigned char xf_star[XF_WIDTH_NUM]; /* Seen one or more '*'s */
358 } xo_format_t;
359 
360 /*
361  * This structure represents the parsed field information, suitable for
362  * processing by xo_do_emit and anything else that needs to parse fields.
363  * Note that all pointers point to the main format string.
364  *
365  * XXX This is a first step toward compilable or cachable format
366  * strings.  We can also cache the results of dgettext when no format
367  * is used, assuming the 'p' modifier has _not_ been set.
368  */
369 typedef struct xo_field_info_s {
370     xo_xff_flags_t xfi_flags;	/* Flags for this field */
371     unsigned xfi_ftype;		/* Field type, as character (e.g. 'V') */
372     const char *xfi_start;   /* Start of field in the format string */
373     const char *xfi_content;	/* Field's content */
374     const char *xfi_format;	/* Field's Format */
375     const char *xfi_encoding;	/* Field's encoding format */
376     const char *xfi_next;	/* Next character in format string */
377     ssize_t xfi_len;		/* Length of field */
378     ssize_t xfi_clen;		/* Content length */
379     ssize_t xfi_flen;		/* Format length */
380     ssize_t xfi_elen;		/* Encoding length */
381     unsigned xfi_fnum;		/* Field number (if used; 0 otherwise) */
382     unsigned xfi_renum;		/* Reordered number (0 == no renumbering) */
383 } xo_field_info_t;
384 
385 /*
386  * We keep a 'default' handle to allow callers to avoid having to
387  * allocate one.  Passing NULL to any of our functions will use
388  * this default handle.  Most functions have a variant that doesn't
389  * require a handle at all, since most output is to stdout, which
390  * the default handle handles handily.
391  */
392 static THREAD_LOCAL(xo_handle_t) xo_default_handle;
393 static THREAD_LOCAL(int) xo_default_inited;
394 static int xo_locale_inited;
395 static const char *xo_program;
396 
397 /*
398  * To allow libxo to be used in diverse environment, we allow the
399  * caller to give callbacks for memory allocation.
400  */
401 xo_realloc_func_t xo_realloc = realloc;
402 xo_free_func_t xo_free = free;
403 
404 /* Forward declarations */
405 static ssize_t
406 xo_transition (xo_handle_t *xop, xo_xof_flags_t flags, const char *name,
407 	       xo_state_t new_state);
408 
409 static int
410 xo_set_options_simple (xo_handle_t *xop, const char *input);
411 
412 static int
413 xo_color_find (const char *str);
414 
415 static void
416 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
417 		   const char *name, ssize_t nlen,
418 		   const char *value, ssize_t vlen,
419 		   const char *fmt, ssize_t flen,
420 		   const char *encoding, ssize_t elen);
421 
422 static void
423 xo_anchor_clear (xo_handle_t *xop);
424 
425 /*
426  * xo_style is used to retrieve the current style.  When we're built
427  * for "text only" mode, we use this function to drive the removal
428  * of most of the code in libxo.  We return a constant and the compiler
429  * happily removes the non-text code that is not longer executed.  This
430  * trims our code nicely without needing to trampel perfectly readable
431  * code with ifdefs.
432  */
433 static inline xo_style_t
xo_style(xo_handle_t * xop UNUSED)434 xo_style (xo_handle_t *xop UNUSED)
435 {
436 #ifdef LIBXO_TEXT_ONLY
437     return XO_STYLE_TEXT;
438 #else /* LIBXO_TEXT_ONLY */
439     return xop->xo_style;
440 #endif /* LIBXO_TEXT_ONLY */
441 }
442 
443 /*
444  * Allow the compiler to optimize out non-text-only code while
445  * still compiling it.
446  */
447 static inline int
xo_text_only(void)448 xo_text_only (void)
449 {
450 #ifdef LIBXO_TEXT_ONLY
451     return TRUE;
452 #else /* LIBXO_TEXT_ONLY */
453     return FALSE;
454 #endif /* LIBXO_TEXT_ONLY */
455 }
456 
457 /*
458  * Callback to write data to a FILE pointer
459  */
460 static xo_ssize_t
xo_write_to_file(void * opaque,const char * data)461 xo_write_to_file (void *opaque, const char *data)
462 {
463     FILE *fp = (FILE *) opaque;
464 
465     return fprintf(fp, "%s", data);
466 }
467 
468 /*
469  * Callback to close a file
470  */
471 static void
xo_close_file(void * opaque)472 xo_close_file (void *opaque)
473 {
474     FILE *fp = (FILE *) opaque;
475 
476     fclose(fp);
477 }
478 
479 /*
480  * Callback to flush a FILE pointer
481  */
482 static int
xo_flush_file(void * opaque)483 xo_flush_file (void *opaque)
484 {
485     FILE *fp = (FILE *) opaque;
486 
487     return fflush(fp);
488 }
489 
490 /*
491  * Use a rotating stock of buffers to make a printable string
492  */
493 #define XO_NUMBUFS 8
494 #define XO_SMBUFSZ 128
495 
496 static const char *
xo_printable(const char * str)497 xo_printable (const char *str)
498 {
499     static THREAD_LOCAL(char) bufset[XO_NUMBUFS][XO_SMBUFSZ];
500     static THREAD_LOCAL(int) bufnum = 0;
501 
502     if (str == NULL)
503 	return "";
504 
505     if (++bufnum == XO_NUMBUFS)
506 	bufnum = 0;
507 
508     char *res = bufset[bufnum], *cp, *ep;
509 
510     for (cp = res, ep = res + XO_SMBUFSZ - 1; *str && cp < ep; cp++, str++) {
511 	if (*str == '\n') {
512 	    *cp++ = '\\';
513 	    *cp = 'n';
514 	} else if (*str == '\r') {
515 	    *cp++ = '\\';
516 	    *cp = 'r';
517 	} else if (*str == '\"') {
518 	    *cp++ = '\\';
519 	    *cp = '"';
520 	} else
521 	    *cp = *str;
522     }
523 
524     *cp = '\0';
525     return res;
526 }
527 
528 static int
xo_depth_check(xo_handle_t * xop,int depth)529 xo_depth_check (xo_handle_t *xop, int depth)
530 {
531     xo_stack_t *xsp;
532 
533     if (depth >= xop->xo_stack_size) {
534 	depth += XO_DEPTH;	/* Extra room */
535 
536 	xsp = xo_realloc(xop->xo_stack, sizeof(xop->xo_stack[0]) * depth);
537 	if (xsp == NULL) {
538 	    xo_failure(xop, "xo_depth_check: out of memory (%d)", depth);
539 	    return -1;
540 	}
541 
542 	int count = depth - xop->xo_stack_size;
543 
544 	bzero(xsp + xop->xo_stack_size, count * sizeof(*xsp));
545 	xop->xo_stack_size = depth;
546 	xop->xo_stack = xsp;
547     }
548 
549     return 0;
550 }
551 
552 void
xo_no_setlocale(void)553 xo_no_setlocale (void)
554 {
555     xo_locale_inited = 1;	/* Skip initialization */
556 }
557 
558 /*
559  * For XML, the first character of a tag cannot be numeric, but people
560  * will likely not notice.  So we people-proof them by forcing a leading
561  * underscore if they use invalid tags.  Note that this doesn't cover
562  * all broken tags, just this fairly specific case.
563  */
564 static const char *
xo_xml_leader_len(xo_handle_t * xop,const char * name,xo_ssize_t nlen)565 xo_xml_leader_len (xo_handle_t *xop, const char *name, xo_ssize_t nlen)
566 {
567     if (name == NULL || isalpha(name[0]) || name[0] == '_')
568         return "";
569 
570     xo_failure(xop, "invalid XML tag name: '%.*s'", nlen, name);
571     return "_";
572 }
573 
574 static const char *
xo_xml_leader(xo_handle_t * xop,const char * name)575 xo_xml_leader (xo_handle_t *xop, const char *name)
576 {
577     return xo_xml_leader_len(xop, name, strlen(name));
578 }
579 
580 /*
581  * We need to decide if stdout is line buffered (_IOLBF).  Lacking a
582  * standard way to decide this (e.g. getlinebuf()), we have configure
583  * look to find __flbf, which glibc supported.  If not, we'll rely on
584  * isatty, with the assumption that terminals are the only thing
585  * that's line buffered.  We _could_ test for "steam._flags & _IOLBF",
586  * which is all __flbf does, but that's even tackier.  Like a
587  * bedazzled Elvis outfit on an ugly lap dog sort of tacky.  Not
588  * something we're willing to do.
589  */
590 static int
xo_is_line_buffered(FILE * stream)591 xo_is_line_buffered (FILE *stream)
592 {
593 #if HAVE___FLBF
594     if (__flbf(stream))
595 	return 1;
596 #else /* HAVE___FLBF */
597     if (isatty(fileno(stream)))
598 	return 1;
599 #endif /* HAVE___FLBF */
600     return 0;
601 }
602 
603 /*
604  * Initialize an xo_handle_t, using both static defaults and
605  * the global settings from the LIBXO_OPTIONS environment
606  * variable.
607  */
608 static void
xo_init_handle(xo_handle_t * xop)609 xo_init_handle (xo_handle_t *xop)
610 {
611     xop->xo_opaque = stdout;
612     xop->xo_write = xo_write_to_file;
613     xop->xo_flush = xo_flush_file;
614 
615     if (xo_is_line_buffered(stdout))
616 	XOF_SET(xop, XOF_FLUSH_LINE);
617 
618     /*
619      * We need to initialize the locale, which isn't really pretty.
620      * Libraries should depend on their caller to set up the
621      * environment.  But we really can't count on the caller to do
622      * this, because well, they won't.  Trust me.
623      */
624     if (!xo_locale_inited) {
625 	xo_locale_inited = 1;	/* Only do this once */
626 
627 #ifdef __FreeBSD__		/* Who does The Right Thing */
628 	const char *cp = "";
629 #else /* __FreeBSD__ */
630 	const char *cp = getenv("LC_ALL");
631 	if (cp == NULL)
632 	    cp = getenv("LC_CTYPE");
633 	if (cp == NULL)
634 	    cp = getenv("LANG");
635 	if (cp == NULL)
636 	    cp = "C";		/* Default for C programs */
637 #endif /* __FreeBSD__ */
638 
639 	(void) setlocale(LC_CTYPE, cp);
640     }
641 
642     /*
643      * Initialize only the xo_buffers we know we'll need; the others
644      * can be allocated as needed.
645      */
646     xo_buf_init(&xop->xo_data);
647     xo_buf_init(&xop->xo_fmt);
648 
649     if (XOIF_ISSET(xop, XOIF_INIT_IN_PROGRESS))
650 	return;
651     XOIF_SET(xop, XOIF_INIT_IN_PROGRESS);
652 
653     xop->xo_indent_by = XO_INDENT_BY;
654     xo_depth_check(xop, XO_DEPTH);
655 
656     XOIF_CLEAR(xop, XOIF_INIT_IN_PROGRESS);
657 }
658 
659 /*
660  * Initialize the default handle.
661  */
662 static void
xo_default_init(void)663 xo_default_init (void)
664 {
665     xo_handle_t *xop = &xo_default_handle;
666 
667     xo_init_handle(xop);
668 
669 #if !defined(NO_LIBXO_OPTIONS)
670     if (!XOF_ISSET(xop, XOF_NO_ENV)) {
671        char *env = getenv("LIBXO_OPTIONS");
672 
673        if (env)
674            xo_set_options_simple(xop, env);
675 
676     }
677 #endif /* NO_LIBXO_OPTIONS */
678 
679     xo_default_inited = 1;
680 }
681 
682 /*
683  * Cheap convenience function to return either the argument, or
684  * the internal handle, after it has been initialized.  The usage
685  * is:
686  *    xop = xo_default(xop);
687  */
688 static xo_handle_t *
xo_default(xo_handle_t * xop)689 xo_default (xo_handle_t *xop)
690 {
691     if (xop == NULL) {
692 	if (xo_default_inited == 0)
693 	    xo_default_init();
694 	xop = &xo_default_handle;
695     }
696 
697     return xop;
698 }
699 
700 /*
701  * Return the number of spaces we should be indenting.  If
702  * we are pretty-printing, this is indent * indent_by.
703  */
704 static int
xo_indent(xo_handle_t * xop)705 xo_indent (xo_handle_t *xop)
706 {
707     int rc = 0;
708 
709     xop = xo_default(xop);
710 
711     if (XOF_ISSET(xop, XOF_PRETTY)) {
712 	rc = xop->xo_indent * xop->xo_indent_by;
713 	if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
714 	    rc += xop->xo_indent_by;
715     }
716 
717     return (rc > 0) ? rc : 0;
718 }
719 
720 static void
xo_buf_indent(xo_handle_t * xop,int indent)721 xo_buf_indent (xo_handle_t *xop, int indent)
722 {
723     xo_buffer_t *xbp = &xop->xo_data;
724 
725     if (indent <= 0)
726 	indent = xo_indent(xop);
727 
728     if (!xo_buf_has_room(xbp, indent))
729 	return;
730 
731     memset(xbp->xb_curp, ' ', indent);
732     xbp->xb_curp += indent;
733 }
734 
735 static char xo_xml_amp[] = "&amp;";
736 static char xo_xml_lt[] = "&lt;";
737 static char xo_xml_gt[] = "&gt;";
738 static char xo_xml_quot[] = "&quot;";
739 
740 static ssize_t
xo_escape_xml(xo_buffer_t * xbp,ssize_t len,xo_xff_flags_t flags)741 xo_escape_xml (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags)
742 {
743     ssize_t slen;
744     ssize_t delta = 0;
745     char *cp, *ep, *ip;
746     const char *sp;
747     int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
748 
749     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
750 	/* We're subtracting 2: 1 for the NUL, 1 for the char we replace */
751 	if (*cp == '<')
752 	    delta += sizeof(xo_xml_lt) - 2;
753 	else if (*cp == '>')
754 	    delta += sizeof(xo_xml_gt) - 2;
755 	else if (*cp == '&')
756 	    delta += sizeof(xo_xml_amp) - 2;
757 	else if (attr && *cp == '"')
758 	    delta += sizeof(xo_xml_quot) - 2;
759     }
760 
761     if (delta == 0)		/* Nothing to escape; bail */
762 	return len;
763 
764     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
765 	return 0;
766 
767     ep = xbp->xb_curp;
768     cp = ep + len;
769     ip = cp + delta;
770     do {
771 	cp -= 1;
772 	ip -= 1;
773 
774 	if (*cp == '<')
775 	    sp = xo_xml_lt;
776 	else if (*cp == '>')
777 	    sp = xo_xml_gt;
778 	else if (*cp == '&')
779 	    sp = xo_xml_amp;
780 	else if (attr && *cp == '"')
781 	    sp = xo_xml_quot;
782 	else {
783 	    *ip = *cp;
784 	    continue;
785 	}
786 
787 	slen = strlen(sp);
788 	ip -= slen - 1;
789 	memcpy(ip, sp, slen);
790 
791     } while (cp > ep && cp != ip);
792 
793     return len + delta;
794 }
795 
796 static ssize_t
xo_escape_json(xo_buffer_t * xbp,ssize_t len,xo_xff_flags_t flags UNUSED)797 xo_escape_json (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
798 {
799     ssize_t delta = 0;
800     char *cp, *ep, *ip;
801 
802     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
803 	if (*cp == '\\' || *cp == '"')
804 	    delta += 1;
805 	else if (*cp == '\n' || *cp == '\r')
806 	    delta += 1;
807     }
808 
809     if (delta == 0)		/* Nothing to escape; bail */
810 	return len;
811 
812     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
813 	return 0;
814 
815     ep = xbp->xb_curp;
816     cp = ep + len;
817     ip = cp + delta;
818     do {
819 	cp -= 1;
820 	ip -= 1;
821 
822 	if (*cp == '\\' || *cp == '"') {
823 	    *ip-- = *cp;
824 	    *ip = '\\';
825 	} else if (*cp == '\n') {
826 	    *ip-- = 'n';
827 	    *ip = '\\';
828 	} else if (*cp == '\r') {
829 	    *ip-- = 'r';
830 	    *ip = '\\';
831 	} else {
832 	    *ip = *cp;
833 	}
834 
835     } while (cp > ep && cp != ip);
836 
837     return len + delta;
838 }
839 
840 /*
841  * PARAM-VALUE     = UTF-8-STRING ; characters '"', '\' and
842  *                                ; ']' MUST be escaped.
843  */
844 static ssize_t
xo_escape_sdparams(xo_buffer_t * xbp,ssize_t len,xo_xff_flags_t flags UNUSED)845 xo_escape_sdparams (xo_buffer_t *xbp, ssize_t len, xo_xff_flags_t flags UNUSED)
846 {
847     ssize_t delta = 0;
848     char *cp, *ep, *ip;
849 
850     for (cp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
851 	if (*cp == '\\' || *cp == '"' || *cp == ']')
852 	    delta += 1;
853     }
854 
855     if (delta == 0)		/* Nothing to escape; bail */
856 	return len;
857 
858     if (!xo_buf_has_room(xbp, delta)) /* No room; bail, but don't append */
859 	return 0;
860 
861     ep = xbp->xb_curp;
862     cp = ep + len;
863     ip = cp + delta;
864     do {
865 	cp -= 1;
866 	ip -= 1;
867 
868 	if (*cp == '\\' || *cp == '"' || *cp == ']') {
869 	    *ip-- = *cp;
870 	    *ip = '\\';
871 	} else {
872 	    *ip = *cp;
873 	}
874 
875     } while (cp > ep && cp != ip);
876 
877     return len + delta;
878 }
879 
880 static void
xo_buf_escape(xo_handle_t * xop,xo_buffer_t * xbp,const char * str,ssize_t len,xo_xff_flags_t flags)881 xo_buf_escape (xo_handle_t *xop, xo_buffer_t *xbp,
882 	       const char *str, ssize_t len, xo_xff_flags_t flags)
883 {
884     if (!xo_buf_has_room(xbp, len))
885 	return;
886 
887     memcpy(xbp->xb_curp, str, len);
888 
889     switch (xo_style(xop)) {
890     case XO_STYLE_XML:
891     case XO_STYLE_HTML:
892 	len = xo_escape_xml(xbp, len, flags);
893 	break;
894 
895     case XO_STYLE_JSON:
896 	len = xo_escape_json(xbp, len, flags);
897 	break;
898 
899     case XO_STYLE_SDPARAMS:
900 	len = xo_escape_sdparams(xbp, len, flags);
901 	break;
902     }
903 
904     xbp->xb_curp += len;
905 }
906 
907 /*
908  * Write the current contents of the data buffer using the handle's
909  * xo_write function.
910  */
911 static ssize_t
xo_write(xo_handle_t * xop)912 xo_write (xo_handle_t *xop)
913 {
914     ssize_t rc = 0;
915     xo_buffer_t *xbp = &xop->xo_data;
916 
917     if (xbp->xb_curp != xbp->xb_bufp) {
918 	xo_buf_append(xbp, "", 1); /* Append ending NUL */
919 	xo_anchor_clear(xop);
920 	if (xop->xo_write)
921 	    rc = xop->xo_write(xop->xo_opaque, xbp->xb_bufp);
922 	xbp->xb_curp = xbp->xb_bufp;
923     }
924 
925     /* Turn off the flags that don't survive across writes */
926     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
927 
928     return rc;
929 }
930 
931 /*
932  * Format arguments into our buffer.  If a custom formatter has been set,
933  * we use that to do the work; otherwise we vsnprintf().
934  */
935 static ssize_t
xo_vsnprintf(xo_handle_t * xop,xo_buffer_t * xbp,const char * fmt,va_list vap)936 xo_vsnprintf (xo_handle_t *xop, xo_buffer_t *xbp, const char *fmt, va_list vap)
937 {
938     va_list va_local;
939     ssize_t rc;
940     ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
941 
942     va_copy(va_local, vap);
943 
944     if (xop->xo_formatter)
945 	rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
946     else
947 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
948 
949     if (rc >= left) {
950 	if (!xo_buf_has_room(xbp, rc)) {
951 	    va_end(va_local);
952 	    return -1;
953 	}
954 
955 	/*
956 	 * After we call vsnprintf(), the stage of vap is not defined.
957 	 * We need to copy it before we pass.  Then we have to do our
958 	 * own logic below to move it along.  This is because the
959 	 * implementation can have va_list be a pointer (bsd) or a
960 	 * structure (macosx) or anything in between.
961 	 */
962 
963 	va_end(va_local);	/* Reset vap to the start */
964 	va_copy(va_local, vap);
965 
966 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
967 	if (xop->xo_formatter)
968 	    rc = xop->xo_formatter(xop, xbp->xb_curp, left, fmt, va_local);
969 	else
970 	    rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
971     }
972     va_end(va_local);
973 
974     return rc;
975 }
976 
977 /*
978  * Print some data through the handle.
979  */
980 static ssize_t
xo_printf_v(xo_handle_t * xop,const char * fmt,va_list vap)981 xo_printf_v (xo_handle_t *xop, const char *fmt, va_list vap)
982 {
983     xo_buffer_t *xbp = &xop->xo_data;
984     ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
985     ssize_t rc;
986     va_list va_local;
987 
988     va_copy(va_local, vap);
989 
990     rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
991 
992     if (rc >= left) {
993 	if (!xo_buf_has_room(xbp, rc)) {
994 	    va_end(va_local);
995 	    return -1;
996 	}
997 
998 	va_end(va_local);	/* Reset vap to the start */
999 	va_copy(va_local, vap);
1000 
1001 	left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1002 	rc = vsnprintf(xbp->xb_curp, left, fmt, va_local);
1003     }
1004 
1005     va_end(va_local);
1006 
1007     if (rc > 0)
1008 	xbp->xb_curp += rc;
1009 
1010     return rc;
1011 }
1012 
1013 static ssize_t
xo_printf(xo_handle_t * xop,const char * fmt,...)1014 xo_printf (xo_handle_t *xop, const char *fmt, ...)
1015 {
1016     ssize_t rc;
1017     va_list vap;
1018 
1019     va_start(vap, fmt);
1020 
1021     rc = xo_printf_v(xop, fmt, vap);
1022 
1023     va_end(vap);
1024     return rc;
1025 }
1026 
1027 /*
1028  * These next few function are make The Essential UTF-8 Ginsu Knife.
1029  * Identify an input and output character, and convert it.
1030  */
1031 static uint8_t xo_utf8_data_bits[5] = { 0, 0x7f, 0x1f, 0x0f, 0x07 };
1032 static uint8_t xo_utf8_len_bits[5]  = { 0, 0x00, 0xc0, 0xe0, 0xf0 };
1033 
1034 /*
1035  * If the byte has a high-bit set, it's UTF-8, not ASCII.
1036  */
1037 static int
xo_is_utf8(char ch)1038 xo_is_utf8 (char ch)
1039 {
1040     return (ch & 0x80);
1041 }
1042 
1043 /*
1044  * Look at the high bits of the first byte to determine the length
1045  * of the UTF-8 character.
1046  */
1047 static inline ssize_t
xo_utf8_to_wc_len(const char * buf)1048 xo_utf8_to_wc_len (const char *buf)
1049 {
1050     uint8_t bval = (uint8_t) *buf;
1051     ssize_t len;
1052 
1053     if ((bval & 0x80) == 0x0)
1054 	len = 1;
1055     else if ((bval & 0xe0) == 0xc0)
1056 	len = 2;
1057     else if ((bval & 0xf0) == 0xe0)
1058 	len = 3;
1059     else if ((bval & 0xf8) == 0xf0)
1060 	len = 4;
1061     else
1062 	len = -1;
1063 
1064     return len;
1065 }
1066 
1067 static ssize_t
xo_buf_utf8_len(xo_handle_t * xop,const char * buf,ssize_t bufsiz)1068 xo_buf_utf8_len (xo_handle_t *xop, const char *buf, ssize_t bufsiz)
1069 {
1070     unsigned b = (unsigned char) *buf;
1071     ssize_t len, i;
1072 
1073     len = xo_utf8_to_wc_len(buf);
1074     if (len < 0) {
1075         xo_failure(xop, "invalid UTF-8 data: %02hhx", b);
1076 	return -1;
1077     }
1078 
1079     if (len > bufsiz) {
1080         xo_failure(xop, "invalid UTF-8 data (short): %02hhx (%d/%d)",
1081 		   b, len, bufsiz);
1082 	return -1;
1083     }
1084 
1085     for (i = 2; i < len; i++) {
1086 	b = (unsigned char ) buf[i];
1087 	if ((b & 0xc0) != 0x80) {
1088 	    xo_failure(xop, "invalid UTF-8 data (byte %d): %x", i, b);
1089 	    return -1;
1090 	}
1091     }
1092 
1093     return len;
1094 }
1095 
1096 /*
1097  * Build a wide character from the input buffer; the number of
1098  * bits we pull off the first character is dependent on the length,
1099  * but we put 6 bits off all other bytes.
1100  */
1101 static inline wchar_t
xo_utf8_char(const char * buf,ssize_t len)1102 xo_utf8_char (const char *buf, ssize_t len)
1103 {
1104     /* Most common case: singleton byte */
1105     if (len == 1)
1106 	return (unsigned char) buf[0];
1107 
1108     ssize_t i;
1109     wchar_t wc;
1110     const unsigned char *cp = (const unsigned char *) buf;
1111 
1112     wc = *cp & xo_utf8_data_bits[len];
1113     for (i = 1; i < len; i++) {
1114 	wc <<= 6;		/* Low six bits have data */
1115 	wc |= cp[i] & 0x3f;
1116 	if ((cp[i] & 0xc0) != 0x80)
1117 	    return (wchar_t) -1;
1118     }
1119 
1120     return wc;
1121 }
1122 
1123 /*
1124  * Determine the number of bytes needed to encode a wide character.
1125  */
1126 static ssize_t
xo_utf8_emit_len(wchar_t wc)1127 xo_utf8_emit_len (wchar_t wc)
1128 {
1129     ssize_t len;
1130 
1131     if ((wc & ((1 << 7) - 1)) == wc) /* Simple case */
1132 	len = 1;
1133     else if ((wc & ((1 << 11) - 1)) == wc)
1134 	len = 2;
1135     else if ((wc & ((1 << 16) - 1)) == wc)
1136 	len = 3;
1137     else if ((wc & ((1 << 21) - 1)) == wc)
1138 	len = 4;
1139     else
1140 	len = -1;		/* Invalid */
1141 
1142     return len;
1143 }
1144 
1145 /*
1146  * Emit one wide character into the given buffer
1147  */
1148 static void
xo_utf8_emit_char(char * buf,ssize_t len,wchar_t wc)1149 xo_utf8_emit_char (char *buf, ssize_t len, wchar_t wc)
1150 {
1151     ssize_t i;
1152 
1153     if (len == 1) { /* Simple case */
1154 	buf[0] = wc & 0x7f;
1155 	return;
1156     }
1157 
1158     /* Start with the low bits and insert them, six bits at a time */
1159     for (i = len - 1; i >= 0; i--) {
1160 	buf[i] = 0x80 | (wc & 0x3f);
1161 	wc >>= 6;		/* Drop the low six bits */
1162     }
1163 
1164     /* Finish off the first byte with the length bits */
1165     buf[0] &= xo_utf8_data_bits[len]; /* Clear out the length bits */
1166     buf[0] |= xo_utf8_len_bits[len]; /* Drop in new length bits */
1167 }
1168 
1169 /*
1170  * Append a single UTF-8 character to a buffer, converting it to locale
1171  * encoding.  Returns the number of columns consumed by that character,
1172  * as best we can determine it.
1173  */
1174 static ssize_t
xo_buf_append_locale_from_utf8(xo_handle_t * xop,xo_buffer_t * xbp,const char * ibuf,ssize_t ilen)1175 xo_buf_append_locale_from_utf8 (xo_handle_t *xop, xo_buffer_t *xbp,
1176 				const char *ibuf, ssize_t ilen)
1177 {
1178     wchar_t wc;
1179     ssize_t len;
1180 
1181     /*
1182      * Build our wide character from the input buffer; the number of
1183      * bits we pull off the first character is dependent on the length,
1184      * but we put 6 bits off all other bytes.
1185      */
1186     wc = xo_utf8_char(ibuf, ilen);
1187     if (wc == (wchar_t) -1) {
1188 	xo_failure(xop, "invalid UTF-8 byte sequence");
1189 	return 0;
1190     }
1191 
1192     if (XOF_ISSET(xop, XOF_NO_LOCALE)) {
1193 	if (!xo_buf_has_room(xbp, ilen))
1194 	    return 0;
1195 
1196 	memcpy(xbp->xb_curp, ibuf, ilen);
1197 	xbp->xb_curp += ilen;
1198 
1199     } else {
1200 	if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
1201 	    return 0;
1202 
1203 	bzero(&xop->xo_mbstate, sizeof(xop->xo_mbstate));
1204 	len = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
1205 
1206 	if (len <= 0) {
1207 	    xo_failure(xop, "could not convert wide char: %lx",
1208 		       (unsigned long) wc);
1209 	    return 0;
1210 	}
1211 	xbp->xb_curp += len;
1212     }
1213 
1214     return xo_wcwidth(wc);
1215 }
1216 
1217 /*
1218  * Append a UTF-8 string to a buffer, converting it into locale encoding
1219  */
1220 static void
xo_buf_append_locale(xo_handle_t * xop,xo_buffer_t * xbp,const char * cp,ssize_t len)1221 xo_buf_append_locale (xo_handle_t *xop, xo_buffer_t *xbp,
1222 		      const char *cp, ssize_t len)
1223 {
1224     const char *sp = cp, *ep = cp + len;
1225     ssize_t save_off = xbp->xb_bufp - xbp->xb_curp;
1226     ssize_t slen;
1227     int cols = 0;
1228 
1229     for ( ; cp < ep; cp++) {
1230 	if (!xo_is_utf8(*cp)) {
1231 	    cols += 1;
1232 	    continue;
1233 	}
1234 
1235 	/*
1236 	 * We're looking at a non-ascii UTF-8 character.
1237 	 * First we copy the previous data.
1238 	 * Then we need find the length and validate it.
1239 	 * Then we turn it into a wide string.
1240 	 * Then we turn it into a localized string.
1241 	 * Then we repeat.  Isn't i18n fun?
1242 	 */
1243 	if (sp != cp)
1244 	    xo_buf_append(xbp, sp, cp - sp); /* Append previous data */
1245 
1246 	slen = xo_buf_utf8_len(xop, cp, ep - cp);
1247 	if (slen <= 0) {
1248 	    /* Bad data; back it all out */
1249 	    xbp->xb_curp = xbp->xb_bufp + save_off;
1250 	    return;
1251 	}
1252 
1253 	cols += xo_buf_append_locale_from_utf8(xop, xbp, cp, slen);
1254 
1255 	/* Next time through, we'll start at the next character */
1256 	cp += slen - 1;
1257 	sp = cp + 1;
1258     }
1259 
1260     /* Update column values */
1261     if (XOF_ISSET(xop, XOF_COLUMNS))
1262 	xop->xo_columns += cols;
1263     if (XOIF_ISSET(xop, XOIF_ANCHOR))
1264 	xop->xo_anchor_columns += cols;
1265 
1266     /* Before we fall into the basic logic below, we need reset len */
1267     len = ep - sp;
1268     if (len != 0) /* Append trailing data */
1269 	xo_buf_append(xbp, sp, len);
1270 }
1271 
1272 /*
1273  * Append the given string to the given buffer, without escaping or
1274  * character set conversion.  This is the straight copy to the data
1275  * buffer with no fanciness.
1276  */
1277 static void
xo_data_append(xo_handle_t * xop,const char * str,ssize_t len)1278 xo_data_append (xo_handle_t *xop, const char *str, ssize_t len)
1279 {
1280     xo_buf_append(&xop->xo_data, str, len);
1281 }
1282 
1283 /*
1284  * Append the given string to the given buffer
1285  */
1286 static void
xo_data_escape(xo_handle_t * xop,const char * str,ssize_t len)1287 xo_data_escape (xo_handle_t *xop, const char *str, ssize_t len)
1288 {
1289     xo_buf_escape(xop, &xop->xo_data, str, len, 0);
1290 }
1291 
1292 #ifdef LIBXO_NO_RETAIN
1293 /*
1294  * Empty implementations of the retain logic
1295  */
1296 
1297 void
xo_retain_clear_all(void)1298 xo_retain_clear_all (void)
1299 {
1300     return;
1301 }
1302 
1303 void
xo_retain_clear(const char * fmt UNUSED)1304 xo_retain_clear (const char *fmt UNUSED)
1305 {
1306     return;
1307 }
1308 static void
xo_retain_add(const char * fmt UNUSED,xo_field_info_t * fields UNUSED,unsigned num_fields UNUSED)1309 xo_retain_add (const char *fmt UNUSED, xo_field_info_t *fields UNUSED,
1310 		unsigned num_fields UNUSED)
1311 {
1312     return;
1313 }
1314 
1315 static int
xo_retain_find(const char * fmt UNUSED,xo_field_info_t ** valp UNUSED,unsigned * nump UNUSED)1316 xo_retain_find (const char *fmt UNUSED, xo_field_info_t **valp UNUSED,
1317 		 unsigned *nump UNUSED)
1318 {
1319     return -1;
1320 }
1321 
1322 #else /* !LIBXO_NO_RETAIN */
1323 /*
1324  * Retain: We retain parsed field definitions to enhance performance,
1325  * especially inside loops.  We depend on the caller treating the format
1326  * strings as immutable, so that we can retain pointers into them.  We
1327  * hold the pointers in a hash table, so allow quick access.  Retained
1328  * information is retained until xo_retain_clear is called.
1329  */
1330 
1331 /*
1332  * xo_retain_entry_t holds information about one retained set of
1333  * parsed fields.
1334  */
1335 typedef struct xo_retain_entry_s {
1336     struct xo_retain_entry_s *xre_next; /* Pointer to next (older) entry */
1337     unsigned long xre_hits;		 /* Number of times we've hit */
1338     const char *xre_format;		 /* Pointer to format string */
1339     unsigned xre_num_fields;		 /* Number of fields saved */
1340     xo_field_info_t *xre_fields;	 /* Pointer to fields */
1341 } xo_retain_entry_t;
1342 
1343 /*
1344  * xo_retain_t holds a complete set of parsed fields as a hash table.
1345  */
1346 #ifndef XO_RETAIN_SIZE
1347 #define XO_RETAIN_SIZE 6
1348 #endif /* XO_RETAIN_SIZE */
1349 #define RETAIN_HASH_SIZE (1<<XO_RETAIN_SIZE)
1350 
1351 typedef struct xo_retain_s {
1352     xo_retain_entry_t *xr_bucket[RETAIN_HASH_SIZE];
1353 } xo_retain_t;
1354 
1355 static THREAD_LOCAL(xo_retain_t) xo_retain;
1356 static THREAD_LOCAL(unsigned) xo_retain_count;
1357 
1358 /*
1359  * Simple hash function based on Thomas Wang's paper.  The original is
1360  * gone, but an archive is available on the Way Back Machine:
1361  *
1362  * http://web.archive.org/web/20071223173210/\
1363  *     http://www.concentric.net/~Ttwang/tech/inthash.htm
1364  *
1365  * For our purposes, we can assume the low four bits are uninteresting
1366  * since any string less that 16 bytes wouldn't be worthy of
1367  * retaining.  We toss the high bits also, since these bits are likely
1368  * to be common among constant format strings.  We then run Wang's
1369  * algorithm, and cap the result at RETAIN_HASH_SIZE.
1370  */
1371 static unsigned
xo_retain_hash(const char * fmt)1372 xo_retain_hash (const char *fmt)
1373 {
1374     volatile uintptr_t iptr = (uintptr_t) (const void *) fmt;
1375 
1376     /* Discard low four bits and high bits; they aren't interesting */
1377     uint32_t val = (uint32_t) ((iptr >> 4) & (((1 << 24) - 1)));
1378 
1379     val = (val ^ 61) ^ (val >> 16);
1380     val = val + (val << 3);
1381     val = val ^ (val >> 4);
1382     val = val * 0x3a8f05c5;	/* My large prime number */
1383     val = val ^ (val >> 15);
1384     val &= RETAIN_HASH_SIZE - 1;
1385 
1386     return val;
1387 }
1388 
1389 /*
1390  * Walk all buckets, clearing all retained entries
1391  */
1392 void
xo_retain_clear_all(void)1393 xo_retain_clear_all (void)
1394 {
1395     int i;
1396     xo_retain_entry_t *xrep, *next;
1397 
1398     for (i = 0; i < RETAIN_HASH_SIZE; i++) {
1399 	for (xrep = xo_retain.xr_bucket[i]; xrep; xrep = next) {
1400 	    next = xrep->xre_next;
1401 	    xo_free(xrep);
1402 	}
1403 	xo_retain.xr_bucket[i] = NULL;
1404     }
1405     xo_retain_count = 0;
1406 }
1407 
1408 /*
1409  * Walk all buckets, clearing all retained entries
1410  */
1411 void
xo_retain_clear(const char * fmt)1412 xo_retain_clear (const char *fmt)
1413 {
1414     xo_retain_entry_t **xrepp;
1415     unsigned hash = xo_retain_hash(fmt);
1416 
1417     for (xrepp = &xo_retain.xr_bucket[hash]; *xrepp;
1418 	 xrepp = &(*xrepp)->xre_next) {
1419 	if ((*xrepp)->xre_format == fmt) {
1420 	    *xrepp = (*xrepp)->xre_next;
1421 	    xo_retain_count -= 1;
1422 	    return;
1423 	}
1424     }
1425 }
1426 
1427 /*
1428  * Search the hash for an entry matching 'fmt'; return it's fields.
1429  */
1430 static int
xo_retain_find(const char * fmt,xo_field_info_t ** valp,unsigned * nump)1431 xo_retain_find (const char *fmt, xo_field_info_t **valp, unsigned *nump)
1432 {
1433     if (xo_retain_count == 0)
1434 	return -1;
1435 
1436     unsigned hash = xo_retain_hash(fmt);
1437     xo_retain_entry_t *xrep;
1438 
1439     for (xrep = xo_retain.xr_bucket[hash]; xrep != NULL;
1440 	 xrep = xrep->xre_next) {
1441 	if (xrep->xre_format == fmt) {
1442 	    *valp = xrep->xre_fields;
1443 	    *nump = xrep->xre_num_fields;
1444 	    xrep->xre_hits += 1;
1445 	    return 0;
1446 	}
1447     }
1448 
1449     return -1;
1450 }
1451 
1452 static void
xo_retain_add(const char * fmt,xo_field_info_t * fields,unsigned num_fields)1453 xo_retain_add (const char *fmt, xo_field_info_t *fields, unsigned num_fields)
1454 {
1455     unsigned hash = xo_retain_hash(fmt);
1456     xo_retain_entry_t *xrep;
1457     ssize_t sz = sizeof(*xrep) + (num_fields + 1) * sizeof(*fields);
1458     xo_field_info_t *xfip;
1459 
1460     xrep = xo_realloc(NULL, sz);
1461     if (xrep == NULL)
1462 	return;
1463 
1464     xfip = (xo_field_info_t *) &xrep[1];
1465     memcpy(xfip, fields, num_fields * sizeof(*fields));
1466 
1467     bzero(xrep, sizeof(*xrep));
1468 
1469     xrep->xre_format = fmt;
1470     xrep->xre_fields = xfip;
1471     xrep->xre_num_fields = num_fields;
1472 
1473     /* Record the field info in the retain bucket */
1474     xrep->xre_next = xo_retain.xr_bucket[hash];
1475     xo_retain.xr_bucket[hash] = xrep;
1476     xo_retain_count += 1;
1477 }
1478 
1479 #endif /* !LIBXO_NO_RETAIN */
1480 
1481 /*
1482  * Generate a warning.  Normally, this is a text message written to
1483  * standard error.  If the XOF_WARN_XML flag is set, then we generate
1484  * XMLified content on standard output.
1485  */
1486 static void
xo_warn_hcv(xo_handle_t * xop,int code,int check_warn,const char * fmt,va_list vap)1487 xo_warn_hcv (xo_handle_t *xop, int code, int check_warn,
1488 	     const char *fmt, va_list vap)
1489 {
1490     xop = xo_default(xop);
1491     if (check_warn && !XOF_ISSET(xop, XOF_WARN))
1492 	return;
1493 
1494     if (fmt == NULL)
1495 	return;
1496 
1497     ssize_t len = strlen(fmt);
1498     ssize_t plen = xo_program ? strlen(xo_program) : 0;
1499     char *newfmt = alloca(len + 1 + plen + 2); /* NUL, and ": " */
1500 
1501     if (plen) {
1502 	memcpy(newfmt, xo_program, plen);
1503 	newfmt[plen++] = ':';
1504 	newfmt[plen++] = ' ';
1505     }
1506 
1507     memcpy(newfmt + plen, fmt, len);
1508     newfmt[len + plen] = '\0';
1509 
1510     if (XOF_ISSET(xop, XOF_WARN_XML)) {
1511 	static char err_open[] = "<error>";
1512 	static char err_close[] = "</error>";
1513 	static char msg_open[] = "<message>";
1514 	static char msg_close[] = "</message>";
1515 
1516 	xo_buffer_t *xbp = &xop->xo_data;
1517 
1518 	xo_buf_append(xbp, err_open, sizeof(err_open) - 1);
1519 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1520 
1521 	va_list va_local;
1522 	va_copy(va_local, vap);
1523 
1524 	ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1525 	ssize_t rc = vsnprintf(xbp->xb_curp, left, newfmt, vap);
1526 
1527 	if (rc >= left) {
1528 	    if (!xo_buf_has_room(xbp, rc)) {
1529 		va_end(va_local);
1530 		return;
1531 	    }
1532 
1533 	    va_end(vap);	/* Reset vap to the start */
1534 	    va_copy(vap, va_local);
1535 
1536 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1537 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1538 	}
1539 
1540 	va_end(va_local);
1541 
1542 	rc = xo_escape_xml(xbp, rc, 1);
1543 	xbp->xb_curp += rc;
1544 
1545 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1546 	xo_buf_append(xbp, err_close, sizeof(err_close) - 1);
1547 
1548 	if (code >= 0) {
1549 	    const char *msg = strerror(code);
1550 
1551 	    if (msg) {
1552 		xo_buf_append(xbp, ": ", 2);
1553 		xo_buf_append(xbp, msg, strlen(msg));
1554 	    }
1555 	}
1556 
1557 	xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1558 	(void) xo_write(xop);
1559 
1560     } else {
1561 	vfprintf(stderr, newfmt, vap);
1562 	if (code >= 0) {
1563 	    const char *msg = strerror(code);
1564 
1565 	    if (msg)
1566 		fprintf(stderr, ": %s", msg);
1567 	}
1568 	fprintf(stderr, "\n");
1569     }
1570 }
1571 
1572 void
xo_warn_hc(xo_handle_t * xop,int code,const char * fmt,...)1573 xo_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1574 {
1575     va_list vap;
1576 
1577     va_start(vap, fmt);
1578     xo_warn_hcv(xop, code, 0, fmt, vap);
1579     va_end(vap);
1580 }
1581 
1582 void
xo_warn_c(int code,const char * fmt,...)1583 xo_warn_c (int code, const char *fmt, ...)
1584 {
1585     va_list vap;
1586 
1587     va_start(vap, fmt);
1588     xo_warn_hcv(NULL, code, 0, fmt, vap);
1589     va_end(vap);
1590 }
1591 
1592 void
xo_warn(const char * fmt,...)1593 xo_warn (const char *fmt, ...)
1594 {
1595     int code = errno;
1596     va_list vap;
1597 
1598     va_start(vap, fmt);
1599     xo_warn_hcv(NULL, code, 0, fmt, vap);
1600     va_end(vap);
1601 }
1602 
1603 void
xo_warnx(const char * fmt,...)1604 xo_warnx (const char *fmt, ...)
1605 {
1606     va_list vap;
1607 
1608     va_start(vap, fmt);
1609     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1610     va_end(vap);
1611 }
1612 
1613 void
xo_err(int eval,const char * fmt,...)1614 xo_err (int eval, const char *fmt, ...)
1615 {
1616     int code = errno;
1617     va_list vap;
1618 
1619     va_start(vap, fmt);
1620     xo_warn_hcv(NULL, code, 0, fmt, vap);
1621     va_end(vap);
1622     xo_finish();
1623     exit(eval);
1624 }
1625 
1626 void
xo_errx(int eval,const char * fmt,...)1627 xo_errx (int eval, const char *fmt, ...)
1628 {
1629     va_list vap;
1630 
1631     va_start(vap, fmt);
1632     xo_warn_hcv(NULL, -1, 0, fmt, vap);
1633     va_end(vap);
1634     xo_finish();
1635     exit(eval);
1636 }
1637 
1638 void
xo_errc(int eval,int code,const char * fmt,...)1639 xo_errc (int eval, int code, const char *fmt, ...)
1640 {
1641     va_list vap;
1642 
1643     va_start(vap, fmt);
1644     xo_warn_hcv(NULL, code, 0, fmt, vap);
1645     va_end(vap);
1646     xo_finish();
1647     exit(eval);
1648 }
1649 
1650 /*
1651  * Generate a warning.  Normally, this is a text message written to
1652  * standard error.  If the XOF_WARN_XML flag is set, then we generate
1653  * XMLified content on standard output.
1654  */
1655 void
xo_message_hcv(xo_handle_t * xop,int code,const char * fmt,va_list vap)1656 xo_message_hcv (xo_handle_t *xop, int code, const char *fmt, va_list vap)
1657 {
1658     static char msg_open[] = "<message>";
1659     static char msg_close[] = "</message>";
1660     xo_buffer_t *xbp;
1661     ssize_t rc;
1662     va_list va_local;
1663 
1664     xop = xo_default(xop);
1665 
1666     if (fmt == NULL || *fmt == '\0')
1667 	return;
1668 
1669     int need_nl = (fmt[strlen(fmt) - 1] != '\n');
1670 
1671     switch (xo_style(xop)) {
1672     case XO_STYLE_XML:
1673 	xbp = &xop->xo_data;
1674 	if (XOF_ISSET(xop, XOF_PRETTY))
1675 	    xo_buf_indent(xop, xop->xo_indent_by);
1676 	xo_buf_append(xbp, msg_open, sizeof(msg_open) - 1);
1677 
1678 	va_copy(va_local, vap);
1679 
1680 	ssize_t left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1681 
1682 	rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1683 	if (rc >= left) {
1684 	    if (!xo_buf_has_room(xbp, rc)) {
1685 		va_end(va_local);
1686 		return;
1687 	    }
1688 
1689 	    va_end(vap);	/* Reset vap to the start */
1690 	    va_copy(vap, va_local);
1691 
1692 	    left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
1693 	    rc = vsnprintf(xbp->xb_curp, left, fmt, vap);
1694 	}
1695 
1696 	va_end(va_local);
1697 
1698 	rc = xo_escape_xml(xbp, rc, 0);
1699 	xbp->xb_curp += rc;
1700 
1701 	if (need_nl && code > 0) {
1702 	    const char *msg = strerror(code);
1703 
1704 	    if (msg) {
1705 		xo_buf_append(xbp, ": ", 2);
1706 		xo_buf_append(xbp, msg, strlen(msg));
1707 	    }
1708 	}
1709 
1710 	if (need_nl)
1711 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1712 
1713 	xo_buf_append(xbp, msg_close, sizeof(msg_close) - 1);
1714 
1715 	if (XOF_ISSET(xop, XOF_PRETTY))
1716 	    xo_buf_append(xbp, "\n", 1); /* Append newline and NUL to string */
1717 
1718 	(void) xo_write(xop);
1719 	break;
1720 
1721     case XO_STYLE_HTML:
1722 	{
1723 	    char buf[BUFSIZ], *bp = buf, *cp;
1724 	    ssize_t bufsiz = sizeof(buf);
1725 	    ssize_t rc2;
1726 
1727 	    va_copy(va_local, vap);
1728 
1729 	    rc = vsnprintf(bp, bufsiz, fmt, va_local);
1730 	    if (rc > bufsiz) {
1731 		bufsiz = rc + BUFSIZ;
1732 		bp = alloca(bufsiz);
1733 		va_end(va_local);
1734 		va_copy(va_local, vap);
1735 		rc = vsnprintf(bp, bufsiz, fmt, va_local);
1736 	    }
1737 
1738 	    va_end(va_local);
1739 	    cp = bp + rc;
1740 
1741 	    if (need_nl) {
1742 		rc2 = snprintf(cp, bufsiz - rc, "%s%s\n",
1743 			       (code > 0) ? ": " : "",
1744 			       (code > 0) ? strerror(code) : "");
1745 		if (rc2 > 0)
1746 		    rc += rc2;
1747 	    }
1748 
1749 	    xo_buf_append_div(xop, "message", 0, NULL, 0, bp, rc,
1750 			      NULL, 0, NULL, 0);
1751 	}
1752 	break;
1753 
1754     case XO_STYLE_JSON:
1755     case XO_STYLE_SDPARAMS:
1756     case XO_STYLE_ENCODER:
1757 	/* No means of representing messages */
1758 	return;
1759 
1760     case XO_STYLE_TEXT:
1761 	rc = xo_printf_v(xop, fmt, vap);
1762 	/*
1763 	 * XXX need to handle UTF-8 widths
1764 	 */
1765 	if (rc > 0) {
1766 	    if (XOF_ISSET(xop, XOF_COLUMNS))
1767 		xop->xo_columns += rc;
1768 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
1769 		xop->xo_anchor_columns += rc;
1770 	}
1771 
1772 	if (need_nl && code > 0) {
1773 	    const char *msg = strerror(code);
1774 
1775 	    if (msg) {
1776 		xo_printf(xop, ": %s", msg);
1777 	    }
1778 	}
1779 	if (need_nl)
1780 	    xo_printf(xop, "\n");
1781 
1782 	break;
1783     }
1784 
1785     switch (xo_style(xop)) {
1786     case XO_STYLE_HTML:
1787 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN)) {
1788 	    static char div_close[] = "</div>";
1789 
1790 	    XOIF_CLEAR(xop, XOIF_DIV_OPEN);
1791 	    xo_data_append(xop, div_close, sizeof(div_close) - 1);
1792 
1793 	    if (XOF_ISSET(xop, XOF_PRETTY))
1794 		xo_data_append(xop, "\n", 1);
1795 	}
1796 	break;
1797     }
1798 
1799     (void) xo_flush_h(xop);
1800 }
1801 
1802 void
xo_message_hc(xo_handle_t * xop,int code,const char * fmt,...)1803 xo_message_hc (xo_handle_t *xop, int code, const char *fmt, ...)
1804 {
1805     va_list vap;
1806 
1807     va_start(vap, fmt);
1808     xo_message_hcv(xop, code, fmt, vap);
1809     va_end(vap);
1810 }
1811 
1812 void
xo_message_c(int code,const char * fmt,...)1813 xo_message_c (int code, const char *fmt, ...)
1814 {
1815     va_list vap;
1816 
1817     va_start(vap, fmt);
1818     xo_message_hcv(NULL, code, fmt, vap);
1819     va_end(vap);
1820 }
1821 
1822 void
xo_message_e(const char * fmt,...)1823 xo_message_e (const char *fmt, ...)
1824 {
1825     int code = errno;
1826     va_list vap;
1827 
1828     va_start(vap, fmt);
1829     xo_message_hcv(NULL, code, fmt, vap);
1830     va_end(vap);
1831 }
1832 
1833 void
xo_message(const char * fmt,...)1834 xo_message (const char *fmt, ...)
1835 {
1836     va_list vap;
1837 
1838     va_start(vap, fmt);
1839     xo_message_hcv(NULL, 0, fmt, vap);
1840     va_end(vap);
1841 }
1842 
1843 void
xo_failure(xo_handle_t * xop,const char * fmt,...)1844 xo_failure (xo_handle_t *xop, const char *fmt, ...)
1845 {
1846     if (!XOF_ISSET(xop, XOF_WARN))
1847 	return;
1848 
1849     va_list vap;
1850 
1851     va_start(vap, fmt);
1852     xo_warn_hcv(xop, -1, 1, fmt, vap);
1853     va_end(vap);
1854 }
1855 
1856 /**
1857  * Create a handle for use by later libxo functions.
1858  *
1859  * Note: normal use of libxo does not require a distinct handle, since
1860  * the default handle (used when NULL is passed) generates text on stdout.
1861  *
1862  * @param style Style of output desired (XO_STYLE_* value)
1863  * @param flags Set of XOF_* flags in use with this handle
1864  * @return Newly allocated handle
1865  * @see xo_destroy
1866  */
1867 xo_handle_t *
xo_create(xo_style_t style,xo_xof_flags_t flags)1868 xo_create (xo_style_t style, xo_xof_flags_t flags)
1869 {
1870     xo_handle_t *xop = xo_realloc(NULL, sizeof(*xop));
1871 
1872     if (xop) {
1873 	bzero(xop, sizeof(*xop));
1874 
1875 	xop->xo_style = style;
1876 	XOF_SET(xop, flags);
1877 	xo_init_handle(xop);
1878 	xop->xo_style = style;	/* Reset style (see LIBXO_OPTIONS) */
1879     }
1880 
1881     return xop;
1882 }
1883 
1884 /**
1885  * Create a handle that will write to the given file.  Use
1886  * the XOF_CLOSE_FP flag to have the file closed on xo_destroy().
1887  *
1888  * @param fp FILE pointer to use
1889  * @param style Style of output desired (XO_STYLE_* value)
1890  * @param flags Set of XOF_* flags to use with this handle
1891  * @return Newly allocated handle
1892  * @see xo_destroy
1893  */
1894 xo_handle_t *
xo_create_to_file(FILE * fp,xo_style_t style,xo_xof_flags_t flags)1895 xo_create_to_file (FILE *fp, xo_style_t style, xo_xof_flags_t flags)
1896 {
1897     xo_handle_t *xop = xo_create(style, flags);
1898 
1899     if (xop) {
1900 	xop->xo_opaque = fp;
1901 	xop->xo_write = xo_write_to_file;
1902 	xop->xo_close = xo_close_file;
1903 	xop->xo_flush = xo_flush_file;
1904     }
1905 
1906     return xop;
1907 }
1908 
1909 /**
1910  * Set the default handler to output to a file.
1911  *
1912  * @param xop libxo handle
1913  * @param fp FILE pointer to use
1914  * @return 0 on success, non-zero on failure
1915  */
1916 int
xo_set_file_h(xo_handle_t * xop,FILE * fp)1917 xo_set_file_h (xo_handle_t *xop, FILE *fp)
1918 {
1919     xop = xo_default(xop);
1920 
1921     if (fp == NULL) {
1922 	xo_failure(xop, "xo_set_file: NULL fp");
1923 	return -1;
1924     }
1925 
1926     xop->xo_opaque = fp;
1927     xop->xo_write = xo_write_to_file;
1928     xop->xo_close = xo_close_file;
1929     xop->xo_flush = xo_flush_file;
1930 
1931     return 0;
1932 }
1933 
1934 /**
1935  * Set the default handler to output to a file.
1936  *
1937  * @param fp FILE pointer to use
1938  * @return 0 on success, non-zero on failure
1939  */
1940 int
xo_set_file(FILE * fp)1941 xo_set_file (FILE *fp)
1942 {
1943     return xo_set_file_h(NULL, fp);
1944 }
1945 
1946 /**
1947  * Release any resources held by the handle.
1948  *
1949  * @param xop XO handle to alter (or NULL for default handle)
1950  */
1951 void
xo_destroy(xo_handle_t * xop_arg)1952 xo_destroy (xo_handle_t *xop_arg)
1953 {
1954     xo_handle_t *xop = xo_default(xop_arg);
1955 
1956     xo_flush_h(xop);
1957 
1958     if (xop->xo_close && XOF_ISSET(xop, XOF_CLOSE_FP))
1959 	xop->xo_close(xop->xo_opaque);
1960 
1961     xo_free(xop->xo_stack);
1962     xo_buf_cleanup(&xop->xo_data);
1963     xo_buf_cleanup(&xop->xo_fmt);
1964     xo_buf_cleanup(&xop->xo_predicate);
1965     xo_buf_cleanup(&xop->xo_attrs);
1966     xo_buf_cleanup(&xop->xo_color_buf);
1967 
1968     if (xop->xo_version)
1969 	xo_free(xop->xo_version);
1970 
1971     if (xop_arg == NULL) {
1972 	bzero(&xo_default_handle, sizeof(xo_default_handle));
1973 	xo_default_inited = 0;
1974     } else
1975 	xo_free(xop);
1976 }
1977 
1978 /**
1979  * Record a new output style to use for the given handle (or default if
1980  * handle is NULL).  This output style will be used for any future output.
1981  *
1982  * @param xop XO handle to alter (or NULL for default handle)
1983  * @param style new output style (XO_STYLE_*)
1984  */
1985 void
xo_set_style(xo_handle_t * xop,xo_style_t style)1986 xo_set_style (xo_handle_t *xop, xo_style_t style)
1987 {
1988     xop = xo_default(xop);
1989     xop->xo_style = style;
1990 }
1991 
1992 /**
1993  * Return the current style of a handle
1994  *
1995  * @param xop XO handle to access
1996  * @return The handle's current style
1997  */
1998 xo_style_t
xo_get_style(xo_handle_t * xop)1999 xo_get_style (xo_handle_t *xop)
2000 {
2001     xop = xo_default(xop);
2002     return xo_style(xop);
2003 }
2004 
2005 /**
2006  * Return the XO_STYLE_* value matching a given name
2007  *
2008  * @param name String name of a style
2009  * @return XO_STYLE_* value
2010  */
2011 static int
xo_name_to_style(const char * name)2012 xo_name_to_style (const char *name)
2013 {
2014     if (xo_streq(name, "xml"))
2015 	return XO_STYLE_XML;
2016     else if (xo_streq(name, "json"))
2017 	return XO_STYLE_JSON;
2018     else if (xo_streq(name, "encoder"))
2019 	return XO_STYLE_ENCODER;
2020     else if (xo_streq(name, "text"))
2021 	return XO_STYLE_TEXT;
2022     else if (xo_streq(name, "html"))
2023 	return XO_STYLE_HTML;
2024     else if (xo_streq(name, "sdparams"))
2025 	return XO_STYLE_SDPARAMS;
2026 
2027     return -1;
2028 }
2029 
2030 /*
2031  * Indicate if the style is an "encoding" one as opposed to a "display" one.
2032  */
2033 static int
xo_style_is_encoding(xo_handle_t * xop)2034 xo_style_is_encoding (xo_handle_t *xop)
2035 {
2036     if (xo_style(xop) == XO_STYLE_JSON
2037 	|| xo_style(xop) == XO_STYLE_XML
2038 	|| xo_style(xop) == XO_STYLE_SDPARAMS
2039 	|| xo_style(xop) == XO_STYLE_ENCODER)
2040 	return 1;
2041     return 0;
2042 }
2043 
2044 /* Simple name-value mapping */
2045 typedef struct xo_mapping_s {
2046     xo_xff_flags_t xm_value;	/* Flag value */
2047     const char *xm_name;	/* String name */
2048 } xo_mapping_t;
2049 
2050 static xo_xff_flags_t
xo_name_lookup(xo_mapping_t * map,const char * value,ssize_t len)2051 xo_name_lookup (xo_mapping_t *map, const char *value, ssize_t len)
2052 {
2053     if (len == 0)
2054 	return 0;
2055 
2056     if (len < 0)
2057 	len = strlen(value);
2058 
2059     while (isspace((int) *value)) {
2060 	value += 1;
2061 	len -= 1;
2062     }
2063 
2064     while (isspace((int) value[len]))
2065 	len -= 1;
2066 
2067     if (*value == '\0')
2068 	return 0;
2069 
2070     for ( ; map->xm_name; map++)
2071 	if (strncmp(map->xm_name, value, len) == 0)
2072 	    return map->xm_value;
2073 
2074     return 0;
2075 }
2076 
2077 #ifdef NOT_NEEDED_YET
2078 static const char *
xo_value_lookup(xo_mapping_t * map,xo_xff_flags_t value)2079 xo_value_lookup (xo_mapping_t *map, xo_xff_flags_t value)
2080 {
2081     if (value == 0)
2082 	return NULL;
2083 
2084     for ( ; map->xm_name; map++)
2085 	if (map->xm_value == value)
2086 	    return map->xm_name;
2087 
2088     return NULL;
2089 }
2090 #endif /* NOT_NEEDED_YET */
2091 
2092 static xo_mapping_t xo_xof_names[] = {
2093     { XOF_COLOR_ALLOWED, "color" },
2094     { XOF_COLOR, "color-force" },
2095     { XOF_COLUMNS, "columns" },
2096     { XOF_DTRT, "dtrt" },
2097     { XOF_FLUSH, "flush" },
2098     { XOF_FLUSH_LINE, "flush-line" },
2099     { XOF_IGNORE_CLOSE, "ignore-close" },
2100     { XOF_INFO, "info" },
2101     { XOF_KEYS, "keys" },
2102     { XOF_LOG_GETTEXT, "log-gettext" },
2103     { XOF_LOG_SYSLOG, "log-syslog" },
2104     { XOF_NO_HUMANIZE, "no-humanize" },
2105     { XOF_NO_LOCALE, "no-locale" },
2106     { XOF_RETAIN_NONE, "no-retain" },
2107     { XOF_NO_TOP, "no-top" },
2108     { XOF_NOT_FIRST, "not-first" },
2109     { XOF_PRETTY, "pretty" },
2110     { XOF_RETAIN_ALL, "retain" },
2111     { XOF_UNDERSCORES, "underscores" },
2112     { XOF_UNITS, "units" },
2113     { XOF_WARN, "warn" },
2114     { XOF_WARN_XML, "warn-xml" },
2115     { XOF_XPATH, "xpath" },
2116     { 0, NULL }
2117 };
2118 
2119 /* Options available via the environment variable ($LIBXO_OPTIONS) */
2120 static xo_mapping_t xo_xof_simple_names[] = {
2121     { XOF_COLOR_ALLOWED, "color" },
2122     { XOF_FLUSH, "flush" },
2123     { XOF_FLUSH_LINE, "flush-line" },
2124     { XOF_NO_HUMANIZE, "no-humanize" },
2125     { XOF_NO_LOCALE, "no-locale" },
2126     { XOF_RETAIN_NONE, "no-retain" },
2127     { XOF_PRETTY, "pretty" },
2128     { XOF_RETAIN_ALL, "retain" },
2129     { XOF_UNDERSCORES, "underscores" },
2130     { XOF_WARN, "warn" },
2131     { 0, NULL }
2132 };
2133 
2134 /*
2135  * Convert string name to XOF_* flag value.
2136  * Not all are useful.  Or safe.  Or sane.
2137  */
2138 static unsigned
xo_name_to_flag(const char * name)2139 xo_name_to_flag (const char *name)
2140 {
2141     return (unsigned) xo_name_lookup(xo_xof_names, name, -1);
2142 }
2143 
2144 /**
2145  * Set the style of an libxo handle based on a string name
2146  *
2147  * @param xop XO handle
2148  * @param name String value of name
2149  * @return 0 on success, non-zero on failure
2150  */
2151 int
xo_set_style_name(xo_handle_t * xop,const char * name)2152 xo_set_style_name (xo_handle_t *xop, const char *name)
2153 {
2154     if (name == NULL)
2155 	return -1;
2156 
2157     int style = xo_name_to_style(name);
2158 
2159     if (style < 0)
2160 	return -1;
2161 
2162     xo_set_style(xop, style);
2163     return 0;
2164 }
2165 
2166 /*
2167  * Fill in the color map, based on the input string; currently unimplemented
2168  * Look for something like "colors=red/blue+green/yellow" as fg/bg pairs.
2169  */
2170 static void
xo_set_color_map(xo_handle_t * xop,char * value)2171 xo_set_color_map (xo_handle_t *xop, char *value)
2172 {
2173     if (xo_text_only())
2174 	return;
2175 
2176     char *cp, *ep, *vp, *np;
2177     ssize_t len = value ? strlen(value) + 1 : 0;
2178     int num = 1, fg, bg;
2179 
2180     for (cp = value, ep = cp + len - 1; cp && *cp && cp < ep; cp = np) {
2181 	np = strchr(cp, '+');
2182 	if (np)
2183 	    *np++ = '\0';
2184 
2185 	vp = strchr(cp, '/');
2186 	if (vp)
2187 	    *vp++ = '\0';
2188 
2189 	fg = *cp ? xo_color_find(cp) : -1;
2190 	bg = (vp && *vp) ? xo_color_find(vp) : -1;
2191 
2192 #ifndef LIBXO_TEXT_ONLY
2193 	xop->xo_color_map_fg[num] = (fg < 0) ? num : fg;
2194 	xop->xo_color_map_bg[num] = (bg < 0) ? num : bg;
2195 #endif /* LIBXO_TEXT_ONLY */
2196 
2197 	if (++num > XO_NUM_COLORS)
2198 	    break;
2199     }
2200 
2201     /* If no color initialization happened, then we don't need the map */
2202     if (num > 1)
2203 	XOF_SET(xop, XOF_COLOR_MAP);
2204     else
2205 	XOF_CLEAR(xop, XOF_COLOR_MAP);
2206 
2207 #ifndef LIBXO_TEXT_ONLY
2208     /* Fill in the rest of the colors with the defaults */
2209     for ( ; num < XO_NUM_COLORS; num++)
2210 	xop->xo_color_map_fg[num] = xop->xo_color_map_bg[num] = num;
2211 #endif /* LIBXO_TEXT_ONLY */
2212 }
2213 
2214 static int
xo_set_options_simple(xo_handle_t * xop,const char * input)2215 xo_set_options_simple (xo_handle_t *xop, const char *input)
2216 {
2217     xo_xof_flags_t new_flag;
2218     char *cp, *ep, *vp, *np, *bp;
2219     ssize_t len = strlen(input) + 1;
2220 
2221     bp = alloca(len);
2222     memcpy(bp, input, len);
2223 
2224     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
2225 	np = strchr(cp, ',');
2226 	if (np)
2227 	    *np++ = '\0';
2228 
2229 	vp = strchr(cp, '=');
2230 	if (vp)
2231 	    *vp++ = '\0';
2232 
2233 	if (xo_streq("colors", cp)) {
2234 	    xo_set_color_map(xop, vp);
2235 	    continue;
2236 	}
2237 
2238 	new_flag = xo_name_lookup(xo_xof_simple_names, cp, -1);
2239 	if (new_flag != 0) {
2240 	    XOF_SET(xop, new_flag);
2241 	} else if (xo_streq(cp, "no-color")) {
2242 	    XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2243 	} else {
2244 	    xo_failure(xop, "unknown simple option: %s", cp);
2245 	    return -1;
2246 	}
2247     }
2248 
2249     return 0;
2250 }
2251 
2252 /**
2253  * Set the options for a handle using a string of options
2254  * passed in.  The input is a comma-separated set of names
2255  * and optional values: "xml,pretty,indent=4"
2256  *
2257  * @param xop XO handle
2258  * @param input Comma-separated set of option values
2259  * @return 0 on success, non-zero on failure
2260  */
2261 int
xo_set_options(xo_handle_t * xop,const char * input)2262 xo_set_options (xo_handle_t *xop, const char *input)
2263 {
2264     char *cp, *ep, *vp, *np, *bp;
2265     int style = -1, new_style, rc = 0;
2266     ssize_t len;
2267     xo_xof_flags_t new_flag;
2268 
2269     if (input == NULL)
2270 	return 0;
2271 
2272     xop = xo_default(xop);
2273 
2274 #ifdef LIBXO_COLOR_ON_BY_DEFAULT
2275     /* If the installer used --enable-color-on-by-default, then we allow it */
2276     XOF_SET(xop, XOF_COLOR_ALLOWED);
2277 #endif /* LIBXO_COLOR_ON_BY_DEFAULT */
2278 
2279     /*
2280      * We support a simpler, old-school style of giving option
2281      * also, using a single character for each option.  It's
2282      * ideal for lazy people, such as myself.
2283      */
2284     if (*input == ':') {
2285 	ssize_t sz;
2286 
2287 	for (input++ ; *input; input++) {
2288 	    switch (*input) {
2289 	    case 'c':
2290 		XOF_SET(xop, XOF_COLOR_ALLOWED);
2291 		break;
2292 
2293 	    case 'f':
2294 		XOF_SET(xop, XOF_FLUSH);
2295 		break;
2296 
2297 	    case 'F':
2298 		XOF_SET(xop, XOF_FLUSH_LINE);
2299 		break;
2300 
2301 	    case 'g':
2302 		XOF_SET(xop, XOF_LOG_GETTEXT);
2303 		break;
2304 
2305 	    case 'H':
2306 		xop->xo_style = XO_STYLE_HTML;
2307 		break;
2308 
2309 	    case 'I':
2310 		XOF_SET(xop, XOF_INFO);
2311 		break;
2312 
2313 	    case 'i':
2314 		sz = strspn(input + 1, "0123456789");
2315 		if (sz > 0) {
2316 		    xop->xo_indent_by = atoi(input + 1);
2317 		    input += sz - 1;	/* Skip value */
2318 		}
2319 		break;
2320 
2321 	    case 'J':
2322 		xop->xo_style = XO_STYLE_JSON;
2323 		break;
2324 
2325 	    case 'k':
2326 		XOF_SET(xop, XOF_KEYS);
2327 		break;
2328 
2329 	    case 'n':
2330 		XOF_SET(xop, XOF_NO_HUMANIZE);
2331 		break;
2332 
2333 	    case 'P':
2334 		XOF_SET(xop, XOF_PRETTY);
2335 		break;
2336 
2337 	    case 'T':
2338 		xop->xo_style = XO_STYLE_TEXT;
2339 		break;
2340 
2341 	    case 'U':
2342 		XOF_SET(xop, XOF_UNITS);
2343 		break;
2344 
2345 	    case 'u':
2346 		XOF_SET(xop, XOF_UNDERSCORES);
2347 		break;
2348 
2349 	    case 'W':
2350 		XOF_SET(xop, XOF_WARN);
2351 		break;
2352 
2353 	    case 'X':
2354 		xop->xo_style = XO_STYLE_XML;
2355 		break;
2356 
2357 	    case 'x':
2358 		XOF_SET(xop, XOF_XPATH);
2359 		break;
2360 	    }
2361 	}
2362 	return 0;
2363     }
2364 
2365     len = strlen(input) + 1;
2366     bp = alloca(len);
2367     memcpy(bp, input, len);
2368 
2369     for (cp = bp, ep = cp + len - 1; cp && cp < ep; cp = np) {
2370 	np = strchr(cp, ',');
2371 	if (np)
2372 	    *np++ = '\0';
2373 
2374 	/*
2375 	 * "@foo" is a shorthand for "encoder=foo".  This is driven
2376 	 * chiefly by a desire to make pluggable encoders not appear
2377 	 * so distinct from built-in encoders.
2378 	 */
2379 	if (*cp == '@') {
2380 	    vp = cp + 1;
2381 
2382 	    if (*vp == '\0')
2383 		xo_failure(xop, "missing value for encoder option");
2384 	    else {
2385 		rc = xo_encoder_init(xop, vp);
2386 		if (rc)
2387 		    xo_warnx("error initializing encoder: %s", vp);
2388 	    }
2389 
2390 	    continue;
2391 	}
2392 
2393 	vp = strchr(cp, '=');
2394 	if (vp)
2395 	    *vp++ = '\0';
2396 
2397 	if (xo_streq("colors", cp)) {
2398 	    xo_set_color_map(xop, vp);
2399 	    continue;
2400 	}
2401 
2402 	/*
2403 	 * For options, we don't allow "encoder" since we want to
2404 	 * handle it explicitly below as "encoder=xxx".
2405 	 */
2406 	new_style = xo_name_to_style(cp);
2407 	if (new_style >= 0 && new_style != XO_STYLE_ENCODER) {
2408 	    if (style >= 0)
2409 		xo_warnx("ignoring multiple styles: '%s'", cp);
2410 	    else
2411 		style = new_style;
2412 	} else {
2413 	    new_flag = xo_name_to_flag(cp);
2414 	    if (new_flag != 0)
2415 		XOF_SET(xop, new_flag);
2416 	    else if (xo_streq(cp, "no-color"))
2417 		XOF_CLEAR(xop, XOF_COLOR_ALLOWED);
2418 	    else if (xo_streq(cp, "indent")) {
2419 		if (vp)
2420 		    xop->xo_indent_by = atoi(vp);
2421 		else
2422 		    xo_failure(xop, "missing value for indent option");
2423 	    } else if (xo_streq(cp, "encoder")) {
2424 		if (vp == NULL)
2425 		    xo_failure(xop, "missing value for encoder option");
2426 		else {
2427 		    rc = xo_encoder_init(xop, vp);
2428 		    if (rc)
2429 			xo_warnx("error initializing encoder: %s", vp);
2430 		}
2431 
2432 	    } else {
2433 		xo_warnx("unknown libxo option value: '%s'", cp);
2434 		rc = -1;
2435 	    }
2436 	}
2437     }
2438 
2439     if (style > 0)
2440 	xop->xo_style= style;
2441 
2442     return rc;
2443 }
2444 
2445 /**
2446  * Set one or more flags for a given handle (or default if handle is NULL).
2447  * These flags will affect future output.
2448  *
2449  * @param xop XO handle to alter (or NULL for default handle)
2450  * @param flags Flags to be set (XOF_*)
2451  */
2452 void
xo_set_flags(xo_handle_t * xop,xo_xof_flags_t flags)2453 xo_set_flags (xo_handle_t *xop, xo_xof_flags_t flags)
2454 {
2455     xop = xo_default(xop);
2456 
2457     XOF_SET(xop, flags);
2458 }
2459 
2460 /**
2461  * Accessor to return the current set of flags for a handle
2462  * @param xop XO handle
2463  * @return Current set of flags
2464  */
2465 xo_xof_flags_t
xo_get_flags(xo_handle_t * xop)2466 xo_get_flags (xo_handle_t *xop)
2467 {
2468     xop = xo_default(xop);
2469 
2470     return xop->xo_flags;
2471 }
2472 
2473 /**
2474  * strndup with a twist: len < 0 means len = strlen(str)
2475  */
2476 static char *
xo_strndup(const char * str,ssize_t len)2477 xo_strndup (const char *str, ssize_t len)
2478 {
2479     if (len < 0)
2480 	len = strlen(str);
2481 
2482     char *cp = xo_realloc(NULL, len + 1);
2483     if (cp) {
2484 	memcpy(cp, str, len);
2485 	cp[len] = '\0';
2486     }
2487 
2488     return cp;
2489 }
2490 
2491 /**
2492  * Record a leading prefix for the XPath we generate.  This allows the
2493  * generated data to be placed within an XML hierarchy but still have
2494  * accurate XPath expressions.
2495  *
2496  * @param xop XO handle to alter (or NULL for default handle)
2497  * @param path The XPath expression
2498  */
2499 void
xo_set_leading_xpath(xo_handle_t * xop,const char * path)2500 xo_set_leading_xpath (xo_handle_t *xop, const char *path)
2501 {
2502     xop = xo_default(xop);
2503 
2504     if (xop->xo_leading_xpath) {
2505 	xo_free(xop->xo_leading_xpath);
2506 	xop->xo_leading_xpath = NULL;
2507     }
2508 
2509     if (path == NULL)
2510 	return;
2511 
2512     xop->xo_leading_xpath = xo_strndup(path, -1);
2513 }
2514 
2515 /**
2516  * Record the info data for a set of tags
2517  *
2518  * @param xop XO handle to alter (or NULL for default handle)
2519  * @param info Info data (xo_info_t) to be recorded (or NULL) (MUST BE SORTED)
2520  * @pararm count Number of entries in info (or -1 to count them ourselves)
2521  */
2522 void
xo_set_info(xo_handle_t * xop,xo_info_t * infop,int count)2523 xo_set_info (xo_handle_t *xop, xo_info_t *infop, int count)
2524 {
2525     xop = xo_default(xop);
2526 
2527     if (count < 0 && infop) {
2528 	xo_info_t *xip;
2529 
2530 	for (xip = infop, count = 0; xip->xi_name; xip++, count++)
2531 	    continue;
2532     }
2533 
2534     xop->xo_info = infop;
2535     xop->xo_info_count = count;
2536 }
2537 
2538 /**
2539  * Set the formatter callback for a handle.  The callback should
2540  * return a newly formatting contents of a formatting instruction,
2541  * meaning the bits inside the braces.
2542  */
2543 void
xo_set_formatter(xo_handle_t * xop,xo_formatter_t func,xo_checkpointer_t cfunc)2544 xo_set_formatter (xo_handle_t *xop, xo_formatter_t func,
2545 		  xo_checkpointer_t cfunc)
2546 {
2547     xop = xo_default(xop);
2548 
2549     xop->xo_formatter = func;
2550     xop->xo_checkpointer = cfunc;
2551 }
2552 
2553 /**
2554  * Clear one or more flags for a given handle (or default if handle is NULL).
2555  * These flags will affect future output.
2556  *
2557  * @param xop XO handle to alter (or NULL for default handle)
2558  * @param flags Flags to be cleared (XOF_*)
2559  */
2560 void
xo_clear_flags(xo_handle_t * xop,xo_xof_flags_t flags)2561 xo_clear_flags (xo_handle_t *xop, xo_xof_flags_t flags)
2562 {
2563     xop = xo_default(xop);
2564 
2565     XOF_CLEAR(xop, flags);
2566 }
2567 
2568 static const char *
xo_state_name(xo_state_t state)2569 xo_state_name (xo_state_t state)
2570 {
2571     static const char *names[] = {
2572 	"init",
2573 	"open_container",
2574 	"close_container",
2575 	"open_list",
2576 	"close_list",
2577 	"open_instance",
2578 	"close_instance",
2579 	"open_leaf_list",
2580 	"close_leaf_list",
2581 	"discarding",
2582 	"marker",
2583 	"emit",
2584 	"emit_leaf_list",
2585 	"finish",
2586 	NULL
2587     };
2588 
2589     if (state < (sizeof(names) / sizeof(names[0])))
2590 	return names[state];
2591 
2592     return "unknown";
2593 }
2594 
2595 static void
xo_line_ensure_open(xo_handle_t * xop,xo_xff_flags_t flags UNUSED)2596 xo_line_ensure_open (xo_handle_t *xop, xo_xff_flags_t flags UNUSED)
2597 {
2598     static char div_open[] = "<div class=\"line\">";
2599     static char div_open_blank[] = "<div class=\"blank-line\">";
2600 
2601     if (XOF_ISSET(xop, XOF_CONTINUATION)) {
2602 	XOF_CLEAR(xop, XOF_CONTINUATION);
2603 	XOIF_SET(xop, XOIF_DIV_OPEN);
2604 	return;
2605     }
2606 
2607     if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
2608 	return;
2609 
2610     if (xo_style(xop) != XO_STYLE_HTML)
2611 	return;
2612 
2613     XOIF_SET(xop, XOIF_DIV_OPEN);
2614     if (flags & XFF_BLANK_LINE)
2615 	xo_data_append(xop, div_open_blank, sizeof(div_open_blank) - 1);
2616     else
2617 	xo_data_append(xop, div_open, sizeof(div_open) - 1);
2618 
2619     if (XOF_ISSET(xop, XOF_PRETTY))
2620 	xo_data_append(xop, "\n", 1);
2621 }
2622 
2623 static void
xo_line_close(xo_handle_t * xop)2624 xo_line_close (xo_handle_t *xop)
2625 {
2626     static char div_close[] = "</div>";
2627 
2628     switch (xo_style(xop)) {
2629     case XO_STYLE_HTML:
2630 	if (!XOIF_ISSET(xop, XOIF_DIV_OPEN))
2631 	    xo_line_ensure_open(xop, 0);
2632 
2633 	XOIF_CLEAR(xop, XOIF_DIV_OPEN);
2634 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
2635 
2636 	if (XOF_ISSET(xop, XOF_PRETTY))
2637 	    xo_data_append(xop, "\n", 1);
2638 	break;
2639 
2640     case XO_STYLE_TEXT:
2641 	xo_data_append(xop, "\n", 1);
2642 	break;
2643     }
2644 }
2645 
2646 static int
xo_info_compare(const void * key,const void * data)2647 xo_info_compare (const void *key, const void *data)
2648 {
2649     const char *name = key;
2650     const xo_info_t *xip = data;
2651 
2652     return strcmp(name, xip->xi_name);
2653 }
2654 
2655 
2656 static xo_info_t *
xo_info_find(xo_handle_t * xop,const char * name,ssize_t nlen)2657 xo_info_find (xo_handle_t *xop, const char *name, ssize_t nlen)
2658 {
2659     xo_info_t *xip;
2660     char *cp = alloca(nlen + 1); /* Need local copy for NUL termination */
2661 
2662     memcpy(cp, name, nlen);
2663     cp[nlen] = '\0';
2664 
2665     xip = bsearch(cp, xop->xo_info, xop->xo_info_count,
2666 		  sizeof(xop->xo_info[0]), xo_info_compare);
2667     return xip;
2668 }
2669 
2670 #define CONVERT(_have, _need) (((_have) << 8) | (_need))
2671 
2672 /*
2673  * Check to see that the conversion is safe and sane.
2674  */
2675 static int
xo_check_conversion(xo_handle_t * xop,int have_enc,int need_enc)2676 xo_check_conversion (xo_handle_t *xop, int have_enc, int need_enc)
2677 {
2678     switch (CONVERT(have_enc, need_enc)) {
2679     case CONVERT(XF_ENC_UTF8, XF_ENC_UTF8):
2680     case CONVERT(XF_ENC_UTF8, XF_ENC_LOCALE):
2681     case CONVERT(XF_ENC_WIDE, XF_ENC_UTF8):
2682     case CONVERT(XF_ENC_WIDE, XF_ENC_LOCALE):
2683     case CONVERT(XF_ENC_LOCALE, XF_ENC_LOCALE):
2684     case CONVERT(XF_ENC_LOCALE, XF_ENC_UTF8):
2685 	return 0;
2686 
2687     default:
2688 	xo_failure(xop, "invalid conversion (%c:%c)", have_enc, need_enc);
2689 	return 1;
2690     }
2691 }
2692 
2693 static int
xo_format_string_direct(xo_handle_t * xop,xo_buffer_t * xbp,xo_xff_flags_t flags,const wchar_t * wcp,const char * cp,ssize_t len,int max,int need_enc,int have_enc)2694 xo_format_string_direct (xo_handle_t *xop, xo_buffer_t *xbp,
2695 			 xo_xff_flags_t flags,
2696 			 const wchar_t *wcp, const char *cp,
2697 			 ssize_t len, int max,
2698 			 int need_enc, int have_enc)
2699 {
2700     int cols = 0;
2701     wchar_t wc = 0;
2702     ssize_t ilen, olen;
2703     ssize_t width;
2704     int attr = XOF_BIT_ISSET(flags, XFF_ATTR);
2705     const char *sp;
2706 
2707     if (len > 0 && !xo_buf_has_room(xbp, len))
2708 	return 0;
2709 
2710     for (;;) {
2711 	if (len == 0)
2712 	    break;
2713 
2714 	if (cp) {
2715 	    if (*cp == '\0')
2716 		break;
2717 	    if ((flags & XFF_UNESCAPE) && (*cp == '\\' || *cp == '%')) {
2718 		cp += 1;
2719 		len -= 1;
2720 		if (len == 0 || *cp == '\0')
2721 		    break;
2722 	    }
2723 	}
2724 
2725 	if (wcp && *wcp == L'\0')
2726 	    break;
2727 
2728 	ilen = 0;
2729 
2730 	switch (have_enc) {
2731 	case XF_ENC_WIDE:		/* Wide character */
2732 	    wc = *wcp++;
2733 	    ilen = 1;
2734 	    break;
2735 
2736 	case XF_ENC_UTF8:		/* UTF-8 */
2737 	    ilen = xo_utf8_to_wc_len(cp);
2738 	    if (ilen < 0) {
2739 		xo_failure(xop, "invalid UTF-8 character: %02hhx", *cp);
2740 		return -1;	/* Can't continue; we can't find the end */
2741 	    }
2742 
2743 	    if (len > 0 && len < ilen) {
2744 		len = 0;	/* Break out of the loop */
2745 		continue;
2746 	    }
2747 
2748 	    wc = xo_utf8_char(cp, ilen);
2749 	    if (wc == (wchar_t) -1) {
2750 		xo_failure(xop, "invalid UTF-8 character: %02hhx/%d",
2751 			   *cp, ilen);
2752 		return -1;	/* Can't continue; we can't find the end */
2753 	    }
2754 	    cp += ilen;
2755 	    break;
2756 
2757 	case XF_ENC_LOCALE:		/* Native locale */
2758 	    ilen = (len > 0) ? len : MB_LEN_MAX;
2759 	    ilen = mbrtowc(&wc, cp, ilen, &xop->xo_mbstate);
2760 	    if (ilen < 0) {		/* Invalid data; skip */
2761 		xo_failure(xop, "invalid mbs char: %02hhx", *cp);
2762 		wc = L'?';
2763 		ilen = 1;
2764 	    }
2765 
2766 	    if (ilen == 0) {		/* Hit a wide NUL character */
2767 		len = 0;
2768 		continue;
2769 	    }
2770 
2771 	    cp += ilen;
2772 	    break;
2773 	}
2774 
2775 	/* Reduce len, but not below zero */
2776 	if (len > 0) {
2777 	    len -= ilen;
2778 	    if (len < 0)
2779 		len = 0;
2780 	}
2781 
2782 	/*
2783 	 * Find the width-in-columns of this character, which must be done
2784 	 * in wide characters, since we lack a mbswidth() function.  If
2785 	 * it doesn't fit
2786 	 */
2787 	width = xo_wcwidth(wc);
2788 	if (width < 0)
2789 	    width = iswcntrl(wc) ? 0 : 1;
2790 
2791 	if (xo_style(xop) == XO_STYLE_TEXT || xo_style(xop) == XO_STYLE_HTML) {
2792 	    if (max > 0 && cols + width > max)
2793 		break;
2794 	}
2795 
2796 	switch (need_enc) {
2797 	case XF_ENC_UTF8:
2798 
2799 	    /* Output in UTF-8 needs to be escaped, based on the style */
2800 	    switch (xo_style(xop)) {
2801 	    case XO_STYLE_XML:
2802 	    case XO_STYLE_HTML:
2803 		if (wc == '<')
2804 		    sp = xo_xml_lt;
2805 		else if (wc == '>')
2806 		    sp = xo_xml_gt;
2807 		else if (wc == '&')
2808 		    sp = xo_xml_amp;
2809 		else if (attr && wc == '"')
2810 		    sp = xo_xml_quot;
2811 		else
2812 		    break;
2813 
2814 		ssize_t slen = strlen(sp);
2815 		if (!xo_buf_has_room(xbp, slen - 1))
2816 		    return -1;
2817 
2818 		memcpy(xbp->xb_curp, sp, slen);
2819 		xbp->xb_curp += slen;
2820 		goto done_with_encoding; /* Need multi-level 'break' */
2821 
2822 	    case XO_STYLE_JSON:
2823 		if (wc != '\\' && wc != '"' && wc != '\n' && wc != '\r')
2824 		    break;
2825 
2826 		if (!xo_buf_has_room(xbp, 2))
2827 		    return -1;
2828 
2829 		*xbp->xb_curp++ = '\\';
2830 		if (wc == '\n')
2831 		    wc = 'n';
2832 		else if (wc == '\r')
2833 		    wc = 'r';
2834 		else wc = wc & 0x7f;
2835 
2836 		*xbp->xb_curp++ = wc;
2837 		goto done_with_encoding;
2838 
2839 	    case XO_STYLE_SDPARAMS:
2840 		if (wc != '\\' && wc != '"' && wc != ']')
2841 		    break;
2842 
2843 		if (!xo_buf_has_room(xbp, 2))
2844 		    return -1;
2845 
2846 		*xbp->xb_curp++ = '\\';
2847 		wc = wc & 0x7f;
2848 		*xbp->xb_curp++ = wc;
2849 		goto done_with_encoding;
2850 	    }
2851 
2852 	    olen = xo_utf8_emit_len(wc);
2853 	    if (olen < 0) {
2854 		xo_failure(xop, "ignoring bad length");
2855 		continue;
2856 	    }
2857 
2858 	    if (!xo_buf_has_room(xbp, olen))
2859 		return -1;
2860 
2861 	    xo_utf8_emit_char(xbp->xb_curp, olen, wc);
2862 	    xbp->xb_curp += olen;
2863 	    break;
2864 
2865 	case XF_ENC_LOCALE:
2866 	    if (!xo_buf_has_room(xbp, MB_LEN_MAX + 1))
2867 		return -1;
2868 
2869 	    olen = wcrtomb(xbp->xb_curp, wc, &xop->xo_mbstate);
2870 	    if (olen <= 0) {
2871 		xo_failure(xop, "could not convert wide char: %lx",
2872 			   (unsigned long) wc);
2873 		width = 1;
2874 		*xbp->xb_curp++ = '?';
2875 	    } else
2876 		xbp->xb_curp += olen;
2877 	    break;
2878 	}
2879 
2880     done_with_encoding:
2881 	cols += width;
2882     }
2883 
2884     return cols;
2885 }
2886 
2887 static int
xo_needed_encoding(xo_handle_t * xop)2888 xo_needed_encoding (xo_handle_t *xop)
2889 {
2890     if (XOF_ISSET(xop, XOF_UTF8)) /* Check the override flag */
2891 	return XF_ENC_UTF8;
2892 
2893     if (xo_style(xop) == XO_STYLE_TEXT) /* Text means locale */
2894 	return XF_ENC_LOCALE;
2895 
2896     return XF_ENC_UTF8;		/* Otherwise, we love UTF-8 */
2897 }
2898 
2899 static ssize_t
xo_format_string(xo_handle_t * xop,xo_buffer_t * xbp,xo_xff_flags_t flags,xo_format_t * xfp)2900 xo_format_string (xo_handle_t *xop, xo_buffer_t *xbp, xo_xff_flags_t flags,
2901 		  xo_format_t *xfp)
2902 {
2903     static char null[] = "(null)";
2904     static char null_no_quotes[] = "null";
2905 
2906     char *cp = NULL;
2907     wchar_t *wcp = NULL;
2908     ssize_t len;
2909     ssize_t cols = 0, rc = 0;
2910     ssize_t off = xbp->xb_curp - xbp->xb_bufp, off2;
2911     int need_enc = xo_needed_encoding(xop);
2912 
2913     if (xo_check_conversion(xop, xfp->xf_enc, need_enc))
2914 	return 0;
2915 
2916     len = xfp->xf_width[XF_WIDTH_SIZE];
2917 
2918     if (xfp->xf_fc == 'm') {
2919 	cp = strerror(xop->xo_errno);
2920 	if (len < 0)
2921 	    len = cp ? strlen(cp) : 0;
2922 	goto normal_string;
2923 
2924     } else if (xfp->xf_enc == XF_ENC_WIDE) {
2925 	wcp = va_arg(xop->xo_vap, wchar_t *);
2926 	if (xfp->xf_skip)
2927 	    return 0;
2928 
2929 	/*
2930 	 * Dont' deref NULL; use the traditional "(null)" instead
2931 	 * of the more accurate "who's been a naughty boy, then?".
2932 	 */
2933 	if (wcp == NULL) {
2934 	    cp = null;
2935 	    len = sizeof(null) - 1;
2936 	}
2937 
2938     } else {
2939 	cp = va_arg(xop->xo_vap, char *); /* UTF-8 or native */
2940 
2941     normal_string:
2942 	if (xfp->xf_skip)
2943 	    return 0;
2944 
2945 	/* Echo "Dont' deref NULL" logic */
2946 	if (cp == NULL) {
2947 	    if ((flags & XFF_NOQUOTE) && xo_style_is_encoding(xop)) {
2948 		cp = null_no_quotes;
2949 		len = sizeof(null_no_quotes) - 1;
2950 	    } else {
2951 		cp = null;
2952 		len = sizeof(null) - 1;
2953 	    }
2954 	}
2955 
2956 	/*
2957 	 * Optimize the most common case, which is "%s".  We just
2958 	 * need to copy the complete string to the output buffer.
2959 	 */
2960 	if (xfp->xf_enc == need_enc
2961 		&& xfp->xf_width[XF_WIDTH_MIN] < 0
2962 		&& xfp->xf_width[XF_WIDTH_SIZE] < 0
2963 		&& xfp->xf_width[XF_WIDTH_MAX] < 0
2964 	        && !(XOIF_ISSET(xop, XOIF_ANCHOR)
2965 		     || XOF_ISSET(xop, XOF_COLUMNS))) {
2966 	    len = strlen(cp);
2967 	    xo_buf_escape(xop, xbp, cp, len, flags);
2968 
2969 	    /*
2970 	     * Our caller expects xb_curp left untouched, so we have
2971 	     * to reset it and return the number of bytes written to
2972 	     * the buffer.
2973 	     */
2974 	    off2 = xbp->xb_curp - xbp->xb_bufp;
2975 	    rc = off2 - off;
2976 	    xbp->xb_curp = xbp->xb_bufp + off;
2977 
2978 	    return rc;
2979 	}
2980     }
2981 
2982     cols = xo_format_string_direct(xop, xbp, flags, wcp, cp, len,
2983 				   xfp->xf_width[XF_WIDTH_MAX],
2984 				   need_enc, xfp->xf_enc);
2985     if (cols < 0)
2986 	goto bail;
2987 
2988     /*
2989      * xo_buf_append* will move xb_curp, so we save/restore it.
2990      */
2991     off2 = xbp->xb_curp - xbp->xb_bufp;
2992     rc = off2 - off;
2993     xbp->xb_curp = xbp->xb_bufp + off;
2994 
2995     if (cols < xfp->xf_width[XF_WIDTH_MIN]) {
2996 	/*
2997 	 * Find the number of columns needed to display the string.
2998 	 * If we have the original wide string, we just call wcswidth,
2999 	 * but if we did the work ourselves, then we need to do it.
3000 	 */
3001 	int delta = xfp->xf_width[XF_WIDTH_MIN] - cols;
3002 	if (!xo_buf_has_room(xbp, xfp->xf_width[XF_WIDTH_MIN]))
3003 	    goto bail;
3004 
3005 	/*
3006 	 * If seen_minus, then pad on the right; otherwise move it so
3007 	 * we can pad on the left.
3008 	 */
3009 	if (xfp->xf_seen_minus) {
3010 	    cp = xbp->xb_curp + rc;
3011 	} else {
3012 	    cp = xbp->xb_curp;
3013 	    memmove(xbp->xb_curp + delta, xbp->xb_curp, rc);
3014 	}
3015 
3016 	/* Set the padding */
3017 	memset(cp, (xfp->xf_leading_zero > 0) ? '0' : ' ', delta);
3018 	rc += delta;
3019 	cols += delta;
3020     }
3021 
3022     if (XOF_ISSET(xop, XOF_COLUMNS))
3023 	xop->xo_columns += cols;
3024     if (XOIF_ISSET(xop, XOIF_ANCHOR))
3025 	xop->xo_anchor_columns += cols;
3026 
3027     return rc;
3028 
3029  bail:
3030     xbp->xb_curp = xbp->xb_bufp + off;
3031     return 0;
3032 }
3033 
3034 /*
3035  * Look backwards in a buffer to find a numeric value
3036  */
3037 static int
xo_buf_find_last_number(xo_buffer_t * xbp,ssize_t start_offset)3038 xo_buf_find_last_number (xo_buffer_t *xbp, ssize_t start_offset)
3039 {
3040     int rc = 0;			/* Fail with zero */
3041     int digit = 1;
3042     char *sp = xbp->xb_bufp;
3043     char *cp = sp + start_offset;
3044 
3045     while (--cp >= sp)
3046 	if (isdigit((int) *cp))
3047 	    break;
3048 
3049     for ( ; cp >= sp; cp--) {
3050 	if (!isdigit((int) *cp))
3051 	    break;
3052 	rc += (*cp - '0') * digit;
3053 	digit *= 10;
3054     }
3055 
3056     return rc;
3057 }
3058 
3059 static ssize_t
xo_count_utf8_cols(const char * str,ssize_t len)3060 xo_count_utf8_cols (const char *str, ssize_t len)
3061 {
3062     ssize_t tlen;
3063     wchar_t wc;
3064     ssize_t cols = 0;
3065     const char *ep = str + len;
3066 
3067     while (str < ep) {
3068 	tlen = xo_utf8_to_wc_len(str);
3069 	if (tlen < 0)		/* Broken input is very bad */
3070 	    return cols;
3071 
3072 	wc = xo_utf8_char(str, tlen);
3073 	if (wc == (wchar_t) -1)
3074 	    return cols;
3075 
3076 	/* We only print printable characters */
3077 	if (iswprint((wint_t) wc)) {
3078 	    /*
3079 	     * Find the width-in-columns of this character, which must be done
3080 	     * in wide characters, since we lack a mbswidth() function.
3081 	     */
3082 	    ssize_t width = xo_wcwidth(wc);
3083 	    if (width < 0)
3084 		width = iswcntrl(wc) ? 0 : 1;
3085 
3086 	    cols += width;
3087 	}
3088 
3089 	str += tlen;
3090     }
3091 
3092     return cols;
3093 }
3094 
3095 #ifdef HAVE_GETTEXT
3096 static inline const char *
xo_dgettext(xo_handle_t * xop,const char * str)3097 xo_dgettext (xo_handle_t *xop, const char *str)
3098 {
3099     const char *domainname = xop->xo_gt_domain;
3100     const char *res;
3101 
3102     res = dgettext(domainname, str);
3103 
3104     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
3105 	fprintf(stderr, "xo: gettext: %s%s%smsgid \"%s\" returns \"%s\"\n",
3106 		domainname ? "domain \"" : "", xo_printable(domainname),
3107 		domainname ? "\", " : "", xo_printable(str), xo_printable(res));
3108 
3109     return res;
3110 }
3111 
3112 static inline const char *
xo_dngettext(xo_handle_t * xop,const char * sing,const char * plural,unsigned long int n)3113 xo_dngettext (xo_handle_t *xop, const char *sing, const char *plural,
3114 	      unsigned long int n)
3115 {
3116     const char *domainname = xop->xo_gt_domain;
3117     const char *res;
3118 
3119     res = dngettext(domainname, sing, plural, n);
3120     if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
3121 	fprintf(stderr, "xo: gettext: %s%s%s"
3122 		"msgid \"%s\", msgid_plural \"%s\" (%lu) returns \"%s\"\n",
3123 		domainname ? "domain \"" : "",
3124 		xo_printable(domainname), domainname ? "\", " : "",
3125 		xo_printable(sing),
3126 		xo_printable(plural), n, xo_printable(res));
3127 
3128     return res;
3129 }
3130 #else /* HAVE_GETTEXT */
3131 static inline const char *
xo_dgettext(xo_handle_t * xop UNUSED,const char * str)3132 xo_dgettext (xo_handle_t *xop UNUSED, const char *str)
3133 {
3134     return str;
3135 }
3136 
3137 static inline const char *
xo_dngettext(xo_handle_t * xop UNUSED,const char * singular,const char * plural,unsigned long int n)3138 xo_dngettext (xo_handle_t *xop UNUSED, const char *singular,
3139 	      const char *plural, unsigned long int n)
3140 {
3141     return (n == 1) ? singular : plural;
3142 }
3143 #endif /* HAVE_GETTEXT */
3144 
3145 /*
3146  * This is really _re_formatting, since the normal format code has
3147  * generated a beautiful string into xo_data, starting at
3148  * start_offset.  We need to see if it's plural, which means
3149  * comma-separated options, or singular.  Then we make the appropriate
3150  * call to d[n]gettext() to get the locale-based version.  Note that
3151  * both input and output of gettext() this should be UTF-8.
3152  */
3153 static ssize_t
xo_format_gettext(xo_handle_t * xop,xo_xff_flags_t flags,ssize_t start_offset,ssize_t cols,int need_enc)3154 xo_format_gettext (xo_handle_t *xop, xo_xff_flags_t flags,
3155 		   ssize_t start_offset, ssize_t cols, int need_enc)
3156 {
3157     xo_buffer_t *xbp = &xop->xo_data;
3158 
3159     if (!xo_buf_has_room(xbp, 1))
3160 	return cols;
3161 
3162     xbp->xb_curp[0] = '\0'; /* NUL-terminate the input string */
3163 
3164     char *cp = xbp->xb_bufp + start_offset;
3165     ssize_t len = xbp->xb_curp - cp;
3166     const char *newstr = NULL;
3167 
3168     /*
3169      * The plural flag asks us to look backwards at the last numeric
3170      * value rendered and disect the string into two pieces.
3171      */
3172     if (flags & XFF_GT_PLURAL) {
3173 	int n = xo_buf_find_last_number(xbp, start_offset);
3174 	char *two = memchr(cp, (int) ',', len);
3175 	if (two == NULL) {
3176 	    xo_failure(xop, "no comma in plural gettext field: '%s'", cp);
3177 	    return cols;
3178 	}
3179 
3180 	if (two == cp) {
3181 	    xo_failure(xop, "nothing before comma in plural gettext "
3182 		       "field: '%s'", cp);
3183 	    return cols;
3184 	}
3185 
3186 	if (two == xbp->xb_curp) {
3187 	    xo_failure(xop, "nothing after comma in plural gettext "
3188 		       "field: '%s'", cp);
3189 	    return cols;
3190 	}
3191 
3192 	*two++ = '\0';
3193 	if (flags & XFF_GT_FIELD) {
3194 	    newstr = xo_dngettext(xop, cp, two, n);
3195 	} else {
3196 	    /* Don't do a gettext() look up, just get the plural form */
3197 	    newstr = (n == 1) ? cp : two;
3198 	}
3199 
3200 	/*
3201 	 * If we returned the first string, optimize a bit by
3202 	 * backing up over comma
3203 	 */
3204 	if (newstr == cp) {
3205 	    xbp->xb_curp = two - 1; /* One for comma */
3206 	    /*
3207 	     * If the caller wanted UTF8, we're done; nothing changed,
3208 	     * but we need to count the columns used.
3209 	     */
3210 	    if (need_enc == XF_ENC_UTF8)
3211 		return xo_count_utf8_cols(cp, xbp->xb_curp - cp);
3212 	}
3213 
3214     } else {
3215 	/* The simple case (singular) */
3216 	newstr = xo_dgettext(xop, cp);
3217 
3218 	if (newstr == cp) {
3219 	    /* If the caller wanted UTF8, we're done; nothing changed */
3220 	    if (need_enc == XF_ENC_UTF8)
3221 		return cols;
3222 	}
3223     }
3224 
3225     /*
3226      * Since the new string string might be in gettext's buffer or
3227      * in the buffer (as the plural form), we make a copy.
3228      */
3229     ssize_t nlen = strlen(newstr);
3230     char *newcopy = alloca(nlen + 1);
3231     memcpy(newcopy, newstr, nlen + 1);
3232 
3233     xbp->xb_curp = xbp->xb_bufp + start_offset; /* Reset the buffer */
3234     return xo_format_string_direct(xop, xbp, flags, NULL, newcopy, nlen, 0,
3235 				   need_enc, XF_ENC_UTF8);
3236 }
3237 
3238 static void
xo_data_append_content(xo_handle_t * xop,const char * str,ssize_t len,xo_xff_flags_t flags)3239 xo_data_append_content (xo_handle_t *xop, const char *str, ssize_t len,
3240 			xo_xff_flags_t flags)
3241 {
3242     int cols;
3243     int need_enc = xo_needed_encoding(xop);
3244     ssize_t start_offset = xo_buf_offset(&xop->xo_data);
3245 
3246     cols = xo_format_string_direct(xop, &xop->xo_data, XFF_UNESCAPE | flags,
3247 				   NULL, str, len, -1,
3248 				   need_enc, XF_ENC_UTF8);
3249     if (flags & XFF_GT_FLAGS)
3250 	cols = xo_format_gettext(xop, flags, start_offset, cols, need_enc);
3251 
3252     if (XOF_ISSET(xop, XOF_COLUMNS))
3253 	xop->xo_columns += cols;
3254     if (XOIF_ISSET(xop, XOIF_ANCHOR))
3255 	xop->xo_anchor_columns += cols;
3256 }
3257 
3258 /**
3259  * Bump one of the 'width' values in a format strings (e.g. "%40.50.60s").
3260  * @param xfp Formatting instructions
3261  * @param digit Single digit (0-9) of input
3262  */
3263 static void
xo_bump_width(xo_format_t * xfp,int digit)3264 xo_bump_width (xo_format_t *xfp, int digit)
3265 {
3266     int *ip = &xfp->xf_width[xfp->xf_dots];
3267 
3268     *ip = ((*ip > 0) ? *ip : 0) * 10 + digit;
3269 }
3270 
3271 static ssize_t
xo_trim_ws(xo_buffer_t * xbp,ssize_t len)3272 xo_trim_ws (xo_buffer_t *xbp, ssize_t len)
3273 {
3274     char *cp, *sp, *ep;
3275     ssize_t delta;
3276 
3277     /* First trim leading space */
3278     for (cp = sp = xbp->xb_curp, ep = cp + len; cp < ep; cp++) {
3279 	if (*cp != ' ')
3280 	    break;
3281     }
3282 
3283     delta = cp - sp;
3284     if (delta) {
3285 	len -= delta;
3286 	memmove(sp, cp, len);
3287     }
3288 
3289     /* Then trim off the end */
3290     for (cp = xbp->xb_curp, sp = ep = cp + len; cp < ep; ep--) {
3291 	if (ep[-1] != ' ')
3292 	    break;
3293     }
3294 
3295     delta = sp - ep;
3296     if (delta) {
3297 	len -= delta;
3298 	cp[len] = '\0';
3299     }
3300 
3301     return len;
3302 }
3303 
3304 /*
3305  * Interface to format a single field.  The arguments are in xo_vap,
3306  * and the format is in 'fmt'.  If 'xbp' is null, we use xop->xo_data;
3307  * this is the most common case.
3308  */
3309 static ssize_t
xo_do_format_field(xo_handle_t * xop,xo_buffer_t * xbp,const char * fmt,ssize_t flen,xo_xff_flags_t flags)3310 xo_do_format_field (xo_handle_t *xop, xo_buffer_t *xbp,
3311 		const char *fmt, ssize_t flen, xo_xff_flags_t flags)
3312 {
3313     xo_format_t xf;
3314     const char *cp, *ep, *sp, *xp = NULL;
3315     ssize_t rc, cols;
3316     int style = (flags & XFF_XML) ? XO_STYLE_XML : xo_style(xop);
3317     unsigned make_output = !(flags & XFF_NO_OUTPUT) ? 1 : 0;
3318     int need_enc = xo_needed_encoding(xop);
3319     int real_need_enc = need_enc;
3320     ssize_t old_cols = xop->xo_columns;
3321 
3322     /* The gettext interface is UTF-8, so we'll need that for now */
3323     if (flags & XFF_GT_FIELD)
3324 	need_enc = XF_ENC_UTF8;
3325 
3326     if (xbp == NULL)
3327 	xbp = &xop->xo_data;
3328 
3329     ssize_t start_offset = xo_buf_offset(xbp);
3330 
3331     for (cp = fmt, ep = fmt + flen; cp < ep; cp++) {
3332 	/*
3333 	 * Since we're starting a new field, save the starting offset.
3334 	 * We'll need this later for field-related operations.
3335 	 */
3336 
3337 	if (*cp != '%') {
3338 	add_one:
3339 	    if (xp == NULL)
3340 		xp = cp;
3341 
3342 	    if (*cp == '\\' && cp[1] != '\0')
3343 		cp += 1;
3344 	    continue;
3345 
3346 	} else if (cp + 1 < ep && cp[1] == '%') {
3347 	    cp += 1;
3348 	    goto add_one;
3349 	}
3350 
3351 	if (xp) {
3352 	    if (make_output) {
3353 		cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
3354 					       NULL, xp, cp - xp, -1,
3355 					       need_enc, XF_ENC_UTF8);
3356 		if (XOF_ISSET(xop, XOF_COLUMNS))
3357 		    xop->xo_columns += cols;
3358 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
3359 		    xop->xo_anchor_columns += cols;
3360 	    }
3361 
3362 	    xp = NULL;
3363 	}
3364 
3365 	bzero(&xf, sizeof(xf));
3366 	xf.xf_leading_zero = -1;
3367 	xf.xf_width[0] = xf.xf_width[1] = xf.xf_width[2] = -1;
3368 
3369 	/*
3370 	 * "%@" starts an XO-specific set of flags:
3371 	 *   @X@ - XML-only field; ignored if style isn't XML
3372 	 */
3373 	if (cp[1] == '@') {
3374 	    for (cp += 2; cp < ep; cp++) {
3375 		if (*cp == '@') {
3376 		    break;
3377 		}
3378 		if (*cp == '*') {
3379 		    /*
3380 		     * '*' means there's a "%*.*s" value in vap that
3381 		     * we want to ignore
3382 		     */
3383 		    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
3384 			va_arg(xop->xo_vap, int);
3385 		}
3386 	    }
3387 	}
3388 
3389 	/* Hidden fields are only visible to JSON and XML */
3390 	if (XOF_ISSET(xop, XFF_ENCODE_ONLY)) {
3391 	    if (style != XO_STYLE_XML
3392 		    && !xo_style_is_encoding(xop))
3393 		xf.xf_skip = 1;
3394 	} else if (XOF_ISSET(xop, XFF_DISPLAY_ONLY)) {
3395 	    if (style != XO_STYLE_TEXT
3396 		    && xo_style(xop) != XO_STYLE_HTML)
3397 		xf.xf_skip = 1;
3398 	}
3399 
3400 	if (!make_output)
3401 	    xf.xf_skip = 1;
3402 
3403 	/*
3404 	 * Looking at one piece of a format; find the end and
3405 	 * call snprintf.  Then advance xo_vap on our own.
3406 	 *
3407 	 * Note that 'n', 'v', and '$' are not supported.
3408 	 */
3409 	sp = cp;		/* Save start pointer */
3410 	for (cp += 1; cp < ep; cp++) {
3411 	    if (*cp == 'l')
3412 		xf.xf_lflag += 1;
3413 	    else if (*cp == 'h')
3414 		xf.xf_hflag += 1;
3415 	    else if (*cp == 'j')
3416 		xf.xf_jflag += 1;
3417 	    else if (*cp == 't')
3418 		xf.xf_tflag += 1;
3419 	    else if (*cp == 'z')
3420 		xf.xf_zflag += 1;
3421 	    else if (*cp == 'q')
3422 		xf.xf_qflag += 1;
3423 	    else if (*cp == '.') {
3424 		if (++xf.xf_dots >= XF_WIDTH_NUM) {
3425 		    xo_failure(xop, "Too many dots in format: '%s'", fmt);
3426 		    return -1;
3427 		}
3428 	    } else if (*cp == '-')
3429 		xf.xf_seen_minus = 1;
3430 	    else if (isdigit((int) *cp)) {
3431 		if (xf.xf_leading_zero < 0)
3432 		    xf.xf_leading_zero = (*cp == '0');
3433 		xo_bump_width(&xf, *cp - '0');
3434 	    } else if (*cp == '*') {
3435 		xf.xf_stars += 1;
3436 		xf.xf_star[xf.xf_dots] = 1;
3437 	    } else if (strchr("diouxXDOUeEfFgGaAcCsSpm", *cp) != NULL)
3438 		break;
3439 	    else if (*cp == 'n' || *cp == 'v') {
3440 		xo_failure(xop, "unsupported format: '%s'", fmt);
3441 		return -1;
3442 	    }
3443 	}
3444 
3445 	if (cp == ep)
3446 	    xo_failure(xop, "field format missing format character: %s",
3447 			  fmt);
3448 
3449 	xf.xf_fc = *cp;
3450 
3451 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
3452 	    if (*cp == 's' || *cp == 'S') {
3453 		/* Handle "%*.*.*s" */
3454 		int s;
3455 		for (s = 0; s < XF_WIDTH_NUM; s++) {
3456 		    if (xf.xf_star[s]) {
3457 			xf.xf_width[s] = va_arg(xop->xo_vap, int);
3458 
3459 			/* Normalize a negative width value */
3460 			if (xf.xf_width[s] < 0) {
3461 			    if (s == 0) {
3462 				xf.xf_width[0] = -xf.xf_width[0];
3463 				xf.xf_seen_minus = 1;
3464 			    } else
3465 				xf.xf_width[s] = -1; /* Ignore negative values */
3466 			}
3467 		    }
3468 		}
3469 	    }
3470 	}
3471 
3472 	/* If no max is given, it defaults to size */
3473 	if (xf.xf_width[XF_WIDTH_MAX] < 0 && xf.xf_width[XF_WIDTH_SIZE] >= 0)
3474 	    xf.xf_width[XF_WIDTH_MAX] = xf.xf_width[XF_WIDTH_SIZE];
3475 
3476 	if (xf.xf_fc == 'D' || xf.xf_fc == 'O' || xf.xf_fc == 'U')
3477 	    xf.xf_lflag = 1;
3478 
3479 	if (!xf.xf_skip) {
3480 	    xo_buffer_t *fbp = &xop->xo_fmt;
3481 	    ssize_t len = cp - sp + 1;
3482 	    if (!xo_buf_has_room(fbp, len + 1))
3483 		return -1;
3484 
3485 	    char *newfmt = fbp->xb_curp;
3486 	    memcpy(newfmt, sp, len);
3487 	    newfmt[0] = '%';	/* If we skipped over a "%@...@s" format */
3488 	    newfmt[len] = '\0';
3489 
3490 	    /*
3491 	     * Bad news: our strings are UTF-8, but the stock printf
3492 	     * functions won't handle field widths for wide characters
3493 	     * correctly.  So we have to handle this ourselves.
3494 	     */
3495 	    if (xop->xo_formatter == NULL
3496 		    && (xf.xf_fc == 's' || xf.xf_fc == 'S'
3497 			|| xf.xf_fc == 'm')) {
3498 
3499 		xf.xf_enc = (xf.xf_fc == 'm') ? XF_ENC_UTF8
3500 		    : (xf.xf_lflag || (xf.xf_fc == 'S')) ? XF_ENC_WIDE
3501 		    : xf.xf_hflag ? XF_ENC_LOCALE : XF_ENC_UTF8;
3502 
3503 		rc = xo_format_string(xop, xbp, flags, &xf);
3504 
3505 		if ((flags & XFF_TRIM_WS) && xo_style_is_encoding(xop))
3506 		    rc = xo_trim_ws(xbp, rc);
3507 
3508 	    } else {
3509 		ssize_t columns = rc = xo_vsnprintf(xop, xbp, newfmt,
3510 						    xop->xo_vap);
3511 
3512 		if (rc > 0) {
3513 		    /*
3514 		     * For XML and HTML, we need "&<>" processing; for JSON,
3515 		     * it's quotes.  Text gets nothing.
3516 		     */
3517 		    switch (style) {
3518 		    case XO_STYLE_XML:
3519 			if (flags & XFF_TRIM_WS)
3520 			    columns = rc = xo_trim_ws(xbp, rc);
3521 			/* FALLTHRU */
3522 		    case XO_STYLE_HTML:
3523 			rc = xo_escape_xml(xbp, rc, (flags & XFF_ATTR));
3524 			break;
3525 
3526 		    case XO_STYLE_JSON:
3527 			if (flags & XFF_TRIM_WS)
3528 			    columns = rc = xo_trim_ws(xbp, rc);
3529 			rc = xo_escape_json(xbp, rc, 0);
3530 			break;
3531 
3532 		    case XO_STYLE_SDPARAMS:
3533 			if (flags & XFF_TRIM_WS)
3534 			    columns = rc = xo_trim_ws(xbp, rc);
3535 			rc = xo_escape_sdparams(xbp, rc, 0);
3536 			break;
3537 
3538 		    case XO_STYLE_ENCODER:
3539 			if (flags & XFF_TRIM_WS)
3540 			    columns = rc = xo_trim_ws(xbp, rc);
3541 			break;
3542 		    }
3543 
3544 		    /*
3545 		     * We can assume all the non-%s data we've
3546 		     * added is ASCII, so the columns and bytes are the
3547 		     * same.  xo_format_string handles all the fancy
3548 		     * string conversions and updates xo_anchor_columns
3549 		     * accordingly.
3550 		     */
3551 		    if (XOF_ISSET(xop, XOF_COLUMNS))
3552 			xop->xo_columns += columns;
3553 		    if (XOIF_ISSET(xop, XOIF_ANCHOR))
3554 			xop->xo_anchor_columns += columns;
3555 		}
3556 	    }
3557 
3558 	    if (rc > 0)
3559 		xbp->xb_curp += rc;
3560 	}
3561 
3562 	/*
3563 	 * Now for the tricky part: we need to move the argument pointer
3564 	 * along by the amount needed.
3565 	 */
3566 	if (!XOF_ISSET(xop, XOF_NO_VA_ARG)) {
3567 
3568 	    if (xf.xf_fc == 's' ||xf.xf_fc == 'S') {
3569 		/*
3570 		 * The 'S' and 's' formats are normally handled in
3571 		 * xo_format_string, but if we skipped it, then we
3572 		 * need to pop it.
3573 		 */
3574 		if (xf.xf_skip)
3575 		    va_arg(xop->xo_vap, char *);
3576 
3577 	    } else if (xf.xf_fc == 'm') {
3578 		/* Nothing on the stack for "%m" */
3579 
3580 	    } else {
3581 		int s;
3582 		for (s = 0; s < XF_WIDTH_NUM; s++) {
3583 		    if (xf.xf_star[s])
3584 			va_arg(xop->xo_vap, int);
3585 		}
3586 
3587 		if (strchr("diouxXDOU", xf.xf_fc) != NULL) {
3588 		    if (xf.xf_hflag > 1) {
3589 			va_arg(xop->xo_vap, int);
3590 
3591 		    } else if (xf.xf_hflag > 0) {
3592 			va_arg(xop->xo_vap, int);
3593 
3594 		    } else if (xf.xf_lflag > 1) {
3595 			va_arg(xop->xo_vap, unsigned long long);
3596 
3597 		    } else if (xf.xf_lflag > 0) {
3598 			va_arg(xop->xo_vap, unsigned long);
3599 
3600 		    } else if (xf.xf_jflag > 0) {
3601 			va_arg(xop->xo_vap, intmax_t);
3602 
3603 		    } else if (xf.xf_tflag > 0) {
3604 			va_arg(xop->xo_vap, ptrdiff_t);
3605 
3606 		    } else if (xf.xf_zflag > 0) {
3607 			va_arg(xop->xo_vap, size_t);
3608 
3609 		    } else if (xf.xf_qflag > 0) {
3610 			va_arg(xop->xo_vap, quad_t);
3611 
3612 		    } else {
3613 			va_arg(xop->xo_vap, int);
3614 		    }
3615 		} else if (strchr("eEfFgGaA", xf.xf_fc) != NULL)
3616 		    if (xf.xf_lflag)
3617 			va_arg(xop->xo_vap, long double);
3618 		    else
3619 			va_arg(xop->xo_vap, double);
3620 
3621 		else if (xf.xf_fc == 'C' || (xf.xf_fc == 'c' && xf.xf_lflag))
3622 		    va_arg(xop->xo_vap, wint_t);
3623 
3624 		else if (xf.xf_fc == 'c')
3625 		    va_arg(xop->xo_vap, int);
3626 
3627 		else if (xf.xf_fc == 'p')
3628 		    va_arg(xop->xo_vap, void *);
3629 	    }
3630 	}
3631     }
3632 
3633     if (xp) {
3634 	if (make_output) {
3635 	    cols = xo_format_string_direct(xop, xbp, flags | XFF_UNESCAPE,
3636 					   NULL, xp, cp - xp, -1,
3637 					   need_enc, XF_ENC_UTF8);
3638 
3639 	    if (XOF_ISSET(xop, XOF_COLUMNS))
3640 		xop->xo_columns += cols;
3641 	    if (XOIF_ISSET(xop, XOIF_ANCHOR))
3642 		xop->xo_anchor_columns += cols;
3643 	}
3644 
3645 	xp = NULL;
3646     }
3647 
3648     if (flags & XFF_GT_FLAGS) {
3649 	/*
3650 	 * Handle gettext()ing the field by looking up the value
3651 	 * and then copying it in, while converting to locale, if
3652 	 * needed.
3653 	 */
3654 	ssize_t new_cols = xo_format_gettext(xop, flags, start_offset,
3655 					 old_cols, real_need_enc);
3656 
3657 	if (XOF_ISSET(xop, XOF_COLUMNS))
3658 	    xop->xo_columns += new_cols - old_cols;
3659 	if (XOIF_ISSET(xop, XOIF_ANCHOR))
3660 	    xop->xo_anchor_columns += new_cols - old_cols;
3661     }
3662 
3663     return 0;
3664 }
3665 
3666 /*
3667  * Remove any numeric precision/width format from the format string by
3668  * inserting the "%" after the [0-9]+, returning the substring.
3669  */
3670 static char *
xo_fix_encoding(xo_handle_t * xop UNUSED,char * encoding)3671 xo_fix_encoding (xo_handle_t *xop UNUSED, char *encoding)
3672 {
3673     char *cp = encoding;
3674 
3675     if (cp[0] != '%' || !isdigit((int) cp[1]))
3676 	return encoding;
3677 
3678     for (cp += 2; *cp; cp++) {
3679 	if (!isdigit((int) *cp))
3680 	    break;
3681     }
3682 
3683     *--cp = '%';		/* Back off and insert the '%' */
3684 
3685     return cp;
3686 }
3687 
3688 static void
xo_color_append_html(xo_handle_t * xop)3689 xo_color_append_html (xo_handle_t *xop)
3690 {
3691     /*
3692      * If the color buffer has content, we add it now.  It's already
3693      * prebuilt and ready, since we want to add it to every <div>.
3694      */
3695     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3696 	xo_buffer_t *xbp = &xop->xo_color_buf;
3697 
3698 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3699     }
3700 }
3701 
3702 /*
3703  * A wrapper for humanize_number that autoscales, since the
3704  * HN_AUTOSCALE flag scales as needed based on the size of
3705  * the output buffer, not the size of the value.  I also
3706  * wish HN_DECIMAL was more imperative, without the <10
3707  * test.  But the boat only goes where we want when we hold
3708  * the rudder, so xo_humanize fixes part of the problem.
3709  */
3710 static ssize_t
xo_humanize(char * buf,ssize_t len,uint64_t value,int flags)3711 xo_humanize (char *buf, ssize_t len, uint64_t value, int flags)
3712 {
3713     int scale = 0;
3714 
3715     if (value) {
3716 	uint64_t left = value;
3717 
3718 	if (flags & HN_DIVISOR_1000) {
3719 	    for ( ; left; scale++)
3720 		left /= 1000;
3721 	} else {
3722 	    for ( ; left; scale++)
3723 		left /= 1024;
3724 	}
3725 	scale -= 1;
3726     }
3727 
3728     return xo_humanize_number(buf, len, value, "", scale, flags);
3729 }
3730 
3731 /*
3732  * This is an area where we can save information from the handle for
3733  * later restoration.  We need to know what data was rendered to know
3734  * what needs cleaned up.
3735  */
3736 typedef struct xo_humanize_save_s {
3737     ssize_t xhs_offset;		/* Saved xo_offset */
3738     ssize_t xhs_columns;	/* Saved xo_columns */
3739     ssize_t xhs_anchor_columns; /* Saved xo_anchor_columns */
3740 } xo_humanize_save_t;
3741 
3742 /*
3743  * Format a "humanized" value for a numeric, meaning something nice
3744  * like "44M" instead of "44470272".  We autoscale, choosing the
3745  * most appropriate value for K/M/G/T/P/E based on the value given.
3746  */
3747 static void
xo_format_humanize(xo_handle_t * xop,xo_buffer_t * xbp,xo_humanize_save_t * savep,xo_xff_flags_t flags)3748 xo_format_humanize (xo_handle_t *xop, xo_buffer_t *xbp,
3749 		    xo_humanize_save_t *savep, xo_xff_flags_t flags)
3750 {
3751     if (XOF_ISSET(xop, XOF_NO_HUMANIZE))
3752 	return;
3753 
3754     ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
3755     if (end_offset == savep->xhs_offset) /* Huh? Nothing to render */
3756 	return;
3757 
3758     /*
3759      * We have a string that's allegedly a number. We want to
3760      * humanize it, which means turning it back into a number
3761      * and calling xo_humanize_number on it.
3762      */
3763     uint64_t value;
3764     char *ep;
3765 
3766     xo_buf_append(xbp, "", 1); /* NUL-terminate it */
3767 
3768     value = strtoull(xbp->xb_bufp + savep->xhs_offset, &ep, 0);
3769     if (!(value == ULLONG_MAX && errno == ERANGE)
3770 	&& (ep != xbp->xb_bufp + savep->xhs_offset)) {
3771 	/*
3772 	 * There are few values where humanize_number needs
3773 	 * more bytes than the original value.  I've used
3774 	 * 10 as a rectal number to cover those scenarios.
3775 	 */
3776 	if (xo_buf_has_room(xbp, 10)) {
3777 	    xbp->xb_curp = xbp->xb_bufp + savep->xhs_offset;
3778 
3779 	    ssize_t rc;
3780 	    ssize_t left = (xbp->xb_bufp + xbp->xb_size) - xbp->xb_curp;
3781 	    int hn_flags = HN_NOSPACE; /* On by default */
3782 
3783 	    if (flags & XFF_HN_SPACE)
3784 		hn_flags &= ~HN_NOSPACE;
3785 
3786 	    if (flags & XFF_HN_DECIMAL)
3787 		hn_flags |= HN_DECIMAL;
3788 
3789 	    if (flags & XFF_HN_1000)
3790 		hn_flags |= HN_DIVISOR_1000;
3791 
3792 	    rc = xo_humanize(xbp->xb_curp, left, value, hn_flags);
3793 	    if (rc > 0) {
3794 		xbp->xb_curp += rc;
3795 		xop->xo_columns = savep->xhs_columns + rc;
3796 		xop->xo_anchor_columns = savep->xhs_anchor_columns + rc;
3797 	    }
3798 	}
3799     }
3800 }
3801 
3802 /*
3803  * Convenience function that either append a fixed value (if one is
3804  * given) or formats a field using a format string.  If it's
3805  * encode_only, then we can't skip formatting the field, since it may
3806  * be pulling arguments off the stack.
3807  */
3808 static inline void
xo_simple_field(xo_handle_t * xop,unsigned encode_only,const char * value,ssize_t vlen,const char * fmt,ssize_t flen,xo_xff_flags_t flags)3809 xo_simple_field (xo_handle_t *xop, unsigned encode_only,
3810 		      const char *value, ssize_t vlen,
3811 		      const char *fmt, ssize_t flen, xo_xff_flags_t flags)
3812 {
3813     if (encode_only)
3814 	flags |= XFF_NO_OUTPUT;
3815 
3816     if (vlen == 0)
3817 	xo_do_format_field(xop, NULL, fmt, flen, flags);
3818     else if (!encode_only)
3819 	xo_data_append_content(xop, value, vlen, flags);
3820 }
3821 
3822 /*
3823  * Html mode: append a <div> to the output buffer contain a field
3824  * along with all the supporting information indicated by the flags.
3825  */
3826 static void
xo_buf_append_div(xo_handle_t * xop,const char * class,xo_xff_flags_t flags,const char * name,ssize_t nlen,const char * value,ssize_t vlen,const char * fmt,ssize_t flen,const char * encoding,ssize_t elen)3827 xo_buf_append_div (xo_handle_t *xop, const char *class, xo_xff_flags_t flags,
3828 		   const char *name, ssize_t nlen,
3829 		   const char *value, ssize_t vlen,
3830 		   const char *fmt, ssize_t flen,
3831 		   const char *encoding, ssize_t elen)
3832 {
3833     static char div_start[] = "<div class=\"";
3834     static char div_tag[] = "\" data-tag=\"";
3835     static char div_xpath[] = "\" data-xpath=\"";
3836     static char div_key[] = "\" data-key=\"key";
3837     static char div_end[] = "\">";
3838     static char div_close[] = "</div>";
3839 
3840     /* The encoding format defaults to the normal format */
3841     if (encoding == NULL && fmt != NULL) {
3842 	char *enc  = alloca(flen + 1);
3843 	memcpy(enc, fmt, flen);
3844 	enc[flen] = '\0';
3845 	encoding = xo_fix_encoding(xop, enc);
3846 	elen = strlen(encoding);
3847     }
3848 
3849     /*
3850      * To build our XPath predicate, we need to save the va_list before
3851      * we format our data, and then restore it before we format the
3852      * xpath expression.
3853      * Display-only keys implies that we've got an encode-only key
3854      * elsewhere, so we don't use them from making predicates.
3855      */
3856     int need_predidate =
3857 	(name && (flags & XFF_KEY) && !(flags & XFF_DISPLAY_ONLY)
3858 	 && XOF_ISSET(xop, XOF_XPATH)) ? 1 : 0;
3859 
3860     if (need_predidate) {
3861 	va_list va_local;
3862 
3863 	va_copy(va_local, xop->xo_vap);
3864 	if (xop->xo_checkpointer)
3865 	    xop->xo_checkpointer(xop, xop->xo_vap, 0);
3866 
3867 	/*
3868 	 * Build an XPath predicate expression to match this key.
3869 	 * We use the format buffer.
3870 	 */
3871 	xo_buffer_t *pbp = &xop->xo_predicate;
3872 	pbp->xb_curp = pbp->xb_bufp; /* Restart buffer */
3873 
3874 	xo_buf_append(pbp, "[", 1);
3875 	xo_buf_escape(xop, pbp, name, nlen, 0);
3876 	if (XOF_ISSET(xop, XOF_PRETTY))
3877 	    xo_buf_append(pbp, " = '", 4);
3878 	else
3879 	    xo_buf_append(pbp, "='", 2);
3880 
3881 	xo_xff_flags_t pflags = flags | XFF_XML | XFF_ATTR;
3882 	pflags &= ~(XFF_NO_OUTPUT | XFF_ENCODE_ONLY);
3883 	xo_do_format_field(xop, pbp, encoding, elen, pflags);
3884 
3885 	xo_buf_append(pbp, "']", 2);
3886 
3887 	/* Now we record this predicate expression in the stack */
3888 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
3889 	ssize_t olen = xsp->xs_keys ? strlen(xsp->xs_keys) : 0;
3890 	ssize_t dlen = pbp->xb_curp - pbp->xb_bufp;
3891 
3892 	char *cp = xo_realloc(xsp->xs_keys, olen + dlen + 1);
3893 	if (cp) {
3894 	    memcpy(cp + olen, pbp->xb_bufp, dlen);
3895 	    cp[olen + dlen] = '\0';
3896 	    xsp->xs_keys = cp;
3897 	}
3898 
3899 	/* Now we reset the xo_vap as if we were never here */
3900 	va_end(xop->xo_vap);
3901 	va_copy(xop->xo_vap, va_local);
3902 	va_end(va_local);
3903 	if (xop->xo_checkpointer)
3904 	    xop->xo_checkpointer(xop, xop->xo_vap, 1);
3905     }
3906 
3907     if (flags & XFF_ENCODE_ONLY) {
3908 	/*
3909 	 * Even if this is encode-only, we need to go through the
3910 	 * work of formatting it to make sure the args are cleared
3911 	 * from xo_vap.  This is not true when vlen is zero, since
3912 	 * that means our "value" isn't on the stack.
3913 	 */
3914 	xo_simple_field(xop, TRUE, NULL, 0, encoding, elen, flags);
3915 	return;
3916     }
3917 
3918     xo_line_ensure_open(xop, 0);
3919 
3920     if (XOF_ISSET(xop, XOF_PRETTY))
3921 	xo_buf_indent(xop, xop->xo_indent_by);
3922 
3923     xo_data_append(xop, div_start, sizeof(div_start) - 1);
3924     xo_data_append(xop, class, strlen(class));
3925 
3926     /*
3927      * If the color buffer has content, we add it now.  It's already
3928      * prebuilt and ready, since we want to add it to every <div>.
3929      */
3930     if (!xo_buf_is_empty(&xop->xo_color_buf)) {
3931 	xo_buffer_t *xbp = &xop->xo_color_buf;
3932 
3933 	xo_data_append(xop, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
3934     }
3935 
3936     if (name) {
3937 	xo_data_append(xop, div_tag, sizeof(div_tag) - 1);
3938 	xo_data_escape(xop, name, nlen);
3939 
3940 	/*
3941 	 * Save the offset at which we'd place units.  See xo_format_units.
3942 	 */
3943 	if (XOF_ISSET(xop, XOF_UNITS)) {
3944 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
3945 	    /*
3946 	     * Note: We need the '+1' here because we know we've not
3947 	     * added the closing quote.  We add one, knowing the quote
3948 	     * will be added shortly.
3949 	     */
3950 	    xop->xo_units_offset =
3951 		xop->xo_data.xb_curp -xop->xo_data.xb_bufp + 1;
3952 	}
3953 
3954 	if (XOF_ISSET(xop, XOF_XPATH)) {
3955 	    int i;
3956 	    xo_stack_t *xsp;
3957 
3958 	    xo_data_append(xop, div_xpath, sizeof(div_xpath) - 1);
3959 	    if (xop->xo_leading_xpath)
3960 		xo_data_append(xop, xop->xo_leading_xpath,
3961 			       strlen(xop->xo_leading_xpath));
3962 
3963 	    for (i = 0; i <= xop->xo_depth; i++) {
3964 		xsp = &xop->xo_stack[i];
3965 		if (xsp->xs_name == NULL)
3966 		    continue;
3967 
3968 		/*
3969 		 * XSS_OPEN_LIST and XSS_OPEN_LEAF_LIST stack frames
3970 		 * are directly under XSS_OPEN_INSTANCE frames so we
3971 		 * don't need to put these in our XPath expressions.
3972 		 */
3973 		if (xsp->xs_state == XSS_OPEN_LIST
3974 			|| xsp->xs_state == XSS_OPEN_LEAF_LIST)
3975 		    continue;
3976 
3977 		xo_data_append(xop, "/", 1);
3978 		xo_data_escape(xop, xsp->xs_name, strlen(xsp->xs_name));
3979 		if (xsp->xs_keys) {
3980 		    /* Don't show keys for the key field */
3981 		    if (i != xop->xo_depth || !(flags & XFF_KEY))
3982 			xo_data_append(xop, xsp->xs_keys, strlen(xsp->xs_keys));
3983 		}
3984 	    }
3985 
3986 	    xo_data_append(xop, "/", 1);
3987 	    xo_data_escape(xop, name, nlen);
3988 	}
3989 
3990 	if (XOF_ISSET(xop, XOF_INFO) && xop->xo_info) {
3991 	    static char in_type[] = "\" data-type=\"";
3992 	    static char in_help[] = "\" data-help=\"";
3993 
3994 	    xo_info_t *xip = xo_info_find(xop, name, nlen);
3995 	    if (xip) {
3996 		if (xip->xi_type) {
3997 		    xo_data_append(xop, in_type, sizeof(in_type) - 1);
3998 		    xo_data_escape(xop, xip->xi_type, strlen(xip->xi_type));
3999 		}
4000 		if (xip->xi_help) {
4001 		    xo_data_append(xop, in_help, sizeof(in_help) - 1);
4002 		    xo_data_escape(xop, xip->xi_help, strlen(xip->xi_help));
4003 		}
4004 	    }
4005 	}
4006 
4007 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS))
4008 	    xo_data_append(xop, div_key, sizeof(div_key) - 1);
4009     }
4010 
4011     xo_buffer_t *xbp = &xop->xo_data;
4012     ssize_t base_offset = xbp->xb_curp - xbp->xb_bufp;
4013 
4014     xo_data_append(xop, div_end, sizeof(div_end) - 1);
4015 
4016     xo_humanize_save_t save;	/* Save values for humanizing logic */
4017 
4018     save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4019     save.xhs_columns = xop->xo_columns;
4020     save.xhs_anchor_columns = xop->xo_anchor_columns;
4021 
4022     xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4023 
4024     if (flags & XFF_HUMANIZE) {
4025 	/*
4026 	 * Unlike text style, we want to retain the original value and
4027 	 * stuff it into the "data-number" attribute.
4028 	 */
4029 	static const char div_number[] = "\" data-number=\"";
4030 	ssize_t div_len = sizeof(div_number) - 1;
4031 
4032 	ssize_t end_offset = xbp->xb_curp - xbp->xb_bufp;
4033 	ssize_t olen = end_offset - save.xhs_offset;
4034 
4035 	char *cp = alloca(olen + 1);
4036 	memcpy(cp, xbp->xb_bufp + save.xhs_offset, olen);
4037 	cp[olen] = '\0';
4038 
4039 	xo_format_humanize(xop, xbp, &save, flags);
4040 
4041 	if (xo_buf_has_room(xbp, div_len + olen)) {
4042 	    ssize_t new_offset = xbp->xb_curp - xbp->xb_bufp;
4043 
4044 
4045 	    /* Move the humanized string off to the left */
4046 	    memmove(xbp->xb_bufp + base_offset + div_len + olen,
4047 		    xbp->xb_bufp + base_offset, new_offset - base_offset);
4048 
4049 	    /* Copy the data_number attribute name */
4050 	    memcpy(xbp->xb_bufp + base_offset, div_number, div_len);
4051 
4052 	    /* Copy the original long value */
4053 	    memcpy(xbp->xb_bufp + base_offset + div_len, cp, olen);
4054 	    xbp->xb_curp += div_len + olen;
4055 	}
4056     }
4057 
4058     xo_data_append(xop, div_close, sizeof(div_close) - 1);
4059 
4060     if (XOF_ISSET(xop, XOF_PRETTY))
4061 	xo_data_append(xop, "\n", 1);
4062 }
4063 
4064 static void
xo_format_text(xo_handle_t * xop,const char * str,ssize_t len)4065 xo_format_text (xo_handle_t *xop, const char *str, ssize_t len)
4066 {
4067     switch (xo_style(xop)) {
4068     case XO_STYLE_TEXT:
4069 	xo_buf_append_locale(xop, &xop->xo_data, str, len);
4070 	break;
4071 
4072     case XO_STYLE_HTML:
4073 	xo_buf_append_div(xop, "text", 0, NULL, 0, str, len, NULL, 0, NULL, 0);
4074 	break;
4075     }
4076 }
4077 
4078 static void
xo_format_title(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)4079 xo_format_title (xo_handle_t *xop, xo_field_info_t *xfip,
4080 		 const char *value, ssize_t vlen)
4081 {
4082     const char *fmt = xfip->xfi_format;
4083     ssize_t flen = xfip->xfi_flen;
4084     xo_xff_flags_t flags = xfip->xfi_flags;
4085 
4086     static char div_open[] = "<div class=\"title";
4087     static char div_middle[] = "\">";
4088     static char div_close[] = "</div>";
4089 
4090     if (flen == 0) {
4091 	fmt = "%s";
4092 	flen = 2;
4093     }
4094 
4095     switch (xo_style(xop)) {
4096     case XO_STYLE_XML:
4097     case XO_STYLE_JSON:
4098     case XO_STYLE_SDPARAMS:
4099     case XO_STYLE_ENCODER:
4100 	/*
4101 	 * Even though we don't care about text, we need to do
4102 	 * enough parsing work to skip over the right bits of xo_vap.
4103 	 */
4104 	xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4105 	return;
4106     }
4107 
4108     xo_buffer_t *xbp = &xop->xo_data;
4109     ssize_t start = xbp->xb_curp - xbp->xb_bufp;
4110     ssize_t left = xbp->xb_size - start;
4111     ssize_t rc;
4112 
4113     if (xo_style(xop) == XO_STYLE_HTML) {
4114 	xo_line_ensure_open(xop, 0);
4115 	if (XOF_ISSET(xop, XOF_PRETTY))
4116 	    xo_buf_indent(xop, xop->xo_indent_by);
4117 	xo_buf_append(&xop->xo_data, div_open, sizeof(div_open) - 1);
4118 	xo_color_append_html(xop);
4119 	xo_buf_append(&xop->xo_data, div_middle, sizeof(div_middle) - 1);
4120     }
4121 
4122     start = xbp->xb_curp - xbp->xb_bufp; /* Reset start */
4123     if (vlen) {
4124 	char *newfmt = alloca(flen + 1);
4125 	memcpy(newfmt, fmt, flen);
4126 	newfmt[flen] = '\0';
4127 
4128 	/* If len is non-zero, the format string apply to the name */
4129 	char *newstr = alloca(vlen + 1);
4130 	memcpy(newstr, value, vlen);
4131 	newstr[vlen] = '\0';
4132 
4133 	if (newstr[vlen - 1] == 's') {
4134 	    char *bp;
4135 
4136 	    rc = snprintf(NULL, 0, newfmt, newstr);
4137 	    if (rc > 0) {
4138 		/*
4139 		 * We have to do this the hard way, since we might need
4140 		 * the columns.
4141 		 */
4142 		bp = alloca(rc + 1);
4143 		rc = snprintf(bp, rc + 1, newfmt, newstr);
4144 
4145 		xo_data_append_content(xop, bp, rc, flags);
4146 	    }
4147 	    goto move_along;
4148 
4149 	} else {
4150 	    rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
4151 	    if (rc >= left) {
4152 		if (!xo_buf_has_room(xbp, rc))
4153 		    return;
4154 		left = xbp->xb_size - (xbp->xb_curp - xbp->xb_bufp);
4155 		rc = snprintf(xbp->xb_curp, left, newfmt, newstr);
4156 	    }
4157 
4158 	    if (rc > 0) {
4159 		if (XOF_ISSET(xop, XOF_COLUMNS))
4160 		    xop->xo_columns += rc;
4161 		if (XOIF_ISSET(xop, XOIF_ANCHOR))
4162 		    xop->xo_anchor_columns += rc;
4163 	    }
4164 	}
4165 
4166     } else {
4167 	xo_do_format_field(xop, NULL, fmt, flen, flags);
4168 
4169 	/* xo_do_format_field moved curp, so we need to reset it */
4170 	rc = xbp->xb_curp - (xbp->xb_bufp + start);
4171 	xbp->xb_curp = xbp->xb_bufp + start;
4172     }
4173 
4174     /* If we're styling HTML, then we need to escape it */
4175     if (xo_style(xop) == XO_STYLE_HTML) {
4176 	rc = xo_escape_xml(xbp, rc, 0);
4177     }
4178 
4179     if (rc > 0)
4180 	xbp->xb_curp += rc;
4181 
4182  move_along:
4183     if (xo_style(xop) == XO_STYLE_HTML) {
4184 	xo_data_append(xop, div_close, sizeof(div_close) - 1);
4185 	if (XOF_ISSET(xop, XOF_PRETTY))
4186 	    xo_data_append(xop, "\n", 1);
4187     }
4188 }
4189 
4190 /*
4191  * strspn() with a string length
4192  */
4193 static ssize_t
xo_strnspn(const char * str,size_t len,const char * accept)4194 xo_strnspn (const char *str, size_t len,  const char *accept)
4195 {
4196     ssize_t i;
4197     const char *cp, *ep;
4198 
4199     for (i = 0, cp = str, ep = str + len; cp < ep && *cp != '\0'; i++, cp++) {
4200 	if (strchr(accept, *cp) == NULL)
4201 	    break;
4202     }
4203 
4204     return i;
4205 }
4206 
4207 /*
4208  * Decide if a format string should be considered "numeric",
4209  * in the sense that the number does not need to be quoted.
4210  * This means that it consists only of a single numeric field
4211  * with nothing exotic or "interesting".  This means that
4212  * static values are never considered numeric.
4213  */
4214 static int
xo_format_is_numeric(const char * fmt,ssize_t flen)4215 xo_format_is_numeric (const char *fmt, ssize_t flen)
4216 {
4217     if (flen <= 0 || *fmt++ != '%') /* Must start with '%' */
4218 	return FALSE;
4219     flen -= 1;
4220 
4221     /* Handle leading flags; don't want "#" since JSON can't handle hex */
4222     ssize_t spn = xo_strnspn(fmt, flen, "0123456789.*+ -");
4223     if (spn >= flen)
4224 	return FALSE;
4225 
4226     fmt += spn;			/* Move along the input string */
4227     flen -= spn;
4228 
4229     /* Handle the length modifiers */
4230     spn = xo_strnspn(fmt, flen, "hljtqz");
4231     if (spn >= flen)
4232 	return FALSE;
4233 
4234     fmt += spn;			/* Move along the input string */
4235     flen -= spn;
4236 
4237     if (flen != 1)		/* Should only be one character left */
4238 	return FALSE;
4239 
4240     return (strchr("diouDOUeEfFgG", *fmt) == NULL) ? FALSE : TRUE;
4241 }
4242 
4243 /*
4244  * Update the stack flags using the object flags, allowing callers
4245  * to monkey with the stack flags without even knowing they exist.
4246  */
4247 static void
xo_stack_set_flags(xo_handle_t * xop)4248 xo_stack_set_flags (xo_handle_t *xop)
4249 {
4250     if (XOF_ISSET(xop, XOF_NOT_FIRST)) {
4251 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4252 
4253 	xsp->xs_flags |= XSF_NOT_FIRST;
4254 	XOF_CLEAR(xop, XOF_NOT_FIRST);
4255     }
4256 }
4257 
4258 static void
xo_format_prep(xo_handle_t * xop,xo_xff_flags_t flags)4259 xo_format_prep (xo_handle_t *xop, xo_xff_flags_t flags)
4260 {
4261     if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST) {
4262 	xo_data_append(xop, ",", 1);
4263 	if (!(flags & XFF_LEAF_LIST) && XOF_ISSET(xop, XOF_PRETTY))
4264 	    xo_data_append(xop, "\n", 1);
4265     } else
4266 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
4267 }
4268 
4269 #if 0
4270 /* Useful debugging function */
4271 void
4272 xo_arg (xo_handle_t *xop);
4273 void
4274 xo_arg (xo_handle_t *xop)
4275 {
4276     xop = xo_default(xop);
4277     fprintf(stderr, "0x%x", va_arg(xop->xo_vap, unsigned));
4278 }
4279 #endif /* 0 */
4280 
4281 static void
xo_format_value(xo_handle_t * xop,const char * name,ssize_t nlen,const char * value,ssize_t vlen,const char * fmt,ssize_t flen,const char * encoding,ssize_t elen,xo_xff_flags_t flags)4282 xo_format_value (xo_handle_t *xop, const char *name, ssize_t nlen,
4283 		 const char *value, ssize_t vlen,
4284 		 const char *fmt, ssize_t flen,
4285 		 const char *encoding, ssize_t elen, xo_xff_flags_t flags)
4286 {
4287     int pretty = XOF_ISSET(xop, XOF_PRETTY);
4288     int quote;
4289 
4290     /*
4291      * Before we emit a value, we need to know that the frame is ready.
4292      */
4293     xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
4294 
4295     if (flags & XFF_LEAF_LIST) {
4296 	/*
4297 	 * Check if we've already started to emit normal leafs
4298 	 * or if we're not in a leaf list.
4299 	 */
4300 	if ((xsp->xs_flags & (XSF_EMIT | XSF_EMIT_KEY))
4301 	    || !(xsp->xs_flags & XSF_EMIT_LEAF_LIST)) {
4302 	    char nbuf[nlen + 1];
4303 	    memcpy(nbuf, name, nlen);
4304 	    nbuf[nlen] = '\0';
4305 
4306 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT_LEAF_LIST);
4307 	    if (rc < 0)
4308 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4309 	    else
4310 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_LEAF_LIST;
4311 	}
4312 
4313 	xsp = &xop->xo_stack[xop->xo_depth];
4314 	if (xsp->xs_name) {
4315 	    name = xsp->xs_name;
4316 	    nlen = strlen(name);
4317 	}
4318 
4319     } else if (flags & XFF_KEY) {
4320 	/* Emitting a 'k' (key) field */
4321 	if ((xsp->xs_flags & XSF_EMIT) && !(flags & XFF_DISPLAY_ONLY)) {
4322 	    xo_failure(xop, "key field emitted after normal value field: '%.*s'",
4323 		       nlen, name);
4324 
4325 	} else if (!(xsp->xs_flags & XSF_EMIT_KEY)) {
4326 	    char nbuf[nlen + 1];
4327 	    memcpy(nbuf, name, nlen);
4328 	    nbuf[nlen] = '\0';
4329 
4330 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4331 	    if (rc < 0)
4332 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4333 	    else
4334 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT_KEY;
4335 
4336 	    xsp = &xop->xo_stack[xop->xo_depth];
4337 	    xsp->xs_flags |= XSF_EMIT_KEY;
4338 	}
4339 
4340     } else {
4341 	/* Emitting a normal value field */
4342 	if ((xsp->xs_flags & XSF_EMIT_LEAF_LIST)
4343 	    || !(xsp->xs_flags & XSF_EMIT)) {
4344 	    char nbuf[nlen + 1];
4345 	    memcpy(nbuf, name, nlen);
4346 	    nbuf[nlen] = '\0';
4347 
4348 	    ssize_t rc = xo_transition(xop, 0, nbuf, XSS_EMIT);
4349 	    if (rc < 0)
4350 		flags |= XFF_DISPLAY_ONLY | XFF_ENCODE_ONLY;
4351 	    else
4352 		xop->xo_stack[xop->xo_depth].xs_flags |= XSF_EMIT;
4353 
4354 	    xsp = &xop->xo_stack[xop->xo_depth];
4355 	    xsp->xs_flags |= XSF_EMIT;
4356 	}
4357     }
4358 
4359     xo_buffer_t *xbp = &xop->xo_data;
4360     xo_humanize_save_t save;	/* Save values for humanizing logic */
4361 
4362     const char *leader = xo_xml_leader_len(xop, name, nlen);
4363 
4364     switch (xo_style(xop)) {
4365     case XO_STYLE_TEXT:
4366 	if (flags & XFF_ENCODE_ONLY)
4367 	    flags |= XFF_NO_OUTPUT;
4368 
4369 	save.xhs_offset = xbp->xb_curp - xbp->xb_bufp;
4370 	save.xhs_columns = xop->xo_columns;
4371 	save.xhs_anchor_columns = xop->xo_anchor_columns;
4372 
4373 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4374 
4375 	if (flags & XFF_HUMANIZE)
4376 	    xo_format_humanize(xop, xbp, &save, flags);
4377 	break;
4378 
4379     case XO_STYLE_HTML:
4380 	if (flags & XFF_ENCODE_ONLY)
4381 	    flags |= XFF_NO_OUTPUT;
4382 
4383 	xo_buf_append_div(xop, "data", flags, name, nlen, value, vlen,
4384 			  fmt, flen, encoding, elen);
4385 	break;
4386 
4387     case XO_STYLE_XML:
4388 	/*
4389 	 * Even though we're not making output, we still need to
4390 	 * let the formatting code handle the va_arg popping.
4391 	 */
4392 	if (flags & XFF_DISPLAY_ONLY) {
4393 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4394 	    break;
4395 	}
4396 
4397 	if (encoding) {
4398    	    fmt = encoding;
4399 	    flen = elen;
4400 	} else {
4401 	    char *enc  = alloca(flen + 1);
4402 	    memcpy(enc, fmt, flen);
4403 	    enc[flen] = '\0';
4404 	    fmt = xo_fix_encoding(xop, enc);
4405 	    flen = strlen(fmt);
4406 	}
4407 
4408 	if (nlen == 0) {
4409 	    static char missing[] = "missing-field-name";
4410 	    xo_failure(xop, "missing field name: %s", fmt);
4411 	    name = missing;
4412 	    nlen = sizeof(missing) - 1;
4413 	}
4414 
4415 	if (pretty)
4416 	    xo_buf_indent(xop, -1);
4417 	xo_data_append(xop, "<", 1);
4418         if (*leader)
4419             xo_data_append(xop, leader, 1);
4420 	xo_data_escape(xop, name, nlen);
4421 
4422 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
4423 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
4424 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
4425 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
4426 	}
4427 
4428 	/*
4429 	 * We indicate 'key' fields using the 'key' attribute.  While
4430 	 * this is really committing the crime of mixing meta-data with
4431 	 * data, it's often useful.  Especially when format meta-data is
4432 	 * difficult to come by.
4433 	 */
4434 	if ((flags & XFF_KEY) && XOF_ISSET(xop, XOF_KEYS)) {
4435 	    static char attr[] = " key=\"key\"";
4436 	    xo_data_append(xop, attr, sizeof(attr) - 1);
4437 	}
4438 
4439 	/*
4440 	 * Save the offset at which we'd place units.  See xo_format_units.
4441 	 */
4442 	if (XOF_ISSET(xop, XOF_UNITS)) {
4443 	    XOIF_SET(xop, XOIF_UNITS_PENDING);
4444 	    xop->xo_units_offset = xop->xo_data.xb_curp -xop->xo_data.xb_bufp;
4445 	}
4446 
4447 	xo_data_append(xop, ">", 1);
4448 
4449 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4450 
4451 	xo_data_append(xop, "</", 2);
4452         if (*leader)
4453             xo_data_append(xop, leader, 1);
4454 	xo_data_escape(xop, name, nlen);
4455 	xo_data_append(xop, ">", 1);
4456 	if (pretty)
4457 	    xo_data_append(xop, "\n", 1);
4458 	break;
4459 
4460     case XO_STYLE_JSON:
4461 	if (flags & XFF_DISPLAY_ONLY) {
4462 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4463 	    break;
4464 	}
4465 
4466 	if (encoding) {
4467 	    fmt = encoding;
4468 	    flen = elen;
4469 	} else {
4470 	    char *enc  = alloca(flen + 1);
4471 	    memcpy(enc, fmt, flen);
4472 	    enc[flen] = '\0';
4473 	    fmt = xo_fix_encoding(xop, enc);
4474 	    flen = strlen(fmt);
4475 	}
4476 
4477 	xo_stack_set_flags(xop);
4478 
4479 	int first = (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
4480 	    ? 0 : 1;
4481 
4482 	xo_format_prep(xop, flags);
4483 
4484 	if (flags & XFF_QUOTE)
4485 	    quote = 1;
4486 	else if (flags & XFF_NOQUOTE)
4487 	    quote = 0;
4488 	else if (vlen != 0)
4489 	    quote = 1;
4490 	else if (flen == 0) {
4491 	    quote = 0;
4492 	    fmt = "true";	/* JSON encodes empty tags as a boolean true */
4493 	    flen = 4;
4494 	} else if (xo_format_is_numeric(fmt, flen))
4495 	    quote = 0;
4496 	else
4497 	    quote = 1;
4498 
4499 	if (nlen == 0) {
4500 	    static char missing[] = "missing-field-name";
4501 	    xo_failure(xop, "missing field name: %s", fmt);
4502 	    name = missing;
4503 	    nlen = sizeof(missing) - 1;
4504 	}
4505 
4506 	if (flags & XFF_LEAF_LIST) {
4507 	    if (!first && pretty)
4508 		xo_data_append(xop, "\n", 1);
4509 	    if (pretty)
4510 		xo_buf_indent(xop, -1);
4511 	} else {
4512 	    if (pretty)
4513 		xo_buf_indent(xop, -1);
4514 	    xo_data_append(xop, "\"", 1);
4515 
4516 	    xbp = &xop->xo_data;
4517 	    ssize_t off = xbp->xb_curp - xbp->xb_bufp;
4518 
4519 	    xo_data_escape(xop, name, nlen);
4520 
4521 	    if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
4522 		ssize_t coff = xbp->xb_curp - xbp->xb_bufp;
4523 		for ( ; off < coff; off++)
4524 		    if (xbp->xb_bufp[off] == '-')
4525 			xbp->xb_bufp[off] = '_';
4526 	    }
4527 	    xo_data_append(xop, "\":", 2);
4528 	    if (pretty)
4529 	        xo_data_append(xop, " ", 1);
4530 	}
4531 
4532 	if (quote)
4533 	    xo_data_append(xop, "\"", 1);
4534 
4535 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4536 
4537 	if (quote)
4538 	    xo_data_append(xop, "\"", 1);
4539 	break;
4540 
4541     case XO_STYLE_SDPARAMS:
4542 	if (flags & XFF_DISPLAY_ONLY) {
4543 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4544 	    break;
4545 	}
4546 
4547 	if (encoding) {
4548 	    fmt = encoding;
4549 	    flen = elen;
4550 	} else {
4551 	    char *enc  = alloca(flen + 1);
4552 	    memcpy(enc, fmt, flen);
4553 	    enc[flen] = '\0';
4554 	    fmt = xo_fix_encoding(xop, enc);
4555 	    flen = strlen(fmt);
4556 	}
4557 
4558 	if (nlen == 0) {
4559 	    static char missing[] = "missing-field-name";
4560 	    xo_failure(xop, "missing field name: %s", fmt);
4561 	    name = missing;
4562 	    nlen = sizeof(missing) - 1;
4563 	}
4564 
4565 	xo_data_escape(xop, name, nlen);
4566 	xo_data_append(xop, "=\"", 2);
4567 
4568 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4569 
4570 	xo_data_append(xop, "\" ", 2);
4571 	break;
4572 
4573     case XO_STYLE_ENCODER:
4574 	if (flags & XFF_DISPLAY_ONLY) {
4575 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4576 	    break;
4577 	}
4578 
4579 	if (flags & XFF_QUOTE)
4580 	    quote = 1;
4581 	else if (flags & XFF_NOQUOTE)
4582 	    quote = 0;
4583 	else if (flen == 0) {
4584 	    quote = 0;
4585 	    fmt = "true";	/* JSON encodes empty tags as a boolean true */
4586 	    flen = 4;
4587 	} else if (strchr("diouxXDOUeEfFgGaAcCp", fmt[flen - 1]) == NULL)
4588 	    quote = 1;
4589 	else
4590 	    quote = 0;
4591 
4592 	if (encoding) {
4593 	    fmt = encoding;
4594 	    flen = elen;
4595 	} else {
4596 	    char *enc  = alloca(flen + 1);
4597 	    memcpy(enc, fmt, flen);
4598 	    enc[flen] = '\0';
4599 	    fmt = xo_fix_encoding(xop, enc);
4600 	    flen = strlen(fmt);
4601 	}
4602 
4603 	if (nlen == 0) {
4604 	    static char missing[] = "missing-field-name";
4605 	    xo_failure(xop, "missing field name: %s", fmt);
4606 	    name = missing;
4607 	    nlen = sizeof(missing) - 1;
4608 	}
4609 
4610 	ssize_t name_offset = xo_buf_offset(&xop->xo_data);
4611 	xo_data_append(xop, name, nlen);
4612 	xo_data_append(xop, "", 1);
4613 
4614 	ssize_t value_offset = xo_buf_offset(&xop->xo_data);
4615 
4616 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4617 
4618 	xo_data_append(xop, "", 1);
4619 
4620 	xo_encoder_handle(xop, quote ? XO_OP_STRING : XO_OP_CONTENT,
4621 			  xo_buf_data(&xop->xo_data, name_offset),
4622 			  xo_buf_data(&xop->xo_data, value_offset), flags);
4623 	xo_buf_reset(&xop->xo_data);
4624 	break;
4625     }
4626 }
4627 
4628 static void
xo_set_gettext_domain(xo_handle_t * xop,xo_field_info_t * xfip,const char * str,ssize_t len)4629 xo_set_gettext_domain (xo_handle_t *xop, xo_field_info_t *xfip,
4630 		       const char *str, ssize_t len)
4631 {
4632     const char *fmt = xfip->xfi_format;
4633     ssize_t flen = xfip->xfi_flen;
4634 
4635     /* Start by discarding previous domain */
4636     if (xop->xo_gt_domain) {
4637 	xo_free(xop->xo_gt_domain);
4638 	xop->xo_gt_domain = NULL;
4639     }
4640 
4641     /* An empty {G:} means no domainname */
4642     if (len == 0 && flen == 0)
4643 	return;
4644 
4645     ssize_t start_offset = -1;
4646     if (len == 0 && flen != 0) {
4647 	/* Need to do format the data to get the domainname from args */
4648 	start_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4649 	xo_do_format_field(xop, NULL, fmt, flen, 0);
4650 
4651 	ssize_t end_offset = xop->xo_data.xb_curp - xop->xo_data.xb_bufp;
4652 	len = end_offset - start_offset;
4653 	str = xop->xo_data.xb_bufp + start_offset;
4654     }
4655 
4656     xop->xo_gt_domain = xo_strndup(str, len);
4657 
4658     /* Reset the current buffer point to avoid emitting the name as output */
4659     if (start_offset >= 0)
4660 	xop->xo_data.xb_curp = xop->xo_data.xb_bufp + start_offset;
4661 }
4662 
4663 static void
xo_format_content(xo_handle_t * xop,const char * class_name,const char * tag_name,const char * value,ssize_t vlen,const char * fmt,ssize_t flen,xo_xff_flags_t flags)4664 xo_format_content (xo_handle_t *xop, const char *class_name,
4665 		   const char *tag_name,
4666 		   const char *value, ssize_t vlen,
4667 		   const char *fmt, ssize_t flen,
4668 		   xo_xff_flags_t flags)
4669 {
4670     switch (xo_style(xop)) {
4671     case XO_STYLE_TEXT:
4672 	xo_simple_field(xop, FALSE, value, vlen, fmt, flen, flags);
4673 	break;
4674 
4675     case XO_STYLE_HTML:
4676 	xo_buf_append_div(xop, class_name, flags, NULL, 0,
4677 			  value, vlen, fmt, flen, NULL, 0);
4678 	break;
4679 
4680     case XO_STYLE_XML:
4681     case XO_STYLE_JSON:
4682     case XO_STYLE_SDPARAMS:
4683 	if (tag_name) {
4684 	    xo_open_container_h(xop, tag_name);
4685 	    xo_format_value(xop, "message", 7, value, vlen,
4686 			    fmt, flen, NULL, 0, flags);
4687 	    xo_close_container_h(xop, tag_name);
4688 
4689 	} else {
4690 	    /*
4691 	     * Even though we don't care about labels, we need to do
4692 	     * enough parsing work to skip over the right bits of xo_vap.
4693 	     */
4694 	    xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4695 	}
4696 	break;
4697 
4698     case XO_STYLE_ENCODER:
4699 	xo_simple_field(xop, TRUE, value, vlen, fmt, flen, flags);
4700 	break;
4701     }
4702 }
4703 
4704 static const char *xo_color_names[] = {
4705     "default",	/* XO_COL_DEFAULT */
4706     "black",	/* XO_COL_BLACK */
4707     "red",	/* XO_CLOR_RED */
4708     "green",	/* XO_COL_GREEN */
4709     "yellow",	/* XO_COL_YELLOW */
4710     "blue",	/* XO_COL_BLUE */
4711     "magenta",	/* XO_COL_MAGENTA */
4712     "cyan",	/* XO_COL_CYAN */
4713     "white",	/* XO_COL_WHITE */
4714     NULL
4715 };
4716 
4717 static int
xo_color_find(const char * str)4718 xo_color_find (const char *str)
4719 {
4720     int i;
4721 
4722     for (i = 0; xo_color_names[i]; i++) {
4723 	if (xo_streq(xo_color_names[i], str))
4724 	    return i;
4725     }
4726 
4727     return -1;
4728 }
4729 
4730 static const char *xo_effect_names[] = {
4731     "reset",			/* XO_EFF_RESET */
4732     "normal",			/* XO_EFF_NORMAL */
4733     "bold",			/* XO_EFF_BOLD */
4734     "underline",		/* XO_EFF_UNDERLINE */
4735     "inverse",			/* XO_EFF_INVERSE */
4736     NULL
4737 };
4738 
4739 static const char *xo_effect_on_codes[] = {
4740     "0",			/* XO_EFF_RESET */
4741     "0",			/* XO_EFF_NORMAL */
4742     "1",			/* XO_EFF_BOLD */
4743     "4",			/* XO_EFF_UNDERLINE */
4744     "7",			/* XO_EFF_INVERSE */
4745     NULL
4746 };
4747 
4748 #if 0
4749 /*
4750  * See comment below re: joy of terminal standards.  These can
4751  * be use by just adding:
4752  * +	if (newp->xoc_effects & bit)
4753  *	    code = xo_effect_on_codes[i];
4754  * +	else
4755  * +	    code = xo_effect_off_codes[i];
4756  * in xo_color_handle_text.
4757  */
4758 static const char *xo_effect_off_codes[] = {
4759     "0",			/* XO_EFF_RESET */
4760     "0",			/* XO_EFF_NORMAL */
4761     "21",			/* XO_EFF_BOLD */
4762     "24",			/* XO_EFF_UNDERLINE */
4763     "27",			/* XO_EFF_INVERSE */
4764     NULL
4765 };
4766 #endif /* 0 */
4767 
4768 static int
xo_effect_find(const char * str)4769 xo_effect_find (const char *str)
4770 {
4771     int i;
4772 
4773     for (i = 0; xo_effect_names[i]; i++) {
4774 	if (xo_streq(xo_effect_names[i], str))
4775 	    return i;
4776     }
4777 
4778     return -1;
4779 }
4780 
4781 static void
xo_colors_parse(xo_handle_t * xop,xo_colors_t * xocp,char * str)4782 xo_colors_parse (xo_handle_t *xop, xo_colors_t *xocp, char *str)
4783 {
4784     if (xo_text_only())
4785 	return;
4786 
4787     char *cp, *ep, *np, *xp;
4788     ssize_t len = strlen(str);
4789     int rc;
4790 
4791     /*
4792      * Possible tokens: colors, bg-colors, effects, no-effects, "reset".
4793      */
4794     for (cp = str, ep = cp + len - 1; cp && cp < ep; cp = np) {
4795 	/* Trim leading whitespace */
4796 	while (isspace((int) *cp))
4797 	    cp += 1;
4798 
4799 	np = strchr(cp, ',');
4800 	if (np)
4801 	    *np++ = '\0';
4802 
4803 	/* Trim trailing whitespace */
4804 	xp = cp + strlen(cp) - 1;
4805 	while (isspace(*xp) && xp > cp)
4806 	    *xp-- = '\0';
4807 
4808 	if (cp[0] == 'f' && cp[1] == 'g' && cp[2] == '-') {
4809 	    rc = xo_color_find(cp + 3);
4810 	    if (rc < 0)
4811 		goto unknown;
4812 
4813 	    xocp->xoc_col_fg = rc;
4814 
4815 	} else if (cp[0] == 'b' && cp[1] == 'g' && cp[2] == '-') {
4816 	    rc = xo_color_find(cp + 3);
4817 	    if (rc < 0)
4818 		goto unknown;
4819 	    xocp->xoc_col_bg = rc;
4820 
4821 	} else if (cp[0] == 'n' && cp[1] == 'o' && cp[2] == '-') {
4822 	    rc = xo_effect_find(cp + 3);
4823 	    if (rc < 0)
4824 		goto unknown;
4825 	    xocp->xoc_effects &= ~(1 << rc);
4826 
4827 	} else {
4828 	    rc = xo_effect_find(cp);
4829 	    if (rc < 0)
4830 		goto unknown;
4831 	    xocp->xoc_effects |= 1 << rc;
4832 
4833 	    switch (1 << rc) {
4834 	    case XO_EFF_RESET:
4835 		xocp->xoc_col_fg = xocp->xoc_col_bg = 0;
4836 		/* Note: not "|=" since we want to wipe out the old value */
4837 		xocp->xoc_effects = XO_EFF_RESET;
4838 		break;
4839 
4840 	    case XO_EFF_NORMAL:
4841 		xocp->xoc_effects &= ~(XO_EFF_BOLD | XO_EFF_UNDERLINE
4842 				      | XO_EFF_INVERSE | XO_EFF_NORMAL);
4843 		break;
4844 	    }
4845 	}
4846 	continue;
4847 
4848     unknown:
4849 	if (XOF_ISSET(xop, XOF_WARN))
4850 	    xo_failure(xop, "unknown color/effect string detected: '%s'", cp);
4851     }
4852 }
4853 
4854 static inline int
xo_colors_enabled(xo_handle_t * xop UNUSED)4855 xo_colors_enabled (xo_handle_t *xop UNUSED)
4856 {
4857 #ifdef LIBXO_TEXT_ONLY
4858     return 0;
4859 #else /* LIBXO_TEXT_ONLY */
4860     return XOF_ISSET(xop, XOF_COLOR);
4861 #endif /* LIBXO_TEXT_ONLY */
4862 }
4863 
4864 /*
4865  * If the color map is in use (--libxo colors=xxxx), then update
4866  * the incoming foreground and background colors from the map.
4867  */
4868 static void
xo_colors_update(xo_handle_t * xop UNUSED,xo_colors_t * newp UNUSED)4869 xo_colors_update (xo_handle_t *xop UNUSED, xo_colors_t *newp UNUSED)
4870 {
4871 #ifndef LIBXO_TEXT_ONLY
4872     xo_color_t fg = newp->xoc_col_fg;
4873     if (XOF_ISSET(xop, XOF_COLOR_MAP) && fg < XO_NUM_COLORS)
4874 	fg = xop->xo_color_map_fg[fg]; /* Fetch from color map */
4875     newp->xoc_col_fg = fg;
4876 
4877     xo_color_t bg = newp->xoc_col_bg;
4878     if (XOF_ISSET(xop, XOF_COLOR_MAP) && bg < XO_NUM_COLORS)
4879 	bg = xop->xo_color_map_bg[bg]; /* Fetch from color map */
4880     newp->xoc_col_bg = bg;
4881 #endif /* LIBXO_TEXT_ONLY */
4882 }
4883 
4884 static void
xo_colors_handle_text(xo_handle_t * xop,xo_colors_t * newp)4885 xo_colors_handle_text (xo_handle_t *xop, xo_colors_t *newp)
4886 {
4887     char buf[BUFSIZ];
4888     char *cp = buf, *ep = buf + sizeof(buf);
4889     unsigned i, bit;
4890     xo_colors_t *oldp = &xop->xo_colors;
4891     const char *code = NULL;
4892 
4893     /*
4894      * Start the buffer with an escape.  We don't want to add the '['
4895      * now, since we let xo_effect_text_add unconditionally add the ';'.
4896      * We'll replace the first ';' with a '[' when we're done.
4897      */
4898     *cp++ = 0x1b;		/* Escape */
4899 
4900     /*
4901      * Terminals were designed back in the age before "certainty" was
4902      * invented, when standards were more what you'd call "guidelines"
4903      * than actual rules.  Anyway we can't depend on them to operate
4904      * correctly.  So when display attributes are changed, we punt,
4905      * reseting them all and turning back on the ones we want to keep.
4906      * Longer, but should be completely reliable.  Savvy?
4907      */
4908     if (oldp->xoc_effects != (newp->xoc_effects & oldp->xoc_effects)) {
4909 	newp->xoc_effects |= XO_EFF_RESET;
4910 	oldp->xoc_effects = 0;
4911     }
4912 
4913     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4914 	if ((newp->xoc_effects & bit) == (oldp->xoc_effects & bit))
4915 	    continue;
4916 
4917 	code = xo_effect_on_codes[i];
4918 
4919 	cp += snprintf(cp, ep - cp, ";%s", code);
4920 	if (cp >= ep)
4921 	    return;		/* Should not occur */
4922 
4923 	if (bit == XO_EFF_RESET) {
4924 	    /* Mark up the old value so we can detect current values as new */
4925 	    oldp->xoc_effects = 0;
4926 	    oldp->xoc_col_fg = oldp->xoc_col_bg = XO_COL_DEFAULT;
4927 	}
4928     }
4929 
4930     xo_color_t fg = newp->xoc_col_fg;
4931     if (fg != oldp->xoc_col_fg) {
4932 	cp += snprintf(cp, ep - cp, ";3%u",
4933 		       (fg != XO_COL_DEFAULT) ? fg - 1 : 9);
4934     }
4935 
4936     xo_color_t bg = newp->xoc_col_bg;
4937     if (bg != oldp->xoc_col_bg) {
4938 	cp += snprintf(cp, ep - cp, ";4%u",
4939 		       (bg != XO_COL_DEFAULT) ? bg - 1 : 9);
4940     }
4941 
4942     if (cp - buf != 1 && cp < ep - 3) {
4943 	buf[1] = '[';		/* Overwrite leading ';' */
4944 	*cp++ = 'm';
4945 	*cp = '\0';
4946 	xo_buf_append(&xop->xo_data, buf, cp - buf);
4947     }
4948 }
4949 
4950 static void
xo_colors_handle_html(xo_handle_t * xop,xo_colors_t * newp)4951 xo_colors_handle_html (xo_handle_t *xop, xo_colors_t *newp)
4952 {
4953     xo_colors_t *oldp = &xop->xo_colors;
4954 
4955     /*
4956      * HTML colors are mostly trivial: fill in xo_color_buf with
4957      * a set of class tags representing the colors and effects.
4958      */
4959 
4960     /* If nothing changed, then do nothing */
4961     if (oldp->xoc_effects == newp->xoc_effects
4962 	&& oldp->xoc_col_fg == newp->xoc_col_fg
4963 	&& oldp->xoc_col_bg == newp->xoc_col_bg)
4964 	return;
4965 
4966     unsigned i, bit;
4967     xo_buffer_t *xbp = &xop->xo_color_buf;
4968 
4969     xo_buf_reset(xbp);		/* We rebuild content after each change */
4970 
4971     for (i = 0, bit = 1; xo_effect_names[i]; i++, bit <<= 1) {
4972 	if (!(newp->xoc_effects & bit))
4973 	    continue;
4974 
4975 	xo_buf_append_str(xbp, " effect-");
4976 	xo_buf_append_str(xbp, xo_effect_names[i]);
4977     }
4978 
4979     const char *fg = NULL;
4980     const char *bg = NULL;
4981 
4982     if (newp->xoc_col_fg != XO_COL_DEFAULT)
4983 	fg = xo_color_names[newp->xoc_col_fg];
4984     if (newp->xoc_col_bg != XO_COL_DEFAULT)
4985 	bg = xo_color_names[newp->xoc_col_bg];
4986 
4987     if (newp->xoc_effects & XO_EFF_INVERSE) {
4988 	const char *tmp = fg;
4989 	fg = bg;
4990 	bg = tmp;
4991 	if (fg == NULL)
4992 	    fg = "inverse";
4993 	if (bg == NULL)
4994 	    bg = "inverse";
4995 
4996     }
4997 
4998     if (fg) {
4999 	xo_buf_append_str(xbp, " color-fg-");
5000 	xo_buf_append_str(xbp, fg);
5001     }
5002 
5003     if (bg) {
5004 	xo_buf_append_str(xbp, " color-bg-");
5005 	xo_buf_append_str(xbp, bg);
5006     }
5007 }
5008 
5009 static void
xo_format_colors(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)5010 xo_format_colors (xo_handle_t *xop, xo_field_info_t *xfip,
5011 		  const char *value, ssize_t vlen)
5012 {
5013     const char *fmt = xfip->xfi_format;
5014     ssize_t flen = xfip->xfi_flen;
5015 
5016     xo_buffer_t xb;
5017 
5018     /* If the string is static and we've in an encoding style, bail */
5019     if (vlen != 0 && xo_style_is_encoding(xop))
5020 	return;
5021 
5022     xo_buf_init(&xb);
5023 
5024     if (vlen)
5025 	xo_buf_append(&xb, value, vlen);
5026     else if (flen)
5027 	xo_do_format_field(xop, &xb, fmt, flen, 0);
5028     else
5029 	xo_buf_append(&xb, "reset", 6); /* Default if empty */
5030 
5031     if (xo_colors_enabled(xop)) {
5032 	switch (xo_style(xop)) {
5033 	case XO_STYLE_TEXT:
5034 	case XO_STYLE_HTML:
5035 	    xo_buf_append(&xb, "", 1);
5036 
5037 	    xo_colors_t xoc = xop->xo_colors;
5038 	    xo_colors_parse(xop, &xoc, xb.xb_bufp);
5039 	    xo_colors_update(xop, &xoc);
5040 
5041 	    if (xo_style(xop) == XO_STYLE_TEXT) {
5042 		/*
5043 		 * Text mode means emitting the colors as ANSI character
5044 		 * codes.  This will allow people who like colors to have
5045 		 * colors.  The issue is, of course conflicting with the
5046 		 * user's perfectly reasonable color scheme.  Which leads
5047 		 * to the hell of LSCOLORS, where even app need to have
5048 		 * customization hooks for adjusting colors.  Instead we
5049 		 * provide a simpler-but-still-annoying answer where one
5050 		 * can map colors to other colors.
5051 		 */
5052 		xo_colors_handle_text(xop, &xoc);
5053 		xoc.xoc_effects &= ~XO_EFF_RESET; /* After handling it */
5054 
5055 	    } else {
5056 		/*
5057 		 * HTML output is wrapped in divs, so the color information
5058 		 * must appear in every div until cleared.  Most pathetic.
5059 		 * Most unavoidable.
5060 		 */
5061 		xoc.xoc_effects &= ~XO_EFF_RESET; /* Before handling effects */
5062 		xo_colors_handle_html(xop, &xoc);
5063 	    }
5064 
5065 	    xop->xo_colors = xoc;
5066 	    break;
5067 
5068 	case XO_STYLE_XML:
5069 	case XO_STYLE_JSON:
5070 	case XO_STYLE_SDPARAMS:
5071 	case XO_STYLE_ENCODER:
5072 	    /*
5073 	     * Nothing to do; we did all that work just to clear the stack of
5074 	     * formatting arguments.
5075 	     */
5076 	    break;
5077 	}
5078     }
5079 
5080     xo_buf_cleanup(&xb);
5081 }
5082 
5083 static void
xo_format_units(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)5084 xo_format_units (xo_handle_t *xop, xo_field_info_t *xfip,
5085 		 const char *value, ssize_t vlen)
5086 {
5087     const char *fmt = xfip->xfi_format;
5088     ssize_t flen = xfip->xfi_flen;
5089     xo_xff_flags_t flags = xfip->xfi_flags;
5090 
5091     static char units_start_xml[] = " units=\"";
5092     static char units_start_html[] = " data-units=\"";
5093 
5094     if (!XOIF_ISSET(xop, XOIF_UNITS_PENDING)) {
5095 	xo_format_content(xop, "units", NULL, value, vlen, fmt, flen, flags);
5096 	return;
5097     }
5098 
5099     xo_buffer_t *xbp = &xop->xo_data;
5100     ssize_t start = xop->xo_units_offset;
5101     ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
5102 
5103     if (xo_style(xop) == XO_STYLE_XML)
5104 	xo_buf_append(xbp, units_start_xml, sizeof(units_start_xml) - 1);
5105     else if (xo_style(xop) == XO_STYLE_HTML)
5106 	xo_buf_append(xbp, units_start_html, sizeof(units_start_html) - 1);
5107     else
5108 	return;
5109 
5110     if (vlen)
5111 	xo_data_escape(xop, value, vlen);
5112     else
5113 	xo_do_format_field(xop, NULL, fmt, flen, flags);
5114 
5115     xo_buf_append(xbp, "\"", 1);
5116 
5117     ssize_t now = xbp->xb_curp - xbp->xb_bufp;
5118     ssize_t delta = now - stop;
5119     if (delta <= 0) {		/* Strange; no output to move */
5120 	xbp->xb_curp = xbp->xb_bufp + stop; /* Reset buffer to prior state */
5121 	return;
5122     }
5123 
5124     /*
5125      * Now we're in it alright.  We've need to insert the unit value
5126      * we just created into the right spot.  We make a local copy,
5127      * move it and then insert our copy.  We know there's room in the
5128      * buffer, since we're just moving this around.
5129      */
5130     char *buf = alloca(delta);
5131 
5132     memcpy(buf, xbp->xb_bufp + stop, delta);
5133     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
5134     memmove(xbp->xb_bufp + start, buf, delta);
5135 }
5136 
5137 static ssize_t
xo_find_width(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)5138 xo_find_width (xo_handle_t *xop, xo_field_info_t *xfip,
5139 	       const char *value, ssize_t vlen)
5140 {
5141     const char *fmt = xfip->xfi_format;
5142     ssize_t flen = xfip->xfi_flen;
5143 
5144     long width = 0;
5145     char *bp;
5146     char *cp;
5147 
5148     if (vlen) {
5149 	bp = alloca(vlen + 1);	/* Make local NUL-terminated copy of value */
5150 	memcpy(bp, value, vlen);
5151 	bp[vlen] = '\0';
5152 
5153 	width = strtol(bp, &cp, 0);
5154 	if (width == LONG_MIN || width == LONG_MAX || bp == cp || *cp != '\0') {
5155 	    width = 0;
5156 	    xo_failure(xop, "invalid width for anchor: '%s'", bp);
5157 	}
5158     } else if (flen) {
5159 	/*
5160 	 * We really expect the format for width to be "{:/%d}" or
5161 	 * "{:/%u}", so if that's the case, we just grab our width off
5162 	 * the argument list.  But we need to avoid optimized logic if
5163 	 * there's a custom formatter.
5164 	 */
5165 	if (xop->xo_formatter == NULL && flen == 2
5166 	        && strncmp("%d", fmt, flen) == 0) {
5167 	    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
5168 		width = va_arg(xop->xo_vap, int);
5169 	} else if (xop->xo_formatter == NULL && flen == 2
5170 		   && strncmp("%u", fmt, flen) == 0) {
5171 	    if (!XOF_ISSET(xop, XOF_NO_VA_ARG))
5172 		width = va_arg(xop->xo_vap, unsigned);
5173 	} else {
5174 	    /*
5175 	     * So we have a format and it's not a simple one like
5176 	     * "{:/%d}".  That means we need to format the field,
5177 	     * extract the value from the formatted output, and then
5178 	     * discard that output.
5179 	     */
5180 	    int anchor_was_set = FALSE;
5181 	    xo_buffer_t *xbp = &xop->xo_data;
5182 	    ssize_t start_offset = xo_buf_offset(xbp);
5183 	    bp = xo_buf_cur(xbp);	/* Save start of the string */
5184 	    cp = NULL;
5185 
5186 	    if (XOIF_ISSET(xop, XOIF_ANCHOR)) {
5187 		XOIF_CLEAR(xop, XOIF_ANCHOR);
5188 		anchor_was_set = TRUE;
5189 	    }
5190 
5191 	    ssize_t rc = xo_do_format_field(xop, xbp, fmt, flen, 0);
5192 	    if (rc >= 0) {
5193 		xo_buf_append(xbp, "", 1); /* Append a NUL */
5194 
5195 		width = strtol(bp, &cp, 0);
5196 		if (width == LONG_MIN || width == LONG_MAX
5197 		        || bp == cp || *cp != '\0') {
5198 		    width = 0;
5199 		    xo_failure(xop, "invalid width for anchor: '%s'", bp);
5200 		}
5201 	    }
5202 
5203 	    /* Reset the cur pointer to where we found it */
5204 	    xbp->xb_curp = xbp->xb_bufp + start_offset;
5205 	    if (anchor_was_set)
5206 		XOIF_SET(xop, XOIF_ANCHOR);
5207 	}
5208     }
5209 
5210     return width;
5211 }
5212 
5213 static void
xo_anchor_clear(xo_handle_t * xop)5214 xo_anchor_clear (xo_handle_t *xop)
5215 {
5216     XOIF_CLEAR(xop, XOIF_ANCHOR);
5217     xop->xo_anchor_offset = 0;
5218     xop->xo_anchor_columns = 0;
5219     xop->xo_anchor_min_width = 0;
5220 }
5221 
5222 /*
5223  * An anchor is a marker used to delay field width implications.
5224  * Imagine the format string "{[:10}{min:%d}/{cur:%d}/{max:%d}{:]}".
5225  * We are looking for output like "     1/4/5"
5226  *
5227  * To make this work, we record the anchor and then return to
5228  * format it when the end anchor tag is seen.
5229  */
5230 static void
xo_anchor_start(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)5231 xo_anchor_start (xo_handle_t *xop, xo_field_info_t *xfip,
5232 		 const char *value, ssize_t vlen)
5233 {
5234     if (XOIF_ISSET(xop, XOIF_ANCHOR))
5235 	xo_failure(xop, "the anchor already recording is discarded");
5236 
5237     XOIF_SET(xop, XOIF_ANCHOR);
5238     xo_buffer_t *xbp = &xop->xo_data;
5239     xop->xo_anchor_offset = xbp->xb_curp - xbp->xb_bufp;
5240     xop->xo_anchor_columns = 0;
5241 
5242     /*
5243      * Now we find the width, if possible.  If it's not there,
5244      * we'll get it on the end anchor.
5245      */
5246     xop->xo_anchor_min_width = xo_find_width(xop, xfip, value, vlen);
5247 }
5248 
5249 static void
xo_anchor_stop(xo_handle_t * xop,xo_field_info_t * xfip,const char * value,ssize_t vlen)5250 xo_anchor_stop (xo_handle_t *xop, xo_field_info_t *xfip,
5251 		 const char *value, ssize_t vlen)
5252 {
5253     if (!XOIF_ISSET(xop, XOIF_ANCHOR)) {
5254 	xo_failure(xop, "no start anchor");
5255 	return;
5256     }
5257 
5258     XOIF_CLEAR(xop, XOIF_UNITS_PENDING);
5259 
5260     ssize_t width = xo_find_width(xop, xfip, value, vlen);
5261     if (width == 0)
5262 	width = xop->xo_anchor_min_width;
5263 
5264     if (width == 0)		/* No width given; nothing to do */
5265 	goto done;
5266 
5267     xo_buffer_t *xbp = &xop->xo_data;
5268     ssize_t start = xop->xo_anchor_offset;
5269     ssize_t stop = xbp->xb_curp - xbp->xb_bufp;
5270     ssize_t abswidth = (width > 0) ? width : -width;
5271     ssize_t blen = abswidth - xop->xo_anchor_columns;
5272 
5273     if (blen <= 0)		/* Already over width */
5274 	goto done;
5275 
5276     if (abswidth > XO_MAX_ANCHOR_WIDTH) {
5277 	xo_failure(xop, "width over %u are not supported",
5278 		   XO_MAX_ANCHOR_WIDTH);
5279 	goto done;
5280     }
5281 
5282     /* Make a suitable padding field and emit it */
5283     char *buf = alloca(blen);
5284     memset(buf, ' ', blen);
5285     xo_format_content(xop, "padding", NULL, buf, blen, NULL, 0, 0);
5286 
5287     if (width < 0)		/* Already left justified */
5288 	goto done;
5289 
5290     ssize_t now = xbp->xb_curp - xbp->xb_bufp;
5291     ssize_t delta = now - stop;
5292     if (delta <= 0)		/* Strange; no output to move */
5293 	goto done;
5294 
5295     /*
5296      * Now we're in it alright.  We've need to insert the padding data
5297      * we just created (which might be an HTML <div> or text) before
5298      * the formatted data.  We make a local copy, move it and then
5299      * insert our copy.  We know there's room in the buffer, since
5300      * we're just moving this around.
5301      */
5302     if (delta > blen)
5303 	buf = alloca(delta);	/* Expand buffer if needed */
5304 
5305     memcpy(buf, xbp->xb_bufp + stop, delta);
5306     memmove(xbp->xb_bufp + start + delta, xbp->xb_bufp + start, stop - start);
5307     memmove(xbp->xb_bufp + start, buf, delta);
5308 
5309  done:
5310     xo_anchor_clear(xop);
5311 }
5312 
5313 static const char *
xo_class_name(int ftype)5314 xo_class_name (int ftype)
5315 {
5316     switch (ftype) {
5317     case 'D': return "decoration";
5318     case 'E': return "error";
5319     case 'L': return "label";
5320     case 'N': return "note";
5321     case 'P': return "padding";
5322     case 'W': return "warning";
5323     }
5324 
5325     return NULL;
5326 }
5327 
5328 static const char *
xo_tag_name(int ftype)5329 xo_tag_name (int ftype)
5330 {
5331     switch (ftype) {
5332     case 'E': return "__error";
5333     case 'W': return "__warning";
5334     }
5335 
5336     return NULL;
5337 }
5338 
5339 static int
xo_role_wants_default_format(int ftype)5340 xo_role_wants_default_format (int ftype)
5341 {
5342     switch (ftype) {
5343 	/* These roles can be completely empty and/or without formatting */
5344     case 'C':
5345     case 'G':
5346     case '[':
5347     case ']':
5348 	return 0;
5349     }
5350 
5351     return 1;
5352 }
5353 
5354 static xo_mapping_t xo_role_names[] = {
5355     { 'C', "color" },
5356     { 'D', "decoration" },
5357     { 'E', "error" },
5358     { 'L', "label" },
5359     { 'N', "note" },
5360     { 'P', "padding" },
5361     { 'T', "title" },
5362     { 'U', "units" },
5363     { 'V', "value" },
5364     { 'W', "warning" },
5365     { '[', "start-anchor" },
5366     { ']', "stop-anchor" },
5367     { 0, NULL }
5368 };
5369 
5370 #define XO_ROLE_EBRACE	'{'	/* Escaped braces */
5371 #define XO_ROLE_TEXT	'+'
5372 #define XO_ROLE_NEWLINE	'\n'
5373 
5374 static xo_mapping_t xo_modifier_names[] = {
5375     { XFF_ARGUMENT, "argument" },
5376     { XFF_COLON, "colon" },
5377     { XFF_COMMA, "comma" },
5378     { XFF_DISPLAY_ONLY, "display" },
5379     { XFF_ENCODE_ONLY, "encoding" },
5380     { XFF_GT_FIELD, "gettext" },
5381     { XFF_HUMANIZE, "humanize" },
5382     { XFF_HUMANIZE, "hn" },
5383     { XFF_HN_SPACE, "hn-space" },
5384     { XFF_HN_DECIMAL, "hn-decimal" },
5385     { XFF_HN_1000, "hn-1000" },
5386     { XFF_KEY, "key" },
5387     { XFF_LEAF_LIST, "leaf-list" },
5388     { XFF_LEAF_LIST, "list" },
5389     { XFF_NOQUOTE, "no-quotes" },
5390     { XFF_NOQUOTE, "no-quote" },
5391     { XFF_GT_PLURAL, "plural" },
5392     { XFF_QUOTE, "quotes" },
5393     { XFF_QUOTE, "quote" },
5394     { XFF_TRIM_WS, "trim" },
5395     { XFF_WS, "white" },
5396     { 0, NULL }
5397 };
5398 
5399 #ifdef NOT_NEEDED_YET
5400 static xo_mapping_t xo_modifier_short_names[] = {
5401     { XFF_COLON, "c" },
5402     { XFF_DISPLAY_ONLY, "d" },
5403     { XFF_ENCODE_ONLY, "e" },
5404     { XFF_GT_FIELD, "g" },
5405     { XFF_HUMANIZE, "h" },
5406     { XFF_KEY, "k" },
5407     { XFF_LEAF_LIST, "l" },
5408     { XFF_NOQUOTE, "n" },
5409     { XFF_GT_PLURAL, "p" },
5410     { XFF_QUOTE, "q" },
5411     { XFF_TRIM_WS, "t" },
5412     { XFF_WS, "w" },
5413     { 0, NULL }
5414 };
5415 #endif /* NOT_NEEDED_YET */
5416 
5417 static int
xo_count_fields(xo_handle_t * xop UNUSED,const char * fmt)5418 xo_count_fields (xo_handle_t *xop UNUSED, const char *fmt)
5419 {
5420     int rc = 1;
5421     const char *cp;
5422 
5423     for (cp = fmt; *cp; cp++)
5424 	if (*cp == '{' || *cp == '\n')
5425 	    rc += 1;
5426 
5427     return rc * 2 + 1;
5428 }
5429 
5430 /*
5431  * The field format is:
5432  *  '{' modifiers ':' content [ '/' print-fmt [ '/' encode-fmt ]] '}'
5433  * Roles are optional and include the following field types:
5434  *   'D': decoration; something non-text and non-data (colons, commmas)
5435  *   'E': error message
5436  *   'G': gettext() the entire string; optional domainname as content
5437  *   'L': label; text preceding data
5438  *   'N': note; text following data
5439  *   'P': padding; whitespace
5440  *   'T': Title, where 'content' is a column title
5441  *   'U': Units, where 'content' is the unit label
5442  *   'V': value, where 'content' is the name of the field (the default)
5443  *   'W': warning message
5444  *   '[': start a section of anchored text
5445  *   ']': end a section of anchored text
5446  * The following modifiers are also supported:
5447  *   'a': content is provided via argument (const char *), not descriptor
5448  *   'c': flag: emit a colon after the label
5449  *   'd': field is only emitted for display styles (text and html)
5450  *   'e': field is only emitted for encoding styles (xml and json)
5451  *   'g': gettext() the field
5452  *   'h': humanize a numeric value (only for display styles)
5453  *   'k': this field is a key, suitable for XPath predicates
5454  *   'l': a leaf-list, a simple list of values
5455  *   'n': no quotes around this field
5456  *   'p': the field has plural gettext semantics (ngettext)
5457  *   'q': add quotes around this field
5458  *   't': trim whitespace around the value
5459  *   'w': emit a blank after the label
5460  * The print-fmt and encode-fmt strings is the printf-style formating
5461  * for this data.  JSON and XML will use the encoding-fmt, if present.
5462  * If the encode-fmt is not provided, it defaults to the print-fmt.
5463  * If the print-fmt is not provided, it defaults to 's'.
5464  */
5465 static const char *
xo_parse_roles(xo_handle_t * xop,const char * fmt,const char * basep,xo_field_info_t * xfip)5466 xo_parse_roles (xo_handle_t *xop, const char *fmt,
5467 		const char *basep, xo_field_info_t *xfip)
5468 {
5469     const char *sp;
5470     unsigned ftype = 0;
5471     xo_xff_flags_t flags = 0;
5472     uint8_t fnum = 0;
5473 
5474     for (sp = basep; sp && *sp; sp++) {
5475 	if (*sp == ':' || *sp == '/' || *sp == '}')
5476 	    break;
5477 
5478 	if (*sp == '\\') {
5479 	    if (sp[1] == '\0') {
5480 		xo_failure(xop, "backslash at the end of string");
5481 		return NULL;
5482 	    }
5483 
5484 	    /* Anything backslashed is ignored */
5485 	    sp += 1;
5486 	    continue;
5487 	}
5488 
5489 	if (*sp == ',') {
5490 	    const char *np;
5491 	    for (np = ++sp; *np; np++)
5492 		if (*np == ':' || *np == '/' || *np == '}' || *np == ',')
5493 		    break;
5494 
5495 	    ssize_t slen = np - sp;
5496 	    if (slen > 0) {
5497 		xo_xff_flags_t value;
5498 
5499 		value = xo_name_lookup(xo_role_names, sp, slen);
5500 		if (value)
5501 		    ftype = value;
5502 		else {
5503 		    value = xo_name_lookup(xo_modifier_names, sp, slen);
5504 		    if (value)
5505 			flags |= value;
5506 		    else
5507 			xo_failure(xop, "unknown keyword ignored: '%.*s'",
5508 				   slen, sp);
5509 		}
5510 	    }
5511 
5512 	    sp = np - 1;
5513 	    continue;
5514 	}
5515 
5516 	switch (*sp) {
5517 	case 'C':
5518 	case 'D':
5519 	case 'E':
5520 	case 'G':
5521 	case 'L':
5522 	case 'N':
5523 	case 'P':
5524 	case 'T':
5525 	case 'U':
5526 	case 'V':
5527 	case 'W':
5528 	case '[':
5529 	case ']':
5530 	    if (ftype != 0) {
5531 		xo_failure(xop, "field descriptor uses multiple types: '%s'",
5532 			   xo_printable(fmt));
5533 		return NULL;
5534 	    }
5535 	    ftype = *sp;
5536 	    break;
5537 
5538 	case '0':
5539 	case '1':
5540 	case '2':
5541 	case '3':
5542 	case '4':
5543 	case '5':
5544 	case '6':
5545 	case '7':
5546 	case '8':
5547 	case '9':
5548 	    fnum = (fnum * 10) + (*sp - '0');
5549 	    break;
5550 
5551 	case 'a':
5552 	    flags |= XFF_ARGUMENT;
5553 	    break;
5554 
5555 	case 'c':
5556 	    flags |= XFF_COLON;
5557 	    break;
5558 
5559 	case 'd':
5560 	    flags |= XFF_DISPLAY_ONLY;
5561 	    break;
5562 
5563 	case 'e':
5564 	    flags |= XFF_ENCODE_ONLY;
5565 	    break;
5566 
5567 	case 'g':
5568 	    flags |= XFF_GT_FIELD;
5569 	    break;
5570 
5571 	case 'h':
5572 	    flags |= XFF_HUMANIZE;
5573 	    break;
5574 
5575 	case 'k':
5576 	    flags |= XFF_KEY;
5577 	    break;
5578 
5579 	case 'l':
5580 	    flags |= XFF_LEAF_LIST;
5581 	    break;
5582 
5583 	case 'n':
5584 	    flags |= XFF_NOQUOTE;
5585 	    break;
5586 
5587 	case 'p':
5588 	    flags |= XFF_GT_PLURAL;
5589 	    break;
5590 
5591 	case 'q':
5592 	    flags |= XFF_QUOTE;
5593 	    break;
5594 
5595 	case 't':
5596 	    flags |= XFF_TRIM_WS;
5597 	    break;
5598 
5599 	case 'w':
5600 	    flags |= XFF_WS;
5601 	    break;
5602 
5603 	default:
5604 	    xo_failure(xop, "field descriptor uses unknown modifier: '%s'",
5605 		       xo_printable(fmt));
5606 	    /*
5607 	     * No good answer here; a bad format will likely
5608 	     * mean a core file.  We just return and hope
5609 	     * the caller notices there's no output, and while
5610 	     * that seems, well, bad, there's nothing better.
5611 	     */
5612 	    return NULL;
5613 	}
5614 
5615 	if (ftype == 'N' || ftype == 'U') {
5616 	    if (flags & XFF_COLON) {
5617 		xo_failure(xop, "colon modifier on 'N' or 'U' field ignored: "
5618 			   "'%s'", xo_printable(fmt));
5619 		flags &= ~XFF_COLON;
5620 	    }
5621 	}
5622     }
5623 
5624     xfip->xfi_flags = flags;
5625     xfip->xfi_ftype = ftype ?: 'V';
5626     xfip->xfi_fnum = fnum;
5627 
5628     return sp;
5629 }
5630 
5631 /*
5632  * Number any remaining fields that need numbers.  Note that some
5633  * field types (text, newline, escaped braces) never get numbers.
5634  */
5635 static void
xo_gettext_finish_numbering_fields(xo_handle_t * xop UNUSED,const char * fmt UNUSED,xo_field_info_t * fields)5636 xo_gettext_finish_numbering_fields (xo_handle_t *xop UNUSED,
5637 				    const char *fmt UNUSED,
5638 				    xo_field_info_t *fields)
5639 {
5640     xo_field_info_t *xfip;
5641     unsigned fnum, max_fields;
5642     uint64_t bits = 0;
5643     const uint64_t one = 1;	/* Avoid "1ULL" */
5644 
5645     /* First make a list of add the explicitly used bits */
5646     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5647 	switch (xfip->xfi_ftype) {
5648 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5649 	case XO_ROLE_TEXT:
5650 	case XO_ROLE_EBRACE:
5651 	case 'G':
5652 	    continue;
5653 	}
5654 
5655 	fnum += 1;
5656 	if (fnum >= 63)
5657 	    break;
5658 
5659 	if (xfip->xfi_fnum)
5660 	    bits |= one << xfip->xfi_fnum;
5661     }
5662 
5663     max_fields = fnum;
5664 
5665     for (xfip = fields, fnum = 0; xfip->xfi_ftype; xfip++) {
5666 	switch (xfip->xfi_ftype) {
5667 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
5668 	case XO_ROLE_TEXT:
5669 	case XO_ROLE_EBRACE:
5670 	case 'G':
5671 	    continue;
5672 	}
5673 
5674 	if (xfip->xfi_fnum != 0)
5675 	    continue;
5676 
5677 	/* Find the next unassigned field */
5678 	for (fnum++; bits & (one << fnum); fnum++)
5679 	    continue;
5680 
5681 	if (fnum > max_fields)
5682 	    break;
5683 
5684 	xfip->xfi_fnum = fnum;	/* Mark the field number */
5685 	bits |= one << fnum;	/* Mark it used */
5686     }
5687 }
5688 
5689 /*
5690  * The format string uses field numbers, so we need to whiffle through it
5691  * and make sure everything's sane and lovely.
5692  */
5693 static int
xo_parse_field_numbers(xo_handle_t * xop,const char * fmt,xo_field_info_t * fields,unsigned num_fields)5694 xo_parse_field_numbers (xo_handle_t *xop, const char *fmt,
5695 			xo_field_info_t *fields, unsigned num_fields)
5696 {
5697     xo_field_info_t *xfip;
5698     unsigned field, fnum;
5699     uint64_t bits = 0;
5700     const uint64_t one = 1;	/* Avoid 1ULL */
5701 
5702     for (xfip = fields, field = 0; field < num_fields; xfip++, field++) {
5703 	/* Fields default to 1:1 with natural position */
5704 	if (xfip->xfi_fnum == 0)
5705 	    xfip->xfi_fnum = field + 1;
5706 	else if (xfip->xfi_fnum > num_fields) {
5707 	    xo_failure(xop, "field number exceeds number of fields: '%s'", fmt);
5708 	    return -1;
5709 	}
5710 
5711 	fnum = xfip->xfi_fnum - 1; /* Move to zero origin */
5712 	if (fnum < 64) {	/* Only test what fits */
5713 	    if (bits & (one << fnum)) {
5714 		xo_failure(xop, "field number %u reused: '%s'",
5715 			   xfip->xfi_fnum, fmt);
5716 		return -1;
5717 	    }
5718 	    bits |= one << fnum;
5719 	}
5720     }
5721 
5722     return 0;
5723 }
5724 
5725 static int
xo_parse_fields(xo_handle_t * xop,xo_field_info_t * fields,unsigned num_fields,const char * fmt)5726 xo_parse_fields (xo_handle_t *xop, xo_field_info_t *fields,
5727 		 unsigned num_fields, const char *fmt)
5728 {
5729     const char *cp, *sp, *ep, *basep;
5730     unsigned field = 0;
5731     xo_field_info_t *xfip = fields;
5732     unsigned seen_fnum = 0;
5733 
5734     for (cp = fmt; *cp && field < num_fields; field++, xfip++) {
5735 	xfip->xfi_start = cp;
5736 
5737 	if (*cp == '\n') {
5738 	    xfip->xfi_ftype = XO_ROLE_NEWLINE;
5739 	    xfip->xfi_len = 1;
5740 	    cp += 1;
5741 	    continue;
5742 	}
5743 
5744 	if (*cp != '{') {
5745 	    /* Normal text */
5746 	    for (sp = cp; *sp; sp++) {
5747 		if (*sp == '{' || *sp == '\n')
5748 		    break;
5749 	    }
5750 
5751 	    xfip->xfi_ftype = XO_ROLE_TEXT;
5752 	    xfip->xfi_content = cp;
5753 	    xfip->xfi_clen = sp - cp;
5754 	    xfip->xfi_next = sp;
5755 
5756 	    cp = sp;
5757 	    continue;
5758 	}
5759 
5760 	if (cp[1] == '{') {	/* Start of {{escaped braces}} */
5761 	    xfip->xfi_start = cp + 1; /* Start at second brace */
5762 	    xfip->xfi_ftype = XO_ROLE_EBRACE;
5763 
5764 	    cp += 2;	/* Skip over _both_ characters */
5765 	    for (sp = cp; *sp; sp++) {
5766 		if (*sp == '}' && sp[1] == '}')
5767 		    break;
5768 	    }
5769 	    if (*sp == '\0') {
5770 		xo_failure(xop, "missing closing '}}': '%s'",
5771 			   xo_printable(fmt));
5772 		return -1;
5773 	    }
5774 
5775 	    xfip->xfi_len = sp - xfip->xfi_start + 1;
5776 
5777 	    /* Move along the string, but don't run off the end */
5778 	    if (*sp == '}' && sp[1] == '}') /* Paranoid; must be true */
5779 		sp += 2;
5780 
5781 	    cp = sp;
5782 	    xfip->xfi_next = cp;
5783 	    continue;
5784 	}
5785 
5786 	/* We are looking at the start of a field definition */
5787 	xfip->xfi_start = basep = cp + 1;
5788 
5789 	const char *format = NULL;
5790 	ssize_t flen = 0;
5791 
5792 	/* Looking at roles and modifiers */
5793 	sp = xo_parse_roles(xop, fmt, basep, xfip);
5794 	if (sp == NULL) {
5795 	    /* xo_failure has already been called */
5796 	    return -1;
5797 	}
5798 
5799 	if (xfip->xfi_fnum)
5800 	    seen_fnum = 1;
5801 
5802 	/* Looking at content */
5803 	if (*sp == ':') {
5804 	    for (ep = ++sp; *sp; sp++) {
5805 		if (*sp == '}' || *sp == '/')
5806 		    break;
5807 		if (*sp == '\\') {
5808 		    if (sp[1] == '\0') {
5809 			xo_failure(xop, "backslash at the end of string");
5810 			return -1;
5811 		    }
5812 		    sp += 1;
5813 		    continue;
5814 		}
5815 	    }
5816 	    if (ep != sp) {
5817 		xfip->xfi_clen = sp - ep;
5818 		xfip->xfi_content = ep;
5819 	    }
5820 	} else {
5821 	    xo_failure(xop, "missing content (':'): '%s'", xo_printable(fmt));
5822 	    return -1;
5823 	}
5824 
5825 	/* Looking at main (display) format */
5826 	if (*sp == '/') {
5827 	    for (ep = ++sp; *sp; sp++) {
5828 		if (*sp == '}' || *sp == '/')
5829 		    break;
5830 		if (*sp == '\\') {
5831 		    if (sp[1] == '\0') {
5832 			xo_failure(xop, "backslash at the end of string");
5833 			return -1;
5834 		    }
5835 		    sp += 1;
5836 		    continue;
5837 		}
5838 	    }
5839 	    flen = sp - ep;
5840 	    format = ep;
5841 	}
5842 
5843 	/* Looking at encoding format */
5844 	if (*sp == '/') {
5845 	    for (ep = ++sp; *sp; sp++) {
5846 		if (*sp == '}')
5847 		    break;
5848 	    }
5849 
5850 	    xfip->xfi_encoding = ep;
5851 	    xfip->xfi_elen = sp - ep;
5852 	}
5853 
5854 	if (*sp != '}') {
5855 	    xo_failure(xop, "missing closing '}': %s", xo_printable(fmt));
5856 	    return -1;
5857 	}
5858 
5859 	xfip->xfi_len = sp - xfip->xfi_start;
5860 	xfip->xfi_next = ++sp;
5861 
5862 	/* If we have content, then we have a default format */
5863 	if (xfip->xfi_clen || format || (xfip->xfi_flags & XFF_ARGUMENT)) {
5864 	    if (format) {
5865 		xfip->xfi_format = format;
5866 		xfip->xfi_flen = flen;
5867 	    } else if (xo_role_wants_default_format(xfip->xfi_ftype)) {
5868 		xfip->xfi_format = xo_default_format;
5869 		xfip->xfi_flen = 2;
5870 	    }
5871 	}
5872 
5873 	cp = sp;
5874     }
5875 
5876     int rc = 0;
5877 
5878     /*
5879      * If we saw a field number on at least one field, then we need
5880      * to enforce some rules and/or guidelines.
5881      */
5882     if (seen_fnum)
5883 	rc = xo_parse_field_numbers(xop, fmt, fields, field);
5884 
5885     return rc;
5886 }
5887 
5888 /*
5889  * We are passed a pointer to a format string just past the "{G:}"
5890  * field.  We build a simplified version of the format string.
5891  */
5892 static int
xo_gettext_simplify_format(xo_handle_t * xop UNUSED,xo_buffer_t * xbp,xo_field_info_t * fields,int this_field,const char * fmt UNUSED,xo_simplify_field_func_t field_cb)5893 xo_gettext_simplify_format (xo_handle_t *xop UNUSED,
5894 		       xo_buffer_t *xbp,
5895 		       xo_field_info_t *fields,
5896 		       int this_field,
5897 		       const char *fmt UNUSED,
5898 		       xo_simplify_field_func_t field_cb)
5899 {
5900     unsigned ftype;
5901     xo_xff_flags_t flags;
5902     int field = this_field + 1;
5903     xo_field_info_t *xfip;
5904     char ch;
5905 
5906     for (xfip = &fields[field]; xfip->xfi_ftype; xfip++, field++) {
5907 	ftype = xfip->xfi_ftype;
5908 	flags = xfip->xfi_flags;
5909 
5910 	if ((flags & XFF_GT_FIELD) && xfip->xfi_content && ftype != 'V') {
5911 	    if (field_cb)
5912 		field_cb(xfip->xfi_content, xfip->xfi_clen,
5913 			 (flags & XFF_GT_PLURAL) ? 1 : 0);
5914 	}
5915 
5916 	switch (ftype) {
5917 	case 'G':
5918 	    /* Ignore gettext roles */
5919 	    break;
5920 
5921 	case XO_ROLE_NEWLINE:
5922 	    xo_buf_append(xbp, "\n", 1);
5923 	    break;
5924 
5925 	case XO_ROLE_EBRACE:
5926 	    xo_buf_append(xbp, "{", 1);
5927 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5928 	    xo_buf_append(xbp, "}", 1);
5929 	    break;
5930 
5931 	case XO_ROLE_TEXT:
5932 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5933 	    break;
5934 
5935 	default:
5936 	    xo_buf_append(xbp, "{", 1);
5937 	    if (ftype != 'V') {
5938 		ch = ftype;
5939 		xo_buf_append(xbp, &ch, 1);
5940 	    }
5941 
5942 	    unsigned fnum = xfip->xfi_fnum ?: 0;
5943 	    if (fnum) {
5944 		char num[12];
5945 		/* Field numbers are origin 1, not 0, following printf(3) */
5946 		snprintf(num, sizeof(num), "%u", fnum);
5947 		xo_buf_append(xbp, num, strlen(num));
5948 	    }
5949 
5950 	    xo_buf_append(xbp, ":", 1);
5951 	    xo_buf_append(xbp, xfip->xfi_content, xfip->xfi_clen);
5952 	    xo_buf_append(xbp, "}", 1);
5953 	}
5954     }
5955 
5956     xo_buf_append(xbp, "", 1);
5957     return 0;
5958 }
5959 
5960 void
5961 xo_dump_fields (xo_field_info_t *); /* Fake prototype for debug function */
5962 void
xo_dump_fields(xo_field_info_t * fields)5963 xo_dump_fields (xo_field_info_t *fields)
5964 {
5965     xo_field_info_t *xfip;
5966 
5967     for (xfip = fields; xfip->xfi_ftype; xfip++) {
5968 	printf("%lu(%u): %lx [%c/%u] [%.*s] [%.*s] [%.*s]\n",
5969 	       (unsigned long) (xfip - fields), xfip->xfi_fnum,
5970 	       (unsigned long) xfip->xfi_flags,
5971 	       isprint((int) xfip->xfi_ftype) ? xfip->xfi_ftype : ' ',
5972 	       xfip->xfi_ftype,
5973 	       (int) xfip->xfi_clen, xfip->xfi_content ?: "",
5974 	       (int) xfip->xfi_flen, xfip->xfi_format ?: "",
5975 	       (int) xfip->xfi_elen, xfip->xfi_encoding ?: "");
5976     }
5977 }
5978 
5979 #ifdef HAVE_GETTEXT
5980 /*
5981  * Find the field that matches the given field number
5982  */
5983 static xo_field_info_t *
xo_gettext_find_field(xo_field_info_t * fields,unsigned fnum)5984 xo_gettext_find_field (xo_field_info_t *fields, unsigned fnum)
5985 {
5986     xo_field_info_t *xfip;
5987 
5988     for (xfip = fields; xfip->xfi_ftype; xfip++)
5989 	if (xfip->xfi_fnum == fnum)
5990 	    return xfip;
5991 
5992     return NULL;
5993 }
5994 
5995 /*
5996  * At this point, we need to consider if the fields have been reordered,
5997  * such as "The {:adjective} {:noun}" to "La {:noun} {:adjective}".
5998  *
5999  * We need to rewrite the new_fields using the old fields order,
6000  * so that we can render the message using the arguments as they
6001  * appear on the stack.  It's a lot of work, but we don't really
6002  * want to (eventually) fall into the standard printf code which
6003  * means using the arguments straight (and in order) from the
6004  * varargs we were originally passed.
6005  */
6006 static void
xo_gettext_rewrite_fields(xo_handle_t * xop UNUSED,xo_field_info_t * fields,unsigned max_fields)6007 xo_gettext_rewrite_fields (xo_handle_t *xop UNUSED,
6008 			   xo_field_info_t *fields, unsigned max_fields)
6009 {
6010     xo_field_info_t tmp[max_fields];
6011     bzero(tmp, max_fields * sizeof(tmp[0]));
6012 
6013     unsigned fnum = 0;
6014     xo_field_info_t *newp, *outp, *zp;
6015     for (newp = fields, outp = tmp; newp->xfi_ftype; newp++, outp++) {
6016 	switch (newp->xfi_ftype) {
6017 	case XO_ROLE_NEWLINE:	/* Don't get numbered */
6018 	case XO_ROLE_TEXT:
6019 	case XO_ROLE_EBRACE:
6020 	case 'G':
6021 	    *outp = *newp;
6022 	    outp->xfi_renum = 0;
6023 	    continue;
6024 	}
6025 
6026 	zp = xo_gettext_find_field(fields, ++fnum);
6027 	if (zp == NULL) { 	/* Should not occur */
6028 	    *outp = *newp;
6029 	    outp->xfi_renum = 0;
6030 	    continue;
6031 	}
6032 
6033 	*outp = *zp;
6034 	outp->xfi_renum = newp->xfi_fnum;
6035     }
6036 
6037     memcpy(fields, tmp, max_fields * sizeof(tmp[0]));
6038 }
6039 
6040 /*
6041  * We've got two lists of fields, the old list from the original
6042  * format string and the new one from the parsed gettext reply.  The
6043  * new list has the localized words, where the old list has the
6044  * formatting information.  We need to combine them into a single list
6045  * (the new list).
6046  *
6047  * If the list needs to be reordered, then we've got more serious work
6048  * to do.
6049  */
6050 static int
xo_gettext_combine_formats(xo_handle_t * xop,const char * fmt UNUSED,const char * gtfmt,xo_field_info_t * old_fields,xo_field_info_t * new_fields,unsigned new_max_fields,int * reorderedp)6051 xo_gettext_combine_formats (xo_handle_t *xop, const char *fmt UNUSED,
6052 		    const char *gtfmt, xo_field_info_t *old_fields,
6053 		    xo_field_info_t *new_fields, unsigned new_max_fields,
6054 		    int *reorderedp)
6055 {
6056     int reordered = 0;
6057     xo_field_info_t *newp, *oldp, *startp = old_fields;
6058 
6059     xo_gettext_finish_numbering_fields(xop, fmt, old_fields);
6060 
6061     for (newp = new_fields; newp->xfi_ftype; newp++) {
6062 	switch (newp->xfi_ftype) {
6063 	case XO_ROLE_NEWLINE:
6064 	case XO_ROLE_TEXT:
6065 	case XO_ROLE_EBRACE:
6066 	    continue;
6067 
6068 	case 'V':
6069 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
6070 		if (oldp->xfi_ftype != 'V')
6071 		    continue;
6072 		if (newp->xfi_clen != oldp->xfi_clen
6073 		    || strncmp(newp->xfi_content, oldp->xfi_content,
6074 			       oldp->xfi_clen) != 0) {
6075 		    reordered = 1;
6076 		    continue;
6077 		}
6078 		startp = oldp + 1;
6079 		break;
6080 	    }
6081 
6082 	    /* Didn't find it on the first pass (starting from start) */
6083 	    if (oldp->xfi_ftype == 0) {
6084 		for (oldp = old_fields; oldp < startp; oldp++) {
6085 		    if (oldp->xfi_ftype != 'V')
6086 			continue;
6087 		    if (newp->xfi_clen != oldp->xfi_clen)
6088 			continue;
6089 		    if (strncmp(newp->xfi_content, oldp->xfi_content,
6090 				oldp->xfi_clen) != 0)
6091 			continue;
6092 		    reordered = 1;
6093 		    break;
6094 		}
6095 		if (oldp == startp) {
6096 		    /* Field not found */
6097 		    xo_failure(xop, "post-gettext format can't find field "
6098 			       "'%.*s' in format '%s'",
6099 			       newp->xfi_clen, newp->xfi_content,
6100 			       xo_printable(gtfmt));
6101 		    return -1;
6102 		}
6103 	    }
6104 	    break;
6105 
6106 	default:
6107 	    /*
6108 	     * Other fields don't have names for us to use, so if
6109 	     * the types aren't the same, then we'll have to assume
6110 	     * the original field is a match.
6111 	     */
6112 	    for (oldp = startp; oldp->xfi_ftype; oldp++) {
6113 		if (oldp->xfi_ftype == 'V') /* Can't go past these */
6114 		    break;
6115 		if (oldp->xfi_ftype == newp->xfi_ftype)
6116 		    goto copy_it; /* Assumably we have a match */
6117 	    }
6118 	    continue;
6119 	}
6120 
6121 	/*
6122 	 * Found a match; copy over appropriate fields
6123 	 */
6124     copy_it:
6125 	newp->xfi_flags = oldp->xfi_flags;
6126 	newp->xfi_fnum = oldp->xfi_fnum;
6127 	newp->xfi_format = oldp->xfi_format;
6128 	newp->xfi_flen = oldp->xfi_flen;
6129 	newp->xfi_encoding = oldp->xfi_encoding;
6130 	newp->xfi_elen = oldp->xfi_elen;
6131     }
6132 
6133     *reorderedp = reordered;
6134     if (reordered) {
6135 	xo_gettext_finish_numbering_fields(xop, fmt, new_fields);
6136 	xo_gettext_rewrite_fields(xop, new_fields, new_max_fields);
6137     }
6138 
6139     return 0;
6140 }
6141 
6142 /*
6143  * We don't want to make gettext() calls here with a complete format
6144  * string, since that means changing a flag would mean a
6145  * labor-intensive re-translation expense.  Instead we build a
6146  * simplified form with a reduced level of detail, perform a lookup on
6147  * that string and then re-insert the formating info.
6148  *
6149  * So something like:
6150  *   xo_emit("{G:}close {:fd/%ld} returned {g:error/%m} {:test/%6.6s}\n", ...)
6151  * would have a lookup string of:
6152  *   "close {:fd} returned {:error} {:test}\n"
6153  *
6154  * We also need to handling reordering of fields, where the gettext()
6155  * reply string uses fields in a different order than the original
6156  * format string:
6157  *   "cluse-a {:fd} retoorned {:test}.  Bork {:error} Bork. Bork.\n"
6158  * If we have to reorder fields within the message, then things get
6159  * complicated.  See xo_gettext_rewrite_fields.
6160  *
6161  * Summary: i18n aighn't cheap.
6162  */
6163 static const char *
xo_gettext_build_format(xo_handle_t * xop,xo_field_info_t * fields,int this_field,const char * fmt,char ** new_fmtp)6164 xo_gettext_build_format (xo_handle_t *xop,
6165 			 xo_field_info_t *fields, int this_field,
6166 			 const char *fmt, char **new_fmtp)
6167 {
6168     if (xo_style_is_encoding(xop))
6169 	goto bail;
6170 
6171     xo_buffer_t xb;
6172     xo_buf_init(&xb);
6173 
6174     if (xo_gettext_simplify_format(xop, &xb, fields,
6175 				   this_field, fmt, NULL))
6176 	goto bail2;
6177 
6178     const char *gtfmt = xo_dgettext(xop, xb.xb_bufp);
6179     if (gtfmt == NULL || gtfmt == fmt || xo_streq(gtfmt, fmt))
6180 	goto bail2;
6181 
6182     char *new_fmt = xo_strndup(gtfmt, -1);
6183     if (new_fmt == NULL)
6184 	goto bail2;
6185 
6186     xo_buf_cleanup(&xb);
6187 
6188     *new_fmtp = new_fmt;
6189     return new_fmt;
6190 
6191  bail2:
6192 	xo_buf_cleanup(&xb);
6193  bail:
6194     *new_fmtp = NULL;
6195     return fmt;
6196 }
6197 
6198 static void
xo_gettext_rebuild_content(xo_handle_t * xop,xo_field_info_t * fields,ssize_t * fstart,unsigned min_fstart,ssize_t * fend,unsigned max_fend)6199 xo_gettext_rebuild_content (xo_handle_t *xop, xo_field_info_t *fields,
6200 			    ssize_t *fstart, unsigned min_fstart,
6201 			    ssize_t *fend, unsigned max_fend)
6202 {
6203     xo_field_info_t *xfip;
6204     char *buf;
6205     ssize_t base = fstart[min_fstart];
6206     ssize_t blen = fend[max_fend] - base;
6207     xo_buffer_t *xbp = &xop->xo_data;
6208 
6209     if (blen == 0)
6210 	return;
6211 
6212     buf = xo_realloc(NULL, blen);
6213     if (buf == NULL)
6214 	return;
6215 
6216     memcpy(buf, xbp->xb_bufp + fstart[min_fstart], blen); /* Copy our data */
6217 
6218     unsigned field = min_fstart, len, fnum;
6219     ssize_t soff, doff = base;
6220     xo_field_info_t *zp;
6221 
6222     /*
6223      * Be aware there are two competing views of "field number": we
6224      * want the user to thing in terms of "The {1:size}" where {G:},
6225      * newlines, escaped braces, and text don't have numbers.  But is
6226      * also the internal view, where we have an array of
6227      * xo_field_info_t and every field have an index.  fnum, fstart[]
6228      * and fend[] are the latter, but xfi_renum is the former.
6229      */
6230     for (xfip = fields + field; xfip->xfi_ftype; xfip++, field++) {
6231 	fnum = field;
6232 	if (xfip->xfi_renum) {
6233 	    zp = xo_gettext_find_field(fields, xfip->xfi_renum);
6234 	    fnum = zp ? zp - fields : field;
6235 	}
6236 
6237 	soff = fstart[fnum];
6238 	len = fend[fnum] - soff;
6239 
6240 	if (len > 0) {
6241 	    soff -= base;
6242 	    memcpy(xbp->xb_bufp + doff, buf + soff, len);
6243 	    doff += len;
6244 	}
6245     }
6246 
6247     xo_free(buf);
6248 }
6249 #else  /* HAVE_GETTEXT */
6250 static const char *
xo_gettext_build_format(xo_handle_t * xop UNUSED,xo_field_info_t * fields UNUSED,int this_field UNUSED,const char * fmt UNUSED,char ** new_fmtp)6251 xo_gettext_build_format (xo_handle_t *xop UNUSED,
6252 			 xo_field_info_t *fields UNUSED,
6253 			 int this_field UNUSED,
6254 			 const char *fmt UNUSED, char **new_fmtp)
6255 {
6256     *new_fmtp = NULL;
6257     return fmt;
6258 }
6259 
6260 static int
xo_gettext_combine_formats(xo_handle_t * xop UNUSED,const char * fmt UNUSED,const char * gtfmt UNUSED,xo_field_info_t * old_fields UNUSED,xo_field_info_t * new_fields UNUSED,unsigned new_max_fields UNUSED,int * reorderedp UNUSED)6261 xo_gettext_combine_formats (xo_handle_t *xop UNUSED, const char *fmt UNUSED,
6262 		    const char *gtfmt UNUSED,
6263 		    xo_field_info_t *old_fields UNUSED,
6264 		    xo_field_info_t *new_fields UNUSED,
6265 		    unsigned new_max_fields UNUSED,
6266 		    int *reorderedp UNUSED)
6267 {
6268     return -1;
6269 }
6270 
6271 static void
xo_gettext_rebuild_content(xo_handle_t * xop UNUSED,xo_field_info_t * fields UNUSED,ssize_t * fstart UNUSED,unsigned min_fstart UNUSED,ssize_t * fend UNUSED,unsigned max_fend UNUSED)6272 xo_gettext_rebuild_content (xo_handle_t *xop UNUSED,
6273 		    xo_field_info_t *fields UNUSED,
6274 		    ssize_t *fstart UNUSED, unsigned min_fstart UNUSED,
6275 		    ssize_t *fend UNUSED, unsigned max_fend UNUSED)
6276 {
6277     return;
6278 }
6279 #endif /* HAVE_GETTEXT */
6280 
6281 /*
6282  * Emit a set of fields.  This is really the core of libxo.
6283  */
6284 static ssize_t
xo_do_emit_fields(xo_handle_t * xop,xo_field_info_t * fields,unsigned max_fields,const char * fmt)6285 xo_do_emit_fields (xo_handle_t *xop, xo_field_info_t *fields,
6286 		   unsigned max_fields, const char *fmt)
6287 {
6288     int gettext_inuse = 0;
6289     int gettext_changed = 0;
6290     int gettext_reordered = 0;
6291     unsigned ftype;
6292     xo_xff_flags_t flags;
6293     xo_field_info_t *new_fields = NULL;
6294     xo_field_info_t *xfip;
6295     unsigned field;
6296     ssize_t rc = 0;
6297 
6298     int flush = XOF_ISSET(xop, XOF_FLUSH);
6299     int flush_line = XOF_ISSET(xop, XOF_FLUSH_LINE);
6300     char *new_fmt = NULL;
6301 
6302     if (XOIF_ISSET(xop, XOIF_REORDER) || xo_style(xop) == XO_STYLE_ENCODER)
6303 	flush_line = 0;
6304 
6305     /*
6306      * Some overhead for gettext; if the fields in the msgstr returned
6307      * by gettext are reordered, then we need to record start and end
6308      * for each field.  We'll go ahead and render the fields in the
6309      * normal order, but later we can then reconstruct the reordered
6310      * fields using these fstart/fend values.
6311      */
6312     unsigned flimit = max_fields * 2; /* Pessimistic limit */
6313     unsigned min_fstart = flimit - 1;
6314     unsigned max_fend = 0;	      /* Highest recorded fend[] entry */
6315     ssize_t fstart[flimit];
6316     bzero(fstart, flimit * sizeof(fstart[0]));
6317     ssize_t fend[flimit];
6318     bzero(fend, flimit * sizeof(fend[0]));
6319 
6320     for (xfip = fields, field = 0; field < max_fields && xfip->xfi_ftype;
6321 	 xfip++, field++) {
6322 	ftype = xfip->xfi_ftype;
6323 	flags = xfip->xfi_flags;
6324 
6325 	/* Record field start offset */
6326 	if (gettext_reordered) {
6327 	    fstart[field] = xo_buf_offset(&xop->xo_data);
6328 	    if (min_fstart > field)
6329 		min_fstart = field;
6330 	}
6331 
6332 	const char *content = xfip->xfi_content;
6333 	ssize_t clen = xfip->xfi_clen;
6334 
6335 	if (flags & XFF_ARGUMENT) {
6336 	    /*
6337 	     * Argument flag means the content isn't given in the descriptor,
6338 	     * but as a UTF-8 string ('const char *') argument in xo_vap.
6339 	     */
6340 	    content = va_arg(xop->xo_vap, char *);
6341 	    clen = content ? strlen(content) : 0;
6342 	}
6343 
6344 	if (ftype == XO_ROLE_NEWLINE) {
6345 	    xo_line_close(xop);
6346 	    if (flush_line && xo_flush_h(xop) < 0)
6347 		return -1;
6348 	    goto bottom;
6349 
6350 	} else if (ftype == XO_ROLE_EBRACE) {
6351 	    xo_format_text(xop, xfip->xfi_start, xfip->xfi_len);
6352 	    goto bottom;
6353 
6354 	} else if (ftype == XO_ROLE_TEXT) {
6355 	    /* Normal text */
6356 	    xo_format_text(xop, xfip->xfi_content, xfip->xfi_clen);
6357 	    goto bottom;
6358 	}
6359 
6360 	/*
6361 	 * Notes and units need the 'w' flag handled before the content.
6362 	 */
6363 	if (ftype == 'N' || ftype == 'U') {
6364 	    if (flags & XFF_WS) {
6365 		xo_format_content(xop, "padding", NULL, " ", 1,
6366 				  NULL, 0, flags);
6367 		flags &= ~XFF_WS; /* Prevent later handling of this flag */
6368 	    }
6369 	}
6370 
6371 	if (ftype == 'V')
6372 	    xo_format_value(xop, content, clen, NULL, 0,
6373 			    xfip->xfi_format, xfip->xfi_flen,
6374 			    xfip->xfi_encoding, xfip->xfi_elen, flags);
6375 	else if (ftype == '[')
6376 	    xo_anchor_start(xop, xfip, content, clen);
6377 	else if (ftype == ']')
6378 	    xo_anchor_stop(xop, xfip, content, clen);
6379 	else if (ftype == 'C')
6380 	    xo_format_colors(xop, xfip, content, clen);
6381 
6382 	else if (ftype == 'G') {
6383 	    /*
6384 	     * A {G:domain} field; disect the domain name and translate
6385 	     * the remaining portion of the input string.  If the user
6386 	     * didn't put the {G:} at the start of the format string, then
6387 	     * assumably they just want us to translate the rest of it.
6388 	     * Since gettext returns strings in a static buffer, we make
6389 	     * a copy in new_fmt.
6390 	     */
6391 	    xo_set_gettext_domain(xop, xfip, content, clen);
6392 
6393 	    if (!gettext_inuse) { /* Only translate once */
6394 		gettext_inuse = 1;
6395 		if (new_fmt) {
6396 		    xo_free(new_fmt);
6397 		    new_fmt = NULL;
6398 		}
6399 
6400 		xo_gettext_build_format(xop, fields, field,
6401 					xfip->xfi_next, &new_fmt);
6402 		if (new_fmt) {
6403 		    gettext_changed = 1;
6404 
6405 		    unsigned new_max_fields = xo_count_fields(xop, new_fmt);
6406 
6407 		    if (++new_max_fields < max_fields)
6408 			new_max_fields = max_fields;
6409 
6410 		    /* Leave a blank slot at the beginning */
6411 		    ssize_t sz = (new_max_fields + 1) * sizeof(xo_field_info_t);
6412 		    new_fields = alloca(sz);
6413 		    bzero(new_fields, sz);
6414 
6415 		    if (!xo_parse_fields(xop, new_fields + 1,
6416 					 new_max_fields, new_fmt)) {
6417 			gettext_reordered = 0;
6418 
6419 			if (!xo_gettext_combine_formats(xop, fmt, new_fmt,
6420 					fields, new_fields + 1,
6421 					new_max_fields, &gettext_reordered)) {
6422 
6423 			    if (gettext_reordered) {
6424 				if (XOF_ISSET(xop, XOF_LOG_GETTEXT))
6425 				    xo_failure(xop, "gettext finds reordered "
6426 					       "fields in '%s' and '%s'",
6427 					       xo_printable(fmt),
6428 					       xo_printable(new_fmt));
6429 				flush_line = 0; /* Must keep at content */
6430 				XOIF_SET(xop, XOIF_REORDER);
6431 			    }
6432 
6433 			    field = -1; /* Will be incremented at top of loop */
6434 			    xfip = new_fields;
6435 			    max_fields = new_max_fields;
6436 			}
6437 		    }
6438 		}
6439 	    }
6440 	    continue;
6441 
6442 	} else  if (clen || xfip->xfi_format) {
6443 
6444 	    const char *class_name = xo_class_name(ftype);
6445 	    if (class_name)
6446 		xo_format_content(xop, class_name, xo_tag_name(ftype),
6447 				  content, clen,
6448 				  xfip->xfi_format, xfip->xfi_flen, flags);
6449 	    else if (ftype == 'T')
6450 		xo_format_title(xop, xfip, content, clen);
6451 	    else if (ftype == 'U')
6452 		xo_format_units(xop, xfip, content, clen);
6453 	    else
6454 		xo_failure(xop, "unknown field type: '%c'", ftype);
6455 	}
6456 
6457 	if (flags & XFF_COLON)
6458 	    xo_format_content(xop, "decoration", NULL, ":", 1, NULL, 0, 0);
6459 
6460 	if (flags & XFF_WS)
6461 	    xo_format_content(xop, "padding", NULL, " ", 1, NULL, 0, 0);
6462 
6463     bottom:
6464 	/* Record the end-of-field offset */
6465 	if (gettext_reordered) {
6466 	    fend[field] = xo_buf_offset(&xop->xo_data);
6467 	    max_fend = field;
6468 	}
6469     }
6470 
6471     if (gettext_changed && gettext_reordered) {
6472 	/* Final step: rebuild the content using the rendered fields */
6473 	xo_gettext_rebuild_content(xop, new_fields + 1, fstart, min_fstart,
6474 				   fend, max_fend);
6475     }
6476 
6477     XOIF_CLEAR(xop, XOIF_REORDER);
6478 
6479     /*
6480      * If we've got enough data, flush it.
6481      */
6482     if (xo_buf_offset(&xop->xo_data) > XO_BUF_HIGH_WATER)
6483 	flush = 1;
6484 
6485     /* If we don't have an anchor, write the text out */
6486     if (flush && !XOIF_ISSET(xop, XOIF_ANCHOR)) {
6487 	if (xo_flush_h(xop) < 0)
6488 	    rc = -1;
6489     }
6490 
6491     if (new_fmt)
6492 	xo_free(new_fmt);
6493 
6494     /*
6495      * We've carried the gettext domainname inside our handle just for
6496      * convenience, but we need to ensure it doesn't survive across
6497      * xo_emit calls.
6498      */
6499     if (xop->xo_gt_domain) {
6500 	xo_free(xop->xo_gt_domain);
6501 	xop->xo_gt_domain = NULL;
6502     }
6503 
6504     return (rc < 0) ? rc : xop->xo_columns;
6505 }
6506 
6507 /*
6508  * Parse and emit a set of fields
6509  */
6510 static int
xo_do_emit(xo_handle_t * xop,xo_emit_flags_t flags,const char * fmt)6511 xo_do_emit (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt)
6512 {
6513     xop->xo_columns = 0;	/* Always reset it */
6514     xop->xo_errno = errno;	/* Save for "%m" */
6515 
6516     if (fmt == NULL)
6517 	return 0;
6518 
6519     unsigned max_fields;
6520     xo_field_info_t *fields = NULL;
6521 
6522     /* Adjust XOEF_RETAIN based on global flags */
6523     if (XOF_ISSET(xop, XOF_RETAIN_ALL))
6524 	flags |= XOEF_RETAIN;
6525     if (XOF_ISSET(xop, XOF_RETAIN_NONE))
6526 	flags &= ~XOEF_RETAIN;
6527 
6528     /*
6529      * Check for 'retain' flag, telling us to retain the field
6530      * information.  If we've already saved it, then we can avoid
6531      * re-parsing the format string.
6532      */
6533     if (!(flags & XOEF_RETAIN)
6534 	|| xo_retain_find(fmt, &fields, &max_fields) != 0
6535 	|| fields == NULL) {
6536 
6537 	/* Nothing retained; parse the format string */
6538 	max_fields = xo_count_fields(xop, fmt);
6539 	fields = alloca(max_fields * sizeof(fields[0]));
6540 	bzero(fields, max_fields * sizeof(fields[0]));
6541 
6542 	if (xo_parse_fields(xop, fields, max_fields, fmt))
6543 	    return -1;		/* Warning already displayed */
6544 
6545 	if (flags & XOEF_RETAIN) {
6546 	    /* Retain the info */
6547 	    xo_retain_add(fmt, fields, max_fields);
6548 	}
6549     }
6550 
6551     return xo_do_emit_fields(xop, fields, max_fields, fmt);
6552 }
6553 
6554 /*
6555  * Rebuild a format string in a gettext-friendly format.  This function
6556  * is exposed to tools can perform this function.  See xo(1).
6557  */
6558 char *
xo_simplify_format(xo_handle_t * xop,const char * fmt,int with_numbers,xo_simplify_field_func_t field_cb)6559 xo_simplify_format (xo_handle_t *xop, const char *fmt, int with_numbers,
6560 		    xo_simplify_field_func_t field_cb)
6561 {
6562     xop = xo_default(xop);
6563 
6564     xop->xo_columns = 0;	/* Always reset it */
6565     xop->xo_errno = errno;	/* Save for "%m" */
6566 
6567     unsigned max_fields = xo_count_fields(xop, fmt);
6568     xo_field_info_t fields[max_fields];
6569 
6570     bzero(fields, max_fields * sizeof(fields[0]));
6571 
6572     if (xo_parse_fields(xop, fields, max_fields, fmt))
6573 	return NULL;		/* Warning already displayed */
6574 
6575     xo_buffer_t xb;
6576     xo_buf_init(&xb);
6577 
6578     if (with_numbers)
6579 	xo_gettext_finish_numbering_fields(xop, fmt, fields);
6580 
6581     if (xo_gettext_simplify_format(xop, &xb, fields, -1, fmt, field_cb))
6582 	return NULL;
6583 
6584     return xb.xb_bufp;
6585 }
6586 
6587 xo_ssize_t
xo_emit_hv(xo_handle_t * xop,const char * fmt,va_list vap)6588 xo_emit_hv (xo_handle_t *xop, const char *fmt, va_list vap)
6589 {
6590     ssize_t rc;
6591 
6592     xop = xo_default(xop);
6593     va_copy(xop->xo_vap, vap);
6594     rc = xo_do_emit(xop, 0, fmt);
6595     va_end(xop->xo_vap);
6596     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6597 
6598     return rc;
6599 }
6600 
6601 xo_ssize_t
xo_emit_h(xo_handle_t * xop,const char * fmt,...)6602 xo_emit_h (xo_handle_t *xop, const char *fmt, ...)
6603 {
6604     ssize_t rc;
6605 
6606     xop = xo_default(xop);
6607     va_start(xop->xo_vap, fmt);
6608     rc = xo_do_emit(xop, 0, fmt);
6609     va_end(xop->xo_vap);
6610     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6611 
6612     return rc;
6613 }
6614 
6615 xo_ssize_t
xo_emit(const char * fmt,...)6616 xo_emit (const char *fmt, ...)
6617 {
6618     xo_handle_t *xop = xo_default(NULL);
6619     ssize_t rc;
6620 
6621     va_start(xop->xo_vap, fmt);
6622     rc = xo_do_emit(xop, 0, fmt);
6623     va_end(xop->xo_vap);
6624     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6625 
6626     return rc;
6627 }
6628 
6629 xo_ssize_t
xo_emit_hvf(xo_handle_t * xop,xo_emit_flags_t flags,const char * fmt,va_list vap)6630 xo_emit_hvf (xo_handle_t *xop, xo_emit_flags_t flags,
6631 	     const char *fmt, va_list vap)
6632 {
6633     ssize_t rc;
6634 
6635     xop = xo_default(xop);
6636     va_copy(xop->xo_vap, vap);
6637     rc = xo_do_emit(xop, flags, fmt);
6638     va_end(xop->xo_vap);
6639     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6640 
6641     return rc;
6642 }
6643 
6644 xo_ssize_t
xo_emit_hf(xo_handle_t * xop,xo_emit_flags_t flags,const char * fmt,...)6645 xo_emit_hf (xo_handle_t *xop, xo_emit_flags_t flags, const char *fmt, ...)
6646 {
6647     ssize_t rc;
6648 
6649     xop = xo_default(xop);
6650     va_start(xop->xo_vap, fmt);
6651     rc = xo_do_emit(xop, flags, fmt);
6652     va_end(xop->xo_vap);
6653     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6654 
6655     return rc;
6656 }
6657 
6658 xo_ssize_t
xo_emit_f(xo_emit_flags_t flags,const char * fmt,...)6659 xo_emit_f (xo_emit_flags_t flags, const char *fmt, ...)
6660 {
6661     xo_handle_t *xop = xo_default(NULL);
6662     ssize_t rc;
6663 
6664     va_start(xop->xo_vap, fmt);
6665     rc = xo_do_emit(xop, flags, fmt);
6666     va_end(xop->xo_vap);
6667     bzero(&xop->xo_vap, sizeof(xop->xo_vap));
6668 
6669     return rc;
6670 }
6671 
6672 /*
6673  * Emit a single field by providing the info information typically provided
6674  * inside the field description (role, modifiers, and formats).  This is
6675  * a convenience function to avoid callers using snprintf to build field
6676  * descriptions.
6677  */
6678 xo_ssize_t
xo_emit_field_hv(xo_handle_t * xop,const char * rolmod,const char * contents,const char * fmt,const char * efmt,va_list vap)6679 xo_emit_field_hv (xo_handle_t *xop, const char *rolmod, const char *contents,
6680 		  const char *fmt, const char *efmt,
6681 		  va_list vap)
6682 {
6683     ssize_t rc;
6684 
6685     xop = xo_default(xop);
6686 
6687     if (rolmod == NULL)
6688 	rolmod = "V";
6689 
6690     xo_field_info_t xfi;
6691 
6692     bzero(&xfi, sizeof(xfi));
6693 
6694     const char *cp;
6695     cp = xo_parse_roles(xop, rolmod, rolmod, &xfi);
6696     if (cp == NULL)
6697 	return -1;
6698 
6699     xfi.xfi_start = fmt;
6700     xfi.xfi_content = contents;
6701     xfi.xfi_format = fmt;
6702     xfi.xfi_encoding = efmt;
6703     xfi.xfi_clen = contents ? strlen(contents) : 0;
6704     xfi.xfi_flen = fmt ? strlen(fmt) : 0;
6705     xfi.xfi_elen = efmt ? strlen(efmt) : 0;
6706 
6707     /* If we have content, then we have a default format */
6708     if (contents && fmt == NULL
6709 		&& xo_role_wants_default_format(xfi.xfi_ftype)) {
6710 	xfi.xfi_format = xo_default_format;
6711 	xfi.xfi_flen = 2;
6712     }
6713 
6714     va_copy(xop->xo_vap, vap);
6715 
6716     rc = xo_do_emit_fields(xop, &xfi, 1, fmt ?: contents ?: "field");
6717 
6718     va_end(xop->xo_vap);
6719 
6720     return rc;
6721 }
6722 
6723 xo_ssize_t
xo_emit_field_h(xo_handle_t * xop,const char * rolmod,const char * contents,const char * fmt,const char * efmt,...)6724 xo_emit_field_h (xo_handle_t *xop, const char *rolmod, const char *contents,
6725 		 const char *fmt, const char *efmt, ...)
6726 {
6727     ssize_t rc;
6728     va_list vap;
6729 
6730     va_start(vap, efmt);
6731     rc = xo_emit_field_hv(xop, rolmod, contents, fmt, efmt, vap);
6732     va_end(vap);
6733 
6734     return rc;
6735 }
6736 
6737 xo_ssize_t
xo_emit_field(const char * rolmod,const char * contents,const char * fmt,const char * efmt,...)6738 xo_emit_field (const char *rolmod, const char *contents,
6739 	       const char *fmt, const char *efmt, ...)
6740 {
6741     ssize_t rc;
6742     va_list vap;
6743 
6744     va_start(vap, efmt);
6745     rc = xo_emit_field_hv(NULL, rolmod, contents, fmt, efmt, vap);
6746     va_end(vap);
6747 
6748     return rc;
6749 }
6750 
6751 xo_ssize_t
xo_attr_hv(xo_handle_t * xop,const char * name,const char * fmt,va_list vap)6752 xo_attr_hv (xo_handle_t *xop, const char *name, const char *fmt, va_list vap)
6753 {
6754     const ssize_t extra = 5; 	/* space, equals, quote, quote, and nul */
6755     xop = xo_default(xop);
6756 
6757     ssize_t rc = 0;
6758     ssize_t nlen = strlen(name);
6759     xo_buffer_t *xbp = &xop->xo_attrs;
6760     ssize_t name_offset, value_offset;
6761 
6762     switch (xo_style(xop)) {
6763     case XO_STYLE_XML:
6764 	if (!xo_buf_has_room(xbp, nlen + extra))
6765 	    return -1;
6766 
6767 	*xbp->xb_curp++ = ' ';
6768 	memcpy(xbp->xb_curp, name, nlen);
6769 	xbp->xb_curp += nlen;
6770 	*xbp->xb_curp++ = '=';
6771 	*xbp->xb_curp++ = '"';
6772 
6773 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
6774 
6775 	if (rc >= 0) {
6776 	    rc = xo_escape_xml(xbp, rc, 1);
6777 	    xbp->xb_curp += rc;
6778 	}
6779 
6780 	if (!xo_buf_has_room(xbp, 2))
6781 	    return -1;
6782 
6783 	*xbp->xb_curp++ = '"';
6784 	*xbp->xb_curp = '\0';
6785 
6786 	rc += nlen + extra;
6787 	break;
6788 
6789     case XO_STYLE_ENCODER:
6790 	name_offset = xo_buf_offset(xbp);
6791 	xo_buf_append(xbp, name, nlen);
6792 	xo_buf_append(xbp, "", 1);
6793 
6794 	value_offset = xo_buf_offset(xbp);
6795 	rc = xo_vsnprintf(xop, xbp, fmt, vap);
6796 	if (rc >= 0) {
6797 	    xbp->xb_curp += rc;
6798 	    *xbp->xb_curp = '\0';
6799 	    rc = xo_encoder_handle(xop, XO_OP_ATTRIBUTE,
6800 				   xo_buf_data(xbp, name_offset),
6801 				   xo_buf_data(xbp, value_offset), 0);
6802 	}
6803     }
6804 
6805     return rc;
6806 }
6807 
6808 xo_ssize_t
xo_attr_h(xo_handle_t * xop,const char * name,const char * fmt,...)6809 xo_attr_h (xo_handle_t *xop, const char *name, const char *fmt, ...)
6810 {
6811     ssize_t rc;
6812     va_list vap;
6813 
6814     va_start(vap, fmt);
6815     rc = xo_attr_hv(xop, name, fmt, vap);
6816     va_end(vap);
6817 
6818     return rc;
6819 }
6820 
6821 xo_ssize_t
xo_attr(const char * name,const char * fmt,...)6822 xo_attr (const char *name, const char *fmt, ...)
6823 {
6824     ssize_t rc;
6825     va_list vap;
6826 
6827     va_start(vap, fmt);
6828     rc = xo_attr_hv(NULL, name, fmt, vap);
6829     va_end(vap);
6830 
6831     return rc;
6832 }
6833 
6834 static void
xo_depth_change(xo_handle_t * xop,const char * name,int delta,int indent,xo_state_t state,xo_xsf_flags_t flags)6835 xo_depth_change (xo_handle_t *xop, const char *name,
6836 		 int delta, int indent, xo_state_t state, xo_xsf_flags_t flags)
6837 {
6838     if (xo_style(xop) == XO_STYLE_HTML || xo_style(xop) == XO_STYLE_TEXT)
6839 	indent = 0;
6840 
6841     if (XOF_ISSET(xop, XOF_DTRT))
6842 	flags |= XSF_DTRT;
6843 
6844     if (delta >= 0) {			/* Push operation */
6845 	if (xo_depth_check(xop, xop->xo_depth + delta))
6846 	    return;
6847 
6848 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth + delta];
6849 	xsp->xs_flags = flags;
6850 	xsp->xs_state = state;
6851 	xo_stack_set_flags(xop);
6852 
6853 	if (name == NULL)
6854 	    name = XO_FAILURE_NAME;
6855 
6856 	xsp->xs_name = xo_strndup(name, -1);
6857 
6858     } else {			/* Pop operation */
6859 	if (xop->xo_depth == 0) {
6860 	    if (!XOF_ISSET(xop, XOF_IGNORE_CLOSE))
6861 		xo_failure(xop, "close with empty stack: '%s'", name);
6862 	    return;
6863 	}
6864 
6865 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
6866 	if (XOF_ISSET(xop, XOF_WARN)) {
6867 	    const char *top = xsp->xs_name;
6868 	    if (top != NULL && name != NULL && !xo_streq(name, top)) {
6869 		xo_failure(xop, "incorrect close: '%s' .vs. '%s'",
6870 			      name, top);
6871 		return;
6872 	    }
6873 	    if ((xsp->xs_flags & XSF_LIST) != (flags & XSF_LIST)) {
6874 		xo_failure(xop, "list close on list confict: '%s'",
6875 			      name);
6876 		return;
6877 	    }
6878 	    if ((xsp->xs_flags & XSF_INSTANCE) != (flags & XSF_INSTANCE)) {
6879 		xo_failure(xop, "list close on instance confict: '%s'",
6880 			      name);
6881 		return;
6882 	    }
6883 	}
6884 
6885 	if (xsp->xs_name) {
6886 	    xo_free(xsp->xs_name);
6887 	    xsp->xs_name = NULL;
6888 	}
6889 	if (xsp->xs_keys) {
6890 	    xo_free(xsp->xs_keys);
6891 	    xsp->xs_keys = NULL;
6892 	}
6893     }
6894 
6895     xop->xo_depth += delta;	/* Record new depth */
6896     xop->xo_indent += indent;
6897 }
6898 
6899 void
xo_set_depth(xo_handle_t * xop,int depth)6900 xo_set_depth (xo_handle_t *xop, int depth)
6901 {
6902     xop = xo_default(xop);
6903 
6904     if (xo_depth_check(xop, depth))
6905 	return;
6906 
6907     xop->xo_depth += depth;
6908     xop->xo_indent += depth;
6909 
6910     /*
6911      * Handling the "top wrapper" for JSON is a bit of a pain.  Here
6912      * we need to detect that the depth has been changed to set the
6913      * "XOIF_TOP_EMITTED" flag correctly.
6914      */
6915     if (xop->xo_style == XO_STYLE_JSON
6916 	&& !XOF_ISSET(xop, XOF_NO_TOP) && xop->xo_depth > 0)
6917 	XOIF_SET(xop, XOIF_TOP_EMITTED);
6918 }
6919 
6920 static xo_xsf_flags_t
xo_stack_flags(xo_xof_flags_t xflags)6921 xo_stack_flags (xo_xof_flags_t xflags)
6922 {
6923     if (xflags & XOF_DTRT)
6924 	return XSF_DTRT;
6925     return 0;
6926 }
6927 
6928 static void
xo_emit_top(xo_handle_t * xop,const char * ppn)6929 xo_emit_top (xo_handle_t *xop, const char *ppn)
6930 {
6931     xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
6932     XOIF_SET(xop, XOIF_TOP_EMITTED);
6933 
6934     if (xop->xo_version) {
6935 	xo_printf(xop, "%*s\"__version\": \"%s\", %s",
6936 		  xo_indent(xop), "", xop->xo_version, ppn);
6937 	xo_free(xop->xo_version);
6938 	xop->xo_version = NULL;
6939     }
6940 }
6941 
6942 static ssize_t
xo_do_open_container(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)6943 xo_do_open_container (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
6944 {
6945     ssize_t rc = 0;
6946     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
6947     const char *pre_nl = "";
6948 
6949     if (name == NULL) {
6950 	xo_failure(xop, "NULL passed for container name");
6951 	name = XO_FAILURE_NAME;
6952     }
6953 
6954     const char *leader = xo_xml_leader(xop, name);
6955     flags |= xop->xo_flags;	/* Pick up handle flags */
6956 
6957     switch (xo_style(xop)) {
6958     case XO_STYLE_XML:
6959 	rc = xo_printf(xop, "%*s<%s%s", xo_indent(xop), "", leader, name);
6960 
6961 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
6962 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
6963 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
6964 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
6965 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
6966 	}
6967 
6968 	rc += xo_printf(xop, ">%s", ppn);
6969 	break;
6970 
6971     case XO_STYLE_JSON:
6972 	xo_stack_set_flags(xop);
6973 
6974 	if (!XOF_ISSET(xop, XOF_NO_TOP)
6975 	        && !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
6976 	    xo_emit_top(xop, ppn);
6977 
6978 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
6979 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
6980 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
6981 
6982 	/* If we need underscores, make a local copy and doctor it */
6983 	const char *new_name = name;
6984 	if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
6985 	    size_t len = strlen(name);
6986 	    const char *old_name = name;
6987 	    char *buf, *cp, *ep;
6988 
6989 	    buf = alloca(len + 1);
6990 	    for (cp = buf, ep = buf + len + 1; cp < ep; cp++, old_name++)
6991 		*cp = (*old_name == '-') ? '_' : *old_name;
6992 	    new_name = buf;
6993 	}
6994 
6995 	rc = xo_printf(xop, "%s%*s\"%s\": {%s",
6996 		       pre_nl, xo_indent(xop), "", new_name, ppn);
6997 	break;
6998 
6999     case XO_STYLE_SDPARAMS:
7000 	break;
7001 
7002     case XO_STYLE_ENCODER:
7003 	rc = xo_encoder_handle(xop, XO_OP_OPEN_CONTAINER, name, NULL, flags);
7004 	break;
7005     }
7006 
7007     xo_depth_change(xop, name, 1, 1, XSS_OPEN_CONTAINER,
7008 		    xo_stack_flags(flags));
7009 
7010     return rc;
7011 }
7012 
7013 xo_ssize_t
xo_open_container_hf(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7014 xo_open_container_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7015 {
7016     return xo_transition(xop, flags, name, XSS_OPEN_CONTAINER);
7017 }
7018 
7019 xo_ssize_t
xo_open_container_h(xo_handle_t * xop,const char * name)7020 xo_open_container_h (xo_handle_t *xop, const char *name)
7021 {
7022     return xo_open_container_hf(xop, 0, name);
7023 }
7024 
7025 xo_ssize_t
xo_open_container(const char * name)7026 xo_open_container (const char *name)
7027 {
7028     return xo_open_container_hf(NULL, 0, name);
7029 }
7030 
7031 xo_ssize_t
xo_open_container_hd(xo_handle_t * xop,const char * name)7032 xo_open_container_hd (xo_handle_t *xop, const char *name)
7033 {
7034     return xo_open_container_hf(xop, XOF_DTRT, name);
7035 }
7036 
7037 xo_ssize_t
xo_open_container_d(const char * name)7038 xo_open_container_d (const char *name)
7039 {
7040     return xo_open_container_hf(NULL, XOF_DTRT, name);
7041 }
7042 
7043 static int
xo_do_close_container(xo_handle_t * xop,const char * name)7044 xo_do_close_container (xo_handle_t *xop, const char *name)
7045 {
7046     xop = xo_default(xop);
7047 
7048     ssize_t rc = 0;
7049     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7050     const char *pre_nl = "";
7051 
7052     if (name == NULL) {
7053 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7054 
7055 	name = xsp->xs_name;
7056 	if (name) {
7057 	    ssize_t len = strlen(name) + 1;
7058 	    /* We need to make a local copy; xo_depth_change will free it */
7059 	    char *cp = alloca(len);
7060 	    memcpy(cp, name, len);
7061 	    name = cp;
7062 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7063 	    xo_failure(xop, "missing name without 'dtrt' mode");
7064 	    name = XO_FAILURE_NAME;
7065 	}
7066     }
7067 
7068     const char *leader = xo_xml_leader(xop, name);
7069 
7070     switch (xo_style(xop)) {
7071     case XO_STYLE_XML:
7072 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
7073 	rc = xo_printf(xop, "%*s</%s%s>%s", xo_indent(xop), "", leader, name, ppn);
7074 	break;
7075 
7076     case XO_STYLE_JSON:
7077 	xo_stack_set_flags(xop);
7078 
7079 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7080 	ppn = "";
7081 
7082 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_CONTAINER, 0);
7083 	rc = xo_printf(xop, "%s%*s}%s", pre_nl, xo_indent(xop), "", ppn);
7084 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7085 	break;
7086 
7087     case XO_STYLE_HTML:
7088     case XO_STYLE_TEXT:
7089 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
7090 	break;
7091 
7092     case XO_STYLE_SDPARAMS:
7093 	break;
7094 
7095     case XO_STYLE_ENCODER:
7096 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_CONTAINER, 0);
7097 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_CONTAINER, name, NULL, 0);
7098 	break;
7099     }
7100 
7101     return rc;
7102 }
7103 
7104 xo_ssize_t
xo_close_container_h(xo_handle_t * xop,const char * name)7105 xo_close_container_h (xo_handle_t *xop, const char *name)
7106 {
7107     return xo_transition(xop, 0, name, XSS_CLOSE_CONTAINER);
7108 }
7109 
7110 xo_ssize_t
xo_close_container(const char * name)7111 xo_close_container (const char *name)
7112 {
7113     return xo_close_container_h(NULL, name);
7114 }
7115 
7116 xo_ssize_t
xo_close_container_hd(xo_handle_t * xop)7117 xo_close_container_hd (xo_handle_t *xop)
7118 {
7119     return xo_close_container_h(xop, NULL);
7120 }
7121 
7122 xo_ssize_t
xo_close_container_d(void)7123 xo_close_container_d (void)
7124 {
7125     return xo_close_container_h(NULL, NULL);
7126 }
7127 
7128 static int
xo_do_open_list(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7129 xo_do_open_list (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7130 {
7131     ssize_t rc = 0;
7132     int indent = 0;
7133 
7134     xop = xo_default(xop);
7135 
7136     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7137     const char *pre_nl = "";
7138 
7139     switch (xo_style(xop)) {
7140     case XO_STYLE_JSON:
7141 
7142 	indent = 1;
7143 	if (!XOF_ISSET(xop, XOF_NO_TOP)
7144 		&& !XOIF_ISSET(xop, XOIF_TOP_EMITTED))
7145 	    xo_emit_top(xop, ppn);
7146 
7147 	if (name == NULL) {
7148 	    xo_failure(xop, "NULL passed for list name");
7149 	    name = XO_FAILURE_NAME;
7150 	}
7151 
7152 	xo_stack_set_flags(xop);
7153 
7154 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7155 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
7156 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7157 
7158 	/* If we need underscores, make a local copy and doctor it */
7159 	const char *new_name = name;
7160 	if (XOF_ISSET(xop, XOF_UNDERSCORES)) {
7161 	    size_t len = strlen(name);
7162 	    const char *old_name = name;
7163 	    char *buf, *cp, *ep;
7164 
7165 	    buf = alloca(len + 1);
7166 	    for (cp = buf, ep = buf + len + 1; cp < ep; cp++, old_name++)
7167 		*cp = (*old_name == '-') ? '_' : *old_name;
7168 	    new_name = buf;
7169 	}
7170 
7171 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
7172 		       pre_nl, xo_indent(xop), "", new_name, ppn);
7173 	break;
7174 
7175     case XO_STYLE_ENCODER:
7176 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LIST, name, NULL, flags);
7177 	break;
7178     }
7179 
7180     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LIST,
7181 		    XSF_LIST | xo_stack_flags(flags));
7182 
7183     return rc;
7184 }
7185 
7186 xo_ssize_t
xo_open_list_hf(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7187 xo_open_list_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7188 {
7189     return xo_transition(xop, flags, name, XSS_OPEN_LIST);
7190 }
7191 
7192 xo_ssize_t
xo_open_list_h(xo_handle_t * xop,const char * name)7193 xo_open_list_h (xo_handle_t *xop, const char *name)
7194 {
7195     return xo_open_list_hf(xop, 0, name);
7196 }
7197 
7198 xo_ssize_t
xo_open_list(const char * name)7199 xo_open_list (const char *name)
7200 {
7201     return xo_open_list_hf(NULL, 0, name);
7202 }
7203 
7204 xo_ssize_t
xo_open_list_hd(xo_handle_t * xop,const char * name)7205 xo_open_list_hd (xo_handle_t *xop, const char *name)
7206 {
7207     return xo_open_list_hf(xop, XOF_DTRT, name);
7208 }
7209 
7210 xo_ssize_t
xo_open_list_d(const char * name)7211 xo_open_list_d (const char *name)
7212 {
7213     return xo_open_list_hf(NULL, XOF_DTRT, name);
7214 }
7215 
7216 static int
xo_do_close_list(xo_handle_t * xop,const char * name)7217 xo_do_close_list (xo_handle_t *xop, const char *name)
7218 {
7219     ssize_t rc = 0;
7220     const char *pre_nl = "";
7221 
7222     if (name == NULL) {
7223 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7224 
7225 	name = xsp->xs_name;
7226 	if (name) {
7227 	    ssize_t len = strlen(name) + 1;
7228 	    /* We need to make a local copy; xo_depth_change will free it */
7229 	    char *cp = alloca(len);
7230 	    memcpy(cp, name, len);
7231 	    name = cp;
7232 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7233 	    xo_failure(xop, "missing name without 'dtrt' mode");
7234 	    name = XO_FAILURE_NAME;
7235 	}
7236     }
7237 
7238     switch (xo_style(xop)) {
7239     case XO_STYLE_JSON:
7240 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7241 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7242 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7243 
7244 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LIST, XSF_LIST);
7245 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
7246 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7247 	break;
7248 
7249     case XO_STYLE_ENCODER:
7250 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
7251 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LIST, name, NULL, 0);
7252 	break;
7253 
7254     default:
7255 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LIST, XSF_LIST);
7256 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7257 	break;
7258     }
7259 
7260     return rc;
7261 }
7262 
7263 xo_ssize_t
xo_close_list_h(xo_handle_t * xop,const char * name)7264 xo_close_list_h (xo_handle_t *xop, const char *name)
7265 {
7266     return xo_transition(xop, 0, name, XSS_CLOSE_LIST);
7267 }
7268 
7269 xo_ssize_t
xo_close_list(const char * name)7270 xo_close_list (const char *name)
7271 {
7272     return xo_close_list_h(NULL, name);
7273 }
7274 
7275 xo_ssize_t
xo_close_list_hd(xo_handle_t * xop)7276 xo_close_list_hd (xo_handle_t *xop)
7277 {
7278     return xo_close_list_h(xop, NULL);
7279 }
7280 
7281 xo_ssize_t
xo_close_list_d(void)7282 xo_close_list_d (void)
7283 {
7284     return xo_close_list_h(NULL, NULL);
7285 }
7286 
7287 static int
xo_do_open_leaf_list(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7288 xo_do_open_leaf_list (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7289 {
7290     ssize_t rc = 0;
7291     int indent = 0;
7292 
7293     xop = xo_default(xop);
7294 
7295     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7296     const char *pre_nl = "";
7297 
7298     switch (xo_style(xop)) {
7299     case XO_STYLE_JSON:
7300 	indent = 1;
7301 
7302 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
7303 	    if (!XOIF_ISSET(xop, XOIF_TOP_EMITTED)) {
7304 		xo_printf(xop, "%*s{%s", xo_indent(xop), "", ppn);
7305 		XOIF_SET(xop, XOIF_TOP_EMITTED);
7306 	    }
7307 	}
7308 
7309 	if (name == NULL) {
7310 	    xo_failure(xop, "NULL passed for list name");
7311 	    name = XO_FAILURE_NAME;
7312 	}
7313 
7314 	xo_stack_set_flags(xop);
7315 
7316 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7317 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
7318 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7319 
7320 	rc = xo_printf(xop, "%s%*s\"%s\": [%s",
7321 		       pre_nl, xo_indent(xop), "", name, ppn);
7322 	break;
7323 
7324     case XO_STYLE_ENCODER:
7325 	rc = xo_encoder_handle(xop, XO_OP_OPEN_LEAF_LIST, name, NULL, flags);
7326 	break;
7327     }
7328 
7329     xo_depth_change(xop, name, 1, indent, XSS_OPEN_LEAF_LIST,
7330 		    XSF_LIST | xo_stack_flags(flags));
7331 
7332     return rc;
7333 }
7334 
7335 static int
xo_do_close_leaf_list(xo_handle_t * xop,const char * name)7336 xo_do_close_leaf_list (xo_handle_t *xop, const char *name)
7337 {
7338     ssize_t rc = 0;
7339     const char *pre_nl = "";
7340 
7341     if (name == NULL) {
7342 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7343 
7344 	name = xsp->xs_name;
7345 	if (name) {
7346 	    ssize_t len = strlen(name) + 1;
7347 	    /* We need to make a local copy; xo_depth_change will free it */
7348 	    char *cp = alloca(len);
7349 	    memcpy(cp, name, len);
7350 	    name = cp;
7351 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7352 	    xo_failure(xop, "missing name without 'dtrt' mode");
7353 	    name = XO_FAILURE_NAME;
7354 	}
7355     }
7356 
7357     switch (xo_style(xop)) {
7358     case XO_STYLE_JSON:
7359 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7360 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7361 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7362 
7363 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_LEAF_LIST, XSF_LIST);
7364 	rc = xo_printf(xop, "%s%*s]", pre_nl, xo_indent(xop), "");
7365 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7366 	break;
7367 
7368     case XO_STYLE_ENCODER:
7369 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_LEAF_LIST, name, NULL, 0);
7370 	/* FALLTHRU */
7371 
7372     default:
7373 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_LEAF_LIST, XSF_LIST);
7374 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7375 	break;
7376     }
7377 
7378     return rc;
7379 }
7380 
7381 static int
xo_do_open_instance(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7382 xo_do_open_instance (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7383 {
7384     xop = xo_default(xop);
7385 
7386     ssize_t rc = 0;
7387     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7388     const char *pre_nl = "";
7389 
7390     if (name == NULL) {
7391 	xo_failure(xop, "NULL passed for instance name");
7392 	name = XO_FAILURE_NAME;
7393     }
7394 
7395     const char *leader = xo_xml_leader(xop, name);
7396     flags |= xop->xo_flags;
7397 
7398     switch (xo_style(xop)) {
7399     case XO_STYLE_XML:
7400 	rc = xo_printf(xop, "%*s<%s%s", xo_indent(xop), "", leader, name);
7401 
7402 	if (xop->xo_attrs.xb_curp != xop->xo_attrs.xb_bufp) {
7403 	    rc += xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp;
7404 	    xo_data_append(xop, xop->xo_attrs.xb_bufp,
7405 			   xop->xo_attrs.xb_curp - xop->xo_attrs.xb_bufp);
7406 	    xop->xo_attrs.xb_curp = xop->xo_attrs.xb_bufp;
7407 	}
7408 
7409 	rc += xo_printf(xop, ">%s", ppn);
7410 	break;
7411 
7412     case XO_STYLE_JSON:
7413 	xo_stack_set_flags(xop);
7414 
7415 	if (xop->xo_stack[xop->xo_depth].xs_flags & XSF_NOT_FIRST)
7416 	    pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? ",\n" : ", ";
7417 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7418 
7419 	rc = xo_printf(xop, "%s%*s{%s",
7420 		       pre_nl, xo_indent(xop), "", ppn);
7421 	break;
7422 
7423     case XO_STYLE_SDPARAMS:
7424 	break;
7425 
7426     case XO_STYLE_ENCODER:
7427 	rc = xo_encoder_handle(xop, XO_OP_OPEN_INSTANCE, name, NULL, flags);
7428 	break;
7429     }
7430 
7431     xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE, xo_stack_flags(flags));
7432 
7433     return rc;
7434 }
7435 
7436 xo_ssize_t
xo_open_instance_hf(xo_handle_t * xop,xo_xof_flags_t flags,const char * name)7437 xo_open_instance_hf (xo_handle_t *xop, xo_xof_flags_t flags, const char *name)
7438 {
7439     return xo_transition(xop, flags, name, XSS_OPEN_INSTANCE);
7440 }
7441 
7442 xo_ssize_t
xo_open_instance_h(xo_handle_t * xop,const char * name)7443 xo_open_instance_h (xo_handle_t *xop, const char *name)
7444 {
7445     return xo_open_instance_hf(xop, 0, name);
7446 }
7447 
7448 xo_ssize_t
xo_open_instance(const char * name)7449 xo_open_instance (const char *name)
7450 {
7451     return xo_open_instance_hf(NULL, 0, name);
7452 }
7453 
7454 xo_ssize_t
xo_open_instance_hd(xo_handle_t * xop,const char * name)7455 xo_open_instance_hd (xo_handle_t *xop, const char *name)
7456 {
7457     return xo_open_instance_hf(xop, XOF_DTRT, name);
7458 }
7459 
7460 xo_ssize_t
xo_open_instance_d(const char * name)7461 xo_open_instance_d (const char *name)
7462 {
7463     return xo_open_instance_hf(NULL, XOF_DTRT, name);
7464 }
7465 
7466 static int
xo_do_close_instance(xo_handle_t * xop,const char * name)7467 xo_do_close_instance (xo_handle_t *xop, const char *name)
7468 {
7469     xop = xo_default(xop);
7470 
7471     ssize_t rc = 0;
7472     const char *ppn = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7473     const char *pre_nl = "";
7474 
7475     if (name == NULL) {
7476 	xo_stack_t *xsp = &xop->xo_stack[xop->xo_depth];
7477 
7478 	name = xsp->xs_name;
7479 	if (name) {
7480 	    ssize_t len = strlen(name) + 1;
7481 	    /* We need to make a local copy; xo_depth_change will free it */
7482 	    char *cp = alloca(len);
7483 	    memcpy(cp, name, len);
7484 	    name = cp;
7485 	} else if (!(xsp->xs_flags & XSF_DTRT)) {
7486 	    xo_failure(xop, "missing name without 'dtrt' mode");
7487 	    name = XO_FAILURE_NAME;
7488 	}
7489     }
7490 
7491     const char *leader = xo_xml_leader(xop, name);
7492 
7493     switch (xo_style(xop)) {
7494     case XO_STYLE_XML:
7495 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
7496 	rc = xo_printf(xop, "%*s</%s%s>%s", xo_indent(xop), "", leader, name, ppn);
7497 	break;
7498 
7499     case XO_STYLE_JSON:
7500 	pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
7501 
7502 	xo_depth_change(xop, name, -1, -1, XSS_CLOSE_INSTANCE, 0);
7503 	rc = xo_printf(xop, "%s%*s}", pre_nl, xo_indent(xop), "");
7504 	xop->xo_stack[xop->xo_depth].xs_flags |= XSF_NOT_FIRST;
7505 	break;
7506 
7507     case XO_STYLE_HTML:
7508     case XO_STYLE_TEXT:
7509 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
7510 	break;
7511 
7512     case XO_STYLE_SDPARAMS:
7513 	break;
7514 
7515     case XO_STYLE_ENCODER:
7516 	xo_depth_change(xop, name, -1, 0, XSS_CLOSE_INSTANCE, 0);
7517 	rc = xo_encoder_handle(xop, XO_OP_CLOSE_INSTANCE, name, NULL, 0);
7518 	break;
7519     }
7520 
7521     return rc;
7522 }
7523 
7524 xo_ssize_t
xo_close_instance_h(xo_handle_t * xop,const char * name)7525 xo_close_instance_h (xo_handle_t *xop, const char *name)
7526 {
7527     return xo_transition(xop, 0, name, XSS_CLOSE_INSTANCE);
7528 }
7529 
7530 xo_ssize_t
xo_close_instance(const char * name)7531 xo_close_instance (const char *name)
7532 {
7533     return xo_close_instance_h(NULL, name);
7534 }
7535 
7536 xo_ssize_t
xo_close_instance_hd(xo_handle_t * xop)7537 xo_close_instance_hd (xo_handle_t *xop)
7538 {
7539     return xo_close_instance_h(xop, NULL);
7540 }
7541 
7542 xo_ssize_t
xo_close_instance_d(void)7543 xo_close_instance_d (void)
7544 {
7545     return xo_close_instance_h(NULL, NULL);
7546 }
7547 
7548 static int
xo_do_close_all(xo_handle_t * xop,xo_stack_t * limit)7549 xo_do_close_all (xo_handle_t *xop, xo_stack_t *limit)
7550 {
7551     xo_stack_t *xsp;
7552     ssize_t rc = 0;
7553     xo_xsf_flags_t flags;
7554 
7555     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp >= limit; xsp--) {
7556 	switch (xsp->xs_state) {
7557 	case XSS_INIT:
7558 	    /* Nothing */
7559 	    rc = 0;
7560 	    break;
7561 
7562 	case XSS_OPEN_CONTAINER:
7563 	    rc = xo_do_close_container(xop, NULL);
7564 	    break;
7565 
7566 	case XSS_OPEN_LIST:
7567 	    rc = xo_do_close_list(xop, NULL);
7568 	    break;
7569 
7570 	case XSS_OPEN_INSTANCE:
7571 	    rc = xo_do_close_instance(xop, NULL);
7572 	    break;
7573 
7574 	case XSS_OPEN_LEAF_LIST:
7575 	    rc = xo_do_close_leaf_list(xop, NULL);
7576 	    break;
7577 
7578 	case XSS_MARKER:
7579 	    flags = xsp->xs_flags & XSF_MARKER_FLAGS;
7580 	    xo_depth_change(xop, xsp->xs_name, -1, 0, XSS_MARKER, 0);
7581 	    xop->xo_stack[xop->xo_depth].xs_flags |= flags;
7582 	    rc = 0;
7583 	    break;
7584 	}
7585 
7586 	if (rc < 0)
7587 	    xo_failure(xop, "close %d failed: %d", xsp->xs_state, rc);
7588     }
7589 
7590     return 0;
7591 }
7592 
7593 /*
7594  * This function is responsible for clearing out whatever is needed
7595  * to get to the desired state, if possible.
7596  */
7597 static int
xo_do_close(xo_handle_t * xop,const char * name,xo_state_t new_state)7598 xo_do_close (xo_handle_t *xop, const char *name, xo_state_t new_state)
7599 {
7600     xo_stack_t *xsp, *limit = NULL;
7601     ssize_t rc;
7602     xo_state_t need_state = new_state;
7603 
7604     if (new_state == XSS_CLOSE_CONTAINER)
7605 	need_state = XSS_OPEN_CONTAINER;
7606     else if (new_state == XSS_CLOSE_LIST)
7607 	need_state = XSS_OPEN_LIST;
7608     else if (new_state == XSS_CLOSE_INSTANCE)
7609 	need_state = XSS_OPEN_INSTANCE;
7610     else if (new_state == XSS_CLOSE_LEAF_LIST)
7611 	need_state = XSS_OPEN_LEAF_LIST;
7612     else if (new_state == XSS_MARKER)
7613 	need_state = XSS_MARKER;
7614     else
7615 	return 0; /* Unknown or useless new states are ignored */
7616 
7617     for (xsp = &xop->xo_stack[xop->xo_depth]; xsp > xop->xo_stack; xsp--) {
7618 	/*
7619 	 * Marker's normally stop us from going any further, unless
7620 	 * we are popping a marker (new_state == XSS_MARKER).
7621 	 */
7622 	if (xsp->xs_state == XSS_MARKER && need_state != XSS_MARKER) {
7623 	    if (name) {
7624 		xo_failure(xop, "close (xo_%s) fails at marker '%s'; "
7625 			   "not found '%s'",
7626 			   xo_state_name(new_state),
7627 			   xsp->xs_name, name);
7628 		return 0;
7629 
7630 	    } else {
7631 		limit = xsp;
7632 		xo_failure(xop, "close stops at marker '%s'", xsp->xs_name);
7633 	    }
7634 	    break;
7635 	}
7636 
7637 	if (xsp->xs_state != need_state)
7638 	    continue;
7639 
7640 	if (name && xsp->xs_name && !xo_streq(name, xsp->xs_name))
7641 	    continue;
7642 
7643 	limit = xsp;
7644 	break;
7645     }
7646 
7647     if (limit == NULL) {
7648 	xo_failure(xop, "xo_%s can't find match for '%s'",
7649 		   xo_state_name(new_state), name);
7650 	return 0;
7651     }
7652 
7653     rc = xo_do_close_all(xop, limit);
7654 
7655     return rc;
7656 }
7657 
7658 /*
7659  * We are in a given state and need to transition to the new state.
7660  */
7661 static ssize_t
xo_transition(xo_handle_t * xop,xo_xof_flags_t flags,const char * name,xo_state_t new_state)7662 xo_transition (xo_handle_t *xop, xo_xof_flags_t flags, const char *name,
7663 	       xo_state_t new_state)
7664 {
7665     xo_stack_t *xsp;
7666     ssize_t rc = 0;
7667     int old_state, on_marker;
7668 
7669     xop = xo_default(xop);
7670 
7671     xsp = &xop->xo_stack[xop->xo_depth];
7672     old_state = xsp->xs_state;
7673     on_marker = (old_state == XSS_MARKER);
7674 
7675     /* If there's a marker on top of the stack, we need to find a real state */
7676     while (old_state == XSS_MARKER) {
7677 	if (xsp == xop->xo_stack)
7678 	    break;
7679 	xsp -= 1;
7680 	old_state = xsp->xs_state;
7681     }
7682 
7683     /*
7684      * At this point, the list of possible states are:
7685      *   XSS_INIT, XSS_OPEN_CONTAINER, XSS_OPEN_LIST,
7686      *   XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST, XSS_DISCARDING
7687      */
7688     switch (XSS_TRANSITION(old_state, new_state)) {
7689 
7690     open_container:
7691     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_CONTAINER):
7692     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_CONTAINER):
7693     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_CONTAINER):
7694        rc = xo_do_open_container(xop, flags, name);
7695        break;
7696 
7697     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_CONTAINER):
7698     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_CONTAINER):
7699 	if (on_marker)
7700 	    goto marker_prevents_close;
7701 	rc = xo_do_close_leaf_list(xop, NULL);
7702 	if (rc >= 0)
7703 	    goto open_container;
7704 	break;
7705 
7706     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_CONTAINER):
7707 	/* This is an exception for "xo --close" */
7708 	rc = xo_do_close_container(xop, name);
7709 	break;
7710 
7711     /*close_container:*/
7712     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_CONTAINER):
7713     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_CONTAINER):
7714     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_CONTAINER):
7715 	if (on_marker)
7716 	    goto marker_prevents_close;
7717 	rc = xo_do_close(xop, name, new_state);
7718 	break;
7719 
7720     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_CONTAINER):
7721 	if (on_marker)
7722 	    goto marker_prevents_close;
7723 	rc = xo_do_close_leaf_list(xop, NULL);
7724 	if (rc >= 0)
7725 	    rc = xo_do_close(xop, name, new_state);
7726 	break;
7727 
7728     open_list:
7729     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LIST):
7730     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LIST):
7731     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LIST):
7732 	rc = xo_do_open_list(xop, flags, name);
7733 	break;
7734 
7735     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LIST):
7736 	if (on_marker)
7737 	    goto marker_prevents_close;
7738 	rc = xo_do_close_list(xop, NULL);
7739 	if (rc >= 0)
7740 	    goto open_list;
7741 	break;
7742 
7743     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LIST):
7744 	if (on_marker)
7745 	    goto marker_prevents_close;
7746 	rc = xo_do_close_leaf_list(xop, NULL);
7747 	if (rc >= 0)
7748 	    goto open_list;
7749 	break;
7750 
7751     /*close_list:*/
7752     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LIST):
7753 	if (on_marker)
7754 	    goto marker_prevents_close;
7755 	rc = xo_do_close(xop, name, new_state);
7756 	break;
7757 
7758     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LIST):
7759     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LIST):
7760     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LIST):
7761     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LIST):
7762 	rc = xo_do_close(xop, name, new_state);
7763 	break;
7764 
7765     open_instance:
7766     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_INSTANCE):
7767 	rc = xo_do_open_instance(xop, flags, name);
7768 	break;
7769 
7770     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_INSTANCE):
7771     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_INSTANCE):
7772 	rc = xo_do_open_list(xop, flags, name);
7773 	if (rc >= 0)
7774 	    goto open_instance;
7775 	break;
7776 
7777     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_INSTANCE):
7778 	if (on_marker) {
7779 	    rc = xo_do_open_list(xop, flags, name);
7780 	} else {
7781 	    rc = xo_do_close_instance(xop, NULL);
7782 	}
7783 	if (rc >= 0)
7784 	    goto open_instance;
7785 	break;
7786 
7787     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_INSTANCE):
7788 	if (on_marker)
7789 	    goto marker_prevents_close;
7790 	rc = xo_do_close_leaf_list(xop, NULL);
7791 	if (rc >= 0)
7792 	    goto open_instance;
7793 	break;
7794 
7795     /*close_instance:*/
7796     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_INSTANCE):
7797 	if (on_marker)
7798 	    goto marker_prevents_close;
7799 	rc = xo_do_close_instance(xop, name);
7800 	break;
7801 
7802     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_INSTANCE):
7803 	/* This one makes no sense; ignore it */
7804 	xo_failure(xop, "xo_close_instance ignored when called from "
7805 		   "initial state ('%s')", name ?: "(unknown)");
7806 	break;
7807 
7808     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_INSTANCE):
7809     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_INSTANCE):
7810 	if (on_marker)
7811 	    goto marker_prevents_close;
7812 	rc = xo_do_close(xop, name, new_state);
7813 	break;
7814 
7815     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_INSTANCE):
7816 	if (on_marker)
7817 	    goto marker_prevents_close;
7818 	rc = xo_do_close_leaf_list(xop, NULL);
7819 	if (rc >= 0)
7820 	    rc = xo_do_close(xop, name, new_state);
7821 	break;
7822 
7823     open_leaf_list:
7824     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_OPEN_LEAF_LIST):
7825     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_OPEN_LEAF_LIST):
7826     case XSS_TRANSITION(XSS_INIT, XSS_OPEN_LEAF_LIST):
7827 	rc = xo_do_open_leaf_list(xop, flags, name);
7828 	break;
7829 
7830     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_OPEN_LEAF_LIST):
7831     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_OPEN_LEAF_LIST):
7832 	if (on_marker)
7833 	    goto marker_prevents_close;
7834 	rc = xo_do_close_list(xop, NULL);
7835 	if (rc >= 0)
7836 	    goto open_leaf_list;
7837 	break;
7838 
7839     /*close_leaf_list:*/
7840     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_CLOSE_LEAF_LIST):
7841 	if (on_marker)
7842 	    goto marker_prevents_close;
7843 	rc = xo_do_close_leaf_list(xop, name);
7844 	break;
7845 
7846     case XSS_TRANSITION(XSS_INIT, XSS_CLOSE_LEAF_LIST):
7847 	/* Makes no sense; ignore */
7848 	xo_failure(xop, "xo_close_leaf_list ignored when called from "
7849 		   "initial state ('%s')", name ?: "(unknown)");
7850 	break;
7851 
7852     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_CLOSE_LEAF_LIST):
7853     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_CLOSE_LEAF_LIST):
7854     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_CLOSE_LEAF_LIST):
7855 	if (on_marker)
7856 	    goto marker_prevents_close;
7857 	rc = xo_do_close(xop, name, new_state);
7858 	break;
7859 
7860     /*emit:*/
7861     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT):
7862     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT):
7863 	break;
7864 
7865     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT):
7866 	if (on_marker)
7867 	    goto marker_prevents_close;
7868 	rc = xo_do_close(xop, NULL, XSS_CLOSE_LIST);
7869 	break;
7870 
7871     case XSS_TRANSITION(XSS_INIT, XSS_EMIT):
7872 	break;
7873 
7874     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT):
7875 	if (on_marker)
7876 	    goto marker_prevents_close;
7877 	rc = xo_do_close_leaf_list(xop, NULL);
7878 	break;
7879 
7880     /*emit_leaf_list:*/
7881     case XSS_TRANSITION(XSS_INIT, XSS_EMIT_LEAF_LIST):
7882     case XSS_TRANSITION(XSS_OPEN_CONTAINER, XSS_EMIT_LEAF_LIST):
7883     case XSS_TRANSITION(XSS_OPEN_INSTANCE, XSS_EMIT_LEAF_LIST):
7884 	rc = xo_do_open_leaf_list(xop, flags, name);
7885 	break;
7886 
7887     case XSS_TRANSITION(XSS_OPEN_LEAF_LIST, XSS_EMIT_LEAF_LIST):
7888 	break;
7889 
7890     case XSS_TRANSITION(XSS_OPEN_LIST, XSS_EMIT_LEAF_LIST):
7891 	/*
7892 	 * We need to be backward compatible with the pre-xo_open_leaf_list
7893 	 * API, where both lists and leaf-lists were opened as lists.  So
7894 	 * if we find an open list that hasn't had anything written to it,
7895 	 * we'll accept it.
7896 	 */
7897 	break;
7898 
7899     default:
7900 	xo_failure(xop, "unknown transition: (%u -> %u)",
7901 		   xsp->xs_state, new_state);
7902     }
7903 
7904     /* Handle the flush flag */
7905     if (rc >= 0 && XOF_ISSET(xop, XOF_FLUSH))
7906 	if (xo_flush_h(xop) < 0)
7907 	    rc = -1;
7908 
7909     /* We have now official made output */
7910     XOIF_SET(xop, XOIF_MADE_OUTPUT);
7911 
7912     return rc;
7913 
7914  marker_prevents_close:
7915     xo_failure(xop, "marker '%s' prevents transition from %s to %s",
7916 	       xop->xo_stack[xop->xo_depth].xs_name,
7917 	       xo_state_name(old_state), xo_state_name(new_state));
7918     return -1;
7919 }
7920 
7921 xo_ssize_t
xo_open_marker_h(xo_handle_t * xop,const char * name)7922 xo_open_marker_h (xo_handle_t *xop, const char *name)
7923 {
7924     xop = xo_default(xop);
7925 
7926     xo_depth_change(xop, name, 1, 0, XSS_MARKER,
7927 		    xop->xo_stack[xop->xo_depth].xs_flags & XSF_MARKER_FLAGS);
7928 
7929     return 0;
7930 }
7931 
7932 xo_ssize_t
xo_open_marker(const char * name)7933 xo_open_marker (const char *name)
7934 {
7935     return xo_open_marker_h(NULL, name);
7936 }
7937 
7938 xo_ssize_t
xo_close_marker_h(xo_handle_t * xop,const char * name)7939 xo_close_marker_h (xo_handle_t *xop, const char *name)
7940 {
7941     xop = xo_default(xop);
7942 
7943     return xo_do_close(xop, name, XSS_MARKER);
7944 }
7945 
7946 xo_ssize_t
xo_close_marker(const char * name)7947 xo_close_marker (const char *name)
7948 {
7949     return xo_close_marker_h(NULL, name);
7950 }
7951 
7952 /*
7953  * Record custom output functions into the xo handle, allowing
7954  * integration with a variety of output frameworks.
7955  */
7956 void
xo_set_writer(xo_handle_t * xop,void * opaque,xo_write_func_t write_func,xo_close_func_t close_func,xo_flush_func_t flush_func)7957 xo_set_writer (xo_handle_t *xop, void *opaque, xo_write_func_t write_func,
7958 	       xo_close_func_t close_func, xo_flush_func_t flush_func)
7959 {
7960     xop = xo_default(xop);
7961 
7962     xop->xo_opaque = opaque;
7963     xop->xo_write = write_func;
7964     xop->xo_close = close_func;
7965     xop->xo_flush = flush_func;
7966 }
7967 
7968 void
xo_set_allocator(xo_realloc_func_t realloc_func,xo_free_func_t free_func)7969 xo_set_allocator (xo_realloc_func_t realloc_func, xo_free_func_t free_func)
7970 {
7971     xo_realloc = realloc_func;
7972     xo_free = free_func;
7973 }
7974 
7975 xo_ssize_t
xo_flush_h(xo_handle_t * xop)7976 xo_flush_h (xo_handle_t *xop)
7977 {
7978     ssize_t rc;
7979 
7980     xop = xo_default(xop);
7981 
7982     switch (xo_style(xop)) {
7983     case XO_STYLE_ENCODER:
7984 	xo_encoder_handle(xop, XO_OP_FLUSH, NULL, NULL, 0);
7985     }
7986 
7987     rc = xo_write(xop);
7988     if (rc >= 0 && xop->xo_flush)
7989 	if (xop->xo_flush(xop->xo_opaque) < 0)
7990 	    return -1;
7991 
7992     return rc;
7993 }
7994 
7995 xo_ssize_t
xo_flush(void)7996 xo_flush (void)
7997 {
7998     return xo_flush_h(NULL);
7999 }
8000 
8001 xo_ssize_t
xo_finish_h(xo_handle_t * xop)8002 xo_finish_h (xo_handle_t *xop)
8003 {
8004     const char *open_if_empty = "";
8005     xop = xo_default(xop);
8006 
8007     if (!XOF_ISSET(xop, XOF_NO_CLOSE))
8008 	xo_do_close_all(xop, xop->xo_stack);
8009 
8010     switch (xo_style(xop)) {
8011     case XO_STYLE_JSON:
8012 	if (!XOF_ISSET(xop, XOF_NO_TOP)) {
8013 	    const char *pre_nl = XOF_ISSET(xop, XOF_PRETTY) ? "\n" : "";
8014 
8015 	    if (XOIF_ISSET(xop, XOIF_TOP_EMITTED))
8016 		XOIF_CLEAR(xop, XOIF_TOP_EMITTED); /* Turn off before output */
8017 	    else if (!XOIF_ISSET(xop, XOIF_MADE_OUTPUT)) {
8018 		open_if_empty = "{ ";
8019 		pre_nl = "";
8020 	    }
8021 
8022 	    xo_printf(xop, "%s%*s%s}\n",
8023 		      pre_nl, xo_indent(xop), "", open_if_empty);
8024 	}
8025 	break;
8026 
8027     case XO_STYLE_ENCODER:
8028 	xo_encoder_handle(xop, XO_OP_FINISH, NULL, NULL, 0);
8029 	break;
8030     }
8031 
8032     return xo_flush_h(xop);
8033 }
8034 
8035 xo_ssize_t
xo_finish(void)8036 xo_finish (void)
8037 {
8038     return xo_finish_h(NULL);
8039 }
8040 
8041 /*
8042  * xo_finish_atexit is suitable for atexit() calls, to force clear up
8043  * and finalizing output.
8044  */
8045 void
xo_finish_atexit(void)8046 xo_finish_atexit (void)
8047 {
8048     (void) xo_finish_h(NULL);
8049 }
8050 
8051 /*
8052  * Generate an error message, such as would be displayed on stderr
8053  */
8054 void
xo_errorn_hv(xo_handle_t * xop,int need_newline,const char * fmt,va_list vap)8055 xo_errorn_hv (xo_handle_t *xop, int need_newline, const char *fmt, va_list vap)
8056 {
8057     xop = xo_default(xop);
8058 
8059     /*
8060      * If the format string doesn't end with a newline, we pop
8061      * one on ourselves.
8062      */
8063     if (need_newline) {
8064 	ssize_t len = strlen(fmt);
8065 	if (len > 0 && fmt[len - 1] != '\n') {
8066 	    char *newfmt = alloca(len + 2);
8067 	    memcpy(newfmt, fmt, len);
8068 	    newfmt[len] = '\n';
8069 	    newfmt[len + 1] = '\0';
8070 	    fmt = newfmt;
8071 	}
8072     }
8073 
8074     switch (xo_style(xop)) {
8075     case XO_STYLE_TEXT:
8076 	vfprintf(stderr, fmt, vap);
8077 	break;
8078 
8079     case XO_STYLE_HTML:
8080 	va_copy(xop->xo_vap, vap);
8081 
8082 	xo_buf_append_div(xop, "error", 0, NULL, 0, NULL, 0,
8083 			  fmt, strlen(fmt), NULL, 0);
8084 
8085 	if (XOIF_ISSET(xop, XOIF_DIV_OPEN))
8086 	    xo_line_close(xop);
8087 
8088 	xo_write(xop);
8089 
8090 	va_end(xop->xo_vap);
8091 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
8092 	break;
8093 
8094     case XO_STYLE_XML:
8095     case XO_STYLE_JSON:
8096 	va_copy(xop->xo_vap, vap);
8097 
8098 	xo_open_container_h(xop, "error");
8099 	xo_format_value(xop, "message", 7, NULL, 0,
8100 			fmt, strlen(fmt), NULL, 0, 0);
8101 	xo_close_container_h(xop, "error");
8102 
8103 	va_end(xop->xo_vap);
8104 	bzero(&xop->xo_vap, sizeof(xop->xo_vap));
8105 	break;
8106 
8107     case XO_STYLE_SDPARAMS:
8108     case XO_STYLE_ENCODER:
8109 	break;
8110     }
8111 }
8112 
8113 void
xo_error_h(xo_handle_t * xop,const char * fmt,...)8114 xo_error_h (xo_handle_t *xop, const char *fmt, ...)
8115 {
8116     va_list vap;
8117 
8118     va_start(vap, fmt);
8119     xo_errorn_hv(xop, 0, fmt, vap);
8120     va_end(vap);
8121 }
8122 
8123 /*
8124  * Generate an error message, such as would be displayed on stderr
8125  */
8126 void
xo_error(const char * fmt,...)8127 xo_error (const char *fmt, ...)
8128 {
8129     va_list vap;
8130 
8131     va_start(vap, fmt);
8132     xo_errorn_hv(NULL, 0, fmt, vap);
8133     va_end(vap);
8134 }
8135 
8136 void
xo_errorn_h(xo_handle_t * xop,const char * fmt,...)8137 xo_errorn_h (xo_handle_t *xop, const char *fmt, ...)
8138 {
8139     va_list vap;
8140 
8141     va_start(vap, fmt);
8142     xo_errorn_hv(xop, 1, fmt, vap);
8143     va_end(vap);
8144 }
8145 
8146 /*
8147  * Generate an error message, such as would be displayed on stderr
8148  */
8149 void
xo_errorn(const char * fmt,...)8150 xo_errorn (const char *fmt, ...)
8151 {
8152     va_list vap;
8153 
8154     va_start(vap, fmt);
8155     xo_errorn_hv(NULL, 1, fmt, vap);
8156     va_end(vap);
8157 }
8158 
8159 /*
8160  * Parse any libxo-specific options from the command line, removing them
8161  * so the main() argument parsing won't see them.  We return the new value
8162  * for argc or -1 for error.  If an error occurred, the program should
8163  * exit.  A suitable error message has already been displayed.
8164  */
8165 int
xo_parse_args(int argc,char ** argv)8166 xo_parse_args (int argc, char **argv)
8167 {
8168     static char libxo_opt[] = "--libxo";
8169     char *cp;
8170     int i, save;
8171 
8172     /*
8173      * If xo_set_program has always been called, we honor that value
8174      */
8175     if (xo_program == NULL) {
8176 	/* Save our program name for xo_err and friends */
8177 	xo_program = argv[0];
8178 	cp = strrchr(xo_program, '/');
8179 	if (cp)
8180 	    xo_program = ++cp;
8181 	else
8182 	    cp = argv[0];		/* Reset to front of string */
8183 
8184 	/*
8185 	 * GNU libtool add an annoying ".test" as the program
8186 	 * extension; we remove it.  libtool also adds a "lt-" prefix
8187 	 * that we cannot remove.
8188 	 */
8189 	size_t len = strlen(xo_program);
8190 	static const char gnu_ext[] = ".test";
8191 	if (len >= sizeof(gnu_ext)) {
8192 	    cp += len + 1 - sizeof(gnu_ext);
8193 	    if (xo_streq(cp, gnu_ext))
8194 		*cp = '\0';
8195 	}
8196     }
8197 
8198     xo_handle_t *xop = xo_default(NULL);
8199 
8200     for (save = i = 1; i < argc; i++) {
8201 	if (argv[i] == NULL
8202 	    || strncmp(argv[i], libxo_opt, sizeof(libxo_opt) - 1) != 0) {
8203 	    if (save != i)
8204 		argv[save] = argv[i];
8205 	    save += 1;
8206 	    continue;
8207 	}
8208 
8209 	cp = argv[i] + sizeof(libxo_opt) - 1;
8210 	if (*cp == '\0') {
8211 	    cp = argv[++i];
8212 	    if (cp == NULL) {
8213 		xo_warnx("missing libxo option");
8214 		return -1;
8215 	    }
8216 
8217 	    if (xo_set_options(xop, cp) < 0)
8218 		return -1;
8219 	} else if (*cp == ':') {
8220 	    if (xo_set_options(xop, cp) < 0)
8221 		return -1;
8222 
8223 	} else if (*cp == '=') {
8224 	    if (xo_set_options(xop, ++cp) < 0)
8225 		return -1;
8226 
8227 	} else if (*cp == '-') {
8228 	    cp += 1;
8229 	    if (xo_streq(cp, "check")) {
8230 		exit(XO_HAS_LIBXO);
8231 
8232 	    } else {
8233 		xo_warnx("unknown libxo option: '%s'", argv[i]);
8234 		return -1;
8235 	    }
8236 	} else {
8237 		xo_warnx("unknown libxo option: '%s'", argv[i]);
8238 	    return -1;
8239 	}
8240     }
8241 
8242     /*
8243      * We only want to do color output on terminals, but we only want
8244      * to do this if the user has asked for color.
8245      */
8246     if (XOF_ISSET(xop, XOF_COLOR_ALLOWED) && isatty(1))
8247 	XOF_SET(xop, XOF_COLOR);
8248 
8249     argv[save] = NULL;
8250     return save;
8251 }
8252 
8253 /*
8254  * Debugging function that dumps the current stack of open libxo constructs,
8255  * suitable for calling from the debugger.
8256  */
8257 void
xo_dump_stack(xo_handle_t * xop)8258 xo_dump_stack (xo_handle_t *xop)
8259 {
8260     int i;
8261     xo_stack_t *xsp;
8262 
8263     xop = xo_default(xop);
8264 
8265     fprintf(stderr, "Stack dump:\n");
8266 
8267     xsp = xop->xo_stack;
8268     for (i = 1, xsp++; i <= xop->xo_depth; i++, xsp++) {
8269 	fprintf(stderr, "   [%d] %s '%s' [%x]\n",
8270 		i, xo_state_name(xsp->xs_state),
8271 		xsp->xs_name ?: "--", xsp->xs_flags);
8272     }
8273 }
8274 
8275 /*
8276  * Record the program name used for error messages
8277  */
8278 void
xo_set_program(const char * name)8279 xo_set_program (const char *name)
8280 {
8281     xo_program = name;
8282 }
8283 
8284 void
xo_set_version_h(xo_handle_t * xop,const char * version)8285 xo_set_version_h (xo_handle_t *xop, const char *version)
8286 {
8287     xop = xo_default(xop);
8288 
8289     if (version == NULL || strchr(version, '"') != NULL)
8290 	return;
8291 
8292     if (!xo_style_is_encoding(xop))
8293 	return;
8294 
8295     switch (xo_style(xop)) {
8296     case XO_STYLE_XML:
8297 	/* For XML, we record this as an attribute for the first tag */
8298 	xo_attr_h(xop, "version", "%s", version);
8299 	break;
8300 
8301     case XO_STYLE_JSON:
8302 	/*
8303 	 * For JSON, we record the version string in our handle, and emit
8304 	 * it in xo_emit_top.
8305 	 */
8306 	xop->xo_version = xo_strndup(version, -1);
8307 	break;
8308 
8309     case XO_STYLE_ENCODER:
8310 	xo_encoder_handle(xop, XO_OP_VERSION, NULL, version, 0);
8311 	break;
8312     }
8313 }
8314 
8315 /*
8316  * Set the version number for the API content being carried through
8317  * the xo handle.
8318  */
8319 void
xo_set_version(const char * version)8320 xo_set_version (const char *version)
8321 {
8322     xo_set_version_h(NULL, version);
8323 }
8324 
8325 /*
8326  * Generate a warning.  Normally, this is a text message written to
8327  * standard error.  If the XOF_WARN_XML flag is set, then we generate
8328  * XMLified content on standard output.
8329  */
8330 void
xo_emit_warn_hcv(xo_handle_t * xop,int as_warning,int code,const char * fmt,va_list vap)8331 xo_emit_warn_hcv (xo_handle_t *xop, int as_warning, int code,
8332 		  const char *fmt, va_list vap)
8333 {
8334     xop = xo_default(xop);
8335 
8336     if (fmt == NULL)
8337 	return;
8338 
8339     xo_open_marker_h(xop, "xo_emit_warn_hcv");
8340     xo_open_container_h(xop, as_warning ? "__warning" : "__error");
8341 
8342     if (xo_program)
8343 	xo_emit("{wc:program}", xo_program);
8344 
8345     if (xo_style(xop) == XO_STYLE_XML || xo_style(xop) == XO_STYLE_JSON) {
8346 	va_list ap;
8347 	xo_handle_t temp;
8348 
8349 	bzero(&temp, sizeof(temp));
8350 	temp.xo_style = XO_STYLE_TEXT;
8351 	xo_buf_init(&temp.xo_data);
8352 	xo_depth_check(&temp, XO_DEPTH);
8353 
8354 	va_copy(ap, vap);
8355 	(void) xo_emit_hv(&temp, fmt, ap);
8356 	va_end(ap);
8357 
8358 	xo_buffer_t *src = &temp.xo_data;
8359 	xo_format_value(xop, "message", 7, src->xb_bufp,
8360 			src->xb_curp - src->xb_bufp, NULL, 0, NULL, 0, 0);
8361 
8362 	xo_free(temp.xo_stack);
8363 	xo_buf_cleanup(src);
8364     }
8365 
8366     (void) xo_emit_hv(xop, fmt, vap);
8367 
8368     ssize_t len = strlen(fmt);
8369     if (len > 0 && fmt[len - 1] != '\n') {
8370 	if (code > 0) {
8371 	    const char *msg = strerror(code);
8372 	    if (msg)
8373 		xo_emit_h(xop, ": {G:strerror}{g:error/%s}", msg);
8374 	}
8375 	xo_emit("\n");
8376     }
8377 
8378     xo_close_marker_h(xop, "xo_emit_warn_hcv");
8379     xo_flush_h(xop);
8380 }
8381 
8382 void
xo_emit_warn_hc(xo_handle_t * xop,int code,const char * fmt,...)8383 xo_emit_warn_hc (xo_handle_t *xop, int code, const char *fmt, ...)
8384 {
8385     va_list vap;
8386 
8387     va_start(vap, fmt);
8388     xo_emit_warn_hcv(xop, 1, code, fmt, vap);
8389     va_end(vap);
8390 }
8391 
8392 void
xo_emit_warn_c(int code,const char * fmt,...)8393 xo_emit_warn_c (int code, const char *fmt, ...)
8394 {
8395     va_list vap;
8396 
8397     va_start(vap, fmt);
8398     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
8399     va_end(vap);
8400 }
8401 
8402 void
xo_emit_warn(const char * fmt,...)8403 xo_emit_warn (const char *fmt, ...)
8404 {
8405     int code = errno;
8406     va_list vap;
8407 
8408     va_start(vap, fmt);
8409     xo_emit_warn_hcv(NULL, 1, code, fmt, vap);
8410     va_end(vap);
8411 }
8412 
8413 void
xo_emit_warnx(const char * fmt,...)8414 xo_emit_warnx (const char *fmt, ...)
8415 {
8416     va_list vap;
8417 
8418     va_start(vap, fmt);
8419     xo_emit_warn_hcv(NULL, 1, -1, fmt, vap);
8420     va_end(vap);
8421 }
8422 
8423 void
xo_emit_err_v(int eval,int code,const char * fmt,va_list vap)8424 xo_emit_err_v (int eval, int code, const char *fmt, va_list vap)
8425 {
8426     xo_emit_warn_hcv(NULL, 0, code, fmt, vap);
8427     xo_finish();
8428     exit(eval);
8429 }
8430 
8431 void
xo_emit_err(int eval,const char * fmt,...)8432 xo_emit_err (int eval, const char *fmt, ...)
8433 {
8434     int code = errno;
8435     va_list vap;
8436     va_start(vap, fmt);
8437     xo_emit_err_v(eval, code, fmt, vap);
8438     /*NOTREACHED*/
8439 }
8440 
8441 void
xo_emit_errx(int eval,const char * fmt,...)8442 xo_emit_errx (int eval, const char *fmt, ...)
8443 {
8444     va_list vap;
8445 
8446     va_start(vap, fmt);
8447     xo_emit_err_v(eval, -1, fmt, vap); /* This will exit */
8448     /*NOTREACHED*/
8449 }
8450 
8451 void
xo_emit_errc(int eval,int code,const char * fmt,...)8452 xo_emit_errc (int eval, int code, const char *fmt, ...)
8453 {
8454     va_list vap;
8455 
8456     va_start(vap, fmt);
8457     xo_emit_err_v(eval, code, fmt, vap); /* This will exit */
8458     /*NOTREACHED*/
8459 }
8460 
8461 /*
8462  * Get the opaque private pointer for an xo handle
8463  */
8464 void *
xo_get_private(xo_handle_t * xop)8465 xo_get_private (xo_handle_t *xop)
8466 {
8467     xop = xo_default(xop);
8468     return xop->xo_private;
8469 }
8470 
8471 /*
8472  * Set the opaque private pointer for an xo handle.
8473  */
8474 void
xo_set_private(xo_handle_t * xop,void * opaque)8475 xo_set_private (xo_handle_t *xop, void *opaque)
8476 {
8477     xop = xo_default(xop);
8478     xop->xo_private = opaque;
8479 }
8480 
8481 /*
8482  * Get the encoder function
8483  */
8484 xo_encoder_func_t
xo_get_encoder(xo_handle_t * xop)8485 xo_get_encoder (xo_handle_t *xop)
8486 {
8487     xop = xo_default(xop);
8488     return xop->xo_encoder;
8489 }
8490 
8491 /*
8492  * Record an encoder callback function in an xo handle.
8493  */
8494 void
xo_set_encoder(xo_handle_t * xop,xo_encoder_func_t encoder)8495 xo_set_encoder (xo_handle_t *xop, xo_encoder_func_t encoder)
8496 {
8497     xop = xo_default(xop);
8498 
8499     xop->xo_style = XO_STYLE_ENCODER;
8500     xop->xo_encoder = encoder;
8501 }
8502 
8503 /*
8504  * The xo(1) utility needs to be able to open and close lists and
8505  * instances, but since it's called without "state", we cannot
8506  * rely on the state transitions (in xo_transition) to DTRT, so
8507  * we have a mechanism for external parties to "force" transitions
8508  * that would otherwise be impossible.  This is not a general
8509  * mechanism, and is really tailored only for xo(1).
8510  */
8511 void
xo_explicit_transition(xo_handle_t * xop,xo_state_t new_state,const char * name,xo_xof_flags_t flags)8512 xo_explicit_transition (xo_handle_t *xop, xo_state_t new_state,
8513 			const char *name, xo_xof_flags_t flags)
8514 {
8515     xo_xsf_flags_t xsf_flags;
8516 
8517     xop = xo_default(xop);
8518 
8519     switch (new_state) {
8520 
8521     case XSS_OPEN_LIST:
8522 	xo_do_open_list(xop, flags, name);
8523 	break;
8524 
8525     case XSS_OPEN_INSTANCE:
8526 	xo_do_open_instance(xop, flags, name);
8527 	break;
8528 
8529     case XSS_CLOSE_INSTANCE:
8530 	xo_depth_change(xop, name, 1, 1, XSS_OPEN_INSTANCE,
8531 			xo_stack_flags(flags));
8532 	xo_stack_set_flags(xop);
8533 	xo_do_close_instance(xop, name);
8534 	break;
8535 
8536     case XSS_CLOSE_LIST:
8537 	xsf_flags = XOF_ISSET(xop, XOF_NOT_FIRST) ? XSF_NOT_FIRST : 0;
8538 
8539 	xo_depth_change(xop, name, 1, 1, XSS_OPEN_LIST,
8540 			XSF_LIST | xsf_flags | xo_stack_flags(flags));
8541 	xo_do_close_list(xop, name);
8542 	break;
8543     }
8544 }
8545