diff --git a/sapl/test_utils.py b/sapl/test_utils.py
index 925b0ce8a..9f3002a21 100644
--- a/sapl/test_utils.py
+++ b/sapl/test_utils.py
@@ -1,4 +1,8 @@
-from .utils import listify
+from django.core.exceptions import ValidationError
+import pytest
+
+from .utils import (listify, rejeita_conteudo_ativo,
+ restringe_tipos_de_arquivo_txt, validar_arquivo)
def test_listify():
@@ -8,3 +12,74 @@ def test_listify():
yield 1
yield 2
assert [1, 2] == gen()
+
+
+def _upload(nome, conteudo):
+ import io
+ from django.core.files.uploadedfile import InMemoryUploadedFile
+ return InMemoryUploadedFile(io.BytesIO(conteudo), 'arquivo', nome, None,
+ len(conteudo), None)
+
+
+def _odt():
+ import io
+ import zipfile
+ buf = io.BytesIO()
+ with zipfile.ZipFile(buf, 'w') as z:
+ z.writestr(zipfile.ZipInfo('mimetype'),
+ 'application/vnd.oasis.opendocument.text')
+ z.writestr('content.xml', '')
+ return buf.getvalue()
+
+
+PNG = (b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01'
+ b'\x08\x06\x00\x00\x00\x1f\x15\xc4\x89')
+
+ATIVOS = [
+ ('x.html', b'%PDF-1.4\n'),
+ ('x.html', b''),
+ ('x.svg', b''),
+ ('x.XHTML', b'%PDF-1.4'),
+ ('x.pdf', b''),
+]
+
+
+@pytest.mark.parametrize('nome,conteudo', ATIVOS)
+def test_validar_arquivo_rejeita_conteudo_ativo(nome, conteudo):
+ with pytest.raises(ValidationError):
+ validar_arquivo(_upload(nome, conteudo), 'Arquivo')
+
+
+@pytest.mark.parametrize('nome,conteudo', ATIVOS)
+def test_restringe_tipos_rejeita_conteudo_ativo(nome, conteudo):
+ with pytest.raises(ValidationError):
+ restringe_tipos_de_arquivo_txt(_upload(nome, conteudo))
+
+
+@pytest.mark.parametrize('nome,conteudo', [
+ ('a.pdf', b'%PDF-1.4\n%%EOF'),
+ ('a.csv', b'nome;valor\nx;1\n'),
+ ('a.odt', _odt()),
+ ('a.png', PNG),
+ ('a.xlsx', b'PK\x03\x04' + b'\x00' * 64),
+])
+def test_validar_arquivo_aceita_tipos_comuns(nome, conteudo):
+ validar_arquivo(_upload(nome, conteudo), 'Arquivo')
+
+
+@pytest.mark.parametrize('nome,conteudo', [
+ ('a.pdf', b'%PDF-1.4\n%%EOF'),
+ ('a.csv', b'nome;valor\nx;1\n'),
+ ('a.odt', _odt()),
+])
+def test_restringe_tipos_aceita_documentos(nome, conteudo):
+ restringe_tipos_de_arquivo_txt(_upload(nome, conteudo))
+
+
+def test_validar_arquivo_ignora_arquivo_ja_gravado():
+ from django.db.models.fields.files import FieldFile
+ from sapl.protocoloadm.models import DocumentoAdministrativo
+ campo = DocumentoAdministrativo._meta.get_field('texto_integral')
+ antigo = FieldFile(None, campo,
+ 'sapl/private/documentoadministrativo/x.html')
+ rejeita_conteudo_ativo(antigo)
diff --git a/sapl/utils.py b/sapl/utils.py
index 615fb2f11..8fff1a785 100644
--- a/sapl/utils.py
+++ b/sapl/utils.py
@@ -133,6 +133,7 @@ def num_materias_por_tipo(qs, attr_tipo='tipo'):
def validar_arquivo(arquivo, nome_campo):
+ rejeita_conteudo_ativo(arquivo)
if len(arquivo.name) > 200:
raise ValidationError(
"Certifique-se de que o nome do arquivo no "
@@ -607,6 +608,26 @@ EXTENSOES_ATIVAS = {
'.js', '.mht', '.mhtml',
}
+MIMES_ATIVOS = {
+ 'text/html', 'application/xhtml+xml', 'image/svg+xml', 'text/xml',
+ 'application/xml', 'application/javascript', 'text/javascript',
+}
+
+
+def rejeita_conteudo_ativo(arquivo):
+ """Recusa uploads que o navegador executaria como página, pela extensão
+ ou pelo conteúdo. Arquivos já gravados não são checados, para não impedir
+ a edição de registros antigos."""
+ if not isinstance(arquivo, UploadedFile):
+ return
+ if os.path.splitext(arquivo.name)[1].lower() in EXTENSOES_ATIVAS:
+ raise ValidationError(_('Tipo de arquivo não permitido.'))
+ arquivo.seek(0)
+ mime = magic.from_buffer(arquivo.read(2048), mime=True)
+ arquivo.seek(0)
+ if mime in MIMES_ATIVOS:
+ raise ValidationError(_('Tipo de arquivo não permitido.'))
+
TIPOS_TEXTO_PERMITIDOS = (
'application/vnd.oasis.opendocument.text',
'application/x-vnd.oasis.opendocument.text',
@@ -654,6 +675,7 @@ TIPOS_IMG_PERMITIDOS = (
def fabrica_validador_de_tipos_de_arquivo(lista, nome):
def restringe_tipos_de_arquivo(value):
+ rejeita_conteudo_ativo(value)
filename = value.name if type(value) in (
InMemoryUploadedFile, TemporaryUploadedFile