/*
* This file is part of the OWASP Proxy, a free intercepting proxy library.
* Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to:
* The Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.owasp.proxy.http;
import static org.owasp.proxy.http.HttpConstants.CHUNKED;
import static org.owasp.proxy.http.HttpConstants.CONTENT_ENCODING;
import static org.owasp.proxy.http.HttpConstants.CONTENT_LENGTH;
import static org.owasp.proxy.http.HttpConstants.DEFLATE;
import static org.owasp.proxy.http.HttpConstants.GZIP;
import static org.owasp.proxy.http.HttpConstants.IDENTITY;
import static org.owasp.proxy.http.HttpConstants.TRANSFER_ENCODING;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.owasp.proxy.io.ChunkedInputStream;
import org.owasp.proxy.io.ChunkingInputStream;
import org.owasp.proxy.io.CopyInputStream;
import org.owasp.proxy.io.CountingInputStream;
import org.owasp.proxy.io.DeflaterInputStream;
import org.owasp.proxy.io.FixedLengthInputStream;
import org.owasp.proxy.io.GunzipInputStream;
import org.owasp.proxy.io.GzipInputStream;
import org.owasp.proxy.io.SizeLimitExceededException;
import org.owasp.proxy.io.SizeLimitedByteArrayOutputStream;
public class MessageUtils {
/**
* @param content
* @param codings
* @return
* @throws MessageFormatException
*/
public static InputStream decode(StreamingMessage message)
throws IOException, MessageFormatException {
return decode(message, message.getContent());
}
public static byte[] decode(BufferedMessage message)
throws MessageFormatException {
return decode(message, message.getContent());
}
@SuppressWarnings("resource")
public static byte[] decode(MessageHeader message, byte[] content)
throws MessageFormatException {
try {
if (content == null || content.length == 0) {
return new byte[0];
}
InputStream is = new ByteArrayInputStream(content);
is = decode(message, is);
ByteArrayOutputStream copy = new ByteArrayOutputStream();
byte[] buff = new byte[4096];
int got;
while ((got = is.read(buff)) > 0) {
copy.write(buff, 0, got);
}
return copy.toByteArray();
} catch (IOException ioe) {
throw new MessageFormatException("Malformed encoded content: "
+ ioe.getMessage(), ioe);
}
}
public static InputStream decode(MessageHeader message, InputStream content)
throws IOException, MessageFormatException {
if (content == null)
return content;
String codings = message.getHeader(TRANSFER_ENCODING);
content = decode(codings, content);
codings = message.getHeader(CONTENT_ENCODING);
content = decode(codings, content);
return content;
}
public static InputStream decode(String codings, InputStream content)
throws IOException, MessageFormatException {
if (codings == null || codings.trim().equals(""))
return content;
String[] algos = codings.split("[ \t]*,[ \t]*");
if (algos.length == 1 && IDENTITY.equalsIgnoreCase(algos[0]))
return content;
for (int i = 0; i < algos.length; i++) {
if (CHUNKED.equalsIgnoreCase(algos[i])) {
content = new ChunkedInputStream(content);
} else if (DEFLATE.equalsIgnoreCase(algos[i])) {
content = new DeflaterInputStream(content);
} else if (GZIP.equalsIgnoreCase(algos[i])) {
content = new GunzipInputStream(content);
} else if (IDENTITY.equalsIgnoreCase(algos[i])) {
// nothing to do
} else
throw new MessageFormatException("Unsupported coding : "
+ algos[i]);
}
return content;
}
/**
* @param content
* @param codings
* @return
* @throws MessageFormatException
*/
public static InputStream encode(StreamingMessage message)
throws MessageFormatException {
return encode(message, message.getContent());
}
public static byte[] encode(BufferedMessage message)
throws MessageFormatException {
return encode(message, message.getContent());
}
public static byte[] encode(MessageHeader header, byte[] content)
throws MessageFormatException {
InputStream contentStream = new ByteArrayInputStream(content);
contentStream = encode(header, contentStream);
ByteArrayOutputStream copy = new ByteArrayOutputStream();
byte[] buff = new byte[4096];
int got;
try {
while ((got = contentStream.read(buff)) > 0)
copy.write(buff, 0, got);
} catch (IOException ioe) {
throw new MessageFormatException("Error encoding content", ioe);
}
return copy.toByteArray();
}
/**
* @param content
* @param codings
* @return
* @throws MessageFormatException
*/
public static InputStream encode(MessageHeader header, InputStream content)
throws MessageFormatException {
String codings = header.getHeader("Content-Encoding");
content = encode(codings, content);
codings = header.getHeader("Transfer-Encoding");
content = encode(codings, content);
return content;
}
public static InputStream encode(String codings, InputStream content)
throws MessageFormatException {
if (codings == null || codings.trim().equals(""))
return content;
String[] algos = codings.split("[ \t]*,[ \t]*");
if (algos.length == 1 && IDENTITY.equalsIgnoreCase(algos[0]))
return content;
for (int i = 0; i < algos.length; i++) {
if (CHUNKED.equalsIgnoreCase(algos[i])) {
content = new ChunkingInputStream(content);
} else if (GZIP.equalsIgnoreCase(algos[i])) {
content = new GzipInputStream(content);
} else if (IDENTITY.equalsIgnoreCase(algos[i])) {
// nothing to do
} else
throw new MessageFormatException("Unsupported coding : "
+ algos[i]);
}
return content;
}
public static boolean flushContent(MutableMessageHeader header,
InputStream in) throws MessageFormatException, IOException {
return flushContent(header, in, null);
}
public static boolean flushContent(MutableMessageHeader header,
InputStream in, OutputStream out) throws MessageFormatException,
IOException {
boolean read = false;
String te = header.getHeader(TRANSFER_ENCODING);
if (CHUNKED.equalsIgnoreCase(te)) {
in = new ChunkedInputStream(in);
} else if (te != null) {
throw new IOException("Unknown Transfer-Encoding '" + te + "'");
} else {
String cl = header.getHeader(CONTENT_LENGTH);
if (cl != null) {
try {
int l = Integer.parseInt(cl.trim());
if (l == 0)
return read;
in = new FixedLengthInputStream(in, l);
} catch (NumberFormatException nfe) {
throw new MessageFormatException(
"Invalid Content-Length header: " + cl, nfe);
}
}
}
byte[] buff = new byte[2048];
int got;
while ((got = in.read(buff)) > 0) {
read = true;
if (out != null)
out.write(buff, 0, got);
}
return read;
}
/**
* Get the exact representation of the message
*
* @return the internal byte[] representing the contents of this message.
*/
public static byte[] getMessage(MutableBufferedMessage message) {
byte[] header = message.getHeader();
byte[] content = message.getContent();
if (content != null && content.length > 0) {
byte[] bytes = new byte[header.length + content.length];
System.arraycopy(header, 0, bytes, 0, header.length);
System.arraycopy(content, 0, bytes, header.length, content.length);
return bytes;
}
return header;
}
public static boolean expectContent(RequestHeader request)
throws MessageFormatException {
String method = request.getMethod();
if (!"POST".equalsIgnoreCase(method) && !"PUT".equalsIgnoreCase(method))
return false;
String contentLength = request.getHeader(CONTENT_LENGTH);
String transferEncoding = request.getHeader(TRANSFER_ENCODING);
if (transferEncoding != null)
return true;
if (contentLength == null)
throw new MessageFormatException(
"Request content expected, but no length specified!",
request.getHeader());
try {
int cl = Integer.parseInt(contentLength);
if (cl < 0)
throw new MessageFormatException("Negative content length: "
+ contentLength, request.getHeader());
if (cl == 0)
return false;
return true;
} catch (NumberFormatException nfe) {
throw new MessageFormatException("Invalid content length: "
+ contentLength, nfe, request.getHeader());
}
}
public static boolean expectContent(RequestHeader request,
ResponseHeader response) throws MessageFormatException {
String method = request.getMethod();
String status = response.getStatus();
return !("HEAD".equalsIgnoreCase(method) || "204".equals(status) || "304"
.equals(status));
}
public static void buffer(StreamingRequest request,
MutableBufferedRequest buff, int max) throws IOException,
SizeLimitExceededException {
buff.setTarget(request.getTarget());
buff.setSsl(request.isSsl());
buffer((StreamingMessage) request, buff, max);
}
public static void buffer(StreamingResponse response,
MutableBufferedResponse buff, int max) throws IOException,
SizeLimitExceededException {
buffer((StreamingMessage) response, buff, max);
}
private static void buffer(StreamingMessage message,
MutableBufferedMessage buffered, int max) throws IOException,
SizeLimitExceededException {
buffered.setHeader(message.getHeader());
InputStream in = message.getContent();
if (in != null) {
ByteArrayOutputStream copy;
copy = new SizeLimitedByteArrayOutputStream(max);
byte[] b = new byte[1024];
int got;
try {
while ((got = in.read(b)) > -1) {
copy.write(b, 0, got);
}
buffered.setContent(copy.toByteArray());
} catch (SizeLimitExceededException slee) {
buffered.setContent(copy.toByteArray());
throw slee;
} catch (IOException e) {
System.err.println("Error while reading content: " + e);
// no content to set
}
}
}
public static void stream(BufferedRequest request, StreamingRequest stream) {
stream.setTarget(request.getTarget());
stream.setSsl(request.isSsl());
stream((BufferedMessage) request, stream);
}
public static void stream(BufferedResponse response,
StreamingResponse stream) {
stream((BufferedMessage) response, stream);
}
private static void stream(BufferedMessage message, StreamingMessage stream) {
stream.setHeader(message.getHeader());
byte[] content = message.getContent();
if (content != null && content.length > 0)
stream.setContent(new ByteArrayInputStream(content));
}
public static void delayedCopy(final StreamingRequest message,
final MutableBufferedRequest copy, int max,
final DelayedCopyObserver observer) {
copy.setTarget(message.getTarget());
copy.setSsl(message.isSsl());
delayedCopy((StreamingMessage) message, (MutableBufferedMessage) copy,
max, new DelayedCopyObserver() {
@Override
public void copyCompleted(boolean overflow, int size) {
copy.setTime(message.getTime());
observer.copyCompleted(overflow, size);
}
});
}
public static void delayedCopy(final StreamingResponse message,
final MutableBufferedResponse copy, int max,
final DelayedCopyObserver observer) {
copy.setHeaderTime(message.getHeaderTime());
delayedCopy((StreamingMessage) message, (MutableBufferedMessage) copy,
max, new DelayedCopyObserver() {
@Override
public void copyCompleted(boolean overflow, int size) {
copy.setHeaderTime(message.getHeaderTime());
copy.setContentTime(message.getContentTime());
observer.copyCompleted(overflow, size);
}
});
}
private static void delayedCopy(StreamingMessage message,
final MutableBufferedMessage copy, final int max,
final DelayedCopyObserver observer) {
if (observer == null)
throw new NullPointerException("Observer may not be null");
copy.setHeader(message.getHeader());
InputStream content = message.getContent();
if (content == null) {
observer.copyCompleted(false, 0);
} else {
final ByteArrayOutputStream copyContent = new SizeLimitedByteArrayOutputStream(
max) {
@Override
protected void overflow() {
// do not throw an exception
}
};
content = new CopyInputStream(content, copyContent);
content = new CountingInputStream(content) {
protected void eof() {
copy.setContent(copyContent.toByteArray());
observer.copyCompleted(getCount() > max, getCount());
}
};
message.setContent(content);
}
}
public static abstract class DelayedCopyObserver {
public abstract void copyCompleted(boolean overflow, int size);
}
public static boolean isExpectContinue(RequestHeader request)
throws MessageFormatException {
return "100-continue".equalsIgnoreCase(request.getHeader("Expect"));
}
}