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.
Files changed (58) hide show
  1. wsgidav/__init__.py +31 -0
  2. wsgidav/dav_error.py +318 -0
  3. wsgidav/dav_provider.py +1621 -0
  4. wsgidav/dc/__init__.py +0 -0
  5. wsgidav/dc/base_dc.py +236 -0
  6. wsgidav/dc/htdigest_dc.py +39 -0
  7. wsgidav/dc/htpasswd_dc.py +33 -0
  8. wsgidav/dc/nt_dc.py +219 -0
  9. wsgidav/dc/pam_dc.py +100 -0
  10. wsgidav/dc/simple_dc.py +138 -0
  11. wsgidav/default_conf.py +123 -0
  12. wsgidav/dir_browser/__init__.py +2 -0
  13. wsgidav/dir_browser/_dir_browser.py +320 -0
  14. wsgidav/dir_browser/htdocs/favicon.ico +0 -0
  15. wsgidav/dir_browser/htdocs/logo.png +0 -0
  16. wsgidav/dir_browser/htdocs/script.js +99 -0
  17. wsgidav/dir_browser/htdocs/style.css +59 -0
  18. wsgidav/dir_browser/htdocs/template.html +105 -0
  19. wsgidav/error_printer.py +124 -0
  20. wsgidav/fs_dav_provider.py +488 -0
  21. wsgidav/http_authenticator.py +583 -0
  22. wsgidav/lock_man/__init__.py +0 -0
  23. wsgidav/lock_man/lock_manager.py +498 -0
  24. wsgidav/lock_man/lock_storage.py +396 -0
  25. wsgidav/lock_man/lock_storage_redis.py +238 -0
  26. wsgidav/mw/__init__.py +0 -0
  27. wsgidav/mw/base_mw.py +48 -0
  28. wsgidav/mw/cors.py +128 -0
  29. wsgidav/mw/debug_filter.py +217 -0
  30. wsgidav/mw/impersonator.py +114 -0
  31. wsgidav/prop_man/__init__.py +0 -0
  32. wsgidav/prop_man/couch_property_manager.py +252 -0
  33. wsgidav/prop_man/mongo_property_manager.py +195 -0
  34. wsgidav/prop_man/property_manager.py +330 -0
  35. wsgidav/request_resolver.py +229 -0
  36. wsgidav/request_server.py +1622 -0
  37. wsgidav/rw_lock.py +226 -0
  38. wsgidav/samples/__init__.py +0 -0
  39. wsgidav/samples/dav_provider_tools.py +195 -0
  40. wsgidav/samples/hg_dav_provider.py +623 -0
  41. wsgidav/samples/mongo_dav_provider.py +164 -0
  42. wsgidav/samples/mysql_dav_provider.py +601 -0
  43. wsgidav/samples/virtual_dav_provider.py +710 -0
  44. wsgidav/server/__init__.py +0 -0
  45. wsgidav/server/ext_wsgiutils_server.py +387 -0
  46. wsgidav/server/run_reloading_server.py +47 -0
  47. wsgidav/server/server_cli.py +814 -0
  48. wsgidav/server/server_sample.py +60 -0
  49. wsgidav/stream_tools.py +148 -0
  50. wsgidav/util.py +1776 -0
  51. wsgidav/wsgidav_app.py +601 -0
  52. wsgidav/xml_tools.py +122 -0
  53. wsgidav-4.3.4.dist-info/LICENSE +21 -0
  54. wsgidav-4.3.4.dist-info/METADATA +163 -0
  55. wsgidav-4.3.4.dist-info/RECORD +58 -0
  56. wsgidav-4.3.4.dist-info/WHEEL +5 -0
  57. wsgidav-4.3.4.dist-info/entry_points.txt +2 -0
  58. 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}")