xref: /freebsd/contrib/pkgconf/libpkgconf/fileio.c (revision f9bc005b8ef3b507a5ecbc2d7fe4411ef70eff38)
1 /*
2  * fileio.c
3  * File reading utilities
4  *
5  * SPDX-License-Identifier: pkgconf
6  *
7  * Copyright (c) 2012, 2025 pkgconf authors (see AUTHORS).
8  *
9  * Permission to use, copy, modify, and/or distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * This software is provided 'as is' and without any warranty, express or
14  * implied.  In no event shall the authors be liable for any damages arising
15  * from the use of this software.
16  */
17 
18 #include <libpkgconf/stdinc.h>
19 #include <libpkgconf/libpkgconf.h>
20 
21 #define push_or_return_fail(buf, c) \
22 	do { if (!pkgconf_buffer_push_byte((buf), (char) (c))) return false; } while (0)
23 
24 #define trim_or_return_fail(buf) \
25 	do { if (!pkgconf_buffer_trim_byte((buf))) return false; } while (0)
26 
27 bool
28 pkgconf_fgetline(pkgconf_buffer_t *buffer, FILE *stream)
29 {
30 	bool quoted = false;
31 	bool got_data = false;
32 	char in[PKGCONF_ITEM_SIZE];
33 	long unread = 0;
34 
35 	while (fgets(in, sizeof in, stream) != NULL)
36 	{
37 		char *p = in;
38 
39 		got_data = true;
40 
41 		while (*p != '\0')
42 		{
43 			unsigned char c = (unsigned char) *p++;
44 
45 			if (c == '\\' && !quoted)
46 			{
47 				quoted = true;
48 				continue;
49 			}
50 			else if (c == '\n')
51 			{
52 				if (quoted)
53 				{
54 					quoted = false;
55 					continue;
56 				}
57 				else
58 					push_or_return_fail(buffer, (char) c);
59 
60 				goto done;
61 			}
62 			else if (c == '\r')
63 			{
64 				if (*p == '\n')
65 				{
66 					p++;
67 				}
68 				else if (*p == '\0')
69 				{
70 					/*
71 					 * The matching '\n' may not have been read into `in`
72 					 * yet if '\r' landed exactly on the fgets() buffer
73 					 * boundary. Peek the real stream so a split CRLF
74 					 * isn't misparsed as two lines.
75 					 */
76 					int next = getc(stream);
77 
78 					if (next != '\n' && next != EOF && ungetc(next, stream) == EOF)
79 						return false;
80 				}
81 
82 				if (quoted)
83 				{
84 					quoted = false;
85 					continue;
86 				}
87 
88 				push_or_return_fail(buffer, '\n');
89 				/* unlike '\n', a lone '\r' doesn't bound the fgets() call above */
90 				unread = (long) strlen(p);
91 				goto done;
92 			}
93 			else
94 			{
95 				if (quoted)
96 				{
97 					push_or_return_fail(buffer, '\\');
98 					quoted = false;
99 				}
100 
101 				push_or_return_fail(buffer, (char) c);
102 			}
103 		}
104 	}
105 
106 done:
107 	if (unread > 0 && fseek(stream, -unread, SEEK_CUR) != 0)
108 		return false;
109 
110 	/* Remove newline character. */
111 	if (pkgconf_buffer_lastc(buffer) == '\n')
112 		trim_or_return_fail(buffer);
113 
114 	if (pkgconf_buffer_lastc(buffer) == '\r')
115 		trim_or_return_fail(buffer);
116 
117 	if (!got_data)
118 		return false;
119 
120 	return !ferror(stream);
121 }
122