Skip to content

Commit

Permalink
Merge branch 'master' into add_lubimyczytac.pl_meta_provider
Browse files Browse the repository at this point in the history
  • Loading branch information
collerek authored Jan 13, 2022
2 parents bea14d1 + a50aff6 commit 20b5a9a
Show file tree
Hide file tree
Showing 85 changed files with 10,271 additions and 6,088 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Calibre-Web is a web app providing a clean interface for browsing, reading and d
- full graphical setup
- User management with fine-grained per-user permissions
- Admin interface
- User Interface in brazilian, czech, dutch, english, finnish, french, german, greek, hungarian, italian, japanese, khmer, polish, russian, simplified and traditional chinese, spanish, swedish, turkish, ukrainian
- User Interface in brazilian, czech, dutch, english, finnish, french, german, greek, hungarian, italian, japanese, khmer, korean, polish, russian, simplified and traditional chinese, spanish, swedish, turkish, ukrainian
- OPDS feed for eBook reader apps
- Filter and search by titles, authors, tags, series and language
- Create a custom book collection (shelves)
Expand Down
3 changes: 3 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ To receive fixes for security vulnerabilities it is required to always upgrade t
| V 0.6.14|Cross-Site Scripting vulnerability on typeahead inputs. Thanks to @notdodo||


## Staement regarding Log4j (CVE-2021-44228 and related)

Calibre-web is not affected by bugs related to Log4j. Calibre-Web is a python program, therefore not using Java, and not using the Java logging feature log4j.
19 changes: 13 additions & 6 deletions cps/about.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,22 @@
for i in (req + opt):
ret[i[1]] = i[0]

if constants.NIGHTLY_VERSION[0] == "$Format:%H$":
calibre_web_version = constants.STABLE_VERSION['version']
else:
calibre_web_version = (constants.STABLE_VERSION['version'] + ' - '
+ constants.NIGHTLY_VERSION[0].replace('%','%%') + ' - '
+ constants.NIGHTLY_VERSION[1].replace('%','%%'))
if getattr(sys, 'frozen', False):
calibre_web_version += " - Exe-Version"
elif constants.HOME_CONFIG:
calibre_web_version += " - pyPi"

if not ret:
_VERSIONS = OrderedDict(
Platform = '{0[0]} {0[2]} {0[3]} {0[4]} {0[5]}'.format(platform.uname()),
Python=sys.version,
Calibre_Web=constants.STABLE_VERSION['version'] + ' - '
+ constants.NIGHTLY_VERSION[0].replace('%','%%') + ' - '
+ constants.NIGHTLY_VERSION[1].replace('%','%%'),
Calibre_Web=calibre_web_version,
WebServer=server.VERSION,
Flask=flask.__version__,
Flask_Login=flask_loginVersion,
Expand Down Expand Up @@ -110,9 +119,7 @@
_VERSIONS = OrderedDict(
Platform = '{0[0]} {0[2]} {0[3]} {0[4]} {0[5]}'.format(platform.uname()),
Python = sys.version,
Calibre_Web = constants.STABLE_VERSION['version'] + ' - '
+ constants.NIGHTLY_VERSION[0].replace('%', '%%') + ' - '
+ constants.NIGHTLY_VERSION[1].replace('%', '%%'),
Calibre_Web=calibre_web_version,
Werkzeug = werkzeug.__version__,
Jinja2=jinja2.__version__,
pySqlite = sqlite3.version,
Expand Down
34 changes: 27 additions & 7 deletions cps/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,11 @@ def admin_forbidden():
abort(403)


@admi.route("/shutdown")
@admi.route("/shutdown", methods=["POST"])
@login_required
@admin_required
def shutdown():
task = int(request.args.get("parameter").strip())
task = request.get_json().get('parameter', -1)
showtext = {}
if task in (0, 1): # valid commandos received
# close all database connections
Expand Down Expand Up @@ -756,7 +756,12 @@ def prepare_tags(user, action, tags_name, id_list):
return ",".join(saved_tags_list)


@admi.route("/ajax/addrestriction/<int:res_type>", defaults={"user_id": 0}, methods=['POST'])
@admi.route("/ajax/addrestriction/<int:res_type>", methods=['POST'])
@login_required
@admin_required
def add_user_0_restriction(res_type):
return add_restriction(res_type, 0)

@admi.route("/ajax/addrestriction/<int:res_type>/<int:user_id>", methods=['POST'])
@login_required
@admin_required
Expand Down Expand Up @@ -803,7 +808,13 @@ def add_restriction(res_type, user_id):
return ""


@admi.route("/ajax/deleterestriction/<int:res_type>", defaults={"user_id": 0}, methods=['POST'])
@admi.route("/ajax/deleterestriction/<int:res_type>", methods=['POST'])
@login_required
@admin_required
def delete_user_0_restriction(res_type):
return delete_restriction(res_type, 0)


@admi.route("/ajax/deleterestriction/<int:res_type>/<int:user_id>", methods=['POST'])
@login_required
@admin_required
Expand Down Expand Up @@ -895,7 +906,7 @@ def list_restriction(res_type, user_id):
response.headers["Content-Type"] = "application/json; charset=utf-8"
return response

@admi.route("/ajax/fullsync")
@admi.route("/ajax/fullsync", methods=["POST"])
@login_required
def ajax_fullsync():
count = ub.session.query(ub.KoboSyncedBooks).filter(current_user.id == ub.KoboSyncedBooks.user_id).delete()
Expand Down Expand Up @@ -1404,7 +1415,16 @@ def _delete_user(content):
for us in ub.session.query(ub.Shelf).filter(content.id == ub.Shelf.user_id):
ub.session.query(ub.BookShelf).filter(us.id == ub.BookShelf.shelf).delete()
ub.session.query(ub.Shelf).filter(content.id == ub.Shelf.user_id).delete()
ub.session.query(ub.Bookmark).filter(content.id == ub.Bookmark.user_id).delete()
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
ub.session.query(ub.ArchivedBook).filter(ub.ArchivedBook.user_id == content.id).delete()
ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.user_id == content.id).delete()
ub.session.query(ub.User_Sessions).filter(ub.User_Sessions.user_id == content.id).delete()
ub.session.query(ub.KoboSyncedBooks).filter(ub.KoboSyncedBooks.user_id == content.id).delete()
# delete KoboReadingState and all it's children
kobo_entries = ub.session.query(ub.KoboReadingState).filter(ub.KoboReadingState.user_id == content.id).all()
for kobo_entry in kobo_entries:
ub.session.delete(kobo_entry)
ub.session_commit()
log.info(u"User {} deleted".format(content.name))
return(_(u"User '%(nick)s' deleted", nick=content.name))
Expand Down Expand Up @@ -1615,7 +1635,7 @@ def edit_user(user_id):
page="edituser")


@admi.route("/admin/resetpassword/<int:user_id>")
@admi.route("/admin/resetpassword/<int:user_id>", methods=["POST"])
@login_required
@admin_required
def reset_user_password(user_id):
Expand Down Expand Up @@ -1791,7 +1811,7 @@ def ldap_import_create_user(user, user_data):
return 0, message


@admi.route('/import_ldap_users')
@admi.route('/import_ldap_users', methods=["POST"])
@login_required
@admin_required
def import_ldap_users():
Expand Down
20 changes: 12 additions & 8 deletions cps/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import ast
import json
from datetime import datetime
from urllib.parse import quote

from sqlalchemy import create_engine
from sqlalchemy import Table, Column, ForeignKey, CheckConstraint
Expand Down Expand Up @@ -166,6 +167,8 @@ def __repr__(self):
return u"https://portal.issn.org/resource/ISSN/{0}".format(self.val)
elif format_type == "isfdb":
return u"http://www.isfdb.org/cgi-bin/pl.cgi?{0}".format(self.val)
elif self.val.lower().startswith("javascript:"):
return quote(self.val)
else:
return u"{0}".format(self.val)

Expand Down Expand Up @@ -796,23 +799,24 @@ def get_search_results(self, term, offset=None, order=None, limit=None, *join):
def speaking_language(self, languages=None, return_all_languages=False, with_count=False, reverse_order=False):
from . import get_locale

if not languages:
if with_count:
if with_count:
if not languages:
languages = self.session.query(Languages, func.count('books_languages_link.book'))\
.join(books_languages_link).join(Books)\
.filter(self.common_filters(return_all_languages=return_all_languages)) \
.group_by(text('books_languages_link.lang_code')).all()
for lang in languages:
lang[0].name = isoLanguages.get_language_name(get_locale(), lang[0].lang_code)
return sorted(languages, key=lambda x: x[0].name, reverse=reverse_order)
else:
for lang in languages:
lang[0].name = isoLanguages.get_language_name(get_locale(), lang[0].lang_code)
return sorted(languages, key=lambda x: x[0].name, reverse=reverse_order)
else:
if not languages:
languages = self.session.query(Languages) \
.join(books_languages_link) \
.join(Books) \
.filter(self.common_filters(return_all_languages=return_all_languages)) \
.group_by(text('books_languages_link.lang_code')).all()
for lang in languages:
lang.name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
for lang in languages:
lang.name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
return sorted(languages, key=lambda x: x.name, reverse=reverse_order)


Expand Down
30 changes: 17 additions & 13 deletions cps/editbooks.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from shutil import copyfile
from uuid import uuid4
from markupsafe import escape
from functools import wraps

try:
from lxml.html.clean import clean_html
except ImportError:
Expand All @@ -51,13 +53,6 @@
from .render_template import render_title_template
from .usermanagement import login_required_if_no_ano

try:
from functools import wraps
except ImportError:
pass # We're not using Python 3




editbook = Blueprint('editbook', __name__)
log = logger.create()
Expand Down Expand Up @@ -237,14 +232,14 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session):
changed = True
return changed, error

@editbook.route("/ajax/delete/<int:book_id>")
@editbook.route("/ajax/delete/<int:book_id>", methods=["POST"])
@login_required
def delete_book_from_details(book_id):
return Response(delete_book_from_table(book_id, "", True), mimetype='application/json')


@editbook.route("/delete/<int:book_id>", defaults={'book_format': ""})
@editbook.route("/delete/<int:book_id>/<string:book_format>")
@editbook.route("/delete/<int:book_id>", defaults={'book_format': ""}, methods=["POST"])
@editbook.route("/delete/<int:book_id>/<string:book_format>", methods=["POST"])
@login_required
def delete_book_ajax(book_id, book_format):
return delete_book_from_table(book_id, book_format, False)
Expand Down Expand Up @@ -362,7 +357,16 @@ def delete_book_from_table(book_id, book_format, jsonResponse):
else:
# book not found
log.error('Book with id "%s" could not be deleted: not found', book_id)
return render_delete_book_result(book_format, jsonResponse, warning, book_id)
return render_delete_book_result(book_format, jsonResponse, warning, book_id)
message = _("You are missing permissions to delete books")
if jsonResponse:
return json.dumps({"location": url_for("editbook.edit_book", book_id=book_id),
"type": "danger",
"format": "",
"message": message})
else:
flash(message, category="error")
return redirect(url_for('editbook.edit_book', book_id=book_id))


def render_edit_book(book_id):
Expand Down Expand Up @@ -832,7 +836,7 @@ def edit_book(book_id):

if modif_date:
book.last_modified = datetime.utcnow()
kobo_sync_status.remove_synced_book(edited_books_id)
kobo_sync_status.remove_synced_book(edited_books_id, all=True)

calibre_db.session.merge(book)
calibre_db.session.commit()
Expand Down Expand Up @@ -1014,7 +1018,7 @@ def move_coverfile(meta, db_book):
category="error")


@editbook.route("/upload", methods=["GET", "POST"])
@editbook.route("/upload", methods=["POST"])
@login_required_if_no_ano
@upload_required
def upload():
Expand Down
2 changes: 1 addition & 1 deletion cps/gdrive.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def revoke_watch_gdrive():
try:
gdriveutils.stopChannel(gdriveutils.Gdrive.Instance().drive, last_watch_response['id'],
last_watch_response['resourceId'])
except HttpError:
except (HttpError, AttributeError):
pass
config.config_google_drive_watch_changes_response = {}
config.save()
Expand Down
7 changes: 7 additions & 0 deletions cps/gdriveutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@
from pydrive2.auth import GoogleAuth
from pydrive2.drive import GoogleDrive
from pydrive2.auth import RefreshError
from pydrive2.files import ApiRequestError
except ImportError as err:
try:
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from pydrive.auth import RefreshError
from pydrive.files import ApiRequestError
except ImportError as err:
importError = err
gdrive_support = False
Expand Down Expand Up @@ -322,6 +324,11 @@ def getFolderId(path, drive):
log.error("gdrive.db DB is not Writeable")
log.debug('Database error: %s', ex)
session.rollback()
except ApiRequestError as ex:
log.error('{} {}'.format(ex.error['message'], path))
session.rollback()
except RefreshError as ex:
log.error(ex)
return currentFolderId


Expand Down
11 changes: 4 additions & 7 deletions cps/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,8 @@
from werkzeug.datastructures import Headers
from werkzeug.security import generate_password_hash
from markupsafe import escape
from urllib.parse import quote

try:
from urllib.parse import quote
except ImportError:
from urllib import quote

try:
import unidecode
Expand Down Expand Up @@ -233,7 +230,7 @@ def get_valid_filename(value, replace_whitespace=True):
value = value[:-1]+u'_'
value = value.replace("/", "_").replace(":", "_").strip('\0')
if use_unidecode:
if not config.config_unicode_filename:
if config.config_unicode_filename:
value = (unidecode.unidecode(value))
else:
value = value.replace(u'§', u'SS')
Expand Down Expand Up @@ -673,9 +670,9 @@ def save_cover(img, book_path):

def do_download_file(book, book_format, client, data, headers):
if config.config_use_google_drive:
startTime = time.time()
#startTime = time.time()
df = gd.getFileFromEbooksFolder(book.path, data.name + "." + book_format)
log.debug('%s', time.time() - startTime)
#log.debug('%s', time.time() - startTime)
if df:
return gd.do_gdrive_download(df, headers)
else:
Expand Down
Loading

0 comments on commit 20b5a9a

Please sign in to comment.