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);
}
}
}
}