View Javadoc

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  package com.beetstra.jutf7;
25  
26  import java.nio.ByteBuffer;
27  import java.nio.CharBuffer;
28  import java.nio.charset.CharsetDecoder;
29  import java.nio.charset.CoderResult;
30  
31  /**
32   * <p>The CharsetDecoder used to decode both variants of the UTF-7 charset and the 
33   * modified-UTF-7 charset.</p>
34   * 
35   * @author Jaap Beetstra
36   */
37  class UTF7StyleCharsetDecoder extends CharsetDecoder {
38  	private final Base64Util base64;
39  	private final byte shift;
40  	private final byte unshift;
41  	private final boolean strict;
42  	private boolean base64mode;
43  	private int bitsRead;
44  	private int tempChar;
45  	private boolean justShifted;
46  	private boolean justUnshifted;
47  
48  	UTF7StyleCharsetDecoder(UTF7StyleCharset cs, Base64Util base64, boolean strict) {
49  		super(cs, 0.6f, 1.0f);
50  		this.base64 = base64;
51  		this.strict = strict;
52  		this.shift = cs.shift();
53  		this.unshift = cs.unshift();
54  	}
55  
56  	/* (non-Javadoc)
57  	 * @see java.nio.charset.CharsetDecoder#decodeLoop(java.nio.ByteBuffer, java.nio.CharBuffer)
58  	 */
59  	protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
60  		while (in.hasRemaining()) {
61  			byte b = in.get();
62  			if (base64mode) {
63  				if (b == unshift) {
64  					if (base64bitsWaiting())
65  						return malformed(in);
66  					if (justShifted) {
67  						if (!out.hasRemaining())
68  							return overflow(in);
69  						out.put((char) shift);
70  					} else
71  						justUnshifted = true;
72  					setUnshifted();
73  				} else {
74  					if (!out.hasRemaining())
75  						return overflow(in);
76  					CoderResult result = handleBase64(in, out, b);
77  					if (result != null)
78  						return result;
79  				}
80  				justShifted = false;
81  			} else {
82  				if (b == shift) {
83  					base64mode = true;
84  					if (justUnshifted && strict)
85  						return malformed(in);
86  					justShifted = true;
87  					continue;
88  				}
89  				if (!out.hasRemaining())
90  					return overflow(in);
91  				out.put((char) b);
92  				justUnshifted = false;
93  			}
94  		}
95  		return CoderResult.UNDERFLOW;
96  	}
97  
98  	private CoderResult overflow(ByteBuffer in) {
99  		in.position(in.position() - 1);
100 		return CoderResult.OVERFLOW;
101 	}
102 
103 	/**
104 	 * <p>Decodes a byte in <i>base 64 mode</i>. Will directly write a character to the output 
105 	 * buffer if completed.</p>
106 	 * 
107 	 * @param in The input buffer
108 	 * @param out The output buffer
109 	 * @param lastRead Last byte read from the input buffer
110 	 * @return CoderResult.malformed if a non-base 64 character was encountered in strict 
111 	 *   mode, null otherwise
112 	 */
113 	private CoderResult handleBase64(ByteBuffer in, CharBuffer out, byte lastRead) {
114 		CoderResult result = null;
115 		int sextet = base64.getSextet(lastRead);
116 		if (sextet >= 0) {
117 			bitsRead += 6;
118 			if (bitsRead < 16) {
119 				tempChar += sextet << (16 - bitsRead);
120 			} else {
121 				bitsRead -= 16;
122 				tempChar += sextet >> (bitsRead);
123 				out.put((char) tempChar);
124 				tempChar = (sextet << (16 - bitsRead)) & 0xFFFF;
125 			}
126 		} else {
127 			if (strict)
128 				return malformed(in);
129 			out.put((char) lastRead);
130 			if (base64bitsWaiting())
131 				result = malformed(in);
132 			setUnshifted();
133 		}
134 		return result;
135 	}
136 
137 	/* (non-Javadoc)
138 	 * @see java.nio.charset.CharsetDecoder#implFlush(java.nio.CharBuffer)
139 	 */
140 	protected CoderResult implFlush(CharBuffer out) {
141 		if ((base64mode && strict) || base64bitsWaiting())
142 			return CoderResult.malformedForLength(1);
143 		return CoderResult.UNDERFLOW;
144 	}
145 
146 	/* (non-Javadoc)
147 	 * @see java.nio.charset.CharsetDecoder#implReset()
148 	 */
149 	protected void implReset() {
150 		setUnshifted();
151 		justUnshifted = false;
152 	}
153 
154 	/**
155 	 * <p>Resets the input buffer position to just before the last byte read, and returns
156 	 * a result indicating to skip the last byte.</p>
157 	 * 
158 	 * @param in The input buffer
159 	 * @return CoderResult.malformedForLength(1);
160 	 */
161 	private CoderResult malformed(ByteBuffer in) {
162 		in.position(in.position() - 1);
163 		return CoderResult.malformedForLength(1);
164 	}
165 
166 	/**
167 	 * @return True if there are base64 encoded characters waiting to be written
168 	 */
169 	private boolean base64bitsWaiting() {
170 		return tempChar != 0 || bitsRead >= 6;
171 	}
172 
173 	/**
174 	 * <p>Updates internal state to reflect the decoder is no longer in <i>base 64 
175 	 * mode</i></p>
176 	 */
177 	private void setUnshifted() {
178 		base64mode = false;
179 		bitsRead = 0;
180 		tempChar = 0;
181 	}
182 }