xref: /haiku/src/add-ons/input_server/filters/shortcut_catcher/ParseCommandLine.cpp (revision 1ecf19b82f7ebecaebf9611cd11badaa09fae087)
1 /*
2  * Copyright 1999-2009 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Jeremy Friesner
7  *		Fredrik Modéen
8  */
9 
10 
11 #include "ParseCommandLine.h"
12 
13 #include <stdio.h>
14 #include <string.h>
15 #include <unistd.h>
16 
17 #include <Directory.h>
18 #include <Entry.h>
19 #include <FindDirectory.h>
20 #include <List.h>
21 #include <Path.h>
22 #include <Roster.h>
23 #include <String.h>
24 #include <SupportKit.h>
25 
26 
27 const char* kTrackerSignature = "application/x-vnd.Be-TRAK";
28 
29 
30 // This char is used to hold words together into single words...
31 #define GUNK_CHAR 0x01
32 
33 
34 // Turn all spaces that are not-to-be-counted-as-spaces into GUNK_CHAR chars.
35 static void
GunkSpaces(char * string)36 GunkSpaces(char* string)
37 {
38 	bool insideQuote = false;
39 	bool afterBackslash = false;
40 
41 	while (*string != '\0') {
42 		switch(*string) {
43 			case '\"':
44 				if (!afterBackslash) {
45 					// toggle escapement mode
46 					insideQuote = !insideQuote;
47 				}
48 			break;
49 
50 			case ' ':
51 			case '\t':
52 				if ((insideQuote)||(afterBackslash))
53 					*string = GUNK_CHAR;
54 			break;
55 		}
56 
57 		afterBackslash = (*string == '\\') ? !afterBackslash : false;
58 		string++;
59 	}
60 }
61 
62 
63 // Removes all un-escaped quotes and backslashes from the string, in place
64 static void
RemoveQuotes(char * string)65 RemoveQuotes(char* string)
66 {
67 	bool afterBackslash = false;
68 	char* endString = strchr(string, '\0');
69 	char* to = string;
70 
71 	while (*string != '\0') {
72 		bool temp = (*string == '\\') ? !afterBackslash : false;
73 		switch(*string) {
74 			case '\"':
75 			case '\\':
76 				if (afterBackslash)
77 					*(to++) = *string;
78 				break;
79 
80 			case 'n':
81 				*(to++) = afterBackslash ? '\n' : *string;
82 				break;
83 
84 			case 't':
85 				*(to++) = afterBackslash ? '\t' : *string;
86 				break;
87 
88 			default:
89 				*(to++) = *string;
90 		}
91 
92 		afterBackslash = temp;
93 		string++;
94 	}
95 	*to = '\0';
96 
97 	if (to < endString) {
98 		// needs to be double-terminated!
99 		*(to + 1) = '\0';
100 	}
101 }
102 
103 
104 static bool
IsValidChar(char c)105 IsValidChar(char c)
106 {
107 	return ((c > ' ')||(c == '\n')||(c == '\t'));
108 }
109 
110 
111 // Returns true if it is returning valid results into (*setBegin) & (*setEnd).
112 // If returning true, (*setBegin) now points to the first char in a new word,
113 // and (*setEnd) now points to the char after the last char in the word, which
114 // has been set to a NUL byte.
115 static bool
GetNextWord(char ** setBegin,char ** setEnd)116 GetNextWord(char** setBegin, char** setEnd)
117 {
118 	char* next = *setEnd;
119 		// we'll start one after the end of the last one...
120 
121 	while (next++) {
122 		if (*next == '\0') {
123 			// no words left!
124 			return false;
125 		}
126 		else if ((IsValidChar(*next) == false) && (*next != GUNK_CHAR))
127 			*next = '\0';
128 		else {
129 			// found a non-whitespace char!
130 			break;
131 		}
132 	}
133 
134 	*setBegin = next;
135 		// we found the first char!
136 
137 	while (next++) {
138 		if ((IsValidChar(*next) == false) && (*next != GUNK_CHAR)) {
139 			*next = '\0';
140 				// terminate the word
141 			*setEnd = next;
142 			return true;
143 		}
144 	}
145 
146 	return false;
147 		// should never get here, actually
148 }
149 
150 
151 // Turns the gunk back into spaces
152 static void
UnGunk(char * str)153 UnGunk(char* str)
154 {
155 	char* temp = str;
156 	while (*temp) {
157 		if (*temp == GUNK_CHAR)
158 			*temp = ' ';
159 
160 		temp++;
161 	}
162 }
163 
164 
165 char**
ParseArgvFromString(const char * command,int32 & argc)166 ParseArgvFromString(const char* command, int32& argc)
167 {
168 	// make our own copy of the string...
169 	int length = strlen(command);
170 
171 	// need an extra \0 byte to get GetNextWord() to stop
172 	char* cmd = new char[length + 2];
173 	strcpy(cmd, command);
174 	cmd[length + 1] = '\0';
175 		// zero out the second nul byte
176 
177 	GunkSpaces(cmd);
178 	RemoveQuotes(cmd);
179 
180 	BList wordlist;
181 	char* beginWord = NULL, *endWord = cmd - 1;
182 
183 	while (GetNextWord(&beginWord, &endWord))
184 		wordlist.AddItem(beginWord);
185 
186 	argc = wordlist.CountItems();
187 	char** argv = new char* [argc + 1];
188 	for (int i = 0; i < argc; i++) {
189 		char* temp = (char*) wordlist.ItemAt(i);
190 		argv[i] = new char[strlen(temp) + 1];
191 		strcpy(argv[i], temp);
192 
193 		// turn space-markers back into real spaces...
194 		UnGunk(argv[i]);
195 	}
196 	argv[argc] = NULL;
197 		// terminate the array
198 	delete[] cmd;
199 		// don't need our local copy any more
200 
201 	return argv;
202 }
203 
204 
205 void
FreeArgv(char ** argv)206 FreeArgv(char** argv)
207 {
208 	if (argv != NULL) {
209 		int i = 0;
210 		while (argv[i] != NULL) {
211 			delete[] argv[i];
212 			i++;
213 		}
214 	}
215 
216 	delete[] argv;
217 }
218 
219 
220 // Make new, independent clone of an argv array and its strings.
221 char**
CloneArgv(char ** argv)222 CloneArgv(char** argv)
223 {
224 	int argc = 0;
225 	while (argv[argc] != NULL)
226 		argc++;
227 
228 	char** newArgv = new char* [argc + 1];
229 	for (int i = 0; i < argc; i++) {
230 		newArgv[i] = new char[strlen(argv[i]) + 1];
231 		strcpy(newArgv[i], argv[i]);
232 	}
233 	newArgv[argc] = NULL;
234 
235 	return newArgv;
236 }
237 
238 
239 BString
ParseArgvZeroFromString(const char * command)240 ParseArgvZeroFromString(const char* command)
241 {
242 	char* array = NULL;
243 
244 	// make our own copy of the array...
245 	int length = strlen(command);
246 
247 	// need an extra nul byte to get GetNextWord() to stop
248 	char* cmd = new char[length + 2];
249 	strcpy(cmd, command);
250 	cmd[length + 1] = '\0';
251 		// zero out the second \0 byte
252 
253 	GunkSpaces(cmd);
254 	RemoveQuotes(cmd);
255 
256 	char* beginWord = NULL, *endWord = cmd - 1;
257 	if (GetNextWord(&beginWord, &endWord)) {
258 		array = new char[strlen(beginWord) + 1];
259 		strcpy(array, beginWord);
260 		UnGunk(array);
261 	}
262 	delete[] cmd;
263 
264 	BString string(array != NULL ? array : "");
265 	delete[] array;
266 
267 	return string;
268 }
269 
270 
271 bool
DoStandardEscapes(BString & string)272 DoStandardEscapes(BString& string)
273 {
274 	bool escape = false;
275 
276 	// Escape any characters that might mess us up
277 	// note: check this first, or we'll detect the slashes WE put in!
278 	escape |= EscapeChars(string, '\\');
279 	escape |= EscapeChars(string, '\"');
280 	escape |= EscapeChars(string, ' ');
281 	escape |= EscapeChars(string, '\t');
282 
283 	return escape;
284 }
285 
286 
287 // Modifies (string) so that each instance of (badChar) in it is preceded by a
288 // backslash. Returns true iff modifications were made.
289 bool
EscapeChars(BString & string,char badChar)290 EscapeChars(BString& string, char badChar)
291 {
292 	if (string.FindFirst(badChar) == -1)
293 		return false;
294 
295 	BString temp;
296 	int stringLen = string.Length();
297 	for (int i = 0; i < stringLen; i++) {
298 		char next = string[i];
299 		if (next == badChar)
300 			temp += '\\';
301 		temp += next;
302 	}
303 
304 	string = temp;
305 	return true;
306 }
307 
308 
309 // Launch the given app/project file. Put here so that Shortcuts and
310 // BartLauncher can share this code!
311 status_t
LaunchCommand(char ** argv,int32 argc)312 LaunchCommand(char** argv, int32 argc)
313 {
314 	BEntry entry(argv[0], true);
315 	if (entry.Exists()) {
316 		// See if it's a directory. If it is, ask Tracker to open it, rather
317 		// than launch.
318 		BDirectory testDir(&entry);
319 		if (testDir.InitCheck() == B_OK) {
320 			entry_ref ref;
321 			status_t status = entry.GetRef(&ref);
322 			if (status < B_OK)
323 				return status;
324 
325 			BMessenger target(kTrackerSignature);
326 			BMessage message(B_REFS_RECEIVED);
327 			message.AddRef("refs", &ref);
328 
329 			return target.SendMessage(&message);
330 		} else {
331 			// It's not a directory, must be a file.
332 			entry_ref ref;
333 			if (entry.GetRef(&ref) == B_OK) {
334 				if (argc > 1)
335 					be_roster->Launch(&ref, argc - 1, &argv[1]);
336 				else
337 					be_roster->Launch(&ref);
338 				return B_OK;
339 			}
340 		}
341 	}
342 
343 	return B_ERROR;
344 }
345