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 * 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 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 * 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 * 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 * 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 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 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 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 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 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