/* * Copyright (C) 2010 Google Inc. * * 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 com.google.clearsilver.jsilver.syntax; import com.google.clearsilver.jsilver.autoescape.AutoEscapeContext; import com.google.clearsilver.jsilver.autoescape.EscapeMode; import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException; import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; import com.google.clearsilver.jsilver.syntax.node.AAltCommand; import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand; import com.google.clearsilver.jsilver.syntax.node.ACallCommand; import com.google.clearsilver.jsilver.syntax.node.AContentTypeCommand; import com.google.clearsilver.jsilver.syntax.node.ACsOpenPosition; import com.google.clearsilver.jsilver.syntax.node.ADataCommand; import com.google.clearsilver.jsilver.syntax.node.ADefCommand; import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand; import com.google.clearsilver.jsilver.syntax.node.AEvarCommand; import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand; import com.google.clearsilver.jsilver.syntax.node.AIfCommand; import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand; import com.google.clearsilver.jsilver.syntax.node.ALvarCommand; import com.google.clearsilver.jsilver.syntax.node.ANameCommand; import com.google.clearsilver.jsilver.syntax.node.AStringExpression; import com.google.clearsilver.jsilver.syntax.node.AUvarCommand; import com.google.clearsilver.jsilver.syntax.node.AVarCommand; import com.google.clearsilver.jsilver.syntax.node.Node; import com.google.clearsilver.jsilver.syntax.node.PCommand; import com.google.clearsilver.jsilver.syntax.node.PPosition; import com.google.clearsilver.jsilver.syntax.node.Start; import com.google.clearsilver.jsilver.syntax.node.TCsOpen; import com.google.clearsilver.jsilver.syntax.node.TString; import com.google.clearsilver.jsilver.syntax.node.Token; /** * Run a context parser (currently only HTML parser) over the AST, determine nodes that need * escaping, and apply the appropriate escaping command to those nodes. The parser is fed literal * data (from DataCommands), which it uses to track the context. When variables (e.g. VarCommand) * are encountered, we query the parser for its current context, and apply the appropriate escaping * command. */ public class AutoEscaper extends DepthFirstAdapter { private AutoEscapeContext autoEscapeContext; private boolean skipAutoEscape; private final EscapeMode escapeMode; private final String templateName; private boolean contentTypeCalled; /** * Create an AutoEscaper, which will apply the specified escaping mode. If templateName is * non-null, it will be used while displaying error messages. * * @param mode * @param templateName */ public AutoEscaper(EscapeMode mode, String templateName) { this.templateName = templateName; if (mode.equals(EscapeMode.ESCAPE_NONE)) { throw new JSilverAutoEscapingException("AutoEscaper called when no escaping is required", templateName); } escapeMode = mode; if (mode.isAutoEscapingMode()) { autoEscapeContext = new AutoEscapeContext(mode, templateName); skipAutoEscape = false; } else { autoEscapeContext = null; } } /** * Create an AutoEscaper, which will apply the specified escaping mode. When possible, use * #AutoEscaper(EscapeMode, String) instead. It specifies the template being auto escaped, which * is useful when displaying error messages. * * @param mode */ public AutoEscaper(EscapeMode mode) { this(mode, null); } @Override public void caseStart(Start start) { if (!escapeMode.isAutoEscapingMode()) { // For an explicit EscapeMode like {@code EscapeMode.ESCAPE_HTML}, we // do not need to parse the rest of the tree. Instead, we just wrap the // entire tree in a node. handleExplicitEscapeMode(start); } else { AutoEscapeContext.AutoEscapeState startState = autoEscapeContext.getCurrentState(); // call super.caseStart, which will make us visit the rest of the tree, // so we can determine the appropriate escaping to apply for each // variable. super.caseStart(start); AutoEscapeContext.AutoEscapeState endState = autoEscapeContext.getCurrentState(); if (!autoEscapeContext.isPermittedStateChangeForIncludes(startState, endState)) { // If template contains a content-type command, the escaping context // was intentionally changed. Such a change in context is fine as long // as the current template is not included inside another. There is no // way to verify that the template is not an include template however, // so ignore the error and depend on developers doing the right thing. if (contentTypeCalled) { return; } // We do not permit templates to end in a different context than they start in. // This is so that an included template does not modify the context of // the template that includes it. throw new JSilverAutoEscapingException("Template starts in context " + startState + " but ends in different context " + endState, templateName); } } } private void handleExplicitEscapeMode(Start start) { AStringExpression escapeExpr = new AStringExpression(new TString("\"" + escapeMode.getEscapeCommand() + "\"")); PCommand node = start.getPCommand(); AEscapeCommand escape = new AEscapeCommand(new ACsOpenPosition(new TCsOpen("