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