1 /*
2 * Copyright (c) 2010, 2011 Ryan Flannery <ryan.flannery@gmail.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include "str2argv.h"
18
19 /* initialize empty argc/argv struct */
20 void
argv_init(int * argc,char *** argv)21 argv_init(int *argc, char ***argv)
22 {
23 if ((*argv = calloc(ARGV_MAX_ENTRIES, sizeof(char*))) == NULL)
24 err(1, "argv_init: argv calloc fail");
25
26 if (((*argv)[0] = calloc(ARGV_MAX_TOKEN_LEN, sizeof(char))) == NULL)
27 err(1, "argv_init: argv[i] calloc fail");
28
29 bzero((*argv)[0], ARGV_MAX_TOKEN_LEN * sizeof(char));
30 *argc = 0;
31 }
32
33 /* free all memory in an arc/argv */
34 void
argv_free(int * argc,char *** argv)35 argv_free(int *argc, char ***argv)
36 {
37 int i;
38
39 for (i = 0; i <= *argc; i++)
40 free((*argv)[i]);
41
42 free(*argv);
43 *argc = 0;
44 }
45
46 /* add a character to the end of the current entry in an argc/argv */
47 void
argv_addch(int argc,char ** argv,int c)48 argv_addch(int argc, char **argv, int c)
49 {
50 int n;
51
52 n = strlen(argv[argc]);
53 if (n == ARGV_MAX_TOKEN_LEN - 1)
54 errx(1, "argv_addch: reached max token length (%d)", ARGV_MAX_TOKEN_LEN);
55
56 argv[argc][n] = c;
57 }
58
59 /* complete the current entry in the argc/argv and setup the next one */
60 void
argv_finish_token(int * argc,char *** argv)61 argv_finish_token(int *argc, char ***argv)
62 {
63 if (*argc == ARGV_MAX_ENTRIES - 1)
64 errx(1, "argv_finish_token: reached max argv entries(%d)", ARGV_MAX_ENTRIES);
65
66 if (strlen((*argv)[*argc]) == 0)
67 return;
68
69 *argc = *argc + 1;
70 if (((*argv)[*argc] = calloc(ARGV_MAX_TOKEN_LEN, sizeof(char))) == NULL)
71 err(1, "argv_finish_token: failed to calloc argv[i]");
72
73 bzero((*argv)[*argc], ARGV_MAX_TOKEN_LEN * sizeof(char));
74 }
75
76 /*
77 * Main parser used for converting a string (str) to an argc/argv style
78 * parameter list. This handles escape sequences and quoting. Possibly
79 * correctly. :D
80 * The argc/argv parameters passed are over-written. After they have been
81 * built by this function, the caller should use argv_free() on them to
82 * free() all associated memory.
83 * If the parsing goes correctly, 0 is returned. Otherwise, 1 is returned
84 * and the errmsg parameter is set to some appropriate error message and
85 * both argc/argv are set to 0/NULL.
86 */
87 int
str2argv(char * str,int * argc,char *** argv,const char ** errmsg)88 str2argv(char *str, int *argc, char ***argv, const char **errmsg)
89 {
90 bool in_token;
91 bool in_container;
92 bool escaped;
93 char container_start;
94 char c;
95 int len;
96 int i;
97
98 const char *ERRORS[2] = {
99 "Unmatched quotes",
100 "Unused/Dangling escape sequence"
101 };
102 *errmsg = NULL;
103
104 container_start = 0;
105 in_token = false;
106 in_container = false;
107 escaped = false;
108
109 len = strlen(str);
110
111 argv_init(argc, argv);
112 for (i = 0; i < len; i++) {
113 c = str[i];
114
115 switch (c) {
116 /* handle whitespace */
117 case ' ':
118 case '\t':
119 case '\n':
120 if (!in_token)
121 continue;
122
123 if (in_container) {
124 argv_addch(*argc, *argv, c);
125 continue;
126 }
127
128 if (escaped) {
129 escaped = false;
130 argv_addch(*argc, *argv, c);
131 continue;
132 }
133
134 /* if reached here, we're at end of token */
135 in_token = false;
136 argv_finish_token(argc, argv);
137 break;
138
139 /* handle quotes */
140 case '\'':
141 case '\"':
142
143 if (escaped) {
144 argv_addch(*argc, *argv, c);
145 escaped = false;
146 continue;
147 }
148
149 if (!in_token) {
150 in_token = true;
151 in_container = true;
152 container_start = c;
153 continue;
154 }
155
156 if (in_token && !in_container) {
157 in_container = true;
158 container_start = c;
159 continue;
160 }
161
162 if (in_container) {
163 if (c == container_start) {
164 in_container = false;
165 in_token = false;
166 argv_finish_token(argc, argv);
167 continue;
168 } else {
169 argv_addch(*argc, *argv, c);
170 continue;
171 }
172 }
173
174 *errmsg = ERRORS[0];
175 argv_free(argc, argv);
176 return 1;
177
178 case '\\':
179 if (in_container && str[i+1] != container_start) {
180 argv_addch(*argc, *argv, c);
181 continue;
182 }
183
184 if (escaped) {
185 escaped = false;
186 argv_addch(*argc, *argv, c);
187 continue;
188 }
189
190 escaped = true;
191 break;
192
193 default:
194 if (!in_token)
195 in_token = true;
196
197 if (escaped)
198 escaped = false;
199
200 argv_addch(*argc, *argv, c);
201 }
202 }
203 argv_finish_token(argc, argv);
204
205 if (in_container) {
206 argv_free(argc, argv);
207 *errmsg = ERRORS[0];
208 return 1;
209 }
210
211 if (escaped) {
212 argv_free(argc, argv);
213 *errmsg = ERRORS[1];
214 return 1;
215 }
216
217 return 0;
218 }
219
220 char *
argv2str(int argc,char * argv[])221 argv2str(int argc, char *argv[])
222 {
223 char *result;
224 int len;
225 int off;
226 int i;
227
228 /* determine length of resulting string */
229 len = 0;
230 for (i = 0; i < argc; i++) {
231 len += strlen(argv[i]) + 1;
232 if (strstr(argv[i], " ") != NULL)
233 len += 2;
234 }
235
236 /* allocate result */
237 if ((result = calloc(len, sizeof(char))) == NULL)
238 err(1, "argv2str: calloc failed");
239 bzero(result, len);
240
241 /* build result */
242 off = 0;
243 for (i = 0; i < argc; i++) {
244 if (strstr(argv[i], " ") == NULL)
245 off += snprintf(result + off, len, "%s ", argv[i]);
246 else
247 off += snprintf(result + off, len, "\'%s\' ", argv[i]);
248 }
249
250 return result;
251 }
252
253