• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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