Adds a `Content-Type` header to image files.
This allows images to be rendered by the browser.
In particular, it allows SVGs to be used in READMEs.
I have restricted this feature to only work on image files for now,
but it might make sense to send this header for other file types as
well if we can reasonably assume that the browser is able to render
it, such as PDFs and XML documents.
---
Fix the PATCH line
gitsrht/blueprints/repo.py | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index e35937c..230a5f1 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -336,6 +336,25 @@ def resolve_blob(git_repo, ref, path):
return orig_commit, ref, path, blob, entry
+MIME_TYPES = {
+ "avif": "image/avif",
+ "gif": "image/gif",
+ "jpeg": "image/jpeg",
+ "jpg": "image/jpeg",
+ "png": "image/png",
+ "svg": "image/svg+xml",
+ "webp": "image/webp",
+}
+
+def resolve_mimetype(path, blob):
+ filename = path[-1]
+ for ext, mimetype in MIME_TYPES.items():
+ if filename.endswith('.' + ext):
+ return mimetype
+ if not blob.is_binary:
+ return "text/plain"
+ return None
+
@repo.route("/<owner>/<repo>/blob/<path:ref>/<path:path>")
def raw_blob(owner, repo, ref, path):
owner, repo = get_repo_or_redir(owner, repo)
@@ -345,7 +364,7 @@ def raw_blob(owner, repo, ref, path):
return send_file(BytesIO(blob.data),
as_attachment=blob.is_binary,
download_name=entry.name,
- mimetype="text/plain" if not blob.is_binary else None)
+ mimetype=resolve_mimetype(path, blob))
def _lookup_user(email, cache):
if email not in cache:
--
2.40.1
---
Fix the PATCH line
This fixes the XSS vulnerability mentioned by Umar Getagazov
gitsrht/blueprints/repo.py | 9 +++++++--
gitsrht/templates/utils.html | 2 +-
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py
index 230a5f1..ef379b2 100644
--- a/gitsrht/blueprints/repo.py
+++ b/gitsrht/blueprints/repo.py
@@ -6,7 +6,7 @@ import pygments
import subprocess
import sys
from datetime import datetime, timedelta
-from flask import Blueprint, render_template, abort, current_app, send_file, request
+from flask import Blueprint, render_template, abort, current_app, send_file, make_response, request
from flask import Response, url_for, session, redirect
from gitsrht.editorconfig import EditorConfig
from gitsrht.git import Repository as GitRepository, commit_time, annotate_tree
@@ -361,10 +361,15 @@ def raw_blob(owner, repo, ref, path):
with GitRepository(repo.path) as git_repo:
orig_commit, ref, path, blob, entry = resolve_blob(git_repo, ref, path)
- return send_file(BytesIO(blob.data),
+ response = send_file(BytesIO(blob.data),
as_attachment=blob.is_binary,
download_name=entry.name,
mimetype=resolve_mimetype(path, blob))
+ response = make_response(response)
+ # Do not allow any other resources, including scripts, to be loaded from this resourse
+ # This prevents XSS attacks in SVG files!
+ response.headers['Content-Security-Policy'] = "upgrade-insecure-requests; sandbox; frame-src 'none'; media-src 'none'; script-src 'none'; object-src 'none'; worker-src 'none';"
+ return response
def _lookup_user(email, cache):
if email not in cache:
diff --git a/gitsrht/templates/utils.html b/gitsrht/templates/utils.html
index 72c6629..3b9e2d5 100644
--- a/gitsrht/templates/utils.html
+++ b/gitsrht/templates/utils.html
@@ -62,7 +62,7 @@ endif %}{% endfor %}
{% endif %}
<li class="nav-item">
- <a class="nav-link"
+ <a class="nav-link" rel="noopener noreferrer nofollow"
href="{{url_for("repo.raw_blob", owner=repo.owner.canonical_name,
repo=repo.name, ref=ref, path=path)}}">View raw</a>
</li>
--
2.40.1