mirror of
				https://github.com/janeczku/calibre-web
				synced 2025-10-26 04:47:40 +00:00 
			
		
		
		
	Merge branch 'master' into cover_thumbnail
# Conflicts: # setup.cfg # test/Calibre-Web TestSummary_Linux.html
This commit is contained in:
		
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| # 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. | ||||
|  | ||||
| [](https://github.com/janeczku/calibre-web/blob/master/LICENSE) | ||||
| []() | ||||
| @@ -49,10 +49,11 @@ In the Wiki there are also examples for: a [manual installation](https://github. | ||||
|  | ||||
| ## Quick start | ||||
|  | ||||
| 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\ | ||||
| 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) | ||||
| Go to Login page | ||||
| Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog \ | ||||
| Login with default admin login \ | ||||
| Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button \ | ||||
| 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: | ||||
| *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](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 | ||||
|  | ||||
|   | ||||
| @@ -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      | 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      | 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 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) | ||||
|   | ||||
| @@ -47,13 +47,16 @@ def init_cache_busting(app): | ||||
|         for filename in filenames: | ||||
|             # compute version component | ||||
|             rooted_filename = os.path.join(dirpath, filename) | ||||
|             with open(rooted_filename, 'rb') as f: | ||||
|                 file_hash = hashlib.md5(f.read()).hexdigest()[:7] # nosec | ||||
|             try: | ||||
|                 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') | ||||
|  | ||||
|     def bust_filename(filename): | ||||
|   | ||||
| @@ -161,7 +161,7 @@ def selected_roles(dictionary): | ||||
| BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, ' | ||||
|                                   'series_id, languages, publisher') | ||||
|  | ||||
| STABLE_VERSION = {'version': '0.6.18 Beta'} | ||||
| STABLE_VERSION = {'version': '0.6.19 Beta'} | ||||
|  | ||||
| NIGHTLY_VERSION = dict() | ||||
| NIGHTLY_VERSION[0] = '$Format:%H$' | ||||
|   | ||||
| @@ -592,6 +592,7 @@ class CalibreDB: | ||||
|                 cls.setup_db_cc_classes(cc) | ||||
|             except OperationalError as e: | ||||
|                 log.error_or_exception(e) | ||||
|                 return False | ||||
|  | ||||
|         cls.session_factory = scoped_session(sessionmaker(autocommit=False, | ||||
|                                                           autoflush=True, | ||||
|   | ||||
| @@ -42,8 +42,9 @@ def error_http(error): | ||||
|  | ||||
| def internal_error(error): | ||||
|     return render_template('http_error.html', | ||||
|                            error_code="Internal Server Error", | ||||
|                            error_name=str(error), | ||||
|                            error_code="500 Internal Server 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, | ||||
|                            unconfigured=False, | ||||
|                            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): | ||||
|     if use_generic_cover: | ||||
|         return send_from_directory(_STATIC_DIR, "generic_cover.jpg") | ||||
|     else: | ||||
|         return None | ||||
|         try: | ||||
|             return send_from_directory(_STATIC_DIR, "generic_cover.jpg") | ||||
|         except PermissionError: | ||||
|             log.error("No permission to access generic_cover.jpg file.") | ||||
|             abort(403) | ||||
|     abort(404) | ||||
|  | ||||
|  | ||||
| def get_book_cover(book_id, resolution=None): | ||||
|   | ||||
| @@ -113,17 +113,18 @@ class LubimyCzytac(Metadata): | ||||
|     ) -> Optional[List[MetaRecord]]: | ||||
|         if self.active: | ||||
|             result = requests.get(self._prepare_query(title=query)) | ||||
|             root = fromstring(result.text) | ||||
|             lc_parser = LubimyCzytacParser(root=root, metadata=self) | ||||
|             matches = lc_parser.parse_search_results() | ||||
|             if matches: | ||||
|                 with ThreadPool(processes=10) as pool: | ||||
|                     final_matches = pool.starmap( | ||||
|                         lc_parser.parse_single_book, | ||||
|                         [(match, generic_cover, locale) for match in matches], | ||||
|                     ) | ||||
|                 return final_matches | ||||
|             return matches | ||||
|             if result.text: | ||||
|                 root = fromstring(result.text) | ||||
|                 lc_parser = LubimyCzytacParser(root=root, metadata=self) | ||||
|                 matches = lc_parser.parse_search_results() | ||||
|                 if matches: | ||||
|                     with ThreadPool(processes=10) as pool: | ||||
|                         final_matches = pool.starmap( | ||||
|                             lc_parser.parse_single_book, | ||||
|                             [(match, generic_cover, locale) for match in matches], | ||||
|                         ) | ||||
|                     return final_matches | ||||
|                 return matches | ||||
|  | ||||
|     def _prepare_query(self, title: str) -> str: | ||||
|         query = "" | ||||
|   | ||||
| @@ -18,11 +18,11 @@ | ||||
|  | ||||
| from flask import render_template, request | ||||
| from flask_babel import gettext as _ | ||||
| from flask import g | ||||
| from flask import g, abort | ||||
| from werkzeug.local import LocalProxy | ||||
| from flask_login import current_user | ||||
|  | ||||
| from . import config, constants, ub, logger, db, calibre_db | ||||
| from . import config, constants, logger | ||||
| from .ub import User | ||||
|  | ||||
|  | ||||
| @@ -119,6 +119,10 @@ def get_sidebar_config(kwargs=None): | ||||
| # Returns the template for rendering and includes the instance name | ||||
| def render_title_template(*args, **kwargs): | ||||
|     sidebar, simple = get_sidebar_config(kwargs) | ||||
|     return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, simple=simple, | ||||
|                            accept=constants.EXTENSIONS_UPLOAD, # read_book_ids=get_readbooks_ids(), | ||||
|                            *args, **kwargs) | ||||
|     try: | ||||
|         return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, simple=simple, | ||||
|                                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)) | ||||
|         return render_search_results(term, offset, order, config.config_books_per_page) | ||||
|     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)) | ||||
|         return render_adv_search_results(term, offset, order, config.config_books_per_page) | ||||
|     else: | ||||
| @@ -1392,7 +1392,11 @@ def get_series_cover(series_id, resolution=None): | ||||
|  | ||||
| @web.route("/robots.txt") | ||||
| 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'}) | ||||
| @@ -1576,7 +1580,7 @@ def login(): | ||||
|                     config.config_is_initial = False | ||||
|                     return redirect_back(url_for("web.index")) | ||||
|                 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") | ||||
|  | ||||
|     next_url = request.args.get('next', default=url_for("web.index"), type=str) | ||||
|   | ||||
| @@ -39,6 +39,7 @@ console_scripts = | ||||
| include_package_data = True | ||||
| install_requires =  | ||||
| 	APScheduler>=3.6.3,<3.8.0 | ||||
| 	werkzeug<2.1.0 | ||||
| 	Babel>=1.3,<3.0 | ||||
| 	Flask-Babel>=0.11.1,<2.1.0 | ||||
| 	Flask-Login>=0.3.2,<0.5.1 | ||||
| @@ -56,6 +57,7 @@ install_requires = | ||||
| 	lxml>=3.8.0,<4.8.0 | ||||
| 	flask-wtf>=0.14.2,<1.1.0 | ||||
| 	chardet>=3.0.0,<4.1.0 | ||||
| 	advocate>=1.0.0,<1.1.0 | ||||
| 	 | ||||
|  | ||||
| [options.extras_require] | ||||
| @@ -72,7 +74,7 @@ gdrive = | ||||
| 	PyYAML>=3.12 | ||||
| 	rsa>=3.4.2,<4.9.0 | ||||
| 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 | ||||
| goodreads =  | ||||
| 	goodreads>=0.3.2,<0.4.0 | ||||
| @@ -85,7 +87,7 @@ oauth = | ||||
| 	SQLAlchemy-Utils>=0.33.5,<0.39.0 | ||||
| metadata =  | ||||
| 	rarfile>=3.2 | ||||
| 	scholarly>=1.2.0,<1.6 | ||||
| 	scholarly>=1.2.0,<1.7 | ||||
| 	markdown2>=2.0.0,<2.5.0 | ||||
| 	html2text>=2020.1.16,<2022.1.1 | ||||
| 	python-dateutil>=2.1,<2.9.0 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Ozzie Isaacs
					Ozzie Isaacs