/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.util;
import com.liferay.portal.kernel.nio.charset.CharsetEncoderUtil;
import com.liferay.portal.kernel.security.pacl.DoPrivileged;
import com.liferay.portal.kernel.spring.osgi.OSGiBeanProperties;
import com.liferay.portal.kernel.util.CharPool;
import com.liferay.portal.kernel.util.FriendlyURLNormalizer;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.util.Normalizer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetEncoder;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Brian Wing Shun Chan
* @author Shuyang Zhou
*/
@DoPrivileged
@OSGiBeanProperties(property = {"service.ranking:Integer=100"})
public class FriendlyURLNormalizerImpl implements FriendlyURLNormalizer {
@Override
public String normalize(String friendlyURL) {
return normalize(friendlyURL, false);
}
/**
* @deprecated As of 7.0.0, with no direct replacement
*/
@Deprecated
@Override
public String normalize(String friendlyURL, Pattern friendlyURLPattern) {
if (Validator.isNull(friendlyURL)) {
return friendlyURL;
}
friendlyURL = StringUtil.toLowerCase(friendlyURL);
friendlyURL = Normalizer.normalizeToAscii(friendlyURL);
Matcher matcher = friendlyURLPattern.matcher(friendlyURL);
friendlyURL = matcher.replaceAll(StringPool.DASH);
StringBuilder sb = new StringBuilder(friendlyURL.length());
for (int i = 0; i < friendlyURL.length(); i++) {
char c = friendlyURL.charAt(i);
if (c == CharPool.DASH) {
if ((i == 0) || (CharPool.DASH != sb.charAt(sb.length() - 1))) {
sb.append(CharPool.DASH);
}
}
else {
sb.append(c);
}
}
if (sb.length() == friendlyURL.length()) {
return friendlyURL;
}
return sb.toString();
}
@Override
public String normalizeWithEncoding(String friendlyURL) {
if (Validator.isNull(friendlyURL)) {
return friendlyURL;
}
StringBuilder sb = new StringBuilder(friendlyURL.length());
boolean modified = false;
ByteBuffer byteBuffer = null;
CharBuffer charBuffer = null;
CharsetEncoder charsetEncoder = null;
for (int i = 0; i < friendlyURL.length(); i++) {
char c = friendlyURL.charAt(i);
if ((CharPool.UPPER_CASE_A <= c) && (c <= CharPool.UPPER_CASE_Z)) {
sb.append((char)(c + 32));
modified = true;
}
else if (((CharPool.LOWER_CASE_A <= c) &&
(c <= CharPool.LOWER_CASE_Z)) ||
((CharPool.NUMBER_0 <= c) && (c <= CharPool.NUMBER_9)) ||
(c == CharPool.PERIOD) || (c == CharPool.SLASH) ||
(c == CharPool.STAR) || (c == CharPool.UNDERLINE)) {
sb.append(c);
}
else if (Arrays.binarySearch(_REPLACE_CHARS, c) >= 0) {
if ((i == 0) || (CharPool.DASH != sb.charAt(sb.length() - 1))) {
sb.append(CharPool.DASH);
if (c != CharPool.DASH) {
modified = true;
}
}
else {
modified = true;
}
}
else {
if (charsetEncoder == null) {
charsetEncoder = CharsetEncoderUtil.getCharsetEncoder(
StringPool.UTF8);
byteBuffer = ByteBuffer.allocate(4);
charBuffer = CharBuffer.allocate(2);
}
else {
byteBuffer.clear();
charBuffer.clear();
}
charBuffer.put(c);
boolean endOfInput = false;
if ((friendlyURL.length() - 1) == i) {
endOfInput = true;
}
if (Character.isHighSurrogate(c) &&
(i + 1) < friendlyURL.length()) {
c = friendlyURL.charAt(i + 1);
if (Character.isLowSurrogate(c)) {
charBuffer.put(c);
i++;
}
else {
endOfInput = true;
}
}
charBuffer.flip();
charsetEncoder.encode(charBuffer, byteBuffer, endOfInput);
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
byte b = byteBuffer.get();
sb.append(CharPool.PERCENT);
sb.append(_HEX_DIGITS[(b >> 4) & 0x0F]);
sb.append(_HEX_DIGITS[b & 0x0F]);
}
if (endOfInput) {
charsetEncoder.flush(byteBuffer);
charsetEncoder.reset();
}
modified = true;
}
}
if (modified) {
return sb.toString();
}
return friendlyURL;
}
@Override
public String normalizeWithPeriodsAndSlashes(String friendlyURL) {
return normalize(friendlyURL, true);
}
protected String normalize(String friendlyURL, boolean periodsAndSlashes) {
if (Validator.isNull(friendlyURL)) {
return friendlyURL;
}
friendlyURL = Normalizer.normalizeToAscii(friendlyURL);
StringBuilder sb = new StringBuilder(friendlyURL.length());
boolean modified = false;
for (int i = 0; i < friendlyURL.length(); i++) {
char c = friendlyURL.charAt(i);
if ((CharPool.UPPER_CASE_A <= c) && (c <= CharPool.UPPER_CASE_Z)) {
sb.append((char)(c + 32));
modified = true;
}
else if (((CharPool.LOWER_CASE_A <= c) &&
(c <= CharPool.LOWER_CASE_Z)) ||
((CharPool.NUMBER_0 <= c) && (c <= CharPool.NUMBER_9)) ||
(c == CharPool.UNDERLINE) ||
(!periodsAndSlashes &&
((c == CharPool.SLASH) || (c == CharPool.PERIOD)))) {
sb.append(c);
}
else {
if ((i == 0) || (CharPool.DASH != sb.charAt(sb.length() - 1))) {
sb.append(CharPool.DASH);
if (c != CharPool.DASH) {
modified = true;
}
}
else {
modified = true;
}
}
}
if (modified) {
return sb.toString();
}
return friendlyURL;
}
private static final char[] _HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
'E', 'F'
};
private static final char[] _REPLACE_CHARS;
static {
char[] replaceChars = new char[] {
'-', ' ', ',', '\\', '\'', '\"', '(', ')', '[', ']', '{', '}', '?',
'#', '@', '+', '~', ';', '$', '!', '=', ':', '&', '\u00a3',
'\u2013', '\u2014', '\u2018', '\u2019', '\u201c', '\u201d'
};
Arrays.sort(replaceChars);
_REPLACE_CHARS = replaceChars;
}
}