/* * ============================================================================= * * Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ============================================================================= */ package org.thymeleaf.standard.processor; import java.util.Set; import org.thymeleaf.IEngineConfiguration; import org.thymeleaf.context.ITemplateContext; import org.thymeleaf.engine.AttributeName; import org.thymeleaf.engine.TemplateModel; import org.thymeleaf.model.IProcessableElementTag; import org.thymeleaf.postprocessor.IPostProcessor; import org.thymeleaf.processor.element.AbstractAttributeTagProcessor; import org.thymeleaf.processor.element.IElementTagStructureHandler; import org.thymeleaf.standard.expression.Fragment; import org.thymeleaf.standard.expression.FragmentExpression; import org.thymeleaf.standard.expression.IStandardExpression; import org.thymeleaf.standard.expression.IStandardExpressionParser; import org.thymeleaf.standard.expression.NoOpToken; import org.thymeleaf.standard.expression.StandardExpressionExecutionContext; import org.thymeleaf.standard.expression.StandardExpressions; import org.thymeleaf.templatemode.TemplateMode; /** * * @author Daniel Fernández * * @since 3.0.0 * */ public final class StandardUtextTagProcessor extends AbstractAttributeTagProcessor { public static final int PRECEDENCE = 1400; public static final String ATTR_NAME = "utext"; public StandardUtextTagProcessor(final TemplateMode templateMode, final String dialectPrefix) { super(templateMode, dialectPrefix, null, false, ATTR_NAME, true, PRECEDENCE, true); } @Override protected void doProcess( final ITemplateContext context, final IProcessableElementTag tag, final AttributeName attributeName, final String attributeValue, final IElementTagStructureHandler structureHandler) { final IEngineConfiguration configuration = context.getConfiguration(); final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration); final IStandardExpression expression = expressionParser.parseExpression(context, attributeValue); final Object expressionResult; if (expression != null && expression instanceof FragmentExpression) { // This is merely a FragmentExpression (not complex, not combined with anything), so we can apply a shortcut // so that we don't require a "null" result for this expression if the template does not exist. That will // save a call to resource.exists() which might be costly. final FragmentExpression.ExecutedFragmentExpression executedFragmentExpression = FragmentExpression.createExecutedFragmentExpression(context, (FragmentExpression) expression, StandardExpressionExecutionContext.RESTRICTED); expressionResult = FragmentExpression.resolveExecutedFragmentExpression(context, executedFragmentExpression, true); } else { expressionResult = expression.execute(context, StandardExpressionExecutionContext.RESTRICTED); } // If result is no-op, there's nothing to execute if (expressionResult == NoOpToken.VALUE) { return; } /* * First of all we should check whether the expression result is a Fragment so that, in such case, we can * avoid creating a String in memory for it and just append its model. */ if (expressionResult != null && expressionResult instanceof Fragment) { if (expressionResult == Fragment.EMPTY_FRAGMENT) { structureHandler.removeBody(); return; } structureHandler.setBody(((Fragment)expressionResult).getTemplateModel(), false); return; } final String unescapedTextStr = (expressionResult == null ? "" : expressionResult.toString()); /* * We will check if there are configured post processors or not. The reason we do this is because output * inserted as a result of a th:utext attribute, even if it might be markup, will never be considered as * 'processable', i.e. no other processors/inliner will ever be able to act on it. The main reason for this * is to protect against code injection. * * So the only other agents that would be able to modify these th:utext results are POST-PROCESSORS. And * they will indeed need markup to have been parsed in order to separate text from structures, so that's why * we check if there actually are any post-processors and, if not (most common case), simply output the * expression result as if it were a mere (unescaped) text node. */ final Set<IPostProcessor> postProcessors = configuration.getPostProcessors(getTemplateMode()); if (postProcessors.isEmpty()) { structureHandler.setBody(unescapedTextStr, false); return; } /* * We have post-processors, so from here one we will have to decide whether we need to parse the unescaped * text or not... */ if (!mightContainStructures(unescapedTextStr)) { // If this text contains no markup structures, there would be no need to parse it or treat it as markup! structureHandler.setBody(unescapedTextStr, false); return; } /* * We have post-processors AND this text might contain structures, so there is no alternative but parsing */ final TemplateModel parsedFragment = configuration.getTemplateManager().parseString( context.getTemplateData(), unescapedTextStr, 0, 0, // we won't apply offset here because the inserted text does not really come from the template itself null, // No template mode forcing required false); // useCache == false because we could potentially pollute the cache with too many entries (th:utext is too variable!) // Setting 'processable' to false avoiding text inliners processing already generated text, // which in turn avoids code injection. structureHandler.setBody(parsedFragment, false); } /* * This method will be used for determining if we actually need to apply a parser to the unescaped text that we * are going to use a a result of this th:utext execution. If there is no '>' character in it, then it is * nothing but a piece of text, and applying the parser would be overkill */ private static boolean mightContainStructures(final CharSequence unescapedText) { int n = unescapedText.length(); char c; while (n-- != 0) { c = unescapedText.charAt(n); if (c == '>' || c == ']') { // Might be the end of a structure! return true; } } return false; } }