• 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>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 
ByteString(byte[] data)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    */
of(byte... data)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    */
of(byte[] data, int offset, int byteCount)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}. */
encodeUtf8(String 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}. */
utf8()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    */
base64()103   public String base64() {
104     return Base64.encode(data);
105   }
106 
107   /** Returns the MD5 hash of this byte string. */
md5()108   public ByteString md5() {
109     return digest("MD5");
110   }
111 
112   /** Returns the SHA-256 hash of this byte string. */
sha256()113   public ByteString sha256() {
114     return digest("SHA-256");
115   }
116 
digest(String digest)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    */
base64Url()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    */
decodeBase64(String base64)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. */
hex()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. */
decodeHex(String hex)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 
decodeHexDigit(char c)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    */
read(InputStream in, int byteCount)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    */
toAsciiLowercase()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    */
toAsciiUppercase()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    */
substring(int beginIndex)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    */
substring(int beginIndex, int endIndex)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}. */
getByte(int pos)275   public byte getByte(int pos) {
276     return data[pos];
277   }
278 
279   /**
280    * Returns the number of bytes in this ByteString.
281    */
size()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    */
toByteArray()289   public byte[] toByteArray() {
290     return data.clone();
291   }
292 
293   /** Writes the contents of this byte string to {@code out}. */
write(OutputStream 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}. */
write(Buffer 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    */
rangeEquals(int offset, ByteString other, int otherOffset, int byteCount)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    */
rangeEquals(int offset, byte[] other, int otherOffset, int byteCount)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 
equals(Object o)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 
hashCode()331   @Override public int hashCode() {
332     int result = hashCode;
333     return result != 0 ? result : (hashCode = Arrays.hashCode(data));
334   }
335 
compareTo(ByteString byteString)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 
toString()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 
readObject(ObjectInputStream in)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 
writeObject(ObjectOutputStream out)375   private void writeObject(ObjectOutputStream out) throws IOException {
376     out.writeInt(data.length);
377     out.write(data);
378   }
379 }
380