1f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project/* 2f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * Licensed to the Apache Software Foundation (ASF) under one or more 3f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * contributor license agreements. See the NOTICE file distributed with 4f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * this work for additional information regarding copyright ownership. 5f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * The ASF licenses this file to You under the Apache License, Version 2.0 6f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * (the "License"); you may not use this file except in compliance with 7f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * the License. You may obtain a copy of the License at 8f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * 9f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 10f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * 11f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 12f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 13f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * See the License for the specific language governing permissions and 15f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * limitations under the License. 16f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project */ 17f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project 18f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Projectpackage java.net; 19f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project 20f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Projectimport java.io.UnsupportedEncodingException; 213819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilsonimport java.nio.ByteBuffer; 223819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilsonimport java.nio.CharBuffer; 233819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilsonimport java.nio.charset.Charset; 243819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilsonimport java.nio.charset.IllegalCharsetNameException; 253819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilsonimport java.nio.charset.UnsupportedCharsetException; 26f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project 27f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Projectimport org.apache.harmony.luni.util.Msg; 28f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project 29f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project/** 30f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * This class is used to decode a string which is encoded in the {@code 31f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * application/x-www-form-urlencoded} MIME content type. 32f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project */ 33f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Projectpublic class URLDecoder { 34f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project 353819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson static Charset defaultCharset; 363819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 37f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project /** 38f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * Decodes the argument which is assumed to be encoded in the {@code 39f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * x-www-form-urlencoded} MIME content type. 40f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * <p> 41f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project *'+' will be converted to space, '%' and two following hex digit 42f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * characters are converted to the equivalent byte value. All other 43f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * characters are passed through unmodified. For example "A+B+C %24%25" -> 44f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * "A B C $%". 453819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson * 46f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * @param s 47f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * the encoded string. 48f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * @return the decoded clear-text representation of the given string. 49f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * @deprecated use {@link #decode(String, String)} instead. 50f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project */ 51f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project @Deprecated 52f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project public static String decode(String s) { 533819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 543819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson if (defaultCharset == null) { 553819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson try { 563819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson defaultCharset = Charset.forName( 573819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson System.getProperty("file.encoding")); //$NON-NLS-1$ 583819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } catch (IllegalCharsetNameException e) { 593819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson // Ignored 603819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } catch (UnsupportedCharsetException e) { 613819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson // Ignored 623819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } 633819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 643819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson if (defaultCharset == null) { 653819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson defaultCharset = Charset.forName("ISO-8859-1"); //$NON-NLS-1$ 663819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } 673819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } 683819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson return decode(s, defaultCharset); 69f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } 703819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 71f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project /** 72f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * Decodes the argument which is assumed to be encoded in the {@code 73f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * x-www-form-urlencoded} MIME content type using the specified encoding 74f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * scheme. 75f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * <p> 76f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project *'+' will be converted to space, '%' and two following hex digit 77f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * characters are converted to the equivalent byte value. All other 78f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * characters are passed through unmodified. For example "A+B+C %24%25" -> 79f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * "A B C $%". 803819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson * 81f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * @param s 82f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * the encoded string. 83f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * @param enc 84f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * the encoding scheme to be used. 85f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * @return the decoded clear-text representation of the given string. 86f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * @throws UnsupportedEncodingException 87f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project * if the specified encoding scheme is invalid. 88f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project */ 89f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project public static String decode(String s, String enc) 90f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project throws UnsupportedEncodingException { 91f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project 92f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project if (enc == null) { 93f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project throw new NullPointerException(); 94f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } 95f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project 96f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project // If the given encoding is an empty string throw an exception. 97f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project if (enc.length() == 0) { 983819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson throw new UnsupportedEncodingException( 993819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson // K00a5=Invalid parameter - {0} 1003819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson Msg.getString("K00a5", "enc")); //$NON-NLS-1$ //$NON-NLS-2$ 1013819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } 1023819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 1033819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson if (s.indexOf('%') == -1) { 1043819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson if (s.indexOf('+') == -1) 1053819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson return s; 1063819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson char str[] = s.toCharArray(); 1073819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson for (int i = 0; i < str.length; i++) { 1083819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson if (str[i] == '+') 1093819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson str[i] = ' '; 1103819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } 1113819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson return new String(str); 1123819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } 1133819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 1143819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson Charset charset = null; 1153819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson try { 1163819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson charset = Charset.forName(enc); 1173819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } catch (IllegalCharsetNameException e) { 1183819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson throw (UnsupportedEncodingException) (new UnsupportedEncodingException( 1193819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson enc).initCause(e)); 1203819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } catch (UnsupportedCharsetException e) { 1213819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson throw (UnsupportedEncodingException) (new UnsupportedEncodingException( 1223819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson enc).initCause(e)); 123f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } 124f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project 1253819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson return decode(s, charset); 1263819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson } 1273819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 1283819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson private static String decode(String s, Charset charset) { 1293819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 1303819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson char str_buf[] = new char[s.length()]; 1313819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson byte buf[] = new byte[s.length() / 3]; 1323819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson int buf_len = 0; 1333819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 134f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project for (int i = 0; i < s.length();) { 135f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project char c = s.charAt(i); 136f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project if (c == '+') { 1373819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson str_buf[buf_len] = ' '; 138f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } else if (c == '%') { 1393819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 1403819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson int len = 0; 141f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project do { 142f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project if (i + 2 >= s.length()) { 1433819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson throw new IllegalArgumentException( 1443819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson // K01fe=Incomplete % sequence at\: {0} 1453819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson Msg.getString("K01fe", i)); //$NON-NLS-1$ 146f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } 147f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project int d1 = Character.digit(s.charAt(i + 1), 16); 148f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project int d2 = Character.digit(s.charAt(i + 2), 16); 149f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project if (d1 == -1 || d2 == -1) { 1503819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson throw new IllegalArgumentException( 1513819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson // K01ff=Invalid % sequence ({0}) at\: {1} 1523819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson Msg.getString( 1533819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson "K01ff", //$NON-NLS-1$ 1543819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson s.substring(i, i + 3), 1553819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson String.valueOf(i))); 156f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } 1573819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson buf[len++] = (byte) ((d1 << 4) + d2); 158f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project i += 3; 159f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } while (i < s.length() && s.charAt(i) == '%'); 1603819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson 1613819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson CharBuffer cb = charset.decode(ByteBuffer.wrap(buf, 0, len)); 1623819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson len = cb.length(); 1633819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson System.arraycopy(cb.array(), 0, str_buf, buf_len, len); 1643819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson buf_len += len; 165f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project continue; 166f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } else { 1673819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson str_buf[buf_len] = c; 168f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } 169f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project i++; 1703819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson buf_len++; 171f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } 1723819a76e7c1f49253f0e077bd497f149340c02b8Jesse Wilson return new String(str_buf, 0, buf_len); 173f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project } 174f6c387128427e121477c1b32ad35cdcaa5101ba3The Android Open Source Project} 175