/*
* 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.io;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* This class is intended to provide as efficient a method of tracking changes in a series of filters operating on
* InputStreams, as possible.
*
* The idea is that we make a copy of the data read from the first InputStream, which is usually wanted anyway. If we
* have a filter that operates selectively on the InputStream, we "watch" it, with a tag to describe the filter.
*
* Watching the InputStream wraps it in a new InputStream that compares any data read through it with the data read
* through the most recent prior watched stream, or the original InputStream if this is the first watch.
*
* No data is copied unless a discrepancy is noted. At that point, a local copy is created, and future data read from
* the watched inputstream is recorded to the local copy.
*
* A possible enhancement is to record how much of the original data had been read before a discrepancy was noted, and
* avoid copying that data as well.
*
* @author rogan
*
*/
public class ChangeMonitorInputStream extends FilterInputStream {
private Copy original = new Copy();
private List<CopyStream> watches = new ArrayList<CopyStream>();
public ChangeMonitorInputStream(InputStream in) {
super(in);
}
/*
* (non-Javadoc)
*
* @see java.io.FilterInputStream#markSupported()
*/
@Override
public boolean markSupported() {
return false;
}
/*
* (non-Javadoc)
*
* @see java.io.FilterInputStream#read()
*/
@Override
public int read() throws IOException {
int ret = super.read();
if (ret > -1)
original.write(ret);
return ret;
}
/*
* (non-Javadoc)
*
* @see java.io.FilterInputStream#read(byte[], int, int)
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
int ret = super.read(b, off, len);
if (ret > 0)
original.write(b, off, ret);
return ret;
}
public byte[] getOriginal() {
return original.toByteArray();
}
public InputStream watch(InputStream in, String tag) throws IOException {
CopyStream watch = new CopyStream(in, tag, watches.size());
watches.add(watch);
return watch;
}
public CopyStream[] getModifiedStreams() {
List<CopyStream> changed = new ArrayList<CopyStream>();
Iterator<CopyStream> it = watches.iterator();
while (it.hasNext()) {
CopyStream copy = it.next();
if (copy.getCopy() != null)
changed.add(copy);
}
return changed.toArray(new CopyStream[changed.size()]);
}
public class CopyStream extends FilterInputStream {
private int index = 0;
private Copy copy = null;
private boolean changed = false;
private String tag;
private int watchPosition;
public CopyStream(InputStream in, String tag, int position) {
super(in);
this.tag = tag;
this.watchPosition = position;
}
public String getTag() {
return tag;
}
/*
* (non-Javadoc)
*
* @see java.io.FilterInputStream#markSupported()
*/
@Override
public boolean markSupported() {
return false;
}
private Copy getPriorCopy() {
Copy prior = null;
for (int i = watchPosition - 1; i >= 0; i--) {
prior = watches.get(i).getCopy();
if (prior != null)
break;
}
if (prior == null)
prior = original;
return prior;
}
/*
* (non-Javadoc)
*
* @see java.io.FilterInputStream#read()
*/
@Override
public int read() throws IOException {
int ret = super.read();
if (ret > -1) {
if (!changed) {
Copy prior = getPriorCopy();
if (!prior.compare(index, ret)) {
copy(prior);
copy.write(ret);
changed = true;
} else {
index++;
}
} else {
copy.write(ret);
}
}
return ret;
}
private void copy(Copy prior) {
this.copy = new Copy();
prior.copyTo(this.copy, index);
}
/*
* (non-Javadoc)
*
* @see java.io.FilterInputStream#read(byte[], int, int)
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
int ret = super.read(b, off, len);
if (ret > 0) {
if (!changed) {
Copy prior = getPriorCopy();
if (!prior.compare(index, b, off, ret)) {
copy(prior);
copy.write(b, off, ret);
changed = true;
} else {
index += ret;
}
} else {
copy.write(b, off, ret);
}
}
return ret;
}
private Copy getCopy() {
return copy;
}
public byte[] toByteArray() {
return copy == null ? null : copy.toByteArray();
}
}
private static class Copy extends ByteArrayOutputStream {
public boolean compare(int start, byte[] data, int offset, int len) {
if (start + len > this.count)
return false;
for (int i = 0; i < len; i++)
if (this.buf[start + i] != data[offset + i])
return false;
return true;
}
private boolean compare(int start, int b) {
if (start > this.count)
return false;
byte l = buf[start];
if (l == (byte) (b & 0xFF))
return true;
return false;
}
/*
* copies the first len bytes from this Copy to the specified one. We do it this way to avoid making more copies
* of the byte[] than we have to.
*/
public void copyTo(Copy copy, int len) {
copy.write(this.buf, 0, len);
}
}
}