„Jede Form ist ein Kerker.“
Friedrich Hebbel
Bei der Verwendung einer Template-Engine ist die größte Herausforderung das Einfügen der variablen Inhalte in das Dokument. Hierbei ist neben der technischen Realisierung auch die Sicherheit zu beachten. Allzu leicht könnten sonst bösartige Inhalte in das generierte Dokument gelangen. Je nach Verwendung als E-Mail, Webseite oder Konfiguration wären so diverse Angriffsszenarien denkbar.
Das Problem der Absicherung gegen jede Art von Injektion wird in FreshMarker durch die Verwendung spezieller Ausgabeformate umgesetzt. Je nach Ausgabeformat werden dann spezielle Zeichen in den Interpolations maskiert.
Es existieren mehrere Möglichkeiten um das Ausgabeformat für ein Template zu setzen. Über die Konfiguration kann das Standardformat für alle Templates vorbelegt werden.
StringTemplateLoader templateLoader = new StringTemplateLoader(); templateLoader.putTemplate("test", "<P>${content}</P>"); Configuration configuration = new Configuration(); configuration.registerTemplateLoader(templateLoader); configuration.setOutputFormat("HTML"); Template template = configuration.getTemplate("test"); System.out.println(template.process(Map.of("content", "<br/>")));
In diesem Beispiel wird das Ausgabeformat HTML
als Standardformat in der Konfiguration gesetzt. Damit werden die Zeichen <
, >
, "
und '
durch <
, >
, "
und '
ersetzt. Die Ausgabe des Beispiels lautet <P><br/></P>
. Neben dem Ausgabeformat HTML
werden auch XHTML
, XML
unterstützt. es können auch plainText
, undefined
, JavaScript
, JSON
und CSS
angegeben werden. Diese führen jedoch keinerlei Maskierung durch.
Für einen Block innerhalb eines Templates kann das Ausgabeformat über die Direktive outputformat
geändert werden.
${content} <#outputformat 'HTML'> ${content} </#outputformat> ${content}
In diesem Beispiel wird nur der Inhalt der Direktive mit dem Ausgabeformat HTML
maskiert und die Interpolations davor und danach werden mit dem Standardformat eingefügt. Die Ausgabe des Beispiels für das Standardformat plainText
lautet <br/>&tl;br/><br/>
.
<#outputformat 'HTML'> ${content?noEsc} </#outputformat>
Soll für eine Interpolation keine Maskierung erfolgen, dann kann dies über das Built-In noEsc
gesteuert werden. Obwohl das Ausgabeformat HTML
gewählt wurde, wird über das Built-In die Maskierung deaktiviert und die Ausgabe des Beispiels ist <br/>
.
Das Herzstück der Implementierung besteht aus der Datenmodell Ergänzung TemplateMarkup
. Diese Klasse speichert Text und das dazu gewünschte Ausgabeformat. Bei der Generierung einer InterpolationFragment
Instanz wird der Interpolation Ausdruck von einer TemplateMarkup
Instanz umfasst,
@Override public BlockFragment visit(Interpolation ftl, BlockFragment input) { logger.debug("interpolation: {}", ftl); TemplateObject interpolation = ftl.getChild(1).accept(interpolationBuilder, null); input.addFragment(new InterpolationFragment(new TemplateMarkup(interpolation), ftl)); return input; }
Der Konstruktor mit nur einem Parameter verwendet ein spezielles Ausgabeformat
. Dieses Ausgabeformat delegiert die Maskierung an das aktuell im Environment eingestellte Ausgabeformat. Ist das Standardformat beispielsweise DelegatingOutputFormat
.INSTANCEXML
, dann werden solche TemplateMarkup
Instanzen mit dem HtmlOutputFormat.XML
maskiert.
Innerhalb der Klasse TemplateMarkup
wird das eingebettete TemplateObject
ausgewertet und wenn es sich nicht wiederum um ein TemplateMarkup
handelt mit dem entsprechenden Formatter
in Text umgewandelt und dann von dem aktuellen Ausgabeformat maskiert.
@Override public TemplateObject evaluateToObject(ProcessContext context) { TemplateObject templateObject = getTemplateObject(context); if (templateObject.isMarkup()) { return templateObject.evaluate(context, TemplateString.class); } Environment environment = context.getEnvironment(); String result = context.getFormatter(templateObject.getClass()).format(templateObject, environment.getLocale()); return outputFormat.escape(environment, result); }
Handelt es sich bei dem eingebetteten TemplateObject
um eine TemplateMarkup
Instanz, dann wird diese ausgewertet und ihr Inhalt direkt zurückgegeben. Auf diese Weise werden die outputformat
Direktive und das noEsc
Built-In realisiert.
Die outputformat
Direktive erzeugt ein spezielle OutputFormatFragment
für das Template, mit dem für das eingebettete BlockFragment
das AusgabeFormat im Environment
gewechselt wird.
public class OutputFormatFragment implements Fragment { private final BlockFragment content; private final String format; public OutputFormatFragment(BlockFragment content, String format) { this.content = content; this.format = format; } @Override public void process(ProcessContext context) { Environment environment = context.getEnvironment(); context.setEnvironment(new SettingEnvironment(environment, null, context.getOutputFormat(format))); content.process(context); context.setEnvironment(environment); } }
Das Built-In noEsc
erzeugt für eine TemplateString
Instanz eine TemplateMarkup
Instanz, die das Ausgabeformat NoEscapeFormat.INSTANCE
verwendet.
@BuiltInMethod public static TemplateMarkup noEsc(TemplateString value) { return new TemplateMarkup(value, NoEscapeFormat.INSTANCE); }
Wie der Name erahnen lässt, nimmt dieses Ausgabeformat keinerlei Maskierungen des Textes vor.
Damit ist der Mechanismus für das Maskieren von Zeichen in der FreshMarker Ausgabe auch schon beschrieben. Momentan existiert noch keine Möglichkeit, zusätzliche Ausgabeformate zu definieren, aber das ist der Inhalt eines zukünftigen Beitrags.