mirror of
				https://github.com/janeczku/calibre-web
				synced 2025-10-31 15:23:02 +00:00 
			
		
		
		
	Plus ("+" vs. "%2B") encoded search strings for opds search feeds are now working in request string (fix for #2175)
This commit is contained in:
		
							
								
								
									
										29
									
								
								cps/gevent_wsgi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								cps/gevent_wsgi.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | #  This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) | ||||||
|  | #    Copyright (C) 2022 OzzieIsaacs | ||||||
|  | # | ||||||
|  | #  This program is free software: you can redistribute it and/or modify | ||||||
|  | #  it under the terms of the GNU General Public License as published by | ||||||
|  | #  the Free Software Foundation, either version 3 of the License, or | ||||||
|  | #  (at your option) any later version. | ||||||
|  | # | ||||||
|  | #  This program is distributed in the hope that it will be useful, | ||||||
|  | #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | #  GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | #  You should have received a copy of the GNU General Public License | ||||||
|  | #  along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | from gevent.pywsgi import WSGIHandler | ||||||
|  |  | ||||||
|  | class MyWSGIHandler(WSGIHandler): | ||||||
|  |     def get_environ(self): | ||||||
|  |         env = super().get_environ() | ||||||
|  |         path, __ = self.path.split('?', 1) if '?' in self.path else (self.path, '') | ||||||
|  |         env['RAW_URI'] = path | ||||||
|  |         return env | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -85,7 +85,7 @@ def feed_osd(): | |||||||
| @requires_basic_auth_if_no_ano | @requires_basic_auth_if_no_ano | ||||||
| def feed_cc_search(query): | def feed_cc_search(query): | ||||||
|     # Handle strange query from Libera Reader with + instead of spaces |     # Handle strange query from Libera Reader with + instead of spaces | ||||||
|     plus_query = unquote_plus(request.base_url.split('/opds/search/')[1]).strip() |     plus_query = unquote_plus(request.environ['RAW_URI'].split('/opds/search/')[1]).strip() | ||||||
|     return feed_search(plus_query) |     return feed_search(plus_query) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ import subprocess  # nosec | |||||||
|  |  | ||||||
| try: | try: | ||||||
|     from gevent.pywsgi import WSGIServer |     from gevent.pywsgi import WSGIServer | ||||||
|  |     from .gevent_wsgi import MyWSGIHandler | ||||||
|     from gevent.pool import Pool |     from gevent.pool import Pool | ||||||
|     from gevent import __version__ as _version |     from gevent import __version__ as _version | ||||||
|     from greenlet import GreenletExit |     from greenlet import GreenletExit | ||||||
| @@ -32,7 +33,7 @@ try: | |||||||
|     VERSION = 'Gevent ' + _version |     VERSION = 'Gevent ' + _version | ||||||
|     _GEVENT = True |     _GEVENT = True | ||||||
| except ImportError: | except ImportError: | ||||||
|     from tornado.wsgi import WSGIContainer |     from .tornado_wsgi import MyWSGIContainer | ||||||
|     from tornado.httpserver import HTTPServer |     from tornado.httpserver import HTTPServer | ||||||
|     from tornado.ioloop import IOLoop |     from tornado.ioloop import IOLoop | ||||||
|     from tornado import version as _version |     from tornado import version as _version | ||||||
| @@ -202,7 +203,8 @@ class WebServer(object): | |||||||
|             if output is None: |             if output is None: | ||||||
|                 output = _readable_listen_address(self.listen_address, self.listen_port) |                 output = _readable_listen_address(self.listen_address, self.listen_port) | ||||||
|             log.info('Starting Gevent server on %s', output) |             log.info('Starting Gevent server on %s', output) | ||||||
|             self.wsgiserver = WSGIServer(sock, self.app, log=self.access_logger, spawn=Pool(), **ssl_args) |             self.wsgiserver = WSGIServer(sock, self.app, log=self.access_logger, handler_class=MyWSGIHandler, | ||||||
|  |                                          spawn=Pool(), **ssl_args) | ||||||
|             if ssl_args: |             if ssl_args: | ||||||
|                 wrap_socket = self.wsgiserver.wrap_socket |                 wrap_socket = self.wsgiserver.wrap_socket | ||||||
|                 def my_wrap_socket(*args, **kwargs): |                 def my_wrap_socket(*args, **kwargs): | ||||||
| @@ -225,8 +227,8 @@ class WebServer(object): | |||||||
|             asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) |             asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) | ||||||
|         log.info('Starting Tornado server on %s', _readable_listen_address(self.listen_address, self.listen_port)) |         log.info('Starting Tornado server on %s', _readable_listen_address(self.listen_address, self.listen_port)) | ||||||
|  |  | ||||||
|         # Max Buffersize set to 200MB            ) |         # Max Buffersize set to 200MB | ||||||
|         http_server = HTTPServer(WSGIContainer(self.app), |         http_server = HTTPServer(MyWSGIContainer(self.app), | ||||||
|                                  max_buffer_size=209700000, |                                  max_buffer_size=209700000, | ||||||
|                                  ssl_options=self.ssl_args) |                                  ssl_options=self.ssl_args) | ||||||
|         http_server.listen(self.listen_port, self.listen_address) |         http_server.listen(self.listen_port, self.listen_address) | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								cps/tornado_wsgi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								cps/tornado_wsgi.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | #  This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) | ||||||
|  | #    Copyright (C) 2022 OzzieIsaacs | ||||||
|  | # | ||||||
|  | #  This program is free software: you can redistribute it and/or modify | ||||||
|  | #  it under the terms of the GNU General Public License as published by | ||||||
|  | #  the Free Software Foundation, either version 3 of the License, or | ||||||
|  | #  (at your option) any later version. | ||||||
|  | # | ||||||
|  | #  This program is distributed in the hope that it will be useful, | ||||||
|  | #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | #  GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | #  You should have received a copy of the GNU General Public License | ||||||
|  | #  along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | from tornado.wsgi import WSGIContainer | ||||||
|  | import tornado | ||||||
|  |  | ||||||
|  | from tornado import escape | ||||||
|  | from tornado import httputil | ||||||
|  |  | ||||||
|  | from typing import List, Tuple, Optional, Callable, Any, Dict, Text | ||||||
|  | from types import TracebackType | ||||||
|  | import typing | ||||||
|  |  | ||||||
|  | if typing.TYPE_CHECKING: | ||||||
|  |     from typing import Type  # noqa: F401 | ||||||
|  |     from wsgiref.types import WSGIApplication as WSGIAppType  # noqa: F4 | ||||||
|  |  | ||||||
|  | class MyWSGIContainer(WSGIContainer): | ||||||
|  |  | ||||||
|  |     def __call__(self, request: httputil.HTTPServerRequest) -> None: | ||||||
|  |         data = {}  # type: Dict[str, Any] | ||||||
|  |         response = []  # type: List[bytes] | ||||||
|  |  | ||||||
|  |         def start_response( | ||||||
|  |             status: str, | ||||||
|  |             headers: List[Tuple[str, str]], | ||||||
|  |             exc_info: Optional[ | ||||||
|  |                 Tuple[ | ||||||
|  |                     "Optional[Type[BaseException]]", | ||||||
|  |                     Optional[BaseException], | ||||||
|  |                     Optional[TracebackType], | ||||||
|  |                 ] | ||||||
|  |             ] = None, | ||||||
|  |         ) -> Callable[[bytes], Any]: | ||||||
|  |             data["status"] = status | ||||||
|  |             data["headers"] = headers | ||||||
|  |             return response.append | ||||||
|  |  | ||||||
|  |         app_response = self.wsgi_application( | ||||||
|  |             MyWSGIContainer.environ(request), start_response | ||||||
|  |         ) | ||||||
|  |         try: | ||||||
|  |             response.extend(app_response) | ||||||
|  |             body = b"".join(response) | ||||||
|  |         finally: | ||||||
|  |             if hasattr(app_response, "close"): | ||||||
|  |                 app_response.close()  # type: ignore | ||||||
|  |         if not data: | ||||||
|  |             raise Exception("WSGI app did not call start_response") | ||||||
|  |  | ||||||
|  |         status_code_str, reason = data["status"].split(" ", 1) | ||||||
|  |         status_code = int(status_code_str) | ||||||
|  |         headers = data["headers"]  # type: List[Tuple[str, str]] | ||||||
|  |         header_set = set(k.lower() for (k, v) in headers) | ||||||
|  |         body = escape.utf8(body) | ||||||
|  |         if status_code != 304: | ||||||
|  |             if "content-length" not in header_set: | ||||||
|  |                 headers.append(("Content-Length", str(len(body)))) | ||||||
|  |             if "content-type" not in header_set: | ||||||
|  |                 headers.append(("Content-Type", "text/html; charset=UTF-8")) | ||||||
|  |         if "server" not in header_set: | ||||||
|  |             headers.append(("Server", "TornadoServer/%s" % tornado.version)) | ||||||
|  |  | ||||||
|  |         start_line = httputil.ResponseStartLine("HTTP/1.1", status_code, reason) | ||||||
|  |         header_obj = httputil.HTTPHeaders() | ||||||
|  |         for key, value in headers: | ||||||
|  |             header_obj.add(key, value) | ||||||
|  |         assert request.connection is not None | ||||||
|  |         request.connection.write_headers(start_line, header_obj, chunk=body) | ||||||
|  |         request.connection.finish() | ||||||
|  |         self._log(status_code, request) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def environ(request: httputil.HTTPServerRequest) -> Dict[Text, Any]: | ||||||
|  |         environ = WSGIContainer.environ(request) | ||||||
|  |         environ['RAW_URI'] = request.path | ||||||
|  |         return environ | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user
	 Ozzie Isaacs
					Ozzie Isaacs