View Javadoc
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