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