Commit cf673cee authored by Phillip Webb's avatar Phillip Webb

Use stricter document properties separator logic

Update `OriginTrackedPropertiesLoader` with stricter logic around the
document separator. If the preceding or following lines are comments
then the separator will be ignored.

Closes gh-22963
parent 0588e989
......@@ -886,7 +886,8 @@ For `application.properties` files a special `#---` comment is used to mark the
spring.application.name=MyCloudApp
----
NOTE: Property file separators must not have any leading or trailing whitespace and must have exactly three hyphen characters.
NOTE: Property file separators must not have any leading whitespace and must have exactly three hyphen characters.
The lines immediately before and after the separator must not be comments.
TIP: Multi-document property files are often used in conjunction with activation properties such as `spring.config.activate.on-profile`.
See the <<boot-features-external-config-file-activation-properties, next section>> for details.
......
......@@ -73,46 +73,58 @@ class OriginTrackedPropertiesLoader {
* @throws IOException on read error
*/
List<Document> load(boolean expandLists) throws IOException {
List<Document> result = new ArrayList<>();
List<Document> documents = new ArrayList<>();
Document document = new Document();
StringBuilder buffer = new StringBuilder();
try (CharacterReader reader = new CharacterReader(this.resource)) {
StringBuilder buffer = new StringBuilder();
while (reader.read()) {
if (reader.isPoundCharacter()) {
if (isNewDocument(reader)) {
if (!document.isEmpty()) {
result.add(document);
documents.add(document);
}
document = new Document();
}
else {
reader.skipComment();
}
}
String key = loadKey(buffer, reader).trim();
if (expandLists && key.endsWith("[]")) {
key = key.substring(0, key.length() - 2);
int index = 0;
do {
OriginTrackedValue value = loadValue(buffer, reader, true);
document.put(key + "[" + (index++) + "]", value);
if (!reader.isEndOfLine()) {
reader.read();
if (document.isEmpty() && !documents.isEmpty()) {
document = documents.remove(documents.size() - 1);
}
reader.setLastLineComment(true);
reader.skipComment();
}
while (!reader.isEndOfLine());
}
else {
OriginTrackedValue value = loadValue(buffer, reader, false);
document.put(key, value);
reader.setLastLineComment(false);
loadKeyAndValue(expandLists, document, reader, buffer);
}
}
}
if (!document.isEmpty() && !result.contains(document)) {
result.add(document);
if (!document.isEmpty() && !documents.contains(document)) {
documents.add(document);
}
return documents;
}
private void loadKeyAndValue(boolean expandLists, Document document, CharacterReader reader, StringBuilder buffer)
throws IOException {
String key = loadKey(buffer, reader).trim();
if (expandLists && key.endsWith("[]")) {
key = key.substring(0, key.length() - 2);
int index = 0;
do {
OriginTrackedValue value = loadValue(buffer, reader, true);
document.put(key + "[" + (index++) + "]", value);
if (!reader.isEndOfLine()) {
reader.read();
}
}
while (!reader.isEndOfLine());
}
else {
OriginTrackedValue value = loadValue(buffer, reader, false);
document.put(key, value);
}
return result;
}
private String loadKey(StringBuilder buffer, CharacterReader reader) throws IOException {
......@@ -149,6 +161,9 @@ class OriginTrackedPropertiesLoader {
}
boolean isNewDocument(CharacterReader reader) throws IOException {
if (reader.isLastLineComment()) {
return false;
}
boolean result = reader.getLocation().getColumn() == 0 && reader.isPoundCharacter();
result = result && readAndExpect(reader, reader::isHyphenCharacter);
result = result && readAndExpect(reader, reader::isHyphenCharacter);
......@@ -179,6 +194,8 @@ class OriginTrackedPropertiesLoader {
private int character;
private boolean lastLineComment;
CharacterReader(Resource resource) throws IOException {
this.reader = new LineNumberReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.ISO_8859_1));
......@@ -222,6 +239,14 @@ class OriginTrackedPropertiesLoader {
}
}
private void setLastLineComment(boolean lastLineComment) {
this.lastLineComment = lastLineComment;
}
private boolean isLastLineComment() {
return this.lastLineComment;
}
private void skipComment() throws IOException {
while (this.character != '\n' && this.character != -1) {
this.character = this.reader.read();
......
......@@ -286,6 +286,13 @@ class OriginTrackedPropertiesLoaderTests {
assertThat(getValue(value)).isEqualTo("trailing ");
}
@Test
void existingCommentsAreNotTreatedAsMultiDoc() throws Exception {
this.resource = new ClassPathResource("existing-non-multi-document.properties", getClass());
this.documents = new OriginTrackedPropertiesLoader(this.resource).load();
assertThat(this.documents.size()).isEqualTo(1);
}
private OriginTrackedValue getFromFirst(String key) {
return this.documents.get(0).asMap().get(key);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment