Compare commits
	
		
			2 Commits
		
	
	
		
			v21.12.2
			...
			py37-catch
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 162cb43c4b | ||
|   | 698a359808 | 
							
								
								
									
										28
									
								
								codecov.yml
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								codecov.yml
									
									
									
									
									
								
							| @@ -1,28 +0,0 @@ | |||||||
| coverage: |  | ||||||
|   status: |  | ||||||
|     patch: |  | ||||||
|       default: |  | ||||||
|         target: auto |  | ||||||
|         threshold: 0.75 |  | ||||||
|         informational: true |  | ||||||
|     project: |  | ||||||
|       default: |  | ||||||
|         target: auto |  | ||||||
|         threshold: 0.5 |  | ||||||
|   precision: 3 |  | ||||||
| codecov: |  | ||||||
|   require_ci_to_pass: false |  | ||||||
| ignore: |  | ||||||
|   - "sanic/__main__.py" |  | ||||||
|   - "sanic/compat.py" |  | ||||||
|   - "sanic/reloader_helpers.py" |  | ||||||
|   - "sanic/simple.py" |  | ||||||
|   - "sanic/utils.py" |  | ||||||
|   - "sanic/cli" |  | ||||||
|   - ".github/" |  | ||||||
|   - "changelogs/" |  | ||||||
|   - "docker/" |  | ||||||
|   - "docs/" |  | ||||||
|   - "examples/" |  | ||||||
|   - "scripts/" |  | ||||||
|   - "tests/" |  | ||||||
| @@ -1 +1 @@ | |||||||
| __version__ = "21.12.2" | __version__ = "21.12.1" | ||||||
|   | |||||||
| @@ -1004,10 +1004,10 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta): | |||||||
|         cancelled = False |         cancelled = False | ||||||
|         try: |         try: | ||||||
|             await fut |             await fut | ||||||
|         except Exception as e: |  | ||||||
|             self.error_handler.log(request, e) |  | ||||||
|         except (CancelledError, ConnectionClosed): |         except (CancelledError, ConnectionClosed): | ||||||
|             cancelled = True |             cancelled = True | ||||||
|  |         except Exception as e: | ||||||
|  |             self.error_handler.log(request, e) | ||||||
|         finally: |         finally: | ||||||
|             self.websocket_tasks.remove(fut) |             self.websocket_tasks.remove(fut) | ||||||
|             if cancelled: |             if cancelled: | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ Options = Dict[str, Union[int, str]]  # key=value fields in various headers | |||||||
| OptionsIterable = Iterable[Tuple[str, str]]  # May contain duplicate keys | OptionsIterable = Iterable[Tuple[str, str]]  # May contain duplicate keys | ||||||
|  |  | ||||||
| _token, _quoted = r"([\w!#$%&'*+\-.^_`|~]+)", r'"([^"]*)"' | _token, _quoted = r"([\w!#$%&'*+\-.^_`|~]+)", r'"([^"]*)"' | ||||||
| _param = re.compile(fr";\s*{_token}=(?:{_token}|{_quoted})", re.ASCII) | _param = re.compile(rf";\s*{_token}=(?:{_token}|{_quoted})", re.ASCII) | ||||||
| _firefox_quote_escape = re.compile(r'\\"(?!; |\s*$)') | _firefox_quote_escape = re.compile(r'\\"(?!; |\s*$)') | ||||||
| _ipv6 = "(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}" | _ipv6 = "(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}" | ||||||
| _ipv6_re = re.compile(_ipv6) | _ipv6_re = re.compile(_ipv6) | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ from functools import partial, wraps | |||||||
| from inspect import getsource, signature | from inspect import getsource, signature | ||||||
| from mimetypes import guess_type | from mimetypes import guess_type | ||||||
| from os import path | from os import path | ||||||
| from pathlib import Path, PurePath | from pathlib import PurePath | ||||||
|  | from re import sub | ||||||
| from textwrap import dedent | from textwrap import dedent | ||||||
| from time import gmtime, strftime | from time import gmtime, strftime | ||||||
| from typing import Any, Callable, Iterable, List, Optional, Set, Tuple, Union | from typing import Any, Callable, Iterable, List, Optional, Set, Tuple, Union | ||||||
| @@ -16,7 +17,12 @@ from sanic.base.meta import SanicMeta | |||||||
| from sanic.compat import stat_async | from sanic.compat import stat_async | ||||||
| from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE, HTTP_METHODS | from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE, HTTP_METHODS | ||||||
| from sanic.errorpages import RESPONSE_MAPPING | from sanic.errorpages import RESPONSE_MAPPING | ||||||
| from sanic.exceptions import ContentRangeError, FileNotFound, HeaderNotFound | from sanic.exceptions import ( | ||||||
|  |     ContentRangeError, | ||||||
|  |     FileNotFound, | ||||||
|  |     HeaderNotFound, | ||||||
|  |     InvalidUsage, | ||||||
|  | ) | ||||||
| from sanic.handlers import ContentRangeHandler | from sanic.handlers import ContentRangeHandler | ||||||
| from sanic.log import deprecation, error_logger | from sanic.log import deprecation, error_logger | ||||||
| from sanic.models.futures import FutureRoute, FutureStatic | from sanic.models.futures import FutureRoute, FutureStatic | ||||||
| @@ -769,40 +775,32 @@ class RouteMixin(metaclass=SanicMeta): | |||||||
|         content_type=None, |         content_type=None, | ||||||
|         __file_uri__=None, |         __file_uri__=None, | ||||||
|     ): |     ): | ||||||
|  |         # Using this to determine if the URL is trying to break out of the path | ||||||
|  |         # served.  os.path.realpath seems to be very slow | ||||||
|  |         if __file_uri__ and "../" in __file_uri__: | ||||||
|  |             raise InvalidUsage("Invalid URL") | ||||||
|         # Merge served directory and requested file if provided |         # Merge served directory and requested file if provided | ||||||
|         file_path_raw = Path(unquote(file_or_directory)) |         # Strip all / that in the beginning of the URL to help prevent python | ||||||
|         root_path = file_path = file_path_raw.resolve() |         # from herping a derp and treating the uri as an absolute path | ||||||
|         not_found = FileNotFound( |         root_path = file_path = file_or_directory | ||||||
|  |         if __file_uri__: | ||||||
|  |             file_path = path.join( | ||||||
|  |                 file_or_directory, sub("^[/]*", "", __file_uri__) | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # URL decode the path sent by the browser otherwise we won't be able to | ||||||
|  |         # match filenames which got encoded (filenames with spaces etc) | ||||||
|  |         file_path = path.abspath(unquote(file_path)) | ||||||
|  |         if not file_path.startswith(path.abspath(unquote(root_path))): | ||||||
|  |             error_logger.exception( | ||||||
|  |                 f"File not found: path={file_or_directory}, " | ||||||
|  |                 f"relative_url={__file_uri__}" | ||||||
|  |             ) | ||||||
|  |             raise FileNotFound( | ||||||
|                 "File not found", |                 "File not found", | ||||||
|                 path=file_or_directory, |                 path=file_or_directory, | ||||||
|                 relative_url=__file_uri__, |                 relative_url=__file_uri__, | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         if __file_uri__: |  | ||||||
|             # Strip all / that in the beginning of the URL to help prevent |  | ||||||
|             # python from herping a derp and treating the uri as an |  | ||||||
|             # absolute path |  | ||||||
|             unquoted_file_uri = unquote(__file_uri__).lstrip("/") |  | ||||||
|             file_path_raw = Path(file_or_directory, unquoted_file_uri) |  | ||||||
|             file_path = file_path_raw.resolve() |  | ||||||
|             if ( |  | ||||||
|                 file_path < root_path and not file_path_raw.is_symlink() |  | ||||||
|             ) or ".." in file_path_raw.parts: |  | ||||||
|                 error_logger.exception( |  | ||||||
|                     f"File not found: path={file_or_directory}, " |  | ||||||
|                     f"relative_url={__file_uri__}" |  | ||||||
|                 ) |  | ||||||
|                 raise not_found |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             file_path.relative_to(root_path) |  | ||||||
|         except ValueError: |  | ||||||
|             if not file_path_raw.is_symlink(): |  | ||||||
|                 error_logger.exception( |  | ||||||
|                     f"File not found: path={file_or_directory}, " |  | ||||||
|                     f"relative_url={__file_uri__}" |  | ||||||
|                 ) |  | ||||||
|                 raise not_found |  | ||||||
|         try: |         try: | ||||||
|             headers = {} |             headers = {} | ||||||
|             # Check if the client has been sent this file before |             # Check if the client has been sent this file before | ||||||
| @@ -870,7 +868,11 @@ class RouteMixin(metaclass=SanicMeta): | |||||||
|         except ContentRangeError: |         except ContentRangeError: | ||||||
|             raise |             raise | ||||||
|         except FileNotFoundError: |         except FileNotFoundError: | ||||||
|             raise not_found |             raise FileNotFound( | ||||||
|  |                 "File not found", | ||||||
|  |                 path=file_or_directory, | ||||||
|  |                 relative_url=__file_uri__, | ||||||
|  |             ) | ||||||
|         except Exception: |         except Exception: | ||||||
|             error_logger.exception( |             error_logger.exception( | ||||||
|                 f"Exception in static request handler: " |                 f"Exception in static request handler: " | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import inspect | import inspect | ||||||
| import logging | import logging | ||||||
| import os | import os | ||||||
| import sys |  | ||||||
|  |  | ||||||
| from collections import Counter | from collections import Counter | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| @@ -9,7 +8,7 @@ from time import gmtime, strftime | |||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from sanic import Sanic, text | from sanic import text | ||||||
| from sanic.exceptions import FileNotFound | from sanic.exceptions import FileNotFound | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -22,22 +21,6 @@ def static_file_directory(): | |||||||
|     return static_directory |     return static_directory | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture(scope="module") |  | ||||||
| def double_dotted_directory_file(static_file_directory: str): |  | ||||||
|     """Generate double dotted directory and its files""" |  | ||||||
|     if sys.platform == "win32": |  | ||||||
|         raise Exception("Windows doesn't support double dotted directories") |  | ||||||
|  |  | ||||||
|     file_path = Path(static_file_directory) / "dotted.." / "dot.txt" |  | ||||||
|     double_dotted_dir = file_path.parent |  | ||||||
|     Path.mkdir(double_dotted_dir, exist_ok=True) |  | ||||||
|     with open(file_path, "w") as f: |  | ||||||
|         f.write("DOT\n") |  | ||||||
|     yield file_path |  | ||||||
|     Path.unlink(file_path) |  | ||||||
|     Path.rmdir(double_dotted_dir) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_file_path(static_file_directory, file_name): | def get_file_path(static_file_directory, file_name): | ||||||
|     return os.path.join(static_file_directory, file_name) |     return os.path.join(static_file_directory, file_name) | ||||||
|  |  | ||||||
| @@ -595,43 +578,3 @@ def test_resource_type_dir(app, static_file_directory): | |||||||
| def test_resource_type_unknown(app, static_file_directory, caplog): | def test_resource_type_unknown(app, static_file_directory, caplog): | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|         app.static("/static", static_file_directory, resource_type="unknown") |         app.static("/static", static_file_directory, resource_type="unknown") | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.skipif( |  | ||||||
|     sys.platform == "win32", |  | ||||||
|     reason="Windows does not support double dotted directories", |  | ||||||
| ) |  | ||||||
| def test_dotted_dir_ok( |  | ||||||
|     app: Sanic, static_file_directory: str, double_dotted_directory_file: Path |  | ||||||
| ): |  | ||||||
|     app.static("/foo", static_file_directory) |  | ||||||
|     dot_relative_path = str( |  | ||||||
|         double_dotted_directory_file.relative_to(static_file_directory) |  | ||||||
|     ) |  | ||||||
|     _, response = app.test_client.get("/foo/" + dot_relative_path) |  | ||||||
|     assert response.status == 200 |  | ||||||
|     assert response.body == b"DOT\n" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_breakout(app: Sanic, static_file_directory: str): |  | ||||||
|     app.static("/foo", static_file_directory) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/foo/..%2Ffake/server.py") |  | ||||||
|     assert response.status == 404 |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/foo/..%2Fstatic/test.file") |  | ||||||
|     assert response.status == 404 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.skipif( |  | ||||||
|     sys.platform != "win32", reason="Block backslash on Windows only" |  | ||||||
| ) |  | ||||||
| def test_double_backslash_prohibited_on_win32( |  | ||||||
|     app: Sanic, static_file_directory: str |  | ||||||
| ): |  | ||||||
|     app.static("/foo", static_file_directory) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/foo/static/..\\static/test.file") |  | ||||||
|     assert response.status == 404 |  | ||||||
|     _, response = app.test_client.get("/foo/static\\../static/test.file") |  | ||||||
|     assert response.status == 404 |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user