package freenet.clients.http;
import java.io.File;
import java.io.FileNotFoundException;
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.HighLevelSimpleClient;
import freenet.client.filter.ContentFilter;
import freenet.client.filter.FilterOperation;
import freenet.client.filter.UnsafeContentTypeException;
import freenet.client.filter.ContentFilter.FilterStatus;
import freenet.l10n.NodeL10n;
import freenet.node.NodeClientCore;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.MultiValueTable;
import freenet.support.api.Bucket;
import freenet.support.api.HTTPRequest;
import freenet.support.api.HTTPUploadedFile;
import freenet.support.io.Closer;
import freenet.support.io.FileBucket;
import freenet.support.io.FileUtil;
/**
* Allows the user to run the content filter on a file and view the result.
*/
public class ContentFilterToadlet extends Toadlet implements LinkEnabledCallback {
public final static String PATH = "/filterfile/";
/**
* What to do the the output from the content filter.
*/
public static enum ResultHandling {
DISPLAY,
SAVE
}
private final NodeClientCore core;
public ContentFilterToadlet(HighLevelSimpleClient client, NodeClientCore clientCore) {
super(client);
this.core = clientCore;
}
@Override
public String path() {
return PATH;
}
public boolean isEnabled (ToadletContext ctx) {
if(ctx == null) return false;
boolean fullAccess = !container.publicGatewayMode() || ctx.isAllowedFullAccess();
return ctx.isAdvancedModeEnabled() && fullAccess;
}
public void handleMethodGET(URI uri, final HTTPRequest request, final ToadletContext ctx)
throws ToadletContextClosedException, IOException, RedirectException {
if (container.publicGatewayMode() && !ctx.isAllowedFullAccess()) {
sendUnauthorizedPage(ctx);
return;
}
PageMaker pageMaker = ctx.getPageMaker();
PageNode page = pageMaker.getPageNode(l10n("pageTitle"), ctx);
HTMLNode pageNode = page.outer;
HTMLNode contentNode = page.content;
contentNode.addChild(ctx.getAlertManager().createSummary());
contentNode.addChild(createContent(pageMaker, ctx));
writeHTMLReply(ctx, 200, "OK", null, pageNode.generate());
}
public void handleMethodPOST(URI uri, final HTTPRequest request, final ToadletContext ctx)
throws ToadletContextClosedException, IOException, RedirectException {
if (container.publicGatewayMode() && !ctx.isAllowedFullAccess()) {
sendUnauthorizedPage(ctx);
return;
}
try {
// Browse... button on filter page
if (request.isPartSet("filter-local")) {
try {
FilterOperation filterOperation = getFilterOperation(request);
ResultHandling resultHandling = getResultHandling(request);
String mimeType = request.getPartAsStringFailsafe("mime-type", 100);
MultiValueTable<String, String> responseHeaders = new MultiValueTable<String, String>();
responseHeaders.put("Location", LocalFileFilterToadlet.PATH
+ "?filter-operation=" + filterOperation
+ "&result-handling=" + resultHandling
+ "&mime-type=" + mimeType);
ctx.sendReplyHeaders(302, "Found", responseHeaders, null, 0);
} catch (BadRequestException e) {
String invalidPart = e.getInvalidRequestPart();
if (invalidPart == "filter-operation") {
writeBadRequestError(l10n("errorMustSpecifyFilterOperationTitle"), l10n("errorMustSpecifyFilterOperation"), ctx, true);
} else if (invalidPart == "result-handling") {
writeBadRequestError(l10n("errorMustSpecifyResultHandlingTitle"), l10n("errorMustSpecifyResultHandling"), ctx, true);
} else {
writeBadRequestError(l10n("errorBadRequestTitle"), l10n("errorBadRequest"), ctx, true);
}
return;
}
// Filter button on local file browser
} else if (request.isPartSet(LocalFileBrowserToadlet.selectFile)) {
handleFilterRequest(request, ctx, core, true);
// Filter File button on filter page
} else if (request.isPartSet("filter-upload")) {
handleFilterRequest(request, ctx, core, false);
} else {
handleMethodGET(uri, new HTTPRequestImpl(uri, "GET"), ctx);
}
} finally {
request.freeParts();
}
}
private HTMLNode createContent(PageMaker pageMaker, ToadletContext ctx) {
InfoboxNode infobox = pageMaker.getInfobox(l10n("filterFile"), "filter-file", true);
HTMLNode filterBox = infobox.outer;
HTMLNode filterContent = infobox.content;
HTMLNode filterForm = ctx.addFormChild(filterContent, PATH, "filterForm");
// apply read filter, write filter, or both
//TODO: radio buttons to select, once ContentFilter supports write filtering
filterForm.addChild("input",
new String[] { "type", "name", "value" },
new String[] { "hidden", "filter-operation", FilterOperation.BOTH.toString() });
// display in browser or save to disk
filterForm.addChild("input",
new String[] { "type", "name", "value" },
new String[] { "radio", "result-handling", ResultHandling.DISPLAY.toString() });
filterForm.addChild("#", l10n("displayResultLabel"));
filterForm.addChild("br");
filterForm.addChild("input",
new String[] { "type", "name", "value" },
new String[] { "radio", "result-handling", ResultHandling.SAVE.toString() });
filterForm.addChild("#", l10n("saveResultLabel"));
filterForm.addChild("br");
filterForm.addChild("br");
// mime type
filterForm.addChild("#", l10n("mimeTypeLabel") + ": ");
filterForm.addChild("input",
new String[] { "type", "name", "value" },
new String[] { "text", "mime-type", "" });
filterForm.addChild("br");
filterForm.addChild("#", l10n("mimeTypeText"));
filterForm.addChild("br");
filterForm.addChild("br");
// file selection
if (ctx.isAllowedFullAccess()) {
filterForm.addChild("#", l10n("filterFileBrowseLabel") + ": ");
filterForm.addChild("input",
new String[] { "type", "name", "value" },
new String[] { "submit", "filter-local", l10n("filterFileBrowseButton") + "..." });
filterForm.addChild("br");
}
filterForm.addChild("#", l10n("filterFileUploadLabel") + ": ");
filterForm.addChild("input",
new String[] { "type", "name", "value" },
new String[] { "file", "filename", "" });
filterForm.addChild("#", " \u00a0 ");
filterForm.addChild("input",
new String[] { "type", "name", "value" },
new String[] { "submit", "filter-upload", l10n("filterFileFilterLabel") });
filterForm.addChild("#", " \u00a0 ");
return filterBox;
}
private void writeBadRequestError(String header, String message, ToadletContext context, boolean returnToFilterPage)
throws ToadletContextClosedException, IOException {
PageMaker pageMaker = context.getPageMaker();
PageNode page = pageMaker.getPageNode(header, context);
HTMLNode pageNode = page.outer;
HTMLNode contentNode = page.content;
if (context.isAllowedFullAccess()) {
contentNode.addChild(context.getAlertManager().createSummary());
}
HTMLNode infoboxContent = pageMaker.getInfobox("infobox-error", header, contentNode, "filter-error", false);
infoboxContent.addChild("#", message);
if (returnToFilterPage) {
NodeL10n.getBase().addL10nSubstitution(infoboxContent.addChild("div"),
"ContentFilterToadlet.tryAgainFilterFilePage", new String[] { "link" },
new HTMLNode[] { HTMLNode.link(ContentFilterToadlet.PATH) });
}
writeHTMLReply(context, 400, "Bad request", pageNode.generate());
}
/**
* Handle a request to filter a file.
*/
private void handleFilterRequest(HTTPRequest request, ToadletContext ctx, NodeClientCore core, boolean localFile)
throws ToadletContextClosedException, IOException {
try {
FilterOperation filterOperation = getFilterOperation(request);
ResultHandling resultHandling = getResultHandling(request);
String mimeType = request.getPartAsStringFailsafe("mime-type", 100);
String filename;
Bucket bucket;
if (localFile) {
filename = request.getPartAsStringFailsafe("filename", QueueToadlet.MAX_FILENAME_LENGTH);
if (mimeType.length() == 0) {
mimeType = DefaultMIMETypes.guessMIMEType(filename, false);
}
File file = new File(filename);
bucket = new FileBucket(file, true, false, false, false);
} else {
HTTPUploadedFile file = request.getUploadedFile("filename");
if (file == null) {
throw new BadRequestException("filename");
}
filename = file.getFilename();
if (mimeType.length() == 0) {
mimeType = file.getContentType();
}
bucket = file.getData();
}
if (filename.length() == 0) {
throw new BadRequestException("filename");
}
String resultFilename = makeResultFilename(filename, mimeType);
try {
handleFilter(bucket, mimeType, filterOperation, resultHandling, resultFilename, ctx, core);
} catch (FileNotFoundException e) {
writeBadRequestError(l10n("errorNoFileOrCannotReadTitle"), l10n("errorNoFileOrCannotRead", "file", filename), ctx, true);
}
} catch (BadRequestException e) {
String invalidPart = e.getInvalidRequestPart();
if (invalidPart == "filter-operation") {
writeBadRequestError(l10n("errorMustSpecifyFilterOperationTitle"), l10n("errorMustSpecifyFilterOperation"), ctx, true);
} else if (invalidPart == "result-handling") {
writeBadRequestError(l10n("errorMustSpecifyResultHandlingTitle"), l10n("errorMustSpecifyResultHandling"), ctx, true);
} else if (invalidPart == "filename") {
writeBadRequestError(l10n("errorNoFileSelectedTitle"), l10n("errorNoFileSelected"), ctx, true);
} else {
writeBadRequestError(l10n("errorBadRequestTitle"), l10n("errorBadRequest"), ctx, true);
}
}
}
private FilterOperation getFilterOperation(HTTPRequest request)
throws BadRequestException {
String s = request.getPartAsStringFailsafe("filter-operation", 100);
try {
return FilterOperation.valueOf(s);
} catch (IllegalArgumentException e) {
throw new BadRequestException("filter-operation", e);
}
}
private ResultHandling getResultHandling(HTTPRequest request)
throws BadRequestException {
String s = request.getPartAsStringFailsafe("result-handling", 100);
try {
return ResultHandling.valueOf(s);
} catch (IllegalArgumentException e) {
throw new BadRequestException("result-handling", e);
}
}
private String makeResultFilename(String originalFilename, String mimeType) {
String filteredFilename;
int p = originalFilename.indexOf('.', 1);
if (p > 0) {
filteredFilename = originalFilename.substring(0, p) + ".filtered" + originalFilename.substring(p);
} else {
filteredFilename = originalFilename + ".filtered";
}
filteredFilename = FileUtil.sanitize(filteredFilename, mimeType);
return filteredFilename;
}
private void handleFilter(Bucket data, String mimeType, FilterOperation operation, ResultHandling resultHandling, String resultFilename, ToadletContext ctx, NodeClientCore core)
throws ToadletContextClosedException, IOException, BadRequestException {
Bucket resultBucket = ctx.getBucketFactory().makeBucket(-1);
String resultMimeType = null;
boolean unsafe = false;
try {
FilterStatus status = applyFilter(data, resultBucket, mimeType, operation, core);
resultMimeType = status.mimeType;
} catch (UnsafeContentTypeException e) {
unsafe = true;
} catch (IOException e) {
Logger.error(this, "IO error running content filter", e);
throw e;
}
if (unsafe) {
sendErrorPage(ctx, 200, l10n("errorUnsafeContentTitle"), l10n("errorUnsafeContent"));
} else {
if (resultHandling == ResultHandling.DISPLAY) {
ctx.sendReplyHeaders(200, "OK", null, resultMimeType, resultBucket.size());
ctx.writeData(resultBucket);
} else if (resultHandling == ResultHandling.SAVE) {
MultiValueTable<String, String> headers = new MultiValueTable<String, String>();
headers.put("Content-Disposition", "attachment; filename=\"" + resultFilename + '"');
headers.put("Cache-Control", "private");
headers.put("Content-Transfer-Encoding", "binary");
ctx.sendReplyHeaders(200, "OK", headers, "application/force-download", resultBucket.size());
ctx.writeData(resultBucket);
} else {
throw new BadRequestException("result-handling");
}
}
}
private FilterStatus applyFilter(Bucket input, Bucket output, String mimeType, FilterOperation operation, NodeClientCore core)
throws UnsafeContentTypeException, IOException {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = input.getInputStream();
outputStream = output.getOutputStream();
return applyFilter(inputStream, outputStream, mimeType, operation, core);
} finally {
Closer.close(inputStream);
Closer.close(outputStream);
}
}
private FilterStatus applyFilter(InputStream input, OutputStream output, String mimeType, FilterOperation operation, NodeClientCore core)
throws UnsafeContentTypeException, IOException {
URI fakeUri;
try {
fakeUri = new URI("http://127.0.0.1:8888/");
} catch (URISyntaxException e) {
Logger.error(ContentFilterToadlet.class, "Inexplicable URI error", e);
return null;
}
//TODO: check operation, once ContentFilter supports write filtering
return ContentFilter.filter(input, output, mimeType, fakeUri, null, null, null,
core.getLinkFilterExceptionProvider());
}
static String l10n(String key) {
return NodeL10n.getBase().getString("ContentFilterToadlet." + key);
}
static String l10n(String key, String pattern, String value) {
return NodeL10n.getBase().getString("ContentFilterToadlet." + key, pattern, value);
}
}