/** * Copyright 2005-2016 hdiv.org * * 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 * * http://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.hdiv.urlProcessor; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import java.util.Stack; import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; import org.hdiv.config.HDIVConfig; import org.hdiv.context.RequestContextHolder; import org.hdiv.util.Constants; import org.hdiv.util.HDIVUtil; import org.hdiv.util.Method; /** * This class contains methods to process urls. * * @author Gotzon Illarramendi * @since HDIV 2.1.0 */ public abstract class AbstractUrlProcessor { private static final String AMP = "&"; private static final int AMP_LENGTH = AMP.length(); /** * Hdiv configuration. */ protected HDIVConfig config; protected static final String processAnchorAndParameters(String url, final UrlDataImpl urlData, final String hdivParameter) { url = urlData.findAnchor(url); // Remove parameters final int paramInit = url.indexOf('?'); if (paramInit > -1) { urlData.setUrlParams(removeStateParameter(hdivParameter, url.substring(paramInit + 1))); url = url.substring(0, paramInit); } return url; } protected String removeURITemplateParams(final UrlDataImpl data) { return data.getUrlWithOutUriTemplate(); } @Deprecated public final UrlData createUrlData(final String url, final Method method, final String hdivParameter, final HttpServletRequest request) { return createUrlData(url, method, hdivParameter, HDIVUtil.getRequestContext(request)); } /** * Create a new instance of {@link UrlData}. * @param url original url * @param method Http method * @param hdivParameter Parameter for HDIV State * @param ctx {@link RequestContextHolder} object * @return new instance of {@link UrlData} */ public UrlData createUrlData(String url, final Method method, final String hdivParameter, final RequestContextHolder ctx) { final String contextPath = ctx.getContextPath(); final String serverName = ctx.getServerName(); final String baseURL = getBaseURL(ctx); final UrlDataImpl urlData = new UrlDataImpl(url, method); url = removeURITemplateParams(urlData); url = processAnchorAndParameters(url, urlData, hdivParameter); // Extract protocol, domain and server if exist final String serverUrl = getServerFromUrl(url); if (serverUrl != null && serverUrl.length() > 0) { urlData.setServer(serverUrl); // Remove server and port url = url.replaceFirst(serverUrl, ""); } // Remove jsessionid url = stripSession(url, urlData); // Calculate contextPath beginning url String contextPathRelativeUrl = getContextPathRelative(baseURL, url); urlData.setContextPathRelativeUrl(contextPathRelativeUrl); // Detect if the url points to current app boolean internal = isInternalUrl(serverName, contextPath, contextPathRelativeUrl, urlData); urlData.setInternal(internal); // Calculate url without the context path for later processing if (internal) { // Remove contextPath String urlWithoutContextPath = contextPathRelativeUrl.substring(contextPath.length()); urlData.setUrlWithoutContextPath(urlWithoutContextPath); } else { // If contextPath is not present, the relative url is out of application urlData.setInternal(false); } return urlData; } @Deprecated protected static final String getBaseURL(final HttpServletRequest request) { return getBaseURL(HDIVUtil.getRequestContext(request)); } protected static final String getBaseURL(final RequestContextHolder context) { // Base url defined by <base> tag in some frameworks String baseUrl = context.getBaseURL(); if (baseUrl != null) { // Remove server part from the url final String serverUrl = getServerFromUrl(baseUrl); if (serverUrl != null && serverUrl.length() > 0) { // Remove server and port baseUrl = baseUrl.replaceFirst(serverUrl, ""); } } else { // Original RequestUri before Jsp processing baseUrl = context.getRequestURI(); } return baseUrl; } /** * Remove _HDIV_STATE_ parameter if it exist. * * @param hdivParameter HDIV state parameter name * @param params parameters string * @return parameters string without state id */ protected static final String removeStateParameter(final String hdivParameter, String params) { int start; if (params == null || (start = params.indexOf(hdivParameter)) == -1) { return params; } if (start > AMP_LENGTH) { int amp = params.indexOf(AMP, start - AMP_LENGTH); if (amp != -1) { params = params.substring(0, amp + 1) + params.substring(amp + AMP_LENGTH); start = params.indexOf(hdivParameter); } } if (start > 0) { char first = params.charAt(start - 1); if (first != '?' && first != '&') { return params; } } int end = params.indexOf('&', start); if (end < 0) { end = params.indexOf('#', start); } if (end < 0) { end = params.length(); } String result = params.substring(0, start); result = result + params.substring(end, params.length()); if (result.endsWith("&")) { result = result.substring(0, result.length() - 1); } return result; } @Deprecated public final Map<String, String[]> getUrlParamsAsMap(final StringBuilder sb, final HttpServletRequest request, final String urlParams) { return getUrlParamsAsMap(HDIVUtil.getRequestContext(request).getHdivParameterName(), sb, urlParams); } /** * Generates a Map with request parameter name and values. * @param hdivParameter Hdiv parameter * @param sb builder * @param urlParams urls query string * @return Parameters as a Map */ public Map<String, String[]> getUrlParamsAsMap(final String hdivParameter, final StringBuilder sb, final String urlParams) { Map<String, String[]> params = new LinkedHashMap<String, String[]>(); if (urlParams == null) { return params; } String value = urlParams.replaceAll(AMP, "&"); StringTokenizer st = new StringTokenizer(value, "&"); while (st.hasMoreTokens()) { String token = st.nextToken(); int index = token.indexOf('='); String param; String val = ""; if (index > -1) { param = token.substring(0, index); val = token.substring(index + 1); } else { param = token; } // Decode parameter value val = HDIVUtil.getDecodedValue(sb, val, Constants.ENCODING_UTF_8); // Ignore Hdiv state parameter if (!param.equals(hdivParameter)) { // Add value to array or create it String[] values = params.get(param); if (values == null) { values = new String[] { val }; } else { int l = values.length; values = Arrays.copyOf(values, l + 1); values[l] = val; } params.put(param, values); } } return params; } /** * Generate a url with all parameters and include hdiv state parameter. * * @param sb builder * @param hdivParameter HDIV parameter name * @param urlData url data object * @param stateParam hdiv state parameter value * @return complete url */ public String getProcessedUrlWithHdivState(final StringBuilder sb, final String hdivParameter, final UrlData urlData, final String stateParam) { return urlData.getProcessedUrlWithHdivState(sb, hdivParameter, stateParam); } /** * Generate final url with all parameters and anchor. * * @param sb StringBuilder * @param urlData url data object * @return complete url */ public String getProcessedUrl(final StringBuilder sb, final UrlData urlData) { return urlData.getProcessedUrl(sb); } /** * Detects if the url points to this application * * @param serverName Server name * @param contextPath contextPath * @param url request url * @param urlData url data * @return is internal? */ protected static final boolean isInternalUrl(final String serverName, final String contextPath, final String url, final UrlDataImpl urlData) { if (urlData.getServer() != null) { // URL is absolute: http://... if (!urlData.getServer().contains(serverName)) { // http://www.google.com return false; } if (url.startsWith(contextPath) && (url.length() == contextPath.length() || url.charAt(contextPath.length()) == '/')) { // http://localhost:8080/APP/... or // http://localhost:8080/APP return true; } // http://localhost:8080/anotherApplication... or return false; } else { if (url.startsWith(contextPath) && (url.length() == contextPath.length() || url.charAt(contextPath.length()) == '/')) { // url of type /APP/... or /APP return true; } else if (url.charAt(0) == '/') { // url of type /anotherApplication/... return false; } else { // url of type section/action... return true; } } } /** * Returns from url the part related with the server side in an absolute url. * * @param url absolute url * @return url protocol, domain and port */ protected static String getServerFromUrl(String url) { final int pos = url.indexOf("://"); if (pos > 0) { int posicion = url.indexOf('/', pos + 3); if (posicion > 0) { url = url.substring(0, posicion); return url; } else { return url; } } return null; } /** * Composes the url starting with context path. Removes any relative url. * * @param baseUrl base URL * @param url url * @return url starting with context path */ protected static final String getContextPathRelative(final String baseUrl, final String url) { String returnValue; if ("".equals(url)) { return baseUrl; } else if (url.charAt(0) == '/') { returnValue = url; } else if (url.startsWith("..")) { returnValue = url; } else { // relative path String uri = baseUrl; uri = uri.substring(uri.indexOf('/'), uri.lastIndexOf('/')); returnValue = uri + "/" + url; } return removeRelativePaths(returnValue, baseUrl); } /** * Removes references to relative paths from the URL. * * @param url URL value * @param originalRequestUri originalRequestUri * @return returns URL without relative paths. */ protected static String removeRelativePaths(final String url, final String originalRequestUri) { String urlWithoutRelativePath = url; if (url.startsWith("..")) { Stack<String> stack = new Stack<String>(); String localUri = originalRequestUri.substring(originalRequestUri.indexOf('/'), originalRequestUri.lastIndexOf('/')); StringTokenizer localUriParts = new StringTokenizer(localUri.replace('\\', '/'), "/"); while (localUriParts.hasMoreTokens()) { String part = localUriParts.nextToken(); stack.push(part); } StringTokenizer pathParts = new StringTokenizer(url.replace('\\', '/'), "/"); while (pathParts.hasMoreTokens()) { String part = pathParts.nextToken(); if (!".".equals(part)) { if ("..".equals(part)) { stack.pop(); } else { stack.push(part); } } } StringBuilder flatPathBuffer = new StringBuilder(); for (int i = 0; i < stack.size(); i++) { flatPathBuffer.append('/').append(stack.elementAt(i)); } urlWithoutRelativePath = flatPathBuffer.toString(); } return urlWithoutRelativePath; } /** * Strips a servlet session ID from <tt>url</tt>. * * @param url url * @param urlData current url data * @return url without sessionId */ public static final String stripSession(final String url, final UrlDataImpl urlData) { return HDIVUtil.stripAndFillSessionData(url, urlData); } /** * @param config the config to set */ public void setConfig(final HDIVConfig config) { this.config = config; } }