package org.prevayler.foundation; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.EOFException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.StringTokenizer; import java.util.regex.Pattern; public class Chunking { private static final String ASCII="US-ASCII"; private static final byte[] CRLF=new byte[]{'\r','\n'}; private static final String SIZE="0|[1-9A-F][0-9A-F]{0,6}|[1-7][0-9A-F]{7}"; private static final String TOKEN="[^\u0000-\u0020()<>@,;:\\\\\"/\\[\\]?={}\u007F-\uFFFF]+"; private static final String HEADER="(" + SIZE + ")(;"+ TOKEN+ "="+ TOKEN+ ")*\r\n"; private static final Pattern TOKEN_PATTERN=Pattern.compile(TOKEN); private static final Pattern HEADER_PATTERN=Pattern.compile(HEADER); private static boolean validToken( String token){ return TOKEN_PATTERN.matcher(token).matches(); } public static void writeChunk( OutputStream stream, Chunk chunk) throws IOException { stream.write(Integer.toHexString(chunk.getBytes().length).toUpperCase().getBytes(ASCII)); Iterator iterator=chunk.getParameters().entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry=(Map.Entry)iterator.next(); String name=(String)entry.getKey(); String value=(String)entry.getValue(); if (!validToken(name)) { throw new IOException("Invalid parameter name '" + name + "'"); } if (!validToken(value)) { throw new IOException("Invalid parameter value '" + value + "'"); } stream.write(';'); stream.write(name.getBytes(ASCII)); stream.write('='); stream.write(value.getBytes(ASCII)); } stream.write(CRLF); stream.write(chunk.getBytes()); stream.write(CRLF); } public static Chunk readChunk( InputStream stream) throws IOException { String header=readLine(stream); if (header == null) { return null; } if (!HEADER_PATTERN.matcher(header).matches()) { throw new IOException("Chunk header corrupted"); } StringTokenizer tokenizer=new StringTokenizer(header,";=\r\n"); int size=Integer.parseInt(tokenizer.nextToken(),16); Map parameters=new LinkedHashMap(); while (tokenizer.hasMoreTokens()) { String name=tokenizer.nextToken(); String value=tokenizer.nextToken(); parameters.put(name,value); } byte[] bytes=new byte[size]; int total=0; while (total < size) { int read=stream.read(bytes,total,size - total); if (read == -1) { throw new EOFException("Unexpected end of stream in chunk data"); } total+=read; } int cr=stream.read(); int lf=stream.read(); if (cr == -1 || cr == '\r' && lf == -1) { throw new EOFException("Unexpected end of stream in chunk trailer"); } else if (cr != '\r' || lf != '\n') { throw new IOException("Chunk trailer corrupted"); } return new Chunk(bytes,parameters); } private static String readLine( InputStream stream) throws IOException { ByteArrayOutputStream header=new ByteArrayOutputStream(); while (true) { int b=stream.read(); if (b == -1) { if (header.size() == 0) { return null; } else { throw new EOFException("Unexpected end of stream in chunk header"); } } header.write(b); if (b == '\n') { return header.toString(ASCII); } } } }