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
27const 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.
35static void
36GunkSpaces(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
64static void
65RemoveQuotes(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
104static bool
105IsValidChar(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.
115static bool
116GetNextWord(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
152static void
153UnGunk(char* str)
154{
155	char* temp = str;
156	while (*temp) {
157		if (*temp == GUNK_CHAR)
158			*temp = ' ';
159
160		temp++;
161	}
162}
163
164
165char**
166ParseArgvFromString(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
205void
206FreeArgv(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.
221char**
222CloneArgv(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
239BString
240ParseArgvZeroFromString(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
271bool
272DoStandardEscapes(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.
289bool
290EscapeChars(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!
311status_t
312LaunchCommand(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