Skip to content

Commit

Permalink
Improve script quoting
Browse files Browse the repository at this point in the history
  • Loading branch information
ascopes committed Jan 27, 2024
1 parent 0f24f5f commit d9c7e28
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.github.ascopes.protobufmavenplugin.system.Digests;
import io.github.ascopes.protobufmavenplugin.system.FileUtils;
import io.github.ascopes.protobufmavenplugin.system.HostSystem;
import io.github.ascopes.protobufmavenplugin.system.Shlex;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
Expand Down Expand Up @@ -151,70 +152,27 @@ private Path writeWindowsBatchScript(
) throws IOException {
var fullScriptPath = resolvePluginScriptPath().resolve(scriptNamePrefix + ".bat");

var script = new StringBuilder()
.append("@echo off\r\n");
for (var arg : argLine) {
quoteBatchArg(script, arg);
script.append(' ');
}
script.append("\r\n");
var script = "@echo off\r\n"
+ Shlex.quoteBatchArgs(argLine)
+ "\r\n";

Files.writeString(fullScriptPath, script, Charset.defaultCharset());
return fullScriptPath;
}

private void quoteBatchArg(StringBuilder sb, String arg) {
for (var i = 0; i < arg.length(); ++i) {
var c = arg.charAt(i);
switch (c) {
case '\\':
case '"':
case '\'':
case ' ':
case '\r':
case '\t':
case '^':
case '&':
case '<':
case '>':
case '|':
sb.append('^');
}

sb.append(c);
}
}

private Path writeShellScript(
String scriptNamePrefix,
List<String> argLine
) throws IOException {
var fullScriptPath = resolvePluginScriptPath().resolve(scriptNamePrefix + ".sh");

var script = new StringBuilder()
.append("#!/usr/bin/env sh\n")
.append("set -eux\n");
for (var arg : argLine) {
quoteShellArg(script, arg);
script.append(' ');
}
script.append('\n');
var script = "#!/usr/bin/env sh\n"
+ "set -eu\n"
+ Shlex.quoteShellArgs(argLine)
+ "\n";

Files.writeString(fullScriptPath, script, Charset.defaultCharset());
FileUtils.makeExecutable(fullScriptPath);
return fullScriptPath;
}

private void quoteShellArg(StringBuilder sb, String arg) {
sb.append('\'');
for (var i = 0; i < arg.length(); ++i) {
var c = arg.charAt(i);
if (c == '\'') {
sb.append("'\"'\"'");
} else {
sb.append(c);
}
}
sb.append('\'');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.github.ascopes.protobufmavenplugin.execute;

import io.github.ascopes.protobufmavenplugin.system.Shlex;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.List;
Expand All @@ -39,7 +40,7 @@ public CommandLineExecutor() {
}

public boolean execute(List<String> args) throws IOException {
log.info("Calling protoc with the following command line: {}", args);
log.info("Calling protoc with the following command line: {}", Shlex.quoteShellArgs(args));

var procBuilder = new ProcessBuilder(args);
procBuilder.environment().putAll(System.getenv());
Expand Down
118 changes: 118 additions & 0 deletions src/main/java/io/github/ascopes/protobufmavenplugin/system/Shlex.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright (C) 2023 - 2024, Ashley Scopes.
*
* 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 io.github.ascopes.protobufmavenplugin.system;

import java.util.function.BiConsumer;

/**
* Shell/batch file quoting.
*
* <p>Losely based on Python's {@code shlex} module.
*
* @author Ashley Scopes
*/
public final class Shlex {

private Shlex() {
// Static-only class
}

public static String quoteShellArgs(Iterable<String> args) {
return quote(args, Shlex::quoteShellArg);
}

public static String quoteBatchArgs(Iterable<String> args) {
return quote(args, Shlex::quoteBatchArg);
}

private static String quote(Iterable<String> args, BiConsumer<StringBuilder, String> quoter) {
var iter = args.iterator();
var sb = new StringBuilder();
quoter.accept(sb, iter.next());

while (iter.hasNext()) {
sb.append(' ');
quoter.accept(sb, iter.next());
}

return sb.toString();
}

private static void quoteShellArg(StringBuilder sb, String arg) {
if (isSafe(arg)) {
sb.append(arg);
return;
}

sb.append('\'');
for (var i = 0; i < arg.length(); ++i) {
var c = arg.charAt(i);
if (c == '\'') {
sb.append("'\"'\"'");

Check warning on line 64 in src/main/java/io/github/ascopes/protobufmavenplugin/system/Shlex.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/io/github/ascopes/protobufmavenplugin/system/Shlex.java#L64

Added line #L64 was not covered by tests
} else {
sb.append(c);
}
}
sb.append('\'');
}

private static void quoteBatchArg(StringBuilder sb, String arg) {
if (isSafe(arg)) {
sb.append(arg);
return;
}

for (var i = 0; i < arg.length(); ++i) {
var c = arg.charAt(i);
switch (c) {
case '\\':
case '"':
case '\'':
case ' ':
case '\r':
case '\t':
case '^':
case '&':
case '<':
case '>':
case '|':
sb.append('^');
}

sb.append(c);
}
}

private static boolean isSafe(String arg) {
for (var i = 0; i < arg.length(); ++i) {
var c = arg.charAt(i);
var safe = 'A' <= c && c <= 'Z'
|| 'a' <= c && c <= 'z'
|| '0' <= c && c <= '9'
|| c == '-'
|| c == '/'
|| c == '_'
|| c == '.'
|| c == '=';

if (!safe) {
return false;
}
}

return true;
}
}

0 comments on commit d9c7e28

Please sign in to comment.