casid / jte

Secure and speedy templates for Java and Kotlin.
https://jte.gg
Apache License 2.0
819 stars 59 forks source link

Bug in StringUtils.startsWithIgnoringCaseAndWhitespaces() prevents writing out "J" #305

Closed erik-alm closed 10 months ago

erik-alm commented 10 months ago

StringUtils.startsWithIgnoringCaseAndWhitespaces() is implemented "backwards". Instead of trying to find the prefix in the string, it tries to find the string in the prefix. This causes this undesired behavior:

final String string = "J";
final String prefix = "javascript";
System.out.println(startsWithIgnoringCaseAndWhitespaces(string, prefix)); // result: true

It also means you cannot write a variable with the content "J" or "Ja" etc. when it's part of an "a href"-attribute like:

<a data-toggle="tab" href="#key_${key}">${key}</a>

Trying to fix the problem using "unsafe", like so:

<a data-toggle="tab" href="#key_$unsafe{key}">${key}</a>

…seems to cause another bug (since "key" is a "Character"? — I had to convert it to a String to make it work):

gg.jte.TemplateException: Failed to compile template, error at pages/browse.jte:82
.../jte-classes/gg/jte/generated/ondemand/pages/JtebrowseGenerated.java:64: error: incompatible types: Character cannot be converted to String
                jteOutput.writeUnsafeContent(key);
                                             ^
.../jte-classes/gg/jte/generated/ondemand/pages/JtebrowseGenerated.java:71: error: incompatible types: Character cannot be converted to String
                jteOutput.writeUnsafeContent(key);
                                             ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
2 errors

at gg.jte.compiler.java.JavaClassCompiler.runCompiler(JavaClassCompiler.java:49)
at gg.jte.compiler.java.JavaClassCompiler.compile(JavaClassCompiler.java:38)
at gg.jte.compiler.TemplateCompiler.precompileClasses(TemplateCompiler.java:114)
at gg.jte.compiler.TemplateCompiler.precompile(TemplateCompiler.java:94)
at gg.jte.compiler.TemplateCompiler.load(TemplateCompiler.java:50)
at gg.jte.TemplateEngine.lambda$resolveTemplateOnDemand$0(TemplateEngine.java:354)
at java.base/java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1955)
at gg.jte.TemplateEngine.resolveTemplateOnDemand(TemplateEngine.java:347)
at gg.jte.TemplateEngine.resolveTemplate(TemplateEngine.java:337)
at gg.jte.TemplateEngine.render(TemplateEngine.java:210)
at net.blockm.starmap.web.WebRepository.render(WebRepository.java:36)
at net.blockm.starmap.app.WebBuilder.writeIndex(WebBuilder.java:1269)
at net.blockm.starmap.app.WebBuilder.buildGroupsIndex(WebBuilder.java:585)
at net.blockm.starmap.app.WebBuilder.build(WebBuilder.java:87)
at net.blockm.starmap.app.Main.main(Main.java:145)
casid commented 10 months ago

Hi @erik-alm, thank you for reporting this issue.

Indeed, that method did not verify that the entire prefix got checked. I just pushed a fix and will release a hotfix version later today.

casid commented 10 months ago

I just released jte version 3.1.6, containing a fix for this issue.

erik-alm commented 10 months ago

Great! It seems to work. Though "unsafe(Character)" is still broken. Here's how to make it break:

$unsafe{Character.valueOf('x')}
casid commented 10 months ago

$unsafe has only implementations for String and Content, since usually it does not make sense to have unsafe for single characters.