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