package org.torproject.jtor.directory.impl;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.InflaterInputStream;
import org.torproject.jtor.TorException;
public class HttpConnection {
private final static String HTTP_RESPONSE_REGEX = "HTTP/1\\.(\\d) (\\d+) (.*)";
private final static String CONTENT_LENGTH_HEADER = "Content-Length";
private final static String CONTENT_ENCODING_HEADER = "Content-Encoding";
private final Date ifModifiedSince = null;
private final String host;
private final BufferedReader reader;
private final BufferedWriter writer;
private final Map<String, String> headers;
private int responseCode;
private boolean bodyCompressed;
private String responseMessage;
private Reader bodyReader;
HttpConnection(String host, BufferedReader reader, BufferedWriter writer) {
this.host = host;
this.reader = reader;
this.writer = writer;
this.headers = new HashMap<String, String>();
}
void sendGetRequest(String request) {
final StringBuilder sb = new StringBuilder();
sb.append("GET ");
sb.append(request);
sb.append(" HTTP/1.0\r\n");
sb.append("Host: "+ host +"\r\n");
if(ifModifiedSince != null) {
}
sb.append("\r\n");
try {
writer.write(sb.toString());
writer.flush();
} catch (IOException e) {
throw new TorException("IO exception sending GET request", e);
}
}
void sendPostRequest(String request, byte[] data){
final StringBuilder sb = new StringBuilder();
sb.append("POST ");
sb.append(request);
sb.append(" HTTP/1.0\r\n");
sb.append("Host: "+ host +"\r\n");
if(ifModifiedSince != null) {
}
sb.append("\r\n");
try {
writer.write(sb.toString());
writer.write(new String(data));
writer.flush();
} catch (IOException e) {
throw new TorException("IO exception sending POST request", e);
}
}
void readResponse() {
readStatusLine();
readHeaders();
readBody();
}
int getStatusCode() {
return responseCode;
}
String getStatusMessage() {
return responseMessage;
}
Reader getBodyReader() {
return bodyReader;
}
private void readStatusLine() {
final String line = nextResponseLine();
final Pattern p = Pattern.compile(HTTP_RESPONSE_REGEX);
final Matcher m = p.matcher(line);
if(!m.find() || m.groupCount() != 3)
throw new TorException("Error parsing HTTP response line: "+ line);
try {
int n1 = Integer.parseInt(m.group(1));
int n2 = Integer.parseInt(m.group(2));
if( (n1 != 0 && n1 != 1) ||
(n2 < 100 || n2 >= 600))
throw new TorException("Failed to parse header: "+ line);
responseCode = n2;
responseMessage = m.group(3);
} catch(NumberFormatException e) {
throw new TorException("Failed to parse header: "+ line);
}
}
private void readHeaders() {
headers.clear();
while(true) {
final String line = nextResponseLine();
if(line.length() == 0)
return;
final String[] args = line.split(": ", 2);
if(args.length != 2)
throw new TorException("Failed to parse HTTP header: "+ line);
headers.put(args[0], args[1]);
}
}
private String nextResponseLine() {
try {
final String line = reader.readLine();
if(line == null)
throw new TorException("Unexpected EOF reading HTTP response");
return line;
} catch (IOException e) {
throw new TorException("IO error reading HTTP response", e);
}
}
private void readBody() {
processContentEncodingHeader();
if(headers.containsKey(CONTENT_LENGTH_HEADER))
readBodyFromContentLength();
else
readBodyUntilEOF();
}
private void processContentEncodingHeader() {
final String encoding = headers.get(CONTENT_ENCODING_HEADER);
if(encoding == null || encoding.equals("identity"))
bodyCompressed = false;
else if(encoding.equals("deflate") || encoding.equals("x-deflate"))
bodyCompressed = true;
else
throw new TorException("Unrecognized content encoding: "+ encoding);
}
private void readBodyFromContentLength() {
int bodyLength = Integer.parseInt(headers.get(CONTENT_LENGTH_HEADER));
char[] bodyBuffer = new char[bodyLength];
readAll(bodyBuffer);
bodyReader = charBufferToReader(bodyBuffer, bodyLength);
}
private void readBodyUntilEOF() {
char[] bodyBuffer = new char[1024];
int offset = 0;
while(true) {
try {
int n = reader.read(bodyBuffer, offset, bodyBuffer.length - offset);
if(n == -1) {
bodyReader = charBufferToReader(bodyBuffer, offset);
return;
}
offset += n;
if(offset == bodyBuffer.length)
bodyBuffer = expandCharBuffer(bodyBuffer);
} catch (IOException e) {
throw new TorException("IO error reading HTTP response");
}
}
}
private char[] expandCharBuffer(char[] buffer) {
final int newLength = buffer.length * 2;
final char[] newBuffer = new char[newLength];
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
return newBuffer;
}
private Reader charBufferToReader(char[] buffer, int length) {
if(bodyCompressed) {
final byte[] bytes = new byte[length];
for(int i = 0; i < length; i++)
bytes[i] = (byte) buffer[i];
final ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
final InflaterInputStream inflater = new InflaterInputStream(byteStream);
return createInputStreamReader(inflater);
} else {
return new StringReader(new String(buffer, 0, length));
}
}
private Reader createInputStreamReader(InputStream input) {
try {
return new InputStreamReader(input, "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
throw new TorException("Unsupported character set encoding: "+ e.getMessage());
}
}
private void readAll(char[] buffer) {
int offset = 0;
int remaining = buffer.length;
while(remaining > 0) {
try {
int n = reader.read(buffer, offset, remaining);
if(n == -1)
throw new TorException("Unexpected EOF reading response");
offset += n;
remaining -= n;
} catch (IOException e) {
throw new TorException("IO error reading HTTP response");
}
}
}
}