mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-24 10:37:23 +00:00
Merge remote-tracking branch 'mimetype/python_magic_poc' into Develop
This commit is contained in:
commit
d5a57e3b07
@ -56,6 +56,7 @@ except ImportError:
|
|||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
mimetypes.add_type('application/xhtml+xml', '.xhtml')
|
mimetypes.add_type('application/xhtml+xml', '.xhtml')
|
||||||
mimetypes.add_type('application/epub+zip', '.epub')
|
mimetypes.add_type('application/epub+zip', '.epub')
|
||||||
|
mimetypes.add_type('application/epub+zip', '.kepub')
|
||||||
mimetypes.add_type('application/fb2+zip', '.fb2')
|
mimetypes.add_type('application/fb2+zip', '.fb2')
|
||||||
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
|
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
|
||||||
mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
|
mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
|
||||||
@ -66,6 +67,7 @@ mimetypes.add_type('application/x-cbz', '.cbz')
|
|||||||
mimetypes.add_type('application/x-cbt', '.cbt')
|
mimetypes.add_type('application/x-cbt', '.cbt')
|
||||||
mimetypes.add_type('application/x-cb7', '.cb7')
|
mimetypes.add_type('application/x-cb7', '.cb7')
|
||||||
mimetypes.add_type('image/vnd.djv', '.djv')
|
mimetypes.add_type('image/vnd.djv', '.djv')
|
||||||
|
mimetypes.add_type('image/vnd.djv', '.djvu')
|
||||||
mimetypes.add_type('application/mpeg', '.mpeg')
|
mimetypes.add_type('application/mpeg', '.mpeg')
|
||||||
mimetypes.add_type('application/mpeg', '.mp3')
|
mimetypes.add_type('application/mpeg', '.mp3')
|
||||||
mimetypes.add_type('application/mp4', '.m4a')
|
mimetypes.add_type('application/mp4', '.m4a')
|
||||||
@ -73,6 +75,7 @@ mimetypes.add_type('application/mp4', '.m4b')
|
|||||||
mimetypes.add_type('application/ogg', '.ogg')
|
mimetypes.add_type('application/ogg', '.ogg')
|
||||||
mimetypes.add_type('application/ogg', '.oga')
|
mimetypes.add_type('application/ogg', '.oga')
|
||||||
mimetypes.add_type('text/css', '.css')
|
mimetypes.add_type('text/css', '.css')
|
||||||
|
mimetypes.add_type('application/x-ms-reader', '.lit')
|
||||||
mimetypes.add_type('text/javascript; charset=UTF-8', '.js')
|
mimetypes.add_type('text/javascript; charset=UTF-8', '.js')
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
@ -1780,7 +1780,7 @@ def _configuration_update_helper():
|
|||||||
to_save["config_upload_formats"] = ','.join(
|
to_save["config_upload_formats"] = ','.join(
|
||||||
helper.uniq([x.lstrip().rstrip().lower() for x in to_save["config_upload_formats"].split(',')]))
|
helper.uniq([x.lstrip().rstrip().lower() for x in to_save["config_upload_formats"].split(',')]))
|
||||||
_config_string(to_save, "config_upload_formats")
|
_config_string(to_save, "config_upload_formats")
|
||||||
constants.EXTENSIONS_UPLOAD = config.config_upload_formats.split(',')
|
# constants.EXTENSIONS_UPLOAD = config.config_upload_formats.split(',')
|
||||||
|
|
||||||
_config_string(to_save, "config_calibre")
|
_config_string(to_save, "config_calibre")
|
||||||
_config_string(to_save, "config_binariesdir")
|
_config_string(to_save, "config_binariesdir")
|
||||||
@ -1830,6 +1830,7 @@ def _configuration_update_helper():
|
|||||||
reboot_required |= reboot
|
reboot_required |= reboot
|
||||||
|
|
||||||
# security configuration
|
# security configuration
|
||||||
|
_config_checkbox(to_save, "config_check_extensions")
|
||||||
_config_checkbox(to_save, "config_password_policy")
|
_config_checkbox(to_save, "config_password_policy")
|
||||||
_config_checkbox(to_save, "config_password_number")
|
_config_checkbox(to_save, "config_password_number")
|
||||||
_config_checkbox(to_save, "config_password_lower")
|
_config_checkbox(to_save, "config_password_lower")
|
||||||
|
@ -169,6 +169,7 @@ class _Settings(_Base):
|
|||||||
config_ratelimiter = Column(Boolean, default=True)
|
config_ratelimiter = Column(Boolean, default=True)
|
||||||
config_limiter_uri = Column(String, default="")
|
config_limiter_uri = Column(String, default="")
|
||||||
config_limiter_options = Column(String, default="")
|
config_limiter_options = Column(String, default="")
|
||||||
|
config_check_extensions = Column(Boolean, default=True)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__class__.__name__
|
return self.__class__.__name__
|
||||||
@ -348,7 +349,7 @@ class ConfigSQL(object):
|
|||||||
db_file = os.path.join(self.config_calibre_dir, 'metadata.db')
|
db_file = os.path.join(self.config_calibre_dir, 'metadata.db')
|
||||||
have_metadata_db = os.path.isfile(db_file)
|
have_metadata_db = os.path.isfile(db_file)
|
||||||
self.db_configured = have_metadata_db
|
self.db_configured = have_metadata_db
|
||||||
constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip().lower() for x in self.config_upload_formats.split(',')]
|
# constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip().lower() for x in self.config_upload_formats.split(',')]
|
||||||
from . import cli_param
|
from . import cli_param
|
||||||
if os.environ.get('FLASK_DEBUG'):
|
if os.environ.get('FLASK_DEBUG'):
|
||||||
logfile = logger.setup(logger.LOG_TO_STDOUT, logger.logging.DEBUG)
|
logfile = logger.setup(logger.LOG_TO_STDOUT, logger.logging.DEBUG)
|
||||||
|
@ -27,22 +27,7 @@ from shutil import copyfile
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from markupsafe import escape, Markup # dependency of flask
|
from markupsafe import escape, Markup # dependency of flask
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
# from lxml.etree import ParserError
|
|
||||||
|
|
||||||
#try:
|
|
||||||
# # at least bleach 6.0 is needed -> incomplatible change from list arguments to set arguments
|
|
||||||
# from bleach import clean_text as clean_html
|
|
||||||
# BLEACH = True
|
|
||||||
#except ImportError:
|
|
||||||
# try:
|
|
||||||
# BLEACH = False
|
|
||||||
# from nh3 import clean as clean_html
|
|
||||||
# except ImportError:
|
|
||||||
# try:
|
|
||||||
# BLEACH = False
|
|
||||||
# from lxml.html.clean import clean_html
|
|
||||||
# except ImportError:
|
|
||||||
# clean_html = None
|
|
||||||
|
|
||||||
from flask import Blueprint, request, flash, redirect, url_for, abort, Response
|
from flask import Blueprint, request, flash, redirect, url_for, abort, Response
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
@ -62,7 +47,7 @@ from .render_template import render_title_template
|
|||||||
from .usermanagement import login_required_if_no_ano
|
from .usermanagement import login_required_if_no_ano
|
||||||
from .kobo_sync_status import change_archived_books
|
from .kobo_sync_status import change_archived_books
|
||||||
from .redirect import get_redirect_location
|
from .redirect import get_redirect_location
|
||||||
|
from .file_helper import validate_mime_type
|
||||||
|
|
||||||
editbook = Blueprint('edit-book', __name__)
|
editbook = Blueprint('edit-book', __name__)
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
@ -738,9 +723,15 @@ def create_book_on_upload(modify_date, meta):
|
|||||||
|
|
||||||
def file_handling_on_upload(requested_file):
|
def file_handling_on_upload(requested_file):
|
||||||
# check if file extension is correct
|
# check if file extension is correct
|
||||||
|
allowed_extensions = config.config_upload_formats.split(',')
|
||||||
|
if requested_file:
|
||||||
|
if config.config_check_extensions:
|
||||||
|
if not validate_mime_type(requested_file, allowed_extensions):
|
||||||
|
flash(_("File type isn't allowed to be uploaded to this server"), category="error")
|
||||||
|
return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||||
if '.' in requested_file.filename:
|
if '.' in requested_file.filename:
|
||||||
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
||||||
if file_ext not in constants.EXTENSIONS_UPLOAD and '' not in constants.EXTENSIONS_UPLOAD:
|
if file_ext not in allowed_extensions and '' not in allowed_extensions:
|
||||||
flash(
|
flash(
|
||||||
_("File extension '%(ext)s' is not allowed to be uploaded to this server",
|
_("File extension '%(ext)s' is not allowed to be uploaded to this server",
|
||||||
ext=file_ext), category="error")
|
ext=file_ext), category="error")
|
||||||
@ -1191,7 +1182,12 @@ def edit_cc_data(book_id, book, to_save, cc):
|
|||||||
def upload_single_file(file_request, book, book_id):
|
def upload_single_file(file_request, book, book_id):
|
||||||
# Check and handle Uploaded file
|
# Check and handle Uploaded file
|
||||||
requested_file = file_request.files.get('btn-upload-format', None)
|
requested_file = file_request.files.get('btn-upload-format', None)
|
||||||
|
allowed_extensions = config.config_upload_formats.split(',')
|
||||||
if requested_file:
|
if requested_file:
|
||||||
|
if config.config_check_extensions:
|
||||||
|
if not validate_mime_type(requested_file, allowed_extensions):
|
||||||
|
flash(_("File type isn't allowed to be uploaded to this server"), category="error")
|
||||||
|
return False
|
||||||
# check for empty request
|
# check for empty request
|
||||||
if requested_file.filename != '':
|
if requested_file.filename != '':
|
||||||
if not current_user.role_upload():
|
if not current_user.role_upload():
|
||||||
@ -1199,7 +1195,7 @@ def upload_single_file(file_request, book, book_id):
|
|||||||
return False
|
return False
|
||||||
if '.' in requested_file.filename:
|
if '.' in requested_file.filename:
|
||||||
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
||||||
if file_ext not in constants.EXTENSIONS_UPLOAD and '' not in constants.EXTENSIONS_UPLOAD:
|
if file_ext not in allowed_extensions and '' not in allowed_extensions:
|
||||||
flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext),
|
flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext),
|
||||||
category="error")
|
category="error")
|
||||||
return False
|
return False
|
||||||
@ -1216,7 +1212,8 @@ def upload_single_file(file_request, book, book_id):
|
|||||||
try:
|
try:
|
||||||
os.makedirs(filepath)
|
os.makedirs(filepath)
|
||||||
except OSError:
|
except OSError:
|
||||||
flash(_("Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
|
flash(_("Failed to create path %(path)s (Permission denied).", path=filepath),
|
||||||
|
category="error")
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
requested_file.save(saved_filename)
|
requested_file.save(saved_filename)
|
||||||
|
@ -19,6 +19,18 @@
|
|||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import zipfile
|
||||||
|
import mimetypes
|
||||||
|
import copy
|
||||||
|
from io import BytesIO
|
||||||
|
try:
|
||||||
|
import magic
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from . import logger
|
||||||
|
|
||||||
|
log = logger.create()
|
||||||
|
|
||||||
def get_temp_dir():
|
def get_temp_dir():
|
||||||
tmp_dir = os.path.join(gettempdir(), 'calibre_web')
|
tmp_dir = os.path.join(gettempdir(), 'calibre_web')
|
||||||
@ -30,3 +42,29 @@ def get_temp_dir():
|
|||||||
def del_temp_dir():
|
def del_temp_dir():
|
||||||
tmp_dir = os.path.join(gettempdir(), 'calibre_web')
|
tmp_dir = os.path.join(gettempdir(), 'calibre_web')
|
||||||
shutil.rmtree(tmp_dir)
|
shutil.rmtree(tmp_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_mime_type(file_buffer, allowed_extensions):
|
||||||
|
mime = magic.Magic(mime=True)
|
||||||
|
allowed_mimetypes =list()
|
||||||
|
for x in allowed_extensions:
|
||||||
|
try:
|
||||||
|
allowed_mimetypes.append(mimetypes.types_map["." + x])
|
||||||
|
except KeyError as e:
|
||||||
|
log.error("Unkown mimetype for Extension: {}".format(x))
|
||||||
|
tmp_mime_type = mime.from_buffer(file_buffer.read())
|
||||||
|
file_buffer.seek(0)
|
||||||
|
if any(mime_type in tmp_mime_type for mime_type in allowed_mimetypes):
|
||||||
|
return True
|
||||||
|
# Some epubs show up as zip mimetypes
|
||||||
|
elif "zip" in tmp_mime_type:
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(BytesIO(file_buffer.read()), 'r') as epub:
|
||||||
|
file_buffer.seek(0)
|
||||||
|
if "mimetype" in epub.namelist():
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
file_buffer.seek(0)
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
@ -112,7 +112,7 @@ def render_title_template(*args, **kwargs):
|
|||||||
sidebar, simple = get_sidebar_config(kwargs)
|
sidebar, simple = get_sidebar_config(kwargs)
|
||||||
try:
|
try:
|
||||||
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, simple=simple,
|
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, simple=simple,
|
||||||
accept=constants.EXTENSIONS_UPLOAD,
|
accept=config.config_upload_formats.split(','),
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
log.error("No permission to access {} file.".format(args[0]))
|
log.error("No permission to access {} file.".format(args[0]))
|
||||||
|
@ -377,6 +377,10 @@
|
|||||||
<input type="text" class="form-control" id="config_limiter_options" name="config_limiter_options" value="{% if config.config_limiter_options != None %}{{ config.config_limiter_options }}{% endif %}" autocomplete="off">
|
<input type="text" class="form-control" id="config_limiter_options" name="config_limiter_options" value="{% if config.config_limiter_options != None %}{{ config.config_limiter_options }}{% endif %}" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="config_check_extensions" name="config_check_extensions" {% if config.config_check_extensions %}checked{% endif %}>
|
||||||
|
<label for="config_check_extensions">{{_('Check if file extensions matches file content on upload')}}</label>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="config_session">{{_('Session protection')}}</label>
|
<label for="config_session">{{_('Session protection')}}</label>
|
||||||
<select name="config_session" id="config_session" class="form-control">
|
<select name="config_session" id="config_session" class="form-control">
|
||||||
|
@ -23,7 +23,7 @@ from flask_babel import gettext as _
|
|||||||
from . import logger, comic, isoLanguages
|
from . import logger, comic, isoLanguages
|
||||||
from .constants import BookMeta
|
from .constants import BookMeta
|
||||||
from .helper import split_authors
|
from .helper import split_authors
|
||||||
from .file_helper import get_temp_dir
|
from .file_helper import get_temp_dir, validate_mime_type
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
|
@ -1582,7 +1582,8 @@ def read_book(book_id, book_format):
|
|||||||
return render_title_template('readtxt.html', txtfile=book_id, title=book.title)
|
return render_title_template('readtxt.html', txtfile=book_id, title=book.title)
|
||||||
elif book_format.lower() in ["djvu", "djv"]:
|
elif book_format.lower() in ["djvu", "djv"]:
|
||||||
log.debug("Start djvu reader for %d", book_id)
|
log.debug("Start djvu reader for %d", book_id)
|
||||||
return render_title_template('readdjvu.html', djvufile=book_id, title=book.title, extension=book_format.lower())
|
return render_title_template('readdjvu.html', djvufile=book_id, title=book.title,
|
||||||
|
extension=book_format.lower())
|
||||||
else:
|
else:
|
||||||
for fileExt in constants.EXTENSIONS_AUDIO:
|
for fileExt in constants.EXTENSIONS_AUDIO:
|
||||||
if book_format.lower() == fileExt:
|
if book_format.lower() == fileExt:
|
||||||
|
@ -19,3 +19,4 @@ chardet>=3.0.0,<4.1.0
|
|||||||
advocate>=1.0.0,<1.1.0
|
advocate>=1.0.0,<1.1.0
|
||||||
Flask-Limiter>=2.3.0,<3.6.0
|
Flask-Limiter>=2.3.0,<3.6.0
|
||||||
regex>=2022.3.2,<2024.2.25
|
regex>=2022.3.2,<2024.2.25
|
||||||
|
python-magic>=0.4.27,<0.5.0
|
||||||
|
Loading…
Reference in New Issue
Block a user