1/*	$NetBSD: fparseln.c,v 1.7 2007/03/08 19:57:53 drochner Exp $	*/
2
3/*-
4 * SPDX-License-Identifier: BSD-4-Clause
5 *
6 * Copyright (c) 1997 Christos Zoulas.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *	This product includes software developed by Christos Zoulas.
19 * 4. The name of the author may not be used to endorse or promote products
20 *    derived from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include <sys/types.h>
35#include <assert.h>
36#include <errno.h>
37#include <stdio.h>
38#include <string.h>
39#include <stdlib.h>
40#include <libutil.h>
41
42static int isescaped(const char *, const char *, int);
43
44/* isescaped():
45 *	Return true if the character in *p that belongs to a string
46 *	that starts in *sp, is escaped by the escape character esc.
47 */
48static int
49isescaped(const char *sp, const char *p, int esc)
50{
51	const char     *cp;
52	size_t		ne;
53
54#if 0
55	_DIAGASSERT(sp != NULL);
56	_DIAGASSERT(p != NULL);
57#endif
58
59	/* No escape character */
60	if (esc == '\0')
61		return 0;
62
63	/* Count the number of escape characters that precede ours */
64	for (ne = 0, cp = p; --cp >= sp && *cp == esc; ne++)
65		continue;
66
67	/* Return true if odd number of escape characters */
68	return (ne & 1) != 0;
69}
70
71
72/* fparseln():
73 *	Read a line from a file parsing continuations ending in \
74 *	and eliminating trailing newlines, or comments starting with
75 *	the comment char.
76 */
77char *
78fparseln(FILE *fp, size_t *size, size_t *lineno, const char str[3], int flags)
79{
80	static const char dstr[3] = { '\\', '\\', '#' };
81
82	size_t	s, len;
83	char   *buf;
84	char   *ptr, *cp;
85	int	cnt;
86	char	esc, con, nl, com;
87
88#if 0
89	_DIAGASSERT(fp != NULL);
90#endif
91
92	len = 0;
93	buf = NULL;
94	cnt = 1;
95
96	if (str == NULL)
97		str = dstr;
98
99	esc = str[0];
100	con = str[1];
101	com = str[2];
102	/*
103	 * XXX: it would be cool to be able to specify the newline character,
104	 * but unfortunately, fgetln does not let us
105	 */
106	nl  = '\n';
107
108	while (cnt) {
109		cnt = 0;
110
111		if (lineno)
112			(*lineno)++;
113
114		if ((ptr = fgetln(fp, &s)) == NULL)
115			break;
116
117		if (s && com) {		/* Check and eliminate comments */
118			for (cp = ptr; cp < ptr + s; cp++)
119				if (*cp == com && !isescaped(ptr, cp, esc)) {
120					s = cp - ptr;
121					cnt = s == 0 && buf == NULL;
122					break;
123				}
124		}
125
126		if (s && nl) { 		/* Check and eliminate newlines */
127			cp = &ptr[s - 1];
128
129			if (*cp == nl)
130				s--;	/* forget newline */
131		}
132
133		if (s && con) {		/* Check and eliminate continuations */
134			cp = &ptr[s - 1];
135
136			if (*cp == con && !isescaped(ptr, cp, esc)) {
137				s--;	/* forget continuation char */
138				cnt = 1;
139			}
140		}
141
142		if (s == 0) {
143			/*
144			 * nothing to add, skip realloc except in case
145			 * we need a minimal buf to return an empty line
146			 */
147			if (cnt || buf != NULL)
148				continue;
149		}
150
151		if ((cp = realloc(buf, len + s + 1)) == NULL) {
152			free(buf);
153			return NULL;
154		}
155		buf = cp;
156
157		(void) memcpy(buf + len, ptr, s);
158		len += s;
159		buf[len] = '\0';
160	}
161
162	if ((flags & FPARSELN_UNESCALL) != 0 && esc && buf != NULL &&
163	    strchr(buf, esc) != NULL) {
164		ptr = cp = buf;
165		while (cp[0] != '\0') {
166			int skipesc;
167
168			while (cp[0] != '\0' && cp[0] != esc)
169				*ptr++ = *cp++;
170			if (cp[0] == '\0' || cp[1] == '\0')
171				break;
172
173			skipesc = 0;
174			if (cp[1] == com)
175				skipesc += (flags & FPARSELN_UNESCCOMM);
176			if (cp[1] == con)
177				skipesc += (flags & FPARSELN_UNESCCONT);
178			if (cp[1] == esc)
179				skipesc += (flags & FPARSELN_UNESCESC);
180			if (cp[1] != com && cp[1] != con && cp[1] != esc)
181				skipesc = (flags & FPARSELN_UNESCREST);
182
183			if (skipesc)
184				cp++;
185			else
186				*ptr++ = *cp++;
187			*ptr++ = *cp++;
188		}
189		*ptr = '\0';
190		len = strlen(buf);
191	}
192
193	if (size)
194		*size = len;
195	return buf;
196}
197
198#ifdef TEST
199
200int
201main(int argc, char *argv[])
202{
203	char   *ptr;
204	size_t	size, line;
205
206	line = 0;
207	while ((ptr = fparseln(stdin, &size, &line, NULL,
208	    FPARSELN_UNESCALL)) != NULL)
209		printf("line %d (%d) |%s|\n", line, size, ptr);
210	return 0;
211}
212
213/*
214
215# This is a test
216line 1
217line 2 \
218line 3 # Comment
219line 4 \# Not comment \\\\
220
221# And a comment \
222line 5 \\\
223line 6
224
225*/
226
227#endif /* TEST */
228