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 */ 16package okio; 17 18import java.io.EOFException; 19import java.io.IOException; 20import java.io.InputStream; 21import java.io.OutputStream; 22import java.security.MessageDigest; 23import java.security.NoSuchAlgorithmException; 24import java.util.Arrays; 25 26/** 27 * An immutable sequence of bytes. 28 * 29 * <p><strong>Full disclosure:</strong> this class provides untrusted input and 30 * output streams with raw access to the underlying byte array. A hostile 31 * stream implementation could keep a reference to the mutable byte string, 32 * violating the immutable guarantee of this class. For this reason a byte 33 * string's immutability guarantee cannot be relied upon for security in applets 34 * and other environments that run both trusted and untrusted code in the same 35 * process. 36 */ 37public final class ByteString { 38 private static final char[] HEX_DIGITS = 39 { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 40 41 /** A singleton empty {@code ByteString}. */ 42 public static final ByteString EMPTY = ByteString.of(); 43 44 final byte[] data; 45 private transient int hashCode; // Lazily computed; 0 if unknown. 46 private transient String utf8; // Lazily computed. 47 48 ByteString(byte[] data) { 49 this.data = data; // Trusted internal constructor doesn't clone data. 50 } 51 52 /** 53 * Returns a new byte string containing a clone of the bytes of {@code data}. 54 */ 55 public static ByteString of(byte... data) { 56 return new ByteString(data.clone()); 57 } 58 59 /** Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. */ 60 public static ByteString encodeUtf8(String s) { 61 ByteString byteString = new ByteString(s.getBytes(Util.UTF_8)); 62 byteString.utf8 = s; 63 return byteString; 64 } 65 66 /** Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. */ 67 public String utf8() { 68 String result = utf8; 69 // We don't care if we double-allocate in racy code. 70 return result != null ? result : (utf8 = new String(data, Util.UTF_8)); 71 } 72 73 /** 74 * Returns this byte string encoded as <a 75 * href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a>. In violation of the 76 * RFC, the returned string does not wrap lines at 76 columns. 77 */ 78 public String base64() { 79 return Base64.encode(data); 80 } 81 82 /** 83 * Decodes the Base64-encoded bytes and returns their value as a byte string. 84 * Returns null if {@code base64} is not a Base64-encoded sequence of bytes. 85 */ 86 public static ByteString decodeBase64(String base64) { 87 byte[] decoded = Base64.decode(base64); 88 return decoded != null ? new ByteString(decoded) : null; 89 } 90 91 /** Returns this byte string encoded in hexadecimal. */ 92 public String hex() { 93 char[] result = new char[data.length * 2]; 94 int c = 0; 95 for (byte b : data) { 96 result[c++] = HEX_DIGITS[(b >> 4) & 0xf]; 97 result[c++] = HEX_DIGITS[b & 0xf]; 98 } 99 return new String(result); 100 } 101 102 /** Decodes the hex-encoded bytes and returns their value a byte string. */ 103 public static ByteString decodeHex(String hex) { 104 if (hex.length() % 2 != 0) throw new IllegalArgumentException("Unexpected hex string: " + hex); 105 106 byte[] result = new byte[hex.length() / 2]; 107 for (int i = 0; i < result.length; i++) { 108 int d1 = decodeHexDigit(hex.charAt(i * 2)) << 4; 109 int d2 = decodeHexDigit(hex.charAt(i * 2 + 1)); 110 result[i] = (byte) (d1 + d2); 111 } 112 return of(result); 113 } 114 115 private static int decodeHexDigit(char c) { 116 if (c >= '0' && c <= '9') return c - '0'; 117 if (c >= 'a' && c <= 'f') return c - 'a' + 10; 118 if (c >= 'A' && c <= 'F') return c - 'A' + 10; 119 throw new IllegalArgumentException("Unexpected hex digit: " + c); 120 } 121 122 /** 123 * Reads {@code count} bytes from {@code in} and returns the result. 124 * 125 * @throws java.io.EOFException if {@code in} has fewer than {@code count} 126 * bytes to read. 127 */ 128 public static ByteString read(InputStream in, int byteCount) throws IOException { 129 byte[] result = new byte[byteCount]; 130 for (int offset = 0, read; offset < byteCount; offset += read) { 131 read = in.read(result, offset, byteCount - offset); 132 if (read == -1) throw new EOFException(); 133 } 134 return new ByteString(result); 135 } 136 137 /** 138 * Returns a byte string equal to this byte string, but with the bytes 'A' 139 * through 'Z' replaced with the corresponding byte in 'a' through 'z'. 140 * Returns this byte string if it contains no bytes in 'A' through 'Z'. 141 */ 142 public ByteString toAsciiLowercase() { 143 // Search for an uppercase character. If we don't find one, return this. 144 for (int i = 0; i < data.length; i++) { 145 byte c = data[i]; 146 if (c < 'A' || c > 'Z') continue; 147 148 // If we reach this point, this string is not not lowercase. Create and 149 // return a new byte string. 150 byte[] lowercase = data.clone(); 151 lowercase[i++] = (byte) (c - ('A' - 'a')); 152 for (; i < lowercase.length; i++) { 153 c = lowercase[i]; 154 if (c < 'A' || c > 'Z') continue; 155 lowercase[i] = (byte) (c - ('A' - 'a')); 156 } 157 return new ByteString(lowercase); 158 } 159 return this; 160 } 161 162 /** Returns the byte at {@code pos}. */ 163 public byte getByte(int pos) { 164 return data[pos]; 165 } 166 167 /** 168 * Returns the number of bytes in this ByteString. 169 */ 170 public int size() { 171 return data.length; 172 } 173 174 /** 175 * Returns a byte array containing a copy of the bytes in this {@code ByteString}. 176 */ 177 public byte[] toByteArray() { 178 return data.clone(); 179 } 180 181 /** Writes the contents of this byte string to {@code out}. */ 182 public void write(OutputStream out) throws IOException { 183 out.write(data); 184 } 185 186 @Override public boolean equals(Object o) { 187 return o == this || o instanceof ByteString && Arrays.equals(((ByteString) o).data, data); 188 } 189 190 @Override public int hashCode() { 191 int result = hashCode; 192 return result != 0 ? result : (hashCode = Arrays.hashCode(data)); 193 } 194 195 @Override public String toString() { 196 if (data.length == 0) { 197 return "ByteString[size=0]"; 198 } 199 200 if (data.length <= 16) { 201 return String.format("ByteString[size=%s data=%s]", data.length, hex()); 202 } 203 204 try { 205 return String.format("ByteString[size=%s md5=%s]", data.length, 206 ByteString.of(MessageDigest.getInstance("MD5").digest(data)).hex()); 207 } catch (NoSuchAlgorithmException e) { 208 throw new AssertionError(); 209 } 210 } 211} 212