mirror of
https://github.com/bringout/oca-storage.git
synced 2026-04-18 03:52:05 +02:00
108 lines
3.8 KiB
Python
108 lines
3.8 KiB
Python
# Copyright 2023 ACSONE SA/NV
|
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
from __future__ import annotations
|
|
|
|
from odoo.http import STATIC_CACHE_LONG, Response, Stream, request
|
|
from odoo.tools import config
|
|
|
|
from .models.ir_attachment import IrAttachment
|
|
|
|
try:
|
|
from werkzeug.utils import secure_filename, send_file as _send_file
|
|
except ImportError:
|
|
from odoo.tools._vendor.send_file import send_file as _send_file
|
|
|
|
|
|
class FsStream(Stream):
|
|
fs_attachment = None
|
|
|
|
@classmethod
|
|
def from_fs_attachment(cls, attachment: IrAttachment) -> FsStream:
|
|
attachment.ensure_one()
|
|
if not attachment.fs_filename:
|
|
raise ValueError("Attachment is not stored into a filesystem storage")
|
|
return cls(
|
|
mimetype=attachment.mimetype,
|
|
download_name=attachment.name,
|
|
conditional=True,
|
|
etag=attachment.checksum,
|
|
type="fs",
|
|
size=attachment.file_size,
|
|
last_modified=attachment["__last_update"],
|
|
fs_attachment=attachment,
|
|
)
|
|
|
|
def read(self):
|
|
if self.type == "fs":
|
|
with self.fs_attachment.open("rb") as f:
|
|
return f.read()
|
|
return super().read()
|
|
|
|
def get_response(
|
|
self,
|
|
as_attachment=None,
|
|
immutable=None,
|
|
content_security_policy="default-src 'none'",
|
|
**send_file_kwargs,
|
|
):
|
|
if self.type != "fs":
|
|
return super().get_response(
|
|
as_attachment=as_attachment, immutable=immutable, **send_file_kwargs
|
|
)
|
|
if as_attachment is None:
|
|
as_attachment = self.as_attachment
|
|
if immutable is None:
|
|
immutable = self.immutable
|
|
# Sanitize the download_name before passing it
|
|
safe_download_name = secure_filename(self.download_name or "")
|
|
send_file_kwargs = {
|
|
"mimetype": self.mimetype,
|
|
"as_attachment": as_attachment,
|
|
"download_name": safe_download_name,
|
|
"conditional": self.conditional,
|
|
"etag": self.etag,
|
|
"last_modified": self.last_modified,
|
|
"max_age": STATIC_CACHE_LONG if immutable else self.max_age,
|
|
"environ": request.httprequest.environ,
|
|
"response_class": Response,
|
|
}
|
|
use_x_sendfile = self._fs_use_x_sendfile
|
|
# The file will be closed by werkzeug...
|
|
send_file_kwargs["use_x_sendfile"] = use_x_sendfile
|
|
if not use_x_sendfile:
|
|
f = self.fs_attachment.open("rb")
|
|
res = _send_file(f, **send_file_kwargs)
|
|
else:
|
|
x_accel_redirect = (
|
|
f"/{self.fs_attachment.fs_storage_code}{self.fs_attachment.fs_url_path}"
|
|
)
|
|
send_file_kwargs["use_x_sendfile"] = True
|
|
res = _send_file("", **send_file_kwargs)
|
|
# nginx specific headers
|
|
res.headers["X-Accel-Redirect"] = x_accel_redirect
|
|
# apache specific headers
|
|
res.headers["X-Sendfile"] = x_accel_redirect
|
|
res.headers["Content-Length"] = 0
|
|
|
|
if immutable and res.cache_control:
|
|
res.cache_control["immutable"] = None
|
|
|
|
res.headers["X-Content-Type-Options"] = "nosniff"
|
|
|
|
if content_security_policy:
|
|
res.headers["Content-Security-Policy"] = content_security_policy
|
|
|
|
return res
|
|
|
|
@classmethod
|
|
def _check_use_x_sendfile(cls, attachment: IrAttachment) -> bool:
|
|
return (
|
|
config["x_sendfile"]
|
|
and attachment.fs_url
|
|
and attachment.fs_storage_id.use_x_sendfile_to_serve_internal_url
|
|
)
|
|
|
|
@property
|
|
def _fs_use_x_sendfile(self) -> bool:
|
|
"""Return True if x-sendfile should be used to serve the file"""
|
|
return self._check_use_x_sendfile(self.fs_attachment)
|