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