/**
* 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.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hdiv.config.HDIVConfig;
import org.hdiv.regex.PatternMatcher;
import org.hdiv.util.Method;
/**
* Contains the data of an url.
*
* @author Gotzon Illarramendi
*/
public class UrlDataImpl implements UrlData {
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
/**
* Original url, previous to any change.
*/
private final String originalUrl;
/**
* Original urls anchor
*/
String anchor;
/**
* JSessionId value
*/
String jSessionId;
/**
* Url that starts with contextPath
*/
String contextPathRelativeUrl;
/**
* The same as contextPathRelativeUrl with contextPath erased
*/
private String urlWithoutContextPath;
/**
* URL parameters in query string format. For example: param1=val1¶m2=val2
*
* @since 2.1.7
*/
private String urlParams;
/**
* Map with original url parameter name and values
*/
private Map<String, String[]> originalUrlParams;
/**
* Map with processed url parameter name and values
*/
private Map<String, String[]> processedUrlParams;
/**
* True if the url points to this app
*/
private boolean internal = true;
/**
* Protocol, server and port of the url
*/
String server;
/**
* Http method.
*/
private Method method;
/**
* UriTemplate https://tools.ietf.org/html/rfc6570
*
* @since 3.0.0
*/
String uriTemplate;
/**
* true if the URL is still templated i.e. path variables
*/
boolean templated;
String composedParams;
boolean urlObfuscation;
/**
* Constructor
*
* @param url Original url
* @param method Http method.
*/
public UrlDataImpl(final String url, final Method method) {
originalUrl = url;
this.method = method;
if (url.indexOf('{') != -1) {
parser(url);
}
}
/**
* Is url method GET?
*
* @return true is it is GET
*/
public boolean isGetMethod() {
return Method.GET == method;
}
/**
* Determines if url contains parameters
*
* @return has parameters?
*/
public boolean containsParams() {
return originalUrlParams != null && originalUrlParams.size() > 0 || urlParams != null && urlParams.length() > 0;
}
/**
* @return the anchor
*/
public String getAnchor() {
return anchor;
}
public String findAnchor(final String url) {
int pos = url.indexOf('#');
if (pos >= 0) {
setAnchor(url.substring(pos + 1));
return url.substring(0, pos);
}
return url;
}
/**
* @param anchor the anchor to set
*/
private void setAnchor(final String anchor) {
this.anchor = anchor;
}
/**
* @return the contextPathRelativeUrl
*/
public String getContextPathRelativeUrl() {
return contextPathRelativeUrl;
}
/**
* @param contextPathRelativeUrl the contextPathRelativeUrl to set
*/
public void setContextPathRelativeUrl(final String contextPathRelativeUrl) {
this.contextPathRelativeUrl = contextPathRelativeUrl;
}
/**
* @return the urlWithoutContextPath
*/
public String getUrlWithoutContextPath() {
return urlWithoutContextPath;
}
/**
* @param urlWithoutContextPath the urlWithoutContextPath to set
*/
public void setUrlWithoutContextPath(final String urlWithoutContextPath) {
this.urlWithoutContextPath = urlWithoutContextPath;
}
/**
* @return the originalUrlParams
*/
public Map<String, String[]> getOriginalUrlParams() {
return originalUrlParams;
}
/**
* @param originalUrlParams the originalUrlParams to set
*/
public void setOriginalUrlParams(final Map<String, String[]> originalUrlParams) {
this.originalUrlParams = originalUrlParams;
}
/**
* @return the processedUrlParams
*/
public Map<String, String[]> getProcessedUrlParams() {
return processedUrlParams;
}
/**
* @param processedUrlParams the processedUrlParams to set
*/
public void setProcessedUrlParams(final Map<String, String[]> processedUrlParams) {
this.processedUrlParams = processedUrlParams;
}
/**
* @return the internal
*/
public boolean isInternal() {
return internal;
}
/**
* @param internal the internal to set
*/
public void setInternal(final boolean internal) {
this.internal = internal;
}
/**
* @return the server
*/
public String getServer() {
return server;
}
/**
* @param server the server to set
*/
public void setServer(final String server) {
this.server = server;
}
/**
* @return the method
*/
public Method getMethod() {
return method;
}
/**
* @param method the method to set
*/
public void setMethod(final Method method) {
this.method = method;
}
/**
* @return the jSessionId
*/
public String getjSessionId() {
return jSessionId;
}
/**
* @param jSessionId the jSessionId to set
*/
public void setjSessionId(final String jSessionId) {
this.jSessionId = jSessionId;
}
/**
* @return the urlParams
*/
public String getUrlParams() {
return urlParams;
}
/**
* @param urlParams the urlParams to set
*/
public void setUrlParams(final String urlParams) {
this.urlParams = urlParams;
}
public void setComposedUrlParams(final String composedParams) {
this.composedParams = composedParams;
}
public boolean hasUriTemplate() {
return uriTemplate != null;
}
public String getUrlWithOutUriTemplate() {
if (hasUriTemplate()) {
return originalUrl.replace(getUriTemplate(), "");
}
return originalUrl;
}
public String getUriTemplate() {
return uriTemplate != null ? uriTemplate : "";
}
/**
* Generate a url with all parameters.
*
* @param urlData url data object
* @return complete url
*/
void getParamProcessedUrl(final StringBuilder sb) {
sb.setLength(0);
if (server != null) {
sb.append(server);
}
if (!urlObfuscation || !internal || templated) {
sb.append(contextPathRelativeUrl);
}
else {
sb.append(contextPathRelativeUrl.substring(0, contextPathRelativeUrl.length() - urlWithoutContextPath.length())).append('/')
.append(OBFUSCATION_PATH);
}
// Add jSessionId
if (jSessionId != null) {
sb.append(';').append(jSessionId);
}
if (composedParams != null) {
sb.append('?').append(composedParams);
}
else if (urlParams != null && urlParams.length() != 0) {
sb.append('?').append(urlParams);
}
}
public String getProcessedUrlWithHdivState(final StringBuilder sb, final String hdivParameter, final String stateParam) {
if (stateParam == null || stateParam.length() <= 0) {
getParamProcessedUrl(sb);
return sb.toString();
}
getParamProcessedUrl(sb);
char separator = containsParams() ? '&' : '?';
sb.append(separator).append(hdivParameter).append('=').append(stateParam);
if (uriTemplate != null) {
sb.append(uriTemplate.replace('?', '&'));
}
if (anchor != null) {
// it could be ""
sb.append('#').append(anchor);
}
return sb.toString();
}
/**
* Generate final url with all parameters and anchor.
*
* @param url Generating url
* @return complete url
*/
public String getProcessedUrl(final StringBuilder url) {
getParamProcessedUrl(url);
if (anchor != null) {
// it could be ""
url.append('#').append(anchor);
}
return url.toString();
}
private void parser(final String uriTemplate) {
int startVars = uriTemplate.lastIndexOf('/');
final Matcher matcher = NAMES_PATTERN.matcher(startVars == -1 ? uriTemplate : uriTemplate.substring(startVars));
StringBuilder sb = null;
boolean variable = false;
while (matcher.find()) {
final String match = matcher.group(1);
if (match.startsWith("?") || match.startsWith("&")) {
final int colonIdx = match.indexOf(':');
if (colonIdx == -1) {
variable = true;
if (sb == null) {
sb = new StringBuilder();
sb.append('{');
}
sb.append(match);
}
else {
if (colonIdx + 1 == match.length()) {
throw new IllegalArgumentException("No custom regular expression specified after ':' in \"" + match + "\"");
}
if (sb == null) {
sb = new StringBuilder();
sb.append('{');
}
sb.append(match.substring(0, colonIdx));
}
}
else {
templated = true;
}
}
if (variable) {
sb.append('}');
this.uriTemplate = sb.toString();
}
}
public boolean isJS() {
return originalUrl.length() > 10 && originalUrl.charAt(10) == ':' && originalUrl.toLowerCase().startsWith("javascript:");
}
public boolean isHdivStateNecessary(final HDIVConfig config) {
if (isJS() || !internal || config.isStartPage(urlWithoutContextPath, method) || hasExtensionToExclude(config)) {
return false;
}
boolean validateParamLessUrls = config.isValidationInUrlsWithoutParamsActivated();
// if url is a link (or a GET method form) and has not got parameters, we do not have to include HDIV's state
if (isGetMethod() && !validateParamLessUrls && !containsParams()) {
return false;
}
urlObfuscation = config.isUrlObfuscation();
return true;
}
/**
* Determines if the url contains a extension to exclude for Hdiv state inclusion.
*
* @param urlData url data object
* @return is excluded or not
*/
private boolean hasExtensionToExclude(final HDIVConfig config) {
if (contextPathRelativeUrl.charAt(contextPathRelativeUrl.length() - 1) == '/') {
return false;
}
List<String> excludedExtensions = config.getExcludedURLExtensions();
if (excludedExtensions != null) {
for (int i = 0; i < excludedExtensions.size(); i++) {
if (contextPathRelativeUrl.endsWith(excludedExtensions.get(i))) {
return true;
}
}
}
// jsp is always protected
if (contextPathRelativeUrl.endsWith(".jsp")) {
return false;
}
List<PatternMatcher> protectedExtension = config.getProtectedURLPatterns();
for (int i = 0; protectedExtension != null && i < protectedExtension.size(); i++) {
PatternMatcher extensionPattern = protectedExtension.get(i);
if (extensionPattern.matches(contextPathRelativeUrl)) {
return false;
}
}
return contextPathRelativeUrl.charAt(0) != '/' && contextPathRelativeUrl.indexOf('.') == -1;
}
}