1 /*
   2  * Copyright (c) 2015, 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 <stdio.h>
  27 
  28 #ifdef DEBUG_ARGFILE
  29   #ifndef NO_JNI
  30     #define NO_JNI
  31   #endif
  32   #define JLI_ReportMessage(p1, p2) printf((p1), (p2))
  33 #else
  34   #include "java.h"
  35 #endif
  36 
  37 #include "jli_util.h"
  38 #include "emessages.h"
  39 
  40 static char* clone_substring(char *begin, size_t len) {
  41     char *rv;
  42 
  43     rv = (char *) JLI_MemAlloc(len + 1);
  44     memcpy(rv, begin, len);
  45     rv[len] = '\0';
  46     return rv;
  47 }
  48 
  49 /*
  50  * get quoted token, *cptr points to the start of the token, the beginning
  51  * quote
  52  */
  53 static char* dequote(char** cptr, const char * const eob) {
  54     char *nextc = *cptr;
  55     char quote_char = *nextc;
  56     char *anchor = nextc + 1;
  57     char *c;
  58     char d, c2, first;
  59     /* arbitarily pick 4, hopefully in most cases no more than 4 escaped chars */
  60     JLI_List escaped = JLI_List_new(4);
  61 
  62 #ifdef DQ_READ
  63     #error "DQ_READ had been defined!"
  64 #endif
  65 #define DQ_READ(V) \
  66     nextc++; \
  67     if (nextc >= eob) { \
  68         JLI_List_free(escaped); \
  69         return NULL; \
  70     } \
  71     V = (*nextc)
  72 /* end DQ_READ */
  73 
  74     DQ_READ(d);
  75     while (d != quote_char && d != '\n' && d != '\r') {
  76         if (d == '\\') {
  77             /* string before '\\' */
  78             JLI_List_addSubstring(escaped, anchor, nextc - anchor);
  79             c = JLI_MemAlloc(2 * sizeof(char));
  80             c[1] = '\0';
  81             DQ_READ(c[0]);
  82             first = c[0];   /* To allow \377, but not \477 */
  83             if (c[0] >= '0' && c[0] <= '7') {
  84                 c[0] = c[0] - '0';
  85                 DQ_READ(c2);
  86                 if ('0' <= c2 && c2 <= '7') {
  87                     c[0] = (c[0] << 3) + (c2 - '0');
  88                     DQ_READ(c2);
  89                     if ('0' <= c2 && c2 <= '7' && first <= '3') {
  90                         c[0] = (c[0] << 3) + (c2 - '0');
  91                         DQ_READ(d);
  92                     } else {
  93                         d = c2;
  94                     }
  95                 } else {
  96                   d = c2;
  97                 }
  98             } else {
  99                 switch (c[0]) {
 100                 case 'a':
 101                     c[0] = 0x7;
 102                     break;
 103                 case 'b':
 104                     c[0] = '\b';
 105                     break;
 106                 case 'f':
 107                     c[0] = 0xC;
 108                     break;
 109                 case 'n':
 110                     c[0] = '\n';
 111                     break;
 112                 case 'r':
 113                     c[0] = '\r';
 114                     break;
 115                 case 't':
 116                     c[0] = '\t';
 117                     break;
 118                 case 'v':
 119                     c[0] = 0xB;
 120                     break;
 121                 }
 122                 DQ_READ(d);
 123             }
 124             JLI_List_add(escaped, c);
 125             anchor = nextc;
 126         } else {
 127             DQ_READ(d);
 128         }
 129     }
 130 #undef DQ_READ
 131 
 132     /* nextc points to end quote or eol */
 133     // assert(nextc < eob);
 134     *cptr = nextc + 1;
 135     if (escaped->size == 0) {
 136         return clone_substring(anchor, nextc - anchor);
 137     } else {
 138         JLI_List_addSubstring(escaped, anchor, nextc - anchor);
 139         return JLI_List_combine(escaped);
 140     }
 141 }
 142 
 143 /*
 144  * cptr: the output parameter of a char* points at next read location
 145  * eob: the pointer points to end of buffer, one after the last character
 146  * return the pointer to start of next token, NULL if the buffer contains no token.
 147  * *cptr will point to next character after the token.
 148  * if returns NULL and cptr != eob indicated partial token remains in the buffer
 149  */
 150 static char* nextToken(char** cptr, const char * const eob) {
 151     char *nextc = *cptr;
 152     char *anchor;
 153     size_t len;
 154     char *rv;
 155 
 156     /* whitespaceChars(0, ' ') */
 157     while (nextc < eob && *nextc <= ' ') nextc++;
 158     if (nextc == eob) {
 159         *cptr = nextc;
 160         return NULL;
 161     }
 162 
 163     /* start with non-whitespace character */
 164     anchor = nextc;
 165     switch (*nextc) {
 166         case '#': /* commentChar('#') */
 167             /* ignore until eol */
 168             for(nextc++; nextc < eob && *nextc != '\n'; nextc++);
 169             if (nextc < eob) {
 170                 /* skip comment, continue read for next token */
 171                 *cptr = nextc++;
 172                 return nextToken(cptr, eob);
 173             }
 174             break;
 175         case '"': /* quoteChar('"') */
 176         case '\'': /* quoteChar('\'') */
 177             *cptr = nextc;
 178             return dequote(cptr, eob);
 179         default:
 180             for (nextc++; nextc < eob && *nextc > ' '; nextc++) {
 181                 /* comment or quote break a token just like whitespace */
 182                 if (*nextc == '#' || *nextc == '"' || *nextc == '\'') {
 183                     break;
 184                 }
 185             }
 186             if (nextc < eob) {
 187                 len = nextc - anchor;
 188                 rv = clone_substring(anchor, len);
 189                 *cptr = nextc;
 190                 return rv;
 191             }
 192     }
 193 
 194     /* nextc == eob, partial of next token
 195      * need to combine with next read
 196      */
 197     *cptr = anchor;
 198     return NULL;
 199 }
 200 
 201 static JLI_List readArgFile(FILE *file) {
 202     char buf[4096];
 203     JLI_List rv;
 204     size_t size = 0;
 205     size_t buf_size = sizeof(buf);
 206     char *nextc = buf;
 207     char *token = NULL;
 208     char *eob = buf;
 209     char *extra_buf = NULL;
 210     size_t extra_size = sizeof(buf);
 211     char *active_buf = buf;
 212 
 213     /* arbitrarily pick 8, seems to be a reasonable number of arguments */
 214     rv = JLI_List_new(8);
 215 
 216     while (!feof(file)) {
 217         size = fread(eob, sizeof(char), buf_size - size, file);
 218         if (ferror(file)) {
 219             JLI_List_free(rv);
 220             if (extra_buf != NULL) {
 221                 JLI_MemFree(extra_buf);
 222             }
 223             return NULL;
 224         }
 225 
 226         /* nextc is next character to read from the buffer
 227          * eob is the end of input
 228          * token is the copied token value, NULL if no a complete token
 229          */
 230         nextc = active_buf;
 231         eob = eob + size;
 232         token = nextToken(&nextc, eob);
 233         while (token != NULL) {
 234             JLI_List_add(rv, token);
 235             token = nextToken(&nextc, eob);
 236         }
 237 
 238         /* remaining characters of a non-complete token,
 239          * need to combine with next read.
 240          * if size = 0, means the input is complete read and no token
 241          * available in this buffer
 242          */
 243         size = eob - nextc;
 244         if (size == 0) {
 245             active_buf = buf;
 246             buf_size = sizeof(buf);
 247         } else {
 248             if (size < sizeof(buf)) {
 249                 // use stack whenever possible
 250                 active_buf = buf;
 251                 buf_size = sizeof(buf);
 252                 memmove(active_buf, nextc, size);
 253             } else {
 254                 if (size >= extra_size) {
 255                     // increase 4K
 256                     extra_size += sizeof(buf);
 257                     extra_buf = (char*) JLI_MemRealloc(extra_buf, extra_size);
 258                     if (active_buf == buf) {
 259                         memmove(extra_buf, nextc, size);
 260                     } // else realloc takes care of copy
 261                 } else {
 262                     // size fit in extra but still to large for buf
 263                     memmove(extra_buf, nextc, size);
 264                 }
 265                 active_buf = extra_buf;
 266                 buf_size = extra_size;
 267             }
 268         }
 269         // assert(size < buf_size);
 270         eob = active_buf + size;
 271     }
 272 
 273     /* last token from remaining */
 274     if (active_buf < eob) {
 275         if (*active_buf == '"' || *active_buf == '\'') {
 276             nextc = active_buf + 1;
 277         } else {
 278             nextc = active_buf;
 279         }
 280         if (*nextc != '#') {
 281             token = clone_substring(nextc, eob - nextc);
 282             JLI_List_add(rv, token);
 283         }
 284     }
 285     if (extra_buf != NULL) {
 286         JLI_MemFree(extra_buf);
 287     }
 288     return rv;
 289 }
 290 
 291 /*
 292  * if the arg represent a file, that is, prefix with a single '@',
 293  * return a list of arguments from the file.
 294  * otherwise, return NULL.
 295  */
 296 JLI_List JLI_ExpandArgFile(const char *arg, jboolean not_java) {
 297     JLI_List rv;
 298     FILE *fptr;
 299 
 300     if (not_java) {
 301         if (JLI_StrNCmp("-J@", arg, 3) != 0) {
 302             return NULL;
 303         } else {
 304             arg += 3;
 305         }
 306     } else if (*arg != '@') {
 307         return NULL;
 308     } else {
 309         arg++;
 310     }
 311 
 312     if (*arg == '@' || *arg == '\0') {
 313         return NULL;
 314     }
 315 
 316     fptr = fopen(arg, "r");
 317     /* arg file cannot be openned */
 318     if (fptr == NULL) {
 319         JLI_ReportMessage(CFG_ERROR6, arg);
 320         exit(1);
 321     }
 322 
 323     rv = readArgFile(fptr);
 324     fclose(fptr);
 325 
 326     /* error occurred reading the file */
 327     if (rv == NULL) {
 328         JLI_ReportMessage(DLL_ERROR4, arg);
 329     }
 330 
 331     return rv;
 332 }
 333 
 334 #ifdef DEBUG_ARGFILE
 335 
 336 void fail(char *expected, char *actual, size_t idx) {
 337     printf("FAILED: Token[%lu] expected to be <%s>, got <%s>\n", idx, expected, actual);
 338     exit(1);
 339 }
 340 
 341 void test_case(char *case_data, char **tokens, size_t cnt_tokens) {
 342     char *b, *eob;
 343     size_t actual_cnt;
 344     char *token;
 345     //char **tokens = case_data + 1;
 346 
 347     b = case_data;
 348     eob = b + strlen(b);
 349     actual_cnt = 0;
 350 
 351     printf("Test case: <%s>, expected %lu tokens.\n", b, cnt_tokens);
 352 
 353     for (token = nextToken(&b, eob); token != NULL; token = nextToken(&b, eob)) {
 354         // should not have more tokens than expected
 355         if (actual_cnt >= cnt_tokens) {
 356             printf("FAILED: Extra token detected: <%s>\n", token);
 357             exit(2);
 358         }
 359         if (JLI_StrCmp(token, tokens[actual_cnt]) != 0) {
 360             fail(tokens[actual_cnt], token, actual_cnt);
 361         }
 362         actual_cnt++;
 363     }
 364 
 365     if (actual_cnt >= cnt_tokens) {
 366         // same number of tokens, should have nothing left to parse
 367         if (b != eob) {
 368             // remainder could be comment
 369             if (*b != '#') {
 370                 printf("FAILED: Leftover detected: <%s>\n", b);
 371                 exit(2);
 372             }
 373         }
 374     } else {
 375         if (JLI_StrCmp(b, tokens[actual_cnt]) != 0) {
 376             fail(tokens[actual_cnt], b, actual_cnt);
 377         }
 378         actual_cnt++;
 379     }
 380     if (actual_cnt != cnt_tokens) {
 381         printf("FAILED: Number of tokens not match, expected %lu, got %lu\n",
 382             cnt_tokens, actual_cnt);
 383         exit(3);
 384     }
 385 
 386     printf("PASS\n");
 387 }
 388 
 389 #define DO_CASE(name) \
 390     test_case(name[0], name + 1, sizeof(name)/sizeof(char*) - 1)
 391 
 392 int main(int argc, char** argv) {
 393     int i, j;
 394 
 395     char* case1[] = { "-version -cp \"c:\\\\java libs\\\\one.jar\" \n",
 396         "-version", "-cp", "c:\\java libs\\one.jar" };
 397     DO_CASE(case1);
 398 
 399     // note the open quote at the end
 400     char* case2[] = { "com.foo.Panda \"Furious 5\" 'Shi Fu' \"escape\tprison",
 401         "com.foo.Panda", "Furious 5", "Shi Fu", "\"escape\tprison"};
 402     DO_CASE(case2);
 403 
 404     char* escaped_chars[] = { "escaped chars testing \"\\a\\b\\c\\f\\n\\r\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\"",
 405         "escaped", "chars", "testing", "\a\bc\f\n\r\t\v9\006\02382\0028\377\0477\0278\00287"};
 406     DO_CASE(escaped_chars);
 407 
 408     char* mixed_quote[]  = { "\"mix 'single quote' in double\" 'mix \"double quote\" in single'",
 409         "mix 'single quote' in double", "mix \"double quote\" in single"};
 410     DO_CASE(mixed_quote);
 411 
 412     char* comments[]  = { "line one #comment\n'line #2' #rest are comment\r#comment on line 3\nline 4 #comment to eof",
 413         "line", "one", "line #2", "line", "4"};
 414     DO_CASE(comments);
 415 
 416     if (argc > 1) {
 417         for (i = 0; i < argc; i++) {
 418             JLI_List tokens = JLI_ExpandArgFile(argv[i], JNI_FALSE);
 419             if (NULL != tokens) {
 420                 for (j = 0; j < tokens->size; j++) {
 421                     printf("Token[%d]: <%s>\n", j, tokens->elements[j]);
 422                 }
 423             }
 424         }
 425     }
 426 }
 427 
 428 #endif // DEBUG_ARGFILE