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 }