package echosign.api.proxy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
/**
* A BufferedHttpStreamReader reads an HTTP stream and can take special care for the Host header
* and the content length tracking.
*/
public class BufferedHttpStreamReader extends InputStreamReader
{
public static final int DEFAULT_BUFFER_SIZE = 8192;
/**
* This is the official EchoSign domain name for the secure API calls.
*/
public static final String ECHOSIGN_DOMAIN_NAME = "secure.echosign.com";
/**
* Use this environment variable if you want to make the secure API calls other than the
* offical domain name 'secure.echogin.com'.
*/
public static final String ECHOSIGN_DOMAIN_NAME_ENV_NAME = "ECHOSIGN_DOMAIN_NAME";
private static final String HOST_HEADER = "Host: ";
private static final String HOST_HEADER_REGEXP = HOST_HEADER + "\\s*[^\\r\\n]+";
private static final String CONTENT_LENGTH_HEADER = "Content-Length: ";
private char[] buffer;
private int offset;
private int remainder;
private int numRead;
private boolean eof;
private boolean readingContent;
private int contentLength;
private int blankLinePos;
private String bufferString;
private String hostHeaderEchoSign;
public BufferedHttpStreamReader(String serverHost, InputStream in) throws UnsupportedEncodingException
{
this(serverHost, in, "UTF-8", DEFAULT_BUFFER_SIZE);
}
public BufferedHttpStreamReader(String serverHost, InputStream in, String charsetName, int bufferSize) throws UnsupportedEncodingException
{
super(in, charsetName);
buffer = new char[bufferSize];
eof = false;
readingContent = false;
contentLength = 0;
// Use the specified domain name for the Host header. Look for it in the environment variable,
// the argument serverHost, or the default one in this order.
String echoSignDomainName = System.getenv(ECHOSIGN_DOMAIN_NAME_ENV_NAME);
hostHeaderEchoSign = HOST_HEADER + ((echoSignDomainName != null) ? echoSignDomainName : ((serverHost != null) ? serverHost : ECHOSIGN_DOMAIN_NAME));
reset();
}
public void reset()
{
offset = 0;
remainder = buffer.length;
numRead = 0;
blankLinePos = -1;
bufferString = null;
}
public int read() throws IOException
{
if (eof())
return -1;
if (remainder <= 0)
{
System.err.println("WARNING: BufferedHttpStreamReader: reset due to out of buffer space");
reset();
}
int n = super.read(buffer, offset, remainder);
if (n < 0)
eof = true;
else if (0 < n)
{
numRead = n;
offset += numRead;
remainder -= numRead;
// the order is important here
readingContent = (0 < contentLength);
contentLength -= numRead;
}
return n;
}
public boolean eof()
{
return eof;
}
public boolean empty()
{
return (offset == 0);
}
public boolean isReadingContent()
{
return readingContent;
}
public boolean hasBlankLine()
{
// look for CRLFCRLF from the last data position
for (int i = offset - numRead; i < offset; i++)
{
if (buffer[i] == '\r'
&& i + 4 <= offset
&& buffer[i + 1] == '\n'
&& buffer[i + 2] == '\r'
&& buffer[i + 3] == '\n')
{
// remember the position for later use
blankLinePos = i + 2;
return true;
}
}
return false;
}
private void convertBytesToString()
{
if (bufferString == null)
bufferString = new String(buffer, 0, offset);
}
public String getBufferString()
{
convertBytesToString();
return bufferString;
}
// start tracking the content length
public void trackContentLength()
{
if (0 < blankLinePos)
{
convertBytesToString();
// find the location of the content length string
int beg = bufferString.indexOf(CONTENT_LENGTH_HEADER);
if (0 <= beg)
{
beg += CONTENT_LENGTH_HEADER.length();
// skip whitespace and find the end of the string
char c = bufferString.charAt(beg);
while (c == ' ' || c == '\t')
c = bufferString.charAt(++beg);
int end = beg;
while ('0' <= c && c <= '9')
c = bufferString.charAt(++end);
if (beg < end)
{
contentLength = Integer.parseInt(bufferString.substring(beg, end));
// some part of the content may be read already
contentLength -= bufferString.length() - (blankLinePos + 2); // 2 := CRLF
}
}
}
}
// coerce the Host header to have the EchoSign domain name
public void coerceHostHeader()
{
if (0 < blankLinePos)
{
convertBytesToString();
bufferString = bufferString.replaceFirst(HOST_HEADER_REGEXP, hostHeaderEchoSign);
}
}
public byte[] toByteArray() throws IOException, UnsupportedEncodingException
{
ByteArrayOutputStream baostrm = new ByteArrayOutputStream();
OutputStreamWriter oswtr = new OutputStreamWriter(baostrm, getEncoding());
if (bufferString != null)
oswtr.write(bufferString, 0, bufferString.length());
else
oswtr.write(buffer, 0, offset);
oswtr.close();
return baostrm.toByteArray();
}
}