001/*
002 * Copyright 2002-2020 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 *      https://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
017package org.springframework.web.util;
018
019import java.net.URLDecoder;
020import java.nio.charset.UnsupportedCharsetException;
021import java.util.LinkedHashMap;
022import java.util.Map;
023import java.util.Properties;
024
025import javax.servlet.http.HttpServletRequest;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.lang.Nullable;
031import org.springframework.util.Assert;
032import org.springframework.util.LinkedMultiValueMap;
033import org.springframework.util.MultiValueMap;
034import org.springframework.util.StringUtils;
035
036/**
037 * Helper class for URL path matching. Provides support for URL paths in
038 * {@code RequestDispatcher} includes and support for consistent URL decoding.
039 *
040 * <p>Used by {@link org.springframework.web.servlet.handler.AbstractUrlHandlerMapping}
041 * and {@link org.springframework.web.servlet.support.RequestContext} for path matching
042 * and/or URI determination.
043 *
044 * @author Juergen Hoeller
045 * @author Rob Harrop
046 * @author Rossen Stoyanchev
047 * @since 14.01.2004
048 * @see #getLookupPathForRequest
049 * @see javax.servlet.RequestDispatcher
050 */
051public class UrlPathHelper {
052
053        /**
054         * Special WebSphere request attribute, indicating the original request URI.
055         * Preferable over the standard Servlet 2.4 forward attribute on WebSphere,
056         * simply because we need the very first URI in the request forwarding chain.
057         */
058        private static final String WEBSPHERE_URI_ATTRIBUTE = "com.ibm.websphere.servlet.uri_non_decoded";
059
060        private static final Log logger = LogFactory.getLog(UrlPathHelper.class);
061
062        @Nullable
063        static volatile Boolean websphereComplianceFlag;
064
065
066        private boolean alwaysUseFullPath = false;
067
068        private boolean urlDecode = true;
069
070        private boolean removeSemicolonContent = true;
071
072        private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
073
074        private boolean readOnly = false;
075
076
077        /**
078         * Whether URL lookups should always use the full path within the current
079         * web application context, i.e. within
080         * {@link javax.servlet.ServletContext#getContextPath()}.
081         * <p>If set to {@literal false} the path within the current servlet mapping
082         * is used instead if applicable (i.e. in the case of a prefix based Servlet
083         * mapping such as "/myServlet/*").
084         * <p>By default this is set to "false".
085         */
086        public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
087                checkReadOnly();
088                this.alwaysUseFullPath = alwaysUseFullPath;
089        }
090
091        /**
092         * Whether the context path and request URI should be decoded -- both of
093         * which are returned <i>undecoded</i> by the Servlet API, in contrast to
094         * the servlet path.
095         * <p>Either the request encoding or the default Servlet spec encoding
096         * (ISO-8859-1) is used when set to "true".
097         * <p>By default this is set to {@literal true}.
098         * <p><strong>Note:</strong> Be aware the servlet path will not match when
099         * compared to encoded paths. Therefore use of {@code urlDecode=false} is
100         * not compatible with a prefix-based Servlet mapping and likewise implies
101         * also setting {@code alwaysUseFullPath=true}.
102         * @see #getServletPath
103         * @see #getContextPath
104         * @see #getRequestUri
105         * @see WebUtils#DEFAULT_CHARACTER_ENCODING
106         * @see javax.servlet.ServletRequest#getCharacterEncoding()
107         * @see java.net.URLDecoder#decode(String, String)
108         */
109        public void setUrlDecode(boolean urlDecode) {
110                checkReadOnly();
111                this.urlDecode = urlDecode;
112        }
113
114        /**
115         * Whether to decode the request URI when determining the lookup path.
116         * @since 4.3.13
117         */
118        public boolean isUrlDecode() {
119                return this.urlDecode;
120        }
121
122        /**
123         * Set if ";" (semicolon) content should be stripped from the request URI.
124         * <p>Default is "true".
125         */
126        public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
127                checkReadOnly();
128                this.removeSemicolonContent = removeSemicolonContent;
129        }
130
131        /**
132         * Whether configured to remove ";" (semicolon) content from the request URI.
133         */
134        public boolean shouldRemoveSemicolonContent() {
135                checkReadOnly();
136                return this.removeSemicolonContent;
137        }
138
139        /**
140         * Set the default character encoding to use for URL decoding.
141         * Default is ISO-8859-1, according to the Servlet spec.
142         * <p>If the request specifies a character encoding itself, the request
143         * encoding will override this setting. This also allows for generically
144         * overriding the character encoding in a filter that invokes the
145         * {@code ServletRequest.setCharacterEncoding} method.
146         * @param defaultEncoding the character encoding to use
147         * @see #determineEncoding
148         * @see javax.servlet.ServletRequest#getCharacterEncoding()
149         * @see javax.servlet.ServletRequest#setCharacterEncoding(String)
150         * @see WebUtils#DEFAULT_CHARACTER_ENCODING
151         */
152        public void setDefaultEncoding(String defaultEncoding) {
153                checkReadOnly();
154                this.defaultEncoding = defaultEncoding;
155        }
156
157        /**
158         * Return the default character encoding to use for URL decoding.
159         */
160        protected String getDefaultEncoding() {
161                return this.defaultEncoding;
162        }
163
164        /**
165         * Switch to read-only mode where further configuration changes are not allowed.
166         */
167        private void setReadOnly() {
168                this.readOnly = true;
169        }
170
171        private void checkReadOnly() {
172                Assert.isTrue(!this.readOnly, "This instance cannot be modified");
173        }
174
175
176        /**
177         * Return the mapping lookup path for the given request, within the current
178         * servlet mapping if applicable, else within the web application.
179         * <p>Detects include request URL if called within a RequestDispatcher include.
180         * @param request current HTTP request
181         * @return the lookup path
182         * @see #getPathWithinServletMapping
183         * @see #getPathWithinApplication
184         */
185        public String getLookupPathForRequest(HttpServletRequest request) {
186                String pathWithinApp = getPathWithinApplication(request);
187                // Always use full path within current servlet context?
188                if (this.alwaysUseFullPath) {
189                        return pathWithinApp;
190                }
191                // Else, use path within current servlet mapping if applicable
192                String rest = getPathWithinServletMapping(request, pathWithinApp);
193                if (StringUtils.hasLength(rest)) {
194                        return rest;
195                }
196                else {
197                        return pathWithinApp;
198                }
199        }
200
201        /**
202         * Variant of {@link #getLookupPathForRequest(HttpServletRequest)} that
203         * automates checking for a previously computed lookupPath saved as a
204         * request attribute. The attribute is only used for lookup purposes.
205         * @param request current HTTP request
206         * @param lookupPathAttributeName the request attribute to check
207         * @return the lookup path
208         * @since 5.2
209         * @see org.springframework.web.servlet.HandlerMapping#LOOKUP_PATH
210         */
211        public String getLookupPathForRequest(HttpServletRequest request, @Nullable String lookupPathAttributeName) {
212                if (lookupPathAttributeName != null) {
213                        String result = (String) request.getAttribute(lookupPathAttributeName);
214                        if (result != null) {
215                                return result;
216                        }
217                }
218                return getLookupPathForRequest(request);
219        }
220
221        /**
222         * Return the path within the servlet mapping for the given request,
223         * i.e. the part of the request's URL beyond the part that called the servlet,
224         * or "" if the whole URL has been used to identify the servlet.
225         * @param request current HTTP request
226         * @return the path within the servlet mapping, or ""
227         * @see #getPathWithinServletMapping(HttpServletRequest, String)
228         */
229        public String getPathWithinServletMapping(HttpServletRequest request) {
230                return getPathWithinServletMapping(request, getPathWithinApplication(request));
231        }
232
233        /**
234         * Return the path within the servlet mapping for the given request,
235         * i.e. the part of the request's URL beyond the part that called the servlet,
236         * or "" if the whole URL has been used to identify the servlet.
237         * <p>Detects include request URL if called within a RequestDispatcher include.
238         * <p>E.g.: servlet mapping = "/*"; request URI = "/test/a" -> "/test/a".
239         * <p>E.g.: servlet mapping = "/"; request URI = "/test/a" -> "/test/a".
240         * <p>E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a".
241         * <p>E.g.: servlet mapping = "/test"; request URI = "/test" -> "".
242         * <p>E.g.: servlet mapping = "/*.test"; request URI = "/a.test" -> "".
243         * @param request current HTTP request
244         * @param pathWithinApp a precomputed path within the application
245         * @return the path within the servlet mapping, or ""
246         * @since 5.2.9
247         * @see #getLookupPathForRequest
248         */
249        protected String getPathWithinServletMapping(HttpServletRequest request, String pathWithinApp) {
250                String servletPath = getServletPath(request);
251                String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
252                String path;
253
254                // If the app container sanitized the servletPath, check against the sanitized version
255                if (servletPath.contains(sanitizedPathWithinApp)) {
256                        path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
257                }
258                else {
259                        path = getRemainingPath(pathWithinApp, servletPath, false);
260                }
261
262                if (path != null) {
263                        // Normal case: URI contains servlet path.
264                        return path;
265                }
266                else {
267                        // Special case: URI is different from servlet path.
268                        String pathInfo = request.getPathInfo();
269                        if (pathInfo != null) {
270                                // Use path info if available. Indicates index page within a servlet mapping?
271                                // e.g. with index page: URI="/", servletPath="/index.html"
272                                return pathInfo;
273                        }
274                        if (!this.urlDecode) {
275                                // No path info... (not mapped by prefix, nor by extension, nor "/*")
276                                // For the default servlet mapping (i.e. "/"), urlDecode=false can
277                                // cause issues since getServletPath() returns a decoded path.
278                                // If decoding pathWithinApp yields a match just use pathWithinApp.
279                                path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
280                                if (path != null) {
281                                        return pathWithinApp;
282                                }
283                        }
284                        // Otherwise, use the full servlet path.
285                        return servletPath;
286                }
287        }
288
289        /**
290         * Return the path within the web application for the given request.
291         * <p>Detects include request URL if called within a RequestDispatcher include.
292         * @param request current HTTP request
293         * @return the path within the web application
294         * @see #getLookupPathForRequest
295         */
296        public String getPathWithinApplication(HttpServletRequest request) {
297                String contextPath = getContextPath(request);
298                String requestUri = getRequestUri(request);
299                String path = getRemainingPath(requestUri, contextPath, true);
300                if (path != null) {
301                        // Normal case: URI contains context path.
302                        return (StringUtils.hasText(path) ? path : "/");
303                }
304                else {
305                        return requestUri;
306                }
307        }
308
309        /**
310         * Match the given "mapping" to the start of the "requestUri" and if there
311         * is a match return the extra part. This method is needed because the
312         * context path and the servlet path returned by the HttpServletRequest are
313         * stripped of semicolon content unlike the requestUri.
314         */
315        @Nullable
316        private String getRemainingPath(String requestUri, String mapping, boolean ignoreCase) {
317                int index1 = 0;
318                int index2 = 0;
319                for (; (index1 < requestUri.length()) && (index2 < mapping.length()); index1++, index2++) {
320                        char c1 = requestUri.charAt(index1);
321                        char c2 = mapping.charAt(index2);
322                        if (c1 == ';') {
323                                index1 = requestUri.indexOf('/', index1);
324                                if (index1 == -1) {
325                                        return null;
326                                }
327                                c1 = requestUri.charAt(index1);
328                        }
329                        if (c1 == c2 || (ignoreCase && (Character.toLowerCase(c1) == Character.toLowerCase(c2)))) {
330                                continue;
331                        }
332                        return null;
333                }
334                if (index2 != mapping.length()) {
335                        return null;
336                }
337                else if (index1 == requestUri.length()) {
338                        return "";
339                }
340                else if (requestUri.charAt(index1) == ';') {
341                        index1 = requestUri.indexOf('/', index1);
342                }
343                return (index1 != -1 ? requestUri.substring(index1) : "");
344        }
345
346        /**
347         * Sanitize the given path. Uses the following rules:
348         * <ul>
349         * <li>replace all "//" by "/"</li>
350         * </ul>
351         */
352        private String getSanitizedPath(final String path) {
353                String sanitized = path;
354                while (true) {
355                        int index = sanitized.indexOf("//");
356                        if (index < 0) {
357                                break;
358                        }
359                        else {
360                                sanitized = sanitized.substring(0, index) + sanitized.substring(index + 1);
361                        }
362                }
363                return sanitized;
364        }
365
366        /**
367         * Return the request URI for the given request, detecting an include request
368         * URL if called within a RequestDispatcher include.
369         * <p>As the value returned by {@code request.getRequestURI()} is <i>not</i>
370         * decoded by the servlet container, this method will decode it.
371         * <p>The URI that the web container resolves <i>should</i> be correct, but some
372         * containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid"
373         * in the URI. This method cuts off such incorrect appendices.
374         * @param request current HTTP request
375         * @return the request URI
376         */
377        public String getRequestUri(HttpServletRequest request) {
378                String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
379                if (uri == null) {
380                        uri = request.getRequestURI();
381                }
382                return decodeAndCleanUriString(request, uri);
383        }
384
385        /**
386         * Return the context path for the given request, detecting an include request
387         * URL if called within a RequestDispatcher include.
388         * <p>As the value returned by {@code request.getContextPath()} is <i>not</i>
389         * decoded by the servlet container, this method will decode it.
390         * @param request current HTTP request
391         * @return the context path
392         */
393        public String getContextPath(HttpServletRequest request) {
394                String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE);
395                if (contextPath == null) {
396                        contextPath = request.getContextPath();
397                }
398                if (StringUtils.matchesCharacter(contextPath, '/')) {
399                        // Invalid case, but happens for includes on Jetty: silently adapt it.
400                        contextPath = "";
401                }
402                return decodeRequestString(request, contextPath);
403        }
404
405        /**
406         * Return the servlet path for the given request, regarding an include request
407         * URL if called within a RequestDispatcher include.
408         * <p>As the value returned by {@code request.getServletPath()} is already
409         * decoded by the servlet container, this method will not attempt to decode it.
410         * @param request current HTTP request
411         * @return the servlet path
412         */
413        public String getServletPath(HttpServletRequest request) {
414                String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE);
415                if (servletPath == null) {
416                        servletPath = request.getServletPath();
417                }
418                if (servletPath.length() > 1 && servletPath.endsWith("/") && shouldRemoveTrailingServletPathSlash(request)) {
419                        // On WebSphere, in non-compliant mode, for a "/foo/" case that would be "/foo"
420                        // on all other servlet containers: removing trailing slash, proceeding with
421                        // that remaining slash as final lookup path...
422                        servletPath = servletPath.substring(0, servletPath.length() - 1);
423                }
424                return servletPath;
425        }
426
427
428        /**
429         * Return the request URI for the given request. If this is a forwarded request,
430         * correctly resolves to the request URI of the original request.
431         */
432        public String getOriginatingRequestUri(HttpServletRequest request) {
433                String uri = (String) request.getAttribute(WEBSPHERE_URI_ATTRIBUTE);
434                if (uri == null) {
435                        uri = (String) request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE);
436                        if (uri == null) {
437                                uri = request.getRequestURI();
438                        }
439                }
440                return decodeAndCleanUriString(request, uri);
441        }
442
443        /**
444         * Return the context path for the given request, detecting an include request
445         * URL if called within a RequestDispatcher include.
446         * <p>As the value returned by {@code request.getContextPath()} is <i>not</i>
447         * decoded by the servlet container, this method will decode it.
448         * @param request current HTTP request
449         * @return the context path
450         */
451        public String getOriginatingContextPath(HttpServletRequest request) {
452                String contextPath = (String) request.getAttribute(WebUtils.FORWARD_CONTEXT_PATH_ATTRIBUTE);
453                if (contextPath == null) {
454                        contextPath = request.getContextPath();
455                }
456                return decodeRequestString(request, contextPath);
457        }
458
459        /**
460         * Return the servlet path for the given request, detecting an include request
461         * URL if called within a RequestDispatcher include.
462         * @param request current HTTP request
463         * @return the servlet path
464         */
465        public String getOriginatingServletPath(HttpServletRequest request) {
466                String servletPath = (String) request.getAttribute(WebUtils.FORWARD_SERVLET_PATH_ATTRIBUTE);
467                if (servletPath == null) {
468                        servletPath = request.getServletPath();
469                }
470                return servletPath;
471        }
472
473        /**
474         * Return the query string part of the given request's URL. If this is a forwarded request,
475         * correctly resolves to the query string of the original request.
476         * @param request current HTTP request
477         * @return the query string
478         */
479        public String getOriginatingQueryString(HttpServletRequest request) {
480                if ((request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE) != null) ||
481                        (request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE) != null)) {
482                        return (String) request.getAttribute(WebUtils.FORWARD_QUERY_STRING_ATTRIBUTE);
483                }
484                else {
485                        return request.getQueryString();
486                }
487        }
488
489        /**
490         * Decode the supplied URI string and strips any extraneous portion after a ';'.
491         */
492        private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
493                uri = removeSemicolonContent(uri);
494                uri = decodeRequestString(request, uri);
495                uri = getSanitizedPath(uri);
496                return uri;
497        }
498
499        /**
500         * Decode the given source string with a URLDecoder. The encoding will be taken
501         * from the request, falling back to the default "ISO-8859-1".
502         * <p>The default implementation uses {@code URLDecoder.decode(input, enc)}.
503         * @param request current HTTP request
504         * @param source the String to decode
505         * @return the decoded String
506         * @see WebUtils#DEFAULT_CHARACTER_ENCODING
507         * @see javax.servlet.ServletRequest#getCharacterEncoding
508         * @see java.net.URLDecoder#decode(String, String)
509         * @see java.net.URLDecoder#decode(String)
510         */
511        public String decodeRequestString(HttpServletRequest request, String source) {
512                if (this.urlDecode) {
513                        return decodeInternal(request, source);
514                }
515                return source;
516        }
517
518        @SuppressWarnings("deprecation")
519        private String decodeInternal(HttpServletRequest request, String source) {
520                String enc = determineEncoding(request);
521                try {
522                        return UriUtils.decode(source, enc);
523                }
524                catch (UnsupportedCharsetException ex) {
525                        if (logger.isWarnEnabled()) {
526                                logger.warn("Could not decode request string [" + source + "] with encoding '" + enc +
527                                                "': falling back to platform default encoding; exception message: " + ex.getMessage());
528                        }
529                        return URLDecoder.decode(source);
530                }
531        }
532
533        /**
534         * Determine the encoding for the given request.
535         * Can be overridden in subclasses.
536         * <p>The default implementation checks the request encoding,
537         * falling back to the default encoding specified for this resolver.
538         * @param request current HTTP request
539         * @return the encoding for the request (never {@code null})
540         * @see javax.servlet.ServletRequest#getCharacterEncoding()
541         * @see #setDefaultEncoding
542         */
543        protected String determineEncoding(HttpServletRequest request) {
544                String enc = request.getCharacterEncoding();
545                if (enc == null) {
546                        enc = getDefaultEncoding();
547                }
548                return enc;
549        }
550
551        /**
552         * Remove ";" (semicolon) content from the given request URI if the
553         * {@linkplain #setRemoveSemicolonContent removeSemicolonContent}
554         * property is set to "true". Note that "jsessionid" is always removed.
555         * @param requestUri the request URI string to remove ";" content from
556         * @return the updated URI string
557         */
558        public String removeSemicolonContent(String requestUri) {
559                return (this.removeSemicolonContent ?
560                                removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri));
561        }
562
563        private String removeSemicolonContentInternal(String requestUri) {
564                int semicolonIndex = requestUri.indexOf(';');
565                while (semicolonIndex != -1) {
566                        int slashIndex = requestUri.indexOf('/', semicolonIndex);
567                        String start = requestUri.substring(0, semicolonIndex);
568                        requestUri = (slashIndex != -1) ? start + requestUri.substring(slashIndex) : start;
569                        semicolonIndex = requestUri.indexOf(';', semicolonIndex);
570                }
571                return requestUri;
572        }
573
574        private String removeJsessionid(String requestUri) {
575                String key = ";jsessionid=";
576                int index = requestUri.toLowerCase().indexOf(key);
577                if (index == -1) {
578                        return requestUri;
579                }
580                String start = requestUri.substring(0, index);
581                for (int i = index + key.length(); i < requestUri.length(); i++) {
582                        char c = requestUri.charAt(i);
583                        if (c == ';' || c == '/') {
584                                return start + requestUri.substring(i);
585                        }
586                }
587                return start;
588        }
589
590        /**
591         * Decode the given URI path variables via {@link #decodeRequestString} unless
592         * {@link #setUrlDecode} is set to {@code true} in which case it is assumed
593         * the URL path from which the variables were extracted is already decoded
594         * through a call to {@link #getLookupPathForRequest(HttpServletRequest)}.
595         * @param request current HTTP request
596         * @param vars the URI variables extracted from the URL path
597         * @return the same Map or a new Map instance
598         */
599        public Map<String, String> decodePathVariables(HttpServletRequest request, Map<String, String> vars) {
600                if (this.urlDecode) {
601                        return vars;
602                }
603                else {
604                        Map<String, String> decodedVars = new LinkedHashMap<>(vars.size());
605                        vars.forEach((key, value) -> decodedVars.put(key, decodeInternal(request, value)));
606                        return decodedVars;
607                }
608        }
609
610        /**
611         * Decode the given matrix variables via {@link #decodeRequestString} unless
612         * {@link #setUrlDecode} is set to {@code true} in which case it is assumed
613         * the URL path from which the variables were extracted is already decoded
614         * through a call to {@link #getLookupPathForRequest(HttpServletRequest)}.
615         * @param request current HTTP request
616         * @param vars the URI variables extracted from the URL path
617         * @return the same Map or a new Map instance
618         */
619        public MultiValueMap<String, String> decodeMatrixVariables(
620                        HttpServletRequest request, MultiValueMap<String, String> vars) {
621
622                if (this.urlDecode) {
623                        return vars;
624                }
625                else {
626                        MultiValueMap<String, String> decodedVars = new LinkedMultiValueMap<>(vars.size());
627                        vars.forEach((key, values) -> {
628                                for (String value : values) {
629                                        decodedVars.add(key, decodeInternal(request, value));
630                                }
631                        });
632                        return decodedVars;
633                }
634        }
635
636        private boolean shouldRemoveTrailingServletPathSlash(HttpServletRequest request) {
637                if (request.getAttribute(WEBSPHERE_URI_ATTRIBUTE) == null) {
638                        // Regular servlet container: behaves as expected in any case,
639                        // so the trailing slash is the result of a "/" url-pattern mapping.
640                        // Don't remove that slash.
641                        return false;
642                }
643                Boolean flagToUse = websphereComplianceFlag;
644                if (flagToUse == null) {
645                        ClassLoader classLoader = UrlPathHelper.class.getClassLoader();
646                        String className = "com.ibm.ws.webcontainer.WebContainer";
647                        String methodName = "getWebContainerProperties";
648                        String propName = "com.ibm.ws.webcontainer.removetrailingservletpathslash";
649                        boolean flag = false;
650                        try {
651                                Class<?> cl = classLoader.loadClass(className);
652                                Properties prop = (Properties) cl.getMethod(methodName).invoke(null);
653                                flag = Boolean.parseBoolean(prop.getProperty(propName));
654                        }
655                        catch (Throwable ex) {
656                                if (logger.isDebugEnabled()) {
657                                        logger.debug("Could not introspect WebSphere web container properties: " + ex);
658                                }
659                        }
660                        flagToUse = flag;
661                        websphereComplianceFlag = flag;
662                }
663                // Don't bother if WebSphere is configured to be fully Servlet compliant.
664                // However, if it is not compliant, do remove the improper trailing slash!
665                return !flagToUse;
666        }
667
668
669
670        /**
671         * Shared, read-only instance with defaults. The following apply:
672         * <ul>
673         * <li>{@code alwaysUseFullPath=false}
674         * <li>{@code urlDecode=true}
675         * <li>{@code removeSemicolon=true}
676         * <li>{@code defaultEncoding=}{@link WebUtils#DEFAULT_CHARACTER_ENCODING}
677         * </ul>
678         */
679        public static final UrlPathHelper defaultInstance = new UrlPathHelper();
680
681        static {
682                defaultInstance.setReadOnly();
683        }
684
685
686        /**
687         * Shared, read-only instance for the full, encoded path. The following apply:
688         * <ul>
689         * <li>{@code alwaysUseFullPath=true}
690         * <li>{@code urlDecode=false}
691         * <li>{@code removeSemicolon=false}
692         * <li>{@code defaultEncoding=}{@link WebUtils#DEFAULT_CHARACTER_ENCODING}
693         * </ul>
694         */
695        public static final UrlPathHelper rawPathInstance = new UrlPathHelper() {
696
697                @Override
698                public String removeSemicolonContent(String requestUri) {
699                        return requestUri;
700                }
701        };
702
703        static {
704                rawPathInstance.setAlwaysUseFullPath(true);
705                rawPathInstance.setUrlDecode(false);
706                rawPathInstance.setRemoveSemicolonContent(false);
707                rawPathInstance.setReadOnly();
708        }
709
710}