• 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.template;
18 
19 import com.google.clearsilver.jsilver.autoescape.AutoEscapeContext;
20 import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
21 import com.google.clearsilver.jsilver.autoescape.EscapeMode;
22 import com.google.clearsilver.jsilver.data.DataContext;
23 import com.google.clearsilver.jsilver.data.UniqueStack;
24 import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
25 import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
26 import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
27 import com.google.clearsilver.jsilver.functions.FunctionExecutor;
28 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
29 import com.google.clearsilver.jsilver.values.Value;
30 
31 import java.io.IOException;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.logging.Logger;
37 
38 /**
39  * Default implementation of RenderingContext.
40  */
41 public class DefaultRenderingContext implements RenderingContext, FunctionExecutor {
42 
43   public static final Logger logger = Logger.getLogger(DefaultRenderingContext.class.getName());
44   private final DataContext dataContext;
45   private final ResourceLoader resourceLoader;
46   private final Appendable out;
47   private final FunctionExecutor globalFunctionExecutor;
48   private final AutoEscapeOptions autoEscapeOptions;
49   private final UniqueStack<String> includeStack;
50 
51   private List<String> escaperStack = new ArrayList<String>(8); // seems like a reasonable initial
52                                                                 // capacity.
53   private String currentEscaper; // optimization to reduce List lookup.
54 
55   private List<Template> executionStack = new ArrayList<Template>(8);
56 
57   private Map<String, Macro> macros = new HashMap<String, Macro>();
58   private List<EscapeMode> autoEscapeStack = new ArrayList<EscapeMode>();
59   private EscapeMode autoEscapeMode;
60   private AutoEscapeContext autoEscapeContext;
61   private int line;
62   private int column;
63   private AutoEscapeContext.AutoEscapeState startingAutoEscapeState;
64 
DefaultRenderingContext(DataContext dataContext, ResourceLoader resourceLoader, Appendable out, FunctionExecutor globalFunctionExecutor, AutoEscapeOptions autoEscapeOptions)65   public DefaultRenderingContext(DataContext dataContext, ResourceLoader resourceLoader,
66       Appendable out, FunctionExecutor globalFunctionExecutor, AutoEscapeOptions autoEscapeOptions) {
67     this.dataContext = dataContext;
68     this.resourceLoader = resourceLoader;
69     this.out = out;
70     this.globalFunctionExecutor = globalFunctionExecutor;
71     this.autoEscapeOptions = autoEscapeOptions;
72     this.autoEscapeMode = EscapeMode.ESCAPE_NONE;
73     this.autoEscapeContext = null;
74     this.includeStack = new UniqueStack<String>();
75   }
76 
77   /**
78    * Lookup a function by name, execute it and return the results.
79    */
80   @Override
executeFunction(String name, Value... args)81   public Value executeFunction(String name, Value... args) {
82     return globalFunctionExecutor.executeFunction(name, args);
83   }
84 
85   @Override
escape(String name, String input, Appendable output)86   public void escape(String name, String input, Appendable output) throws IOException {
87     globalFunctionExecutor.escape(name, input, output);
88   }
89 
90   @Override
isEscapingFunction(String name)91   public boolean isEscapingFunction(String name) {
92     return globalFunctionExecutor.isEscapingFunction(name);
93   }
94 
95   @Override
pushEscapingFunction(String name)96   public void pushEscapingFunction(String name) {
97     escaperStack.add(currentEscaper);
98     if (name == null || name.equals("")) {
99       currentEscaper = null;
100     } else {
101       currentEscaper = name;
102     }
103   }
104 
105   @Override
popEscapingFunction()106   public void popEscapingFunction() {
107     int len = escaperStack.size();
108     if (len == 0) {
109       throw new IllegalStateException("No more escaping functions to pop.");
110     }
111     currentEscaper = escaperStack.remove(len - 1);
112   }
113 
114   @Override
writeEscaped(String text)115   public void writeEscaped(String text) {
116     // If runtime auto escaping is enabled, only apply it if
117     // we are not going to do any other default escaping on the variable.
118     boolean applyAutoEscape = isRuntimeAutoEscaping() && (currentEscaper == null);
119     if (applyAutoEscape) {
120       autoEscapeContext.setCurrentPosition(line, column);
121       pushEscapingFunction(autoEscapeContext.getEscapingFunctionForCurrentState());
122     }
123     try {
124       if (shouldLogEscapedVariables()) {
125         StringBuilder tmp = new StringBuilder();
126         globalFunctionExecutor.escape(currentEscaper, text, tmp);
127         if (!tmp.toString().equals(text)) {
128           logger.warning(new StringBuilder(getLoggingPrefix()).append(" Auto-escape changed [")
129               .append(text).append("] to [").append(tmp.toString()).append("]").toString());
130         }
131         out.append(tmp);
132       } else {
133         globalFunctionExecutor.escape(currentEscaper, text, out);
134       }
135     } catch (IOException e) {
136       throw new JSilverIOException(e);
137     } finally {
138       if (applyAutoEscape) {
139         autoEscapeContext.insertText();
140         popEscapingFunction();
141       }
142     }
143   }
144 
getLoggingPrefix()145   private String getLoggingPrefix() {
146     return "[" + getCurrentResourceName() + ":" + line + ":" + column + "]";
147   }
148 
shouldLogEscapedVariables()149   private boolean shouldLogEscapedVariables() {
150     return (autoEscapeOptions != null && autoEscapeOptions.getLogEscapedVariables());
151   }
152 
153   @Override
writeUnescaped(CharSequence text)154   public void writeUnescaped(CharSequence text) {
155     if (isRuntimeAutoEscaping() && (currentEscaper == null)) {
156       autoEscapeContext.setCurrentPosition(line, column);
157       autoEscapeContext.parseData(text.toString());
158     }
159     try {
160       out.append(text);
161     } catch (IOException e) {
162       throw new JSilverIOException(e);
163     }
164   }
165 
166   @Override
pushExecutionContext(Template template)167   public void pushExecutionContext(Template template) {
168     executionStack.add(template);
169   }
170 
171   @Override
popExecutionContext()172   public void popExecutionContext() {
173     executionStack.remove(executionStack.size() - 1);
174   }
175 
176   @Override
setCurrentPosition(int line, int column)177   public void setCurrentPosition(int line, int column) {
178     // TODO: Should these be saved in executionStack as part
179     // of pushExecutionContext?
180     this.line = line;
181     this.column = column;
182   }
183 
184   @Override
registerMacro(String name, Macro macro)185   public void registerMacro(String name, Macro macro) {
186     macros.put(name, macro);
187   }
188 
189   @Override
findMacro(String name)190   public Macro findMacro(String name) {
191     Macro macro = macros.get(name);
192     if (macro == null) {
193       throw new JSilverInterpreterException("No such macro: " + name);
194     }
195     return macro;
196   }
197 
198   @Override
getDataContext()199   public DataContext getDataContext() {
200     return dataContext;
201   }
202 
203   @Override
getResourceLoader()204   public ResourceLoader getResourceLoader() {
205     return resourceLoader;
206   }
207 
208   @Override
getAutoEscapeOptions()209   public AutoEscapeOptions getAutoEscapeOptions() {
210     return autoEscapeOptions;
211   }
212 
213   @Override
getAutoEscapeMode()214   public EscapeMode getAutoEscapeMode() {
215     if (isRuntimeAutoEscaping() || (currentEscaper != null)) {
216       return EscapeMode.ESCAPE_NONE;
217     } else {
218       return autoEscapeMode;
219     }
220   }
221 
222   @Override
pushAutoEscapeMode(EscapeMode mode)223   public void pushAutoEscapeMode(EscapeMode mode) {
224     if (isRuntimeAutoEscaping()) {
225       throw new JSilverInterpreterException(
226           "cannot call pushAutoEscapeMode while runtime auto escaping is in progress");
227     }
228     autoEscapeStack.add(autoEscapeMode);
229     autoEscapeMode = mode;
230   }
231 
232   @Override
popAutoEscapeMode()233   public void popAutoEscapeMode() {
234     int len = autoEscapeStack.size();
235     if (len == 0) {
236       throw new IllegalStateException("No more auto escaping modes to pop.");
237     }
238     autoEscapeMode = autoEscapeStack.remove(autoEscapeStack.size() - 1);
239   }
240 
241   @Override
isRuntimeAutoEscaping()242   public boolean isRuntimeAutoEscaping() {
243     return autoEscapeContext != null;
244   }
245 
246   /**
247    * {@inheritDoc}
248    *
249    * @throws JSilverInterpreterException if startRuntimeAutoEscaping is called while runtime
250    *         autoescaping is already in progress.
251    */
252   @Override
startRuntimeAutoEscaping()253   public void startRuntimeAutoEscaping() {
254     if (isRuntimeAutoEscaping()) {
255       throw new JSilverInterpreterException("startRuntimeAutoEscaping() is not re-entrant at "
256           + getCurrentResourceName());
257     }
258     if (!autoEscapeMode.equals(EscapeMode.ESCAPE_NONE)) {
259       // TODO: Get the resourceName as a parameter to this function
260       autoEscapeContext = new AutoEscapeContext(autoEscapeMode, getCurrentResourceName());
261       startingAutoEscapeState = autoEscapeContext.getCurrentState();
262     } else {
263       autoEscapeContext = null;
264     }
265   }
266 
getCurrentResourceName()267   private String getCurrentResourceName() {
268     if (executionStack.size() == 0) {
269       return "";
270     } else {
271       return executionStack.get(executionStack.size() - 1).getDisplayName();
272     }
273   }
274 
275   @Override
stopRuntimeAutoEscaping()276   public void stopRuntimeAutoEscaping() {
277     if (autoEscapeContext != null) {
278       if (!startingAutoEscapeState.equals(autoEscapeContext.getCurrentState())) {
279         // We do not allow a macro call to change context of the rest of the template.
280         // Since the rest of the template has already been auto-escaped at parse time
281         // with the assumption that the macro call will not modify the context.
282         throw new JSilverAutoEscapingException("Macro starts in context " + startingAutoEscapeState
283             + " but ends in different context " + autoEscapeContext.getCurrentState(),
284             autoEscapeContext.getResourceName());
285       }
286     }
287     autoEscapeContext = null;
288   }
289 
290   @Override
pushIncludeStackEntry(String templateName)291   public boolean pushIncludeStackEntry(String templateName) {
292     return includeStack.push(templateName);
293   }
294 
295   @Override
popIncludeStackEntry(String templateName)296   public boolean popIncludeStackEntry(String templateName) {
297     return templateName.equals(includeStack.pop());
298   }
299 
300   @Override
getIncludedTemplateNames()301   public Iterable<String> getIncludedTemplateNames() {
302     return includeStack;
303   }
304 }
305