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); } } }