// 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(); } } }