Skip to content

Commit

Permalink
Merge pull request #38365 from aloubyansky/reproducible-extension-met…
Browse files Browse the repository at this point in the history
…adata

Make sure extension metadata properties are not including timestamps
  • Loading branch information
gsmet authored Jan 24, 2024
2 parents f099ed3 + e14daa1 commit d1ab63a
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Map;
import java.util.regex.Pattern;

import io.quarkus.bootstrap.util.PropertyUtils;
import io.quarkus.deployment.configuration.BuildTimeConfigurationReader;
import io.quarkus.runtime.LaunchMode;

Expand Down Expand Up @@ -107,108 +108,6 @@ public static void write(Map<String, String> readOptions, ConfigTrackingConfig c
* @throws IOException in case of a failure
*/
public static void write(Writer writer, String name, String value) throws IOException {
if (value != null) {
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 &#92;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 &#92;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)];
PropertyUtils.store(writer, name, value);
}

/** 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'
};
}
10 changes: 10 additions & 0 deletions core/processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-app-model</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- Test -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import io.quarkus.annotation.processor.generate_doc.ConfigDocItemScanner;
import io.quarkus.annotation.processor.generate_doc.ConfigDocWriter;
import io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil;
import io.quarkus.bootstrap.util.PropertyUtils;

public class ExtensionAnnotationProcessor extends AbstractProcessor {

Expand Down Expand Up @@ -239,23 +240,23 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
}
}

try {

final FileObject listResource = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
"META-INF/quarkus-javadoc.properties");
try (OutputStream os = listResource.openOutputStream()) {
try (BufferedOutputStream bos = new BufferedOutputStream(os)) {
try (OutputStreamWriter osw = new OutputStreamWriter(bos, StandardCharsets.UTF_8)) {
try (BufferedWriter bw = new BufferedWriter(osw)) {
javaDocProperties.store(bw, Constants.EMPTY);
if (!javaDocProperties.isEmpty()) {
try {
final FileObject listResource = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
"META-INF/quarkus-javadoc.properties");
try (OutputStream os = listResource.openOutputStream()) {
try (BufferedOutputStream bos = new BufferedOutputStream(os)) {
try (OutputStreamWriter osw = new OutputStreamWriter(bos, StandardCharsets.UTF_8)) {
try (BufferedWriter bw = new BufferedWriter(osw)) {
PropertyUtils.store(javaDocProperties, bw);
}
}
}
}
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write javadoc properties: " + e);
return;
}

} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write javadoc properties: " + e);
return;
}

try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package io.quarkus.bootstrap.util;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

/**
*
Expand Down Expand Up @@ -67,4 +77,166 @@ public static final boolean getBoolean(String name, boolean notFoundValue) {
final String value = getProperty(name, (notFoundValue ? TRUE : FALSE));
return value.isEmpty() ? true : Boolean.parseBoolean(value);
}

/**
* Stores properties into a file sorting the keys alphabetically and following
* {@link Properties#store(Writer, String)} format but skipping the timestamp and comments.
*
* @param properties properties to store
* @param file target file
* @throws IOException in case of a failure
*/
public static void store(Properties properties, Path file) throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(file)) {
store(properties, writer);
}
}

/**
* Stores properties into a file sorting the keys alphabetically and following
* {@link Properties#store(Writer, String)} format but skipping the timestamp and comments.
*
* @param properties properties to store
* @param writer target writer
* @throws IOException in case of a failure
*/
public static void store(Properties properties, Writer writer) throws IOException {
final List<String> names = new ArrayList<>(properties.size());
for (var name : properties.keySet()) {
names.add(name == null ? null : name.toString());
}
Collections.sort(names);
for (String name : names) {
store(writer, name, properties.getProperty(name));
}
}

/**
* Stores a map of strings into a file sorting the keys alphabetically and following
* {@link Properties#store(Writer, String)} format but skipping the timestamp and comments.
*
* @param properties properties to store
* @param file target file
* @throws IOException in case of a failure
*/
public static void store(Map<String, String> properties, Path file) throws IOException {
final List<String> names = new ArrayList<>(properties.keySet());
Collections.sort(names);
try (BufferedWriter writer = Files.newBufferedWriter(file)) {
for (String name : names) {
store(writer, name, properties.get(name));
}
}
}

/**
* Writes a config option with its value to the target writer,
* possibly applying some transformations, such as character escaping
* prior to writing.
*
* @param writer target writer
* @param name option name
* @param value option value
* @throws IOException in case of a failure
*/
public static void store(Writer writer, String name, String value) throws IOException {
if (value != null) {
name = toWritableValue(name, true, true);
value = toWritableValue(value, false, true);
writer.write(name);
writer.write("=");
writer.write(value);
writer.write(System.lineSeparator());
}
}

/**
* 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 &#92;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'
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace;
import io.quarkus.bootstrap.util.DependencyUtils;
import io.quarkus.bootstrap.util.PropertyUtils;
import io.quarkus.devtools.project.extensions.ScmInfoProvider;
import io.quarkus.fs.util.ZipUtils;
import io.quarkus.maven.capabilities.CapabilitiesConfig;
Expand Down Expand Up @@ -388,10 +389,7 @@ public void execute() throws MojoExecutionException {
final Path output = outputDirectory.toPath().resolve(BootstrapConstants.META_INF);
try {
Files.createDirectories(output);
try (BufferedWriter writer = Files
.newBufferedWriter(output.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME))) {
props.store(writer, "Generated by extension-descriptor");
}
PropertyUtils.store(props, output.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME));
} catch (IOException e) {
throw new MojoExecutionException(
"Failed to persist extension descriptor " + output.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME),
Expand Down

0 comments on commit d1ab63a

Please sign in to comment.