001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.web.util;
018:
019: import java.io.UnsupportedEncodingException;
020: import java.net.URLDecoder;
021:
022: import javax.servlet.http.HttpServletRequest;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026:
027: import org.springframework.util.StringUtils;
028:
029: /**
030: * Helper class for URL path matching. Provides support for URL paths in
031: * RequestDispatcher includes and support for consistent URL decoding.
032: *
033: * <p>Used by {@link org.springframework.web.servlet.handler.AbstractUrlHandlerMapping},
034: * {@link org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver}
035: * and {@link org.springframework.web.servlet.support.RequestContext} for path matching
036: * and/or URI determination.
037: *
038: * @author Juergen Hoeller
039: * @author Rob Harrop
040: * @since 14.01.2004
041: */
042: public class UrlPathHelper {
043:
044: /**
045: * @deprecated as of Spring 2.0, in favor of <code>WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE</code>
046: * @see org.springframework.web.util.WebUtils#INCLUDE_REQUEST_URI_ATTRIBUTE
047: */
048: public static final String INCLUDE_URI_REQUEST_ATTRIBUTE = WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE;
049:
050: /**
051: * @deprecated as of Spring 2.0, in favor of <code>WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE</code>
052: * @see org.springframework.web.util.WebUtils#INCLUDE_CONTEXT_PATH_ATTRIBUTE
053: */
054: public static final String INCLUDE_CONTEXT_PATH_REQUEST_ATTRIBUTE = WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE;
055:
056: /**
057: * @deprecated as of Spring 2.0, in favor of <code>WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE</code>
058: * @see org.springframework.web.util.WebUtils#INCLUDE_SERVLET_PATH_ATTRIBUTE
059: */
060: public static final String INCLUDE_SERVLET_PATH_REQUEST_ATTRIBUTE = WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE;
061:
062: private final Log logger = LogFactory.getLog(getClass());
063:
064: private boolean alwaysUseFullPath = false;
065:
066: private boolean urlDecode = true;
067:
068: private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
069:
070: /**
071: * Set if URL lookup should always use full path within current servlet
072: * context. Else, the path within the current servlet mapping is used
073: * if applicable (i.e. in the case of a ".../*" servlet mapping in web.xml).
074: * Default is "false".
075: */
076: public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
077: this .alwaysUseFullPath = alwaysUseFullPath;
078: }
079:
080: /**
081: * Set if context path and request URI should be URL-decoded.
082: * Both are returned <i>undecoded</i> by the Servlet API,
083: * in contrast to the servlet path.
084: * <p>Uses either the request encoding or the default encoding according
085: * to the Servlet spec (ISO-8859-1).
086: * <p>Default is "true", as of Spring 2.5.
087: * @see #getServletPath
088: * @see #getContextPath
089: * @see #getRequestUri
090: * @see WebUtils#DEFAULT_CHARACTER_ENCODING
091: * @see javax.servlet.ServletRequest#getCharacterEncoding()
092: * @see java.net.URLDecoder#decode(String, String)
093: */
094: public void setUrlDecode(boolean urlDecode) {
095: this .urlDecode = urlDecode;
096: }
097:
098: /**
099: * Set the default character encoding to use for URL decoding.
100: * Default is ISO-8859-1, according to the Servlet spec.
101: * <p>If the request specifies a character encoding itself, the request
102: * encoding will override this setting. This also allows for generically
103: * overriding the character encoding in a filter that invokes the
104: * <code>ServletRequest.setCharacterEncoding</code> method.
105: * @param defaultEncoding the character encoding to use
106: * @see #determineEncoding
107: * @see javax.servlet.ServletRequest#getCharacterEncoding()
108: * @see javax.servlet.ServletRequest#setCharacterEncoding(String)
109: * @see WebUtils#DEFAULT_CHARACTER_ENCODING
110: */
111: public void setDefaultEncoding(String defaultEncoding) {
112: this .defaultEncoding = defaultEncoding;
113: }
114:
115: /**
116: * Return the default character encoding to use for URL decoding.
117: */
118: protected String getDefaultEncoding() {
119: return this .defaultEncoding;
120: }
121:
122: /**
123: * Return the mapping lookup path for the given request, within the current
124: * servlet mapping if applicable, else within the web application.
125: * <p>Detects include request URL if called within a RequestDispatcher include.
126: * @param request current HTTP request
127: * @return the lookup path
128: * @see #getPathWithinApplication
129: * @see #getPathWithinServletMapping
130: */
131: public String getLookupPathForRequest(HttpServletRequest request) {
132: // Always use full path within current servlet context?
133: if (this .alwaysUseFullPath) {
134: return getPathWithinApplication(request);
135: }
136: // Else, use path within current servlet mapping if applicable
137: String rest = getPathWithinServletMapping(request);
138: if (!"".equals(rest)) {
139: return rest;
140: } else {
141: return getPathWithinApplication(request);
142: }
143: }
144:
145: /**
146: * Return the path within the servlet mapping for the given request,
147: * i.e. the part of the request's URL beyond the part that called the servlet,
148: * or "" if the whole URL has been used to identify the servlet.
149: * <p>Detects include request URL if called within a RequestDispatcher include.
150: * <p>E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a".
151: * <p>E.g.: servlet mapping = "/test"; request URI = "/test" -> "".
152: * <p>E.g.: servlet mapping = "/*.test"; request URI = "/a.test" -> "".
153: * @param request current HTTP request
154: * @return the path within the servlet mapping, or ""
155: */
156: public String getPathWithinServletMapping(HttpServletRequest request) {
157: String pathWithinApp = getPathWithinApplication(request);
158: String servletPath = getServletPath(request);
159: if (pathWithinApp.startsWith(servletPath)) {
160: // Normal case: URI contains servlet path.
161: return pathWithinApp.substring(servletPath.length());
162: } else {
163: // Special case: URI is different from servlet path.
164: // Can happen e.g. with index page: URI="/", servletPath="/index.html"
165: // Use servlet path in this case, as it indicates the actual target path.
166: return servletPath;
167: }
168: }
169:
170: /**
171: * Return the path within the web application for the given request.
172: * <p>Detects include request URL if called within a RequestDispatcher include.
173: * @param request current HTTP request
174: * @return the path within the web application
175: */
176: public String getPathWithinApplication(HttpServletRequest request) {
177: String contextPath = getContextPath(request);
178: String requestUri = getRequestUri(request);
179: if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
180: // Normal case: URI contains context path.
181: String path = requestUri.substring(contextPath.length());
182: return (StringUtils.hasText(path) ? path : "/");
183: } else {
184: // Special case: rather unusual.
185: return requestUri;
186: }
187: }
188:
189: /**
190: * Return the request URI for the given request, detecting an include request
191: * URL if called within a RequestDispatcher include.
192: * <p>As the value returned by <code>request.getRequestURI()</code> is <i>not</i>
193: * decoded by the servlet container, this method will decode it.
194: * <p>The URI that the web container resolves <i>should</i> be correct, but some
195: * containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid"
196: * in the URI. This method cuts off such incorrect appendices.
197: * @param request current HTTP request
198: * @return the request URI
199: */
200: public String getRequestUri(HttpServletRequest request) {
201: String uri = (String) request
202: .getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
203: if (uri == null) {
204: uri = request.getRequestURI();
205: }
206: return decodeAndCleanUriString(request, uri);
207: }
208:
209: /**
210: * Return the context path for the given request, detecting an include request
211: * URL if called within a RequestDispatcher include.
212: * <p>As the value returned by <code>request.getContextPath()</code> is <i>not</i>
213: * decoded by the servlet container, this method will decode it.
214: * @param request current HTTP request
215: * @return the context path
216: */
217: public String getContextPath(HttpServletRequest request) {
218: String contextPath = (String) request
219: .getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE);
220: if (contextPath == null) {
221: contextPath = request.getContextPath();
222: }
223: return decodeRequestString(request, contextPath);
224: }
225:
226: /**
227: * Return the servlet path for the given request, regarding an include request
228: * URL if called within a RequestDispatcher include.
229: * <p>As the value returned by <code>request.getServletPath()</code> is already
230: * decoded by the servlet container, this method will not attempt to decode it.
231: * @param request current HTTP request
232: * @return the servlet path
233: */
234: public String getServletPath(HttpServletRequest request) {
235: String servletPath = (String) request
236: .getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE);
237: if (servletPath == null) {
238: servletPath = request.getServletPath();
239: }
240: return servletPath;
241: }
242:
243: /**
244: * Return the request URI for root of the given request. If this is a forwarded request,
245: * correctly resolves to the request URI of the original request. Relies on the Servlet
246: * 2.4 'forward' attributes. These attributes may be set by other components when
247: * running in a Servlet 2.3 environment.
248: */
249: public String getOriginatingRequestUri(HttpServletRequest request) {
250: String uri = (String) request
251: .getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE);
252: if (uri == null) {
253: uri = request.getRequestURI();
254: }
255: return decodeAndCleanUriString(request, uri);
256: }
257:
258: /**
259: * Return the context path for the given request, detecting an include request
260: * URL if called within a RequestDispatcher include.
261: * <p>As the value returned by <code>request.getContextPath()</code> is <i>not</i>
262: * decoded by the servlet container, this method will decode it.
263: * @param request current HTTP request
264: * @return the context path
265: */
266: public String getOriginatingContextPath(HttpServletRequest request) {
267: String contextPath = (String) request
268: .getAttribute(WebUtils.FORWARD_CONTEXT_PATH_ATTRIBUTE);
269: if (contextPath == null) {
270: contextPath = request.getContextPath();
271: }
272: return decodeRequestString(request, contextPath);
273: }
274:
275: /**
276: * Return the request URI for root of the given request. If this is a forwarded request,
277: * correctly resolves to the request URI of the original request. Relies on the Servlet
278: * 2.4 'forward' attributes. These attributes may be set by other components when
279: * running in a Servlet 2.3 environment.
280: */
281: public String getOriginatingQueryString(HttpServletRequest request) {
282: String queryString = (String) request
283: .getAttribute(WebUtils.FORWARD_QUERY_STRING_ATTRIBUTE);
284: if (queryString == null) {
285: queryString = request.getQueryString();
286: }
287: return queryString;
288: }
289:
290: /**
291: * Decode the supplied URI string and strips any extraneous portion after a ';'.
292: */
293: private String decodeAndCleanUriString(HttpServletRequest request,
294: String uri) {
295: uri = decodeRequestString(request, uri);
296: int semicolonIndex = uri.indexOf(';');
297: return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex)
298: : uri);
299: }
300:
301: /**
302: * Decode the given source string with a URLDecoder. The encoding will be taken
303: * from the request, falling back to the default "ISO-8859-1".
304: * <p>The default implementation uses <code>URLDecoder.decode(input, enc)</code>.
305: * @param request current HTTP request
306: * @param source the String to decode
307: * @return the decoded String
308: * @see WebUtils#DEFAULT_CHARACTER_ENCODING
309: * @see javax.servlet.ServletRequest#getCharacterEncoding
310: * @see java.net.URLDecoder#decode(String, String)
311: * @see java.net.URLDecoder#decode(String)
312: */
313: public String decodeRequestString(HttpServletRequest request,
314: String source) {
315: if (this .urlDecode) {
316: String enc = determineEncoding(request);
317: try {
318: return URLDecoder.decode(source, enc);
319: } catch (UnsupportedEncodingException ex) {
320: if (logger.isWarnEnabled()) {
321: logger
322: .warn("Could not decode request string ["
323: + source
324: + "] with encoding '"
325: + enc
326: + "': falling back to platform default encoding; exception message: "
327: + ex.getMessage());
328: }
329: return URLDecoder.decode(source);
330: }
331: }
332: return source;
333: }
334:
335: /**
336: * Determine the encoding for the given request.
337: * Can be overridden in subclasses.
338: * <p>The default implementation checks the request encoding,
339: * falling back to the default encoding specified for this resolver.
340: * @param request current HTTP request
341: * @return the encoding for the request (never <code>null</code>)
342: * @see javax.servlet.ServletRequest#getCharacterEncoding()
343: * @see #setDefaultEncoding
344: */
345: protected String determineEncoding(HttpServletRequest request) {
346: String enc = request.getCharacterEncoding();
347: if (enc == null) {
348: enc = getDefaultEncoding();
349: }
350: return enc;
351: }
352:
353: }
|