Radicale 3.7.2__tar.gz → 3.7.3__tar.gz
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.
- {radicale-3.7.2 → radicale-3.7.3}/CHANGELOG.md +8 -0
- {radicale-3.7.2 → radicale-3.7.3}/DOCUMENTATION.md +88 -29
- {radicale-3.7.2 → radicale-3.7.3}/PKG-INFO +1 -1
- {radicale-3.7.2 → radicale-3.7.3}/Radicale.egg-info/PKG-INFO +1 -1
- {radicale-3.7.2 → radicale-3.7.3}/config +12 -0
- {radicale-3.7.2 → radicale-3.7.3}/pyproject.toml +1 -1
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/__init__.py +57 -10
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/base.py +43 -14
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/delete.py +2 -2
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/get.py +16 -6
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/head.py +2 -2
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/mkcalendar.py +3 -3
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/mkcol.py +2 -2
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/move.py +2 -2
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/options.py +1 -1
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/post.py +3 -3
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/propfind.py +57 -22
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/proppatch.py +7 -4
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/put.py +4 -3
- {radicale-3.7.2 → radicale-3.7.3}/radicale/app/report.py +10 -6
- {radicale-3.7.2 → radicale-3.7.3}/radicale/config.py +79 -2
- {radicale-3.7.2 → radicale-3.7.3}/radicale/httputils.py +19 -6
- {radicale-3.7.2 → radicale-3.7.3}/radicale/log.py +103 -1
- {radicale-3.7.2 → radicale-3.7.3}/radicale/sharing/__init__.py +8 -3
- {radicale-3.7.2 → radicale-3.7.3}/radicale/tests/custom/web.py +4 -4
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/__init__.py +8 -3
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal.py +3 -2
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/main.css +11 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/index.html +1 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/api/api.js +6 -8
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/models/collection.js +3 -1
- radicale-3.7.3/radicale/web/internal_data/js/scenes/CollectionsScene.js +364 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/scenes/CreateEditShareScene.js +1 -5
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/scenes/IncomingSharingScene.js +1 -1
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/scenes/LoginScene.js +6 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/scenes/ShareCollectionScene.js +10 -14
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/utils/collection_utils.js +1 -1
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/utils/collections_cache.js +17 -10
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/none.py +2 -2
- radicale-3.7.2/radicale/web/internal_data/js/scenes/CollectionsScene.js +0 -321
- {radicale-3.7.2 → radicale-3.7.3}/COPYING.md +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/MANIFEST.in +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/README.md +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/Radicale.egg-info/SOURCES.txt +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/Radicale.egg-info/dependency_links.txt +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/Radicale.egg-info/entry_points.txt +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/Radicale.egg-info/requires.txt +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/Radicale.egg-info/top_level.txt +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/__main__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/denyall.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/dovecot.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/htpasswd.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/http_remote_user.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/http_x_remote_user.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/imap.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/ldap.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/none.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/oauth2.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/pam.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/auth/remote_user.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/hook/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/hook/email/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/hook/none.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/hook/rabbitmq/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/item/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/item/filter.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/pathutils.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/py.typed +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/rights/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/rights/authenticated.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/rights/from_file.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/rights/owner_only.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/rights/owner_write.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/server.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/sharing/csv.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/sharing/files.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/sharing/none.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/base.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/cache.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/create_collection.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/delete.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/discover.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/get.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/history.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/lock.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/meta.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/move.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/sync.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/upload.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem/verify.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/storage/multifilesystem_nolock.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/tests/custom/__init__.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/tests/custom/auth.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/tests/custom/rights.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/tests/custom/storage_simple_sync.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/types.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/utils.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icon.png +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icons/delete.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icons/download.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icons/edit.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icons/eye.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icons/key.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icons/new.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icons/repeat.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icons/share.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/icons/upload.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/loading.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/css/logo.svg +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/api/common.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/api/sharing.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/constants.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/main.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/scenes/CreateEditCollectionScene.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/scenes/DeleteConfirmationScene.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/scenes/LoadingScene.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/scenes/UploadCollectionScene.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/scenes/scene_manager.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/utils/error.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/utils/form_validator.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/utils/misc.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/utils/permissions.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/web/internal_data/js/utils/url_text.js +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale/xmlutils.py +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/radicale.wsgi +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/rights +0 -0
- {radicale-3.7.2 → radicale-3.7.3}/setup.cfg +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.7.3
|
|
4
|
+
* Extension: expose RADICALE:version for authenticated users via PROPFIND
|
|
5
|
+
* Fix: sharing: GET request on single item with bday conversion
|
|
6
|
+
* Fix: sharing: PROPFIND response on single item with bday conversion
|
|
7
|
+
* Fix: sharing: PROPFIND response related to C:supported-calendar-component-set
|
|
8
|
+
* Feature: conditional log of request/response header/content by new options *_on_notice_condition
|
|
9
|
+
* Fix: user/path validation related to 'unicode-letter'
|
|
10
|
+
|
|
3
11
|
## 3.7.2
|
|
4
12
|
* Fix: broken storage/mtime granularity detection on vfat
|
|
5
13
|
* Improve: `path_to_filesystem()` by pre-detection of collision-free file system
|
|
@@ -927,6 +927,35 @@ Strip script name from URI if called by reverse proxy
|
|
|
927
927
|
|
|
928
928
|
Default: (taken from HTTP_X_SCRIPT_NAME or SCRIPT_NAME)
|
|
929
929
|
|
|
930
|
+
##### validate_user_value
|
|
931
|
+
|
|
932
|
+
_(>= 3.7.2)_
|
|
933
|
+
|
|
934
|
+
Validate user value content
|
|
935
|
+
|
|
936
|
+
Available types are:
|
|
937
|
+
* `none`
|
|
938
|
+
* `minimal` (control and some special chars)
|
|
939
|
+
* `unicode-letter` (unicode letters)
|
|
940
|
+
* `no-unicode` (no unicode)
|
|
941
|
+
* `strict` (reduced ASCII set)
|
|
942
|
+
|
|
943
|
+
Default: `minimal`
|
|
944
|
+
|
|
945
|
+
##### validate_path_value
|
|
946
|
+
|
|
947
|
+
_(>= 3.7.2)_
|
|
948
|
+
|
|
949
|
+
Validate path value content
|
|
950
|
+
|
|
951
|
+
* `none`
|
|
952
|
+
* `minimal` (control and some special chars)
|
|
953
|
+
* `unicode-letter` (unicode letters)
|
|
954
|
+
* `no-unicode` (no unicode)
|
|
955
|
+
* `strict` (reduced ASCII set)
|
|
956
|
+
|
|
957
|
+
Default: `minimal`
|
|
958
|
+
|
|
930
959
|
#### [encoding]
|
|
931
960
|
|
|
932
961
|
##### request
|
|
@@ -1617,35 +1646,6 @@ Strict preconditions check on PUT in case item already exists [RFC6352#9.2](http
|
|
|
1617
1646
|
|
|
1618
1647
|
Default: `False`
|
|
1619
1648
|
|
|
1620
|
-
##### validate_user_value
|
|
1621
|
-
|
|
1622
|
-
_(>= 3.7.2)_
|
|
1623
|
-
|
|
1624
|
-
Validate user value content
|
|
1625
|
-
|
|
1626
|
-
Available types are:
|
|
1627
|
-
* `none`
|
|
1628
|
-
* `minimal` (control and some special chars)
|
|
1629
|
-
* `unicode-letter` (unicode letters)
|
|
1630
|
-
* `no-unicode` (no unicode)
|
|
1631
|
-
* `strict` (reduced ASCII set)
|
|
1632
|
-
|
|
1633
|
-
Default: `minimum`
|
|
1634
|
-
|
|
1635
|
-
##### validate_path_type
|
|
1636
|
-
|
|
1637
|
-
_(>= 3.7.2)_
|
|
1638
|
-
|
|
1639
|
-
Validate path value content
|
|
1640
|
-
|
|
1641
|
-
* `none`
|
|
1642
|
-
* `minimal` (control and some special chars)
|
|
1643
|
-
* `unicode-letter` (unicode letters)
|
|
1644
|
-
* `no-unicode` (no unicode)
|
|
1645
|
-
* `strict` (reduced ASCII set)
|
|
1646
|
-
|
|
1647
|
-
Default: `minimum`
|
|
1648
|
-
|
|
1649
1649
|
##### hook
|
|
1650
1650
|
|
|
1651
1651
|
Command that is run after changes to storage. See the
|
|
@@ -1803,6 +1803,65 @@ Log response content (body) on `level = debug`
|
|
|
1803
1803
|
|
|
1804
1804
|
Default: `False`
|
|
1805
1805
|
|
|
1806
|
+
##### request_header_on_notice_condition
|
|
1807
|
+
|
|
1808
|
+
_(>= 3.7.3)_
|
|
1809
|
+
|
|
1810
|
+
Log request header on `level = notice` if condition is fulfilled
|
|
1811
|
+
|
|
1812
|
+
Default: `{}`
|
|
1813
|
+
|
|
1814
|
+
Format: JSON structure as text
|
|
1815
|
+
|
|
1816
|
+
Supported tokens:
|
|
1817
|
+
* `method`
|
|
1818
|
+
* `path`
|
|
1819
|
+
* `useragent`
|
|
1820
|
+
* `host` (IPv4/IPv6 address/network)
|
|
1821
|
+
* `login`
|
|
1822
|
+
* `status` (only supported on responses)
|
|
1823
|
+
|
|
1824
|
+
Supported matches:
|
|
1825
|
+
* str: `startswith`, `endswith`, `equal`, `re`
|
|
1826
|
+
* int: `==`, `<`, `<=`, `>=`, `>`, `!=`
|
|
1827
|
+
* IP address: `equal`, `==`, `!=`
|
|
1828
|
+
* IP network: `included`, `excluded`
|
|
1829
|
+
|
|
1830
|
+
Examples:
|
|
1831
|
+
* `{"method": {"match": "equal", "value": "GET"}, "login": {"match": "equal", "value": "owner"}}`
|
|
1832
|
+
* `{"method": {"match": "equal", "value": "PUT"}, "status": {"match": ">=", "value": "201"}, "host": {"match": "included", "value": "127.0.0.0/8"}}`
|
|
1833
|
+
* `{"method": {"match": "equal", "value": "PUT"}, "path": {"match": "re", "value": "ev?nt[01]"}}`
|
|
1834
|
+
|
|
1835
|
+
##### request_content_on_notice_condition
|
|
1836
|
+
|
|
1837
|
+
_(>= 3.7.3)_
|
|
1838
|
+
|
|
1839
|
+
Log request content (body) on `level = notice` if condition is fulfilled
|
|
1840
|
+
|
|
1841
|
+
Default: `{}`
|
|
1842
|
+
|
|
1843
|
+
Format: see `request_header_on_notice_condition`
|
|
1844
|
+
|
|
1845
|
+
##### response_header_on_notice_condition
|
|
1846
|
+
|
|
1847
|
+
_(>= 3.7.3)_
|
|
1848
|
+
|
|
1849
|
+
Log response header on `level = notice` if condition is fulfilled
|
|
1850
|
+
|
|
1851
|
+
Default: `{}`
|
|
1852
|
+
|
|
1853
|
+
Format: see `request_header_on_notice_condition`
|
|
1854
|
+
|
|
1855
|
+
##### response_content_on_notice_condition
|
|
1856
|
+
|
|
1857
|
+
_(>= 3.7.3)_
|
|
1858
|
+
|
|
1859
|
+
Log response content (body) on `level = notice` if condition is fulfilled
|
|
1860
|
+
|
|
1861
|
+
Default: `{}`
|
|
1862
|
+
|
|
1863
|
+
Format: see `request_header_on_notice_condition`
|
|
1864
|
+
|
|
1806
1865
|
##### rights_rule_doesnt_match_on_debug
|
|
1807
1866
|
|
|
1808
1867
|
_(>= 3.2.3)_
|
|
@@ -397,6 +397,18 @@
|
|
|
397
397
|
# Log response content on level=debug
|
|
398
398
|
#response_content_on_debug = False
|
|
399
399
|
|
|
400
|
+
# Log request header on level=notice with condition
|
|
401
|
+
#request_header_on_notice_condition = {}
|
|
402
|
+
|
|
403
|
+
# Log request content on level=notice with condition
|
|
404
|
+
#request_content_on_notice_condition = {}
|
|
405
|
+
|
|
406
|
+
# Log response header on level=notice with condition
|
|
407
|
+
#response_header_on_notice_condition = {}
|
|
408
|
+
|
|
409
|
+
# Log response content on level=notice with condition
|
|
410
|
+
#response_content_on_notice_condition = {}
|
|
411
|
+
|
|
400
412
|
# Log rights rule which doesn't match on level=debug
|
|
401
413
|
#rights_rule_doesnt_match_on_debug = False
|
|
402
414
|
|
|
@@ -3,7 +3,7 @@ name = "Radicale"
|
|
|
3
3
|
# When the version is updated, a new section in the CHANGELOG.md file must be
|
|
4
4
|
# added too.
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
version = "3.7.
|
|
6
|
+
version = "3.7.3"
|
|
7
7
|
authors = [{name = "Guillaume Ayoub", email = "guillaume.ayoub@kozea.fr"}, {name = "Unrud", email = "unrud@outlook.com"}, {name = "Peter Bieringer", email = "pb@bieringer.de"}]
|
|
8
8
|
license = {text = "GNU GPL v3"}
|
|
9
9
|
description = "CalDAV and CardDAV Server"
|
|
@@ -130,6 +130,14 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
130
130
|
logger.debug("log request content on debug: %s", self._request_content_on_debug)
|
|
131
131
|
logger.debug("log response header on debug: %s", self._response_header_on_debug)
|
|
132
132
|
logger.debug("log response content on debug: %s", self._response_content_on_debug)
|
|
133
|
+
self._request_header_on_notice_condition = configuration.get("logging", "request_header_on_notice_condition")
|
|
134
|
+
self._request_content_on_notice_condition = configuration.get("logging", "request_content_on_notice_condition")
|
|
135
|
+
self._response_header_on_notice_condition = configuration.get("logging", "response_header_on_notice_condition")
|
|
136
|
+
self._response_content_on_notice_condition = configuration.get("logging", "response_content_on_notice_condition")
|
|
137
|
+
logger.notice("log request header on notice condition: %s", self._request_header_on_notice_condition)
|
|
138
|
+
logger.notice("log request content on notice condition: %s", self._request_content_on_notice_condition)
|
|
139
|
+
logger.notice("log response header on notice condition: %s", self._response_header_on_notice_condition)
|
|
140
|
+
logger.notice("log response content on notice condition: %s", self._response_content_on_notice_condition)
|
|
133
141
|
self._limit_content = configuration.get("logging", "limit_content")
|
|
134
142
|
logger.debug("log limit for content: %d", self._limit_content)
|
|
135
143
|
self._auth_delay = configuration.get("auth", "delay")
|
|
@@ -274,7 +282,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
274
282
|
"""Manage a request."""
|
|
275
283
|
def response(status: int, headers: types.WSGIResponseHeaders,
|
|
276
284
|
answer: Union[None, str, bytes],
|
|
277
|
-
xml_request: Union[None, str] = None) -> _IntermediateResponse:
|
|
285
|
+
xml_request: Union[None, str] = None, request_info: dict = {}) -> _IntermediateResponse:
|
|
278
286
|
"""Helper to create response from internal types.WSGIResponse"""
|
|
279
287
|
headers = dict(headers)
|
|
280
288
|
content_encoding = "plain"
|
|
@@ -286,8 +294,16 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
286
294
|
if logger.isEnabledFor(logging.DEBUG):
|
|
287
295
|
logger.debug("Response content (nonXML):\n%s", utils.textwrap_str(answer, self._limit_content))
|
|
288
296
|
else:
|
|
289
|
-
if
|
|
290
|
-
|
|
297
|
+
if self._response_content_on_notice_condition != {}:
|
|
298
|
+
if log.log_conditional(
|
|
299
|
+
"response-content",
|
|
300
|
+
condition=self._response_content_on_notice_condition,
|
|
301
|
+
value=request_info,
|
|
302
|
+
):
|
|
303
|
+
logger.notice("Response content (nonXML, log condition passed):\n%s", utils.textwrap_str(answer, self._limit_content))
|
|
304
|
+
else:
|
|
305
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
306
|
+
logger.debug("Response content: suppressed by config/option [logging] response_content_on_debug")
|
|
291
307
|
headers["Content-Type"] += "; charset=%s" % self._encoding
|
|
292
308
|
answer = answer.encode(self._encoding)
|
|
293
309
|
accept_encoding = [
|
|
@@ -307,12 +323,22 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
307
323
|
# Add extra headers set in configuration
|
|
308
324
|
headers.update(self._extra_headers)
|
|
309
325
|
|
|
326
|
+
request_info["status"] = status
|
|
327
|
+
|
|
310
328
|
if self._response_header_on_debug:
|
|
311
329
|
if logger.isEnabledFor(logging.DEBUG):
|
|
312
330
|
logger.debug("Response header:\n%s", utils.textwrap_str(pprint.pformat(headers), self._limit_content))
|
|
313
331
|
else:
|
|
314
|
-
if
|
|
315
|
-
|
|
332
|
+
if self._response_header_on_notice_condition != {}:
|
|
333
|
+
if log.log_conditional(
|
|
334
|
+
"response-header",
|
|
335
|
+
condition=self._response_header_on_notice_condition,
|
|
336
|
+
value=request_info,
|
|
337
|
+
):
|
|
338
|
+
logger.notice("Response header (log condition passed):\n%s", utils.textwrap_str(pprint.pformat(headers), self._limit_content))
|
|
339
|
+
else:
|
|
340
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
341
|
+
logger.debug("Response header: suppressed by config/option [logging] response_header_on_debug")
|
|
316
342
|
|
|
317
343
|
# Start response
|
|
318
344
|
# delay on error
|
|
@@ -428,8 +454,10 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
428
454
|
if environ.get("HTTP_X_FORWARDED_HOST") or environ.get("HTTP_X_FORWARDED_PROTO") or environ.get("HTTP_X_FORWARDED_SERVER"):
|
|
429
455
|
reverse_proxy = True
|
|
430
456
|
remote_useragent = ""
|
|
457
|
+
remote_useragent_txt = ""
|
|
431
458
|
if environ.get("HTTP_USER_AGENT"):
|
|
432
|
-
remote_useragent =
|
|
459
|
+
remote_useragent = environ["HTTP_USER_AGENT"]
|
|
460
|
+
remote_useragent_txt = " using %r" % environ["HTTP_USER_AGENT"]
|
|
433
461
|
depthinfo = ""
|
|
434
462
|
if environ.get("HTTP_DEPTH"):
|
|
435
463
|
depthinfo = " with depth %r" % environ["HTTP_DEPTH"]
|
|
@@ -439,12 +467,14 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
439
467
|
https_info = ""
|
|
440
468
|
logger.info("%s request for %r%s received from %s%s%s",
|
|
441
469
|
request_method, unsafe_path, depthinfo,
|
|
442
|
-
remote_host,
|
|
470
|
+
remote_host, remote_useragent_txt, https_info)
|
|
443
471
|
if self._request_header_on_debug:
|
|
444
472
|
logger.debug("Request header:\n%s",
|
|
445
473
|
utils.textwrap_str(pprint.pformat(self._scrub_headers(environ)), self._limit_content))
|
|
446
474
|
else:
|
|
447
|
-
|
|
475
|
+
if not self._request_header_on_notice_condition != {}:
|
|
476
|
+
# conditional request header logging is later
|
|
477
|
+
logger.debug("Request header: suppressed by config/option [logging] request_header_on_debug")
|
|
448
478
|
|
|
449
479
|
# SCRIPT_NAME is already removed from PATH_INFO, according to the
|
|
450
480
|
# WSGI specification.
|
|
@@ -527,6 +557,23 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
527
557
|
self._rights._user_groups = self._auth._ldap_groups
|
|
528
558
|
except AttributeError:
|
|
529
559
|
pass
|
|
560
|
+
|
|
561
|
+
request_info: dict = {
|
|
562
|
+
"method": request_method,
|
|
563
|
+
"login": login, # not 'user' in this step
|
|
564
|
+
"path": path,
|
|
565
|
+
"useragent": remote_useragent,
|
|
566
|
+
"host": remote_host,
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if not self._request_header_on_debug and self._request_header_on_notice_condition != {}:
|
|
570
|
+
if log.log_conditional(
|
|
571
|
+
"request-header",
|
|
572
|
+
condition=self._request_header_on_notice_condition,
|
|
573
|
+
value=request_info,
|
|
574
|
+
):
|
|
575
|
+
logger.notice("Request header (log condition passed):\n%s", utils.textwrap_str(pprint.pformat(self._scrub_headers(environ)), self._limit_content))
|
|
576
|
+
|
|
530
577
|
if user and login == user:
|
|
531
578
|
logger.info("Successful login: %r (%s)", user, info)
|
|
532
579
|
elif user:
|
|
@@ -611,7 +658,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
611
658
|
|
|
612
659
|
try:
|
|
613
660
|
status, headers, answer, xml_request = function(
|
|
614
|
-
environ, base_prefix, path, user,
|
|
661
|
+
environ, base_prefix, path, user, request_info)
|
|
615
662
|
except PermissionError as e:
|
|
616
663
|
logger.error("PermissionError: %s", e)
|
|
617
664
|
status, headers, answer, xml_request = httputils.INTERNAL_SERVER_ERROR
|
|
@@ -648,4 +695,4 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
648
695
|
"WWW-Authenticate":
|
|
649
696
|
"Basic realm=\"%s\"" % self._auth_realm})
|
|
650
697
|
|
|
651
|
-
return response(status, headers, answer, xml_request)
|
|
698
|
+
return response(status, headers, answer, xml_request, request_info)
|
|
@@ -23,7 +23,7 @@ import unicodedata
|
|
|
23
23
|
import xml.etree.ElementTree as ET
|
|
24
24
|
from typing import Optional, Union
|
|
25
25
|
|
|
26
|
-
from radicale import (auth, config, hook, httputils, pathutils, rights,
|
|
26
|
+
from radicale import (auth, config, hook, httputils, log, pathutils, rights,
|
|
27
27
|
sharing, storage, types, utils, web, xmlutils)
|
|
28
28
|
from radicale.log import logger
|
|
29
29
|
from radicale.rights import intersect
|
|
@@ -41,8 +41,10 @@ PATH_PATTERN_STRICT_RE: str = "^[" + PATH_PATTERN_STRICT + "]+$"
|
|
|
41
41
|
USER_BLACKLIST_MINIMAL: list = [":", "'", '"', '*', '?']
|
|
42
42
|
PATH_BLACKLIST_MINIMAL: list = USER_BLACKLIST_MINIMAL
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
BASE_WHITELIST_UNICODE: list = ["-", ".", "@", "_", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
|
|
45
|
+
|
|
46
|
+
USER_WHITELIST_UNICODE: list = BASE_WHITELIST_UNICODE # from USER_PATTERN_STRICT
|
|
47
|
+
PATH_WHITELIST_UNICODE: list = BASE_WHITELIST_UNICODE + ["/", "~"] # from PATH_PATTERN_STRICT
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
def _check_format(self: storage.BaseStorage,
|
|
@@ -72,6 +74,7 @@ def _check_format(self: storage.BaseStorage,
|
|
|
72
74
|
if check_unicode_letter:
|
|
73
75
|
if c not in whitelist_unicode:
|
|
74
76
|
if unicodedata.category(c)[0] != "L":
|
|
77
|
+
logger.trace("_check_format found %r (%r)", c, unicodedata.category(c)[0])
|
|
75
78
|
return False
|
|
76
79
|
if check_no_unicode:
|
|
77
80
|
if ord(c) > 255:
|
|
@@ -137,12 +140,16 @@ class ApplicationBase:
|
|
|
137
140
|
self._log_bad_put_request_content = configuration.get("logging", "bad_put_request_content")
|
|
138
141
|
self._response_content_on_debug = configuration.get("logging", "response_content_on_debug")
|
|
139
142
|
self._request_content_on_debug = configuration.get("logging", "request_content_on_debug")
|
|
143
|
+
self._response_content_on_notice_condition = configuration.get("logging", "response_content_on_notice_condition")
|
|
144
|
+
self._request_content_on_notice_condition = configuration.get("logging", "request_content_on_notice_condition")
|
|
140
145
|
self._limit_content = configuration.get("logging", "limit_content")
|
|
141
146
|
self._validate_user_value = configuration.get("server", "validate_user_value")
|
|
142
147
|
self._validate_path_value = configuration.get("server", "validate_path_value")
|
|
143
148
|
self._hook = hook.load(configuration)
|
|
144
149
|
|
|
145
|
-
def _read_xml_request_body(self,
|
|
150
|
+
def _read_xml_request_body(self,
|
|
151
|
+
environ: types.WSGIEnviron,
|
|
152
|
+
request_info: dict,
|
|
146
153
|
) -> Optional[ET.Element]:
|
|
147
154
|
content = httputils.decode_request(
|
|
148
155
|
self.configuration, environ,
|
|
@@ -154,19 +161,41 @@ class ApplicationBase:
|
|
|
154
161
|
except ET.ParseError as e:
|
|
155
162
|
logger.debug("Request content (Invalid XML):\n%s", content)
|
|
156
163
|
raise RuntimeError("Failed to parse XML: %s" % e) from e
|
|
157
|
-
if
|
|
158
|
-
if
|
|
159
|
-
logger.debug("Request content (XML):\n%s",
|
|
160
|
-
|
|
164
|
+
if self._request_content_on_debug:
|
|
165
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
166
|
+
logger.debug("Request content (XML):\n%s", utils.textwrap_str(xmlutils.pretty_xml(xml_content)))
|
|
167
|
+
else:
|
|
168
|
+
if self._request_content_on_notice_condition != {}:
|
|
169
|
+
if log.log_conditional(
|
|
170
|
+
"request-content",
|
|
171
|
+
condition=self._request_content_on_notice_condition,
|
|
172
|
+
value=request_info,
|
|
173
|
+
):
|
|
174
|
+
logger.notice("Request content (XML, log condition passed):\n%s", utils.textwrap_str(xmlutils.pretty_xml(xml_content)))
|
|
175
|
+
else:
|
|
176
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
177
|
+
logger.debug("Request content (XML, log condition skipped): suppressed")
|
|
161
178
|
else:
|
|
162
|
-
logger.
|
|
179
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
180
|
+
logger.debug("Request content (XML): suppressed by config/option [logging] request_content_on_debug")
|
|
163
181
|
return xml_content
|
|
164
182
|
|
|
165
|
-
def _xml_response(self, xml_content: ET.Element) -> bytes:
|
|
166
|
-
if
|
|
167
|
-
if
|
|
183
|
+
def _xml_response(self, xml_content: ET.Element, request_info: dict) -> bytes:
|
|
184
|
+
if self._response_content_on_debug:
|
|
185
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
168
186
|
logger.debug("Response content (XML):\n%s",
|
|
169
187
|
utils.textwrap_str(xmlutils.pretty_xml(xml_content), self._limit_content))
|
|
188
|
+
else:
|
|
189
|
+
if self._response_content_on_notice_condition != {}:
|
|
190
|
+
if log.log_conditional(
|
|
191
|
+
"response-content",
|
|
192
|
+
condition=self._response_content_on_notice_condition,
|
|
193
|
+
value=request_info,
|
|
194
|
+
):
|
|
195
|
+
logger.notice("Response content (XML, log condition passed):\n%s", utils.textwrap_str(xmlutils.pretty_xml(xml_content)))
|
|
196
|
+
else:
|
|
197
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
198
|
+
logger.debug("Response content (XML, log condition skipped): suppressed")
|
|
170
199
|
else:
|
|
171
200
|
logger.debug("Response content (XML): suppressed by config/option [logging] response_content_on_debug")
|
|
172
201
|
f = io.BytesIO()
|
|
@@ -174,11 +203,11 @@ class ApplicationBase:
|
|
|
174
203
|
xml_declaration=True)
|
|
175
204
|
return f.getvalue()
|
|
176
205
|
|
|
177
|
-
def _webdav_error_response(self, status: int, human_tag: str
|
|
206
|
+
def _webdav_error_response(self, status: int, human_tag: str, request_info: dict
|
|
178
207
|
) -> types.WSGIResponse:
|
|
179
208
|
"""Generate XML error response."""
|
|
180
209
|
headers = {"Content-Type": "text/xml; charset=%s" % self._encoding}
|
|
181
|
-
content = self._xml_response(xmlutils.webdav_error(human_tag))
|
|
210
|
+
content = self._xml_response(xmlutils.webdav_error(human_tag), request_info)
|
|
182
211
|
return status, headers, content, None
|
|
183
212
|
|
|
184
213
|
|
|
@@ -55,7 +55,7 @@ def xml_delete(base_prefix: str, path: str, collection: storage.BaseCollection,
|
|
|
55
55
|
class ApplicationPartDelete(ApplicationBase):
|
|
56
56
|
|
|
57
57
|
def do_DELETE(self, environ: types.WSGIEnviron, base_prefix: str,
|
|
58
|
-
path: str, user: str,
|
|
58
|
+
path: str, user: str, request_info: dict) -> types.WSGIResponse:
|
|
59
59
|
"""Manage DELETE request."""
|
|
60
60
|
actor = user
|
|
61
61
|
permissions_filter = None
|
|
@@ -126,4 +126,4 @@ class ApplicationPartDelete(ApplicationBase):
|
|
|
126
126
|
for notification_item in hook_notification_item_list: # Will be empty if hook not enabled
|
|
127
127
|
self._hook.notify(notification_item)
|
|
128
128
|
headers = {"Content-Type": "text/xml; charset=%s" % self._encoding}
|
|
129
|
-
return client.OK, headers, self._xml_response(xml_answer), None
|
|
129
|
+
return client.OK, headers, self._xml_response(xml_answer, request_info), None
|
|
@@ -68,7 +68,7 @@ class ApplicationPartGet(ApplicationBase):
|
|
|
68
68
|
return value
|
|
69
69
|
|
|
70
70
|
def do_GET(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
|
|
71
|
-
user: str,
|
|
71
|
+
user: str, request_info: dict) -> types.WSGIResponse:
|
|
72
72
|
"""Manage GET request."""
|
|
73
73
|
# Redirect to /.web if the root path is requested
|
|
74
74
|
if not pathutils.strip_path(path):
|
|
@@ -84,7 +84,7 @@ class ApplicationPartGet(ApplicationBase):
|
|
|
84
84
|
base_prefix + unsafe_path, location)
|
|
85
85
|
return httputils.redirect(location, client.MOVED_PERMANENTLY)
|
|
86
86
|
# Dispatch /.web path to web module
|
|
87
|
-
return self._web.get(environ, base_prefix, path, user)
|
|
87
|
+
return self._web.get(environ, base_prefix, path, user, request_info)
|
|
88
88
|
permissions_filter = None
|
|
89
89
|
share = None
|
|
90
90
|
if self._sharing._enabled:
|
|
@@ -121,7 +121,10 @@ class ApplicationPartGet(ApplicationBase):
|
|
|
121
121
|
elif limited_access:
|
|
122
122
|
return httputils.NOT_ALLOWED
|
|
123
123
|
else:
|
|
124
|
-
|
|
124
|
+
if share and share['Conversion'] == "bday":
|
|
125
|
+
content_type = xmlutils.MIMETYPES["VCALENDAR"]
|
|
126
|
+
else:
|
|
127
|
+
content_type = xmlutils.OBJECT_MIMETYPES[item.name]
|
|
125
128
|
content_disposition = ""
|
|
126
129
|
assert item.last_modified
|
|
127
130
|
headers = {
|
|
@@ -130,9 +133,16 @@ class ApplicationPartGet(ApplicationBase):
|
|
|
130
133
|
"ETag": item.etag}
|
|
131
134
|
if content_disposition:
|
|
132
135
|
headers["Content-Disposition"] = content_disposition
|
|
133
|
-
if
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
if share and share['Conversion'] == "bday":
|
|
137
|
+
if isinstance(item, storage.BaseCollection):
|
|
138
|
+
# convert VCF to ICS
|
|
139
|
+
answer = item.serialize(vcf_to_ics=True)
|
|
140
|
+
else:
|
|
141
|
+
item_converted = item.convert_vcf_to_ics()
|
|
142
|
+
if item_converted is not None:
|
|
143
|
+
answer = item_converted.serialize()
|
|
144
|
+
else:
|
|
145
|
+
return httputils.NOT_FOUND
|
|
136
146
|
else:
|
|
137
147
|
answer = item.serialize()
|
|
138
148
|
return client.OK, headers, answer, None
|
|
@@ -26,7 +26,7 @@ from radicale.app.get import ApplicationPartGet
|
|
|
26
26
|
class ApplicationPartHead(ApplicationPartGet, ApplicationBase):
|
|
27
27
|
|
|
28
28
|
def do_HEAD(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
|
|
29
|
-
user: str,
|
|
29
|
+
user: str, request_info: dict) -> types.WSGIResponse:
|
|
30
30
|
"""Manage HEAD request."""
|
|
31
31
|
# Body is dropped in `Application.__call__` for HEAD requests
|
|
32
|
-
return self.do_GET(environ, base_prefix, path, user,
|
|
32
|
+
return self.do_GET(environ, base_prefix, path, user, request_info)
|
|
@@ -32,12 +32,12 @@ from radicale.log import logger
|
|
|
32
32
|
class ApplicationPartMkcalendar(ApplicationBase):
|
|
33
33
|
|
|
34
34
|
def do_MKCALENDAR(self, environ: types.WSGIEnviron, base_prefix: str,
|
|
35
|
-
path: str, user: str,
|
|
35
|
+
path: str, user: str, request_info: dict) -> types.WSGIResponse:
|
|
36
36
|
"""Manage MKCALENDAR request."""
|
|
37
37
|
if "w" not in self._rights.authorization(user, path):
|
|
38
38
|
return httputils.NOT_ALLOWED
|
|
39
39
|
try:
|
|
40
|
-
xml_content = self._read_xml_request_body(environ)
|
|
40
|
+
xml_content = self._read_xml_request_body(environ, request_info)
|
|
41
41
|
except RuntimeError as e:
|
|
42
42
|
logger.warning(
|
|
43
43
|
"Bad MKCALENDAR request on %r: %s", path, e, exc_info=True)
|
|
@@ -67,7 +67,7 @@ class ApplicationPartMkcalendar(ApplicationBase):
|
|
|
67
67
|
item = next(iter(self._storage.discover(path)), None)
|
|
68
68
|
if item:
|
|
69
69
|
return self._webdav_error_response(
|
|
70
|
-
client.CONFLICT, "D:resource-must-be-null")
|
|
70
|
+
client.CONFLICT, "D:resource-must-be-null", request_info)
|
|
71
71
|
parent_path = pathutils.parent_path(path)
|
|
72
72
|
parent_item = next(iter(self._storage.discover(parent_path)), None)
|
|
73
73
|
if not parent_item:
|
|
@@ -32,13 +32,13 @@ from radicale.log import logger
|
|
|
32
32
|
class ApplicationPartMkcol(ApplicationBase):
|
|
33
33
|
|
|
34
34
|
def do_MKCOL(self, environ: types.WSGIEnviron, base_prefix: str,
|
|
35
|
-
path: str, user: str,
|
|
35
|
+
path: str, user: str, request_info: dict) -> types.WSGIResponse:
|
|
36
36
|
"""Manage MKCOL request."""
|
|
37
37
|
permissions = self._rights.authorization(user, path)
|
|
38
38
|
if not rights.intersect(permissions, "Ww"):
|
|
39
39
|
return httputils.NOT_ALLOWED
|
|
40
40
|
try:
|
|
41
|
-
xml_content = self._read_xml_request_body(environ)
|
|
41
|
+
xml_content = self._read_xml_request_body(environ, request_info)
|
|
42
42
|
except RuntimeError as e:
|
|
43
43
|
logger.warning(
|
|
44
44
|
"Bad MKCOL request on %r: %s", path, e, exc_info=True)
|
|
@@ -49,7 +49,7 @@ def get_server_netloc(environ: types.WSGIEnviron, force_port: bool = False):
|
|
|
49
49
|
class ApplicationPartMove(ApplicationBase):
|
|
50
50
|
|
|
51
51
|
def do_MOVE(self, environ: types.WSGIEnviron, base_prefix: str,
|
|
52
|
-
path: str, user: str,
|
|
52
|
+
path: str, user: str, request_info: dict) -> types.WSGIResponse:
|
|
53
53
|
"""Manage MOVE request."""
|
|
54
54
|
raw_dest = environ.get("HTTP_DESTINATION", "")
|
|
55
55
|
|
|
@@ -136,7 +136,7 @@ class ApplicationPartMove(ApplicationBase):
|
|
|
136
136
|
to_collection.has_uid(item.uid)):
|
|
137
137
|
return self._webdav_error_response(
|
|
138
138
|
client.CONFLICT, "%s:no-uid-conflict" % (
|
|
139
|
-
"C" if collection_tag == "VCALENDAR" else "CR"))
|
|
139
|
+
"C" if collection_tag == "VCALENDAR" else "CR"), request_info)
|
|
140
140
|
to_href = posixpath.basename(pathutils.strip_path(to_path))
|
|
141
141
|
try:
|
|
142
142
|
self._storage.move(item, to_collection, to_href)
|
|
@@ -28,7 +28,7 @@ from radicale.app.base import ApplicationBase
|
|
|
28
28
|
class ApplicationPartOptions(ApplicationBase):
|
|
29
29
|
|
|
30
30
|
def do_OPTIONS(self, environ: types.WSGIEnviron, base_prefix: str,
|
|
31
|
-
path: str, user: str,
|
|
31
|
+
path: str, user: str, request_info: dict) -> types.WSGIResponse:
|
|
32
32
|
"""Manage OPTIONS request."""
|
|
33
33
|
headers = {
|
|
34
34
|
"Allow": ", ".join(
|
|
@@ -26,10 +26,10 @@ from radicale.app.base import ApplicationBase
|
|
|
26
26
|
class ApplicationPartPost(ApplicationBase):
|
|
27
27
|
|
|
28
28
|
def do_POST(self, environ: types.WSGIEnviron, base_prefix: str,
|
|
29
|
-
path: str, user: str,
|
|
29
|
+
path: str, user: str, request_info: dict) -> types.WSGIResponse:
|
|
30
30
|
"""Manage POST request."""
|
|
31
31
|
if path == "/.web" or path.startswith("/.web/"):
|
|
32
|
-
return self._web.post(environ, base_prefix, path, user)
|
|
32
|
+
return self._web.post(environ, base_prefix, path, user, request_info)
|
|
33
33
|
elif path == "/.sharing" or path.startswith("/.sharing/"):
|
|
34
|
-
return self._sharing.post(environ, base_prefix, path, user)
|
|
34
|
+
return self._sharing.post(environ, base_prefix, path, user, request_info)
|
|
35
35
|
return httputils.METHOD_NOT_ALLOWED
|