/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.internal.wc.IOExceptionWrapper; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class ChunkedInputStream extends InputStream { private String myCharset; private InputStream myInputStream; private int myChunkSize; private int myPosition; private boolean myIsBOF = true; private boolean myIsEOF = false; private boolean myIsClosed = false; public ChunkedInputStream(final InputStream in, String charset) { myInputStream = in; myPosition = 0; myCharset = charset; } public int read() throws IOException { if (myIsClosed) { throw new IOException("Attempted read from closed stream."); } if (myIsEOF) { return -1; } if (myPosition >= myChunkSize) { nextChunk(); if (myIsEOF) { return -1; } } myPosition++; return myInputStream.read(); } public int read (byte[] b, int off, int len) throws IOException { if (myIsClosed) { throw new IOException("Attempted read from closed stream."); } if (myIsEOF) { return -1; } if (myPosition >= myChunkSize) { nextChunk(); if (myIsEOF) { return -1; } } len = Math.min(len, myChunkSize - myPosition); int count = myInputStream.read(b, off, len); myPosition += count; return count; } public int read (byte[] b) throws IOException { return read(b, 0, b.length); } private void readCRLF() throws IOException { int cr = myInputStream.read(); int lf = myInputStream.read(); if ((cr != '\r') || (lf != '\n')) { throw new IOException( "CRLF expected at end of chunk: " + cr + "/" + lf); } } private void nextChunk() throws IOException { if (!myIsBOF) { readCRLF(); } myChunkSize = getChunkSizeFromInputStream(myInputStream, myCharset); myIsBOF = false; myPosition = 0; if (myChunkSize == 0) { myIsEOF = true; } } private static int getChunkSizeFromInputStream(final InputStream in, String charset) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // States: 0=normal, 1=\r was scanned, 2=inside quoted string, // -1=end int state = 0; while (state != -1) { int b = in.read(); if (b == -1) { throw new IOException("chunked stream ended unexpectedly"); } switch (state) { case 0: switch (b) { case '\r': state = 1; break; case '\"': state = 2; /* fall through */ default: baos.write(b); } break; case 1: if (b == '\n') { state = -1; } else { // this was not CRLF throw new IOException("Protocol violation: Unexpected" + " single newline character in chunk size"); } break; case 2: switch (b) { case '\\': b = in.read(); baos.write(b); break; case '\"': state = 0; /* fall through */ default: baos.write(b); } break; default: try { SVNErrorManager.assertionFailure(false, null, SVNLogType.NETWORK); } catch (SVNException svne) { throw new IOExceptionWrapper(svne); } } } // parse data String dataString = new String(baos.toByteArray(), charset); int separator = dataString.indexOf(';'); dataString = (separator > 0) ? dataString.substring(0, separator).trim() : dataString.trim(); int result; try { result = Integer.parseInt(dataString.trim(), 16); } catch (NumberFormatException e) { throw new IOException ("Bad chunk size: " + dataString); } return result; } public void close() throws IOException { if (!myIsClosed) { try { if (!myIsEOF) { FixedSizeInputStream.consumeRemaining(this); } } finally { myIsEOF = true; myIsClosed = true; } } } }