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.io.UnsupportedEncodingException; 020import java.net.URLDecoder; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Properties; 025 026import javax.servlet.http.HttpServletRequest; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030 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 * {@link org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver} 042 * and {@link org.springframework.web.servlet.support.RequestContext} for path matching 043 * and/or URI determination. 044 * 045 * @author Juergen Hoeller 046 * @author Rob Harrop 047 * @author Rossen Stoyanchev 048 * @since 14.01.2004 049 * @see #getLookupPathForRequest 050 * @see javax.servlet.RequestDispatcher 051 */ 052public class UrlPathHelper { 053 054 /** 055 * Special WebSphere request attribute, indicating the original request URI. 056 * Preferable over the standard Servlet 2.4 forward attribute on WebSphere, 057 * simply because we need the very first URI in the request forwarding chain. 058 */ 059 private static final String WEBSPHERE_URI_ATTRIBUTE = "com.ibm.websphere.servlet.uri_non_decoded"; 060 061 private static final Log logger = LogFactory.getLog(UrlPathHelper.class); 062 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 // Always use full path within current servlet context? 187 if (this.alwaysUseFullPath) { 188 return getPathWithinApplication(request); 189 } 190 // Else, use path within current servlet mapping if applicable 191 String rest = getPathWithinServletMapping(request); 192 if (!"".equals(rest)) { 193 return rest; 194 } 195 else { 196 return getPathWithinApplication(request); 197 } 198 } 199 200 /** 201 * Return the path within the servlet mapping for the given request, 202 * i.e. the part of the request's URL beyond the part that called the servlet, 203 * or "" if the whole URL has been used to identify the servlet. 204 * <p>Detects include request URL if called within a RequestDispatcher include. 205 * <p>E.g.: servlet mapping = "/*"; request URI = "/test/a" -> "/test/a". 206 * <p>E.g.: servlet mapping = "/"; request URI = "/test/a" -> "/test/a". 207 * <p>E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a". 208 * <p>E.g.: servlet mapping = "/test"; request URI = "/test" -> "". 209 * <p>E.g.: servlet mapping = "/*.test"; request URI = "/a.test" -> "". 210 * @param request current HTTP request 211 * @return the path within the servlet mapping, or "" 212 * @see #getLookupPathForRequest 213 */ 214 public String getPathWithinServletMapping(HttpServletRequest request) { 215 String pathWithinApp = getPathWithinApplication(request); 216 String servletPath = getServletPath(request); 217 String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp); 218 String path; 219 220 // If the app container sanitized the servletPath, check against the sanitized version 221 if (servletPath.contains(sanitizedPathWithinApp)) { 222 path = getRemainingPath(sanitizedPathWithinApp, servletPath, false); 223 } 224 else { 225 path = getRemainingPath(pathWithinApp, servletPath, false); 226 } 227 228 if (path != null) { 229 // Normal case: URI contains servlet path. 230 return path; 231 } 232 else { 233 // Special case: URI is different from servlet path. 234 String pathInfo = request.getPathInfo(); 235 if (pathInfo != null) { 236 // Use path info if available. Indicates index page within a servlet mapping? 237 // e.g. with index page: URI="/", servletPath="/index.html" 238 return pathInfo; 239 } 240 if (!this.urlDecode) { 241 // No path info... (not mapped by prefix, nor by extension, nor "/*") 242 // For the default servlet mapping (i.e. "/"), urlDecode=false can 243 // cause issues since getServletPath() returns a decoded path. 244 // If decoding pathWithinApp yields a match just use pathWithinApp. 245 path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false); 246 if (path != null) { 247 return pathWithinApp; 248 } 249 } 250 // Otherwise, use the full servlet path. 251 return servletPath; 252 } 253 } 254 255 /** 256 * Return the path within the web application for the given request. 257 * <p>Detects include request URL if called within a RequestDispatcher include. 258 * @param request current HTTP request 259 * @return the path within the web application 260 * @see #getLookupPathForRequest 261 */ 262 public String getPathWithinApplication(HttpServletRequest request) { 263 String contextPath = getContextPath(request); 264 String requestUri = getRequestUri(request); 265 String path = getRemainingPath(requestUri, contextPath, true); 266 if (path != null) { 267 // Normal case: URI contains context path. 268 return (StringUtils.hasText(path) ? path : "/"); 269 } 270 else { 271 return requestUri; 272 } 273 } 274 275 /** 276 * Match the given "mapping" to the start of the "requestUri" and if there 277 * is a match return the extra part. This method is needed because the 278 * context path and the servlet path returned by the HttpServletRequest are 279 * stripped of semicolon content unlike the requesUri. 280 */ 281 private String getRemainingPath(String requestUri, String mapping, boolean ignoreCase) { 282 int index1 = 0; 283 int index2 = 0; 284 for (; (index1 < requestUri.length()) && (index2 < mapping.length()); index1++, index2++) { 285 char c1 = requestUri.charAt(index1); 286 char c2 = mapping.charAt(index2); 287 if (c1 == ';') { 288 index1 = requestUri.indexOf('/', index1); 289 if (index1 == -1) { 290 return null; 291 } 292 c1 = requestUri.charAt(index1); 293 } 294 if (c1 == c2 || (ignoreCase && (Character.toLowerCase(c1) == Character.toLowerCase(c2)))) { 295 continue; 296 } 297 return null; 298 } 299 if (index2 != mapping.length()) { 300 return null; 301 } 302 else if (index1 == requestUri.length()) { 303 return ""; 304 } 305 else if (requestUri.charAt(index1) == ';') { 306 index1 = requestUri.indexOf('/', index1); 307 } 308 return (index1 != -1 ? requestUri.substring(index1) : ""); 309 } 310 311 /** 312 * Sanitize the given path. Uses the following rules: 313 * <ul> 314 * <li>replace all "//" by "/"</li> 315 * </ul> 316 */ 317 private String getSanitizedPath(final String path) { 318 String sanitized = path; 319 while (true) { 320 int index = sanitized.indexOf("//"); 321 if (index < 0) { 322 break; 323 } 324 else { 325 sanitized = sanitized.substring(0, index) + sanitized.substring(index + 1); 326 } 327 } 328 return sanitized; 329 } 330 331 /** 332 * Return the request URI for the given request, detecting an include request 333 * URL if called within a RequestDispatcher include. 334 * <p>As the value returned by {@code request.getRequestURI()} is <i>not</i> 335 * decoded by the servlet container, this method will decode it. 336 * <p>The URI that the web container resolves <i>should</i> be correct, but some 337 * containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid" 338 * in the URI. This method cuts off such incorrect appendices. 339 * @param request current HTTP request 340 * @return the request URI 341 */ 342 public String getRequestUri(HttpServletRequest request) { 343 String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE); 344 if (uri == null) { 345 uri = request.getRequestURI(); 346 } 347 return decodeAndCleanUriString(request, uri); 348 } 349 350 /** 351 * Return the context path for the given request, detecting an include request 352 * URL if called within a RequestDispatcher include. 353 * <p>As the value returned by {@code request.getContextPath()} is <i>not</i> 354 * decoded by the servlet container, this method will decode it. 355 * @param request current HTTP request 356 * @return the context path 357 */ 358 public String getContextPath(HttpServletRequest request) { 359 String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE); 360 if (contextPath == null) { 361 contextPath = request.getContextPath(); 362 } 363 if ("/".equals(contextPath)) { 364 // Invalid case, but happens for includes on Jetty: silently adapt it. 365 contextPath = ""; 366 } 367 return decodeRequestString(request, contextPath); 368 } 369 370 /** 371 * Return the servlet path for the given request, regarding an include request 372 * URL if called within a RequestDispatcher include. 373 * <p>As the value returned by {@code request.getServletPath()} is already 374 * decoded by the servlet container, this method will not attempt to decode it. 375 * @param request current HTTP request 376 * @return the servlet path 377 */ 378 public String getServletPath(HttpServletRequest request) { 379 String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE); 380 if (servletPath == null) { 381 servletPath = request.getServletPath(); 382 } 383 if (servletPath.length() > 1 && servletPath.endsWith("/") && shouldRemoveTrailingServletPathSlash(request)) { 384 // On WebSphere, in non-compliant mode, for a "/foo/" case that would be "/foo" 385 // on all other servlet containers: removing trailing slash, proceeding with 386 // that remaining slash as final lookup path... 387 servletPath = servletPath.substring(0, servletPath.length() - 1); 388 } 389 return servletPath; 390 } 391 392 393 /** 394 * Return the request URI for the given request. If this is a forwarded request, 395 * correctly resolves to the request URI of the original request. 396 */ 397 public String getOriginatingRequestUri(HttpServletRequest request) { 398 String uri = (String) request.getAttribute(WEBSPHERE_URI_ATTRIBUTE); 399 if (uri == null) { 400 uri = (String) request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE); 401 if (uri == null) { 402 uri = request.getRequestURI(); 403 } 404 } 405 return decodeAndCleanUriString(request, uri); 406 } 407 408 /** 409 * Return the context path for the given request, detecting an include request 410 * URL if called within a RequestDispatcher include. 411 * <p>As the value returned by {@code request.getContextPath()} is <i>not</i> 412 * decoded by the servlet container, this method will decode it. 413 * @param request current HTTP request 414 * @return the context path 415 */ 416 public String getOriginatingContextPath(HttpServletRequest request) { 417 String contextPath = (String) request.getAttribute(WebUtils.FORWARD_CONTEXT_PATH_ATTRIBUTE); 418 if (contextPath == null) { 419 contextPath = request.getContextPath(); 420 } 421 return decodeRequestString(request, contextPath); 422 } 423 424 /** 425 * Return the servlet path for the given request, detecting an include request 426 * URL if called within a RequestDispatcher include. 427 * @param request current HTTP request 428 * @return the servlet path 429 */ 430 public String getOriginatingServletPath(HttpServletRequest request) { 431 String servletPath = (String) request.getAttribute(WebUtils.FORWARD_SERVLET_PATH_ATTRIBUTE); 432 if (servletPath == null) { 433 servletPath = request.getServletPath(); 434 } 435 return servletPath; 436 } 437 438 /** 439 * Return the query string part of the given request's URL. If this is a forwarded request, 440 * correctly resolves to the query string of the original request. 441 * @param request current HTTP request 442 * @return the query string 443 */ 444 public String getOriginatingQueryString(HttpServletRequest request) { 445 if ((request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE) != null) || 446 (request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE) != null)) { 447 return (String) request.getAttribute(WebUtils.FORWARD_QUERY_STRING_ATTRIBUTE); 448 } 449 else { 450 return request.getQueryString(); 451 } 452 } 453 454 /** 455 * Decode the supplied URI string and strips any extraneous portion after a ';'. 456 */ 457 private String decodeAndCleanUriString(HttpServletRequest request, String uri) { 458 uri = removeSemicolonContent(uri); 459 uri = decodeRequestString(request, uri); 460 uri = getSanitizedPath(uri); 461 return uri; 462 } 463 464 /** 465 * Decode the given source string with a URLDecoder. The encoding will be taken 466 * from the request, falling back to the default "ISO-8859-1". 467 * <p>The default implementation uses {@code URLDecoder.decode(input, enc)}. 468 * @param request current HTTP request 469 * @param source the String to decode 470 * @return the decoded String 471 * @see WebUtils#DEFAULT_CHARACTER_ENCODING 472 * @see javax.servlet.ServletRequest#getCharacterEncoding 473 * @see java.net.URLDecoder#decode(String, String) 474 * @see java.net.URLDecoder#decode(String) 475 */ 476 public String decodeRequestString(HttpServletRequest request, String source) { 477 if (this.urlDecode && source != null) { 478 return decodeInternal(request, source); 479 } 480 return source; 481 } 482 483 @SuppressWarnings("deprecation") 484 private String decodeInternal(HttpServletRequest request, String source) { 485 String enc = determineEncoding(request); 486 try { 487 return UriUtils.decode(source, enc); 488 } 489 catch (UnsupportedEncodingException ex) { 490 if (logger.isWarnEnabled()) { 491 logger.warn("Could not decode request string [" + source + "] with encoding '" + enc + 492 "': falling back to platform default encoding; exception message: " + ex.getMessage()); 493 } 494 return URLDecoder.decode(source); 495 } 496 } 497 498 /** 499 * Determine the encoding for the given request. 500 * Can be overridden in subclasses. 501 * <p>The default implementation checks the request encoding, 502 * falling back to the default encoding specified for this resolver. 503 * @param request current HTTP request 504 * @return the encoding for the request (never {@code null}) 505 * @see javax.servlet.ServletRequest#getCharacterEncoding() 506 * @see #setDefaultEncoding 507 */ 508 protected String determineEncoding(HttpServletRequest request) { 509 String enc = request.getCharacterEncoding(); 510 if (enc == null) { 511 enc = getDefaultEncoding(); 512 } 513 return enc; 514 } 515 516 /** 517 * Remove ";" (semicolon) content from the given request URI if the 518 * {@linkplain #setRemoveSemicolonContent removeSemicolonContent} 519 * property is set to "true". Note that "jsessionid" is always removed. 520 * @param requestUri the request URI string to remove ";" content from 521 * @return the updated URI string 522 */ 523 public String removeSemicolonContent(String requestUri) { 524 return (this.removeSemicolonContent ? 525 removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri)); 526 } 527 528 private String removeSemicolonContentInternal(String requestUri) { 529 int semicolonIndex = requestUri.indexOf(';'); 530 while (semicolonIndex != -1) { 531 int slashIndex = requestUri.indexOf('/', semicolonIndex); 532 String start = requestUri.substring(0, semicolonIndex); 533 requestUri = (slashIndex != -1) ? start + requestUri.substring(slashIndex) : start; 534 semicolonIndex = requestUri.indexOf(';', semicolonIndex); 535 } 536 return requestUri; 537 } 538 539 private String removeJsessionid(String requestUri) { 540 String key = ";jsessionid="; 541 int index = requestUri.toLowerCase().indexOf(key); 542 if (index == -1) { 543 return requestUri; 544 } 545 String start = requestUri.substring(0, index); 546 for (int i = index + key.length(); i < requestUri.length(); i++) { 547 char c = requestUri.charAt(i); 548 if (c == ';' || c == '/') { 549 return start + requestUri.substring(i); 550 } 551 } 552 return start; 553 } 554 555 /** 556 * Decode the given URI path variables via {@link #decodeRequestString} unless 557 * {@link #setUrlDecode} is set to {@code true} in which case it is assumed 558 * the URL path from which the variables were extracted is already decoded 559 * through a call to {@link #getLookupPathForRequest(HttpServletRequest)}. 560 * @param request current HTTP request 561 * @param vars the URI variables extracted from the URL path 562 * @return the same Map or a new Map instance 563 */ 564 public Map<String, String> decodePathVariables(HttpServletRequest request, Map<String, String> vars) { 565 if (this.urlDecode) { 566 return vars; 567 } 568 else { 569 Map<String, String> decodedVars = new LinkedHashMap<String, String>(vars.size()); 570 for (Map.Entry<String, String> entry : vars.entrySet()) { 571 decodedVars.put(entry.getKey(), decodeInternal(request, entry.getValue())); 572 } 573 return decodedVars; 574 } 575 } 576 577 /** 578 * Decode the given matrix variables via {@link #decodeRequestString} unless 579 * {@link #setUrlDecode} is set to {@code true} in which case it is assumed 580 * the URL path from which the variables were extracted is already decoded 581 * through a call to {@link #getLookupPathForRequest(HttpServletRequest)}. 582 * @param request current HTTP request 583 * @param vars the URI variables extracted from the URL path 584 * @return the same Map or a new Map instance 585 */ 586 public MultiValueMap<String, String> decodeMatrixVariables( 587 HttpServletRequest request, MultiValueMap<String, String> vars) { 588 589 if (this.urlDecode) { 590 return vars; 591 } 592 else { 593 MultiValueMap<String, String> decodedVars = new LinkedMultiValueMap<String, String>(vars.size()); 594 for (Map.Entry<String, List<String>> entry : vars.entrySet()) { 595 for (String value : entry.getValue()) { 596 decodedVars.add(entry.getKey(), decodeInternal(request, value)); 597 } 598 } 599 return decodedVars; 600 } 601 } 602 603 private boolean shouldRemoveTrailingServletPathSlash(HttpServletRequest request) { 604 if (request.getAttribute(WEBSPHERE_URI_ATTRIBUTE) == null) { 605 // Regular servlet container: behaves as expected in any case, 606 // so the trailing slash is the result of a "/" url-pattern mapping. 607 // Don't remove that slash. 608 return false; 609 } 610 if (websphereComplianceFlag == null) { 611 ClassLoader classLoader = UrlPathHelper.class.getClassLoader(); 612 String className = "com.ibm.ws.webcontainer.WebContainer"; 613 String methodName = "getWebContainerProperties"; 614 String propName = "com.ibm.ws.webcontainer.removetrailingservletpathslash"; 615 boolean flag = false; 616 try { 617 Class<?> cl = classLoader.loadClass(className); 618 Properties prop = (Properties) cl.getMethod(methodName).invoke(null); 619 flag = Boolean.parseBoolean(prop.getProperty(propName)); 620 } 621 catch (Throwable ex) { 622 if (logger.isDebugEnabled()) { 623 logger.debug("Could not introspect WebSphere web container properties: " + ex); 624 } 625 } 626 websphereComplianceFlag = flag; 627 } 628 // Don't bother if WebSphere is configured to be fully Servlet compliant. 629 // However, if it is not compliant, do remove the improper trailing slash! 630 return !websphereComplianceFlag; 631 } 632 633 634 /** 635 * Shared, read-only instance with defaults. The following apply: 636 * <ul> 637 * <li>{@code alwaysUseFullPath=false} 638 * <li>{@code urlDecode=true} 639 * <li>{@code removeSemicolon=true} 640 * <li>{@code defaultEncoding=}{@link WebUtils#DEFAULT_CHARACTER_ENCODING} 641 * </ul> 642 */ 643 public static final UrlPathHelper defaultInstance = new UrlPathHelper(); 644 645 static { 646 defaultInstance.setReadOnly(); 647 } 648 649 650 /** 651 * Shared, read-only instance for the full, encoded path. The following apply: 652 * <ul> 653 * <li>{@code alwaysUseFullPath=true} 654 * <li>{@code urlDecode=false} 655 * <li>{@code removeSemicolon=false} 656 * <li>{@code defaultEncoding=}{@link WebUtils#DEFAULT_CHARACTER_ENCODING} 657 * </ul> 658 */ 659 public static final UrlPathHelper rawPathInstance = new UrlPathHelper() { 660 661 @Override 662 public String removeSemicolonContent(String requestUri) { 663 return requestUri; 664 } 665 }; 666 667 static { 668 rawPathInstance.setAlwaysUseFullPath(true); 669 rawPathInstance.setUrlDecode(false); 670 rawPathInstance.setRemoveSemicolonContent(false); 671 rawPathInstance.setReadOnly(); 672 } 673 674}