001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.codec.net; 019 020import java.io.ByteArrayOutputStream; 021import java.io.UnsupportedEncodingException; 022import java.net.URLDecoder; 023import java.net.URLEncoder; 024import java.util.BitSet; 025 026import org.apache.commons.codec.BinaryDecoder; 027import org.apache.commons.codec.BinaryEncoder; 028import org.apache.commons.codec.CharEncoding; 029import org.apache.commons.codec.DecoderException; 030import org.apache.commons.codec.EncoderException; 031import org.apache.commons.codec.StringDecoder; 032import org.apache.commons.codec.StringEncoder; 033import org.apache.commons.codec.binary.StringUtils; 034 035/** 036 * Implements the 'www-form-urlencoded' encoding scheme, also misleadingly known as URL encoding. 037 * <p> 038 * This codec is meant to be a replacement for standard Java classes {@link URLEncoder} and 039 * {@link URLDecoder} on older Java platforms, as these classes in Java versions below 040 * 1.4 rely on the platform's default charset encoding. 041 * </p> 042 * <p> 043 * This class is thread-safe as of 1.11 044 * </p> 045 * 046 * @see <a href="https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1">Chapter 17.13.4 Form content types</a> 047 * of the <a href="https://www.w3.org/TR/html4/">HTML 4.01 Specification</a> 048 * 049 * @since 1.2 050 */ 051public class URLCodec implements BinaryEncoder, BinaryDecoder, StringEncoder, StringDecoder { 052 053 /** 054 * Release 1.5 made this field final. 055 */ 056 protected static final byte ESCAPE_CHAR = '%'; 057 058 private static final byte PLUS_CHAR = '+'; 059 060 /** 061 * BitSet of www-form-url safe characters. 062 * This is a copy of the internal BitSet which is now used for the conversion. 063 * Changes to this field are ignored. 064 * 065 * @deprecated 1.11 Will be removed in 2.0 (CODEC-230) 066 */ 067 @Deprecated 068 protected static final BitSet WWW_FORM_URL; 069 070 private static final BitSet WWW_FORM_URL_SAFE = new BitSet(256); 071 072 // Static initializer for www_form_url 073 static { 074 // alpha characters 075 for (int i = 'a'; i <= 'z'; i++) { 076 WWW_FORM_URL_SAFE.set(i); 077 } 078 for (int i = 'A'; i <= 'Z'; i++) { 079 WWW_FORM_URL_SAFE.set(i); 080 } 081 // numeric characters 082 for (int i = '0'; i <= '9'; i++) { 083 WWW_FORM_URL_SAFE.set(i); 084 } 085 // special chars 086 WWW_FORM_URL_SAFE.set('-'); 087 WWW_FORM_URL_SAFE.set('_'); 088 WWW_FORM_URL_SAFE.set('.'); 089 WWW_FORM_URL_SAFE.set('*'); 090 // blank to be replaced with + 091 WWW_FORM_URL_SAFE.set(' '); 092 093 // Create a copy in case anyone (ab)uses it 094 WWW_FORM_URL = (BitSet) WWW_FORM_URL_SAFE.clone(); 095 } 096 097 /** 098 * Decodes an array of URL safe 7-bit characters into an array of original bytes. Escaped characters are converted 099 * back to their original representation. 100 * 101 * @param bytes 102 * array of URL safe characters. 103 * @return array of original bytes. 104 * @throws DecoderException 105 * Thrown if URL decoding is unsuccessful. 106 */ 107 public static final byte[] decodeUrl(final byte[] bytes) throws DecoderException { 108 if (bytes == null) { 109 return null; 110 } 111 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 112 for (int i = 0; i < bytes.length; i++) { 113 final int b = bytes[i]; 114 if (b == PLUS_CHAR) { 115 buffer.write(' '); 116 } else if (b == ESCAPE_CHAR) { 117 try { 118 final int u = Utils.digit16(bytes[++i]); 119 final int l = Utils.digit16(bytes[++i]); 120 buffer.write((char) ((u << 4) + l)); 121 } catch (final ArrayIndexOutOfBoundsException e) { 122 throw new DecoderException("Invalid URL encoding: ", e); 123 } 124 } else { 125 buffer.write(b); 126 } 127 } 128 return buffer.toByteArray(); 129 } 130 131 /** 132 * Encodes an array of bytes into an array of URL safe 7-bit characters. Unsafe characters are escaped. 133 * The characters {@code %} and {@code +} are always escaped because {@link #decodeUrl(byte[])} 134 * treats them as URL-encoding syntax. 135 * 136 * @param urlsafe 137 * bitset of characters deemed URL safe, except for {@code %} and {@code +}. 138 * @param bytes 139 * array of bytes to convert to URL safe characters. 140 * @return array of bytes containing URL safe characters. 141 */ 142 public static final byte[] encodeUrl(BitSet urlsafe, final byte[] bytes) { 143 if (bytes == null) { 144 return null; 145 } 146 if (urlsafe == null) { 147 urlsafe = WWW_FORM_URL_SAFE; 148 } 149 150 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 151 for (final byte c : bytes) { 152 int b = c; 153 if (b < 0) { 154 b = 256 + b; 155 } 156 if (urlsafe.get(b) && b != ESCAPE_CHAR && b != PLUS_CHAR) { 157 if (b == ' ') { 158 b = PLUS_CHAR; 159 } 160 buffer.write(b); 161 } else { 162 buffer.write(ESCAPE_CHAR); 163 final char hex1 = Utils.hexChar(b >> 4); 164 final char hex2 = Utils.hexChar(b); 165 buffer.write(hex1); 166 buffer.write(hex2); 167 } 168 } 169 return buffer.toByteArray(); 170 } 171 172 /** 173 * The default charset used for string decoding and encoding. 174 * 175 * @deprecated TODO: This field will be changed to a private final Charset in 2.0. (CODEC-126) 176 */ 177 @Deprecated 178 protected volatile String charset; // added volatile: see CODEC-232 179 180 /** 181 * Default constructor. 182 */ 183 public URLCodec() { 184 this(CharEncoding.UTF_8); 185 } 186 187 /** 188 * Constructs a new instance for the selection of a default charset. 189 * 190 * @param charset The default string charset to use. 191 */ 192 public URLCodec(final String charset) { 193 this.charset = charset; 194 } 195 196 /** 197 * Decodes an array of URL safe 7-bit characters into an array of original bytes. Escaped characters are converted 198 * back to their original representation. 199 * 200 * @param bytes 201 * array of URL safe characters. 202 * @return array of original bytes. 203 * @throws DecoderException 204 * Thrown if URL decoding is unsuccessful. 205 */ 206 @Override 207 public byte[] decode(final byte[] bytes) throws DecoderException { 208 return decodeUrl(bytes); 209 } 210 211 /** 212 * Decodes a URL safe object into its original form. Escaped characters are converted back to their original 213 * representation. 214 * 215 * @param obj 216 * URL safe object to convert into its original form. 217 * @return original object. 218 * @throws DecoderException 219 * Thrown if the argument is not a {@code String} or {@code byte[]}. Thrown if a failure 220 * condition is encountered during the decode process. 221 */ 222 @Override 223 public Object decode(final Object obj) throws DecoderException { 224 if (obj == null) { 225 return null; 226 } 227 if (obj instanceof byte[]) { 228 return decode((byte[]) obj); 229 } 230 if (obj instanceof String) { 231 return decode((String) obj); 232 } 233 throw new DecoderException("Objects of type " + obj.getClass().getName() + " cannot be URL decoded"); 234 } 235 236 /** 237 * Decodes a URL safe string into its original form using the default string charset. Escaped characters are 238 * converted back to their original representation. 239 * 240 * @param str 241 * URL safe string to convert into its original form. 242 * @return original string. 243 * @throws DecoderException 244 * Thrown if URL decoding is unsuccessful. 245 * @see #getDefaultCharset() 246 */ 247 @Override 248 public String decode(final String str) throws DecoderException { 249 if (str == null) { 250 return null; 251 } 252 try { 253 return decode(str, getDefaultCharset()); 254 } catch (final UnsupportedEncodingException e) { 255 throw new DecoderException(e.getMessage(), e); 256 } 257 } 258 259 /** 260 * Decodes a URL safe string into its original form using the specified encoding. Escaped characters are converted 261 * back to their original representation. 262 * 263 * @param str 264 * URL safe string to convert into its original form. 265 * @param charsetName 266 * the original string charset. 267 * @return original string. 268 * @throws DecoderException 269 * Thrown if URL decoding is unsuccessful. 270 * @throws UnsupportedEncodingException 271 * Thrown if charset is not supported. 272 */ 273 public String decode(final String str, final String charsetName) 274 throws DecoderException, UnsupportedEncodingException { 275 if (str == null) { 276 return null; 277 } 278 return new String(decode(StringUtils.getBytesUsAscii(str)), charsetName); 279 } 280 281 /** 282 * Encodes an array of bytes into an array of URL safe 7-bit characters. Unsafe characters are escaped. 283 * 284 * @param bytes 285 * array of bytes to convert to URL safe characters. 286 * @return array of bytes containing URL safe characters. 287 */ 288 @Override 289 public byte[] encode(final byte[] bytes) { 290 return encodeUrl(WWW_FORM_URL_SAFE, bytes); 291 } 292 293 /** 294 * Encodes an object into its URL safe form. Unsafe characters are escaped. 295 * 296 * @param obj 297 * string to convert to a URL safe form. 298 * @return URL safe object. 299 * @throws EncoderException 300 * Thrown if URL encoding is not applicable to objects of this type or if encoding is unsuccessful. 301 */ 302 @Override 303 public Object encode(final Object obj) throws EncoderException { 304 if (obj == null) { 305 return null; 306 } 307 if (obj instanceof byte[]) { 308 return encode((byte[]) obj); 309 } 310 if (obj instanceof String) { 311 return encode((String) obj); 312 } 313 throw new EncoderException("Objects of type " + obj.getClass().getName() + " cannot be URL encoded"); 314 } 315 316 /** 317 * Encodes a string into its URL safe form using the default string charset. Unsafe characters are escaped. 318 * 319 * @param str 320 * string to convert to a URL safe form. 321 * @return URL safe string. 322 * @throws EncoderException 323 * Thrown if URL encoding is unsuccessful. 324 * @see #getDefaultCharset() 325 */ 326 @Override 327 public String encode(final String str) throws EncoderException { 328 if (str == null) { 329 return null; 330 } 331 try { 332 return encode(str, getDefaultCharset()); 333 } catch (final UnsupportedEncodingException e) { 334 throw new EncoderException(e.getMessage(), e); 335 } 336 } 337 338 /** 339 * Encodes a string into its URL safe form using the specified string charset. Unsafe characters are escaped. 340 * 341 * @param str 342 * string to convert to a URL safe form. 343 * @param charsetName 344 * the charset for str. 345 * @return URL safe string. 346 * @throws UnsupportedEncodingException 347 * Thrown if charset is not supported. 348 */ 349 public String encode(final String str, final String charsetName) throws UnsupportedEncodingException { 350 if (str == null) { 351 return null; 352 } 353 return StringUtils.newStringUsAscii(encode(str.getBytes(charsetName))); 354 } 355 356 /** 357 * The default charset used for string decoding and encoding. 358 * 359 * @return The default string charset. 360 */ 361 public String getDefaultCharset() { 362 return this.charset; 363 } 364 365 /** 366 * The {@code String} encoding used for decoding and encoding. 367 * 368 * @return The encoding. 369 * @deprecated Use {@link #getDefaultCharset()}, will be removed in 2.0. 370 */ 371 @Deprecated 372 public String getEncoding() { 373 return this.charset; 374 } 375 376}