Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qute: ignore template files that contain whitespace in its name #42783

Merged
merged 1 commit into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,8 @@ If you want to use Qute in your Quarkus application, add the following dependenc
In Quarkus, a preconfigured engine instance is provided and available for injection - a bean with scope `@ApplicationScoped`, bean type `io.quarkus.qute.Engine` and qualifier `@Default` is registered automatically.
Moreover, all templates located in the `src/main/resources/templates` directory are validated and can be easily injected.

NOTE: A valid template file name is a sequence of non-whitespace characters. For example, a template file named `foo and bar.html` will be ignored.

[source,java]
----
import io.quarkus.qute.Engine;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
import io.quarkus.qute.ErrorCode;
import io.quarkus.qute.Expression;
import io.quarkus.qute.Expression.VirtualMethodPart;
import io.quarkus.qute.Identifiers;
import io.quarkus.qute.LoopSectionHelper;
import io.quarkus.qute.NamespaceResolver;
import io.quarkus.qute.ParameterDeclaration;
Expand Down Expand Up @@ -2180,6 +2181,10 @@ private void scanPathTree(PathTree pathTree, TemplateRootsBuildItem templateRoot
if (PathTreeUtils.containsCaseSensitivePath(pathTree, templateRoot)) {
pathTree.walkIfContains(templateRoot, visit -> {
if (Files.isRegularFile(visit.getPath())) {
if (!Identifiers.isValid(visit.getPath().getFileName().toString())) {
LOGGER.warnf("Invalid file name detected [%s] - template is ignored", visit.getPath());
return;
}
LOGGER.debugf("Found template: %s", visit.getPath());
// remove templateRoot + /
final String relativePath = visit.getRelativePath();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.quarkus.qute.deployment.identifiers;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;

import java.util.logging.LogRecord;

import jakarta.inject.Inject;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.qute.Engine;
import io.quarkus.test.QuarkusUnitTest;

public class InvalidTemplateFileNameIgnoredTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(root -> root
.addAsResource(new StringAsset(
"ignored"),
"templates/foo o.txt"))
.setLogRecordPredicate(log -> log.getLoggerName().contains("QuteProcessor"))
.assertLogRecords(records -> {
for (LogRecord r : records) {
if (r.getMessage().startsWith("Invalid file name detected")) {
return;
}
}
fail();
});

@Inject
Engine engine;

@Test
public void testTemplateFileIgnored() {
assertFalse(engine.isTemplateLoaded("foo o.txt"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ default Template parse(String content, Variant variant) {
String mapResult(Object result, Expression expression);

/**
* A valid identifier is a sequence of non-whitespace characters.
*
* @param id
* @param template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ public String mapResult(Object result, Expression expression) {
}

public Template putTemplate(String id, Template template) {
if (!Identifiers.isValid(id)) {
throw new IllegalArgumentException("Invalid identifier found: [" + id + "]");
}
return templates.put(id, template);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.qute;

public final class Identifiers {

/**
* A valid identifier is a sequence of non-whitespace characters.
*
* @param value
* @return {@code true} if the value represents a valid identifier, {@code false} otherwise
*/
public static boolean isValid(String value) {
if (value == null || value.isBlank()) {
return false;
}
int offset = 0;
int length = value.length();
while (offset < length) {
int c = value.codePointAt(offset);
if (!Character.isWhitespace(c)) {
offset += Character.charCount(c);
continue;
}
return false;
}
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -400,20 +400,6 @@ private boolean isValidIdentifierStart(char character) {
|| Character.isAlphabetic(character);
}

static boolean isValidIdentifier(String value) {
int offset = 0;
int length = value.length();
while (offset < length) {
int c = value.codePointAt(offset);
if (!Character.isWhitespace(c)) {
offset += Character.charCount(c);
continue;
}
return false;
}
return true;
}

private boolean isLineSeparatorStart(char character) {
return character == LINE_SEPARATOR_CR || character == LINE_SEPARATOR_LF;
}
Expand Down Expand Up @@ -1084,7 +1070,7 @@ private static Part createPart(Supplier<Integer> idGenerator, String namespace,
.build();
}
} else {
if (!isValidIdentifier(value)) {
if (!Identifiers.isValid(value)) {
throw error(ParserError.INVALID_IDENTIFIER, "invalid identifier found [{value}]", origin)
.argument("value", value)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

Expand Down Expand Up @@ -116,4 +117,11 @@ public void accept(TemplateInstance templateInstance) {
assertEquals(engine1.getSectionHelperFactories().size(), engine2.getSectionHelperFactories().size());
}

@Test
public void testInvalidTemplateIdentifier() {
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> Engine.builder().build().putTemplate("foo o", null));
assertEquals("Invalid identifier found: [foo o]", e.getMessage());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.qute;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

public class IdentifiersTest {

@Test
public void testIsValid() {
assertTrue(Identifiers.isValid("foo"));
assertTrue(Identifiers.isValid("foo-bar"));
assertTrue(Identifiers.isValid("fíí_"));
assertFalse(Identifiers.isValid("foo bar"));
assertFalse(Identifiers.isValid(""));
assertFalse(Identifiers.isValid(" "));
assertTrue(Identifiers.isValid("%ů="));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -262,16 +262,16 @@ public void testRemoveStandaloneLines() {

@Test
public void testValidIdentifiers() {
assertTrue(Parser.isValidIdentifier("foo"));
assertTrue(Parser.isValidIdentifier("_foo"));
assertTrue(Parser.isValidIdentifier("foo$$bar"));
assertTrue(Parser.isValidIdentifier("1Foo_$"));
assertTrue(Parser.isValidIdentifier("1"));
assertTrue(Parser.isValidIdentifier("1?"));
assertTrue(Parser.isValidIdentifier("1:"));
assertTrue(Parser.isValidIdentifier("-foo"));
assertTrue(Parser.isValidIdentifier("foo["));
assertTrue(Parser.isValidIdentifier("foo^"));
assertTrue(Identifiers.isValid("foo"));
assertTrue(Identifiers.isValid("_foo"));
assertTrue(Identifiers.isValid("foo$$bar"));
assertTrue(Identifiers.isValid("1Foo_$"));
assertTrue(Identifiers.isValid("1"));
assertTrue(Identifiers.isValid("1?"));
assertTrue(Identifiers.isValid("1:"));
assertTrue(Identifiers.isValid("-foo"));
assertTrue(Identifiers.isValid("foo["));
assertTrue(Identifiers.isValid("foo^"));
Engine engine = Engine.builder().addDefaults().build();
assertThatExceptionOfType(TemplateException.class)
.isThrownBy(() -> engine.parse("{foo\nfoo}"))
Expand Down
Loading