1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.webapp.filter;
20
21 import java.io.IOException;
22
23 import javax.servlet.Filter;
24 import javax.servlet.FilterChain;
25 import javax.servlet.FilterConfig;
26 import javax.servlet.ServletContext;
27 import javax.servlet.ServletException;
28 import javax.servlet.ServletRequest;
29 import javax.servlet.ServletResponse;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.apache.commons.fileupload.servlet.ServletFileUpload;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36 import org.apache.myfaces.renderkit.html.util.AddResource;
37 import org.apache.myfaces.renderkit.html.util.AddResource2;
38 import org.apache.myfaces.renderkit.html.util.AddResourceFactory;
39
40 /**
41 * This filter provides a number of functions that many tomahawk components require.
42 * <p>
43 * In tomahawk versions up to and including 1.1.6, it is mandatory to define this filter in the application's
44 * web.xml in order to use some tomahawk components. In Tomahawk version 1.1.7, this filter is now optional;
45 * when defined it will be used as for earlier versions. When omitted, the same functionality is now
46 * automatically provided via classes TomahawkFacesContextFactory and ServeResourcePhaseListener.
47 *
48 * <h2>Response Buffering</h2>
49 *
50 * When the request is for a JSF page, and the configured "resource manager"
51 * requires response buffering then a response wrapper is created which
52 * buffers the entire output from the current request in memory.
53 * <p>
54 * Tomahawk provides at least three "resource managers":
55 * <ul>
56 * <li> DefaultAddResources (the default) does require response buffering.
57 * <li> NonBufferingAddResource does not require response buffering, but it
58 * renders javascript on body and offer support in portlets enviroments.
59 * <li> StreamingAddResources does not, but only provides a subset of the
60 * features of DefaultAddResources and therefore does not work with all
61 * Tomahawk components.
62 * </ul>
63 * <p>
64 * Only one "resource manager" may be configured for a webapp. See class
65 * AddResourceFactory for further details on configuring this.
66 * <p>
67 * When DefaultAddResources is enabled (default behaviour), the resulting
68 * response buffering does cause some unnecessary memory and performance
69 * impact for pages where no component in the page actually registers a
70 * resource that needs to be inserted into the page - but whether a page
71 * does that or not cannot be known until after the page has been rendered.
72 * In the rare case where a request to a JSF page generates non-html
73 * output (eg a PDF is generated as a response to a submit of a jsf page),
74 * the data is unfortunately buffered. However it is not post-processed,
75 * because its http content-type header will not be "text/html" (see other
76 * documentation for this class).
77 *
78 * <h2>Inserting Resources into HTML responses (DefaultAddResources)</h2>
79 *
80 * After the response has been completely generated (and cached in memory),
81 * this filter checks the http content-type header. If it is not html or xhtml,
82 * then the data is simply send to the response stream without further processing.
83 *
84 * For html or xhtml responses, this filter causes the data to be post-processed
85 * to insert any "resources" registered via the AddResources framework. This
86 * allows jsf components (and other code if it wants) to register data that
87 * should be output into an HTML page, particularly into places like an html
88 * "head" section, even when the component occurs after that part of the
89 * response has been generated.
90 * <p>
91 * The default "resource manager" (DefaultAddResources) supports inserting
92 * resources into any part of the generated page. The alternate class
93 * StreamingAddResources does not; it does not buffer output and therefore
94 * can only insert resources for a jsf component into the page after the
95 * point at which that component is rendered. In particular, this means that
96 * components that use external javascript files will not work with that
97 * "resource manager" as [script href=...] only works in the head section
98 * of an html page.
99 *
100 * <h2>Serving Resources from the Classpath</h2>
101 *
102 * Exactly what text gets inserted into an HTML page via a "resource"
103 * (see above) is managed by the AddResources class, not this one. However
104 * often it is useful for jsf components to be able to refer to resources
105 * that are on the classpath, and in particular when the resource is in the
106 * same jar as the component code. Servlet engines do not support serving
107 * resources from the classpath. However the AddResources framework can be
108 * used to generate a special url prefix that this filter can be mapped to,
109 * allowing this to be done. In particular, many Tomahawk components use
110 * this to bundle their necessary resources within the tomahawk jarfile.
111 * <p>
112 * When a request to such a url is found by this filter, the actual resource
113 * is located and streamed back to the user (no buffering required). See the
114 * AddResource class documentation for further details.
115 *
116 * <h2>Handling File Uploads</h2>
117 *
118 * Sometimes an application needs to allow a user to send a file of data
119 * to the web application. The html page needs only to include an input
120 * tag with type=file; clicking on this will prompt the user for a file
121 * to send. The browser will then send an http request to the server
122 * which is marked as a "mime multipart request" with normal http post
123 * parameters in one mime part, and the file contents in another mime part.
124 * <p>
125 * This filter also handles such requests, using the Apache HttpClient
126 * library to save the file into a configurable local directory before
127 * allowing the normal processing for the url that the post request
128 * refers to. A number of configuration properties on this filter control
129 * maximum file upload sizes and various other useful settings. See the
130 * documentation for the init method for more details.
131 *
132 * <h2>Avoiding Processing</h2>
133 *
134 * When the ExtensionsFilter is enabled, and the DefaultAddResources
135 * implementation is used then there is no way to avoid having the
136 * response buffered in memory. However as long as the mime-type set
137 * for the response data is <i>not</i> text/html then the data will
138 * be written out without any modifications.
139 *
140 * @author Sylvain Vieujot (latest modification by $Author: lu4242 $)
141 * @version $Revision: 954965 $ $Date: 2010-06-15 11:58:31 -0500 (Mar, 15 Jun 2010) $
142 */
143 public class ExtensionsFilter implements Filter {
144
145 private Log log = LogFactory.getLog(ExtensionsFilter.class);
146
147 private int _uploadMaxSize = 100 * 1024 * 1024; // 100 MB
148
149 private int _uploadMaxFileSize = 100 * 1024 * 1024; // 100 MB
150
151 private int _uploadThresholdSize = 1 * 1024 * 1024; // 1 MB
152
153 private String _uploadRepositoryPath = null; //standard temp directory
154
155 private boolean _cacheFileSizeErrors = false;
156
157 private ServletContext _servletContext;
158
159 public static final String DOFILTER_CALLED = "org.apache.myfaces.component.html.util.ExtensionFilter.doFilterCalled";
160
161 /**
162 * Flag that indicates extensions filter initialization code has been done.
163 * This flag is added on application map.
164 */
165 public static final String EXTENSIONS_FILTER_INITIALIZED = "org.apache.myfaces.tomahawk.EXTENSIONS_FILTER_INITIALIZED";
166
167 /**
168 * Init method for this filter.
169 * <p>
170 * The following filter init parameters can be configured in the web.xml file
171 * for this filter:
172 * <ul>
173 * <li>uploadMaxFileSize</li>
174 * <li>uploadThresholdSize</li>
175 * <li>uploadRepositoryPath</li>
176 * <li>uploadMaxSize</li>
177 * <li>cacheFileSizeErrors</li>
178 * </ul>
179 * </p>
180 * <p>
181 * All size parameters may have the suffix "g" (gigabytes), "m" (megabytes) or "k" (kilobytes).
182 * </p>
183 *
184 * <h2>uploadMaxFileSize</h2>
185 *
186 * Sets the maximum allowable size for uploaded files.
187 * <p>
188 * If the user attempts to upload a file which is larger than this, then the data <i>is</i>
189 * transmitted from the browser to the server (this cannot be prevented with standard HTML
190 * functionality). However the file will not be saved in memory or on disk. An error message
191 * will be added to the standard JSF error messages, and the page re-rendered (as for a
192 * validation failure).
193 * </p>
194 * <p>
195 * The default value is 100 Megabytes.
196 * </p>
197 *
198 * <h2>uploadThresholdSize</h2>
199 *
200 * Sets the size threshold beyond which files are written directly to disk. Files which are
201 * smaller than this are simply held in memory. The default is 1 Megabyte.
202 *
203 * <h2>uploadRepositoryPath</h2>
204 *
205 * Sets the directory in which temporary files (ie caches for those uploaded files that
206 * are larger than uploadThresholdSize) are to be stored.
207 *
208 * <h2>uploadMaxSize</h2>
209 *
210 * Sets the maximum allowable size for the current request. If not set, its value is the
211 * value set on uploadMaxFileSize param.
212 *
213 * <h2>cacheFileSizeErrors</h2>
214 *
215 * Catch and swallow FileSizeLimitExceededExceptions in order to return as
216 * many usable items as possible.
217 *
218 */
219 public void init(FilterConfig filterConfig) {
220 // Note that the code here to extract FileUpload configuration params is not actually used.
221 // The handling of multipart requests was moved from this Filter into a custom FacesContext
222 // (TomahawkFacesContextWrapper) so that Portlets could be supported (Portlets cannot use
223 // servlet filters).
224 //
225 // For backwards compatibility, the TomahawkFacesContextWrapper class *parses* the
226 // web.xml to retrieve these same filter config params. That is IMO seriously ugly
227 // and hopefully will be fixed.
228
229 String param = filterConfig.getInitParameter("uploadMaxFileSize");
230
231 _uploadMaxFileSize = resolveSize(param, _uploadMaxFileSize);
232
233 param = filterConfig.getInitParameter("uploadMaxSize");
234
235 if (param != null)
236 {
237 _uploadMaxSize = resolveSize(param, _uploadMaxSize);
238 }
239 else
240 {
241 //If not set, default to uploadMaxFileSize
242 _uploadMaxSize = resolveSize(param, _uploadMaxFileSize);
243 }
244
245 param = filterConfig.getInitParameter("uploadThresholdSize");
246
247 _uploadThresholdSize = resolveSize(param, _uploadThresholdSize);
248
249 _uploadRepositoryPath = filterConfig.getInitParameter("uploadRepositoryPath");
250
251 _cacheFileSizeErrors = getBooleanValue(filterConfig.getInitParameter("cacheFileSizeErrors"), false);
252
253 _servletContext = filterConfig.getServletContext();
254
255 filterConfig.getServletContext().setAttribute(EXTENSIONS_FILTER_INITIALIZED, true);
256 }
257
258 private int resolveSize(String param, int defaultValue) {
259 int numberParam = defaultValue;
260
261 if (param != null) {
262 param = param.toLowerCase();
263 int factor = 1;
264 String number = param;
265
266 if (param.endsWith("g")) {
267 factor = 1024 * 1024 * 1024;
268 number = param.substring(0, param.length() - 1);
269 } else if (param.endsWith("m")) {
270 factor = 1024 * 1024;
271 number = param.substring(0, param.length() - 1);
272 } else if (param.endsWith("k")) {
273 factor = 1024;
274 number = param.substring(0, param.length() - 1);
275 }
276
277 numberParam = Integer.parseInt(number) * factor;
278 }
279 return numberParam;
280 }
281
282 private static boolean getBooleanValue(String initParameter, boolean defaultVal)
283 {
284 if(initParameter == null || initParameter.trim().length()==0)
285 return defaultVal;
286
287 return (initParameter.equalsIgnoreCase("on") || initParameter.equals("1") || initParameter.equalsIgnoreCase("true"));
288 }
289
290 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
291
292 if(request.getAttribute(DOFILTER_CALLED)!=null)
293 {
294 chain.doFilter(request, response);
295 return;
296 }
297
298 request.setAttribute(DOFILTER_CALLED,"true");
299
300 if (!(response instanceof HttpServletResponse)) {
301 //If this is a portlet request, just continue the chaining
302 chain.doFilter(request, response);
303 return;
304 }
305
306 HttpServletResponse httpResponse = (HttpServletResponse) response;
307 HttpServletRequest httpRequest = (HttpServletRequest) request;
308
309 // Serve resources
310 AddResource addResource;
311
312 try
313 {
314 addResource = AddResourceFactory.getInstance(httpRequest,_servletContext);
315 if( addResource.isResourceUri(_servletContext, httpRequest ) ){
316 addResource.serveResource(_servletContext, httpRequest, httpResponse);
317 return;
318 }
319 }
320 catch(Throwable th)
321 {
322 log.error("Exception wile retrieving addResource",th);
323 throw new ServletException(th);
324 }
325
326 HttpServletRequest extendedRequest = httpRequest;
327
328 // For multipart/form-data requests
329 // This is done by TomahawkFacesContextWrapper
330 if (ServletFileUpload.isMultipartContent(httpRequest)) {
331 extendedRequest = new MultipartRequestWrapper(httpRequest, _uploadMaxFileSize,
332 _uploadThresholdSize, _uploadRepositoryPath, _uploadMaxSize, _cacheFileSizeErrors);
333 }
334
335 try
336 {
337 if (addResource instanceof AddResource2)
338 {
339 ((AddResource2)addResource).responseStarted(_servletContext, extendedRequest);
340 }
341 else
342 {
343 addResource.responseStarted();
344 }
345
346 //This case is necessary when is used
347 //org.apache.myfaces.renderkit.html.util.DefaultAddResource
348 //Buffers the output and add to the header the necessary stuff
349 //In other case this is simply ignored (NonBufferingAddResource and
350 //StreamingAddResource), because this not require buffering
351 //and the chaining continues.
352 if (addResource.requiresBuffer())
353 {
354 ExtensionsResponseWrapper extendedResponse = new ExtensionsResponseWrapper((HttpServletResponse) response);
355
356 // Standard request
357 chain.doFilter(extendedRequest, extendedResponse);
358
359 extendedResponse.finishResponse();
360
361 // write the javascript stuff for myfaces and headerInfo, if needed
362 HttpServletResponse servletResponse = (HttpServletResponse)response;
363
364 // only parse HTML responses
365 if (extendedResponse.getContentType() != null && isValidContentType(extendedResponse.getContentType()))
366 {
367 addResource.parseResponse(extendedRequest, extendedResponse.toString(),
368 servletResponse);
369
370 addResource.writeMyFacesJavascriptBeforeBodyEnd(extendedRequest,
371 servletResponse);
372
373 if( ! addResource.hasHeaderBeginInfos() ){
374 // writes the response if no header info is needed
375 addResource.writeResponse(extendedRequest, servletResponse);
376 return;
377 }
378
379 // Some headerInfo has to be added
380 addResource.writeWithFullHeader(extendedRequest, servletResponse);
381
382 // writes the response
383 addResource.writeResponse(extendedRequest, servletResponse);
384 }
385 else
386 {
387
388 byte[] responseArray = extendedResponse.getBytes();
389
390 if(responseArray.length > 0)
391 {
392 // When not filtering due to not valid content-type, deliver the byte-array instead of a charset-converted string.
393 // Otherwise a binary stream gets corrupted.
394 servletResponse.getOutputStream().write(responseArray);
395 }
396 }
397 }
398 else
399 {
400 chain.doFilter(extendedRequest, response);
401 }
402 }
403 finally
404 {
405 addResource.responseFinished();
406 }
407
408 //chain.doFilter(extendedRequest, response);
409 }
410
411 public boolean isValidContentType(String contentType)
412 {
413 return contentType.startsWith("text/html") ||
414 contentType.startsWith("text/xml") ||
415 contentType.startsWith("application/xhtml+xml") ||
416 contentType.startsWith("application/xml");
417 }
418
419 /**
420 * Destroy method for this filter
421 */
422 public void destroy() {
423 // NoOp
424 }
425
426
427 }