Home | History | Annotate | Line # | Download | only in src
      1 /*	$NetBSD: is_csv.c,v 1.6 2026/06/10 20:54:16 christos Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2019 Christos Zoulas
      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  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     26  * POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 /*
     30  * Parse CSV object serialization format (RFC-4180, RFC-7111)
     31  */
     32 
     33 #ifndef TEST
     34 #include "file.h"
     35 
     36 #ifndef lint
     37 #if 0
     38 FILE_RCSID("@(#)$File: is_csv.c,v 1.16 2026/06/02 17:50:54 christos Exp $")
     39 #else
     40 __RCSID("$NetBSD: is_csv.c,v 1.6 2026/06/10 20:54:16 christos Exp $");
     41 #endif
     42 #endif
     43 
     44 #include <string.h>
     45 #include "magic.h"
     46 #else
     47 #define CAST(a, b)	((a)(b))
     48 #include <sys/types.h>
     49 #endif
     50 
     51 
     52 #ifdef DEBUG
     53 #include <stdio.h>
     54 #define DPRINTF(fmt, ...) printf(fmt, __VA_ARGS__)
     55 #else
     56 #define DPRINTF(fmt, ...)
     57 #endif
     58 
     59 /*
     60  * if CSV_LINES == 0:
     61  *	check all the lines in the buffer
     62  * otherwise:
     63  *	check only up-to the number of lines specified
     64  *
     65  * the last line count is always ignored if it does not end in CRLF
     66  */
     67 #ifndef CSV_LINES
     68 #define CSV_LINES 10
     69 #endif
     70 
     71 static int csv_parse(const unsigned char *, const unsigned char *);
     72 
     73 static const unsigned char *
     74 eatquote(const unsigned char *uc, const unsigned char *ue)
     75 {
     76 	int quote = 0;
     77 
     78 	while (uc < ue) {
     79 		unsigned char c = *uc++;
     80 		if (c != '"') {
     81 			// We already got one, done.
     82 			if (quote) {
     83 				return --uc;
     84 			}
     85 			continue;
     86 		}
     87 		if (quote) {
     88 			// quote-quote escapes
     89 			quote = 0;
     90 			continue;
     91 		}
     92 		// first quote
     93 		quote = 1;
     94 	}
     95 	return ue;
     96 }
     97 
     98 static int
     99 csv_parse(const unsigned char *uc, const unsigned char *ue)
    100 {
    101 	size_t nf = 0, tf = 0, nl = 0;
    102 
    103 	while (uc < ue) {
    104 		switch (*uc++) {
    105 		case '"':
    106 			// Eat until the matching quote
    107 			uc = eatquote(uc, ue);
    108 			break;
    109 		case ',':
    110 			nf++;
    111 			break;
    112 		case '\n':
    113 			DPRINTF("nl=%zu nf=%zu tf=%zu\n", nl, nf, tf);
    114 			nl++;
    115 #if CSV_LINES
    116 			if (nl == CSV_LINES)
    117 				return tf > 1 && tf == nf;
    118 #endif
    119 			if (tf == 0) {
    120 				// First time and no fields, give up
    121 				if (nf == 0)
    122 					return 0;
    123 				// First time, set the number of fields
    124 				tf = nf;
    125 			} else if (tf != nf) {
    126 				// Field number mismatch, we are done.
    127 				return 0;
    128 			}
    129 			nf = 0;
    130 			break;
    131 		default:
    132 			break;
    133 		}
    134 	}
    135 	DPRINTF("tf=%zu>1 nl=%zu>=2\n", tf, nl);
    136 	return tf >= 1 && nl >= 2;
    137 }
    138 
    139 #ifndef TEST
    140 int
    141 file_is_csv(struct magic_set *ms, const struct buffer *b, int looks_text,
    142     const char *code)
    143 {
    144 	const unsigned char *uc = CAST(const unsigned char *, b->fbuf);
    145 	const unsigned char *ue = uc + b->flen;
    146 	int mime = ms->flags & MAGIC_MIME;
    147 
    148 	if (!looks_text)
    149 		return 0;
    150 
    151 	if ((ms->flags & (MAGIC_APPLE|MAGIC_EXTENSION)) != 0)
    152 		return 0;
    153 
    154 	if (!csv_parse(uc, ue))
    155 		return 0;
    156 
    157 	if (mime == MAGIC_MIME_ENCODING)
    158 		return 1;
    159 
    160 	if (mime) {
    161 		if (file_printf(ms, "text/csv") == -1)
    162 			return -1;
    163 		return 1;
    164 	}
    165 
    166 	if (file_printf(ms, "CSV %s%stext", code ? code : "",
    167 	    code ? " " : "") == -1)
    168 		return -1;
    169 
    170 	return 1;
    171 }
    172 
    173 #else
    174 
    175 #include <sys/types.h>
    176 #include <sys/stat.h>
    177 #include <stdio.h>
    178 #include <fcntl.h>
    179 #include <unistd.h>
    180 #include <stdlib.h>
    181 #include <stdint.h>
    182 #include <err.h>
    183 
    184 int
    185 main(int argc, char *argv[])
    186 {
    187 	int fd;
    188 	struct stat st;
    189 	unsigned char *p;
    190 
    191 	if ((fd = open(argv[1], O_RDONLY)) == -1)
    192 		err(EXIT_FAILURE, "Can't open `%s'", argv[1]);
    193 
    194 	if (fstat(fd, &st) == -1)
    195 		err(EXIT_FAILURE, "Can't stat `%s'", argv[1]);
    196 
    197 	if ((p = CAST(unsigned char *, malloc(st.st_size))) == NULL)
    198 		err(EXIT_FAILURE, "Can't allocate %jd bytes",
    199 		    (intmax_t)st.st_size);
    200 	if (read(fd, p, st.st_size) != st.st_size)
    201 		err(EXIT_FAILURE, "Can't read %jd bytes",
    202 		    (intmax_t)st.st_size);
    203 	printf("is csv %d\n", csv_parse(p, p + st.st_size));
    204 	return 0;
    205 }
    206 #endif
    207