Home | History | Annotate | Download | only in okio
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 /**
     19  * @author Alexander Y. Kleymenov
     20  */
     21 package okio;
     22 
     23 import java.io.UnsupportedEncodingException;
     24 
     25 final class Base64 {
     26   private Base64() {
     27   }
     28 
     29   public static byte[] decode(String in) {
     30     // Ignore trailing '=' padding and whitespace from the input.
     31     int limit = in.length();
     32     for (; limit > 0; limit--) {
     33       char c = in.charAt(limit - 1);
     34       if (c != '=' && c != '\n' && c != '\r' && c != ' ' && c != '\t') {
     35         break;
     36       }
     37     }
     38 
     39     // If the input includes whitespace, this output array will be longer than necessary.
     40     byte[] out = new byte[(int) (limit * 6L / 8L)];
     41     int outCount = 0;
     42     int inCount = 0;
     43 
     44     int word = 0;
     45     for (int pos = 0; pos < limit; pos++) {
     46       char c = in.charAt(pos);
     47 
     48       int bits;
     49       if (c >= 'A' && c <= 'Z') {
     50         // char ASCII value
     51         //  A    65    0
     52         //  Z    90    25 (ASCII - 65)
     53         bits = c - 65;
     54       } else if (c >= 'a' && c <= 'z') {
     55         // char ASCII value
     56         //  a    97    26
     57         //  z    122   51 (ASCII - 71)
     58         bits = c - 71;
     59       } else if (c >= '0' && c <= '9') {
     60         // char ASCII value
     61         //  0    48    52
     62         //  9    57    61 (ASCII + 4)
     63         bits = c + 4;
     64       } else if (c == '+' || c == '-') {
     65         bits = 62;
     66       } else if (c == '/' || c == '_') {
     67         bits = 63;
     68       } else if (c == '\n' || c == '\r' || c == ' ' || c == '\t') {
     69         continue;
     70       } else {
     71         return null;
     72       }
     73 
     74       // Append this char's 6 bits to the word.
     75       word = (word << 6) | (byte) bits;
     76 
     77       // For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes.
     78       inCount++;
     79       if (inCount % 4 == 0) {
     80         out[outCount++] = (byte) (word >> 16);
     81         out[outCount++] = (byte) (word >> 8);
     82         out[outCount++] = (byte) word;
     83       }
     84     }
     85 
     86     int lastWordChars = inCount % 4;
     87     if (lastWordChars == 1) {
     88       // We read 1 char followed by "===". But 6 bits is a truncated byte! Fail.
     89       return null;
     90     } else if (lastWordChars == 2) {
     91       // We read 2 chars followed by "==". Emit 1 byte with 8 of those 12 bits.
     92       word = word << 12;
     93       out[outCount++] = (byte) (word >> 16);
     94     } else if (lastWordChars == 3) {
     95       // We read 3 chars, followed by "=". Emit 2 bytes for 16 of those 18 bits.
     96       word = word << 6;
     97       out[outCount++] = (byte) (word >> 16);
     98       out[outCount++] = (byte) (word >> 8);
     99     }
    100 
    101     // If we sized our out array perfectly, we're done.
    102     if (outCount == out.length) return out;
    103 
    104     // Copy the decoded bytes to a new, right-sized array.
    105     byte[] prefix = new byte[outCount];
    106     System.arraycopy(out, 0, prefix, 0, outCount);
    107     return prefix;
    108   }
    109 
    110   private static final byte[] MAP = new byte[] {
    111       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
    112       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
    113       'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
    114       '5', '6', '7', '8', '9', '+', '/'
    115   };
    116 
    117   private static final byte[] URL_MAP = new byte[] {
    118       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
    119       'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
    120       'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
    121       '5', '6', '7', '8', '9', '-', '_'
    122   };
    123 
    124   public static String encode(byte[] in) {
    125     return encode(in, MAP);
    126   }
    127 
    128   public static String encodeUrl(byte[] in) {
    129     return encode(in, URL_MAP);
    130   }
    131 
    132   private static String encode(byte[] in, byte[] map) {
    133     int length = (in.length + 2) * 4 / 3;
    134     byte[] out = new byte[length];
    135     int index = 0, end = in.length - in.length % 3;
    136     for (int i = 0; i < end; i += 3) {
    137       out[index++] = map[(in[i] & 0xff) >> 2];
    138       out[index++] = map[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)];
    139       out[index++] = map[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)];
    140       out[index++] = map[(in[i + 2] & 0x3f)];
    141     }
    142     switch (in.length % 3) {
    143       case 1:
    144         out[index++] = map[(in[end] & 0xff) >> 2];
    145         out[index++] = map[(in[end] & 0x03) << 4];
    146         out[index++] = '=';
    147         out[index++] = '=';
    148         break;
    149       case 2:
    150         out[index++] = map[(in[end] & 0xff) >> 2];
    151         out[index++] = map[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)];
    152         out[index++] = map[((in[end + 1] & 0x0f) << 2)];
    153         out[index++] = '=';
    154         break;
    155     }
    156     try {
    157       return new String(out, 0, index, "US-ASCII");
    158     } catch (UnsupportedEncodingException e) {
    159       throw new AssertionError(e);
    160     }
    161   }
    162 }
    163