diff --git a/docker/Dockerfile b/docker/Dockerfile
index 831627ec8..a1dc565f4 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -51,7 +51,7 @@ ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 \
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
- curl jq bash tzdata fontconfig tini libmagic1 \
+ curl jq bash tzdata fontconfig tini libmagic1 media-types \
libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf-2.0-0 \
libharfbuzz0b libfreetype6 libjpeg62-turbo zlib1g fonts-dejavu-core; \
if [ "$WITH_GRAPHVIZ" = "1" ]; then apt-get install -y --no-install-recommends graphviz; fi; \
diff --git a/docker/config/nginx/sapl.conf b/docker/config/nginx/sapl.conf
index 7e0802ef1..926adc37d 100644
--- a/docker/config/nginx/sapl.conf
+++ b/docker/config/nginx/sapl.conf
@@ -66,7 +66,7 @@ server {
# Demais arquivos são públicos e servidos direto do disco; conteúdo que o
# navegador executaria como página é forçado a download.
- location ~* ^/media/.+\.(html?|xhtml|xht|shtml|svgz?|xml|xsl|xslt|rdf|js|mjs|mht|mhtml|rss|atom|mml)$ {
+ location ~* ^/media/.+\.(html?|xhtml|xht|shtml|svgz?|xml|xsl|xslt|rdf|js|mjs|mht|mhtml|rss|atom|mml|kml|xspf)$ {
root /var/interlegis/sapl;
types { }
default_type application/octet-stream;
diff --git a/sapl/base/tests/test_media.py b/sapl/base/tests/test_media.py
index ee7a27446..de4ac7c94 100644
--- a/sapl/base/tests/test_media.py
+++ b/sapl/base/tests/test_media.py
@@ -17,6 +17,7 @@ from sapl.base.models import AppConfig, Autor
from sapl.materia.models import Proposicao
from sapl.protocoloadm.models import (DocumentoAcessorioAdministrativo,
DocumentoAdministrativo)
+from sapl.utils import EXTENSOES_ATIVAS
DOC_PATH = 'sapl/private/documentoadministrativo/2026/1/texto.pdf'
ACESSORIO_PATH = 'sapl/public/documentoacessorioadministrativo/2026/1/a.pdf'
@@ -237,7 +238,7 @@ def test_arquivo_publico_passa_por_x_accel(client):
@pytest.mark.django_db(transaction=False)
@pytest.mark.parametrize('nome', ['x.svg', 'x.html', 'x.XML', 'x.js',
- 'x.xht', 'x.rdf', 'x.xslt'])
+ 'x.xht', 'x.rdf', 'x.xslt', 'x.kml'])
def test_arquivo_publico_ativo_e_baixado(client, nome):
response = _get(client, 'sapl/public/normajuridica/2026/1/' + nome)
@@ -363,3 +364,21 @@ def test_nginx_envia_ao_django_todos_os_caminhos_protegidos():
no_nginx = set(regex.group(1).split('|'))
assert set(RESOLVEDORES) | set(MODELOS_DE_SESSAO) <= no_nginx
assert conf.index(regex.group(0)) < conf.index('location /media/ {')
+
+
+def test_nginx_forca_download_das_extensoes_ativas():
+ caminho = os.path.join(settings.PROJECT_DIR, 'docker', 'config',
+ 'nginx', 'sapl.conf')
+ with open(caminho) as f:
+ conf = f.read()
+ regex = re.search(r'location ~\*? \^/media/\.\+\\\.\(([a-z?|]+)\)\$',
+ conf)
+
+ assert regex, 'location de extensões ativas não encontrada'
+ no_nginx = set()
+ for ext in regex.group(1).split('|'):
+ if ext.endswith('?'):
+ no_nginx |= {ext[:-2], ext[:-1]}
+ else:
+ no_nginx.add(ext)
+ assert {'.' + e for e in no_nginx} == EXTENSOES_ATIVAS
diff --git a/sapl/test_utils.py b/sapl/test_utils.py
index f3956c8fe..1bb724056 100644
--- a/sapl/test_utils.py
+++ b/sapl/test_utils.py
@@ -43,6 +43,7 @@ ATIVOS = [
('x.XHTML', b'%PDF-1.4'),
('x.xht', b'%PDF-1.4\n'),
('x.rdf', b'%PDF-1.4'),
+ ('x.kml', b''),
('x.pdf', b''),
]
diff --git a/sapl/utils.py b/sapl/utils.py
index fca09b993..532594801 100644
--- a/sapl/utils.py
+++ b/sapl/utils.py
@@ -603,10 +603,13 @@ class FilterOverridesMetaMixin:
}
-# Extensões que o navegador pode executar como página na origem do SAPL
+# Extensões que o navegador pode executar como página na origem do SAPL.
+# Não depender só de mimetypes: sem /etc/mime.types (imagem slim) várias
+# destas não são reconhecidas. Manter em sincronia com sapl.conf.
EXTENSOES_ATIVAS = {
- '.html', '.htm', '.xhtml', '.shtml', '.svg', '.svgz', '.xml', '.xsl',
- '.js', '.mht', '.mhtml',
+ '.html', '.htm', '.xhtml', '.xht', '.shtml', '.svg', '.svgz', '.xml',
+ '.xsl', '.xslt', '.rdf', '.js', '.mjs', '.mht', '.mhtml', '.rss', '.atom',
+ '.mml', '.kml', '.xspf',
}
MIMES_ATIVOS = {