Changeset View
Changeset View
Standalone View
Standalone View
swh/web/browse/views/content.py
# Copyright (C) 2017-2022 The Software Heritage developers | # Copyright (C) 2017-2022 The Software Heritage developers | ||||
# See the AUTHORS file at the top-level directory of this distribution | # See the AUTHORS file at the top-level directory of this distribution | ||||
# License: GNU Affero General Public License version 3, or any later version | # License: GNU Affero General Public License version 3, or any later version | ||||
# See top-level LICENSE file for more information | # See top-level LICENSE file for more information | ||||
import difflib | import difflib | ||||
from distutils.util import strtobool | from distutils.util import strtobool | ||||
import io | |||||
from typing import Any, Dict, Optional | from typing import Any, Dict, Optional | ||||
from django.http import HttpRequest, HttpResponse, JsonResponse | from django.http import FileResponse, HttpRequest, HttpResponse, JsonResponse | ||||
from django.shortcuts import redirect, render | from django.shortcuts import redirect, render | ||||
from swh.model.hashutil import hash_to_hex | from swh.model.hashutil import hash_to_hex | ||||
from swh.model.swhids import ObjectType | from swh.model.swhids import ObjectType | ||||
from swh.web.browse.browseurls import browse_route | from swh.web.browse.browseurls import browse_route | ||||
from swh.web.browse.snapshot_context import get_snapshot_context | from swh.web.browse.snapshot_context import get_snapshot_context | ||||
from swh.web.browse.utils import ( | from swh.web.browse.utils import ( | ||||
content_display_max_size, | content_display_max_size, | ||||
Show All 19 Lines | |||||
from swh.web.utils.typing import ContentMetadata, SWHObjectInfo | from swh.web.utils.typing import ContentMetadata, SWHObjectInfo | ||||
@browse_route( | @browse_route( | ||||
r"content/(?P<query_string>[0-9a-z_:]*[0-9a-f]+)/raw/", | r"content/(?P<query_string>[0-9a-z_:]*[0-9a-f]+)/raw/", | ||||
view_name="browse-content-raw", | view_name="browse-content-raw", | ||||
checksum_args=["query_string"], | checksum_args=["query_string"], | ||||
) | ) | ||||
def content_raw(request: HttpRequest, query_string: str) -> HttpResponse: | def content_raw(request: HttpRequest, query_string: str) -> FileResponse: | ||||
"""Django view that produces a raw display of a content identified | """Django view that produces a raw display of a content identified | ||||
by its hash value. | by its hash value. | ||||
The url that points to it is | The url that points to it is | ||||
:http:get:`/browse/content/[(algo_hash):](hash)/raw/` | :http:get:`/browse/content/[(algo_hash):](hash)/raw/` | ||||
""" | """ | ||||
re_encode = bool(strtobool(request.GET.get("re_encode", "false"))) | re_encode = bool(strtobool(request.GET.get("re_encode", "false"))) | ||||
algo, checksum = query.parse_hash(query_string) | algo, checksum = query.parse_hash(query_string) | ||||
checksum = hash_to_hex(checksum) | checksum = hash_to_hex(checksum) | ||||
content_data = request_content(query_string, max_size=None, re_encode=re_encode) | content_data = request_content(query_string, max_size=None, re_encode=re_encode) | ||||
filename = request.GET.get("filename", None) | filename = request.GET.get("filename", None) | ||||
if not filename: | if not filename: | ||||
filename = "%s_%s" % (algo, checksum) | filename = "%s_%s" % (algo, checksum) | ||||
if ( | if ( | ||||
content_data["mimetype"].startswith("text/") | content_data["mimetype"].startswith("text/") | ||||
or content_data["mimetype"] == "inode/x-empty" | or content_data["mimetype"] == "inode/x-empty" | ||||
): | ): | ||||
response = HttpResponse(content_data["raw_data"], content_type="text/plain") | content_type = "text/plain" | ||||
response["Content-disposition"] = "filename=%s" % filename | as_attachment = False | ||||
else: | else: | ||||
response = HttpResponse( | content_type = "application/octet-stream" | ||||
content_data["raw_data"], content_type="application/octet-stream" | as_attachment = True | ||||
response = FileResponse( | |||||
io.BytesIO(content_data["raw_data"]), # not copied, as this is never modified | |||||
filename=filename, | |||||
content_type=content_type, | |||||
as_attachment=True, | |||||
) | |||||
if not as_attachment: | |||||
# django 2.2.24 used in production does not set Content-Disposition header | |||||
# if as_attachment is False so we use that workaround to preserve old behavior | |||||
# TODO: remove that block once we use upstream django in production | |||||
response["Content-Disposition"] = response["Content-Disposition"].replace( | |||||
"attachment; ", "" | |||||
) | ) | ||||
response["Content-disposition"] = "attachment; filename=%s" % filename | |||||
return response | return response | ||||
_auto_diff_size_limit = 20000 | _auto_diff_size_limit = 20000 | ||||
@browse_route( | @browse_route( | ||||
r"content/(?P<from_query_string>.*)/diff/(?P<to_query_string>.*)/", | r"content/(?P<from_query_string>.*)/diff/(?P<to_query_string>.*)/", | ||||
▲ Show 20 Lines • Show All 387 Lines • Show Last 20 Lines |