package freenet.clients.fcp;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import freenet.client.DefaultMIMETypes;
import freenet.client.async.ClientContext;
import freenet.client.filter.ContentFilter;
import freenet.client.filter.FilterOperation;
import freenet.client.filter.UnsafeContentTypeException;
import freenet.client.filter.ContentFilter.FilterStatus;
import freenet.node.FSParseException;
import freenet.node.Node;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
import freenet.support.api.Bucket;
import freenet.support.api.BucketFactory;
import freenet.support.io.Closer;
import freenet.support.io.FileBucket;
/**
* Message for testing the content filter on a file. Server will respond with a FilterResultMessage.
*
* Filter
* Identifier=filter1 // identifier
* Operation=BOTH // READ/WRITE/BOTH (ignored for now)
* MimeType=text/html // required if DataSource=DIRECT
*
* DataSource=DISK // read a file from disk
* Filename=/home/bob/file.html // path to the file
* End
* or
* DataSource=DIRECT // the data is in the message
* DataLength=1000 // 1000 bytes
* Data
*/
public class FilterMessage extends DataCarryingMessage {
public static final String NAME = "Filter";
private final String identifier;
private final FilterOperation operation;
private final DataSource dataSource;
private final String mimeType;
private final long dataLength;
private final String filename;
private final BucketFactory bf;
public FilterMessage(SimpleFieldSet fs, BucketFactory bf) throws MessageInvalidException {
try {
identifier = fs.getString(IDENTIFIER);
} catch (FSParseException e) {
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Must contain an " + IDENTIFIER + " field", null, false);
}
String op;
try {
op = fs.getString("Operation");
} catch (FSParseException e) {
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Must contain an Operation field", identifier, false);
}
try {
operation = FilterOperation.valueOf(op);
} catch (IllegalArgumentException e) {
throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Illegal Operation value", identifier, false);
}
String ds;
try {
ds = fs.getString("DataSource");
} catch (FSParseException e) {
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Must contain a DataSource field", identifier, false);
}
try {
dataSource = DataSource.valueOf(ds);
} catch (IllegalArgumentException e) {
throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Illegal DataSource value", identifier, false);
}
String inputMimeType = fs.get("MimeType");
filename = fs.get("Filename");
if (dataSource == DataSource.DIRECT) {
mimeType = inputMimeType;
if (mimeType == null) {
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Must contain a MimeType field", identifier, false);
}
String dl = fs.get("DataLength");
if (dl == null) {
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Must contain a DataLength field", identifier, false);
}
try {
dataLength = fs.getLong("DataLength");
} catch (FSParseException e) {
throw new MessageInvalidException(ProtocolErrorMessage.ERROR_PARSING_NUMBER, "DataLength field must be a long", identifier, false);
}
} else if (dataSource == DataSource.DISK) {
if (filename == null) {
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Must contain a Filename field", identifier, false);
}
File file = new File(filename);
if (!file.exists()) {
throw new MessageInvalidException(ProtocolErrorMessage.FILE_NOT_FOUND, null, identifier, false);
}
if (!file.isFile()) {
throw new MessageInvalidException(ProtocolErrorMessage.NOT_A_FILE_ERROR, null, identifier, false);
}
if (!file.canRead()) {
throw new MessageInvalidException(ProtocolErrorMessage.COULD_NOT_READ_FILE, null, identifier, false);
}
if (inputMimeType != null) {
mimeType = inputMimeType;
} else {
mimeType = bestGuessMimeType(filename);
if (mimeType == null) {
throw new MessageInvalidException(ProtocolErrorMessage.BAD_MIME_TYPE, "Could not determine MIME type from filename", identifier, false);
}
}
dataLength = -1;
this.bucket = new FileBucket(file, true, false, false, false);
} else {
throw new MessageInvalidException(ProtocolErrorMessage.INVALID_FIELD, "Illegal DataSource value", identifier, false);
}
this.bf = bf;
}
@Override
String getIdentifier() {
return identifier;
}
@Override
boolean isGlobal() {
return false;
}
@Override
long dataLength() {
return dataLength;
}
@Override
public SimpleFieldSet getFieldSet() {
SimpleFieldSet fs = new SimpleFieldSet(true);
fs.putSingle(IDENTIFIER, identifier);
fs.putOverwrite("Operation", operation.name());
fs.putOverwrite("DataSource", dataSource.name());
fs.putOverwrite("MimeType", mimeType);
fs.putOverwrite("Filename", filename);
fs.put("DataLength", dataLength);
return fs;
}
@Override
public String getName() {
return NAME;
}
@Override
public void run(FCPConnectionHandler handler, Node node) throws MessageInvalidException {
if (bucket == null) {
throw new MessageInvalidException(ProtocolErrorMessage.MISSING_FIELD, "Must contain data", identifier, false);
}
Bucket resultBucket;
try {
resultBucket = bf.makeBucket(-1);
} catch (IOException e) {
Logger.error(this, "Failed to create temporary bucket", e);
throw new MessageInvalidException(ProtocolErrorMessage.INTERNAL_ERROR, e.toString(), identifier, false);
}
String resultCharset = null;
String resultMimeType = null;
boolean unsafe = false;
InputStream input = null;
OutputStream output = null;
try {
input = bucket.getInputStream();
output = resultBucket.getOutputStream();
FilterStatus status = applyFilter(input, output, handler.server.core.clientContext);
resultCharset = status.charset;
resultMimeType = status.mimeType;
} catch (UnsafeContentTypeException e) {
unsafe = true;
} catch (IOException e) {
Logger.error(this, "IO error running content filter", e);
throw new MessageInvalidException(ProtocolErrorMessage.INTERNAL_ERROR, e.toString(), identifier, false);
} finally {
Closer.close(input);
Closer.close(output);
}
FilterResultMessage response = new FilterResultMessage(identifier, resultCharset, resultMimeType, unsafe, resultBucket);
handler.outputHandler.queue(response);
}
private FilterStatus applyFilter(InputStream input, OutputStream output, ClientContext clientContext) throws MessageInvalidException, UnsafeContentTypeException, IOException {
URI fakeUri;
try {
fakeUri = new URI("http://127.0.0.1:8888/");
} catch (URISyntaxException e) {
Logger.error(this, "Inexplicable URI error", e);
throw new MessageInvalidException(ProtocolErrorMessage.INTERNAL_ERROR, e.toString(), identifier, false);
}
//TODO: check operation, once ContentFilter supports write filtering
return ContentFilter.filter(input, output, mimeType, fakeUri, null, null, null, clientContext.linkFilterExceptionProvider);
}
private String bestGuessMimeType(String filename)
{
String guessedMimeType = null;
if (filename != null) {
guessedMimeType = DefaultMIMETypes.guessMIMEType(filename, true);
}
return guessedMimeType;
}
}