1 /* 2 * Copyright 2014 Square Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package okio; 17 18 import java.io.EOFException; 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.io.ObjectInputStream; 22 import java.io.ObjectOutputStream; 23 import java.io.OutputStream; 24 import java.io.Serializable; 25 import java.lang.reflect.Field; 26 import java.security.MessageDigest; 27 import java.security.NoSuchAlgorithmException; 28 import java.util.Arrays; 29 30 import static okio.Util.arrayRangeEquals; 31 import static okio.Util.checkOffsetAndCount; 32 33 /** 34 * An immutable sequence of bytes. 35 * 36 * <p><strong>Full disclosure:</strong> this class provides untrusted input and 37 * output streams with raw access to the underlying byte array. A hostile 38 * stream implementation could keep a reference to the mutable byte string, 39 * violating the immutable guarantee of this class. For this reason a byte 40 * string's immutability guarantee cannot be relied upon for security in applets 41 * and other environments that run both trusted and untrusted code in the same 42 * process. 43 */ 44 public class ByteString implements Serializable { 45 static final char[] HEX_DIGITS = 46 { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 47 private static final long serialVersionUID = 1L; 48 49 /** A singleton empty {@code ByteString}. */ 50 public static final ByteString EMPTY = ByteString.of(); 51 52 final byte[] data; 53 transient int hashCode; // Lazily computed; 0 if unknown. 54 transient String utf8; // Lazily computed. 55 ByteString(byte[] data)56 ByteString(byte[] data) { 57 this.data = data; // Trusted internal constructor doesn't clone data. 58 } 59 60 /** 61 * Returns a new byte string containing a clone of the bytes of {@code data}. 62 */ of(byte... data)63 public static ByteString of(byte... data) { 64 if (data == null) throw new IllegalArgumentException("data == null"); 65 return new ByteString(data.clone()); 66 } 67 68 /** 69 * Returns a new byte string containing a copy of {@code byteCount} bytes of {@code data} starting 70 * at {@code offset}. 71 */ of(byte[] data, int offset, int byteCount)72 public static ByteString of(byte[] data, int offset, int byteCount) { 73 if (data == null) throw new IllegalArgumentException("data == null"); 74 checkOffsetAndCount(data.length, offset, byteCount); 75 76 byte[] copy = new byte[byteCount]; 77 System.arraycopy(data, offset, copy, 0, byteCount); 78 return new ByteString(copy); 79 } 80 81 /** Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. */ encodeUtf8(String s)82 public static ByteString encodeUtf8(String s) { 83 if (s == null) throw new IllegalArgumentException("s == null"); 84 ByteString byteString = new ByteString(s.getBytes(Util.UTF_8)); 85 byteString.utf8 = s; 86 return byteString; 87 } 88 89 /** Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. */ utf8()90 public String utf8() { 91 String result = utf8; 92 // We don't care if we double-allocate in racy code. 93 return result != null ? result : (utf8 = new String(data, Util.UTF_8)); 94 } 95 96 /** 97 * Returns this byte string encoded as <a 98 * href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a>. In violation of the 99 * RFC, the returned string does not wrap lines at 76 columns. 100 */ base64()101 public String base64() { 102 return Base64.encode(data); 103 } 104 105 /** Returns the MD5 hash of this byte string. */ md5()106 public ByteString md5() { 107 return digest("MD5"); 108 } 109 110 /** Returns the SHA-256 hash of this byte string. */ sha256()111 public ByteString sha256() { 112 return digest("SHA-256"); 113 } 114 digest(String digest)115 private ByteString digest(String digest) { 116 try { 117 return ByteString.of(MessageDigest.getInstance(digest).digest(data)); 118 } catch (NoSuchAlgorithmException e) { 119 throw new AssertionError(e); 120 } 121 } 122 123 /** 124 * Returns this byte string encoded as <a href="http://www.ietf.org/rfc/rfc4648.txt">URL-safe 125 * Base64</a>. 126 */ base64Url()127 public String base64Url() { 128 return Base64.encodeUrl(data); 129 } 130 131 /** 132 * Decodes the Base64-encoded bytes and returns their value as a byte string. 133 * Returns null if {@code base64} is not a Base64-encoded sequence of bytes. 134 */ decodeBase64(String base64)135 public static ByteString decodeBase64(String base64) { 136 if (base64 == null) throw new IllegalArgumentException("base64 == null"); 137 byte[] decoded = Base64.decode(base64); 138 return decoded != null ? new ByteString(decoded) : null; 139 } 140 141 /** Returns this byte string encoded in hexadecimal. */ hex()142 public String hex() { 143 char[] result = new char[data.length * 2]; 144 int c = 0; 145 for (byte b : data) { 146 result[c++] = HEX_DIGITS[(b >> 4) & 0xf]; 147 result[c++] = HEX_DIGITS[b & 0xf]; 148 } 149 return new String(result); 150 } 151 152 /** Decodes the hex-encoded bytes and returns their value a byte string. */ decodeHex(String hex)153 public static ByteString decodeHex(String hex) { 154 if (hex == null) throw new IllegalArgumentException("hex == null"); 155 if (hex.length() % 2 != 0) throw new IllegalArgumentException("Unexpected hex string: " + hex); 156 157 byte[] result = new byte[hex.length() / 2]; 158 for (int i = 0; i < result.length; i++) { 159 int d1 = decodeHexDigit(hex.charAt(i * 2)) << 4; 160 int d2 = decodeHexDigit(hex.charAt(i * 2 + 1)); 161 result[i] = (byte) (d1 + d2); 162 } 163 return of(result); 164 } 165 decodeHexDigit(char c)166 private static int decodeHexDigit(char c) { 167 if (c >= '0' && c <= '9') return c - '0'; 168 if (c >= 'a' && c <= 'f') return c - 'a' + 10; 169 if (c >= 'A' && c <= 'F') return c - 'A' + 10; 170 throw new IllegalArgumentException("Unexpected hex digit: " + c); 171 } 172 173 /** 174 * Reads {@code count} bytes from {@code in} and returns the result. 175 * 176 * @throws java.io.EOFException if {@code in} has fewer than {@code count} 177 * bytes to read. 178 */ read(InputStream in, int byteCount)179 public static ByteString read(InputStream in, int byteCount) throws IOException { 180 if (in == null) throw new IllegalArgumentException("in == null"); 181 if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); 182 183 byte[] result = new byte[byteCount]; 184 for (int offset = 0, read; offset < byteCount; offset += read) { 185 read = in.read(result, offset, byteCount - offset); 186 if (read == -1) throw new EOFException(); 187 } 188 return new ByteString(result); 189 } 190 191 /** 192 * Returns a byte string equal to this byte string, but with the bytes 'A' 193 * through 'Z' replaced with the corresponding byte in 'a' through 'z'. 194 * Returns this byte string if it contains no bytes in 'A' through 'Z'. 195 */ toAsciiLowercase()196 public ByteString toAsciiLowercase() { 197 // Search for an uppercase character. If we don't find one, return this. 198 for (int i = 0; i < data.length; i++) { 199 byte c = data[i]; 200 if (c < 'A' || c > 'Z') continue; 201 202 // If we reach this point, this string is not not lowercase. Create and 203 // return a new byte string. 204 byte[] lowercase = data.clone(); 205 lowercase[i++] = (byte) (c - ('A' - 'a')); 206 for (; i < lowercase.length; i++) { 207 c = lowercase[i]; 208 if (c < 'A' || c > 'Z') continue; 209 lowercase[i] = (byte) (c - ('A' - 'a')); 210 } 211 return new ByteString(lowercase); 212 } 213 return this; 214 } 215 216 /** 217 * Returns a byte string equal to this byte string, but with the bytes 'a' 218 * through 'z' replaced with the corresponding byte in 'A' through 'Z'. 219 * Returns this byte string if it contains no bytes in 'a' through 'z'. 220 */ toAsciiUppercase()221 public ByteString toAsciiUppercase() { 222 // Search for an lowercase character. If we don't find one, return this. 223 for (int i = 0; i < data.length; i++) { 224 byte c = data[i]; 225 if (c < 'a' || c > 'z') continue; 226 227 // If we reach this point, this string is not not uppercase. Create and 228 // return a new byte string. 229 byte[] lowercase = data.clone(); 230 lowercase[i++] = (byte) (c - ('a' - 'A')); 231 for (; i < lowercase.length; i++) { 232 c = lowercase[i]; 233 if (c < 'a' || c > 'z') continue; 234 lowercase[i] = (byte) (c - ('a' - 'A')); 235 } 236 return new ByteString(lowercase); 237 } 238 return this; 239 } 240 241 /** 242 * Returns a byte string that is a substring of this byte string, beginning at the specified 243 * index until the end of this string. Returns this byte string if {@code beginIndex} is 0. 244 */ substring(int beginIndex)245 public ByteString substring(int beginIndex) { 246 return substring(beginIndex, data.length); 247 } 248 249 /** 250 * Returns a byte string that is a substring of this byte string, beginning at the specified 251 * {@code beginIndex} and ends at the specified {@code endIndex}. Returns this byte string if 252 * {@code beginIndex} is 0 and {@code endIndex} is the length of this byte string. 253 */ substring(int beginIndex, int endIndex)254 public ByteString substring(int beginIndex, int endIndex) { 255 if (beginIndex < 0) throw new IllegalArgumentException("beginIndex < 0"); 256 if (endIndex > data.length) { 257 throw new IllegalArgumentException("endIndex > length(" + data.length + ")"); 258 } 259 260 int subLen = endIndex - beginIndex; 261 if (subLen < 0) throw new IllegalArgumentException("endIndex < beginIndex"); 262 263 if ((beginIndex == 0) && (endIndex == data.length)) { 264 return this; 265 } 266 267 byte[] copy = new byte[subLen]; 268 System.arraycopy(data, beginIndex, copy, 0, subLen); 269 return new ByteString(copy); 270 } 271 272 /** Returns the byte at {@code pos}. */ getByte(int pos)273 public byte getByte(int pos) { 274 return data[pos]; 275 } 276 277 /** 278 * Returns the number of bytes in this ByteString. 279 */ size()280 public int size() { 281 return data.length; 282 } 283 284 /** 285 * Returns a byte array containing a copy of the bytes in this {@code ByteString}. 286 */ toByteArray()287 public byte[] toByteArray() { 288 return data.clone(); 289 } 290 291 /** Writes the contents of this byte string to {@code out}. */ write(OutputStream out)292 public void write(OutputStream out) throws IOException { 293 if (out == null) throw new IllegalArgumentException("out == null"); 294 out.write(data); 295 } 296 297 /** Writes the contents of this byte string to {@code buffer}. */ write(Buffer buffer)298 void write(Buffer buffer) { 299 buffer.write(data, 0, data.length); 300 } 301 302 /** 303 * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of 304 * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is 305 * out of bounds. 306 */ rangeEquals(int offset, ByteString other, int otherOffset, int byteCount)307 public boolean rangeEquals(int offset, ByteString other, int otherOffset, int byteCount) { 308 return other.rangeEquals(otherOffset, this.data, offset, byteCount); 309 } 310 311 /** 312 * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of 313 * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is 314 * out of bounds. 315 */ rangeEquals(int offset, byte[] other, int otherOffset, int byteCount)316 public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) { 317 return offset <= data.length - byteCount 318 && otherOffset <= other.length - byteCount 319 && arrayRangeEquals(data, offset, other, otherOffset, byteCount); 320 } 321 equals(Object o)322 @Override public boolean equals(Object o) { 323 if (o == this) return true; 324 return o instanceof ByteString 325 && ((ByteString) o).size() == data.length 326 && ((ByteString) o).rangeEquals(0, data, 0, data.length); 327 } 328 hashCode()329 @Override public int hashCode() { 330 int result = hashCode; 331 return result != 0 ? result : (hashCode = Arrays.hashCode(data)); 332 } 333 toString()334 @Override public String toString() { 335 if (data.length == 0) { 336 return "ByteString[size=0]"; 337 } 338 339 if (data.length <= 16) { 340 return String.format("ByteString[size=%s data=%s]", data.length, hex()); 341 } 342 343 return String.format("ByteString[size=%s md5=%s]", data.length, md5().hex()); 344 } 345 readObject(ObjectInputStream in)346 private void readObject(ObjectInputStream in) throws IOException { 347 int dataLength = in.readInt(); 348 ByteString byteString = ByteString.read(in, dataLength); 349 try { 350 Field field = ByteString.class.getDeclaredField("data"); 351 field.setAccessible(true); 352 field.set(this, byteString.data); 353 } catch (NoSuchFieldException e) { 354 throw new AssertionError(); 355 } catch (IllegalAccessException e) { 356 throw new AssertionError(); 357 } 358 } 359 writeObject(ObjectOutputStream out)360 private void writeObject(ObjectOutputStream out) throws IOException { 361 out.writeInt(data.length); 362 out.write(data); 363 } 364 } 365