A validação de tipos de arquivos é uma funcionalidade essencial em diversas aplicações web. Ao verificar corretamente o tipo do arquivo enviado, podemos não apenas filtrar uploads inadequados, mas também prevenir a execução de arquivos maliciosos e evitar que o servidor seja utilizado como armazenamento gratuito.
Não devemos confiar apenas na extensão do arquivo para determinar seu tipo, pois invasores podem renomear arqiuvos executáveis com extensões de imagens ou outros formatos para induzir usuários à execução. Portnato, é necessário um método mais seguro para identificar o tipo real do arquivo.
Similarmente aos arquivos .class do Java, muitos tipos de arquivo possuem sequências de bytes iniciais fixas que os caracterizam. Esses bytes são conhecidos como "números mágicos" e permitem identificar o tipo do arquivo de forma precisa. Por exemplo, o número mágico para arquivos .class é "CAFEBABE".
A seguir, apresentamos uma implementação em Java para determinar o tipo de arquivo com base em seus números mágicos.
- Primeiro, criamos uma enumeração que define os tipos de arquivo e seus respectivos números mágicos:
public enum TipoArquivo {
/** JPEG */
JPEG("FFD8FF"),
/** PNG */
PNG("89504E47"),
/** GIF */
GIF("47494638"),
/** TIFF */
TIFF("49492A00"),
/** Bitmap do Windows */
BMP("424D"),
/** CAD */
DWG("41433130"),
/** Adobe Photoshop */
PSD("38425053"),
/** Rich Text Format */
RTF("7B5C727466"),
/** XML */
XML("3C3F786D6C"),
/** HTML */
HTML("68746D6C3E"),
/** Outlook Express */
DBX("CFAD12FEC5FD746F"),
/** Outlook */
PST("2142444E"),
/** doc;xls;dot;ppt;xla;ppa;pps;pot;msi;sdw;db */
OLE2("0xD0CF11E0A1B11AE1"),
/** Microsoft Word/Excel */
DOC_XLS("D0CF11E0"),
/** Microsoft Access */
MDB("5374616E64617264204A"),
/** Word Perfect */
WPB("FF575043"),
/** Postscript */
EPS_PS("252150532D41646F6265"),
/** Adobe Acrobat */
PDF("255044462D312E"),
/** Senha do Windows */
PWL("E3828596"),
/** ZIP */
ZIP("504B0304"),
/** ARAR */
RAR("52617221"),
/** WAVE */
WAV("57415645"),
/** AVI */
AVI("41564920"),
/** Real Audio */
RAM("2E7261FD"),
/** Real Media */
RM("2E524D46"),
/** Quicktime */
MOV("6D6F6F76"),
/** Windows Media */
ASF("3026B2758E66CF11"),
/** MIDI */
MID("4D546864");
private String valor = "";
private TipoArquivo(String valor) {
this.valor = valor;
}
public String getValor() {
return valor;
}
public void setValor(String valor) {
this.valor = valor;
}
}
- Em seguida, implementamos uma classe utilitária para identificar o tipo do arquivo:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class IdentificadorArquivo {
/** Determina o tipo do arquivo */
public static TipoArquivo identificarTipo(String caminhoArquivo) throws IOException {
// Obtém o cabeçalho do arquivo
String cabecalhoArquivo = obterCabecalhoArquivo(caminhoArquivo);
if (cabecalhoArquivo != null && cabecalhoArquivo.length() > 0) {
cabecalhoArquivo = cabecalhoArquivo.toUpperCase();
TipoArquivo[] tiposArquivo = TipoArquivo.values();
for (TipoArquivo tipo : tiposArquivo) {
if (cabecalhoArquivo.startsWith(tipo.getValor())) {
return tipo;
}
}
}
return null;
}
/** Lê o cabeçalho do arquivo */
private static String obterCabecalhoArquivo(String caminhoArquivo) throws IOException {
byte[] b = new byte[28];
InputStream fluxoEntrada = null;
try {
fluxoEntrada = new FileInputStream(caminhoArquivo);
fluxoEntrada.read(b, 0, 28);
} finally {
if (fluxoEntrada != null) {
fluxoEntrada.close();
}
}
return converterBytesParaHexadecimal(b);
}
/** Converte um array de bytes em string hexadecimal */
public static String converterBytesParaHexadecimal(byte[] src){
StringBuilder construtorString = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
construtorString.append(0);
}
construtorString.append(hv);
}
return construtorString.toString();
}
}
É importante notar que alguns tipos de arquivo podem ter números mágicos iniciais idênticos (como 'D0CF11E0' e 'D0CF11E0A1B11AE1'). Portanto, os números mágicos mais longos devem ser verificados primeiro. A lista de arquivos e números mágicos apresentada não é exaustiva, mas demonstra o conceito fundamental.