diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java index 20c51265c5032..b3e067be1f74f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java @@ -99,12 +99,107 @@ public static void write(Map readOptions, ConfigTrackingConfig c */ public static void write(Writer writer, String name, String value) throws IOException { if (value != null) { - // escape the backslash before persisting - value = value.replace("\\", "\\\\"); + name = toWritableValue(name, true, true); + value = toWritableValue(value, false, true); writer.write(name); writer.write("="); writer.write(value); writer.write(System.lineSeparator()); } } + + /* + * Converts unicodes to encoded \uxxxx and escapes + * special characters with a preceding slash + */ + + /** + * Escapes characters that are expected to be escaped when {@link java.util.Properties} load + * files from disk. + * + * @param str property name or value + * @param escapeSpace whether to escape a whitespace (should be true for property names) + * @param escapeUnicode whether to converts unicodes to encoded \uxxxx + * @return property name or value that can be written to a file + */ + private static String toWritableValue(String str, boolean escapeSpace, boolean escapeUnicode) { + int len = str.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuilder outBuffer = new StringBuilder(bufLen); + + for (int x = 0; x < len; x++) { + char aChar = str.charAt(x); + // Handle common case first, selecting largest block that + // avoids the specials below + if ((aChar > 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); + outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + switch (aChar) { + case ' ': + if (x == 0 || escapeSpace) { + outBuffer.append('\\'); + } + outBuffer.append(' '); + break; + case '\t': + outBuffer.append('\\'); + outBuffer.append('t'); + break; + case '\n': + outBuffer.append('\\'); + outBuffer.append('n'); + break; + case '\r': + outBuffer.append('\\'); + outBuffer.append('r'); + break; + case '\f': + outBuffer.append('\\'); + outBuffer.append('f'); + break; + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + outBuffer.append('\\'); + outBuffer.append(aChar); + break; + default: + if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode) { + outBuffer.append('\\'); + outBuffer.append('u'); + outBuffer.append(toHex((aChar >> 12) & 0xF)); + outBuffer.append(toHex((aChar >> 8) & 0xF)); + outBuffer.append(toHex((aChar >> 4) & 0xF)); + outBuffer.append(toHex(aChar & 0xF)); + } else { + outBuffer.append(aChar); + } + } + } + return outBuffer.toString(); + } + + /** + * Convert a nibble to a hex character + * + * @param nibble the nibble to convert. + */ + private static char toHex(int nibble) { + return hexDigit[(nibble & 0xF)]; + } + + /** A table of hex digits */ + private static final char[] hexDigit = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; }