• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.clearsilver.jsilver.syntax;
18 
19 import com.google.clearsilver.jsilver.autoescape.AutoEscapeContext;
20 import com.google.clearsilver.jsilver.autoescape.EscapeMode;
21 import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
22 import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
23 import com.google.clearsilver.jsilver.syntax.node.AAltCommand;
24 import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand;
25 import com.google.clearsilver.jsilver.syntax.node.ACallCommand;
26 import com.google.clearsilver.jsilver.syntax.node.AContentTypeCommand;
27 import com.google.clearsilver.jsilver.syntax.node.ACsOpenPosition;
28 import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
29 import com.google.clearsilver.jsilver.syntax.node.ADefCommand;
30 import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand;
31 import com.google.clearsilver.jsilver.syntax.node.AEvarCommand;
32 import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand;
33 import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand;
34 import com.google.clearsilver.jsilver.syntax.node.AIfCommand;
35 import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand;
36 import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand;
37 import com.google.clearsilver.jsilver.syntax.node.ALvarCommand;
38 import com.google.clearsilver.jsilver.syntax.node.ANameCommand;
39 import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
40 import com.google.clearsilver.jsilver.syntax.node.AUvarCommand;
41 import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
42 import com.google.clearsilver.jsilver.syntax.node.Node;
43 import com.google.clearsilver.jsilver.syntax.node.PCommand;
44 import com.google.clearsilver.jsilver.syntax.node.PPosition;
45 import com.google.clearsilver.jsilver.syntax.node.Start;
46 import com.google.clearsilver.jsilver.syntax.node.TCsOpen;
47 import com.google.clearsilver.jsilver.syntax.node.TString;
48 import com.google.clearsilver.jsilver.syntax.node.Token;
49 
50 /**
51  * Run a context parser (currently only HTML parser) over the AST, determine nodes that need
52  * escaping, and apply the appropriate escaping command to those nodes. The parser is fed literal
53  * data (from DataCommands), which it uses to track the context. When variables (e.g. VarCommand)
54  * are encountered, we query the parser for its current context, and apply the appropriate escaping
55  * command.
56  */
57 public class AutoEscaper extends DepthFirstAdapter {
58 
59   private AutoEscapeContext autoEscapeContext;
60   private boolean skipAutoEscape;
61   private final EscapeMode escapeMode;
62   private final String templateName;
63   private boolean contentTypeCalled;
64 
65   /**
66    * Create an AutoEscaper, which will apply the specified escaping mode. If templateName is
67    * non-null, it will be used while displaying error messages.
68    *
69    * @param mode
70    * @param templateName
71    */
AutoEscaper(EscapeMode mode, String templateName)72   public AutoEscaper(EscapeMode mode, String templateName) {
73     this.templateName = templateName;
74     if (mode.equals(EscapeMode.ESCAPE_NONE)) {
75       throw new JSilverAutoEscapingException("AutoEscaper called when no escaping is required",
76           templateName);
77     }
78     escapeMode = mode;
79     if (mode.isAutoEscapingMode()) {
80       autoEscapeContext = new AutoEscapeContext(mode, templateName);
81       skipAutoEscape = false;
82     } else {
83       autoEscapeContext = null;
84     }
85   }
86 
87   /**
88    * Create an AutoEscaper, which will apply the specified escaping mode. When possible, use
89    * #AutoEscaper(EscapeMode, String) instead. It specifies the template being auto escaped, which
90    * is useful when displaying error messages.
91    *
92    * @param mode
93    */
AutoEscaper(EscapeMode mode)94   public AutoEscaper(EscapeMode mode) {
95     this(mode, null);
96   }
97 
98   @Override
caseStart(Start start)99   public void caseStart(Start start) {
100     if (!escapeMode.isAutoEscapingMode()) {
101       // For an explicit EscapeMode like {@code EscapeMode.ESCAPE_HTML}, we
102       // do not need to parse the rest of the tree. Instead, we just wrap the
103       // entire tree in a <?cs escape ?> node.
104       handleExplicitEscapeMode(start);
105     } else {
106       AutoEscapeContext.AutoEscapeState startState = autoEscapeContext.getCurrentState();
107       // call super.caseStart, which will make us visit the rest of the tree,
108       // so we can determine the appropriate escaping to apply for each
109       // variable.
110       super.caseStart(start);
111       AutoEscapeContext.AutoEscapeState endState = autoEscapeContext.getCurrentState();
112       if (!autoEscapeContext.isPermittedStateChangeForIncludes(startState, endState)) {
113         // If template contains a content-type command, the escaping context
114         // was intentionally changed. Such a change in context is fine as long
115         // as the current template is not included inside another. There is no
116         // way to verify that the template is not an include template however,
117         // so ignore the error and depend on developers doing the right thing.
118         if (contentTypeCalled) {
119           return;
120         }
121         // We do not permit templates to end in a different context than they start in.
122         // This is so that an included template does not modify the context of
123         // the template that includes it.
124         throw new JSilverAutoEscapingException("Template starts in context " + startState
125             + " but ends in different context " + endState, templateName);
126       }
127     }
128   }
129 
handleExplicitEscapeMode(Start start)130   private void handleExplicitEscapeMode(Start start) {
131     AStringExpression escapeExpr =
132         new AStringExpression(new TString("\"" + escapeMode.getEscapeCommand() + "\""));
133 
134     PCommand node = start.getPCommand();
135     AEscapeCommand escape =
136         new AEscapeCommand(new ACsOpenPosition(new TCsOpen("<?cs ", 0, 0)), escapeExpr,
137             (PCommand) node.clone());
138 
139     node.replaceBy(escape);
140   }
141 
142   @Override
caseADataCommand(ADataCommand node)143   public void caseADataCommand(ADataCommand node) {
144     String data = node.getData().getText();
145     autoEscapeContext.setCurrentPosition(node.getData().getLine(), node.getData().getPos());
146     autoEscapeContext.parseData(data);
147   }
148 
149   @Override
caseADefCommand(ADefCommand node)150   public void caseADefCommand(ADefCommand node) {
151   // Ignore the entire defcommand subtree, don't even parse it.
152   }
153 
154   @Override
caseAIfCommand(AIfCommand node)155   public void caseAIfCommand(AIfCommand node) {
156     setCurrentPosition(node.getPosition());
157 
158     /*
159      * Since AutoEscaper is being applied while building the AST, and not during rendering, the html
160      * context of variables is sometimes ambiguous. For instance: <?cs if: X ?><script><?cs /if ?>
161      * <?cs var: MyVar ?>
162      *
163      * Here MyVar may require js escaping or html escaping depending on whether the "if" condition
164      * is true or false.
165      *
166      * To avoid such ambiguity, we require all branches of a conditional statement to end in the
167      * same context. So, <?cs if: X ?><script>X <?cs else ?><script>Y<?cs /if ?> is fine but,
168      *
169      * <?cs if: X ?><script>X <?cs elif: Y ?><script>Y<?cs /if ?> is not.
170      */
171     AutoEscapeContext originalEscapedContext = autoEscapeContext.cloneCurrentEscapeContext();
172     // Save position of the start of if statement.
173     int line = autoEscapeContext.getLineNumber();
174     int column = autoEscapeContext.getColumnNumber();
175 
176     if (node.getBlock() != null) {
177       node.getBlock().apply(this);
178     }
179     AutoEscapeContext.AutoEscapeState ifEndState = autoEscapeContext.getCurrentState();
180     // restore original context before executing else block
181     autoEscapeContext = originalEscapedContext;
182 
183     // Interestingly, getOtherwise() is not null even when the if command
184     // has no else branch. In such cases, getOtherwise() contains a
185     // Noop command.
186     // In practice this does not matter for the checks being run here.
187     if (node.getOtherwise() != null) {
188       node.getOtherwise().apply(this);
189     }
190     AutoEscapeContext.AutoEscapeState elseEndState = autoEscapeContext.getCurrentState();
191 
192     if (!ifEndState.equals(elseEndState)) {
193       throw new JSilverAutoEscapingException("'if/else' branches have different ending contexts "
194           + ifEndState + " and " + elseEndState, templateName, line, column);
195     }
196   }
197 
198   @Override
caseAEscapeCommand(AEscapeCommand node)199   public void caseAEscapeCommand(AEscapeCommand node) {
200     boolean saved_skip = skipAutoEscape;
201     skipAutoEscape = true;
202     node.getCommand().apply(this);
203     skipAutoEscape = saved_skip;
204   }
205 
206   @Override
caseACallCommand(ACallCommand node)207   public void caseACallCommand(ACallCommand node) {
208     saveAutoEscapingContext(node, node.getPosition());
209   }
210 
211   @Override
caseALvarCommand(ALvarCommand node)212   public void caseALvarCommand(ALvarCommand node) {
213     saveAutoEscapingContext(node, node.getPosition());
214   }
215 
216   @Override
caseAEvarCommand(AEvarCommand node)217   public void caseAEvarCommand(AEvarCommand node) {
218     saveAutoEscapingContext(node, node.getPosition());
219   }
220 
221   @Override
caseALincludeCommand(ALincludeCommand node)222   public void caseALincludeCommand(ALincludeCommand node) {
223     saveAutoEscapingContext(node, node.getPosition());
224   }
225 
226   @Override
caseAIncludeCommand(AIncludeCommand node)227   public void caseAIncludeCommand(AIncludeCommand node) {
228     saveAutoEscapingContext(node, node.getPosition());
229   }
230 
231   @Override
caseAHardLincludeCommand(AHardLincludeCommand node)232   public void caseAHardLincludeCommand(AHardLincludeCommand node) {
233     saveAutoEscapingContext(node, node.getPosition());
234   }
235 
236   @Override
caseAHardIncludeCommand(AHardIncludeCommand node)237   public void caseAHardIncludeCommand(AHardIncludeCommand node) {
238     saveAutoEscapingContext(node, node.getPosition());
239   }
240 
241   @Override
caseAVarCommand(AVarCommand node)242   public void caseAVarCommand(AVarCommand node) {
243     applyAutoEscaping(node, node.getPosition());
244   }
245 
246   @Override
caseAAltCommand(AAltCommand node)247   public void caseAAltCommand(AAltCommand node) {
248     applyAutoEscaping(node, node.getPosition());
249   }
250 
251   @Override
caseANameCommand(ANameCommand node)252   public void caseANameCommand(ANameCommand node) {
253     applyAutoEscaping(node, node.getPosition());
254   }
255 
256   @Override
caseAUvarCommand(AUvarCommand node)257   public void caseAUvarCommand(AUvarCommand node) {
258     // Let parser know that was some text that it has not seen
259     setCurrentPosition(node.getPosition());
260     autoEscapeContext.insertText();
261   }
262 
263   /**
264    * Handles a &lt;?cs content-type: "content type" ?&gt; command.
265    *
266    * This command is used when the auto escaping context of a template cannot be determined from its
267    * contents - for example, a CSS stylesheet or a javascript source file. Note that &lt;?cs
268    * content-type: ?&gt; command is not required for all javascript and css templates. If the
269    * template contains a &lt;script&gt; or &lt;style&gt; tag (or is included from another template
270    * within the right tag), auto escaping will recognize the tag and switch context accordingly. On
271    * the other hand, if the template serves a resource that is loaded via a &lt;script src= &gt; or
272    * &lt;link rel &gt; command, the explicit &lt;?cs content-type: ?&gt; command would be required.
273    */
274   @Override
caseAContentTypeCommand(AContentTypeCommand node)275   public void caseAContentTypeCommand(AContentTypeCommand node) {
276     setCurrentPosition(node.getPosition());
277     String contentType = node.getString().getText();
278     // Strip out quotes around the string
279     contentType = contentType.substring(1, contentType.length() - 1);
280     autoEscapeContext.setContentType(contentType);
281     contentTypeCalled = true;
282   }
283 
applyAutoEscaping(PCommand node, PPosition position)284   private void applyAutoEscaping(PCommand node, PPosition position) {
285     setCurrentPosition(position);
286     if (skipAutoEscape) {
287       return;
288     }
289 
290     AStringExpression escapeExpr = new AStringExpression(new TString("\"" + getEscaping() + "\""));
291     AEscapeCommand escape = new AEscapeCommand(position, escapeExpr, (PCommand) node.clone());
292 
293     node.replaceBy(escape);
294     // Now that we have determined the correct escaping for this variable,
295     // let parser know that there was some text that it has not seen. The
296     // parser may choose to update its state based on this.
297     autoEscapeContext.insertText();
298 
299   }
300 
setCurrentPosition(PPosition position)301   private void setCurrentPosition(PPosition position) {
302     // Will eventually call caseACsOpenPosition
303     position.apply(this);
304   }
305 
306   @Override
caseACsOpenPosition(ACsOpenPosition node)307   public void caseACsOpenPosition(ACsOpenPosition node) {
308     Token token = node.getCsOpen();
309     autoEscapeContext.setCurrentPosition(token.getLine(), token.getPos());
310   }
311 
saveAutoEscapingContext(Node node, PPosition position)312   private void saveAutoEscapingContext(Node node, PPosition position) {
313     setCurrentPosition(position);
314     if (skipAutoEscape) {
315       return;
316     }
317     EscapeMode mode = autoEscapeContext.getEscapeModeForCurrentState();
318     AStringExpression escapeStrategy =
319         new AStringExpression(new TString("\"" + mode.getEscapeCommand() + "\""));
320     AAutoescapeCommand command =
321         new AAutoescapeCommand(position, escapeStrategy, (PCommand) node.clone());
322     node.replaceBy(command);
323     autoEscapeContext.insertText();
324   }
325 
getEscaping()326   private String getEscaping() {
327     return autoEscapeContext.getEscapingFunctionForCurrentState();
328   }
329 }
330