io.wcm.handler.url.suffix.impl.UrlSuffixUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of io.wcm.handler.url Show documentation
Show all versions of io.wcm.handler.url Show documentation
URL resolving and processing.
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2014 wcm.io
* %%
* 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.
* #L%
*/
package io.wcm.handler.url.suffix.impl;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
import org.apache.commons.lang3.text.translate.EntityArrays;
import org.apache.commons.lang3.text.translate.LookupTranslator;
import org.apache.sling.api.resource.Resource;
/**
* Static methods and constants for URL suffix handling.
*/
public final class UrlSuffixUtil {
/**
* Delimiter char for suffix parts and key/value pairs
*/
public static final char SUFFIX_PART_DELIMITER = '/';
/**
* Delimiter char for suffix parts and key and value
*/
public static final char KEY_VALUE_DELIMITER = '=';
/**
* Double escaping is necessary when constructing urls so that escaping is not resolved by the webserver
*/
public static final String ESCAPE_DELIMITER = "~";
/**
* Slash value within suffix part
*/
public static final String ESCAPED_SLASH = ESCAPE_DELIMITER + hexCode('/');
/**
* Map with special chars and their replacements that are escaped with special ~ and hexcode
*/
private static final String[][] SPECIAL_CHARS_MAPPING = {
// escape delimiter chars
{
Character.toString(SUFFIX_PART_DELIMITER), ESCAPE_DELIMITER + hexCode(SUFFIX_PART_DELIMITER)
},
{
Character.toString(KEY_VALUE_DELIMITER), ESCAPE_DELIMITER + hexCode(KEY_VALUE_DELIMITER)
},
// '.' must be custom-escaped (if no file extension is added to suffix,
// anything after a dot would be interpreted as file extension during parsing)
{
Character.toString('.'), ESCAPE_DELIMITER + hexCode('.')
},
// escape '%' to avoid confusion with URL escaping
{
Character.toString('%'), ESCAPE_DELIMITER + hexCode('%')
},
// '/' must be custom-escaped (dispatcher/webserver may filter out/misinterpret urls with unescaped slashes)
{
Character.toString('/'), ESCAPE_DELIMITER + hexCode('/')
},
// escape ':'
{
Character.toString(':'), ESCAPE_DELIMITER + hexCode(':')
},
// escape ' ' as well (singular problem occurred once)
{
Character.toString(' '), ESCAPE_DELIMITER + hexCode(' ')
}
};
/**
* Escape special chars for suffix.
*/
private static final CharSequenceTranslator ESCAPE_SPECIAL_CHARS = new LookupTranslator(SPECIAL_CHARS_MAPPING);
/**
* Unesacpe special chars in suffix.
*/
private static final CharSequenceTranslator UNESCAPE_SPECIAL_CHARS = new LookupTranslator(EntityArrays.invert(SPECIAL_CHARS_MAPPING));
private UrlSuffixUtil() {
// static methods only
}
/**
* Convert to hex code
* @param c char
* @return Hex code
*/
public static String hexCode(char c) {
return Integer.toString(c, 16).toUpperCase();
}
/**
* Encode resource path part
* @param relativePath Relative path
* @return Encodes path part
*/
public static String encodeResourcePathPart(String relativePath) {
return ESCAPE_SPECIAL_CHARS.translate(relativePath);
}
/**
* Decode resource path part
* @param suffixPart Suffix part
* @return Decoded path part
*/
public static String decodeResourcePathPart(String suffixPart) {
return UNESCAPE_SPECIAL_CHARS.translate(suffixPart);
}
/**
* Encode key value part
* @param string String
* @return Encoded string
*/
public static String encodeKeyValuePart(String string) {
return ESCAPE_SPECIAL_CHARS.translate(string);
}
/**
* Decode value
* @param suffixPart Suffix part
* @return Decoded value
*/
public static String decodeValue(String suffixPart) {
// value is the part *after* KEY_VALUE_DELIMITER
String value = StringUtils.substringAfter(suffixPart, Character.toString(KEY_VALUE_DELIMITER));
return UNESCAPE_SPECIAL_CHARS.translate(value);
}
/**
* Decode key
* @param suffixPart Suffix part
* @return Decoded key
*/
public static String decodeKey(String suffixPart) {
// key is the part *before* KEY_VALUE_DELIMITER
String key = StringUtils.substringBefore(suffixPart, Character.toString(KEY_VALUE_DELIMITER));
return UNESCAPE_SPECIAL_CHARS.translate(key);
}
/**
* Split suffix
* @param suffix Suffix
* @return Suffix parts
*/
public static String[] splitSuffix(String suffix) {
String theSuffix = suffix;
String[] parts;
if (StringUtils.isBlank(theSuffix)) {
// no suffix given - return empty list
parts = new String[0];
}
else {
// remove leading slash
if (theSuffix.startsWith(ESCAPED_SLASH)) {
theSuffix = theSuffix.substring(ESCAPED_SLASH.length());
}
// remove file extension
theSuffix = StringUtils.substringBeforeLast(theSuffix, ".");
// split the suffix to extract the paths of the selected components
parts = StringUtils.split(theSuffix, SUFFIX_PART_DELIMITER);
}
return parts;
}
/**
* Convert key value pair to map
* @param key Key
* @param value Value
* @return Map
*/
public static Map keyValuePairAsMap(String key, Object value) {
Map paramMap = new HashMap<>();
paramMap.put(key, value);
return paramMap;
}
/**
* @param resource the resource being addressed by the relative path
* @param baseResource the resource used as base to resolve the relative path
* @return the relative path (without leading slash)
*/
public static String getRelativePath(Resource resource, Resource baseResource) {
if (baseResource == null) {
throw new IllegalArgumentException("the base resource for constructing relative path must not be null");
}
if (resource == null) {
throw new IllegalArgumentException("the resource for constructing relative path must not be null");
}
String absolutePath = resource.getPath();
String basePath = baseResource.getPath();
if (absolutePath.equals(basePath)) {
// relative path for the root resource is "."
return ".";
}
// be picky about resources not located beneath the base resource
if (!absolutePath.startsWith(basePath + "/")) {
throw new IllegalArgumentException("the resource " + resource + " is not a descendent of the base resource " + baseResource);
}
// return relative path
return StringUtils.substringAfter(absolutePath, basePath + "/");
}
}