Home | History | Annotate | Download | only in okio
      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