Home | History | Annotate | Download | only in jutf7
      1 /* ====================================================================
      2  * Copyright (c) 2006 J.T. Beetstra
      3  *
      4  * Permission is hereby granted, free of charge, to any person obtaining
      5  * a copy of this software and associated documentation files (the
      6  * "Software"), to deal in the Software without restriction, including
      7  * without limitation the rights to use, copy, modify, merge, publish,
      8  * distribute, sublicense, and/or sell copies of the Software, and to
      9  * permit persons to whom the Software is furnished to do so, subject to
     10  * the following conditions:
     11  *
     12  * The above copyright notice and this permission notice shall be
     13  * included in all copies or substantial portions of the Software.
     14  *
     15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
     19  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     20  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     21  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     22  * ====================================================================
     23  */
     24 
     25 package com.beetstra.jutf7;
     26 
     27 import java.nio.ByteBuffer;
     28 import java.nio.CharBuffer;
     29 import java.nio.charset.CharsetDecoder;
     30 import java.nio.charset.CoderResult;
     31 
     32 /**
     33  * <p>
     34  * The CharsetDecoder used to decode both variants of the UTF-7 charset and the
     35  * modified-UTF-7 charset.
     36  * </p>
     37  *
     38  * @author Jaap Beetstra
     39  */
     40 class UTF7StyleCharsetDecoder extends CharsetDecoder {
     41     private final Base64Util base64;
     42     private final byte shift;
     43     private final byte unshift;
     44     private final boolean strict;
     45     private boolean base64mode;
     46     private int bitsRead;
     47     private int tempChar;
     48     private boolean justShifted;
     49     private boolean justUnshifted;
     50 
     51     UTF7StyleCharsetDecoder(UTF7StyleCharset cs, Base64Util base64, boolean strict) {
     52         super(cs, 0.6f, 1.0f);
     53         this.base64 = base64;
     54         this.strict = strict;
     55         this.shift = cs.shift();
     56         this.unshift = cs.unshift();
     57     }
     58 
     59     /*
     60      * (non-Javadoc)
     61      * @see java.nio.charset.CharsetDecoder#decodeLoop(java.nio.ByteBuffer,
     62      * java.nio.CharBuffer)
     63      */
     64     protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
     65         while (in.hasRemaining()) {
     66             byte b = in.get();
     67             if (base64mode) {
     68                 if (b == unshift) {
     69                     if (base64bitsWaiting())
     70                         return malformed(in);
     71                     if (justShifted) {
     72                         if (!out.hasRemaining())
     73                             return overflow(in);
     74                         out.put((char)shift);
     75                     } else
     76                         justUnshifted = true;
     77                     setUnshifted();
     78                 } else {
     79                     if (!out.hasRemaining())
     80                         return overflow(in);
     81                     CoderResult result = handleBase64(in, out, b);
     82                     if (result != null)
     83                         return result;
     84                 }
     85                 justShifted = false;
     86             } else {
     87                 if (b == shift) {
     88                     base64mode = true;
     89                     if (justUnshifted && strict)
     90                         return malformed(in);
     91                     justShifted = true;
     92                     continue;
     93                 }
     94                 if (!out.hasRemaining())
     95                     return overflow(in);
     96                 out.put((char)b);
     97                 justUnshifted = false;
     98             }
     99         }
    100         return CoderResult.UNDERFLOW;
    101     }
    102 
    103     private CoderResult overflow(ByteBuffer in) {
    104         in.position(in.position() - 1);
    105         return CoderResult.OVERFLOW;
    106     }
    107 
    108     /**
    109      * <p>
    110      * Decodes a byte in <i>base 64 mode</i>. Will directly write a character to
    111      * the output buffer if completed.
    112      * </p>
    113      *
    114      * @param in The input buffer
    115      * @param out The output buffer
    116      * @param lastRead Last byte read from the input buffer
    117      * @return CoderResult.malformed if a non-base 64 character was encountered
    118      *         in strict mode, null otherwise
    119      */
    120     private CoderResult handleBase64(ByteBuffer in, CharBuffer out, byte lastRead) {
    121         CoderResult result = null;
    122         int sextet = base64.getSextet(lastRead);
    123         if (sextet >= 0) {
    124             bitsRead += 6;
    125             if (bitsRead < 16) {
    126                 tempChar += sextet << (16 - bitsRead);
    127             } else {
    128                 bitsRead -= 16;
    129                 tempChar += sextet >> (bitsRead);
    130                 out.put((char)tempChar);
    131                 tempChar = (sextet << (16 - bitsRead)) & 0xFFFF;
    132             }
    133         } else {
    134             if (strict)
    135                 return malformed(in);
    136             out.put((char)lastRead);
    137             if (base64bitsWaiting())
    138                 result = malformed(in);
    139             setUnshifted();
    140         }
    141         return result;
    142     }
    143 
    144     /*
    145      * (non-Javadoc)
    146      * @see java.nio.charset.CharsetDecoder#implFlush(java.nio.CharBuffer)
    147      */
    148     protected CoderResult implFlush(CharBuffer out) {
    149         if ((base64mode && strict) || base64bitsWaiting())
    150             return CoderResult.malformedForLength(1);
    151         return CoderResult.UNDERFLOW;
    152     }
    153 
    154     /*
    155      * (non-Javadoc)
    156      * @see java.nio.charset.CharsetDecoder#implReset()
    157      */
    158     protected void implReset() {
    159         setUnshifted();
    160         justUnshifted = false;
    161     }
    162 
    163     /**
    164      * <p>
    165      * Resets the input buffer position to just before the last byte read, and
    166      * returns a result indicating to skip the last byte.
    167      * </p>
    168      *
    169      * @param in The input buffer
    170      * @return CoderResult.malformedForLength(1);
    171      */
    172     private CoderResult malformed(ByteBuffer in) {
    173         in.position(in.position() - 1);
    174         return CoderResult.malformedForLength(1);
    175     }
    176 
    177     /**
    178      * @return True if there are base64 encoded characters waiting to be written
    179      */
    180     private boolean base64bitsWaiting() {
    181         return tempChar != 0 || bitsRead >= 6;
    182     }
    183 
    184     /**
    185      * <p>
    186      * Updates internal state to reflect the decoder is no longer in <i>base 64
    187      * mode</i>
    188      * </p>
    189      */
    190     private void setUnshifted() {
    191         base64mode = false;
    192         bitsRead = 0;
    193         tempChar = 0;
    194     }
    195 }
    196