5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster/**
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * The contents of this file are subject to the terms
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * of the Common Development and Distribution License
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * (the License). You may not use this file except in
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * compliance with the License.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * You can obtain a copy of the License at
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * https://opensso.dev.java.net/public/CDDLv1.0.html or
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * opensso/legal/CDDLv1.0.txt
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * See the License for the specific language governing
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * permission and limitations under the License.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * When distributing Covered Code, include this CDDL
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Header Notice in each file and include the License file
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * at opensso/legal/CDDLv1.0.txt.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * If applicable, add the following below the CDDL Header,
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * with the fields enclosed by brackets [] replaced by
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * your own identifying information:
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * "Portions Copyrighted [year] [name of copyright owner]"
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * $Id: URLEncDec.java,v 1.5 2009/08/11 13:18:15 si224302 Exp $
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterpackage com.sun.identity.shared.encode;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterimport java.io.ByteArrayOutputStream;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterimport java.io.IOException;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterimport java.io.OutputStreamWriter;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterimport java.io.UnsupportedEncodingException;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterimport java.net.MalformedURLException;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterimport java.net.URL;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterimport java.net.URLDecoder;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterimport java.net.URLEncoder;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterimport java.util.BitSet;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Fosterpublic class URLEncDec {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster final private static String UTF_8 = "UTF-8";
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster final private static String SPACE = "%20";
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster static BitSet dontNeedEncoding;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster static final int caseDiff = ('a' - 'A');
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster static {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /*
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * The list of characters that are not encoded has been determined as
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * follows:
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * RFC 2396 states: ----- Data characters that are allowed in a URI but
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * do not have a reserved purpose are called unreserved. These include
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * upper and lower case letters, decimal digits, and a limited set of
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * punctuation marks and symbols.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * unreserved = alphanum | mark
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Unreserved characters can be escaped without changing the semantics
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * of the URI, but this should not be done unless the URI is being used
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * in a context that does not allow the unescaped character to appear.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * -----
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * It appears that both Netscape and Internet Explorer escape all
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * special characters from this list with the exception of "-", "_",
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * ".", "*". While it is not clear why they are escaping the other
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * characters, perhaps it is safest to assume that there might be
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * contexts in which the others are unsafe if not escaped. Therefore, we
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * will use the same list. It is also noteworthy that this is consistent
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * with O'Reilly's "HTML: The Definitive Guide" (page 164).
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * As a last note, Intenet Explorer does not encode the "@" character
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * which is clearly not unreserved according to the RFC. We are being
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * consistent with the RFC in this matter, as is Netscape.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster dontNeedEncoding = new BitSet(256);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int i;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster for (i = 'a'; i <= 'z'; i++) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster dontNeedEncoding.set(i);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster for (i = 'A'; i <= 'Z'; i++) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster dontNeedEncoding.set(i);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster for (i = '0'; i <= '9'; i++) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster dontNeedEncoding.set(i);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster dontNeedEncoding.set(' '); /*
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * encoding a space to a + is done in the
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * encode() method
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster dontNeedEncoding.set('-');
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster dontNeedEncoding.set('_');
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster dontNeedEncoding.set('.');
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster dontNeedEncoding.set('*');
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /**
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Translates a string into <code>application/x-www-form-urlencoded</code>
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * format using the UTF-8 encoding scheme. The <a
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * href="http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * World Wide Web Consortium Recommendation</a> states that UTF-8 should be
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * used to ensure compatibilities.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @param s
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * <code>String</code> to be translated.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @return the translated <code>String</code>.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster public static String encode(String s) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster String ret = null;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster try {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ret = encode(s, UTF_8);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster } catch (UnsupportedEncodingException e) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster return ret;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /**
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Decodes a <code>application/x-www-form-urlencoded</code> string using
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * the UTF-8 encoding scheme. The <a
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * href="http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * World Wide Web Consortium Recommendation</a> states that UTF-8 should be
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * used to ensure compatibilities.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @param s
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * the <code>String</code> to decode
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @return the newly decoded <code>String</code>
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster public static String decode(String s) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster String ret = null;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster try {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ret = decode(s, UTF_8);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster } catch (UnsupportedEncodingException e) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster return ret;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /**
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Translates a string into <code>application/x-www-form-urlencoded</code>
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * format using a specific encoding scheme. This method uses the supplied
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * encoding scheme to obtain the bytes for unsafe characters.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * <p>
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * <em><strong>Note:</strong> The <a href=
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * "http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * World Wide Web Consortium Recommendation</a> states that
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * UTF-8 should be used. Not doing so may introduce
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * incompatibilites.</em>
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @param s
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * <code>String</code> to be translated.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @param enc
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * The name of a supported <a
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * href="../lang/package-summary.html#charenc">
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * character encoding</a>.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @return the translated <code>String</code>.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @exception UnsupportedEncodingException
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * If the named encoding is not supported
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @see URLDecoder#decode(java.lang.String, java.lang.String)
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @since 1.4
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster private static String encode(String s, String enc)
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster throws UnsupportedEncodingException {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster boolean needToChange = false;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster boolean wroteUnencodedChar = false;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int maxBytesPerChar = 10; // rather arbitrary limit, but safe for now
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster StringBuffer out = new StringBuffer(s.length());
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster OutputStreamWriter writer = new OutputStreamWriter(buf, enc);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster for (int i = 0; i < s.length(); i++) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int c = s.charAt(i);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // System.out.println("Examining character: " + c);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (dontNeedEncoding.get(c)) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (c == ' ') {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster c = '+';
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster needToChange = true;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // System.out.println("Storing: " + c);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster out.append((char) c);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster wroteUnencodedChar = true;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster } else {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // convert to external encoding before hex conversion
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster try {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (wroteUnencodedChar) { // Fix for 4407610
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster writer = new OutputStreamWriter(buf, enc);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster wroteUnencodedChar = false;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster writer.write(c);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /*
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * If this character represents the start of a Unicode
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * surrogate pair, then pass in two characters. It's not
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * clear what should be done if a bytes reserved in the
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * surrogate pairs range occurs outside of a legal surrogate
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * pair. For now, just treat it as if it were any other
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * character.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (c >= 0xD800 && c <= 0xDBFF) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /*
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * System.out.println(Integer.toHexString(c) + " is high
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * surrogate");
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if ((i + 1) < s.length()) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int d = s.charAt(i + 1);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /*
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * System.out.println("\tExamining " +
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Integer.toHexString(d));
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (d >= 0xDC00 && d <= 0xDFFF) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /*
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * System.out.println("\t" +
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Integer.toHexString(d) + " is low
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * surrogate");
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster writer.write(d);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster i++;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster writer.flush();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster } catch (IOException e) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster buf.reset();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster continue;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster byte[] ba = buf.toByteArray();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster for (int j = 0; j < ba.length; j++) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster out.append('%');
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // converting to use uppercase letter as part of
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // the hex value if ch is a letter.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (Character.isLetter(ch)) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ch -= caseDiff;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster out.append(ch);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ch = Character.forDigit(ba[j] & 0xF, 16);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (Character.isLetter(ch)) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ch -= caseDiff;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster out.append(ch);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster buf.reset();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster needToChange = true;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster return (needToChange ? out.toString() : s);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /**
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Decodes a <code>application/x-www-form-urlencoded</code> string using a
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * specific encoding scheme. The supplied encoding is used to determine what
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * characters are represented by any consecutive sequences of the form
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * "<code>%<i>xy</i></code>".
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * <p>
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * <em><strong>Note:</strong> The <a href=
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * "http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * World Wide Web Consortium Recommendation</a> states that
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * UTF-8 should be used. Not doing so may introduce
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * incompatibilites.</em>
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster *
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @param s
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * the <code>String</code> to decode
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @param enc
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * The name of a supported <a
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * href="../lang/package-summary.html#charenc">
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * character encoding</a>.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @return the newly decoded <code>String</code>
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @exception UnsupportedEncodingException
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * If the named encoding is not supported
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @see URLEncoder#encode(java.lang.String, java.lang.String)
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * @since 1.4
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster private static String decode(String s, String enc)
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster throws UnsupportedEncodingException {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster boolean needToChange = false;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster StringBuffer sb = new StringBuffer();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int numChars = s.length();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int i = 0;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (enc.length() == 0) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster throw new UnsupportedEncodingException(
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster "URLDecoder: empty string enc parameter");
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster while (i < numChars) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster char c = s.charAt(i);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster switch (c) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster case '+':
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster sb.append(' ');
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster i++;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster needToChange = true;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster break;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster case '%':
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /*
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Starting with this instance of %, process all consecutive
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * substrings of the form %xy. Each substring %xy will yield a
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * byte. Convert all consecutive bytes obtained this way to
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * whatever character(s) they represent in the provided
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * encoding.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster try {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // (numChars-i)/3 is an upper bound for the number
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // of remaining bytes
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster byte[] bytes = new byte[(numChars - i) / 3];
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int pos = 0;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster while (((i + 2) < numChars) && (c == '%')) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster bytes[pos++] = (byte) Integer.parseInt(s.substring(
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster i + 1, i + 3), 16);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster i += 3;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (i < numChars)
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster c = s.charAt(i);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // A trailing, incomplete byte encoding such as
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // "%x" will cause an exception to be thrown
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if ((i < numChars) && (c == '%'))
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster throw new IllegalArgumentException(
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster "URLDecoder: Incomplete trailing " +
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster "escape (%) pattern");
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster sb.append(new String(bytes, 0, pos, enc));
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster } catch (NumberFormatException e) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster throw new IllegalArgumentException(
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster "URLDecoder: Illegal hex characters " +
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster "in escape (%) pattern - "
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster + e.getMessage());
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster needToChange = true;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster break;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster default:
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster sb.append(c);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster i++;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster break;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster return (needToChange ? sb.toString() : s);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /*
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * Construct LDAP url string by converting non-ascii characters to
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * application/x-www-form-urlencoded format using UTF8. See RFC 2255 and RFC
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * 1738 for valid LDAP url format.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster public static String encodeLDAPUrl(String s) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster boolean needToChange = false;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster boolean wroteUnencodedChar = false;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int maxBytesPerChar = 10; // rather arbitrary limit, but safe for now
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster StringBuffer out = new StringBuffer(s.length());
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster try {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ByteArrayOutputStream buf = new ByteArrayOutputStream(
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster maxBytesPerChar);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster OutputStreamWriter writer = new OutputStreamWriter(buf, UTF_8);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster for (int i = 0; i < s.length(); i++) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int c = s.charAt(i);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (c <= 0x80) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (c == ' ') {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster needToChange = true;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster out.append(SPACE);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster } else {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster out.append((char) c);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster wroteUnencodedChar = true;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster } else {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // convert to external encoding before hex conversion
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster try {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (wroteUnencodedChar) { // Fix for 4407610
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster writer = new OutputStreamWriter(buf, UTF_8);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster wroteUnencodedChar = false;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster writer.write(c);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster /*
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * If this character represents the start of a Unicode
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * surrogate pair, then pass in two characters. It's not
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * clear what should be done if a bytes reserved in the
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * surrogate pairs range occurs outside of a legal
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * surrogate pair. For now, just treat it as if it were
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster * any other character.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster */
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (c >= 0xD800 && c <= 0xDBFF) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if ((i + 1) < s.length()) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster int d = s.charAt(i + 1);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (d >= 0xDC00 && d <= 0xDFFF) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster writer.write(d);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster i++;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster writer.flush();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster } catch (IOException e) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster buf.reset();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster continue;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster byte[] ba = buf.toByteArray();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster for (int j = 0; j < ba.length; j++) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster out.append('%');
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // converting to use uppercase letter as part of
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // the hex value if ch is a letter.
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (Character.isLetter(ch)) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ch -= caseDiff;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster out.append(ch);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ch = Character.forDigit(ba[j] & 0xF, 16);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (Character.isLetter(ch)) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster ch -= caseDiff;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster out.append(ch);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster buf.reset();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster needToChange = true;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster } catch (UnsupportedEncodingException uee) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster return (needToChange ? out.toString() : s);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster public static String encodeUrlPath(String u) throws MalformedURLException {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster URL url = new URL(u);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster String path = url.getPath();
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (path.length() != 0 && !path.equals("/")) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster String[] ps = path.split("/");
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster StringBuffer sb = new StringBuffer(u.length() + 20);
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster for (int i = 1; i < ps.length; i++) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster sb.append('/').append(encode(ps[i]));
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster // add '/' back if original path has it at the end
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster if (path.lastIndexOf('/') == (path.length() - 1)) {
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster sb.append('/');
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster u = u.replaceFirst(path, sb.toString());
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster return u;
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster }
5c099afa7c9361afc2f4477fec0e3018588d7840Allan Foster}