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