wsgidav 4.3.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wsgidav/__init__.py +31 -0
- wsgidav/dav_error.py +318 -0
- wsgidav/dav_provider.py +1621 -0
- wsgidav/dc/__init__.py +0 -0
- wsgidav/dc/base_dc.py +236 -0
- wsgidav/dc/htdigest_dc.py +39 -0
- wsgidav/dc/htpasswd_dc.py +33 -0
- wsgidav/dc/nt_dc.py +219 -0
- wsgidav/dc/pam_dc.py +100 -0
- wsgidav/dc/simple_dc.py +138 -0
- wsgidav/default_conf.py +123 -0
- wsgidav/dir_browser/__init__.py +2 -0
- wsgidav/dir_browser/_dir_browser.py +320 -0
- wsgidav/dir_browser/htdocs/favicon.ico +0 -0
- wsgidav/dir_browser/htdocs/logo.png +0 -0
- wsgidav/dir_browser/htdocs/script.js +99 -0
- wsgidav/dir_browser/htdocs/style.css +59 -0
- wsgidav/dir_browser/htdocs/template.html +105 -0
- wsgidav/error_printer.py +124 -0
- wsgidav/fs_dav_provider.py +488 -0
- wsgidav/http_authenticator.py +583 -0
- wsgidav/lock_man/__init__.py +0 -0
- wsgidav/lock_man/lock_manager.py +498 -0
- wsgidav/lock_man/lock_storage.py +396 -0
- wsgidav/lock_man/lock_storage_redis.py +238 -0
- wsgidav/mw/__init__.py +0 -0
- wsgidav/mw/base_mw.py +48 -0
- wsgidav/mw/cors.py +128 -0
- wsgidav/mw/debug_filter.py +217 -0
- wsgidav/mw/impersonator.py +114 -0
- wsgidav/prop_man/__init__.py +0 -0
- wsgidav/prop_man/couch_property_manager.py +252 -0
- wsgidav/prop_man/mongo_property_manager.py +195 -0
- wsgidav/prop_man/property_manager.py +330 -0
- wsgidav/request_resolver.py +229 -0
- wsgidav/request_server.py +1622 -0
- wsgidav/rw_lock.py +226 -0
- wsgidav/samples/__init__.py +0 -0
- wsgidav/samples/dav_provider_tools.py +195 -0
- wsgidav/samples/hg_dav_provider.py +623 -0
- wsgidav/samples/mongo_dav_provider.py +164 -0
- wsgidav/samples/mysql_dav_provider.py +601 -0
- wsgidav/samples/virtual_dav_provider.py +710 -0
- wsgidav/server/__init__.py +0 -0
- wsgidav/server/ext_wsgiutils_server.py +387 -0
- wsgidav/server/run_reloading_server.py +47 -0
- wsgidav/server/server_cli.py +814 -0
- wsgidav/server/server_sample.py +60 -0
- wsgidav/stream_tools.py +148 -0
- wsgidav/util.py +1776 -0
- wsgidav/wsgidav_app.py +601 -0
- wsgidav/xml_tools.py +122 -0
- wsgidav-4.3.4.dist-info/LICENSE +21 -0
- wsgidav-4.3.4.dist-info/METADATA +163 -0
- wsgidav-4.3.4.dist-info/RECORD +58 -0
- wsgidav-4.3.4.dist-info/WHEEL +5 -0
- wsgidav-4.3.4.dist-info/entry_points.txt +2 -0
- wsgidav-4.3.4.dist-info/top_level.txt +1 -0
wsgidav/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Current WsgiDAV version number.
|
|
3
|
+
|
|
4
|
+
See https://www.python.org/dev/peps/pep-0440
|
|
5
|
+
|
|
6
|
+
Examples
|
|
7
|
+
Pre-releases (alpha, beta, release candidate):
|
|
8
|
+
'3.0.0a1', '3.0.0b1', '3.0.0rc1'
|
|
9
|
+
Final Release:
|
|
10
|
+
'3.0.0'
|
|
11
|
+
Developmental release (to mark 3.0.0 as 'used'. Don't publish this):
|
|
12
|
+
'3.0.0.dev1'
|
|
13
|
+
NOTE:
|
|
14
|
+
When pywin32 is installed, number must be a.b.c for MSI builds?
|
|
15
|
+
"3.0.0a4" seems not to work in this case!
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
__version__ = "4.3.4"
|
|
19
|
+
|
|
20
|
+
# make version accessible as 'wsgidav.__version__'
|
|
21
|
+
# from wsgidav._version import __version__ # noqa: F401
|
|
22
|
+
|
|
23
|
+
# Initialize a silent 'wsgidav' logger
|
|
24
|
+
# http://docs.python-guide.org/en/latest/writing/logging/#logging-in-a-library
|
|
25
|
+
# https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
|
|
26
|
+
import logging
|
|
27
|
+
|
|
28
|
+
_base_logger = logging.getLogger(__name__)
|
|
29
|
+
_base_logger.addHandler(logging.NullHandler())
|
|
30
|
+
_base_logger.propagate = False
|
|
31
|
+
_base_logger.setLevel(logging.INFO)
|
wsgidav/dav_error.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# (c) 2009-2024 Martin Wendt and contributors; see WsgiDAV https://github.com/mar10/wsgidav
|
|
2
|
+
# Original PyFileServer (c) 2005 Ho Chun Wei.
|
|
3
|
+
# Licensed under the MIT license:
|
|
4
|
+
# http://www.opensource.org/licenses/mit-license.php
|
|
5
|
+
"""
|
|
6
|
+
Implements a DAVError class that is used to signal WebDAV and HTTP errors.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import datetime
|
|
10
|
+
from html import escape as html_escape
|
|
11
|
+
|
|
12
|
+
from wsgidav import xml_tools
|
|
13
|
+
from wsgidav.xml_tools import etree
|
|
14
|
+
|
|
15
|
+
__docformat__ = "reStructuredText"
|
|
16
|
+
|
|
17
|
+
# ========================================================================
|
|
18
|
+
# List of HTTP Response Codes.
|
|
19
|
+
# ========================================================================
|
|
20
|
+
HTTP_CONTINUE = 100
|
|
21
|
+
HTTP_SWITCHING_PROTOCOLS = 101
|
|
22
|
+
HTTP_PROCESSING = 102
|
|
23
|
+
|
|
24
|
+
HTTP_OK = 200
|
|
25
|
+
HTTP_CREATED = 201
|
|
26
|
+
HTTP_ACCEPTED = 202
|
|
27
|
+
HTTP_NON_AUTHORITATIVE_INFO = 203
|
|
28
|
+
HTTP_NO_CONTENT = 204
|
|
29
|
+
HTTP_RESET_CONTENT = 205
|
|
30
|
+
HTTP_PARTIAL_CONTENT = 206
|
|
31
|
+
HTTP_MULTI_STATUS = 207
|
|
32
|
+
HTTP_IM_USED = 226
|
|
33
|
+
|
|
34
|
+
HTTP_MULTIPLE_CHOICES = 300
|
|
35
|
+
HTTP_MOVED = 301
|
|
36
|
+
HTTP_FOUND = 302
|
|
37
|
+
HTTP_SEE_OTHER = 303
|
|
38
|
+
HTTP_NOT_MODIFIED = 304
|
|
39
|
+
HTTP_USE_PROXY = 305
|
|
40
|
+
HTTP_TEMP_REDIRECT = 307
|
|
41
|
+
|
|
42
|
+
HTTP_BAD_REQUEST = 400
|
|
43
|
+
HTTP_UNAUTHORIZED = 401
|
|
44
|
+
HTTP_PAYMENT_REQUIRED = 402
|
|
45
|
+
HTTP_FORBIDDEN = 403
|
|
46
|
+
HTTP_NOT_FOUND = 404
|
|
47
|
+
HTTP_METHOD_NOT_ALLOWED = 405
|
|
48
|
+
HTTP_NOT_ACCEPTABLE = 406
|
|
49
|
+
HTTP_PROXY_AUTH_REQUIRED = 407
|
|
50
|
+
HTTP_REQUEST_TIMEOUT = 408
|
|
51
|
+
HTTP_CONFLICT = 409
|
|
52
|
+
HTTP_GONE = 410
|
|
53
|
+
HTTP_LENGTH_REQUIRED = 411
|
|
54
|
+
HTTP_PRECONDITION_FAILED = 412
|
|
55
|
+
HTTP_REQUEST_ENTITY_TOO_LARGE = 413
|
|
56
|
+
HTTP_REQUEST_URI_TOO_LONG = 414
|
|
57
|
+
HTTP_MEDIATYPE_NOT_SUPPORTED = 415
|
|
58
|
+
HTTP_RANGE_NOT_SATISFIABLE = 416
|
|
59
|
+
HTTP_EXPECTATION_FAILED = 417
|
|
60
|
+
HTTP_UNPROCESSABLE_ENTITY = 422
|
|
61
|
+
HTTP_LOCKED = 423
|
|
62
|
+
HTTP_FAILED_DEPENDENCY = 424
|
|
63
|
+
HTTP_UPGRADE_REQUIRED = 426
|
|
64
|
+
|
|
65
|
+
HTTP_INTERNAL_ERROR = 500
|
|
66
|
+
HTTP_NOT_IMPLEMENTED = 501
|
|
67
|
+
HTTP_BAD_GATEWAY = 502
|
|
68
|
+
HTTP_SERVICE_UNAVAILABLE = 503
|
|
69
|
+
HTTP_GATEWAY_TIMEOUT = 504
|
|
70
|
+
HTTP_VERSION_NOT_SUPPORTED = 505
|
|
71
|
+
HTTP_INSUFFICIENT_STORAGE = 507
|
|
72
|
+
HTTP_NOT_EXTENDED = 510
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ========================================================================
|
|
76
|
+
# if ERROR_DESCRIPTIONS exists for an error code, the error description will be
|
|
77
|
+
# sent as the error response code.
|
|
78
|
+
# Otherwise only the numeric code itself is sent.
|
|
79
|
+
# ========================================================================
|
|
80
|
+
# TODO: paste.httpserver may raise exceptions, if a status code is not
|
|
81
|
+
# followed by a description, so should define all of them.
|
|
82
|
+
ERROR_DESCRIPTIONS = {
|
|
83
|
+
HTTP_OK: "200 OK",
|
|
84
|
+
HTTP_CREATED: "201 Created",
|
|
85
|
+
HTTP_NO_CONTENT: "204 No Content",
|
|
86
|
+
HTTP_NOT_MODIFIED: "304 Not Modified",
|
|
87
|
+
HTTP_BAD_REQUEST: "400 Bad Request",
|
|
88
|
+
HTTP_UNAUTHORIZED: "401 Unauthorized",
|
|
89
|
+
HTTP_FORBIDDEN: "403 Forbidden",
|
|
90
|
+
HTTP_METHOD_NOT_ALLOWED: "405 Method Not Allowed",
|
|
91
|
+
HTTP_NOT_FOUND: "404 Not Found",
|
|
92
|
+
HTTP_CONFLICT: "409 Conflict",
|
|
93
|
+
HTTP_PRECONDITION_FAILED: "412 Precondition Failed",
|
|
94
|
+
HTTP_MEDIATYPE_NOT_SUPPORTED: "415 Media Type Not Supported",
|
|
95
|
+
HTTP_RANGE_NOT_SATISFIABLE: "416 Range Not Satisfiable",
|
|
96
|
+
HTTP_LOCKED: "423 Locked",
|
|
97
|
+
HTTP_FAILED_DEPENDENCY: "424 Failed Dependency",
|
|
98
|
+
HTTP_INTERNAL_ERROR: "500 Internal Server Error",
|
|
99
|
+
HTTP_NOT_IMPLEMENTED: "501 Not Implemented",
|
|
100
|
+
HTTP_BAD_GATEWAY: "502 Bad Gateway",
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# ========================================================================
|
|
104
|
+
# if ERROR_RESPONSES exists for an error code, a html output will be sent as response
|
|
105
|
+
# body including the ERROR_RESPONSES value. Otherwise a null response body is sent.
|
|
106
|
+
# Mostly for browser viewing
|
|
107
|
+
# ========================================================================
|
|
108
|
+
|
|
109
|
+
ERROR_RESPONSES = {
|
|
110
|
+
HTTP_BAD_REQUEST: "An invalid request was specified",
|
|
111
|
+
HTTP_NOT_FOUND: "The specified resource was not found",
|
|
112
|
+
HTTP_UNAUTHORIZED: "Invalid authentication credentials for the requested resource",
|
|
113
|
+
HTTP_FORBIDDEN: "Access denied to the specified resource",
|
|
114
|
+
HTTP_INTERNAL_ERROR: "An internal server error occurred",
|
|
115
|
+
HTTP_NOT_IMPLEMENTED: "Not implemented",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ========================================================================
|
|
120
|
+
# Condition codes
|
|
121
|
+
# http://www.webdav.org/specs/rfc4918.html#precondition.postcondition.xml.elements
|
|
122
|
+
# ========================================================================
|
|
123
|
+
|
|
124
|
+
PRECONDITION_CODE_ProtectedProperty = "{DAV:}cannot-modify-protected-property"
|
|
125
|
+
PRECONDITION_CODE_MissingLockToken = "{DAV:}lock-token-submitted"
|
|
126
|
+
PRECONDITION_CODE_LockTokenMismatch = "{DAV:}lock-token-matches-request-uri"
|
|
127
|
+
PRECONDITION_CODE_LockConflict = "{DAV:}no-conflicting-lock"
|
|
128
|
+
PRECONDITION_CODE_PropfindFiniteDepth = "{DAV:}propfind-finite-depth"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def to_bytes(s, encoding="utf8"):
|
|
132
|
+
"""Convert a text string (unicode) to bytestring (str on Py2 and bytes on Py3)."""
|
|
133
|
+
if type(s) is not bytes:
|
|
134
|
+
s = bytes(s, encoding)
|
|
135
|
+
return s
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def to_str(s, encoding="utf8"):
|
|
139
|
+
"""Convert data to native str type (bytestring on Py2 and unicode on Py3)."""
|
|
140
|
+
if type(s) is bytes:
|
|
141
|
+
s = str(s, encoding)
|
|
142
|
+
elif type(s) is not str:
|
|
143
|
+
s = str(s)
|
|
144
|
+
return s
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class DAVErrorCondition:
|
|
148
|
+
"""May be embedded in :class:`DAVError` instances to store additional data."""
|
|
149
|
+
|
|
150
|
+
def __init__(self, condition_code):
|
|
151
|
+
self.condition_code = condition_code
|
|
152
|
+
self.hrefs = []
|
|
153
|
+
|
|
154
|
+
def __str__(self):
|
|
155
|
+
return f"{self.condition_code}({self.hrefs})"
|
|
156
|
+
|
|
157
|
+
def add_href(self, href):
|
|
158
|
+
assert href.startswith("/")
|
|
159
|
+
assert self.condition_code in (
|
|
160
|
+
PRECONDITION_CODE_LockConflict,
|
|
161
|
+
PRECONDITION_CODE_MissingLockToken,
|
|
162
|
+
)
|
|
163
|
+
if href not in self.hrefs:
|
|
164
|
+
self.hrefs.append(href)
|
|
165
|
+
|
|
166
|
+
def as_xml(self):
|
|
167
|
+
if self.condition_code == PRECONDITION_CODE_MissingLockToken:
|
|
168
|
+
assert (
|
|
169
|
+
len(self.hrefs) > 0
|
|
170
|
+
), "lock-token-submitted requires at least one href"
|
|
171
|
+
error_el = etree.Element("{DAV:}error")
|
|
172
|
+
cond_el = etree.SubElement(error_el, self.condition_code)
|
|
173
|
+
for href in self.hrefs:
|
|
174
|
+
etree.SubElement(cond_el, "{DAV:}href").text = href
|
|
175
|
+
return error_el
|
|
176
|
+
|
|
177
|
+
def as_string(self):
|
|
178
|
+
return to_str(xml_tools.xml_to_bytes(self.as_xml(), pretty=True))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ========================================================================
|
|
182
|
+
# DAVError
|
|
183
|
+
# ========================================================================
|
|
184
|
+
# @@: I prefer having a separate exception type for each response,
|
|
185
|
+
# as in paste.httpexceptions. This way you can catch just the exceptions
|
|
186
|
+
# you want (or you can catch an abstract superclass to get any of them)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class DAVError(Exception):
|
|
190
|
+
"""General error class that is used to signal HTTP and WEBDAV errors.
|
|
191
|
+
|
|
192
|
+
May contain a :class:`DAVErrorCondition`.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
# TODO: Ian Bicking proposed to add an additional 'comment' arg, but
|
|
196
|
+
# couldn't we use the existing 'context_info'?
|
|
197
|
+
# @@: This should also take some message value, for a detailed error message.
|
|
198
|
+
# This would be helpful for debugging.
|
|
199
|
+
|
|
200
|
+
def __init__(
|
|
201
|
+
self,
|
|
202
|
+
status_code,
|
|
203
|
+
context_info=None,
|
|
204
|
+
*,
|
|
205
|
+
src_exception=None,
|
|
206
|
+
err_condition=None,
|
|
207
|
+
add_headers=None,
|
|
208
|
+
):
|
|
209
|
+
# allow passing of Pre- and Postconditions, see
|
|
210
|
+
# http://www.webdav.org/specs/rfc4918.html#precondition.postcondition.xml.elements
|
|
211
|
+
self.value = int(status_code)
|
|
212
|
+
self.context_info = context_info
|
|
213
|
+
self.src_exception = src_exception
|
|
214
|
+
self.err_condition = err_condition
|
|
215
|
+
self.add_headers = add_headers
|
|
216
|
+
if isinstance(err_condition, str):
|
|
217
|
+
self.err_condition = DAVErrorCondition(err_condition)
|
|
218
|
+
assert (
|
|
219
|
+
self.err_condition is None or type(self.err_condition) is DAVErrorCondition
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def __repr__(self):
|
|
223
|
+
return f"DAVError({self.get_user_info()})"
|
|
224
|
+
|
|
225
|
+
def get_user_info(self):
|
|
226
|
+
"""Return readable string."""
|
|
227
|
+
if self.value in ERROR_DESCRIPTIONS:
|
|
228
|
+
s = f"{ERROR_DESCRIPTIONS[self.value]}"
|
|
229
|
+
else:
|
|
230
|
+
s = f"{self.value}"
|
|
231
|
+
|
|
232
|
+
if self.context_info:
|
|
233
|
+
s += f": {self.context_info}"
|
|
234
|
+
elif self.value in ERROR_RESPONSES:
|
|
235
|
+
s += f": {ERROR_RESPONSES[self.value]}"
|
|
236
|
+
|
|
237
|
+
if self.src_exception:
|
|
238
|
+
s += f"\n Source exception: {self.src_exception!r}"
|
|
239
|
+
|
|
240
|
+
if self.err_condition:
|
|
241
|
+
s += f"\n Error condition: {self.err_condition!r}"
|
|
242
|
+
|
|
243
|
+
# if self.add_headers:
|
|
244
|
+
# s += f"\n Add headers: {self.add_headers}"
|
|
245
|
+
return s
|
|
246
|
+
|
|
247
|
+
def get_response_page(self):
|
|
248
|
+
"""Return a tuple (content-type, response page)."""
|
|
249
|
+
from wsgidav.util import public_wsgidav_info
|
|
250
|
+
|
|
251
|
+
# If it has pre- or post-condition: return as XML response
|
|
252
|
+
if self.err_condition:
|
|
253
|
+
return (
|
|
254
|
+
"application/xml; charset=utf-8",
|
|
255
|
+
to_bytes(self.err_condition.as_string()),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Else return as HTML
|
|
259
|
+
status = get_http_status_string(self)
|
|
260
|
+
html = []
|
|
261
|
+
html.append(
|
|
262
|
+
"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' "
|
|
263
|
+
"'http://www.w3.org/TR/html4/strict.dtd'>"
|
|
264
|
+
)
|
|
265
|
+
html.append("<html><head>")
|
|
266
|
+
html.append(
|
|
267
|
+
" <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>"
|
|
268
|
+
)
|
|
269
|
+
html.append(f" <title>{status}</title>")
|
|
270
|
+
html.append("</head><body>")
|
|
271
|
+
html.append(f" <h1>{status}</h1>")
|
|
272
|
+
html.append(f" <p>{html_escape(self.get_user_info())}</p>")
|
|
273
|
+
html.append("<hr/>")
|
|
274
|
+
html.append(
|
|
275
|
+
"<a href='https://github.com/mar10/wsgidav/'>{}</a> - {}".format(
|
|
276
|
+
public_wsgidav_info,
|
|
277
|
+
html_escape(str(datetime.datetime.now()), "utf-8"),
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
html.append("</body></html>")
|
|
281
|
+
html = "\n".join(html)
|
|
282
|
+
return ("text/html; charset=utf-8", to_bytes(html))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def get_http_status_code(v):
|
|
286
|
+
"""Return HTTP response code as integer, e.g. 204."""
|
|
287
|
+
if hasattr(v, "value"):
|
|
288
|
+
return int(v.value) # v is a DAVError
|
|
289
|
+
return int(v)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def get_http_status_string(v):
|
|
293
|
+
"""Return HTTP response string, e.g. 204 -> ('204 No Content').
|
|
294
|
+
The return string always includes descriptive text, to satisfy Apache mod_dav.
|
|
295
|
+
|
|
296
|
+
`v`: status code or DAVError
|
|
297
|
+
"""
|
|
298
|
+
code = get_http_status_code(v)
|
|
299
|
+
try:
|
|
300
|
+
return ERROR_DESCRIPTIONS[code]
|
|
301
|
+
except KeyError:
|
|
302
|
+
return f"{code} Status"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def get_response_page(v):
|
|
306
|
+
v = as_DAVError(v)
|
|
307
|
+
return v.get_response_page()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def as_DAVError(e):
|
|
311
|
+
"""Convert any non-DAVError exception to HTTP_INTERNAL_ERROR."""
|
|
312
|
+
if isinstance(e, DAVError):
|
|
313
|
+
return e
|
|
314
|
+
elif isinstance(e, Exception):
|
|
315
|
+
# traceback.print_exc()
|
|
316
|
+
return DAVError(HTTP_INTERNAL_ERROR, src_exception=e)
|
|
317
|
+
else:
|
|
318
|
+
return DAVError(HTTP_INTERNAL_ERROR, f"{e}")
|