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}