/** * 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.kernel.util; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.nio.charset.CharsetDecoderUtil; import com.liferay.portal.kernel.nio.charset.CharsetEncoderUtil; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; /** * @author Shuyang Zhou * @author Brian Wing Shun Chan */ public class URLCodec { public static String decodeURL(String encodedURLString) { return decodeURL(encodedURLString, StringPool.UTF8); } public static String decodeURL( String encodedURLString, String charsetName) { if (encodedURLString == null) { return null; } if (encodedURLString.length() == 0) { return StringPool.BLANK; } StringBuilder sb = null; CharsetDecoder charsetDecoder = null; for (int i = 0; i < encodedURLString.length(); i++) { char c = encodedURLString.charAt(i); switch (c) { case CharPool.PERCENT: ByteBuffer byteBuffer = _getEncodedByteBuffer( encodedURLString, i); if (charsetDecoder == null) { charsetDecoder = CharsetDecoderUtil.getCharsetDecoder( charsetName); } CharBuffer charBuffer = null; try { charBuffer = charsetDecoder.decode(byteBuffer); } catch (CharacterCodingException cce) { _log.error(cce, cce); return StringPool.BLANK; } if (sb == null) { sb = new StringBuilder(encodedURLString.length()); if (i > 0) { sb.append(encodedURLString, 0, i); } } sb.append(charBuffer); i += byteBuffer.capacity() * 3 - 1; break; case CharPool.PLUS: if (sb == null) { sb = new StringBuilder(encodedURLString.length()); if (i > 0) { sb.append(encodedURLString, 0, i); } } sb.append(CharPool.SPACE); break; default: if (sb != null) { sb.append(c); } } } if (sb == null) { return encodedURLString; } else { return sb.toString(); } } public static String encodeURL(String rawURLString) { return encodeURL(rawURLString, StringPool.UTF8, false); } public static String encodeURL(String rawURLString, boolean escapeSpaces) { return encodeURL(rawURLString, StringPool.UTF8, escapeSpaces); } public static String encodeURL( String rawURLString, String charsetName, boolean escapeSpaces) { if (rawURLString == null) { return null; } if (rawURLString.isEmpty()) { return StringPool.BLANK; } CharsetEncoder charsetEncoder = null; char[] hexes = new char[2]; int lastReplacementIndex = 0; StringBuilder sb = null; for (int i = 0; i < rawURLString.length(); i++) { char c = rawURLString.charAt(i); if ((c < 128) && _validChars[c]) { continue; } if (sb == null) { sb = new StringBuilder(rawURLString.length() + 64); sb.append(rawURLString, 0, i); } else if (i > lastReplacementIndex) { sb.append(rawURLString, lastReplacementIndex, i); } if (c < 128) { char[] encodingReplacement = _ENCODING_REPLACEMENTS[c]; if (encodingReplacement != null) { if (!escapeSpaces && (c == CharPool.SPACE)) { sb.append(CharPool.PLUS); } else { sb.append(encodingReplacement); } lastReplacementIndex = i + 1; continue; } } CharBuffer charBuffer = _getRawCharBuffer( rawURLString, i, escapeSpaces); if (charsetEncoder == null) { charsetEncoder = CharsetEncoderUtil.getCharsetEncoder( charsetName); } i += charBuffer.length() - 1; lastReplacementIndex = i + 1; ByteBuffer byteBuffer = null; try { byteBuffer = charsetEncoder.encode(charBuffer); } catch (CharacterCodingException cce) { _log.error(cce, cce); return StringPool.BLANK; } for (int j = byteBuffer.position(); j < byteBuffer.limit(); j++) { sb.append(CharPool.PERCENT); sb.append( UnicodeFormatter.byteToHex(byteBuffer.get(), hexes, true)); } } if (sb == null) { return rawURLString; } if (lastReplacementIndex < rawURLString.length()) { sb.append( rawURLString, lastReplacementIndex, rawURLString.length()); } return sb.toString(); } private static int _charToHex(char c) { if ((c >= CharPool.LOWER_CASE_A) && (c <= CharPool.LOWER_CASE_F)) { return c - CharPool.LOWER_CASE_A + 10; } if ((c >= CharPool.UPPER_CASE_A) && (c <= CharPool.UPPER_CASE_F)) { return c - CharPool.UPPER_CASE_A + 10; } if ((c >= CharPool.NUMBER_0) && (c <= CharPool.NUMBER_9)) { return c - CharPool.NUMBER_0; } throw new IllegalArgumentException(c + " is not a hex char"); } private static ByteBuffer _getEncodedByteBuffer( String encodedString, int start) { int count = 1; for (int i = start + 3; i < encodedString.length(); i += 3) { if (encodedString.charAt(i) == CharPool.PERCENT) { count++; } else { break; } } if (encodedString.length() < (start + count * 3)) { throw new IllegalArgumentException( "Invalid URL encoding " + encodedString); } ByteBuffer byteBuffer = ByteBuffer.allocate(count); for (int i = start; i < start + count * 3; i += 3) { int high = _charToHex(encodedString.charAt(i + 1)); int low = _charToHex(encodedString.charAt(i + 2)); byteBuffer.put((byte)((high << 4) + low)); } byteBuffer.flip(); return byteBuffer; } private static CharBuffer _getRawCharBuffer( String rawString, int start, boolean escapeSpaces) { int count = 0; for (int i = start; i < rawString.length(); i++) { char rawChar = rawString.charAt(i); if (((rawChar >= 128) || !_validChars[rawChar]) && (escapeSpaces || (rawChar != CharPool.SPACE))) { count++; if (Character.isHighSurrogate(rawChar)) { if (((i + 1) < rawString.length()) && Character.isLowSurrogate(rawString.charAt(i + 1))) { i++; count++; } } } else { break; } } return CharBuffer.wrap(rawString, start, start + count); } private static final char[][] _ENCODING_REPLACEMENTS = new char[128][]; private static final Log _log = LogFactoryUtil.getLog(URLCodec.class); private static final boolean[] _validChars = new boolean[128]; static { _ENCODING_REPLACEMENTS[CharPool.AMPERSAND] = "%26".toCharArray(); _ENCODING_REPLACEMENTS[CharPool.COLON] = "%3A".toCharArray(); _ENCODING_REPLACEMENTS[CharPool.EQUAL] = "%3D".toCharArray(); _ENCODING_REPLACEMENTS[CharPool.PERCENT] = "%25".toCharArray(); _ENCODING_REPLACEMENTS[CharPool.PLUS] = "%2B".toCharArray(); _ENCODING_REPLACEMENTS[CharPool.QUESTION] = "%3F".toCharArray(); _ENCODING_REPLACEMENTS[CharPool.SLASH] = "%2F".toCharArray(); _ENCODING_REPLACEMENTS[CharPool.SPACE] = "%20".toCharArray(); for (int i = 'a'; i <= 'z'; i++) { _validChars[i] = true; } for (int i = 'A'; i <= 'Z'; i++) { _validChars[i] = true; } for (int i = '0'; i <= '9'; i++) { _validChars[i] = true; } _validChars['-'] = true; _validChars['_'] = true; _validChars['.'] = true; _validChars['*'] = true; } }