1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.shiro.lang.codec; 20 21 import org.apache.shiro.lang.util.ByteSource; 22 23 import java.io.ByteArrayOutputStream; 24 import java.io.File; 25 import java.io.FileInputStream; 26 import java.io.FileNotFoundException; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.UnsupportedEncodingException; 30 31 /** 32 * Base abstract class that provides useful encoding and decoding operations, especially for character data. 33 * 34 * @since 0.9 35 */ 36 @SuppressWarnings("checkstyle:BooleanExpressionComplexity") 37 public abstract class CodecSupport { 38 39 /** 40 * Shiro's default preferred character encoding, equal to <b><code>UTF-8</code></b>. 41 */ 42 public static final String PREFERRED_ENCODING = "UTF-8"; 43 44 /** 45 * Converts the specified character array to a byte array using the Shiro's preferred encoding (UTF-8). 46 * <p/> 47 * This is a convenience method equivalent to calling the {@link #toBytes(String, String)} method with a 48 * a wrapping String and {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}, i.e. 49 * <p/> 50 * <code>toBytes( new String(chars), {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING} );</code> 51 * 52 * @param chars the character array to be converted to a byte array. 53 * @return the byte array of the UTF-8 encoded character array. 54 */ 55 public static byte[] toBytes(char[] chars) { 56 return toBytes(new String(chars), PREFERRED_ENCODING); 57 } 58 59 /** 60 * Converts the specified character array into a byte array using the specified character encoding. 61 * <p/> 62 * This is a convenience method equivalent to calling the {@link #toBytes(String, String)} method with a 63 * a wrapping String and the specified encoding, i.e. 64 * <p/> 65 * <code>toBytes( new String(chars), encoding );</code> 66 * 67 * @param chars the character array to be converted to a byte array 68 * @param encoding the character encoding to use to when converting to bytes. 69 * @return the bytes of the specified character array under the specified encoding. 70 * @throws CodecException if the JVM does not support the specified encoding. 71 */ 72 public static byte[] toBytes(char[] chars, String encoding) throws CodecException { 73 return toBytes(new String(chars), encoding); 74 } 75 76 /** 77 * Converts the specified source argument to a byte array with Shiro's 78 * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}. 79 * 80 * @param source the string to convert to a byte array. 81 * @return the bytes representing the specified string under the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}. 82 * @see #toBytes(String, String) 83 */ 84 public static byte[] toBytes(String source) { 85 return toBytes(source, PREFERRED_ENCODING); 86 } 87 88 /** 89 * Converts the specified source to a byte array via the specified encoding, throwing a 90 * {@link CodecException CodecException} if the encoding fails. 91 * 92 * @param source the source string to convert to a byte array. 93 * @param encoding the encoding to use to use. 94 * @return the byte array of the specified source with the given encoding. 95 * @throws CodecException if the JVM does not support the specified encoding. 96 */ 97 public static byte[] toBytes(String source, String encoding) throws CodecException { 98 try { 99 return source.getBytes(encoding); 100 } catch (UnsupportedEncodingException e) { 101 String msg = "Unable to convert source [" + source + "] to byte array using " 102 + "encoding '" + encoding + "'"; 103 throw new CodecException(msg, e); 104 } 105 } 106 107 /** 108 * Converts the specified byte array to a String using the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}. 109 * 110 * @param bytes the byte array to turn into a String. 111 * @return the specified byte array as an encoded String ({@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}). 112 * @see #toString(byte[], String) 113 */ 114 public static String toString(byte[] bytes) { 115 return toString(bytes, PREFERRED_ENCODING); 116 } 117 118 /** 119 * Converts the specified byte array to a String using the specified character encoding. This implementation 120 * does the same thing as <code>new {@link String#String(byte[], String) String(byte[], encoding)}</code>, but will 121 * wrap any {@link UnsupportedEncodingException} with a nicer runtime {@link CodecException}, allowing you to 122 * decide whether or not you want to catch the exception or let it propagate. 123 * 124 * @param bytes the byte array to convert to a String 125 * @param encoding the character encoding used to encode the String. 126 * @return the specified byte array as an encoded String 127 * @throws CodecException if the JVM does not support the specified encoding. 128 */ 129 public static String toString(byte[] bytes, String encoding) throws CodecException { 130 try { 131 return new String(bytes, encoding); 132 } catch (UnsupportedEncodingException e) { 133 String msg = "Unable to convert byte array to String with encoding '" + encoding + "'."; 134 throw new CodecException(msg, e); 135 } 136 } 137 138 /** 139 * Returns the specified byte array as a character array using the 140 * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}. 141 * 142 * @param bytes the byte array to convert to a char array 143 * @return the specified byte array encoded as a character array ({@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}). 144 * @see #toChars(byte[], String) 145 */ 146 public static char[] toChars(byte[] bytes) { 147 return toChars(bytes, PREFERRED_ENCODING); 148 } 149 150 /** 151 * Converts the specified byte array to a character array using the specified character encoding. 152 * <p/> 153 * Effectively calls <code>{@link #toString(byte[], String) toString(bytes,encoding)} 154 * .{@link String#toCharArray() toCharArray()};</code> 155 * 156 * @param bytes the byte array to convert to a String 157 * @param encoding the character encoding used to encode the bytes. 158 * @return the specified byte array as an encoded char array 159 * @throws CodecException if the JVM does not support the specified encoding. 160 */ 161 public static char[] toChars(byte[] bytes, String encoding) throws CodecException { 162 return toString(bytes, encoding).toCharArray(); 163 } 164 165 /** 166 * Returns {@code true} if the specified object can be easily converted to bytes by instances of this class, 167 * {@code false} otherwise. 168 * <p/> 169 * The default implementation returns {@code true} IFF the specified object is an instance of one of the following 170 * types: 171 * <ul> 172 * <li>{@code byte[]}</li> 173 * <li>{@code char[]}</li> 174 * <li>{@link ByteSource}</li> 175 * <li>{@link String}</li> 176 * <li>{@link File}</li> 177 * </li>{@link InputStream}</li> 178 * </ul> 179 * 180 * @param o the object to test to see if it can be easily converted to a byte array 181 * @return {@code true} if the specified object can be easily converted to bytes by instances of this class, 182 * {@code false} otherwise. 183 * @since 1.0 184 */ 185 protected boolean isByteSource(Object o) { 186 return o instanceof byte[] || o instanceof char[] || o instanceof String 187 || o instanceof ByteSource || o instanceof File || o instanceof InputStream; 188 } 189 190 /** 191 * Converts the specified Object into a byte array. 192 * <p/> 193 * If the argument is a {@code byte[]}, {@code char[]}, {@link ByteSource}, {@link String}, {@link File}, or 194 * {@link InputStream}, it will be converted automatically and returned.} 195 * <p/> 196 * If the argument is anything other than these types, it is passed to the 197 * {@link #objectToBytes(Object) objectToBytes} method which must be overridden by subclasses. 198 * 199 * @param object the Object to convert into a byte array 200 * @return a byte array representation of the Object argument. 201 */ 202 protected byte[] toBytes(Object object) { 203 if (object == null) { 204 String msg = "Argument for byte conversion cannot be null."; 205 throw new IllegalArgumentException(msg); 206 } 207 if (object instanceof byte[]) { 208 return (byte[]) object; 209 } else if (object instanceof ByteSource) { 210 return ((ByteSource) object).getBytes(); 211 } else if (object instanceof char[]) { 212 return toBytes((char[]) object); 213 } else if (object instanceof String) { 214 return toBytes((String) object); 215 } else if (object instanceof File) { 216 return toBytes((File) object); 217 } else if (object instanceof InputStream) { 218 return toBytes((InputStream) object); 219 } else { 220 return objectToBytes(object); 221 } 222 } 223 224 /** 225 * Converts the specified Object into a String. 226 * <p/> 227 * If the argument is a {@code byte[]} or {@code char[]} it will be converted to a String using the 228 * {@link #PREFERRED_ENCODING}. If a String, it will be returned as is. 229 * <p/> 230 * If the argument is anything other than these three types, it is passed to the 231 * {@link #objectToString(Object) objectToString} method. 232 * 233 * @param o the Object to convert into a byte array 234 * @return a byte array representation of the Object argument. 235 */ 236 protected String toString(Object o) { 237 if (o == null) { 238 String msg = "Argument for String conversion cannot be null."; 239 throw new IllegalArgumentException(msg); 240 } 241 if (o instanceof byte[]) { 242 return toString((byte[]) o); 243 } else if (o instanceof char[]) { 244 return new String((char[]) o); 245 } else if (o instanceof String) { 246 return (String) o; 247 } else { 248 return objectToString(o); 249 } 250 } 251 252 protected byte[] toBytes(File file) { 253 if (file == null) { 254 throw new IllegalArgumentException("File argument cannot be null."); 255 } 256 try { 257 return toBytes(new FileInputStream(file)); 258 } catch (FileNotFoundException e) { 259 String msg = "Unable to acquire InputStream for file [" + file + "]"; 260 throw new CodecException(msg, e); 261 } 262 } 263 264 /** 265 * Converts the specified {@link InputStream InputStream} into a byte array. 266 * 267 * @param in the InputStream to convert to a byte array 268 * @return the bytes of the input stream 269 * @throws IllegalArgumentException if the {@code InputStream} argument is {@code null}. 270 * @throws CodecException if there is any problem reading from the {@link InputStream}. 271 * @since 1.0 272 */ 273 protected byte[] toBytes(InputStream in) { 274 if (in == null) { 275 throw new IllegalArgumentException("InputStream argument cannot be null."); 276 } 277 final int bufferSize = 512; 278 ByteArrayOutputStream out = new ByteArrayOutputStream(bufferSize); 279 byte[] buffer = new byte[bufferSize]; 280 int bytesRead; 281 try { 282 while ((bytesRead = in.read(buffer)) != -1) { 283 out.write(buffer, 0, bytesRead); 284 } 285 return out.toByteArray(); 286 } catch (IOException ioe) { 287 throw new CodecException(ioe); 288 } finally { 289 try { 290 in.close(); 291 } catch (IOException ignored) { 292 } 293 try { 294 out.close(); 295 } catch (IOException ignored) { 296 } 297 } 298 } 299 300 /** 301 * Default implementation throws a CodecException immediately since it can't infer how to convert the Object 302 * to a byte array. This method must be overridden by subclasses if anything other than the three default 303 * types (listed in the {@link #toBytes(Object) toBytes(Object)} JavaDoc) are to be converted to a byte array. 304 * 305 * @param o the Object to convert to a byte array. 306 * @return a byte array representation of the Object argument. 307 */ 308 protected byte[] objectToBytes(Object o) { 309 String msg = "The " + getClass().getName() + " implementation only supports conversion to " 310 + "byte[] if the source is of type byte[], char[], String, " + ByteSource.class.getName() 311 + " File or InputStream. The instance provided as a method " 312 + "argument is of type [" + o.getClass().getName() + "]. If you would like to convert " 313 + "this argument type to a byte[], you can 1) convert the argument to one of the supported types " 314 + "yourself and then use that as the method argument or 2) subclass " + getClass().getName() 315 + "and override the objectToBytes(Object o) method."; 316 throw new CodecException(msg); 317 } 318 319 /** 320 * Default implementation merely returns <code>objectArgument.toString()</code>. Subclasses can override this 321 * method for different mechanisms of converting an object to a String. 322 * 323 * @param o the Object to convert to a byte array. 324 * @return a String representation of the Object argument. 325 */ 326 protected String objectToString(Object o) { 327 return o.toString(); 328 } 329 }