Home | History | Annotate | Download | only in options
      1 // Copyright 2017 The Bazel Authors. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //    http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 package com.google.devtools.common.options;
     15 
     16 import static java.nio.charset.StandardCharsets.UTF_8;
     17 
     18 import java.io.IOException;
     19 import java.io.PushbackReader;
     20 import java.io.Reader;
     21 import java.nio.file.FileSystem;
     22 import java.nio.file.Files;
     23 import java.nio.file.Path;
     24 import java.util.ArrayList;
     25 import java.util.List;
     26 
     27 /**
     28  * A {@link ParamsFilePreProcessor} that processes a parameter file using the {@code
     29  * com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType.SHELL_QUOTED} format. This
     30  * format assumes each parameter is separated by whitespace and is quoted using singe quotes
     31  * ({@code '}) if it contains any special characters or is an empty string.
     32  */
     33 public class ShellQuotedParamsFilePreProcessor extends ParamsFilePreProcessor {
     34 
     35   public ShellQuotedParamsFilePreProcessor(FileSystem fs) {
     36     super(fs);
     37   }
     38 
     39   @Override
     40   protected List<String> parse(Path paramsFile) throws IOException {
     41     List<String> args = new ArrayList<>();
     42     try (ShellQuotedReader reader =
     43         new ShellQuotedReader(Files.newBufferedReader(paramsFile, UTF_8))) {
     44       String arg;
     45       while ((arg = reader.readArg()) != null) {
     46         args.add(arg);
     47       }
     48     }
     49     return args;
     50   }
     51 
     52   private static class ShellQuotedReader implements AutoCloseable {
     53 
     54     private final PushbackReader reader;
     55     private int position = -1;
     56 
     57     public ShellQuotedReader(Reader reader) {
     58       this.reader = new PushbackReader(reader, 10);
     59     }
     60 
     61     private char read() throws IOException {
     62       int value = reader.read();
     63       position++;
     64       return (char) value;
     65     }
     66 
     67     private void unread(char value) throws IOException {
     68       reader.unread(value);
     69       position--;
     70     }
     71 
     72     private boolean hasNext() throws IOException {
     73       char value = read();
     74       boolean hasNext = value != (char) -1;
     75       unread(value);
     76       return hasNext;
     77     }
     78 
     79     @Override
     80     public void close() throws IOException {
     81       reader.close();
     82     }
     83 
     84     public String readArg() throws IOException {
     85       if (!hasNext()) {
     86         return null;
     87       }
     88 
     89       StringBuilder arg = new StringBuilder();
     90 
     91       int quoteStart = -1;
     92       boolean quoted = false;
     93       char current;
     94 
     95       while ((current = read()) != (char) -1) {
     96         if (quoted) {
     97           if (current == '\'') {
     98             StringBuilder escapedQuoteRemainder =
     99                 new StringBuilder().append(read()).append(read()).append(read());
    100             if (escapedQuoteRemainder.toString().equals("\\''")) {
    101               arg.append("'");
    102             } else {
    103               for (char c : escapedQuoteRemainder.reverse().toString().toCharArray()) {
    104                 unread(c);
    105               }
    106               quoted = false;
    107               quoteStart = -1;
    108             }
    109           } else {
    110             arg.append(current);
    111           }
    112         } else {
    113           if (current == '\'') {
    114             quoted = true;
    115             quoteStart = position;
    116           } else if (current == '\r') {
    117             char next = read();
    118             if (next == '\n') {
    119               return arg.toString();
    120             } else {
    121               unread(next);
    122               return arg.toString();
    123             }
    124           } else if (Character.isWhitespace(current)) {
    125             return arg.toString();
    126           } else {
    127             arg.append(current);
    128           }
    129         }
    130       }
    131       if (quoted) {
    132         throw new IOException(
    133             String.format(UNFINISHED_QUOTE_MESSAGE_FORMAT, "'", quoteStart));
    134       }
    135       return arg.toString();
    136     }
    137   }
    138 }
    139