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