/* ==================================================================== | |
* Copyright (c) 2006 J.T. Beetstra | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files (the | |
* "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be | |
* included in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
* ==================================================================== | |
*/ | |
package com.beetstra.jutf7; | |
import java.nio.ByteBuffer; | |
import java.nio.CharBuffer; | |
import java.nio.charset.CharsetDecoder; | |
import java.nio.charset.CoderResult; | |
/** | |
* <p> | |
* The CharsetDecoder used to decode both variants of the UTF-7 charset and the | |
* modified-UTF-7 charset. | |
* </p> | |
* | |
* @author Jaap Beetstra | |
*/ | |
class UTF7StyleCharsetDecoder extends CharsetDecoder { | |
private final Base64Util base64; | |
private final byte shift; | |
private final byte unshift; | |
private final boolean strict; | |
private boolean base64mode; | |
private int bitsRead; | |
private int tempChar; | |
private boolean justShifted; | |
private boolean justUnshifted; | |
UTF7StyleCharsetDecoder(UTF7StyleCharset cs, Base64Util base64, boolean strict) { | |
super(cs, 0.6f, 1.0f); | |
this.base64 = base64; | |
this.strict = strict; | |
this.shift = cs.shift(); | |
this.unshift = cs.unshift(); | |
} | |
/* | |
* (non-Javadoc) | |
* @see java.nio.charset.CharsetDecoder#decodeLoop(java.nio.ByteBuffer, | |
* java.nio.CharBuffer) | |
*/ | |
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { | |
while (in.hasRemaining()) { | |
byte b = in.get(); | |
if (base64mode) { | |
if (b == unshift) { | |
if (base64bitsWaiting()) | |
return malformed(in); | |
if (justShifted) { | |
if (!out.hasRemaining()) | |
return overflow(in); | |
out.put((char)shift); | |
} else | |
justUnshifted = true; | |
setUnshifted(); | |
} else { | |
if (!out.hasRemaining()) | |
return overflow(in); | |
CoderResult result = handleBase64(in, out, b); | |
if (result != null) | |
return result; | |
} | |
justShifted = false; | |
} else { | |
if (b == shift) { | |
base64mode = true; | |
if (justUnshifted && strict) | |
return malformed(in); | |
justShifted = true; | |
continue; | |
} | |
if (!out.hasRemaining()) | |
return overflow(in); | |
out.put((char)b); | |
justUnshifted = false; | |
} | |
} | |
return CoderResult.UNDERFLOW; | |
} | |
private CoderResult overflow(ByteBuffer in) { | |
in.position(in.position() - 1); | |
return CoderResult.OVERFLOW; | |
} | |
/** | |
* <p> | |
* Decodes a byte in <i>base 64 mode</i>. Will directly write a character to | |
* the output buffer if completed. | |
* </p> | |
* | |
* @param in The input buffer | |
* @param out The output buffer | |
* @param lastRead Last byte read from the input buffer | |
* @return CoderResult.malformed if a non-base 64 character was encountered | |
* in strict mode, null otherwise | |
*/ | |
private CoderResult handleBase64(ByteBuffer in, CharBuffer out, byte lastRead) { | |
CoderResult result = null; | |
int sextet = base64.getSextet(lastRead); | |
if (sextet >= 0) { | |
bitsRead += 6; | |
if (bitsRead < 16) { | |
tempChar += sextet << (16 - bitsRead); | |
} else { | |
bitsRead -= 16; | |
tempChar += sextet >> (bitsRead); | |
out.put((char)tempChar); | |
tempChar = (sextet << (16 - bitsRead)) & 0xFFFF; | |
} | |
} else { | |
if (strict) | |
return malformed(in); | |
out.put((char)lastRead); | |
if (base64bitsWaiting()) | |
result = malformed(in); | |
setUnshifted(); | |
} | |
return result; | |
} | |
/* | |
* (non-Javadoc) | |
* @see java.nio.charset.CharsetDecoder#implFlush(java.nio.CharBuffer) | |
*/ | |
protected CoderResult implFlush(CharBuffer out) { | |
if ((base64mode && strict) || base64bitsWaiting()) | |
return CoderResult.malformedForLength(1); | |
return CoderResult.UNDERFLOW; | |
} | |
/* | |
* (non-Javadoc) | |
* @see java.nio.charset.CharsetDecoder#implReset() | |
*/ | |
protected void implReset() { | |
setUnshifted(); | |
justUnshifted = false; | |
} | |
/** | |
* <p> | |
* Resets the input buffer position to just before the last byte read, and | |
* returns a result indicating to skip the last byte. | |
* </p> | |
* | |
* @param in The input buffer | |
* @return CoderResult.malformedForLength(1); | |
*/ | |
private CoderResult malformed(ByteBuffer in) { | |
in.position(in.position() - 1); | |
return CoderResult.malformedForLength(1); | |
} | |
/** | |
* @return True if there are base64 encoded characters waiting to be written | |
*/ | |
private boolean base64bitsWaiting() { | |
return tempChar != 0 || bitsRead >= 6; | |
} | |
/** | |
* <p> | |
* Updates internal state to reflect the decoder is no longer in <i>base 64 | |
* mode</i> | |
* </p> | |
*/ | |
private void setUnshifted() { | |
base64mode = false; | |
bitsRead = 0; | |
tempChar = 0; | |
} | |
} |