1
2 package jsdsi.sexp;
3
4 import java.io.ByteArrayInputStream;
5 import java.io.ByteArrayOutputStream;
6 import java.io.DataInputStream;
7 import java.io.EOFException;
8 import java.io.FilterInputStream;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.io.PushbackInputStream;
12 import java.util.ArrayList;
13
14 import cryptix.util.mime.Base64InputStream;
15
16 /***
17 * Reads serialized S-expressions from an underlying stream. Supports
18 * the canonical, transport, and readable S-expression encodings.
19 *
20 * @author Sameer Ajmani
21 * @version $Revision: 1.1.6.1 $ $Date: 2005/11/08 03:12:52 $
22 *
23 * @see SexpOutputStream
24 */
25 public class SexpInputStream extends FilterInputStream
26 implements SexpInput
27 {
28 private PushbackInputStream p;
29
30 private int lineno = 1;
31
32 /***
33 * Creates a new SexpInputStream that reads from the given stream.
34 */
35 public SexpInputStream(InputStream is) {
36 super(new PushbackInputStream(is));
37 p = (PushbackInputStream) super.in;
38 }
39
40 private int readSafe() throws SexpException, IOException {
41 int nextChar = p.read();
42 if (nextChar == -1) {
43 throw new SexpException("Line " + lineno + ": Unexpected EOF");
44 }
45 return nextChar;
46 }
47 private int readSkipWhitespace() throws SexpException, IOException {
48 int nextChar;
49 do {
50 nextChar = readSafe();
51 if (nextChar == '\n') {
52 lineno++;
53 }
54 } while (Character.isWhitespace((char) nextChar));
55 return nextChar;
56 }
57
58 /***
59 * Reads the next S-expression from the stream.
60 *
61 * @return the decoded S-expression.
62 * @throws EOFException if the stream is empty.
63 * @throws SexpException if there is a decoding error.
64 * @throws IOException if there is an IO error.
65 */
66 public Sexp readSexp() throws SexpException, IOException {
67 int nextChar;
68 try {
69 nextChar = readSkipWhitespace();
70 } catch (SexpException e) {
71 // this EOF is not necessarily "unexpected"
72 throw new EOFException();
73 }
74 if (nextChar == '(') {
75 return readSexpList();
76 }
77 if (nextChar == '{') {
78 // read transport form
79 byte[] decoded = readBase64Until('}');
80 return (new SexpInputStream(new ByteArrayInputStream(decoded)))
81 .readSexp();
82 }
83 p.unread(nextChar);
84 return readSexpString();
85 }
86
87 private SexpList readSexpList() throws SexpException, IOException {
88 // read type
89 int nextChar = readSkipWhitespace();
90 if (nextChar == ')') {
91 throw new SexpException("Line " + lineno + ": Empty list");
92 }
93 p.unread(nextChar);
94 Sexp type = readSexp();
95 if (!(type instanceof SexpString)) {
96 throw new SexpException("Line " + lineno + ": Expected list type");
97 }
98 // read elements
99 ArrayList l = new ArrayList();
100 while (true) {
101 nextChar = readSkipWhitespace();
102 if (nextChar == ')') {
103 // complete list
104 Sexp[] elems = new Sexp[l.size()];
105 l.toArray(elems);
106 return new SexpList((SexpString) type, elems);
107 }
108 // add next sexp to list
109 p.unread(nextChar);
110 l.add(readSexp());
111 }
112 }
113
114 private SexpString readSexpString() throws SexpException, IOException {
115 int nextChar = readSafe();
116 byte[] display = null;
117 if (nextChar == '[') {
118 // read display hint
119 display = readByteArray();
120 if (readSafe() != ']') {
121 throw new SexpException(
122 "Line " + lineno + ": Missing ']' after display hint");
123 }
124 } else {
125 p.unread(nextChar);
126 }
127 // read string content
128 byte[] content = readByteArray();
129 if (display == null) {
130 return new SexpString(content);
131 }
132 return new SexpString(display, content);
133 }
134
135 private byte[] readByteArray() throws SexpException, IOException {
136 int nextChar = readSafe();
137 if (nextChar == '0') {
138 // leading zero allowed only if it defines the empty string
139 if (readSafe() != ':') {
140 throw new SexpException(
141 "Line " + lineno + ": Unexpected leading '0'");
142 }
143 return new byte[0];
144 }
145
146 int length = 0; // we handled length == 0 already
147 if (Sexp.isDecimalDigit(nextChar)) {
148 // read the length field
149 ByteArrayOutputStream bos = new ByteArrayOutputStream(20);
150 do {
151 bos.write(nextChar);
152 nextChar = readSafe();
153 } while (Sexp.isDecimalDigit(nextChar));
154 length = Integer.parseInt(new String(bos.toByteArray(), "8859_1"));
155 }
156 // read the data itself
157 switch (nextChar) {
158 case ':' :
159 if (length > 0) {
160 byte[] data = new byte[length];
161 (new DataInputStream(p)).readFully(data);
162 return data;
163 } else {
164 return readTokenData(nextChar);
165 }
166 case '#' :
167 return readHexData(length);
168 case '|' :
169 return readBase64Data(length);
170 case '\"' :
171 return readQuotedData(length);
172 default :
173 return readTokenData(nextChar);
174 }
175 }
176
177 private byte[] readTokenData(int nextChar)
178 throws SexpException, IOException {
179 ByteArrayOutputStream bos = new ByteArrayOutputStream();
180 do {
181 bos.write(nextChar);
182 nextChar = readSafe();
183 } while (Sexp.isTokenChar(nextChar));
184 p.unread(nextChar);
185 return bos.toByteArray();
186 }
187
188 private byte[] readHexData(int length) throws SexpException, IOException {
189 byte[] bs = new byte[2];
190 ByteArrayOutputStream bos = new ByteArrayOutputStream();
191 while (true) {
192 int nextChar = readSkipWhitespace();
193 if (nextChar == '#') {
194 byte[] data = bos.toByteArray();
195 if ((length > 0) && (length != data.length)) {
196 throw new SexpException(
197 "Line " + lineno + ": Base-16 encoded length mismatch");
198 }
199 return data;
200 }
201 bs[0] = (byte) nextChar;
202 bs[1] = (byte) readSkipWhitespace();
203 bos.write(Integer.parseInt(new String(bs, "8859_1"), 16));
204 }
205 }
206
207 private byte[] readBase64Data(int length)
208 throws SexpException, IOException {
209 if (length > 0) {
210 // length provided: read the expected amount of data
211 byte[] data = new byte[length];
212 (new DataInputStream(new Base64InputStream(p))).readFully(data);
213 if (readSkipWhitespace() != '|') {
214 throw new SexpException(
215 "Line "
216 + lineno
217 + ": Missing '|' after base-64 encoded data");
218 }
219 return data;
220 }
221 // no length provided: read until we find terminator
222 return readBase64Until('|');
223 }
224
225 private byte[] readBase64Until(int terminator)
226 throws SexpException, IOException {
227 ByteArrayOutputStream bos = new ByteArrayOutputStream();
228 int numEquals = 0;
229 int nextChar = readSkipWhitespace();
230 while (Sexp.isBase64Digit(nextChar)) {
231 bos.write(nextChar);
232 nextChar = readSkipWhitespace();
233 }
234 while (nextChar == '=') {
235 numEquals++;
236 bos.write(nextChar);
237 nextChar = readSkipWhitespace();
238 }
239 if (nextChar != terminator) {
240 throw new SexpException(
241 "Line "
242 + lineno
243 + ": Missing '"
244 + terminator
245 + "' after base-64 encoded data, got "
246 + (char) nextChar);
247 }
248 // decode the encoded data
249 byte[] encoded = bos.toByteArray();
250 if ((encoded.length % 4) != 0) {
251 throw new SexpException(
252 "Line "
253 + lineno
254 + ": Base-64 encoded length not a multiple of 4");
255 }
256 byte[] decoded = new byte[3 * (encoded.length / 4) - numEquals];
257 ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
258 (new DataInputStream(new Base64InputStream(bis))).readFully(decoded);
259 if (bis.read() != -1) {
260 // sanity check: we should have read the entire encoded array
261 throw new Error("Did not fully read Base-64 encoded data");
262 }
263 return decoded;
264 }
265
266 private byte[] readQuotedData(int length)
267 throws SexpException, IOException {
268 ByteArrayOutputStream bos = new ByteArrayOutputStream();
269 while (true) {
270 int nextChar = readSafe();
271 if (nextChar == '\"') {
272 byte[] data = bos.toByteArray();
273 if ((length > 0) && (length != data.length)) {
274 throw new SexpException(
275 "Line " + lineno + ": Quoted string length mismatch");
276 }
277 return data;
278 }
279 if (nextChar == '//') {
280 nextChar = processEscapeSequence();
281 if (nextChar == -1)
282 continue; // read next char
283 }
284 bos.write(nextChar);
285 }
286 }
287
288 private int processEscapeSequence() throws SexpException, IOException {
289 int nextChar = readSafe();
290 switch (nextChar) {
291 case 'b' :
292 return '\b';
293 case 't' :
294 return '\t';
295 case 'v' :
296 // no vertical tab
297 return '\t';
298 case 'f' :
299 return '\f';
300 case 'n' :
301 return '\n';
302 case 'r' :
303 return '\r';
304 case '\'' :
305 return '\'';
306 case '\"' :
307 return '\"';
308 case '//' :
309 return '//';
310 case '\n' :
311 // slash separates lines; check for \n\r
312 nextChar = readSafe();
313 if (nextChar != '\r') {
314 p.unread(nextChar);
315 }
316 return -1;
317 case '\r' :
318 // slash separates lines; check for \r\n
319 nextChar = readSafe();
320 if (nextChar != '\n') {
321 p.unread(nextChar);
322 }
323 return -1;
324 case 'x' :
325 byte[] hex = new byte[2];
326 hex[0] = (byte) readSafe();
327 hex[1] = (byte) readSafe();
328 return Integer.parseInt(new String(hex, "8859_1"), 16);
329 default :
330 byte[] oct = new byte[3];
331 oct[0] = (byte) nextChar;
332 oct[1] = (byte) readSafe();
333 oct[2] = (byte) readSafe();
334 return Integer.parseInt(new String(oct, "8859_1"), 8);
335 }
336 }
337 }
This page was automatically generated by Maven