From 418524b427d56b6819d6a1175d03fa13a94a5080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Sat, 19 Nov 2016 19:00:26 +0100 Subject: [PATCH] refactor property placeholder resolution, check BytesStreamOutput for length limit --- README.adoc | 2 + build.gradle | 2 +- .../xbib/content/io/BytesStreamOutput.java | 3 + .../content/settings/PlaceholderResolver.java | 16 ++ .../content/settings/PropertyPlaceholder.java | 120 ++++++++++++ .../org/xbib/content/settings/Settings.java | 176 +++--------------- 6 files changed, 167 insertions(+), 152 deletions(-) create mode 100644 content-core/src/main/java/org/xbib/content/settings/PlaceholderResolver.java create mode 100644 content-core/src/main/java/org/xbib/content/settings/PropertyPlaceholder.java diff --git a/README.adoc b/README.adoc index 32ca80a..b305fbe 100644 --- a/README.adoc +++ b/README.adoc @@ -4,6 +4,8 @@ image:https://api.travis-ci.org/xbib/content.svg[title="Build status", link="htt image:https://img.shields.io/sonar/http/nemo.sonarqube.com/org.xbib%3Acontent/coverage.svg?style=flat-square[title="Coverage", link="https://sonarqube.com/dashboard/index?id=org.xbib%3Acontent"] image:https://maven-badges.herokuapp.com/maven-central/org.xbib/content/badge.svg[title="Maven Central", link="http://search.maven.org/#search%7Cga%7C1%7Cxbib%20content"] image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[title="Apache License 2.0", link="https://opensource.org/licenses/Apache-2.0"] +image:https://img.shields.io/twitter/url/https/twitter.com/xbib.svg?style=social&label=Follow%20%40xbib[title="Twitter", link="https://twitter.com/xbib"] +image:https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif[title="PayPal", link="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=GVHFQYZ9WZ8HG"] This is a Java library for processing structured data ("content") in the most popular formats, such as JSON, SMILE-JSON, YAML, XML, CSV, and also semantic descriptions in RDF (N-Triples, Turtle, RDF/XML). diff --git a/build.gradle b/build.gradle index 05ab537..3685b3c 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ ext { allprojects { group = 'org.xbib' - version = '1.0.5' + version = '1.0.6' apply plugin: 'java' apply plugin: 'maven' diff --git a/content-core/src/main/java/org/xbib/content/io/BytesStreamOutput.java b/content-core/src/main/java/org/xbib/content/io/BytesStreamOutput.java index b8f7f6d..4a50eab 100644 --- a/content-core/src/main/java/org/xbib/content/io/BytesStreamOutput.java +++ b/content-core/src/main/java/org/xbib/content/io/BytesStreamOutput.java @@ -96,6 +96,9 @@ public class BytesStreamOutput extends OutputStream { if (length == 0) { return; } + if ((long)count + length > Integer.MAX_VALUE) { + throw new IllegalArgumentException("overflow, stream output larger than " + Integer.MAX_VALUE); + } int newcount = count + length; if (newcount > buf.length) { buf = Arrays.copyOf(buf, oversize(newcount)); diff --git a/content-core/src/main/java/org/xbib/content/settings/PlaceholderResolver.java b/content-core/src/main/java/org/xbib/content/settings/PlaceholderResolver.java new file mode 100644 index 0000000..aed8c71 --- /dev/null +++ b/content-core/src/main/java/org/xbib/content/settings/PlaceholderResolver.java @@ -0,0 +1,16 @@ +package org.xbib.content.settings; + +/** + * Strategy interface used to resolve replacement values for placeholders contained in Strings. + */ +@FunctionalInterface +public interface PlaceholderResolver { + + /** + * Resolves the supplied placeholder name into the replacement value. + * + * @param placeholderName the name of the placeholder to resolve. + * @return the replacement value or null if no replacement is to be made. + */ + String resolvePlaceholder(String placeholderName); +} \ No newline at end of file diff --git a/content-core/src/main/java/org/xbib/content/settings/PropertyPlaceholder.java b/content-core/src/main/java/org/xbib/content/settings/PropertyPlaceholder.java new file mode 100644 index 0000000..0a2a219 --- /dev/null +++ b/content-core/src/main/java/org/xbib/content/settings/PropertyPlaceholder.java @@ -0,0 +1,120 @@ +package org.xbib.content.settings; + +import java.util.HashSet; +import java.util.Set; + +/** + * + */ +public class PropertyPlaceholder { + + private final String placeholderPrefix; + + private final String placeholderSuffix; + + private final boolean ignoreUnresolvablePlaceholders; + + /** + * Creates a new PropertyPlaceholderHelper that uses the supplied prefix and suffix. + * + * @param placeholderPrefix the prefix that denotes the start of a placeholder. + * @param placeholderSuffix the suffix that denotes the end of a placeholder. + * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored + * (true) or cause an exception (false). + */ + public PropertyPlaceholder(String placeholderPrefix, String placeholderSuffix, + boolean ignoreUnresolvablePlaceholders) { + this.placeholderPrefix = placeholderPrefix; + this.placeholderSuffix = placeholderSuffix; + this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; + } + + + /** + * Replaces all placeholders of format ${name} with the value returned from the supplied {@link + * PlaceholderResolver}. + * + * @param value the value containing the placeholders to be replaced. + * @param placeholderResolver the PlaceholderResolver to use for replacement. + * @return the supplied value with placeholders replaced inline. + */ + public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { + return parseStringValue(value, placeholderResolver, new HashSet()); + } + + protected String parseStringValue(String strVal, PlaceholderResolver placeholderResolver, + Set visitedPlaceholders) { + StringBuilder buf = new StringBuilder(strVal); + int startIndex = strVal.indexOf(this.placeholderPrefix); + while (startIndex != -1) { + int endIndex = findPlaceholderEndIndex(buf, startIndex); + if (endIndex != -1) { + String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex); + if (!visitedPlaceholders.add(placeholder)) { + throw new IllegalArgumentException( + "Circular placeholder reference '" + placeholder + "' in property definitions"); + } + // Recursive invocation, parsing placeholders contained in the placeholder key. + placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); + // Now obtain the value for the fully resolved key... + int defaultValueIdx = placeholder.indexOf(':'); + String defaultValue = null; + if (defaultValueIdx != -1) { + defaultValue = placeholder.substring(defaultValueIdx + 1); + placeholder = placeholder.substring(0, defaultValueIdx); + } + String propVal = placeholderResolver.resolvePlaceholder(placeholder); + if (propVal == null) { + propVal = defaultValue; + } + if (propVal != null) { + // Recursive invocation, parsing placeholders contained in the + // previously resolved placeholder value. + propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); + buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); + startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length()); + } else if (this.ignoreUnresolvablePlaceholders) { + // Proceed with unprocessed value. + startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); + } else { + throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'"); + } + visitedPlaceholders.remove(placeholder); + } else { + startIndex = -1; + } + } + return buf.toString(); + } + + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + this.placeholderPrefix.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (substringMatch(buf, index, this.placeholderSuffix)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + this.placeholderPrefix.length() - 1; + } else { + return index; + } + } else if (substringMatch(buf, index, this.placeholderPrefix)) { + withinNestedPlaceholder++; + index = index + this.placeholderPrefix.length(); + } else { + index++; + } + } + return -1; + } + + private boolean substringMatch(CharSequence str, int index, CharSequence substring) { + for (int j = 0; j < substring.length(); j++) { + int i = index + j; + if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/content-core/src/main/java/org/xbib/content/settings/Settings.java b/content-core/src/main/java/org/xbib/content/settings/Settings.java index 482a874..a44f1a0 100644 --- a/content-core/src/main/java/org/xbib/content/settings/Settings.java +++ b/content-core/src/main/java/org/xbib/content/settings/Settings.java @@ -23,11 +23,9 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -687,26 +685,31 @@ public class Settings { * @return builder */ public Builder replacePropertyPlaceholders() { - PropertyPlaceholder propertyPlaceholder = new PropertyPlaceholder("${", "}", false); - PropertyPlaceholder.PlaceholderResolver placeholderResolver = placeholderName -> { - // system property - String value = System.getProperty(placeholderName); - if (value != null) { - return value; - } - // environment - value = System.getenv(placeholderName); - if (value != null) { - return value; - } - // current date - try { - return DateTimeFormatter.ofPattern(placeholderName).format(LocalDate.now()); - } catch (IllegalArgumentException | DateTimeException e) { - logger.log(Level.FINER, e.getMessage(), e); - return map.get(placeholderName); - } - }; + return replacePropertyPlaceholders(new PropertyPlaceholder("${", "}", false), + placeholderName -> { + // system property + String value = System.getProperty(placeholderName); + if (value != null) { + return value; + } + // environment + value = System.getenv(placeholderName); + if (value != null) { + return value; + } + // current date + try { + return DateTimeFormatter.ofPattern(placeholderName).format(LocalDate.now()); + } catch (IllegalArgumentException | DateTimeException e) { + logger.log(Level.FINER, e.getMessage(), e); + return map.get(placeholderName); + } + } + ); + } + + public Builder replacePropertyPlaceholders(PropertyPlaceholder propertyPlaceholder, + PlaceholderResolver placeholderResolver) { for (Map.Entry entry : map.entrySet()) { map.put(entry.getKey(), propertyPlaceholder.replacePlaceholders(entry.getValue(), placeholderResolver)); } @@ -717,133 +720,4 @@ public class Settings { return new Settings(map); } } - - private static class PropertyPlaceholder { - - private final String placeholderPrefix; - - private final String placeholderSuffix; - - private final boolean ignoreUnresolvablePlaceholders; - - /** - * Creates a new PropertyPlaceholderHelper that uses the supplied prefix and suffix. - * - * @param placeholderPrefix the prefix that denotes the start of a placeholder. - * @param placeholderSuffix the suffix that denotes the end of a placeholder. - * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored - * (true) or cause an exception (false). - */ - public PropertyPlaceholder(String placeholderPrefix, String placeholderSuffix, - boolean ignoreUnresolvablePlaceholders) { - this.placeholderPrefix = placeholderPrefix; - this.placeholderSuffix = placeholderSuffix; - this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; - } - - - /** - * Replaces all placeholders of format ${name} with the value returned from the supplied {@link - * PlaceholderResolver}. - * - * @param value the value containing the placeholders to be replaced. - * @param placeholderResolver the PlaceholderResolver to use for replacement. - * @return the supplied value with placeholders replaced inline. - */ - public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { - return parseStringValue(value, placeholderResolver, new HashSet()); - } - - protected String parseStringValue(String strVal, PlaceholderResolver placeholderResolver, - Set visitedPlaceholders) { - StringBuilder buf = new StringBuilder(strVal); - int startIndex = strVal.indexOf(this.placeholderPrefix); - while (startIndex != -1) { - int endIndex = findPlaceholderEndIndex(buf, startIndex); - if (endIndex != -1) { - String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex); - if (!visitedPlaceholders.add(placeholder)) { - throw new IllegalArgumentException( - "Circular placeholder reference '" + placeholder + "' in property definitions"); - } - // Recursive invocation, parsing placeholders contained in the placeholder key. - placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); - - // Now obtain the value for the fully resolved key... - int defaultValueIdx = placeholder.indexOf(':'); - String defaultValue = null; - if (defaultValueIdx != -1) { - defaultValue = placeholder.substring(defaultValueIdx + 1); - placeholder = placeholder.substring(0, defaultValueIdx); - } - String propVal = placeholderResolver.resolvePlaceholder(placeholder); - if (propVal == null) { - propVal = defaultValue; - } - if (propVal != null) { - // Recursive invocation, parsing placeholders contained in the - // previously resolved placeholder value. - propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); - buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); - startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length()); - } else if (this.ignoreUnresolvablePlaceholders) { - // Proceed with unprocessed value. - startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); - } else { - throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'"); - } - visitedPlaceholders.remove(placeholder); - } else { - startIndex = -1; - } - } - return buf.toString(); - } - - private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { - int index = startIndex + this.placeholderPrefix.length(); - int withinNestedPlaceholder = 0; - while (index < buf.length()) { - if (substringMatch(buf, index, this.placeholderSuffix)) { - if (withinNestedPlaceholder > 0) { - withinNestedPlaceholder--; - index = index + this.placeholderPrefix.length() - 1; - } else { - return index; - } - } else if (substringMatch(buf, index, this.placeholderPrefix)) { - withinNestedPlaceholder++; - index = index + this.placeholderPrefix.length(); - } else { - index++; - } - } - return -1; - } - - private boolean substringMatch(CharSequence str, int index, CharSequence substring) { - for (int j = 0; j < substring.length(); j++) { - int i = index + j; - if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { - return false; - } - } - return true; - } - - /** - * Strategy interface used to resolve replacement values for placeholders contained in Strings. - */ - @FunctionalInterface - public interface PlaceholderResolver { - - /** - * Resolves the supplied placeholder name into the replacement value. - * - * @param placeholderName the name of the placeholder to resolve. - * @return the replacement value or null if no replacement is to be made. - */ - String resolvePlaceholder(String placeholderName); - } - } }