mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-28 20:39:59 +00:00
Merge branch 'master' into cover_thumbnail
# Conflicts: # setup.cfg # test/Calibre-Web TestSummary_Linux.html
This commit is contained in:
commit
3d2e7e847e
13
README.md
13
README.md
@ -1,6 +1,6 @@
|
|||||||
# About
|
# About
|
||||||
|
|
||||||
Calibre-Web is a web app providing a clean interface for browsing, reading and downloading eBooks using an existing [Calibre](https://calibre-ebook.com) database.
|
Calibre-Web is a web app providing a clean interface for browsing, reading and downloading eBooks using a valid [Calibre](https://calibre-ebook.com) database.
|
||||||
|
|
||||||
[![GitHub License](https://img.shields.io/github/license/janeczku/calibre-web?style=flat-square)](https://github.com/janeczku/calibre-web/blob/master/LICENSE)
|
[![GitHub License](https://img.shields.io/github/license/janeczku/calibre-web?style=flat-square)](https://github.com/janeczku/calibre-web/blob/master/LICENSE)
|
||||||
[![GitHub commit activity](https://img.shields.io/github/commit-activity/w/janeczku/calibre-web?logo=github&style=flat-square&label=commits)]()
|
[![GitHub commit activity](https://img.shields.io/github/commit-activity/w/janeczku/calibre-web?logo=github&style=flat-square&label=commits)]()
|
||||||
@ -49,10 +49,11 @@ In the Wiki there are also examples for: a [manual installation](https://github.
|
|||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog
|
Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog \
|
||||||
Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button\
|
Login with default admin login \
|
||||||
Optionally a Google Drive can be used to host the calibre library [-> Using Google Drive integration](https://github.com/janeczku/calibre-web/wiki/Configuration#using-google-drive-integration)
|
Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button \
|
||||||
Go to Login page
|
Optionally a Google Drive can be used to host the calibre library [-> Using Google Drive integration](https://github.com/janeczku/calibre-web/wiki/Configuration#using-google-drive-integration) \
|
||||||
|
Afterwards you can configure your Calibre-Web instance ([Basic Configuration](https://github.com/janeczku/calibre-web/wiki/Configuration#basic-configuration) and [UI Configuration](https://github.com/janeczku/calibre-web/wiki/Configuration#ui-configuration) on admin page)
|
||||||
|
|
||||||
#### Default admin login:
|
#### Default admin login:
|
||||||
*Username:* admin\
|
*Username:* admin\
|
||||||
@ -67,7 +68,7 @@ Optionally, to enable on-the-fly conversion from one ebook format to another whe
|
|||||||
|
|
||||||
[Download and install](https://calibre-ebook.com/download) the Calibre desktop program for your platform and enter the folder including program name (normally /opt/calibre/ebook-convert, or C:\Program Files\calibre\ebook-convert.exe) in the field "calibre's converter tool" on the setup page.
|
[Download and install](https://calibre-ebook.com/download) the Calibre desktop program for your platform and enter the folder including program name (normally /opt/calibre/ebook-convert, or C:\Program Files\calibre\ebook-convert.exe) in the field "calibre's converter tool" on the setup page.
|
||||||
|
|
||||||
[Download](https://github.com/pgaskin/kepubify/releases/latest) Kepubify tool for your platform and place the binary starting with `kepubify` in Linux: `\opt\kepubify` Windows: `C:\Program Files\kepubify`.
|
[Download](https://github.com/pgaskin/kepubify/releases/latest) Kepubify tool for your platform and place the binary starting with `kepubify` in Linux: `/opt/kepubify` Windows: `C:\Program Files\kepubify`.
|
||||||
|
|
||||||
## Docker Images
|
## Docker Images
|
||||||
|
|
||||||
|
@ -32,8 +32,12 @@ To receive fixes for security vulnerabilities it is required to always upgrade t
|
|||||||
| V 0.6.16 | JavaScript could get executed on authors page. Thanks to @alicaz ||
|
| V 0.6.16 | JavaScript could get executed on authors page. Thanks to @alicaz ||
|
||||||
| V 0.6.16 | Localhost can no longer be used to upload covers. Thanks to @scara31 ||
|
| V 0.6.16 | Localhost can no longer be used to upload covers. Thanks to @scara31 ||
|
||||||
| V 0.6.16 | Another case where public shelfs could be created without permission is prevented. Thanks to @nhiephon ||
|
| V 0.6.16 | Another case where public shelfs could be created without permission is prevented. Thanks to @nhiephon ||
|
||||||
|
| V 0.6.16 | It's prevented to get the name of a private shelfs. Thanks to @nhiephon ||
|
||||||
| V 0.6.17 | The SSRF Protection can no longer be bypassed via an HTTP redirect. Thanks to @416e6e61 ||
|
| V 0.6.17 | The SSRF Protection can no longer be bypassed via an HTTP redirect. Thanks to @416e6e61 ||
|
||||||
| V 0.6.17 | The SSRF Protection can no longer be bypassed via 0.0.0.0 and it's ipv6 equivalent. Thanks to @r0hanSH ||
|
| V 0.6.17 | The SSRF Protection can no longer be bypassed via 0.0.0.0 and it's ipv6 equivalent. Thanks to @r0hanSH ||
|
||||||
|
| V 0.6.18 | Possible SQL Injection is prevented in user table Thanks to Iman Sharafaldin (Forward Security) ||
|
||||||
|
| V 0.6.18 | The SSRF protection no longer can be bypassed by IPV6/IPV4 embedding. Thanks to @416e6e61 ||
|
||||||
|
| V 0.6.18 | The SSRF protection no longer can be bypassed to connect to other servers in the local network. Thanks to @michaellrowley ||
|
||||||
|
|
||||||
|
|
||||||
## Statement regarding Log4j (CVE-2021-44228 and related)
|
## Statement regarding Log4j (CVE-2021-44228 and related)
|
||||||
|
@ -47,13 +47,16 @@ def init_cache_busting(app):
|
|||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
# compute version component
|
# compute version component
|
||||||
rooted_filename = os.path.join(dirpath, filename)
|
rooted_filename = os.path.join(dirpath, filename)
|
||||||
with open(rooted_filename, 'rb') as f:
|
try:
|
||||||
file_hash = hashlib.md5(f.read()).hexdigest()[:7] # nosec
|
with open(rooted_filename, 'rb') as f:
|
||||||
|
file_hash = hashlib.md5(f.read()).hexdigest()[:7] # nosec
|
||||||
|
# save version to tables
|
||||||
|
file_path = rooted_filename.replace(static_folder, "")
|
||||||
|
file_path = file_path.replace("\\", "/") # Convert Windows path to web path
|
||||||
|
hash_table[file_path] = file_hash
|
||||||
|
except PermissionError:
|
||||||
|
log.error("No permission to access {} file.".format(rooted_filename))
|
||||||
|
|
||||||
# save version to tables
|
|
||||||
file_path = rooted_filename.replace(static_folder, "")
|
|
||||||
file_path = file_path.replace("\\", "/") # Convert Windows path to web path
|
|
||||||
hash_table[file_path] = file_hash
|
|
||||||
log.debug('Finished computing cache-busting values')
|
log.debug('Finished computing cache-busting values')
|
||||||
|
|
||||||
def bust_filename(filename):
|
def bust_filename(filename):
|
||||||
|
@ -161,7 +161,7 @@ def selected_roles(dictionary):
|
|||||||
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
|
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
|
||||||
'series_id, languages, publisher')
|
'series_id, languages, publisher')
|
||||||
|
|
||||||
STABLE_VERSION = {'version': '0.6.18 Beta'}
|
STABLE_VERSION = {'version': '0.6.19 Beta'}
|
||||||
|
|
||||||
NIGHTLY_VERSION = dict()
|
NIGHTLY_VERSION = dict()
|
||||||
NIGHTLY_VERSION[0] = '$Format:%H$'
|
NIGHTLY_VERSION[0] = '$Format:%H$'
|
||||||
|
@ -592,6 +592,7 @@ class CalibreDB:
|
|||||||
cls.setup_db_cc_classes(cc)
|
cls.setup_db_cc_classes(cc)
|
||||||
except OperationalError as e:
|
except OperationalError as e:
|
||||||
log.error_or_exception(e)
|
log.error_or_exception(e)
|
||||||
|
return False
|
||||||
|
|
||||||
cls.session_factory = scoped_session(sessionmaker(autocommit=False,
|
cls.session_factory = scoped_session(sessionmaker(autocommit=False,
|
||||||
autoflush=True,
|
autoflush=True,
|
||||||
|
@ -42,8 +42,9 @@ def error_http(error):
|
|||||||
|
|
||||||
def internal_error(error):
|
def internal_error(error):
|
||||||
return render_template('http_error.html',
|
return render_template('http_error.html',
|
||||||
error_code="Internal Server Error",
|
error_code="500 Internal Server Error",
|
||||||
error_name=str(error),
|
error_name='The server encountered an internal error and was unable to complete your '
|
||||||
|
'request. There is an error in the application.',
|
||||||
issue=True,
|
issue=True,
|
||||||
unconfigured=False,
|
unconfigured=False,
|
||||||
error_stack=traceback.format_exc().split("\n"),
|
error_stack=traceback.format_exc().split("\n"),
|
||||||
|
@ -702,9 +702,12 @@ def delete_book(book, calibrepath, book_format):
|
|||||||
|
|
||||||
def get_cover_on_failure(use_generic_cover):
|
def get_cover_on_failure(use_generic_cover):
|
||||||
if use_generic_cover:
|
if use_generic_cover:
|
||||||
return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
|
try:
|
||||||
else:
|
return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
|
||||||
return None
|
except PermissionError:
|
||||||
|
log.error("No permission to access generic_cover.jpg file.")
|
||||||
|
abort(403)
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover(book_id, resolution=None):
|
def get_book_cover(book_id, resolution=None):
|
||||||
|
@ -113,17 +113,18 @@ class LubimyCzytac(Metadata):
|
|||||||
) -> Optional[List[MetaRecord]]:
|
) -> Optional[List[MetaRecord]]:
|
||||||
if self.active:
|
if self.active:
|
||||||
result = requests.get(self._prepare_query(title=query))
|
result = requests.get(self._prepare_query(title=query))
|
||||||
root = fromstring(result.text)
|
if result.text:
|
||||||
lc_parser = LubimyCzytacParser(root=root, metadata=self)
|
root = fromstring(result.text)
|
||||||
matches = lc_parser.parse_search_results()
|
lc_parser = LubimyCzytacParser(root=root, metadata=self)
|
||||||
if matches:
|
matches = lc_parser.parse_search_results()
|
||||||
with ThreadPool(processes=10) as pool:
|
if matches:
|
||||||
final_matches = pool.starmap(
|
with ThreadPool(processes=10) as pool:
|
||||||
lc_parser.parse_single_book,
|
final_matches = pool.starmap(
|
||||||
[(match, generic_cover, locale) for match in matches],
|
lc_parser.parse_single_book,
|
||||||
)
|
[(match, generic_cover, locale) for match in matches],
|
||||||
return final_matches
|
)
|
||||||
return matches
|
return final_matches
|
||||||
|
return matches
|
||||||
|
|
||||||
def _prepare_query(self, title: str) -> str:
|
def _prepare_query(self, title: str) -> str:
|
||||||
query = ""
|
query = ""
|
||||||
|
@ -18,11 +18,11 @@
|
|||||||
|
|
||||||
from flask import render_template, request
|
from flask import render_template, request
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask import g
|
from flask import g, abort
|
||||||
from werkzeug.local import LocalProxy
|
from werkzeug.local import LocalProxy
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from . import config, constants, ub, logger, db, calibre_db
|
from . import config, constants, logger
|
||||||
from .ub import User
|
from .ub import User
|
||||||
|
|
||||||
|
|
||||||
@ -119,6 +119,10 @@ def get_sidebar_config(kwargs=None):
|
|||||||
# Returns the template for rendering and includes the instance name
|
# Returns the template for rendering and includes the instance name
|
||||||
def render_title_template(*args, **kwargs):
|
def render_title_template(*args, **kwargs):
|
||||||
sidebar, simple = get_sidebar_config(kwargs)
|
sidebar, simple = get_sidebar_config(kwargs)
|
||||||
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, simple=simple,
|
try:
|
||||||
accept=constants.EXTENSIONS_UPLOAD, # read_book_ids=get_readbooks_ids(),
|
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, simple=simple,
|
||||||
*args, **kwargs)
|
accept=constants.EXTENSIONS_UPLOAD,
|
||||||
|
*args, **kwargs)
|
||||||
|
except PermissionError:
|
||||||
|
log.error("No permission to access {} file.".format(args[0]))
|
||||||
|
abort(403)
|
||||||
|
10
cps/web.py
10
cps/web.py
@ -383,7 +383,7 @@ def render_books_list(data, sort_param, book_id, page):
|
|||||||
offset = int(int(config.config_books_per_page) * (page - 1))
|
offset = int(int(config.config_books_per_page) * (page - 1))
|
||||||
return render_search_results(term, offset, order, config.config_books_per_page)
|
return render_search_results(term, offset, order, config.config_books_per_page)
|
||||||
elif data == "advsearch":
|
elif data == "advsearch":
|
||||||
term = json.loads(flask_session['query'])
|
term = json.loads(flask_session.get('query', '{}'))
|
||||||
offset = int(int(config.config_books_per_page) * (page - 1))
|
offset = int(int(config.config_books_per_page) * (page - 1))
|
||||||
return render_adv_search_results(term, offset, order, config.config_books_per_page)
|
return render_adv_search_results(term, offset, order, config.config_books_per_page)
|
||||||
else:
|
else:
|
||||||
@ -1392,7 +1392,11 @@ def get_series_cover(series_id, resolution=None):
|
|||||||
|
|
||||||
@web.route("/robots.txt")
|
@web.route("/robots.txt")
|
||||||
def get_robots():
|
def get_robots():
|
||||||
return send_from_directory(constants.STATIC_DIR, "robots.txt")
|
try:
|
||||||
|
return send_from_directory(constants.STATIC_DIR, "robots.txt")
|
||||||
|
except PermissionError:
|
||||||
|
log.error("No permission to access robots.txt file.")
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
@web.route("/show/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
|
@web.route("/show/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
|
||||||
@ -1576,7 +1580,7 @@ def login():
|
|||||||
config.config_is_initial = False
|
config.config_is_initial = False
|
||||||
return redirect_back(url_for("web.index"))
|
return redirect_back(url_for("web.index"))
|
||||||
else:
|
else:
|
||||||
log.warning('Login failed for user "%s" IP-address: %s', form['username'], ip_address)
|
log.warning('Login failed for user "{}" IP-address: {}'.format(form['username'], ip_address))
|
||||||
flash(_(u"Wrong Username or Password"), category="error")
|
flash(_(u"Wrong Username or Password"), category="error")
|
||||||
|
|
||||||
next_url = request.args.get('next', default=url_for("web.index"), type=str)
|
next_url = request.args.get('next', default=url_for("web.index"), type=str)
|
||||||
|
@ -39,6 +39,7 @@ console_scripts =
|
|||||||
include_package_data = True
|
include_package_data = True
|
||||||
install_requires =
|
install_requires =
|
||||||
APScheduler>=3.6.3,<3.8.0
|
APScheduler>=3.6.3,<3.8.0
|
||||||
|
werkzeug<2.1.0
|
||||||
Babel>=1.3,<3.0
|
Babel>=1.3,<3.0
|
||||||
Flask-Babel>=0.11.1,<2.1.0
|
Flask-Babel>=0.11.1,<2.1.0
|
||||||
Flask-Login>=0.3.2,<0.5.1
|
Flask-Login>=0.3.2,<0.5.1
|
||||||
@ -56,6 +57,7 @@ install_requires =
|
|||||||
lxml>=3.8.0,<4.8.0
|
lxml>=3.8.0,<4.8.0
|
||||||
flask-wtf>=0.14.2,<1.1.0
|
flask-wtf>=0.14.2,<1.1.0
|
||||||
chardet>=3.0.0,<4.1.0
|
chardet>=3.0.0,<4.1.0
|
||||||
|
advocate>=1.0.0,<1.1.0
|
||||||
|
|
||||||
|
|
||||||
[options.extras_require]
|
[options.extras_require]
|
||||||
@ -72,7 +74,7 @@ gdrive =
|
|||||||
PyYAML>=3.12
|
PyYAML>=3.12
|
||||||
rsa>=3.4.2,<4.9.0
|
rsa>=3.4.2,<4.9.0
|
||||||
gmail =
|
gmail =
|
||||||
google-auth-oauthlib>=0.4.3,<0.5.0
|
google-auth-oauthlib>=0.4.3,<0.6.0
|
||||||
google-api-python-client>=1.7.11,<2.43.0
|
google-api-python-client>=1.7.11,<2.43.0
|
||||||
goodreads =
|
goodreads =
|
||||||
goodreads>=0.3.2,<0.4.0
|
goodreads>=0.3.2,<0.4.0
|
||||||
@ -85,7 +87,7 @@ oauth =
|
|||||||
SQLAlchemy-Utils>=0.33.5,<0.39.0
|
SQLAlchemy-Utils>=0.33.5,<0.39.0
|
||||||
metadata =
|
metadata =
|
||||||
rarfile>=3.2
|
rarfile>=3.2
|
||||||
scholarly>=1.2.0,<1.6
|
scholarly>=1.2.0,<1.7
|
||||||
markdown2>=2.0.0,<2.5.0
|
markdown2>=2.0.0,<2.5.0
|
||||||
html2text>=2020.1.16,<2022.1.1
|
html2text>=2020.1.16,<2022.1.1
|
||||||
python-dateutil>=2.1,<2.9.0
|
python-dateutil>=2.1,<2.9.0
|
||||||
|
Loading…
Reference in New Issue
Block a user