|  | // Copyright 2017 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  | package com.google.devtools.common.options; | 
|  |  | 
|  | import static java.nio.charset.StandardCharsets.UTF_8; | 
|  |  | 
|  | import java.io.IOException; | 
|  | import java.io.PushbackReader; | 
|  | import java.io.Reader; | 
|  | import java.nio.file.FileSystem; | 
|  | import java.nio.file.Files; | 
|  | import java.nio.file.Path; | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  |  | 
|  | /** | 
|  | * A {@link ParamsFilePreProcessor} that processes a parameter file using the {@code | 
|  | * com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType.SHELL_QUOTED} format. This | 
|  | * format assumes each parameter is separated by whitespace and is quoted using singe quotes | 
|  | * ({@code '}) if it contains any special characters or is an empty string. | 
|  | */ | 
|  | public class ShellQuotedParamsFilePreProcessor extends ParamsFilePreProcessor { | 
|  |  | 
|  | public ShellQuotedParamsFilePreProcessor(FileSystem fs) { | 
|  | super(fs); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected List<String> parse(Path paramsFile) throws IOException { | 
|  | List<String> args = new ArrayList<>(); | 
|  | try (ShellQuotedReader reader = | 
|  | new ShellQuotedReader(Files.newBufferedReader(paramsFile, UTF_8))) { | 
|  | String arg; | 
|  | while ((arg = reader.readArg()) != null) { | 
|  | args.add(arg); | 
|  | } | 
|  | } | 
|  | return args; | 
|  | } | 
|  |  | 
|  | private static class ShellQuotedReader implements AutoCloseable { | 
|  |  | 
|  | private final PushbackReader reader; | 
|  | private int position = -1; | 
|  |  | 
|  | public ShellQuotedReader(Reader reader) { | 
|  | this.reader = new PushbackReader(reader, 10); | 
|  | } | 
|  |  | 
|  | private char read() throws IOException { | 
|  | int value = reader.read(); | 
|  | position++; | 
|  | return (char) value; | 
|  | } | 
|  |  | 
|  | private void unread(char value) throws IOException { | 
|  | reader.unread(value); | 
|  | position--; | 
|  | } | 
|  |  | 
|  | private boolean hasNext() throws IOException { | 
|  | char value = read(); | 
|  | boolean hasNext = value != (char) -1; | 
|  | unread(value); | 
|  | return hasNext; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void close() throws IOException { | 
|  | reader.close(); | 
|  | } | 
|  |  | 
|  | public String readArg() throws IOException { | 
|  | if (!hasNext()) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | StringBuilder arg = new StringBuilder(); | 
|  |  | 
|  | int quoteStart = -1; | 
|  | boolean quoted = false; | 
|  | char current; | 
|  |  | 
|  | while ((current = read()) != (char) -1) { | 
|  | if (quoted) { | 
|  | if (current == '\'') { | 
|  | StringBuilder escapedQuoteRemainder = | 
|  | new StringBuilder().append(read()).append(read()).append(read()); | 
|  | if (escapedQuoteRemainder.toString().equals("\\''")) { | 
|  | arg.append("'"); | 
|  | } else { | 
|  | for (char c : escapedQuoteRemainder.reverse().toString().toCharArray()) { | 
|  | unread(c); | 
|  | } | 
|  | quoted = false; | 
|  | quoteStart = -1; | 
|  | } | 
|  | } else { | 
|  | arg.append(current); | 
|  | } | 
|  | } else { | 
|  | if (current == '\'') { | 
|  | quoted = true; | 
|  | quoteStart = position; | 
|  | } else if (current == '\r') { | 
|  | char next = read(); | 
|  | if (next == '\n') { | 
|  | return arg.toString(); | 
|  | } else { | 
|  | unread(next); | 
|  | return arg.toString(); | 
|  | } | 
|  | } else if (Character.isWhitespace(current)) { | 
|  | return arg.toString(); | 
|  | } else { | 
|  | arg.append(current); | 
|  | } | 
|  | } | 
|  | } | 
|  | if (quoted) { | 
|  | throw new IOException( | 
|  | String.format(UNFINISHED_QUOTE_MESSAGE_FORMAT, "'", quoteStart)); | 
|  | } | 
|  | return arg.toString(); | 
|  | } | 
|  | } | 
|  | } |