[freenet-cvs] r17725 - trunk/freenet/src/freenet/clients/http/filter

nextgens at freenetproject.org nextgens at freenetproject.org
Fri Feb 8 23:05:53 UTC 2008


Author: nextgens
Date: 2008-02-08 23:05:53 +0000 (Fri, 08 Feb 2008)
New Revision: 17725

Modified:
   trunk/freenet/src/freenet/clients/http/filter/ContentFilter.java
   trunk/freenet/src/freenet/clients/http/filter/PNGFilter.java
Log:
PNGFilter: improved filter stripping out comments, timestamps and invalid chunks on demand

Modified: trunk/freenet/src/freenet/clients/http/filter/ContentFilter.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/filter/ContentFilter.java	2008-02-08 22:49:26 UTC (rev 17724)
+++ trunk/freenet/src/freenet/clients/http/filter/ContentFilter.java	2008-02-08 23:05:53 UTC (rev 17725)
@@ -54,7 +54,7 @@
 		
 		// PNG - has a filter
 		register(new MIMEType("image/png", "png", new String[0], new String[0],
-				true, false, new PNGFilter(), null, false, false, false, false, true, false,
+				true, false, new PNGFilter(false, false, false)/* FIXME: reenable when they work */, null, false, false, false, false, true, false,
 				l10n("imagePngReadAdvice"),
 				l10n("imagePngWriteAdvice"), false, null, null));
 		

Modified: trunk/freenet/src/freenet/clients/http/filter/PNGFilter.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/filter/PNGFilter.java	2008-02-08 22:49:26 UTC (rev 17724)
+++ trunk/freenet/src/freenet/clients/http/filter/PNGFilter.java	2008-02-08 23:05:53 UTC (rev 17725)
@@ -11,23 +11,73 @@
 import java.util.HashMap;
 
 import freenet.l10n.L10n;
+import freenet.support.CRC;
+import freenet.support.Fields;
 import freenet.support.HTMLNode;
+import freenet.support.HexUtil;
+import freenet.support.Logger;
+import freenet.support.LoggerHook.InvalidThresholdException;
 import freenet.support.api.Bucket;
 import freenet.support.api.BucketFactory;
+import freenet.support.io.ArrayBucketFactory;
+import freenet.support.io.BucketTools;
 import freenet.support.io.Closer;
+import freenet.support.io.FileBucket;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
 
 /**
  * Content filter for PNG's.
  * This one just verifies that a PNG is valid, and throws if it isn't.
+ *
+ * It can strip the timestamp and "text(.)*" chunks if asked to
+ * 
+ * FIXME: should be a whitelisting filter instead of a blacklisting one
  */
 public class PNGFilter implements ContentDataFilter {
 
-	static final byte[] pngHeader = 
-		{ (byte)137, (byte)80, (byte)78, (byte)71, (byte)13, (byte)10, (byte)26, (byte)10 };
-	
+	private final boolean deleteText;
+	private final boolean deleteTimestamp;
+	private final boolean checkCRCs; // need a fair amount of memory
+	static final byte[] pngHeader =
+		{(byte) 137, (byte) 80, (byte) 78, (byte) 71, (byte) 13, (byte) 10, (byte) 26, (byte) 10};
+
+	PNGFilter(boolean deleteText, boolean deleteTimestamp, boolean checkCRCs) {
+		this.deleteText = deleteText;
+		this.deleteTimestamp = deleteTimestamp;
+		this.checkCRCs = checkCRCs;
+	}
+
 	public Bucket readFilter(Bucket data, BucketFactory bf, String charset,
-			HashMap otherParams, FilterCallback cb) throws DataFilterException,
-			IOException {
+		HashMap otherParams, FilterCallback cb) throws DataFilterException,
+		IOException {
+		Bucket output = readFilter(data, bf, charset, otherParams, cb, deleteText, deleteTimestamp, checkCRCs, null);
+		if(output != null)
+			return output;
+		if(Logger.shouldLog(Logger.MINOR, this))
+			Logger.minor(this, "Need to modify PNG...");
+		Bucket filtered = bf.makeBucket(data.size());
+		OutputStream os = new BufferedOutputStream(filtered.getOutputStream());
+		try {
+			readFilter(data, bf, charset, otherParams, cb, deleteText, deleteTimestamp, checkCRCs, os);
+			os.flush();
+			os.close();
+		} finally {
+			Closer.close(os);
+		}
+		return filtered;
+	}
+
+	public Bucket readFilter(Bucket data, BucketFactory bf, String charset,
+		HashMap otherParams, FilterCallback cb, boolean deleteText, boolean deleteTimestamp, boolean checkCRCs, OutputStream output) throws DataFilterException,
+		IOException {
+		boolean logMINOR = Logger.shouldLog(Logger.MINOR, this);
+		boolean logDEBUG = Logger.shouldLog(Logger.DEBUG, this);
 		InputStream is = null;
 		BufferedInputStream bis = null;
 		DataInputStream dis = null;
@@ -43,26 +93,170 @@
 				String message = l10n("invalidHeader");
 				String title = l10n("invalidHeaderTitle");
 				throw new DataFilterException(title, title,
-						"<p>"+message+"</p>", new HTMLNode("p").addChild("#", message));
+					"<p>" + message + "</p>", new HTMLNode("p").addChild("#", message));
 			}
+
+			ByteArrayOutputStream baos = null;
+			DataOutputStream dos = null;
+			if(output != null) {
+				baos = new ByteArrayOutputStream();
+				dos = new DataOutputStream(baos);
+				output.write(pngHeader);
+				if(logMINOR)
+					Logger.minor(this, "Writing the PNG header to the output bucket");
+			}
+
+			// Check the chunks :
+			// We should have an IHDR chunk at the beginning and a IEND at the end
+			boolean finished = false;
+			boolean hasSeenIHDR = false;
+			boolean hasSeenIEND = false;
+
+			while(!finished) {
+				boolean skip = false;
+				if(baos != null)
+					baos.reset();
+				String chunkTypeString = null;
+				// Length of the chunk
+				byte[] lengthBytes = new byte[4];
+				if(dis.read(lengthBytes) < 4)
+					throw new IOException("The length of the chunk is invalid!");
+				
+				int length = ((lengthBytes[0] & 0xff) << 24) + ((lengthBytes[1] & 0xff) << 16) + ((lengthBytes[2] & 0xff) << 8) + (lengthBytes[3] & 0xff);
+				if(logMINOR)
+					Logger.minor(this, "length " + length);
+				if(dos != null)
+					dos.write(lengthBytes);
+
+				// Type of the chunk : Should match [a-zA-Z]{4}
+				if(dis.read(lengthBytes) < 4)
+					throw new IOException("The name of the chunk is invalid!");
+				StringBuffer sb = new StringBuffer();
+				byte[] chunkTypeBytes = new byte[4];
+				for(int i = 0; i < 4; i++) {
+					char val = (char) lengthBytes[i];
+					if((val >= 65 && val <= 99) || (val >= 97 && val <= 122)) {
+						chunkTypeBytes[i] = lengthBytes[i];
+						sb.append(val);
+					} else
+						throw new IOException("The name of the chunk is invalid!");
+				}
+				if(dos != null)
+					dos.write(chunkTypeBytes);
+				chunkTypeString = sb.toString();
+				if(logMINOR)
+					Logger.minor(this, "name " + chunkTypeString);
+
+				// Content of the chunk
+				byte[] chunkData = new byte[length];
+				int readLength = dis.read(chunkData, 0, length);
+				if(readLength < length)
+					throw new IOException("The data in the chunk '" + chunkTypeString + "' is " + readLength + " but should be " + length);
+				if(logMINOR)
+					if(logDEBUG)
+						Logger.minor(this, "data " + (chunkData.length == 0 ? "null" : HexUtil.bytesToHex(chunkData)));
+					else
+						Logger.minor(this, "data " + chunkData.length);
+				if(dos != null)
+					dos.write(chunkData);
+
+				// CRC of the chunk
+				if(dis.read(lengthBytes) < 4)
+					throw new IOException("The length of the CRC is invalid!");
+				if(dos != null)
+					dos.write(lengthBytes);
+
+				if(checkCRCs) {
+					long readCRC = ((lengthBytes[0] & 0xff) << 24) + ((lengthBytes[1] & 0xff) << 16) + ((lengthBytes[2] & 0xff) << 8) + (lengthBytes[3] & 0xff);
+					byte[] toCheck = new byte[chunkTypeBytes.length + chunkData.length];
+					System.arraycopy(toCheck, 0, chunkTypeBytes, 0, chunkTypeBytes.length);
+					System.arraycopy(toCheck, 0, chunkData, 0, chunkData.length);
+					long computedCRC = CRC.crc(toCheck);
+
+					if(readCRC != computedCRC) {
+						skip = true;
+						if(logMINOR)
+							Logger.minor(this, "CRC of the chunk " + chunkTypeString + " doesn't match (" + Long.toHexString(readCRC) + " but should be " + Long.toHexString(computedCRC) + ")!");
+					}
+				}
+
+				if(!skip && "IHDR".equals(chunkTypeString)) {
+					if(hasSeenIHDR)
+						throw new IOException("Two IHDR chunks detected!!");
+					hasSeenIHDR = true;
+				}
+
+				if(!hasSeenIHDR)
+					throw new IOException("No IHDR chunk!");
+
+				if(!skip && "IEND".equals(chunkTypeString)) {
+					if(hasSeenIEND)
+						throw new IOException("Two IEND chunks detected!!");
+					hasSeenIEND = true;
+				}
+
+				if(dis.available() < 1) {
+					if(!(hasSeenIEND && hasSeenIHDR))
+						throw new IOException("Missing IEND or IHDR!");
+					finished = true;
+				}
+
+				if(deleteText && "text".equalsIgnoreCase(chunkTypeString))
+					skip = true;
+				else if(deleteTimestamp && "time".equalsIgnoreCase(chunkTypeString))
+					skip = true;
+				
+				if(skip && output == null)
+					return null;
+				else if(!skip && output != null) {
+					if(logMINOR)
+						Logger.minor(this, "Writing " + chunkTypeString + " (" + baos.size() + ") to the output bucket");
+					baos.writeTo(output);
+				}
+			}
+			
 			dis.close();
+			if(dos != null) {
+				output.flush();
+				output.close();
+			}
 		} finally {
 			Closer.close(dis);
 			Closer.close(bis);
 			Closer.close(is);
+			Closer.close(output);
 		}
 		return data;
 	}
 
 	private String l10n(String key) {
-		return L10n.getString("PNGFilter."+key);
+		return L10n.getString("PNGFilter." + key);
 	}
 
 	public Bucket writeFilter(Bucket data, BucketFactory bf, String charset,
-			HashMap otherParams, FilterCallback cb) throws DataFilterException,
-			IOException {
+		HashMap otherParams, FilterCallback cb) throws DataFilterException,
+		IOException {
 		// TODO Auto-generated method stub
 		return null;
 	}
 
+	public static void main(String arg[]) {
+		final File fin = new File("/tmp/test.png");
+		final File fout = new File("/tmp/test2.png");
+		fout.delete();
+		final Bucket data = new FileBucket(fin, true, false, false, false, false);
+		final Bucket out = new FileBucket(fout, false, true, false, false, false);
+		try {
+			Logger.setupStdoutLogging(Logger.MINOR, "");
+			ContentFilter.FilterOutput output = ContentFilter.filter(data, new ArrayBucketFactory(), "image/png", new URI("http://127.0.0.1:8888/"), null);
+			BucketTools.copy(output.data, out);
+		} catch(IOException e) {
+			System.out.println("Bucket error?: " + e.getMessage());
+		} catch(URISyntaxException e) {
+			System.out.println("Internal error: " + e.getMessage());
+		} catch(InvalidThresholdException e) {
+		} finally {
+			data.free();
+		}
+	}
 }




More information about the cvs mailing list