All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.web.servlet.tags.UrlTag Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.tags;

import java.io.IOException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import org.springframework.web.util.JavaScriptUtils;
import org.springframework.web.util.TagUtils;
import org.springframework.web.util.UriUtils;

/**
 * The {@code } tag creates URLs. Modeled after the JSTL {@code c:url} tag with
 * backwards compatibility in mind.
 *
 * 

Enhancements to the JSTL functionality include: *

    *
  • URL encoded template URI variables
  • *
  • HTML/XML escaping of URLs
  • *
  • JavaScript escaping of URLs
  • *
* *

Template URI variables are indicated in the {@link #setValue(String) 'value'} * attribute and marked by braces '{variableName}'. The braces and attribute name are * replaced by the URL encoded value of a parameter defined with the spring:param tag * in the body of the url tag. If no parameter is available the literal value is * passed through. Params matched to template variables will not be added to the query * string. * *

Use of the spring:param tag for URI template variables is strongly recommended * over direct EL substitution as the values are URL encoded. Failure to properly * encode URL can leave an application vulnerable to XSS and other injection attacks. * *

URLs can be HTML/XML escaped by setting the {@link #setHtmlEscape(boolean) * 'htmlEscape'} attribute to 'true'. Detects an HTML escaping setting, either on * this tag instance, the page level, or the {@code web.xml} level. The default * is 'false'. When setting the URL value into a variable, escaping is not recommended. * *

Example usage: *

<spring:url value="/url/path/{variableName}">
 *   <spring:param name="variableName" value="more than JSTL c:url" />
 * </spring:url>
* *

The above results in: * {@code /currentApplicationContext/url/path/more%20than%20JSTL%20c%3Aurl} * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Attribute Summary
AttributeRequired?Runtime Expression?Description
valuetruetrueThe URL to build. This value can include template {placeholders} that are * replaced with the URL encoded value of the named parameter. Parameters * must be defined using the param tag inside the body of this tag.
contextfalsetrueSpecifies a remote application context path. * The default is the current application context path.
varfalsetrueThe name of the variable to export the URL value to. * If not specified the URL is written as output.
scopefalsetrueThe scope for the var. 'application', 'session', 'request' and 'page' * scopes are supported. Defaults to page scope. This attribute has no * effect unless the var attribute is also defined.
htmlEscapefalsetrueSet HTML escaping for this tag, as a boolean value. Overrides the * default HTML escaping setting for the current page.
javaScriptEscapefalsetrueSet JavaScript escaping for this tag, as a boolean value. * Default is false.
* * @author Scott Andrews * @since 3.0 * @see ParamTag */ @SuppressWarnings("serial") public class UrlTag extends HtmlEscapingAwareTag implements ParamAware { private static final String URL_TEMPLATE_DELIMITER_PREFIX = "{"; private static final String URL_TEMPLATE_DELIMITER_SUFFIX = "}"; private static final String URL_TYPE_ABSOLUTE = "://"; private List params = Collections.emptyList(); private Set templateParams = Collections.emptySet(); @Nullable private UrlType type; @Nullable private String value; @Nullable private String context; @Nullable private String var; private int scope = PageContext.PAGE_SCOPE; private boolean javaScriptEscape = false; /** * Set the value of the URL. */ public void setValue(String value) { if (value.contains(URL_TYPE_ABSOLUTE)) { this.type = UrlType.ABSOLUTE; this.value = value; } else if (value.startsWith("/")) { this.type = UrlType.CONTEXT_RELATIVE; this.value = value; } else { this.type = UrlType.RELATIVE; this.value = value; } } /** * Set the context path for the URL. * Defaults to the current context. */ public void setContext(String context) { if (context.startsWith("/")) { this.context = context; } else { this.context = "/" + context; } } /** * Set the variable name to expose the URL under. Defaults to rendering the * URL to the current JspWriter */ public void setVar(String var) { this.var = var; } /** * Set the scope to export the URL variable to. This attribute has no * meaning unless var is also defined. */ public void setScope(String scope) { this.scope = TagUtils.getScope(scope); } /** * Set JavaScript escaping for this tag, as boolean value. * Default is "false". */ public void setJavaScriptEscape(boolean javaScriptEscape) throws JspException { this.javaScriptEscape = javaScriptEscape; } @Override public void addParam(Param param) { this.params.add(param); } @Override public int doStartTagInternal() throws JspException { this.params = new LinkedList<>(); this.templateParams = new HashSet<>(); return EVAL_BODY_INCLUDE; } @Override public int doEndTag() throws JspException { String url = createUrl(); RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor(); ServletRequest request = this.pageContext.getRequest(); if ((processor != null) && (request instanceof HttpServletRequest)) { url = processor.processUrl((HttpServletRequest) request, url); } if (this.var == null) { // print the url to the writer try { this.pageContext.getOut().print(url); } catch (IOException ex) { throw new JspException(ex); } } else { // store the url as a variable this.pageContext.setAttribute(this.var, url, this.scope); } return EVAL_PAGE; } /** * Build the URL for the tag from the tag attributes and parameters. * @return the URL value as a String */ String createUrl() throws JspException { Assert.state(this.value != null, "No value set"); HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest(); HttpServletResponse response = (HttpServletResponse) this.pageContext.getResponse(); StringBuilder url = new StringBuilder(); if (this.type == UrlType.CONTEXT_RELATIVE) { // add application context to url if (this.context == null) { url.append(request.getContextPath()); } else { if (this.context.endsWith("/")) { url.append(this.context.substring(0, this.context.length() - 1)); } else { url.append(this.context); } } } if (this.type != UrlType.RELATIVE && this.type != UrlType.ABSOLUTE && !this.value.startsWith("/")) { url.append("/"); } url.append(replaceUriTemplateParams(this.value, this.params, this.templateParams)); url.append(createQueryString(this.params, this.templateParams, (url.indexOf("?") == -1))); String urlStr = url.toString(); if (this.type != UrlType.ABSOLUTE) { // Add the session identifier if needed // (Do not embed the session identifier in a remote link!) urlStr = response.encodeURL(urlStr); } // HTML and/or JavaScript escape, if demanded. urlStr = htmlEscape(urlStr); urlStr = (this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(urlStr) : urlStr); return urlStr; } /** * Build the query string from available parameters that have not already * been applied as template params. *

The names and values of parameters are URL encoded. * @param params the parameters to build the query string from * @param usedParams set of parameter names that have been applied as * template params * @param includeQueryStringDelimiter true if the query string should start * with a '?' instead of '&' * @return the query string */ protected String createQueryString(List params, Set usedParams, boolean includeQueryStringDelimiter) throws JspException { String encoding = this.pageContext.getResponse().getCharacterEncoding(); StringBuilder qs = new StringBuilder(); for (Param param : params) { if (!usedParams.contains(param.getName()) && StringUtils.hasLength(param.getName())) { if (includeQueryStringDelimiter && qs.length() == 0) { qs.append("?"); } else { qs.append("&"); } try { qs.append(UriUtils.encodeQueryParam(param.getName(), encoding)); if (param.getValue() != null) { qs.append("="); qs.append(UriUtils.encodeQueryParam(param.getValue(), encoding)); } } catch (UnsupportedCharsetException ex) { throw new JspException(ex); } } } return qs.toString(); } /** * Replace template markers in the URL matching available parameters. The * name of matched parameters are added to the used parameters set. *

Parameter values are URL encoded. * @param uri the URL with template parameters to replace * @param params parameters used to replace template markers * @param usedParams set of template parameter names that have been replaced * @return the URL with template parameters replaced */ protected String replaceUriTemplateParams(String uri, List params, Set usedParams) throws JspException { String encoding = this.pageContext.getResponse().getCharacterEncoding(); for (Param param : params) { String template = URL_TEMPLATE_DELIMITER_PREFIX + param.getName() + URL_TEMPLATE_DELIMITER_SUFFIX; if (uri.contains(template)) { usedParams.add(param.getName()); String value = param.getValue(); try { uri = StringUtils.replace(uri, template, (value != null ? UriUtils.encodePath(value, encoding) : "")); } catch (UnsupportedCharsetException ex) { throw new JspException(ex); } } else { template = URL_TEMPLATE_DELIMITER_PREFIX + '/' + param.getName() + URL_TEMPLATE_DELIMITER_SUFFIX; if (uri.contains(template)) { usedParams.add(param.getName()); String value = param.getValue(); try { uri = StringUtils.replace(uri, template, (value != null ? UriUtils.encodePathSegment(value, encoding) : "")); } catch (UnsupportedCharsetException ex) { throw new JspException(ex); } } } } return uri; } /** * Internal enum that classifies URLs by type. */ private enum UrlType { CONTEXT_RELATIVE, RELATIVE, ABSOLUTE } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy