package com.limegroup.gnutella.malware;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import org.limewire.bittorrent.bencoding.Token;
import org.limewire.core.settings.FilterSettings;
import org.limewire.io.IOUtils;
import org.limewire.io.InvalidDataException;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.util.Base32;
import org.limewire.util.BEncoder;
import org.limewire.util.StringUtils;
/**
* Encoder/decoder for the base32-encoded, deflated, bencoded string that
* defines dangerous file types for the FileExtensionChecker.
*
* The bencoded data consists of a map with two keys, "m" and "e", which point
* to two lists of equal length. Each element of the "m" list is a lowercase
* UTF-8 string containing the name of a mime type. The corresponding element
* in the "e" list is a list of file extensions, in lowercase UTF-8, without
* leading dots.
*
* A file is misnamed if it has a mime type that indicates a dangerous file
* type but does not have one of the extensions allowed for that type.
*/
class DangerousFileTypeEncoder {
private static final Log LOG =
LogFactory.getLog(DangerousFileTypeEncoder.class);
private final DangerousFileType[] fileTypes;
DangerousFileTypeEncoder() {
DangerousFileType[] ft;
try {
ft = decodeSetting(FilterSettings.DANGEROUS_FILE_TYPES.get());
} catch(InvalidDataException e) {
ft = new DangerousFileType[0];
}
fileTypes = ft;
}
/**
* Returns true if the given mime type is allowed to have the given
* extension.
*/
boolean isAllowed(String mimeType, String extension) {
for(DangerousFileType fileType : fileTypes) {
if(fileType.mimeType.equals(mimeType)) {
if(LOG.isDebugEnabled())
LOG.debug("Dangerous mime type " + mimeType);
for(String allowed : fileType.extensions) {
if(extension.equals(allowed)) {
if(LOG.isDebugEnabled())
LOG.debug("Safe extension " + extension);
return true;
}
}
if(LOG.isDebugEnabled())
LOG.debug("Dangerous extension " + extension);
return false;
}
}
if(LOG.isDebugEnabled())
LOG.debug("Safe mime type " + mimeType);
return true;
}
/**
* Encodes a string defining dangerous file types.
*/
static String encodeSetting(DangerousFileType[] types) throws InvalidDataException {
List<String> mimeTypes = new ArrayList<String>();
List<List<String>> extensions = new ArrayList<List<String>>();
HashMap<String, List<?>> payload = new HashMap<String, List<?>>();
payload.put("m", mimeTypes);
payload.put("e", extensions);
for(DangerousFileType d : types) {
mimeTypes.add(d.mimeType);
extensions.add(Arrays.asList(d.extensions));
}
ByteArrayOutputStream zipped = new ByteArrayOutputStream();
DeflaterOutputStream zip = new DeflaterOutputStream(zipped);
try {
BEncoder.getEncoder(zip, true, false, "UTF-8").encodeDict(payload);
zip.flush();
} catch(IOException e) {
LOG.error("Error encoding setting", e);
throw new InvalidDataException(e);
} finally {
IOUtils.close(zip);
}
return Base32.encode(zipped.toByteArray());
}
/**
* Decodes a string defining dangerous file types.
*/
static DangerousFileType[] decodeSetting(String setting) throws InvalidDataException {
if(setting.isEmpty())
return new DangerousFileType[0];
InflaterInputStream unzip = null;
ReadableByteChannel byteChannel = null;
try {
ByteArrayInputStream zipped =
new ByteArrayInputStream(Base32.decode(setting));
unzip = new InflaterInputStream(zipped);
byteChannel = Channels.newChannel(unzip);
Object bencoded = Token.parse(byteChannel, "UTF-8");
if(bencoded == null)
throw new InvalidDataException("No bencoded object");
Map<?, ?> map = (Map)bencoded;
List<?> m = (List)map.get("m");
List<?> e = (List)map.get("e");
if(m == null || e == null)
throw new InvalidDataException("Missing key");
int length = m.size();
if(e.size() != length)
throw new InvalidDataException("List lengths differ");
DangerousFileType[] types = new DangerousFileType[length];
for(int i = 0; i < length; i++) {
String mimeType = StringUtils.getUTF8String((byte[])m.get(i));
if(LOG.isDebugEnabled())
LOG.debug("Mime type: " + mimeType);
List<?> extensions = (List)e.get(i);
String[] ext = new String[extensions.size()];
for(int j = 0; j < ext.length; j++) {
ext[j] = StringUtils.getUTF8String((byte[])extensions.get(j));
if(LOG.isDebugEnabled())
LOG.debug("Extension: " + ext[j]);
}
types[i] = new DangerousFileType(mimeType, ext);
}
return types;
} catch(ClassCastException e) {
LOG.debug("Error decoding setting", e);
throw new InvalidDataException(e);
} catch(Exception e) {
LOG.debug("Error decoding setting", e);
throw new InvalidDataException(e);
} finally {
IOUtils.close(byteChannel);
IOUtils.close(unzip);
}
}
}