xref: /freebsd/contrib/pkgconf/libpkgconf/argvsplit.c (revision a3cefe7f2b4df0f70ff92d4570ce18e517af43ec)
1 /*
2  * argvsplit.c
3  * argv_split() routine
4  *
5  * Copyright (c) 2012, 2017 pkgconf authors (see AUTHORS).
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * This software is provided 'as is' and without any warranty, express or
12  * implied.  In no event shall the authors be liable for any damages arising
13  * from the use of this software.
14  */
15 
16 #include <libpkgconf/stdinc.h>
17 #include <libpkgconf/libpkgconf.h>
18 
19 /*
20  * !doc
21  *
22  * libpkgconf `argvsplit` module
23  * =============================
24  *
25  * This is a lowlevel module which provides parsing of strings into argument vectors,
26  * similar to what a shell would do.
27  */
28 
29 /*
30  * !doc
31  *
32  * .. c:function:: void pkgconf_argv_free(char **argv)
33  *
34  *    Frees an argument vector.
35  *
36  *    :param char** argv: The argument vector to free.
37  *    :return: nothing
38  */
39 void
pkgconf_argv_free(char ** argv)40 pkgconf_argv_free(char **argv)
41 {
42 	free(argv[0]);
43 	free(argv);
44 }
45 
46 /*
47  * !doc
48  *
49  * .. c:function:: int pkgconf_argv_split(const char *src, int *argc, char ***argv)
50  *
51  *    Splits a string into an argument vector.
52  *
53  *    :param char*   src: The string to split.
54  *    :param int*    argc: A pointer to an integer to store the argument count.
55  *    :param char*** argv: A pointer to a pointer for an argument vector.
56  *    :return: 0 on success, -1 on error.
57  *    :rtype: int
58  */
59 int
pkgconf_argv_split(const char * src,int * argc,char *** argv)60 pkgconf_argv_split(const char *src, int *argc, char ***argv)
61 {
62 	char *buf = calloc(1, strlen(src) + 1);
63 	if (buf == NULL)
64 		return -1;
65 
66 	const char *src_iter;
67 	char *dst_iter;
68 	int argc_count = 0;
69 	int argv_size = 5;
70 	char quote = 0;
71 	bool escaped = false;
72 
73 	src_iter = src;
74 	dst_iter = buf;
75 
76 	*argv = calloc(argv_size, sizeof (void *));
77 	if (*argv == NULL)
78 	{
79 		free(buf);
80 		return -1;
81 	}
82 
83 	(*argv)[argc_count] = dst_iter;
84 
85 	while (*src_iter)
86 	{
87 		if (escaped)
88 		{
89 			/* POSIX: only \CHAR is special inside a double quote if CHAR is {$, `, ", \, newline}. */
90 			if (quote == '"')
91 			{
92 				if (!(*src_iter == '$' || *src_iter == '`' || *src_iter == '"' || *src_iter == '\\'))
93 					*dst_iter++ = '\\';
94 
95 				*dst_iter++ = *src_iter;
96 			}
97 			else
98 			{
99 				*dst_iter++ = *src_iter;
100 			}
101 
102 			escaped = false;
103 		}
104 		else if (quote)
105 		{
106 			if (*src_iter == quote)
107 				quote = 0;
108 			else if (*src_iter == '\\' && quote != '\'')
109 				escaped = true;
110 			else
111 				*dst_iter++ = *src_iter;
112 		}
113 		else if (isspace((unsigned char)*src_iter))
114 		{
115 			if ((*argv)[argc_count] != NULL)
116 			{
117 				argc_count++, dst_iter++;
118 
119 				if (argc_count == argv_size)
120 				{
121 					argv_size += 5;
122 					*argv = realloc(*argv, sizeof(void *) * argv_size);
123 				}
124 
125 				(*argv)[argc_count] = dst_iter;
126 			}
127 		}
128 		else switch(*src_iter)
129 		{
130 			case '\\':
131 				escaped = true;
132 				break;
133 
134 			case '\"':
135 			case '\'':
136 				quote = *src_iter;
137 				break;
138 
139 			default:
140 				*dst_iter++ = *src_iter;
141 				break;
142 		}
143 
144 		src_iter++;
145 	}
146 
147 	if (escaped || quote)
148 	{
149 		free(*argv);
150 		free(buf);
151 		return -1;
152 	}
153 
154 	if (strlen((*argv)[argc_count]))
155 	{
156 		argc_count++;
157 	}
158 
159 	*argc = argc_count;
160 	return 0;
161 }
162