Merge pull request #1990 from pbiering/feature-sharing-prepare
Feature sharing prepare
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# This file is part of Radicale - CalDAV and CardDAV server
|
# This file is part of Radicale - CalDAV and CardDAV server
|
||||||
# Copyright © 2020 Unrud <unrud@outlook.com>
|
# Copyright © 2020 Unrud <unrud@outlook.com>
|
||||||
# Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2024-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,14 +17,14 @@
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import posixpath
|
|
||||||
import sys
|
import sys
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
from radicale import (auth, config, hook, httputils, pathutils, rights,
|
from radicale import (auth, config, hook, httputils, pathutils, rights,
|
||||||
storage, types, utils, web, xmlutils)
|
storage, types, utils, web, xmlutils)
|
||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
|
from radicale.rights import intersect
|
||||||
|
|
||||||
# HACK: https://github.com/tiran/defusedxml/issues/54
|
# HACK: https://github.com/tiran/defusedxml/issues/54
|
||||||
import defusedxml.ElementTree as DefusedET # isort:skip
|
import defusedxml.ElementTree as DefusedET # isort:skip
|
||||||
@@ -106,15 +106,19 @@ class Access:
|
|||||||
permissions: str
|
permissions: str
|
||||||
_rights: rights.BaseRights
|
_rights: rights.BaseRights
|
||||||
_parent_permissions: Optional[str]
|
_parent_permissions: Optional[str]
|
||||||
|
_permissions_filter: Union[str, None] = None
|
||||||
|
|
||||||
def __init__(self, rights: rights.BaseRights, user: str, path: str
|
def __init__(self, rights: rights.BaseRights, user: str, path: str, permissions_filter: Union[str, None] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
self._rights = rights
|
self._rights = rights
|
||||||
self.user = user
|
self.user = user
|
||||||
self.path = path
|
self.path = path
|
||||||
self.parent_path = pathutils.unstrip_path(
|
self.parent_path = pathutils.parent_path(path)
|
||||||
posixpath.dirname(pathutils.strip_path(path)), True)
|
|
||||||
self.permissions = self._rights.authorization(self.user, self.path)
|
self.permissions = self._rights.authorization(self.user, self.path)
|
||||||
|
if permissions_filter is not None:
|
||||||
|
self._permissions_filter = permissions_filter
|
||||||
|
permissions_filtered = intersect(self.permissions, permissions_filter)
|
||||||
|
self.permissions = permissions_filtered
|
||||||
self._parent_permissions = None
|
self._parent_permissions = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -124,6 +128,9 @@ class Access:
|
|||||||
if self._parent_permissions is None:
|
if self._parent_permissions is None:
|
||||||
self._parent_permissions = self._rights.authorization(
|
self._parent_permissions = self._rights.authorization(
|
||||||
self.user, self.parent_path)
|
self.user, self.parent_path)
|
||||||
|
if self._permissions_filter is not None:
|
||||||
|
parent_permissions_filtered = intersect(self._parent_permissions, self._permissions_filter)
|
||||||
|
self._parent_permissions = parent_permissions_filtered
|
||||||
return self._parent_permissions
|
return self._parent_permissions
|
||||||
|
|
||||||
def check(self, permission: str,
|
def check(self, permission: str,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Copyright © 2008 Pascal Halter
|
# Copyright © 2008 Pascal Halter
|
||||||
# Copyright © 2008-2017 Guillaume Ayoub
|
# Copyright © 2008-2017 Guillaume Ayoub
|
||||||
# Copyright © 2017-2020 Unrud <unrud@outlook.com>
|
# Copyright © 2017-2020 Unrud <unrud@outlook.com>
|
||||||
# Copyright © 2024-2025 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2024-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -57,7 +57,8 @@ class ApplicationPartDelete(ApplicationBase):
|
|||||||
def do_DELETE(self, environ: types.WSGIEnviron, base_prefix: str,
|
def do_DELETE(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||||
"""Manage DELETE request."""
|
"""Manage DELETE request."""
|
||||||
access = Access(self._rights, user, path)
|
permissions_filter = None
|
||||||
|
access = Access(self._rights, user, path, permissions_filter)
|
||||||
if not access.check("w"):
|
if not access.check("w"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
with self._storage.acquire_lock("w", user, path=path, request="DELETE"):
|
with self._storage.acquire_lock("w", user, path=path, request="DELETE"):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Copyright © 2008 Pascal Halter
|
# Copyright © 2008 Pascal Halter
|
||||||
# Copyright © 2008-2017 Guillaume Ayoub
|
# Copyright © 2008-2017 Guillaume Ayoub
|
||||||
# Copyright © 2017-2023 Unrud <unrud@outlook.com>
|
# Copyright © 2017-2023 Unrud <unrud@outlook.com>
|
||||||
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2025-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -76,7 +76,8 @@ class ApplicationPartGet(ApplicationBase):
|
|||||||
return httputils.redirect(location, client.MOVED_PERMANENTLY)
|
return httputils.redirect(location, client.MOVED_PERMANENTLY)
|
||||||
# Dispatch /.web path to web module
|
# Dispatch /.web path to web module
|
||||||
return self._web.get(environ, base_prefix, path, user)
|
return self._web.get(environ, base_prefix, path, user)
|
||||||
access = Access(self._rights, user, path)
|
permissions_filter = None
|
||||||
|
access = Access(self._rights, user, path, permissions_filter)
|
||||||
if not access.check("r") and "i" not in access.permissions:
|
if not access.check("r") and "i" not in access.permissions:
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
with self._storage.acquire_lock("r", user):
|
with self._storage.acquire_lock("r", user):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Copyright © 2008 Pascal Halter
|
# Copyright © 2008 Pascal Halter
|
||||||
# Copyright © 2008-2017 Guillaume Ayoub
|
# Copyright © 2008-2017 Guillaume Ayoub
|
||||||
# Copyright © 2017-2021 Unrud <unrud@outlook.com>
|
# Copyright © 2017-2021 Unrud <unrud@outlook.com>
|
||||||
# Copyright © 2024-2025 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2024-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import posixpath
|
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
from http import client
|
from http import client
|
||||||
@@ -62,8 +61,7 @@ class ApplicationPartMkcalendar(ApplicationBase):
|
|||||||
if item:
|
if item:
|
||||||
return self._webdav_error_response(
|
return self._webdav_error_response(
|
||||||
client.CONFLICT, "D:resource-must-be-null")
|
client.CONFLICT, "D:resource-must-be-null")
|
||||||
parent_path = pathutils.unstrip_path(
|
parent_path = pathutils.parent_path(path)
|
||||||
posixpath.dirname(pathutils.strip_path(path)), True)
|
|
||||||
parent_item = next(iter(self._storage.discover(parent_path)), None)
|
parent_item = next(iter(self._storage.discover(parent_path)), None)
|
||||||
if not parent_item:
|
if not parent_item:
|
||||||
return httputils.CONFLICT
|
return httputils.CONFLICT
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Copyright © 2008 Pascal Halter
|
# Copyright © 2008 Pascal Halter
|
||||||
# Copyright © 2008-2017 Guillaume Ayoub
|
# Copyright © 2008-2017 Guillaume Ayoub
|
||||||
# Copyright © 2017-2021 Unrud <unrud@outlook.com>
|
# Copyright © 2017-2021 Unrud <unrud@outlook.com>
|
||||||
# Copyright © 2024-2025 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2024-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import posixpath
|
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
from http import client
|
from http import client
|
||||||
@@ -66,8 +65,7 @@ class ApplicationPartMkcol(ApplicationBase):
|
|||||||
item = next(iter(self._storage.discover(path)), None)
|
item = next(iter(self._storage.discover(path)), None)
|
||||||
if item:
|
if item:
|
||||||
return httputils.METHOD_NOT_ALLOWED
|
return httputils.METHOD_NOT_ALLOWED
|
||||||
parent_path = pathutils.unstrip_path(
|
parent_path = pathutils.parent_path(path)
|
||||||
posixpath.dirname(pathutils.strip_path(path)), True)
|
|
||||||
parent_item = next(iter(self._storage.discover(parent_path)), None)
|
parent_item = next(iter(self._storage.discover(parent_path)), None)
|
||||||
if not parent_item:
|
if not parent_item:
|
||||||
return httputils.CONFLICT
|
return httputils.CONFLICT
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Copyright © 2008 Pascal Halter
|
# Copyright © 2008 Pascal Halter
|
||||||
# Copyright © 2008-2017 Guillaume Ayoub
|
# Copyright © 2008-2017 Guillaume Ayoub
|
||||||
# Copyright © 2017-2023 Unrud <unrud@outlook.com>
|
# Copyright © 2017-2023 Unrud <unrud@outlook.com>
|
||||||
# Copyright © 2023-2025 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2023-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -67,7 +67,8 @@ class ApplicationPartMove(ApplicationBase):
|
|||||||
# Remote destination server, not supported
|
# Remote destination server, not supported
|
||||||
return httputils.REMOTE_DESTINATION
|
return httputils.REMOTE_DESTINATION
|
||||||
|
|
||||||
access = Access(self._rights, user, path)
|
permissions_filter = None
|
||||||
|
access = Access(self._rights, user, path, permissions_filter)
|
||||||
if not access.check("w"):
|
if not access.check("w"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
to_path = pathutils.sanitize_path(to_url.path)
|
to_path = pathutils.sanitize_path(to_url.path)
|
||||||
@@ -76,7 +77,9 @@ class ApplicationPartMove(ApplicationBase):
|
|||||||
"start with base prefix", to_path, path)
|
"start with base prefix", to_path, path)
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
to_path = to_path[len(base_prefix):]
|
to_path = to_path[len(base_prefix):]
|
||||||
to_access = Access(self._rights, user, to_path)
|
to_user = user
|
||||||
|
to_permissions_filter = None
|
||||||
|
to_access = Access(self._rights, to_user, to_path, to_permissions_filter)
|
||||||
if not to_access.check("w"):
|
if not to_access.check("w"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
|
|
||||||
@@ -94,8 +97,7 @@ class ApplicationPartMove(ApplicationBase):
|
|||||||
to_item = next(iter(self._storage.discover(to_path)), None)
|
to_item = next(iter(self._storage.discover(to_path)), None)
|
||||||
if isinstance(to_item, storage.BaseCollection):
|
if isinstance(to_item, storage.BaseCollection):
|
||||||
return httputils.FORBIDDEN
|
return httputils.FORBIDDEN
|
||||||
to_parent_path = pathutils.unstrip_path(
|
to_parent_path = pathutils.parent_path(to_path)
|
||||||
posixpath.dirname(pathutils.strip_path(to_path)), True)
|
|
||||||
to_collection = next(iter(
|
to_collection = next(iter(
|
||||||
self._storage.discover(to_parent_path)), None)
|
self._storage.discover(to_parent_path)), None)
|
||||||
if not to_collection:
|
if not to_collection:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Copyright © 2008 Pascal Halter
|
# Copyright © 2008 Pascal Halter
|
||||||
# Copyright © 2008-2017 Guillaume Ayoub
|
# Copyright © 2008-2017 Guillaume Ayoub
|
||||||
# Copyright © 2017-2021 Unrud <unrud@outlook.com>
|
# Copyright © 2017-2021 Unrud <unrud@outlook.com>
|
||||||
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2025-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -405,7 +405,9 @@ class ApplicationPartPropfind(ApplicationBase):
|
|||||||
def do_PROPFIND(self, environ: types.WSGIEnviron, base_prefix: str,
|
def do_PROPFIND(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||||
"""Manage PROPFIND request."""
|
"""Manage PROPFIND request."""
|
||||||
access = Access(self._rights, user, path)
|
http_depth = environ.get("HTTP_DEPTH", "0")
|
||||||
|
permissions_filter = None
|
||||||
|
access = Access(self._rights, user, path, permissions_filter)
|
||||||
if not access.check("r"):
|
if not access.check("r"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
try:
|
try:
|
||||||
@@ -419,7 +421,7 @@ class ApplicationPartPropfind(ApplicationBase):
|
|||||||
return httputils.REQUEST_TIMEOUT
|
return httputils.REQUEST_TIMEOUT
|
||||||
with self._storage.acquire_lock("r", user):
|
with self._storage.acquire_lock("r", user):
|
||||||
items_iter = iter(self._storage.discover(
|
items_iter = iter(self._storage.discover(
|
||||||
path, environ.get("HTTP_DEPTH", "0"),
|
path, http_depth,
|
||||||
None, self._rights._user_groups))
|
None, self._rights._user_groups))
|
||||||
# take root item for rights checking
|
# take root item for rights checking
|
||||||
item = next(items_iter, None)
|
item = next(items_iter, None)
|
||||||
@@ -429,11 +431,11 @@ class ApplicationPartPropfind(ApplicationBase):
|
|||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
# put item back
|
# put item back
|
||||||
items_iter = itertools.chain([item], items_iter)
|
items_iter = itertools.chain([item], items_iter)
|
||||||
allowed_items = self._collect_allowed_items(items_iter, user)
|
allowed_items = list(self._collect_allowed_items(items_iter, user))
|
||||||
headers = {"DAV": httputils.DAV_HEADERS,
|
headers = {"DAV": httputils.DAV_HEADERS,
|
||||||
"Content-Type": "text/xml; charset=%s" % self._encoding}
|
"Content-Type": "text/xml; charset=%s" % self._encoding}
|
||||||
xml_answer = xml_propfind(base_prefix, path, xml_content,
|
xml_answer = xml_propfind(base_prefix, path, xml_content,
|
||||||
allowed_items, user, self._encoding, max_resource_size=self._max_resource_size)
|
allowed_items, user, self._encoding, max_resource_size=self._max_resource_size)
|
||||||
if xml_answer is None:
|
if xml_answer is None:
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
return client.MULTI_STATUS, headers, self._xml_response(xml_answer), xmlutils.pretty_xml(xml_content)
|
return client.MULTI_STATUS, headers, self._xml_response(xml_answer), xmlutils.pretty_xml(xml_content)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# Copyright © 2008-2017 Guillaume Ayoub
|
# Copyright © 2008-2017 Guillaume Ayoub
|
||||||
# Copyright © 2017-2020 Unrud <unrud@outlook.com>
|
# Copyright © 2017-2020 Unrud <unrud@outlook.com>
|
||||||
# Copyright © 2020-2020 Tuna Celik <tuna@jakpark.com>
|
# Copyright © 2020-2020 Tuna Celik <tuna@jakpark.com>
|
||||||
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2025-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -75,7 +75,8 @@ class ApplicationPartProppatch(ApplicationBase):
|
|||||||
def do_PROPPATCH(self, environ: types.WSGIEnviron, base_prefix: str,
|
def do_PROPPATCH(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||||
"""Manage PROPPATCH request."""
|
"""Manage PROPPATCH request."""
|
||||||
access = Access(self._rights, user, path)
|
permissions_filter = None
|
||||||
|
access = Access(self._rights, user, path, permissions_filter)
|
||||||
if not access.check("w"):
|
if not access.check("w"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
# Copyright © 2024-2024 Pieter Hijma <pieterhijma@users.noreply.github.com>
|
# Copyright © 2024-2024 Pieter Hijma <pieterhijma@users.noreply.github.com>
|
||||||
# Copyright © 2024-2024 Ray <ray@react0r.com>
|
# Copyright © 2024-2024 Ray <ray@react0r.com>
|
||||||
# Copyright © 2024-2025 Georgiy <metallerok@gmail.com>
|
# Copyright © 2024-2025 Georgiy <metallerok@gmail.com>
|
||||||
# Copyright © 2024-2025 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2024-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
# Copyright © 2025-2025 David Greaves <david@dgreaves.com>
|
# Copyright © 2025-2025 David Greaves <david@dgreaves.com>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
@@ -810,7 +810,8 @@ class ApplicationPartReport(ApplicationBase):
|
|||||||
def do_REPORT(self, environ: types.WSGIEnviron, base_prefix: str,
|
def do_REPORT(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||||
"""Manage REPORT request."""
|
"""Manage REPORT request."""
|
||||||
access = Access(self._rights, user, path)
|
permissions_filter = None
|
||||||
|
access = Access(self._rights, user, path, permissions_filter)
|
||||||
if not access.check("r"):
|
if not access.check("r"):
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Copyright © 2014 Jean-Marc Martins
|
# Copyright © 2014 Jean-Marc Martins
|
||||||
# Copyright © 2012-2017 Guillaume Ayoub
|
# Copyright © 2012-2017 Guillaume Ayoub
|
||||||
# Copyright © 2017-2022 Unrud <unrud@outlook.com>
|
# Copyright © 2017-2022 Unrud <unrud@outlook.com>
|
||||||
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
# Copyright © 2025-2026 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -293,6 +293,10 @@ def path_to_filesystem(root: str, sane_path: str) -> str:
|
|||||||
return safe_path
|
return safe_path
|
||||||
|
|
||||||
|
|
||||||
|
def parent_path(path: str) -> str:
|
||||||
|
return unstrip_path(posixpath.dirname(strip_path(path)), True)
|
||||||
|
|
||||||
|
|
||||||
class UnsafePathError(ValueError):
|
class UnsafePathError(ValueError):
|
||||||
|
|
||||||
def __init__(self, path: str) -> None:
|
def __init__(self, path: str) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user