1/*
2 * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#include <stdlib.h>
27#include <stdio.h>
28#include <assert.h>
29#include <sys/stat.h>
30#include <ctype.h>
31
32#ifdef DEBUG_ARGFILE
33 #ifndef NO_JNI
34 #define NO_JNI
35 #endif
36 #define JLI_ReportMessage(...) printf(__VA_ARGS__)
37 #define JDK_JAVA_OPTIONS "JDK_JAVA_OPTIONS"
38 int IsWhiteSpaceOption(const char* name) { return 1; }
39#else
40 #include "java.h"
41 #include "jni.h"
42#endif
43
44#include "jli_util.h"
45#include "emessages.h"
46
47#define MAX_ARGF_SIZE 0x7fffffffL
48
49static char* clone_substring(const char *begin, size_t len) {
50 char *rv = (char *) JLI_MemAlloc(len + 1);
51 memcpy(rv, begin, len);
52 rv[len] = '\0';
53 return rv;
54}
55
56enum STATE {
57 FIND_NEXT,
58 IN_COMMENT,
59 IN_QUOTE,
60 IN_ESCAPE,
61 SKIP_LEAD_WS,
62 IN_TOKEN
63};
64
65typedef struct {
66 enum STATE state;
67 const char* cptr;
68 const char* eob;
69 char quote_char;
70 JLI_List parts;
71} __ctx_args;
72
73#define NOT_FOUND -1
74static int firstAppArgIndex = NOT_FOUND;
75
76static jboolean expectingNoDashArg = JNI_FALSE;
77// Initialize to 1, as the first argument is the app name and not preprocessed
78static size_t argsCount = 1;
79static jboolean stopExpansion = JNI_FALSE;
80static jboolean relaunch = JNI_FALSE;
81
82/*
83 * Prototypes for internal functions.
84 */
85static jboolean expand(JLI_List args, const char *str, const char *var_name);
86
87JNIEXPORT void JNICALL
88JLI_InitArgProcessing(jboolean hasJavaArgs, jboolean disableArgFile) {
89 // No expansion for relaunch
90 if (argsCount != 1) {
91 relaunch = JNI_TRUE;
92 stopExpansion = JNI_TRUE;
93 argsCount = 1;
94 } else {
95 stopExpansion = disableArgFile;
96 }
97
98 expectingNoDashArg = JNI_FALSE;
99
100 // for tools, this value remains 0 all the time.
101 firstAppArgIndex = hasJavaArgs ? 0: NOT_FOUND;
102}
103
104JNIEXPORT int JNICALL
105JLI_GetAppArgIndex() {
106 // Will be 0 for tools
107 return firstAppArgIndex;
108}
109
110static void checkArg(const char *arg) {
111 size_t idx = 0;
112 argsCount++;
113
114 // All arguments arrive here must be a launcher argument,
115 // ie. by now, all argfile expansions must have been performed.
116 if (*arg == '-') {
117 expectingNoDashArg = JNI_FALSE;
118 if (IsWhiteSpaceOption(arg)) {
119 // expect an argument
120 expectingNoDashArg = JNI_TRUE;
121
122 if (JLI_StrCmp(arg, "-jar") == 0 ||
123 JLI_StrCmp(arg, "--module") == 0 ||
124 JLI_StrCmp(arg, "-m") == 0) {
125 // This is tricky, we do expect NoDashArg
126 // But that is considered main class to stop expansion
127 expectingNoDashArg = JNI_FALSE;
128 // We can not just update the idx here because if -jar @file
129 // still need expansion of @file to get the argument for -jar
130 }
131 } else if (JLI_StrCmp(arg, "--disable-@files") == 0) {
132 stopExpansion = JNI_TRUE;
133 }
134 } else {
135 if (!expectingNoDashArg) {
136 // this is main class, argsCount is index to next arg
137 idx = argsCount;
138 }
139 expectingNoDashArg = JNI_FALSE;
140 }
141 // only update on java mode and not yet found main class
142 if (firstAppArgIndex == NOT_FOUND && idx != 0) {
143 firstAppArgIndex = (int) idx;
144 }
145}
146
147/*
148 [\n\r] +------------+ +------------+ [\n\r]
149 +---------+ IN_COMMENT +<------+ | IN_ESCAPE +---------+
150 | +------------+ | +------------+ |
151 | [#] ^ |[#] ^ | |
152 | +----------+ | [\\]| |[^\n\r] |
153 v | | | v |
154+------------+ [^ \t\n\r\f] +------------+['"]> +------------+ |
155| FIND_NEXT +-------------->+ IN_TOKEN +-----------+ IN_QUOTE + |
156+------------+ +------------+ <[quote]+------------+ |
157 | ^ | | ^ ^ |
158 | | [ \t\n\r\f]| [\n\r]| | |[^ \t\n\r\f]v
159 | +--------------------------+-----------------------+ | +--------------+
160 | ['"] | | SKIP_LEAD_WS |
161 +---------------------------------------------------------+ +--------------+
162*/
163static char* nextToken(__ctx_args *pctx) {
164 const char* nextc = pctx->cptr;
165 const char* const eob = pctx->eob;
166 const char* anchor = nextc;
167 char *token;
168
169 for (; nextc < eob; nextc++) {
170 register char ch = *nextc;
171
172 // Skip white space characters
173 if (pctx->state == FIND_NEXT || pctx->state == SKIP_LEAD_WS) {
174 while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') {
175 nextc++;
176 if (nextc >= eob) {
177 return NULL;
178 }
179 ch = *nextc;
180 }
181 pctx->state = (pctx->state == FIND_NEXT) ? IN_TOKEN : IN_QUOTE;
182 anchor = nextc;
183 // Deal with escape sequences
184 } else if (pctx->state == IN_ESCAPE) {
185 // concatenation directive
186 if (ch == '\n' || ch == '\r') {
187 pctx->state = SKIP_LEAD_WS;
188 } else {
189 // escaped character
190 char* escaped = (char*) JLI_MemAlloc(2 * sizeof(char));
191 escaped[1] = '\0';
192 switch (ch) {
193 case 'n':
194 escaped[0] = '\n';
195 break;
196 case 'r':
197 escaped[0] = '\r';
198 break;
199 case 't':
200 escaped[0] = '\t';
201 break;
202 case 'f':
203 escaped[0] = '\f';
204 break;
205 default:
206 escaped[0] = ch;
207 break;
208 }
209 JLI_List_add(pctx->parts, escaped);
210 pctx->state = IN_QUOTE;
211 }
212 // anchor to next character
213 anchor = nextc + 1;
214 continue;
215 // ignore comment to EOL
216 } else if (pctx->state == IN_COMMENT) {
217 while (ch != '\n' && ch != '\r') {
218 nextc++;
219 if (nextc > eob) {
220 return NULL;
221 }
222 ch = *nextc;
223 }
224 pctx->state = FIND_NEXT;
225 continue;
226 }
227
228 assert(pctx->state != IN_ESCAPE);
229 assert(pctx->state != FIND_NEXT);
230 assert(pctx->state != SKIP_LEAD_WS);
231 assert(pctx->state != IN_COMMENT);
232
233 switch(ch) {
234 case ' ':
235 case '\t':
236 case '\f':
237 if (pctx->state == IN_QUOTE) {
238 continue;
239 }
240 // fall through
241 case '\n':
242 case '\r':
243 if (pctx->parts->size == 0) {
244 token = clone_substring(anchor, nextc - anchor);
245 } else {
246 JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);
247 token = JLI_List_combine(pctx->parts);
248 JLI_List_free(pctx->parts);
249 pctx->parts = JLI_List_new(4);
250 }
251 pctx->cptr = nextc + 1;
252 pctx->state = FIND_NEXT;
253 return token;
254 case '#':
255 if (pctx->state == IN_QUOTE) {
256 continue;
257 }
258 pctx->state = IN_COMMENT;
259 break;
260 case '\\':
261 if (pctx->state != IN_QUOTE) {
262 continue;
263 }
264 JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);
265 pctx->state = IN_ESCAPE;
266 // anchor after backslash character
267 anchor = nextc + 1;
268 break;
269 case '\'':
270 case '"':
271 if (pctx->state == IN_QUOTE && pctx->quote_char != ch) {
272 // not matching quote
273 continue;
274 }
275 // partial before quote
276 if (anchor != nextc) {
277 JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);
278 }
279 // anchor after quote character
280 anchor = nextc + 1;
281 if (pctx->state == IN_TOKEN) {
282 pctx->quote_char = ch;
283 pctx->state = IN_QUOTE;
284 } else {
285 pctx->state = IN_TOKEN;
286 }
287 break;
288 default:
289 break;
290 }
291 }
292
293 assert(nextc == eob);
294 if (anchor != nextc) {
295 // not yet return until end of stream, we have part of a token.
296 JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);
297 }
298 return NULL;
299}
300
301static JLI_List readArgFile(FILE *file) {
302 char buf[4096];
303 JLI_List rv;
304 __ctx_args ctx;
305 size_t size;
306 char *token;
307
308 ctx.state = FIND_NEXT;
309 ctx.parts = JLI_List_new(4);
310 // initialize to avoid -Werror=maybe-uninitialized issues from gcc 7.3 onwards.
311 ctx.quote_char = '"';
312
313 /* arbitrarily pick 8, seems to be a reasonable number of arguments */
314 rv = JLI_List_new(8);
315
316 while (!feof(file)) {
317 size = fread(buf, sizeof(char), sizeof(buf), file);
318 if (ferror(file)) {
319 JLI_List_free(rv);
320 return NULL;
321 }
322
323 /* nextc is next character to read from the buffer
324 * eob is the end of input
325 * token is the copied token value, NULL if no a complete token
326 */
327 ctx.cptr = buf;
328 ctx.eob = buf + size;
329 token = nextToken(&ctx);
330 while (token != NULL) {
331 checkArg(token);
332 JLI_List_add(rv, token);
333 token = nextToken(&ctx);
334 }
335 }
336
337 // remaining partial token
338 if (ctx.state == IN_TOKEN || ctx.state == IN_QUOTE) {
339 if (ctx.parts->size != 0) {
340 JLI_List_add(rv, JLI_List_combine(ctx.parts));
341 }
342 }
343 JLI_List_free(ctx.parts);
344
345 return rv;
346}
347
348/*
349 * if the arg represent a file, that is, prefix with a single '@',
350 * return a list of arguments from the file.
351 * otherwise, return NULL.
352 */
353static JLI_List expandArgFile(const char *arg) {
354 FILE *fptr;
355 struct stat st;
356 JLI_List rv;
357
358 /* failed to access the file */
359 if (stat(arg, &st) != 0) {
360 JLI_ReportMessage(CFG_ERROR6, arg);
361 exit(1);
362 }
363
364 if (st.st_size > MAX_ARGF_SIZE) {
365 JLI_ReportMessage(CFG_ERROR10, MAX_ARGF_SIZE);
366 exit(1);
367 }
368
369 fptr = fopen(arg, "r");
370 /* arg file cannot be openned */
371 if (fptr == NULL) {
372 JLI_ReportMessage(CFG_ERROR6, arg);
373 exit(1);
374 }
375
376 rv = readArgFile(fptr);
377 fclose(fptr);
378
379 /* error occurred reading the file */
380 if (rv == NULL) {
381 JLI_ReportMessage(DLL_ERROR4, arg);
382 exit(1);
383 }
384
385 return rv;
386}
387
388/*
389 * expand a string into a list of words separated by whitespace.
390 */
391static JLI_List expandArg(const char *arg) {
392 JLI_List rv;
393
394 /* arbitrarily pick 8, seems to be a reasonable number of arguments */
395 rv = JLI_List_new(8);
396
397 expand(rv, arg, NULL);
398
399 return rv;
400}
401
402JNIEXPORT JLI_List JNICALL
403JLI_PreprocessArg(const char *arg, jboolean expandSourceOpt) {
404 JLI_List rv;
405
406 if (firstAppArgIndex > 0) {
407 // In user application arg, no more work.
408 return NULL;
409 }
410
411 if (stopExpansion) {
412 // still looking for user application arg
413 checkArg(arg);
414 return NULL;
415 }
416
417 if (expandSourceOpt
418 && JLI_StrCCmp(arg, "--source") == 0
419 && JLI_StrChr(arg, ' ') != NULL) {
420 return expandArg(arg);
421 }
422
423 if (arg[0] != '@') {
424 checkArg(arg);
425 return NULL;
426 }
427
428 if (arg[1] == '\0') {
429 // @ by itself is an argument
430 checkArg(arg);
431 return NULL;
432 }
433
434 arg++;
435 if (arg[0] == '@') {
436 // escaped @argument
437 rv = JLI_List_new(1);
438 checkArg(arg);
439 JLI_List_add(rv, JLI_StringDup(arg));
440 } else {
441 rv = expandArgFile(arg);
442 }
443 return rv;
444}
445
446int isTerminalOpt(char *arg) {
447 return JLI_StrCmp(arg, "-jar") == 0 ||
448 JLI_StrCmp(arg, "-m") == 0 ||
449 JLI_StrCmp(arg, "--module") == 0 ||
450 JLI_StrCmp(arg, "--dry-run") == 0 ||
451 JLI_StrCmp(arg, "-h") == 0 ||
452 JLI_StrCmp(arg, "-?") == 0 ||
453 JLI_StrCmp(arg, "-help") == 0 ||
454 JLI_StrCmp(arg, "--help") == 0 ||
455 JLI_StrCmp(arg, "-X") == 0 ||
456 JLI_StrCmp(arg, "--help-extra") == 0 ||
457 JLI_StrCmp(arg, "-version") == 0 ||
458 JLI_StrCmp(arg, "--version") == 0 ||
459 JLI_StrCmp(arg, "-fullversion") == 0 ||
460 JLI_StrCmp(arg, "--full-version") == 0;
461}
462
463JNIEXPORT jboolean JNICALL
464JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) {
465 char *env = getenv(var_name);
466
467 if (firstAppArgIndex == 0) {
468 // Not 'java', return
469 return JNI_FALSE;
470 }
471
472 if (relaunch) {
473 return JNI_FALSE;
474 }
475
476 if (NULL == env) {
477 return JNI_FALSE;
478 }
479
480 JLI_ReportMessage(ARG_INFO_ENVVAR, var_name, env);
481 return expand(args, env, var_name);
482}
483
484/*
485 * Expand a string into a list of args.
486 * If the string is the result of looking up an environment variable,
487 * var_name should be set to the name of that environment variable,
488 * for use if needed in error messages.
489 */
490
491static jboolean expand(JLI_List args, const char *str, const char *var_name) {
492 jboolean inEnvVar = (var_name != NULL);
493
494 char *p, *arg;
495 char quote;
496 JLI_List argsInFile;
497
498 // This is retained until the process terminates as it is saved as the args
499 p = JLI_MemAlloc(JLI_StrLen(str) + 1);
500 while (*str != '\0') {
501 while (*str != '\0' && isspace(*str)) {
502 str++;
503 }
504
505 // Trailing space
506 if (*str == '\0') {
507 break;
508 }
509
510 arg = p;
511 while (*str != '\0' && !isspace(*str)) {
512 if (inEnvVar && (*str == '"' || *str == '\'')) {
513 quote = *str++;
514 while (*str != quote && *str != '\0') {
515 *p++ = *str++;
516 }
517
518 if (*str == '\0') {
519 JLI_ReportMessage(ARG_ERROR8, var_name);
520 exit(1);
521 }
522 str++;
523 } else {
524 *p++ = *str++;
525 }
526 }
527
528 *p++ = '\0';
529
530 argsInFile = JLI_PreprocessArg(arg, JNI_FALSE);
531
532 if (NULL == argsInFile) {
533 if (isTerminalOpt(arg)) {
534 if (inEnvVar) {
535 JLI_ReportMessage(ARG_ERROR9, arg, var_name);
536 } else {
537 JLI_ReportMessage(ARG_ERROR15, arg);
538 }
539 exit(1);
540 }
541 JLI_List_add(args, arg);
542 } else {
543 size_t cnt, idx;
544 char *argFile = arg;
545 cnt = argsInFile->size;
546 for (idx = 0; idx < cnt; idx++) {
547 arg = argsInFile->elements[idx];
548 if (isTerminalOpt(arg)) {
549 if (inEnvVar) {
550 JLI_ReportMessage(ARG_ERROR10, arg, argFile, var_name);
551 } else {
552 JLI_ReportMessage(ARG_ERROR16, arg, argFile);
553 }
554 exit(1);
555 }
556 JLI_List_add(args, arg);
557 }
558 // Shallow free, we reuse the string to avoid copy
559 JLI_MemFree(argsInFile->elements);
560 JLI_MemFree(argsInFile);
561 }
562 /*
563 * Check if main-class is specified after argument being checked. It
564 * must always appear after expansion, as a main-class could be specified
565 * indirectly into environment variable via an @argfile, and it must be
566 * caught now.
567 */
568 if (firstAppArgIndex != NOT_FOUND) {
569 if (inEnvVar) {
570 JLI_ReportMessage(ARG_ERROR11, var_name);
571 } else {
572 JLI_ReportMessage(ARG_ERROR17);
573 }
574 exit(1);
575 }
576
577 assert (*str == '\0' || isspace(*str));
578 }
579
580 return JNI_TRUE;
581}
582
583#ifdef DEBUG_ARGFILE
584/*
585 * Stand-alone sanity test, build with following command line
586 * $ CC -DDEBUG_ARGFILE -DNO_JNI -g args.c jli_util.c
587 */
588
589void fail(char *expected, char *actual, size_t idx) {
590 printf("FAILED: Token[%lu] expected to be <%s>, got <%s>\n", idx, expected, actual);
591 exit(1);
592}
593
594void test_case(char *case_data, char **tokens, size_t cnt_tokens) {
595 size_t actual_cnt;
596 char *token;
597 __ctx_args ctx;
598
599 actual_cnt = 0;
600
601 ctx.state = FIND_NEXT;
602 ctx.parts = JLI_List_new(4);
603 ctx.cptr = case_data;
604 ctx.eob = case_data + strlen(case_data);
605
606 printf("Test case: <%s>, expected %lu tokens.\n", case_data, cnt_tokens);
607
608 for (token = nextToken(&ctx); token != NULL; token = nextToken(&ctx)) {
609 // should not have more tokens than expected
610 if (actual_cnt >= cnt_tokens) {
611 printf("FAILED: Extra token detected: <%s>\n", token);
612 exit(2);
613 }
614 if (JLI_StrCmp(token, tokens[actual_cnt]) != 0) {
615 fail(tokens[actual_cnt], token, actual_cnt);
616 }
617 actual_cnt++;
618 }
619
620 char* last = NULL;
621 if (ctx.parts->size != 0) {
622 last = JLI_List_combine(ctx.parts);
623 }
624 JLI_List_free(ctx.parts);
625
626 if (actual_cnt >= cnt_tokens) {
627 // same number of tokens, should have nothing left to parse
628 if (last != NULL) {
629 if (*last != '#') {
630 printf("Leftover detected: %s", last);
631 exit(2);
632 }
633 }
634 } else {
635 if (JLI_StrCmp(last, tokens[actual_cnt]) != 0) {
636 fail(tokens[actual_cnt], last, actual_cnt);
637 }
638 actual_cnt++;
639 }
640 if (actual_cnt != cnt_tokens) {
641 printf("FAILED: Number of tokens not match, expected %lu, got %lu\n",
642 cnt_tokens, actual_cnt);
643 exit(3);
644 }
645
646 printf("PASS\n");
647}
648
649#define DO_CASE(name) \
650 test_case(name[0], name + 1, sizeof(name)/sizeof(char*) - 1)
651
652int main(int argc, char** argv) {
653 size_t i, j;
654
655 char* case1[] = { "-version -cp \"c:\\\\java libs\\\\one.jar\" \n",
656 "-version", "-cp", "c:\\java libs\\one.jar" };
657 DO_CASE(case1);
658
659 // note the open quote at the end
660 char* case2[] = { "com.foo.Panda \"Furious 5\"\fand\t'Shi Fu' \"escape\tprison",
661 "com.foo.Panda", "Furious 5", "and", "Shi Fu", "escape\tprison"};
662 DO_CASE(case2);
663
664 char* escaped_chars[] = { "escaped chars testing \"\\a\\b\\c\\f\\n\\r\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\"",
665 "escaped", "chars", "testing", "abc\f\n\r\tv96238228377477278287"};
666 DO_CASE(escaped_chars);
667
668 char* mixed_quote[] = { "\"mix 'single quote' in double\" 'mix \"double quote\" in single' partial\"quote me\"this",
669 "mix 'single quote' in double", "mix \"double quote\" in single", "partialquote methis"};
670 DO_CASE(mixed_quote);
671
672 char* comments[] = { "line one #comment\n'line #2' #rest are comment\r\n#comment on line 3\nline 4 #comment to eof",
673 "line", "one", "line #2", "line", "4"};
674 DO_CASE(comments);
675
676 char* open_quote[] = { "This is an \"open quote \n across line\n\t, note for WS.",
677 "This", "is", "an", "open quote ", "across", "line", ",", "note", "for", "WS." };
678 DO_CASE(open_quote);
679
680 char* escape_in_open_quote[] = { "Try \"this \\\\\\\\ escape\\n double quote \\\" in open quote",
681 "Try", "this \\\\ escape\n double quote \" in open quote" };
682 DO_CASE(escape_in_open_quote);
683
684 char* quote[] = { "'-Dmy.quote.single'='Property in single quote. Here a double quote\" Add some slashes \\\\/'",
685 "-Dmy.quote.single=Property in single quote. Here a double quote\" Add some slashes \\/" };
686 DO_CASE(quote);
687
688 char* multi[] = { "\"Open quote to \n new \"line \\\n\r third\\\n\r\\\tand\ffourth\"",
689 "Open quote to ", "new", "line third\tand\ffourth" };
690 DO_CASE(multi);
691
692 char* escape_quote[] = { "c:\\\"partial quote\"\\lib",
693 "c:\\partial quote\\lib" };
694 DO_CASE(escape_quote);
695
696 if (argc > 1) {
697 for (i = 0; i < argc; i++) {
698 JLI_List tokens = JLI_PreprocessArg(argv[i], JNI_FALSE);
699 if (NULL != tokens) {
700 for (j = 0; j < tokens->size; j++) {
701 printf("Token[%lu]: <%s>\n", (unsigned long) j, tokens->elements[j]);
702 }
703 }
704 }
705 }
706}
707
708#endif // DEBUG_ARGFILE
709