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 
ShellQuotedParamsFilePreProcessor(FileSystem fs)35   public ShellQuotedParamsFilePreProcessor(FileSystem fs) {
36     super(fs);
37   }
38 
39   @Override
parse(Path paramsFile)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 
ShellQuotedReader(Reader reader)57     public ShellQuotedReader(Reader reader) {
58       this.reader = new PushbackReader(reader, 10);
59     }
60 
read()61     private char read() throws IOException {
62       int value = reader.read();
63       position++;
64       return (char) value;
65     }
66 
unread(char value)67     private void unread(char value) throws IOException {
68       reader.unread(value);
69       position--;
70     }
71 
hasNext()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
close()80     public void close() throws IOException {
81       reader.close();
82     }
83 
readArg()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