1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* kdc/tdumputil.c - utilities for tab-separated, etc. files */
3 /*
4 * Copyright (C) 2015 by the Massachusetts Institute of Technology.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include "k5-int.h"
34 #include "k5-platform.h" /* for vasprintf */
35 #include <assert.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40
41 #include "tdumputil.h"
42
43 /*
44 * Structure describing flavor of a tabular output format.
45 *
46 * fieldsep is the field separator
47 *
48 * recordsep is the record/line separator
49 *
50 * quotechar begins and ends a quoted field. If an instance of quotechar
51 * occurs within a quoted field value, it is doubled.
52 *
53 * Values are only quoted if they contain fieldsep, recordsep, or quotechar.
54 */
55 struct flavor {
56 int fieldsep; /* field separator */
57 int recordsep; /* record separator */
58 int quotechar; /* quote character */
59 };
60
61 struct rechandle {
62 FILE *fh;
63 const char *rectype;
64 int do_sep;
65 struct flavor flavor;
66 };
67
68 static const struct flavor tabsep = {
69 '\t', /* fieldsep */
70 '\n', /* recordsep */
71 '\0' /* quotechar */
72 };
73
74 static const struct flavor csv = {
75 ',', /* fieldsep */
76 '\n', /* recordsep */
77 '"' /* quotechar */
78 };
79
80 /*
81 * Double any quote characters present in a quoted field.
82 */
83 static char *
qquote(struct flavor * fl,const char * s)84 qquote(struct flavor *fl, const char *s)
85 {
86 const char *sp;
87 struct k5buf buf;
88
89 k5_buf_init_dynamic(&buf);
90 for (sp = s; *sp != '\0'; sp++) {
91 k5_buf_add_len(&buf, sp, 1);
92 if (*sp == fl->quotechar)
93 k5_buf_add_len(&buf, sp, 1);
94 }
95 return k5_buf_cstring(&buf);
96 }
97
98 /*
99 * Write an optionally quoted field.
100 */
101 static int
writequoted(struct rechandle * h,const char * fmt,va_list ap)102 writequoted(struct rechandle *h, const char *fmt, va_list ap)
103 {
104 int doquote = 0, ret;
105 char *s = NULL, *qs = NULL;
106 struct flavor fl = h->flavor;
107
108 assert(fl.quotechar != '\0');
109 ret = vasprintf(&s, fmt, ap);
110 if (ret < 0)
111 return ret;
112 if (strchr(s, fl.fieldsep) != NULL)
113 doquote = 1;
114 if (strchr(s, fl.recordsep) != NULL)
115 doquote = 1;
116 if (strchr(s, fl.quotechar) != NULL)
117 doquote = 1;
118
119 if (doquote) {
120 qs = qquote(&fl, s);
121 if (qs == NULL) {
122 ret = -1;
123 goto cleanup;
124 }
125 ret = fprintf(h->fh, "%c%s%c", fl.quotechar, qs, fl.quotechar);
126 } else {
127 ret = fprintf(h->fh, "%s", s);
128 }
129 cleanup:
130 free(s);
131 free(qs);
132 return ret;
133 }
134
135 /*
136 * Return a rechandle with the requested file handle and rectype.
137 *
138 * rectype must be a valid pointer for the entire lifetime of the rechandle (or
139 * null)
140 */
141 static struct rechandle *
rechandle_common(FILE * fh,const char * rectype)142 rechandle_common(FILE *fh, const char *rectype)
143 {
144 struct rechandle *h = calloc(1, sizeof(*h));
145
146 if (h == NULL)
147 return NULL;
148 h->fh = fh;
149 h->rectype = rectype;
150 h->do_sep = 0;
151 return h;
152 }
153
154 /*
155 * Return a rechandle for tab-separated output.
156 */
157 struct rechandle *
rechandle_tabsep(FILE * fh,const char * rectype)158 rechandle_tabsep(FILE *fh, const char *rectype)
159 {
160 struct rechandle *h = rechandle_common(fh, rectype);
161
162 if (h == NULL)
163 return NULL;
164 h->flavor = tabsep;
165 return h;
166 }
167
168 /*
169 * Return a rechandle for CSV output.
170 */
171 struct rechandle *
rechandle_csv(FILE * fh,const char * rectype)172 rechandle_csv(FILE *fh, const char *rectype)
173 {
174 struct rechandle *h = rechandle_common(fh, rectype);
175
176 if (h == NULL)
177 return NULL;
178 h->flavor = csv;
179 return h;
180 }
181
182 /*
183 * Free a rechandle.
184 */
185 void
rechandle_free(struct rechandle * h)186 rechandle_free(struct rechandle *h)
187 {
188 free(h);
189 }
190
191 /*
192 * Start a record. This includes writing a record type prefix (rectype) if
193 * specified.
194 */
195 int
startrec(struct rechandle * h)196 startrec(struct rechandle *h)
197 {
198 if (h->rectype == NULL) {
199 h->do_sep = 0;
200 return 0;
201 }
202 h->do_sep = 1;
203 return fputs(h->rectype, h->fh);
204 }
205
206 /*
207 * Write a single field of a record. This includes writing a separator
208 * character, if appropriate.
209 */
210 int
writefield(struct rechandle * h,const char * fmt,...)211 writefield(struct rechandle *h, const char *fmt, ...)
212 {
213 int ret = 0;
214 va_list ap;
215 struct flavor fl = h->flavor;
216
217 if (h->do_sep) {
218 ret = fputc(fl.fieldsep, h->fh);
219 if (ret < 0)
220 return ret;
221 }
222 h->do_sep = 1;
223 va_start(ap, fmt);
224 if (fl.quotechar == '\0')
225 ret = vfprintf(h->fh, fmt, ap);
226 else
227 ret = writequoted(h, fmt, ap);
228 va_end(ap);
229 return ret;
230 }
231
232 /*
233 * Finish a record (line).
234 */
235 int
endrec(struct rechandle * h)236 endrec(struct rechandle *h)
237 {
238 int ret = 0;
239 struct flavor fl = h->flavor;
240
241 ret = fputc(fl.recordsep, h->fh);
242 h->do_sep = 0;
243 return ret;
244 }
245
246 /*
247 * Write a header line if h->rectype is null. (If rectype is set, it will be
248 * prefixed to output lines, most likely in a mixed record type output file, so
249 * it doesn't make sense to output a header line in that case.)
250 */
251 int
writeheader(struct rechandle * h,char * const * a)252 writeheader(struct rechandle *h, char * const *a)
253 {
254 int ret = 0;
255 char * const *p;
256
257 if (h->rectype != NULL)
258 return 0;
259 for (p = a; *p != NULL; p++) {
260 ret = writefield(h, "%s", *p);
261 if (ret < 0)
262 return ret;
263 }
264 ret = endrec(h);
265 return ret;
266 }
267