Skip to content

Commit

Permalink
Add WAV content filter
Browse files Browse the repository at this point in the history
  • Loading branch information
torusrxxx committed Sep 25, 2024
1 parent 523d84b commit 5bb6fff
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/freenet/client/DefaultMIMETypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ public synchronized static short byName(String s) {
addMIMEType((short)439, "audio/x-realaudio", "ra");
addMIMEType((short)440, "audio/x-scpls", "pls");
addMIMEType((short)441, "audio/x-sd2", "sd2");
addMIMEType((short)442, "audio/x-wav", "wav");
addMIMEType((short)442, "audio/vnd.wave", "wav");
addMIMEType((short)443, "chemical/x-pdb", "pdb");
addMIMEType((short)444, "chemical/x-xyz", "xyz");
addMIMEType((short)445, "image/cgm");
Expand Down Expand Up @@ -755,6 +755,9 @@ public synchronized static short byName(String s) {
addMIMEType((short)620, "audio/ogg", "oga");
addMIMEType((short)621, "audio/flac", "flac");
addMIMEType((short)622, "image/webp", "webp");
addMIMEType((short)623, "image/avif", "avif");
addMIMEType((short)624, "image/heic", "heic");
addMIMEType((short)625, "image/heif", "heif");
}

/** Guess a MIME type from a filename.
Expand Down
10 changes: 9 additions & 1 deletion src/freenet/client/filter/ContentFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public static void init() {
l10n("textPlainReadAdvice"),
true, "US-ASCII", null, false));

// Images
// GIF - has a filter
register(new FilterMIMEType("image/gif", "gif", new String[0], new String[0],
true, false, new GIFFilter(), false, false, false, false, false, false,
Expand All @@ -74,7 +75,6 @@ true, false, new PNGFilter(true, true, true), false, false, false, false, true,
l10n("imagePngReadAdvice"),
false, null, null, false));


// BMP - has a filter
// Reference: http://filext.com/file-extension/BMP
register(new FilterMIMEType("image/bmp", "bmp", new String[] { "image/x-bmp","image/x-bitmap","image/x-xbitmap","image/x-win-bitmap","image/x-windows-bmp","image/ms-bmp","image/x-ms-bmp","application/bmp","application/x-bmp","application/x-win-bitmap" }, new String[0],
Expand All @@ -88,6 +88,7 @@ true, false, new WebPFilter(), false, false, false, false, true, false,
l10n("imageWebPReadAdvice"),
false, null, null, false));

// Audio
/* Ogg - has a filter
* Xiph's container format. Contains one or more logical bitstreams.
* Each type of bitstream will likely require additional processing,
Expand Down Expand Up @@ -123,6 +124,11 @@ false, false, new M3UFilter(), false, false, false, false, false, false,
register(new FilterMIMEType("audio/mpeg", "mp3", new String[] {"audio/mp3", "audio/x-mp3", "audio/x-mpeg", "audio/mpeg3", "audio/x-mpeg3", "audio/mpg", "audio/x-mpg", "audio/mpegaudio"},
new String[0], true, false, new MP3Filter(), true, true, false, true, false, false,
l10n("audioMP3ReadAdvice"), false, null, null, false));

// WAV - has a filter
register(new FilterMIMEType("audio/vnd.wave", "mp3", new String[] {"audio/vnd.wave", "audio/x-wav", "audio/wav", "audio/wave"},
new String[0], true, false, new WAVFilter(), true, true, false, true, false, false,
l10n("audioWAVReadAdvice"), false, null, null, false));

// ICO needs filtering.
// Format is not the same as BMP iirc.
Expand Down Expand Up @@ -546,6 +552,8 @@ public static String mimeTypeForSrc(String uriold) {
subMimetype = "video/ogg";
} else if (uriPath.endsWith(".ogg")) {
subMimetype = "application/ogg";
} else if (uriPath.endsWith(".wav")) {
subMimetype = "audio/vnd.wave";
} else { // force mp3 for anything we do not know
subMimetype = "audio/mpeg";
}
Expand Down
127 changes: 127 additions & 0 deletions src/freenet/client/filter/WAVFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package freenet.client.filter;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Map;

import freenet.client.filter.WebPFilter.WebPFilterContext;
import freenet.l10n.NodeL10n;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;

public class WAVFilter extends RIFFFilter {
// RFC 2361
private final int WAVE_FORMAT_UNKNOWN = 0;
private final int WAVE_FORMAT_PCM = 1;
private final int WAVE_FORMAT_IEEE_FLOAT = 3;
private final int WAVE_FORMAT_ALAW = 6;
private final int WAVE_FORMAT_MULAW = 7;

@Override
protected byte[] getChunkMagicNumber() {
return new byte[] {'W', 'A', 'V', 'E'};
}

class WAVFilterContext {
public boolean hasfmt = false;
public boolean hasdata = false;
public int nSamplesPerSec = 0;
public int nChannels = 0;
public int nBlockAlign = 0;
public int wBitsPerSample = 0;
public int format = 0;
}

@Override
protected Object createContext() {
return new WAVFilterContext();
}

@Override
protected void readFilterChunk(byte[] ID, int size, Object context, DataInputStream input, DataOutputStream output,
String charset, Map<String, String> otherParams, String schemeHostAndPort, FilterCallback cb)
throws DataFilterException, IOException {
WAVFilterContext ctx = (WAVFilterContext)context;
if(ID[0] == 'f' && ID[1] == 'm' && ID[2] == 't' && ID[3] == ' ') {
if(ctx.hasfmt) {
throw new DataFilterException(l10n("invalidTitle"), l10n("invalidTitle"), "Unexpected fmt chunk was encountered");
}
if(size != 16 && size != 18 && size != 40) {
throw new DataFilterException(l10n("invalidTitle"), l10n("invalidTitle"), "fmt chunk size is invalid");
}
ctx.format = Short.reverseBytes(input.readShort());
if(ctx.format != WAVE_FORMAT_PCM && ctx.format != WAVE_FORMAT_IEEE_FLOAT && ctx.format != WAVE_FORMAT_ALAW && ctx.format != WAVE_FORMAT_MULAW) {
throw new DataFilterException(l10n("invalidTitle"), l10n("invalidTitle"), "WAV file uses a not yet supported format");
}
ctx.nChannels = Short.reverseBytes(input.readShort());
output.write(ID);
writeLittleEndianInt(output, size);
output.writeInt((Short.reverseBytes((short) ctx.format) << 16) | Short.reverseBytes((short) ctx.nChannels));
ctx.nSamplesPerSec = readLittleEndianInt(input);
writeLittleEndianInt(output, ctx.nSamplesPerSec);
int nAvgBytesPerSec = readLittleEndianInt(input);
writeLittleEndianInt(output, nAvgBytesPerSec);
ctx.nBlockAlign = Short.reverseBytes(input.readShort());
ctx.wBitsPerSample = Short.reverseBytes(input.readShort());
output.writeInt((Short.reverseBytes((short) ctx.nBlockAlign) << 16) | Short.reverseBytes((short) ctx.wBitsPerSample));
ctx.hasfmt = true;
if(size > 16) {
short cbSize = Short.reverseBytes(input.readShort());
if(cbSize + 18 != size) {
throw new DataFilterException(l10n("invalidTitle"), l10n("invalidTitle"), "fmt chunk size is invalid");
}
output.writeShort(Short.reverseBytes(cbSize));
}
if(size > 18) {
// wValidBitsPerSample, dwChannelMask, and SubFormat GUID
passthroughBytes(input, output, 22);
}
// Further checks
if((ctx.format == WAVE_FORMAT_ALAW || ctx.format == WAVE_FORMAT_MULAW) && ctx.wBitsPerSample != 8) {
throw new DataFilterException(l10n("invalidTitle"), l10n("invalidTitle"), "Unexpected bits per sample value");
}
return;
} else if(!ctx.hasfmt) {
throw new DataFilterException(l10n("invalidTitle"), l10n("invalidTitle"), "Unexpected header chunk was encountered, instead of fmt chunk");
}
if(ID[0] == 'd' && ID[1] == 'a' && ID[2] == 't' && ID[3] == 'a') {
if(ctx.format == WAVE_FORMAT_PCM || ctx.format == WAVE_FORMAT_IEEE_FLOAT || ctx.format == WAVE_FORMAT_ALAW || ctx.format == WAVE_FORMAT_MULAW) {
// Safe format, pass through
output.write(ID);
writeLittleEndianInt(output, size);
passthroughBytes(input, output, size);
if((size & 1) != 0) // Add padding if necessary
output.writeByte(input.readByte());
ctx.hasdata = true;
} else {
throw new DataFilterException(l10n("invalidTitle"), l10n("invalidTitle"), "Data format is not yet supported");
}
} else if(ID[0] == 'f' && ID[1] == 'a' && ID[2] == 'c' && ID[3] == 't') {
if(size < 4) {
throw new DataFilterException(l10n("invalidTitle"), l10n("invalidTitle"), "fact chunk must contain at least 4 bytes");
}
// Just dwSampleLength (Number of samples) here, pass through
output.write(ID);
writeLittleEndianInt(output, size);
passthroughBytes(input, output, size);
if((size & 1) != 0) // Add padding if necessary
output.writeByte(input.readByte());
} else {
// Unknown block
writeJunkChunk(input, output, size);
}
}

@Override
protected void EOFCheck(Object context) throws DataFilterException {
WAVFilterContext ctx = (WAVFilterContext)context;
if(!ctx.hasfmt || !ctx.hasdata) {
throw new DataFilterException(l10n("invalidTitle"), l10n("invalidTitle"), "WAV file is missing fmt chunk or data chunk");
}
}

private static String l10n(String key) {
return NodeL10n.getBase().getString("WAVFilter."+key);
}
}
2 changes: 2 additions & 0 deletions src/freenet/l10n/freenet.l10n.en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ ContentDataFilter.warningUnknownCharsetTitle=Warning: Unknown character set (${c
ContentFilter.applicationPdfReadAdvice=Adobe(R) PDF document - VERY DANGEROUS!
ContentFilter.audioM3UReadAdvice=MP3 music/audio file - probably not dangerous but can contain metadata which might include URLs of unencrypted content; the filter will strip these out
ContentFilter.audioMP3ReadAdvice=MP3 music/audio file - probably not dangerous but can contain metadata which might include URLs of unencrypted content; the filter will strip these out
ContentFilter.audioWAVReadAdvice=WAV music/audio file - probably not dangerous.
ContentFilter.EOFMessage=Unexpected end of file
ContentFilter.EOFDescription=The filter needed more data from the file you were accessing than was available. The file may be malformed or corrupted.
ContentFilter.audioFLACReadAdvice=FLAC audio format - Dangerous. May contain off Freenet links to album art. If followed, these links can harm anonymity.
Expand Down Expand Up @@ -2248,6 +2249,7 @@ UserAlertsToadlet.title=Status messages
UserAlertsToadlet.noMessages=No messages
VorbisBitstreamFilter.MalformedTitle=Malformed Vorbis Bitstream
VorbisBitstreamFilter.MalformedMessage=The Vorbis bitstream is not correctly formatted, and could not be properly validated.
WAVFilter.invalidTitle=Invalid WAV file
WebPFilter.animUnsupportedTitle=WebP animation is currently not supported
WebPFilter.animUnsupported=WebP animation is currently not supported by the filter, because it could contain frames using the lossless encoding. WebP lossless format has known buffer overflow exploit. When viewed on unpatched browsers and applications, it can damage the security of the system. Therefore, the content filter cannot ensure the safety of this animation.
WebPFilter.alphUnsupportedTitle=WebP alpha channel with lossless compression is currently not supported
Expand Down
45 changes: 45 additions & 0 deletions test/freenet/client/filter/WAVFilterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package freenet.client.filter;

import static freenet.client.filter.ResourceFileUtil.resourceToBucket;
import static org.junit.Assert.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;

import org.junit.Test;

import freenet.support.api.Bucket;
import freenet.support.io.ArrayBucket;
import freenet.support.io.BucketTools;

/**
* Unit test for (parts of) {@link WAVFilter}.
*/
public class WAVFilterTest {

@Test
public void testValidWAV() throws IOException {
Bucket input = resourceToBucket("./wav/test.wav");
Bucket output = filterWAV(input);

//Filter should return the original
assertEquals("Input and output should be the same length", input.size(), output.size());
assertArrayEquals("Input and output are not identical", BucketTools.toByteArray(input), BucketTools.toByteArray(output));
}

private Bucket filterWAV(Bucket input) throws IOException {
WAVFilter objWAVFilter = new WAVFilter();
Bucket output = new ArrayBucket();
try (
InputStream inStream = input.getInputStream();
OutputStream outStream = output.getOutputStream()
) {
objWAVFilter.readFilter(inStream, outStream, "", null, null, null);
}
return output;
}
}
Binary file added test/freenet/client/filter/wav/test.wav
Binary file not shown.

0 comments on commit 5bb6fff

Please sign in to comment.