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