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