1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.net;
19
20import java.io.UnsupportedEncodingException;
21import java.nio.ByteBuffer;
22import java.nio.CharBuffer;
23import java.nio.charset.Charset;
24import java.nio.charset.IllegalCharsetNameException;
25import java.nio.charset.UnsupportedCharsetException;
26
27import org.apache.harmony.luni.util.Msg;
28
29/**
30 * This class is used to decode a string which is encoded in the {@code
31 * application/x-www-form-urlencoded} MIME content type.
32 */
33public class URLDecoder {
34
35    static Charset defaultCharset;
36
37    /**
38     * Decodes the argument which is assumed to be encoded in the {@code
39     * x-www-form-urlencoded} MIME content type.
40     * <p>
41     *'+' will be converted to space, '%' and two following hex digit
42     * characters are converted to the equivalent byte value. All other
43     * characters are passed through unmodified. For example "A+B+C %24%25" ->
44     * "A B C $%".
45     *
46     * @param s
47     *            the encoded string.
48     * @return the decoded clear-text representation of the given string.
49     * @deprecated use {@link #decode(String, String)} instead.
50     */
51    @Deprecated
52    public static String decode(String s) {
53
54        if (defaultCharset == null) {
55            try {
56                defaultCharset = Charset.forName(
57                        System.getProperty("file.encoding")); //$NON-NLS-1$
58            } catch (IllegalCharsetNameException e) {
59                // Ignored
60            } catch (UnsupportedCharsetException e) {
61                // Ignored
62            }
63
64            if (defaultCharset == null) {
65                defaultCharset = Charset.forName("ISO-8859-1"); //$NON-NLS-1$
66            }
67        }
68        return decode(s, defaultCharset);
69    }
70
71    /**
72     * Decodes the argument which is assumed to be encoded in the {@code
73     * x-www-form-urlencoded} MIME content type using the specified encoding
74     * scheme.
75     * <p>
76     *'+' will be converted to space, '%' and two following hex digit
77     * characters are converted to the equivalent byte value. All other
78     * characters are passed through unmodified. For example "A+B+C %24%25" ->
79     * "A B C $%".
80     *
81     * @param s
82     *            the encoded string.
83     * @param enc
84     *            the encoding scheme to be used.
85     * @return the decoded clear-text representation of the given string.
86     * @throws UnsupportedEncodingException
87     *             if the specified encoding scheme is invalid.
88     */
89    public static String decode(String s, String enc)
90            throws UnsupportedEncodingException {
91
92        if (enc == null) {
93            throw new NullPointerException();
94        }
95
96        // If the given encoding is an empty string throw an exception.
97        if (enc.length() == 0) {
98            throw new UnsupportedEncodingException(
99                    // K00a5=Invalid parameter - {0}
100                    Msg.getString("K00a5", "enc")); //$NON-NLS-1$ //$NON-NLS-2$
101        }
102
103        if (s.indexOf('%') == -1) {
104            if (s.indexOf('+') == -1)
105                return s;
106            char str[] = s.toCharArray();
107            for (int i = 0; i < str.length; i++) {
108                if (str[i] == '+')
109                    str[i] = ' ';
110            }
111            return new String(str);
112        }
113
114        Charset charset = null;
115        try {
116            charset = Charset.forName(enc);
117        } catch (IllegalCharsetNameException e) {
118            throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
119                    enc).initCause(e));
120        } catch (UnsupportedCharsetException e) {
121            throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
122                    enc).initCause(e));
123        }
124
125        return decode(s, charset);
126    }
127
128    private static String decode(String s, Charset charset) {
129
130        char str_buf[] = new char[s.length()];
131        byte buf[] = new byte[s.length() / 3];
132        int buf_len = 0;
133
134        for (int i = 0; i < s.length();) {
135            char c = s.charAt(i);
136            if (c == '+') {
137                str_buf[buf_len] = ' ';
138            } else if (c == '%') {
139
140                int len = 0;
141                do {
142                    if (i + 2 >= s.length()) {
143                        throw new IllegalArgumentException(
144                                // K01fe=Incomplete % sequence at\: {0}
145                                Msg.getString("K01fe", i)); //$NON-NLS-1$
146                    }
147                    int d1 = Character.digit(s.charAt(i + 1), 16);
148                    int d2 = Character.digit(s.charAt(i + 2), 16);
149                    if (d1 == -1 || d2 == -1) {
150                        throw new IllegalArgumentException(
151                                // K01ff=Invalid % sequence ({0}) at\: {1}
152                                Msg.getString(
153                                        "K01ff", //$NON-NLS-1$
154                                        s.substring(i, i + 3),
155                                        String.valueOf(i)));
156                    }
157                    buf[len++] = (byte) ((d1 << 4) + d2);
158                    i += 3;
159                } while (i < s.length() && s.charAt(i) == '%');
160
161                CharBuffer cb = charset.decode(ByteBuffer.wrap(buf, 0, len));
162                len = cb.length();
163                System.arraycopy(cb.array(), 0, str_buf, buf_len, len);
164                buf_len += len;
165                continue;
166            } else {
167                str_buf[buf_len] = c;
168            }
169            i++;
170            buf_len++;
171        }
172        return new String(str_buf, 0, buf_len);
173    }
174}
175