Use module-wide logger and remove logging config
This commit is contained in:
parent
6c9299cf16
commit
54b9995e22
8
config
8
config
@ -136,13 +136,7 @@
|
|||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
|
|
||||||
# Logging configuration file
|
# Set the logging level to debug
|
||||||
# If no config is given, simple information is printed on the standard output
|
|
||||||
# For more information about the syntax of the configuration file, see:
|
|
||||||
# http://docs.python.org/library/logging.config.html
|
|
||||||
#config =
|
|
||||||
|
|
||||||
# Set the default logging level to debug
|
|
||||||
#debug = False
|
#debug = False
|
||||||
|
|
||||||
# Store all environment variables (including those set in the shell)
|
# Store all environment variables (including those set in the shell)
|
||||||
|
52
logging
52
logging
@ -1,52 +0,0 @@
|
|||||||
# -*- mode: conf -*-
|
|
||||||
# vim:ft=cfg
|
|
||||||
|
|
||||||
# Logging config file for Radicale - A simple calendar server
|
|
||||||
#
|
|
||||||
# The recommended path for this file is /etc/radicale/logging
|
|
||||||
# The path must be specified in the logging section of the configuration file
|
|
||||||
#
|
|
||||||
# Some examples are included in Radicale's documentation, see:
|
|
||||||
# http://radicale.org/logging/
|
|
||||||
#
|
|
||||||
# Other handlers are available. For more information, see:
|
|
||||||
# http://docs.python.org/library/logging.config.html
|
|
||||||
|
|
||||||
|
|
||||||
# Loggers, handlers and formatters keys
|
|
||||||
|
|
||||||
[loggers]
|
|
||||||
# Loggers names, main configuration slots
|
|
||||||
keys = root
|
|
||||||
|
|
||||||
[handlers]
|
|
||||||
# Logging handlers, defining logging output methods
|
|
||||||
keys = console
|
|
||||||
|
|
||||||
[formatters]
|
|
||||||
# Logging formatters
|
|
||||||
keys = simple
|
|
||||||
|
|
||||||
|
|
||||||
# Loggers
|
|
||||||
|
|
||||||
[logger_root]
|
|
||||||
# Root logger
|
|
||||||
level = WARNING
|
|
||||||
handlers = console
|
|
||||||
|
|
||||||
|
|
||||||
# Handlers
|
|
||||||
|
|
||||||
[handler_console]
|
|
||||||
# Console handler
|
|
||||||
class = StreamHandler
|
|
||||||
args = (sys.stderr,)
|
|
||||||
formatter = simple
|
|
||||||
|
|
||||||
|
|
||||||
# Formatters
|
|
||||||
|
|
||||||
[formatter_simple]
|
|
||||||
# Simple output format
|
|
||||||
format = [%(thread)x] %(levelname)s: %(message)s
|
|
@ -52,7 +52,7 @@ from xml.etree import ElementTree as ET
|
|||||||
import vobject
|
import vobject
|
||||||
|
|
||||||
from radicale import auth, config, log, rights, storage, web, xmlutils
|
from radicale import auth, config, log, rights, storage, web, xmlutils
|
||||||
|
from radicale.log import logger
|
||||||
|
|
||||||
VERSION = pkg_resources.get_distribution('radicale').version
|
VERSION = pkg_resources.get_distribution('radicale').version
|
||||||
|
|
||||||
@ -104,7 +104,6 @@ class HTTPServer(wsgiref.simple_server.WSGIServer):
|
|||||||
# These class attributes must be set before creating instance
|
# These class attributes must be set before creating instance
|
||||||
client_timeout = None
|
client_timeout = None
|
||||||
max_connections = None
|
max_connections = None
|
||||||
logger = None
|
|
||||||
|
|
||||||
def __init__(self, address, handler, bind_and_activate=True):
|
def __init__(self, address, handler, bind_and_activate=True):
|
||||||
"""Create server."""
|
"""Create server."""
|
||||||
@ -136,7 +135,7 @@ class HTTPServer(wsgiref.simple_server.WSGIServer):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
if self.client_timeout and sys.version_info < (3, 5, 2):
|
if self.client_timeout and sys.version_info < (3, 5, 2):
|
||||||
self.logger.warning("Using server.timeout with Python < 3.5.2 "
|
logger.warning("Using server.timeout with Python < 3.5.2 "
|
||||||
"can cause network connection failures")
|
"can cause network connection failures")
|
||||||
|
|
||||||
def get_request(self):
|
def get_request(self):
|
||||||
@ -148,9 +147,9 @@ class HTTPServer(wsgiref.simple_server.WSGIServer):
|
|||||||
|
|
||||||
def handle_error(self, request, client_address):
|
def handle_error(self, request, client_address):
|
||||||
if issubclass(sys.exc_info()[0], socket.timeout):
|
if issubclass(sys.exc_info()[0], socket.timeout):
|
||||||
self.logger.info("client timed out", exc_info=True)
|
logger.info("client timed out", exc_info=True)
|
||||||
else:
|
else:
|
||||||
self.logger.error("An exception occurred during request: %s",
|
logger.error("An exception occurred during request: %s",
|
||||||
sys.exc_info()[1], exc_info=True)
|
sys.exc_info()[1], exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
@ -208,9 +207,6 @@ class ThreadedHTTPSServer(socketserver.ThreadingMixIn, HTTPSServer):
|
|||||||
class RequestHandler(wsgiref.simple_server.WSGIRequestHandler):
|
class RequestHandler(wsgiref.simple_server.WSGIRequestHandler):
|
||||||
"""HTTP requests handler."""
|
"""HTTP requests handler."""
|
||||||
|
|
||||||
# These class attributes must be set before creating instance
|
|
||||||
logger = None
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Store exception for logging
|
# Store exception for logging
|
||||||
self.error_stream = io.StringIO()
|
self.error_stream = io.StringIO()
|
||||||
@ -236,22 +232,21 @@ class RequestHandler(wsgiref.simple_server.WSGIRequestHandler):
|
|||||||
# Log exception
|
# Log exception
|
||||||
error = self.error_stream.getvalue().strip("\n")
|
error = self.error_stream.getvalue().strip("\n")
|
||||||
if error:
|
if error:
|
||||||
self.logger.error(
|
logger.error(
|
||||||
"An unhandled exception occurred during request:\n%s" % error)
|
"An unhandled exception occurred during request:\n%s" % error)
|
||||||
|
|
||||||
|
|
||||||
class Application:
|
class Application:
|
||||||
"""WSGI application managing collections."""
|
"""WSGI application managing collections."""
|
||||||
|
|
||||||
def __init__(self, configuration, logger):
|
def __init__(self, configuration):
|
||||||
"""Initialize application."""
|
"""Initialize application."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.logger = logger
|
self.Auth = auth.load(configuration)
|
||||||
self.Auth = auth.load(configuration, logger)
|
self.Collection = storage.load(configuration)
|
||||||
self.Collection = storage.load(configuration, logger)
|
self.Rights = rights.load(configuration)
|
||||||
self.Rights = rights.load(configuration, logger)
|
self.Web = web.load(configuration)
|
||||||
self.Web = web.load(configuration, logger)
|
|
||||||
self.encoding = configuration.get("encoding", "request")
|
self.encoding = configuration.get("encoding", "request")
|
||||||
|
|
||||||
def headers_log(self, environ):
|
def headers_log(self, environ):
|
||||||
@ -321,7 +316,7 @@ class Application:
|
|||||||
if can_write:
|
if can_write:
|
||||||
text_status.append("write")
|
text_status.append("write")
|
||||||
write_allowed_items.append(item)
|
write_allowed_items.append(item)
|
||||||
self.logger.debug(
|
logger.debug(
|
||||||
"%s has %s access to %s",
|
"%s has %s access to %s",
|
||||||
repr(user) if user else "anonymous user",
|
repr(user) if user else "anonymous user",
|
||||||
" and ".join(text_status) if text_status else "NO", target)
|
" and ".join(text_status) if text_status else "NO", target)
|
||||||
@ -339,7 +334,7 @@ class Application:
|
|||||||
path = str(environ.get("PATH_INFO", ""))
|
path = str(environ.get("PATH_INFO", ""))
|
||||||
except Exception:
|
except Exception:
|
||||||
path = ""
|
path = ""
|
||||||
self.logger.error("An exception occurred during %s request on %r: "
|
logger.error("An exception occurred during %s request on %r: "
|
||||||
"%s", method, path, e, exc_info=True)
|
"%s", method, path, e, exc_info=True)
|
||||||
status, headers, answer = INTERNAL_SERVER_ERROR
|
status, headers, answer = INTERNAL_SERVER_ERROR
|
||||||
answer = answer.encode("ascii")
|
answer = answer.encode("ascii")
|
||||||
@ -357,7 +352,7 @@ class Application:
|
|||||||
# Set content length
|
# Set content length
|
||||||
if answer:
|
if answer:
|
||||||
if hasattr(answer, "encode"):
|
if hasattr(answer, "encode"):
|
||||||
self.logger.debug("Response content:\n%s", answer)
|
logger.debug("Response content:\n%s", answer)
|
||||||
headers["Content-Type"] += "; charset=%s" % self.encoding
|
headers["Content-Type"] += "; charset=%s" % self.encoding
|
||||||
answer = answer.encode(self.encoding)
|
answer = answer.encode(self.encoding)
|
||||||
accept_encoding = [
|
accept_encoding = [
|
||||||
@ -381,7 +376,7 @@ class Application:
|
|||||||
time_end = datetime.datetime.now()
|
time_end = datetime.datetime.now()
|
||||||
status = "%d %s" % (
|
status = "%d %s" % (
|
||||||
status, client.responses.get(status, "Unknown"))
|
status, client.responses.get(status, "Unknown"))
|
||||||
self.logger.info(
|
logger.info(
|
||||||
"%s response status for %r%s in %.3f seconds: %s",
|
"%s response status for %r%s in %.3f seconds: %s",
|
||||||
environ["REQUEST_METHOD"], environ.get("PATH_INFO", ""),
|
environ["REQUEST_METHOD"], environ.get("PATH_INFO", ""),
|
||||||
depthinfo, (time_end - time_begin).total_seconds(), status)
|
depthinfo, (time_end - time_begin).total_seconds(), status)
|
||||||
@ -403,18 +398,18 @@ class Application:
|
|||||||
if environ.get("HTTP_DEPTH"):
|
if environ.get("HTTP_DEPTH"):
|
||||||
depthinfo = " with depth %r" % environ["HTTP_DEPTH"]
|
depthinfo = " with depth %r" % environ["HTTP_DEPTH"]
|
||||||
time_begin = datetime.datetime.now()
|
time_begin = datetime.datetime.now()
|
||||||
self.logger.info(
|
logger.info(
|
||||||
"%s request for %r%s received from %s%s",
|
"%s request for %r%s received from %s%s",
|
||||||
environ["REQUEST_METHOD"], environ.get("PATH_INFO", ""), depthinfo,
|
environ["REQUEST_METHOD"], environ.get("PATH_INFO", ""), depthinfo,
|
||||||
remote_host, remote_useragent)
|
remote_host, remote_useragent)
|
||||||
headers = pprint.pformat(self.headers_log(environ))
|
headers = pprint.pformat(self.headers_log(environ))
|
||||||
self.logger.debug("Request headers:\n%s", headers)
|
logger.debug("Request headers:\n%s", headers)
|
||||||
|
|
||||||
# Let reverse proxies overwrite SCRIPT_NAME
|
# Let reverse proxies overwrite SCRIPT_NAME
|
||||||
if "HTTP_X_SCRIPT_NAME" in environ:
|
if "HTTP_X_SCRIPT_NAME" in environ:
|
||||||
# script_name must be removed from PATH_INFO by the client.
|
# script_name must be removed from PATH_INFO by the client.
|
||||||
unsafe_base_prefix = environ["HTTP_X_SCRIPT_NAME"]
|
unsafe_base_prefix = environ["HTTP_X_SCRIPT_NAME"]
|
||||||
self.logger.debug("Script name overwritten by client: %r",
|
logger.debug("Script name overwritten by client: %r",
|
||||||
unsafe_base_prefix)
|
unsafe_base_prefix)
|
||||||
else:
|
else:
|
||||||
# SCRIPT_NAME is already removed from PATH_INFO, according to the
|
# SCRIPT_NAME is already removed from PATH_INFO, according to the
|
||||||
@ -422,11 +417,11 @@ class Application:
|
|||||||
unsafe_base_prefix = environ.get("SCRIPT_NAME", "")
|
unsafe_base_prefix = environ.get("SCRIPT_NAME", "")
|
||||||
# Sanitize base prefix
|
# Sanitize base prefix
|
||||||
base_prefix = storage.sanitize_path(unsafe_base_prefix).rstrip("/")
|
base_prefix = storage.sanitize_path(unsafe_base_prefix).rstrip("/")
|
||||||
self.logger.debug("Sanitized script name: %r", base_prefix)
|
logger.debug("Sanitized script name: %r", base_prefix)
|
||||||
# Sanitize request URI (a WSGI server indicates with an empty path,
|
# Sanitize request URI (a WSGI server indicates with an empty path,
|
||||||
# that the URL targets the application root without a trailing slash)
|
# that the URL targets the application root without a trailing slash)
|
||||||
path = storage.sanitize_path(environ.get("PATH_INFO", ""))
|
path = storage.sanitize_path(environ.get("PATH_INFO", ""))
|
||||||
self.logger.debug("Sanitized path: %r", path)
|
logger.debug("Sanitized path: %r", path)
|
||||||
|
|
||||||
# Get function corresponding to method
|
# Get function corresponding to method
|
||||||
function = getattr(self, "do_%s" % environ["REQUEST_METHOD"].upper())
|
function = getattr(self, "do_%s" % environ["REQUEST_METHOD"].upper())
|
||||||
@ -452,21 +447,21 @@ class Application:
|
|||||||
|
|
||||||
user = self.Auth.login(login, password) or "" if login else ""
|
user = self.Auth.login(login, password) or "" if login else ""
|
||||||
if user and login == user:
|
if user and login == user:
|
||||||
self.logger.info("Successful login: %r", user)
|
logger.info("Successful login: %r", user)
|
||||||
elif user:
|
elif user:
|
||||||
self.logger.info("Successful login: %r -> %r", login, user)
|
logger.info("Successful login: %r -> %r", login, user)
|
||||||
elif login:
|
elif login:
|
||||||
self.logger.info("Failed login attempt: %r", login)
|
logger.info("Failed login attempt: %r", login)
|
||||||
# Random delay to avoid timing oracles and bruteforce attacks
|
# Random delay to avoid timing oracles and bruteforce attacks
|
||||||
delay = self.configuration.getfloat("auth", "delay")
|
delay = self.configuration.getfloat("auth", "delay")
|
||||||
if delay > 0:
|
if delay > 0:
|
||||||
random_delay = delay * (0.5 + random.random())
|
random_delay = delay * (0.5 + random.random())
|
||||||
self.logger.debug("Sleeping %.3f seconds", random_delay)
|
logger.debug("Sleeping %.3f seconds", random_delay)
|
||||||
time.sleep(random_delay)
|
time.sleep(random_delay)
|
||||||
|
|
||||||
if user and not storage.is_safe_path_component(user):
|
if user and not storage.is_safe_path_component(user):
|
||||||
# Prevent usernames like "user/calendar.ics"
|
# Prevent usernames like "user/calendar.ics"
|
||||||
self.logger.info("Refused unsafe username: %r", user)
|
logger.info("Refused unsafe username: %r", user)
|
||||||
user = ""
|
user = ""
|
||||||
|
|
||||||
# Create principal collection
|
# Create principal collection
|
||||||
@ -482,11 +477,11 @@ class Application:
|
|||||||
try:
|
try:
|
||||||
self.Collection.create_collection(principal_path)
|
self.Collection.create_collection(principal_path)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.warning("Failed to create principal "
|
logger.warning("Failed to create principal "
|
||||||
"collection %r: %s", user, e)
|
"collection %r: %s", user, e)
|
||||||
user = ""
|
user = ""
|
||||||
else:
|
else:
|
||||||
self.logger.warning("Access to principal path %r denied by "
|
logger.warning("Access to principal path %r denied by "
|
||||||
"rights backend", principal_path)
|
"rights backend", principal_path)
|
||||||
|
|
||||||
# Verify content length
|
# Verify content length
|
||||||
@ -495,15 +490,14 @@ class Application:
|
|||||||
max_content_length = self.configuration.getint(
|
max_content_length = self.configuration.getint(
|
||||||
"server", "max_content_length")
|
"server", "max_content_length")
|
||||||
if max_content_length and content_length > max_content_length:
|
if max_content_length and content_length > max_content_length:
|
||||||
self.logger.info(
|
logger.info("Request body too large: %d", content_length)
|
||||||
"Request body too large: %d", content_length)
|
|
||||||
return response(*REQUEST_ENTITY_TOO_LARGE)
|
return response(*REQUEST_ENTITY_TOO_LARGE)
|
||||||
|
|
||||||
if not login or user:
|
if not login or user:
|
||||||
status, headers, answer = function(
|
status, headers, answer = function(
|
||||||
environ, base_prefix, path, user)
|
environ, base_prefix, path, user)
|
||||||
if (status, headers, answer) == NOT_ALLOWED:
|
if (status, headers, answer) == NOT_ALLOWED:
|
||||||
self.logger.info("Access to %r denied for %s", path,
|
logger.info("Access to %r denied for %s", path,
|
||||||
repr(user) if user else "anonymous user")
|
repr(user) if user else "anonymous user")
|
||||||
else:
|
else:
|
||||||
status, headers, answer = NOT_ALLOWED
|
status, headers, answer = NOT_ALLOWED
|
||||||
@ -511,7 +505,7 @@ class Application:
|
|||||||
if ((status, headers, answer) == NOT_ALLOWED and not user and
|
if ((status, headers, answer) == NOT_ALLOWED and not user and
|
||||||
not external_login):
|
not external_login):
|
||||||
# Unknown or unauthorized user
|
# Unknown or unauthorized user
|
||||||
self.logger.debug("Asking client for authentication")
|
logger.debug("Asking client for authentication")
|
||||||
status = client.UNAUTHORIZED
|
status = client.UNAUTHORIZED
|
||||||
realm = self.configuration.get("server", "realm")
|
realm = self.configuration.get("server", "realm")
|
||||||
headers = dict(headers)
|
headers = dict(headers)
|
||||||
@ -547,7 +541,7 @@ class Application:
|
|||||||
|
|
||||||
def _read_content(self, environ):
|
def _read_content(self, environ):
|
||||||
content = self.decode(self._read_raw_content(environ), environ)
|
content = self.decode(self._read_raw_content(environ), environ)
|
||||||
self.logger.debug("Request content:\n%s", content)
|
logger.debug("Request content:\n%s", content)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def _read_xml_content(self, environ):
|
def _read_xml_content(self, environ):
|
||||||
@ -557,16 +551,16 @@ class Application:
|
|||||||
try:
|
try:
|
||||||
xml_content = ET.fromstring(content)
|
xml_content = ET.fromstring(content)
|
||||||
except ET.ParseError as e:
|
except ET.ParseError as e:
|
||||||
self.logger.debug("Request content (Invalid XML):\n%s", content)
|
logger.debug("Request content (Invalid XML):\n%s", content)
|
||||||
raise RuntimeError("Failed to parse XML: %s" % e) from e
|
raise RuntimeError("Failed to parse XML: %s" % e) from e
|
||||||
if self.logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
self.logger.debug("Request content:\n%s",
|
logger.debug("Request content:\n%s",
|
||||||
xmlutils.pretty_xml(xml_content))
|
xmlutils.pretty_xml(xml_content))
|
||||||
return xml_content
|
return xml_content
|
||||||
|
|
||||||
def _write_xml_content(self, xml_content):
|
def _write_xml_content(self, xml_content):
|
||||||
if self.logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
self.logger.debug("Response content:\n%s",
|
logger.debug("Response content:\n%s",
|
||||||
xmlutils.pretty_xml(xml_content))
|
xmlutils.pretty_xml(xml_content))
|
||||||
f = io.BytesIO()
|
f = io.BytesIO()
|
||||||
ET.ElementTree(xml_content).write(f, encoding=self.encoding,
|
ET.ElementTree(xml_content).write(f, encoding=self.encoding,
|
||||||
@ -652,11 +646,11 @@ class Application:
|
|||||||
try:
|
try:
|
||||||
xml_content = self._read_xml_content(environ)
|
xml_content = self._read_xml_content(environ)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad MKCALENDAR request on %r: %s", path, e, exc_info=True)
|
"Bad MKCALENDAR request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
self.logger.debug("client timed out", exc_info=True)
|
logger.debug("client timed out", exc_info=True)
|
||||||
return REQUEST_TIMEOUT
|
return REQUEST_TIMEOUT
|
||||||
with self.Collection.acquire_lock("w", user):
|
with self.Collection.acquire_lock("w", user):
|
||||||
item = next(self.Collection.discover(path), None)
|
item = next(self.Collection.discover(path), None)
|
||||||
@ -679,7 +673,7 @@ class Application:
|
|||||||
storage.check_and_sanitize_props(props)
|
storage.check_and_sanitize_props(props)
|
||||||
self.Collection.create_collection(path, props=props)
|
self.Collection.create_collection(path, props=props)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad MKCALENDAR request on %r: %s", path, e, exc_info=True)
|
"Bad MKCALENDAR request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
return client.CREATED, {}, None
|
return client.CREATED, {}, None
|
||||||
@ -691,11 +685,11 @@ class Application:
|
|||||||
try:
|
try:
|
||||||
xml_content = self._read_xml_content(environ)
|
xml_content = self._read_xml_content(environ)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad MKCOL request on %r: %s", path, e, exc_info=True)
|
"Bad MKCOL request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
self.logger.debug("client timed out", exc_info=True)
|
logger.debug("client timed out", exc_info=True)
|
||||||
return REQUEST_TIMEOUT
|
return REQUEST_TIMEOUT
|
||||||
with self.Collection.acquire_lock("w", user):
|
with self.Collection.acquire_lock("w", user):
|
||||||
item = next(self.Collection.discover(path), None)
|
item = next(self.Collection.discover(path), None)
|
||||||
@ -714,7 +708,7 @@ class Application:
|
|||||||
storage.check_and_sanitize_props(props)
|
storage.check_and_sanitize_props(props)
|
||||||
self.Collection.create_collection(path, props=props)
|
self.Collection.create_collection(path, props=props)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad MKCOL request on %r: %s", path, e, exc_info=True)
|
"Bad MKCOL request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
return client.CREATED, {}, None
|
return client.CREATED, {}, None
|
||||||
@ -724,15 +718,15 @@ class Application:
|
|||||||
raw_dest = environ.get("HTTP_DESTINATION", "")
|
raw_dest = environ.get("HTTP_DESTINATION", "")
|
||||||
to_url = urlparse(raw_dest)
|
to_url = urlparse(raw_dest)
|
||||||
if to_url.netloc != environ["HTTP_HOST"]:
|
if to_url.netloc != environ["HTTP_HOST"]:
|
||||||
self.logger.info("Unsupported destination address: %r", raw_dest)
|
logger.info("Unsupported destination address: %r", raw_dest)
|
||||||
# Remote destination server, not supported
|
# Remote destination server, not supported
|
||||||
return REMOTE_DESTINATION
|
return REMOTE_DESTINATION
|
||||||
if not self._access(user, path, "w"):
|
if not self._access(user, path, "w"):
|
||||||
return NOT_ALLOWED
|
return NOT_ALLOWED
|
||||||
to_path = storage.sanitize_path(to_url.path)
|
to_path = storage.sanitize_path(to_url.path)
|
||||||
if not (to_path + "/").startswith(base_prefix + "/"):
|
if not (to_path + "/").startswith(base_prefix + "/"):
|
||||||
self.logger.warning("Destination %r from MOVE request on %r does"
|
logger.warning("Destination %r from MOVE request on %r doesn't "
|
||||||
"n't start with base prefix", to_path, path)
|
"start with base prefix", to_path, path)
|
||||||
return NOT_ALLOWED
|
return NOT_ALLOWED
|
||||||
to_path = to_path[len(base_prefix):]
|
to_path = to_path[len(base_prefix):]
|
||||||
if not self._access(user, to_path, "w"):
|
if not self._access(user, to_path, "w"):
|
||||||
@ -774,7 +768,7 @@ class Application:
|
|||||||
try:
|
try:
|
||||||
self.Collection.move(item, to_collection, to_href)
|
self.Collection.move(item, to_collection, to_href)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad MOVE request on %r: %s", path, e, exc_info=True)
|
"Bad MOVE request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
return client.NO_CONTENT if to_item else client.CREATED, {}, None
|
return client.NO_CONTENT if to_item else client.CREATED, {}, None
|
||||||
@ -794,11 +788,11 @@ class Application:
|
|||||||
try:
|
try:
|
||||||
xml_content = self._read_xml_content(environ)
|
xml_content = self._read_xml_content(environ)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad PROPFIND request on %r: %s", path, e, exc_info=True)
|
"Bad PROPFIND request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
self.logger.debug("client timed out", exc_info=True)
|
logger.debug("client timed out", exc_info=True)
|
||||||
return REQUEST_TIMEOUT
|
return REQUEST_TIMEOUT
|
||||||
with self.Collection.acquire_lock("r", user):
|
with self.Collection.acquire_lock("r", user):
|
||||||
items = self.Collection.discover(
|
items = self.Collection.discover(
|
||||||
@ -827,11 +821,11 @@ class Application:
|
|||||||
try:
|
try:
|
||||||
xml_content = self._read_xml_content(environ)
|
xml_content = self._read_xml_content(environ)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad PROPPATCH request on %r: %s", path, e, exc_info=True)
|
"Bad PROPPATCH request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
self.logger.debug("client timed out", exc_info=True)
|
logger.debug("client timed out", exc_info=True)
|
||||||
return REQUEST_TIMEOUT
|
return REQUEST_TIMEOUT
|
||||||
with self.Collection.acquire_lock("w", user):
|
with self.Collection.acquire_lock("w", user):
|
||||||
item = next(self.Collection.discover(path), None)
|
item = next(self.Collection.discover(path), None)
|
||||||
@ -845,7 +839,7 @@ class Application:
|
|||||||
xml_answer = xmlutils.proppatch(base_prefix, path, xml_content,
|
xml_answer = xmlutils.proppatch(base_prefix, path, xml_content,
|
||||||
item)
|
item)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad PROPPATCH request on %r: %s", path, e, exc_info=True)
|
"Bad PROPPATCH request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
return (client.MULTI_STATUS, headers,
|
return (client.MULTI_STATUS, headers,
|
||||||
@ -858,11 +852,10 @@ class Application:
|
|||||||
try:
|
try:
|
||||||
content = self._read_content(environ)
|
content = self._read_content(environ)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.logger.warning(
|
logger.warning("Bad PUT request on %r: %s", path, e, exc_info=True)
|
||||||
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
self.logger.debug("client timed out", exc_info=True)
|
logger.debug("client timed out", exc_info=True)
|
||||||
return REQUEST_TIMEOUT
|
return REQUEST_TIMEOUT
|
||||||
with self.Collection.acquire_lock("w", user):
|
with self.Collection.acquire_lock("w", user):
|
||||||
parent_path = storage.sanitize_path(
|
parent_path = storage.sanitize_path(
|
||||||
@ -917,7 +910,7 @@ class Application:
|
|||||||
if not write_whole_collection and item else None,
|
if not write_whole_collection and item else None,
|
||||||
tag=tag)
|
tag=tag)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
|
|
||||||
@ -939,7 +932,7 @@ class Application:
|
|||||||
new_item = self.Collection.create_collection(
|
new_item = self.Collection.create_collection(
|
||||||
path, items, props)
|
path, items, props)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
else:
|
else:
|
||||||
@ -952,7 +945,7 @@ class Application:
|
|||||||
parent_item.set_meta_all(new_props)
|
parent_item.set_meta_all(new_props)
|
||||||
new_item = parent_item.upload(href, items[0])
|
new_item = parent_item.upload(href, items[0])
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
headers = {"ETag": new_item.etag}
|
headers = {"ETag": new_item.etag}
|
||||||
@ -965,11 +958,11 @@ class Application:
|
|||||||
try:
|
try:
|
||||||
xml_content = self._read_xml_content(environ)
|
xml_content = self._read_xml_content(environ)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad REPORT request on %r: %s", path, e, exc_info=True)
|
"Bad REPORT request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
self.logger.debug("client timed out", exc_info=True)
|
logger.debug("client timed out", exc_info=True)
|
||||||
return REQUEST_TIMEOUT
|
return REQUEST_TIMEOUT
|
||||||
with self.Collection.acquire_lock("r", user):
|
with self.Collection.acquire_lock("r", user):
|
||||||
item = next(self.Collection.discover(path), None)
|
item = next(self.Collection.discover(path), None)
|
||||||
@ -986,7 +979,7 @@ class Application:
|
|||||||
status, xml_answer = xmlutils.report(
|
status, xml_answer = xmlutils.report(
|
||||||
base_prefix, path, xml_content, collection)
|
base_prefix, path, xml_content, collection)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Bad REPORT request on %r: %s", path, e, exc_info=True)
|
"Bad REPORT request on %r: %s", path, e, exc_info=True)
|
||||||
return BAD_REQUEST
|
return BAD_REQUEST
|
||||||
return (status, headers, self._write_xml_content(xml_answer))
|
return (status, headers, self._write_xml_content(xml_answer))
|
||||||
@ -1002,13 +995,12 @@ def _init_application(config_path):
|
|||||||
with _application_lock:
|
with _application_lock:
|
||||||
if _application is not None:
|
if _application is not None:
|
||||||
return
|
return
|
||||||
|
log.setup()
|
||||||
_application_config_path = config_path
|
_application_config_path = config_path
|
||||||
configuration = config.load([config_path] if config_path else [],
|
configuration = config.load([config_path] if config_path else [],
|
||||||
ignore_missing_paths=False)
|
ignore_missing_paths=False)
|
||||||
filename = os.path.expanduser(configuration.get("logging", "config"))
|
log.set_debug(configuration.getboolean("logging", "debug"))
|
||||||
debug = configuration.getboolean("logging", "debug")
|
_application = Application(configuration)
|
||||||
logger = log.start("radicale", filename, debug)
|
|
||||||
_application = Application(configuration, logger)
|
|
||||||
|
|
||||||
|
|
||||||
def application(environ, start_response):
|
def application(environ, start_response):
|
||||||
|
@ -34,10 +34,13 @@ from wsgiref.simple_server import make_server
|
|||||||
|
|
||||||
from radicale import (VERSION, Application, RequestHandler, ThreadedHTTPServer,
|
from radicale import (VERSION, Application, RequestHandler, ThreadedHTTPServer,
|
||||||
ThreadedHTTPSServer, config, log, storage)
|
ThreadedHTTPSServer, config, log, storage)
|
||||||
|
from radicale.log import logger
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
"""Run Radicale as a standalone server."""
|
"""Run Radicale as a standalone server."""
|
||||||
|
log.setup()
|
||||||
|
|
||||||
# Get command-line arguments
|
# Get command-line arguments
|
||||||
parser = argparse.ArgumentParser(usage="radicale [OPTIONS]")
|
parser = argparse.ArgumentParser(usage="radicale [OPTIONS]")
|
||||||
|
|
||||||
@ -79,6 +82,10 @@ def run():
|
|||||||
group.add_argument(*args, **kwargs)
|
group.add_argument(*args, **kwargs)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Preliminary configure logging
|
||||||
|
log.set_debug(args.logging_debug)
|
||||||
|
|
||||||
if args.config is not None:
|
if args.config is not None:
|
||||||
config_paths = [args.config] if args.config else []
|
config_paths = [args.config] if args.config else []
|
||||||
ignore_missing_paths = False
|
ignore_missing_paths = False
|
||||||
@ -92,9 +99,7 @@ def run():
|
|||||||
configuration = config.load(config_paths,
|
configuration = config.load(config_paths,
|
||||||
ignore_missing_paths=ignore_missing_paths)
|
ignore_missing_paths=ignore_missing_paths)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("ERROR: Invalid configuration: %s" % e, file=sys.stderr)
|
log.error("Invalid configuration: %s", e, exc_info=True)
|
||||||
if args.logging_debug:
|
|
||||||
raise
|
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Update Radicale configuration according to arguments
|
# Update Radicale configuration according to arguments
|
||||||
@ -105,25 +110,13 @@ def run():
|
|||||||
if value is not None:
|
if value is not None:
|
||||||
configuration.set(section, action.split('_', 1)[1], value)
|
configuration.set(section, action.split('_', 1)[1], value)
|
||||||
|
|
||||||
if args.verify_storage:
|
# Configure logging
|
||||||
# Write to stderr when storage verification is requested
|
log.set_debug(configuration.getboolean("logging", "debug"))
|
||||||
configuration["logging"]["config"] = ""
|
|
||||||
|
|
||||||
# Start logging
|
|
||||||
filename = os.path.expanduser(configuration.get("logging", "config"))
|
|
||||||
debug = configuration.getboolean("logging", "debug")
|
|
||||||
try:
|
|
||||||
logger = log.start("radicale", filename, debug)
|
|
||||||
except Exception as e:
|
|
||||||
print("ERROR: Failed to start logger: %s" % e, file=sys.stderr)
|
|
||||||
if debug:
|
|
||||||
raise
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if args.verify_storage:
|
if args.verify_storage:
|
||||||
logger.info("Verifying storage")
|
logger.info("Verifying storage")
|
||||||
try:
|
try:
|
||||||
Collection = storage.load(configuration, logger)
|
Collection = storage.load(configuration)
|
||||||
with Collection.acquire_lock("r"):
|
with Collection.acquire_lock("r"):
|
||||||
if not Collection.verify():
|
if not Collection.verify():
|
||||||
logger.error("Storage verifcation failed")
|
logger.error("Storage verifcation failed")
|
||||||
@ -135,14 +128,14 @@ def run():
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
serve(configuration, logger)
|
serve(configuration)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("An exception occurred during server startup: %s", e,
|
logger.error("An exception occurred during server startup: %s", e,
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def daemonize(configuration, logger):
|
def daemonize(configuration):
|
||||||
"""Fork and decouple if Radicale is configured as daemon."""
|
"""Fork and decouple if Radicale is configured as daemon."""
|
||||||
# Check and create PID file in a race-free manner
|
# Check and create PID file in a race-free manner
|
||||||
if configuration.get("server", "pid"):
|
if configuration.get("server", "pid"):
|
||||||
@ -181,7 +174,7 @@ def daemonize(configuration, logger):
|
|||||||
os.dup2(null_out.fileno(), sys.stderr.fileno())
|
os.dup2(null_out.fileno(), sys.stderr.fileno())
|
||||||
|
|
||||||
|
|
||||||
def serve(configuration, logger):
|
def serve(configuration):
|
||||||
"""Serve radicale from configuration."""
|
"""Serve radicale from configuration."""
|
||||||
logger.info("Starting Radicale")
|
logger.info("Starting Radicale")
|
||||||
|
|
||||||
@ -211,9 +204,7 @@ def serve(configuration, logger):
|
|||||||
server_class.client_timeout = configuration.getint("server", "timeout")
|
server_class.client_timeout = configuration.getint("server", "timeout")
|
||||||
server_class.max_connections = configuration.getint(
|
server_class.max_connections = configuration.getint(
|
||||||
"server", "max_connections")
|
"server", "max_connections")
|
||||||
server_class.logger = logger
|
|
||||||
|
|
||||||
RequestHandler.logger = logger
|
|
||||||
if not configuration.getboolean("server", "dns_lookup"):
|
if not configuration.getboolean("server", "dns_lookup"):
|
||||||
RequestHandler.address_string = lambda self: self.client_address[0]
|
RequestHandler.address_string = lambda self: self.client_address[0]
|
||||||
|
|
||||||
@ -226,7 +217,7 @@ def serve(configuration, logger):
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Failed to parse address %r: %s" % (host, e)) from e
|
"Failed to parse address %r: %s" % (host, e)) from e
|
||||||
application = Application(configuration, logger)
|
application = Application(configuration)
|
||||||
try:
|
try:
|
||||||
server = make_server(
|
server = make_server(
|
||||||
address, port, application, server_class, RequestHandler)
|
address, port, application, server_class, RequestHandler)
|
||||||
@ -270,7 +261,7 @@ def serve(configuration, logger):
|
|||||||
# Fallback to busy waiting. (select.select blocks SIGINT on Windows.)
|
# Fallback to busy waiting. (select.select blocks SIGINT on Windows.)
|
||||||
select_timeout = 1.0
|
select_timeout = 1.0
|
||||||
if configuration.getboolean("server", "daemon"):
|
if configuration.getboolean("server", "daemon"):
|
||||||
daemonize(configuration, logger)
|
daemonize(configuration)
|
||||||
logger.info("Radicale server ready")
|
logger.info("Radicale server ready")
|
||||||
while not shutdown_program:
|
while not shutdown_program:
|
||||||
try:
|
try:
|
||||||
|
@ -60,11 +60,13 @@ import hmac
|
|||||||
import os
|
import os
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
|
from radicale.log import logger
|
||||||
|
|
||||||
INTERNAL_TYPES = ("None", "none", "remote_user", "http_x_remote_user",
|
INTERNAL_TYPES = ("None", "none", "remote_user", "http_x_remote_user",
|
||||||
"htpasswd")
|
"htpasswd")
|
||||||
|
|
||||||
|
|
||||||
def load(configuration, logger):
|
def load(configuration):
|
||||||
"""Load the authentication manager chosen in configuration."""
|
"""Load the authentication manager chosen in configuration."""
|
||||||
auth_type = configuration.get("auth", "type")
|
auth_type = configuration.get("auth", "type")
|
||||||
if auth_type in ("None", "none"): # DEPRECATED: use "none"
|
if auth_type in ("None", "none"): # DEPRECATED: use "none"
|
||||||
@ -82,13 +84,12 @@ def load(configuration, logger):
|
|||||||
raise RuntimeError("Failed to load authentication module %r: %s" %
|
raise RuntimeError("Failed to load authentication module %r: %s" %
|
||||||
(auth_type, e)) from e
|
(auth_type, e)) from e
|
||||||
logger.info("Authentication type is %r", auth_type)
|
logger.info("Authentication type is %r", auth_type)
|
||||||
return class_(configuration, logger)
|
return class_(configuration)
|
||||||
|
|
||||||
|
|
||||||
class BaseAuth:
|
class BaseAuth:
|
||||||
def __init__(self, configuration, logger):
|
def __init__(self, configuration):
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.logger = logger
|
|
||||||
|
|
||||||
def get_external_login(self, environ):
|
def get_external_login(self, environ):
|
||||||
"""Optionally provide the login and password externally.
|
"""Optionally provide the login and password externally.
|
||||||
@ -149,8 +150,8 @@ class NoneAuth(BaseAuth):
|
|||||||
|
|
||||||
|
|
||||||
class Auth(BaseAuth):
|
class Auth(BaseAuth):
|
||||||
def __init__(self, configuration, logger):
|
def __init__(self, configuration):
|
||||||
super().__init__(configuration, logger)
|
super().__init__(configuration)
|
||||||
self.filename = os.path.expanduser(
|
self.filename = os.path.expanduser(
|
||||||
configuration.get("auth", "htpasswd_filename"))
|
configuration.get("auth", "htpasswd_filename"))
|
||||||
self.encryption = configuration.get("auth", "htpasswd_encryption")
|
self.encryption = configuration.get("auth", "htpasswd_encryption")
|
||||||
|
@ -192,10 +192,6 @@ INITIAL_CONFIG = OrderedDict([
|
|||||||
"type": str,
|
"type": str,
|
||||||
"internal": web.INTERNAL_TYPES})])),
|
"internal": web.INTERNAL_TYPES})])),
|
||||||
("logging", OrderedDict([
|
("logging", OrderedDict([
|
||||||
("config", {
|
|
||||||
"value": "",
|
|
||||||
"help": "logging configuration file",
|
|
||||||
"type": str}),
|
|
||||||
("debug", {
|
("debug", {
|
||||||
"value": "False",
|
"value": "False",
|
||||||
"help": "print debug information",
|
"help": "print debug information",
|
||||||
|
@ -23,18 +23,15 @@ http://docs.python.org/library/logging.config.html
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import logging.config
|
|
||||||
import signal
|
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
def configure_from_file(logger, filename, debug):
|
LOGGER_NAME = "radicale"
|
||||||
logging.config.fileConfig(filename, disable_existing_loggers=False)
|
LOGGER_FORMAT = "[%(processName)s/%(threadName)s] %(levelname)s: %(message)s"
|
||||||
if debug:
|
|
||||||
logger.setLevel(logging.DEBUG)
|
root_logger = logging.getLogger()
|
||||||
for handler in logger.handlers:
|
logger = logging.getLogger(LOGGER_NAME)
|
||||||
handler.setLevel(logging.DEBUG)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveTracebackFilter(logging.Filter):
|
class RemoveTracebackFilter(logging.Filter):
|
||||||
@ -43,33 +40,29 @@ class RemoveTracebackFilter(logging.Filter):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def start(name="radicale", filename=None, debug=False):
|
removeTracebackFilter = RemoveTracebackFilter()
|
||||||
"""Start the logging according to the configuration."""
|
|
||||||
logger = logging.getLogger(name)
|
|
||||||
if debug:
|
def get_default_handler():
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
else:
|
|
||||||
logger.addFilter(RemoveTracebackFilter())
|
|
||||||
if filename:
|
|
||||||
# Configuration taken from file
|
|
||||||
try:
|
|
||||||
configure_from_file(logger, filename, debug)
|
|
||||||
except Exception as e:
|
|
||||||
raise RuntimeError("Failed to load logging configuration file %r: "
|
|
||||||
"%s" % (filename, e)) from e
|
|
||||||
# Reload config on SIGHUP (UNIX only)
|
|
||||||
if hasattr(signal, "SIGHUP"):
|
|
||||||
def handler(signum, frame):
|
|
||||||
try:
|
|
||||||
configure_from_file(logger, filename, debug)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Failed to reload logging configuration file "
|
|
||||||
"%r: %s", filename, e, exc_info=True)
|
|
||||||
signal.signal(signal.SIGHUP, handler)
|
|
||||||
else:
|
|
||||||
# Default configuration, standard output
|
|
||||||
handler = logging.StreamHandler(sys.stderr)
|
handler = logging.StreamHandler(sys.stderr)
|
||||||
handler.setFormatter(
|
return handler
|
||||||
logging.Formatter("[%(thread)x] %(levelname)s: %(message)s"))
|
|
||||||
logger.addHandler(handler)
|
|
||||||
return logger
|
def setup():
|
||||||
|
"""Set global logging up."""
|
||||||
|
global register_stream, unregister_stream
|
||||||
|
handler = get_default_handler()
|
||||||
|
logging.basicConfig(format=LOGGER_FORMAT, handlers=[handler])
|
||||||
|
set_debug(True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_debug(debug):
|
||||||
|
"""Set debug mode for global logger."""
|
||||||
|
if debug:
|
||||||
|
root_logger.setLevel(logging.DEBUG)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
logger.removeFilter(removeTracebackFilter)
|
||||||
|
else:
|
||||||
|
root_logger.setLevel(logging.WARNING)
|
||||||
|
logger.setLevel(logging.WARNING)
|
||||||
|
logger.addFilter(removeTracebackFilter)
|
||||||
|
@ -44,12 +44,13 @@ import re
|
|||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from radicale import storage
|
from radicale import storage
|
||||||
|
from radicale.log import logger
|
||||||
|
|
||||||
INTERNAL_TYPES = ("None", "none", "authenticated", "owner_write", "owner_only",
|
INTERNAL_TYPES = ("None", "none", "authenticated", "owner_write", "owner_only",
|
||||||
"from_file")
|
"from_file")
|
||||||
|
|
||||||
|
|
||||||
def load(configuration, logger):
|
def load(configuration):
|
||||||
"""Load the rights manager chosen in configuration."""
|
"""Load the rights manager chosen in configuration."""
|
||||||
rights_type = configuration.get("rights", "type")
|
rights_type = configuration.get("rights", "type")
|
||||||
if configuration.get("auth", "type") in ("None", "none"): # DEPRECATED
|
if configuration.get("auth", "type") in ("None", "none"): # DEPRECATED
|
||||||
@ -71,13 +72,12 @@ def load(configuration, logger):
|
|||||||
raise RuntimeError("Failed to load rights module %r: %s" %
|
raise RuntimeError("Failed to load rights module %r: %s" %
|
||||||
(rights_type, e)) from e
|
(rights_type, e)) from e
|
||||||
logger.info("Rights type is %r", rights_type)
|
logger.info("Rights type is %r", rights_type)
|
||||||
return rights_class(configuration, logger)
|
return rights_class(configuration)
|
||||||
|
|
||||||
|
|
||||||
class BaseRights:
|
class BaseRights:
|
||||||
def __init__(self, configuration, logger):
|
def __init__(self, configuration):
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.logger = logger
|
|
||||||
|
|
||||||
def authorized(self, user, path, permission):
|
def authorized(self, user, path, permission):
|
||||||
"""Check if the user is allowed to read or write the collection.
|
"""Check if the user is allowed to read or write the collection.
|
||||||
@ -131,8 +131,8 @@ class OwnerOnlyRights(BaseRights):
|
|||||||
|
|
||||||
|
|
||||||
class Rights(BaseRights):
|
class Rights(BaseRights):
|
||||||
def __init__(self, configuration, logger):
|
def __init__(self, configuration):
|
||||||
super().__init__(configuration, logger)
|
super().__init__(configuration)
|
||||||
self.filename = os.path.expanduser(configuration.get("rights", "file"))
|
self.filename = os.path.expanduser(configuration.get("rights", "file"))
|
||||||
|
|
||||||
def authorized(self, user, path, permission):
|
def authorized(self, user, path, permission):
|
||||||
@ -163,14 +163,13 @@ class Rights(BaseRights):
|
|||||||
raise RuntimeError("Error in section %r of rights file %r: "
|
raise RuntimeError("Error in section %r of rights file %r: "
|
||||||
"%s" % (section, self.filename, e)) from e
|
"%s" % (section, self.filename, e)) from e
|
||||||
if user_match and collection_match:
|
if user_match and collection_match:
|
||||||
self.logger.debug("Rule %r:%r matches %r:%r from section %r",
|
logger.debug("Rule %r:%r matches %r:%r from section %r",
|
||||||
user, sane_path, re_user_pattern,
|
user, sane_path, re_user_pattern,
|
||||||
re_collection_pattern, section)
|
re_collection_pattern, section)
|
||||||
return permission in regex.get(section, "permission")
|
return permission in regex.get(section, "permission")
|
||||||
else:
|
else:
|
||||||
self.logger.debug("Rule %r:%r doesn't match %r:%r from section"
|
logger.debug("Rule %r:%r doesn't match %r:%r from section %r",
|
||||||
" %r", user, sane_path, re_user_pattern,
|
user, sane_path, re_user_pattern,
|
||||||
re_collection_pattern, section)
|
re_collection_pattern, section)
|
||||||
self.logger.info(
|
logger.info("Rights: %r:%r doesn't match any section", user, sane_path)
|
||||||
"Rights: %r:%r doesn't match any section", user, sane_path)
|
|
||||||
return False
|
return False
|
||||||
|
@ -47,6 +47,8 @@ from tempfile import NamedTemporaryFile, TemporaryDirectory
|
|||||||
|
|
||||||
import vobject
|
import vobject
|
||||||
|
|
||||||
|
from radicale.log import logger
|
||||||
|
|
||||||
if sys.version_info >= (3, 5):
|
if sys.version_info >= (3, 5):
|
||||||
# HACK: Avoid import cycle for Python < 3.5
|
# HACK: Avoid import cycle for Python < 3.5
|
||||||
from radicale import xmlutils
|
from radicale import xmlutils
|
||||||
@ -93,7 +95,7 @@ elif os.name == "posix":
|
|||||||
INTERNAL_TYPES = ("multifilesystem",)
|
INTERNAL_TYPES = ("multifilesystem",)
|
||||||
|
|
||||||
|
|
||||||
def load(configuration, logger):
|
def load(configuration):
|
||||||
"""Load the storage manager chosen in configuration."""
|
"""Load the storage manager chosen in configuration."""
|
||||||
if sys.version_info < (3, 5):
|
if sys.version_info < (3, 5):
|
||||||
# HACK: Avoid import cycle for Python < 3.5
|
# HACK: Avoid import cycle for Python < 3.5
|
||||||
@ -113,7 +115,6 @@ def load(configuration, logger):
|
|||||||
class CollectionCopy(collection_class):
|
class CollectionCopy(collection_class):
|
||||||
"""Collection copy, avoids overriding the original class attributes."""
|
"""Collection copy, avoids overriding the original class attributes."""
|
||||||
CollectionCopy.configuration = configuration
|
CollectionCopy.configuration = configuration
|
||||||
CollectionCopy.logger = logger
|
|
||||||
CollectionCopy.static_init()
|
CollectionCopy.static_init()
|
||||||
return CollectionCopy
|
return CollectionCopy
|
||||||
|
|
||||||
@ -429,7 +430,6 @@ class BaseCollection:
|
|||||||
|
|
||||||
# Overriden on copy by the "load" function
|
# Overriden on copy by the "load" function
|
||||||
configuration = None
|
configuration = None
|
||||||
logger = None
|
|
||||||
|
|
||||||
# Properties of instance
|
# Properties of instance
|
||||||
"""The sanitized path of the collection without leading or trailing ``/``.
|
"""The sanitized path of the collection without leading or trailing ``/``.
|
||||||
@ -883,7 +883,7 @@ class Collection(BaseCollection):
|
|||||||
filesystem_path = path_to_filesystem(folder, sane_path)
|
filesystem_path = path_to_filesystem(folder, sane_path)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
# Path is unsafe
|
# Path is unsafe
|
||||||
cls.logger.debug("Unsafe path %r requested from storage: %s",
|
logger.debug("Unsafe path %r requested from storage: %s",
|
||||||
sane_path, e, exc_info=True)
|
sane_path, e, exc_info=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -915,8 +915,8 @@ class Collection(BaseCollection):
|
|||||||
for href in scandir(filesystem_path, only_dirs=True):
|
for href in scandir(filesystem_path, only_dirs=True):
|
||||||
if not is_safe_filesystem_path_component(href):
|
if not is_safe_filesystem_path_component(href):
|
||||||
if not href.startswith(".Radicale"):
|
if not href.startswith(".Radicale"):
|
||||||
cls.logger.debug("Skipping collection %r in %r", href,
|
logger.debug("Skipping collection %r in %r",
|
||||||
sane_path)
|
href, sane_path)
|
||||||
continue
|
continue
|
||||||
child_path = posixpath.join(sane_path, href)
|
child_path = posixpath.join(sane_path, href)
|
||||||
with child_context_manager(child_path):
|
with child_context_manager(child_path):
|
||||||
@ -938,12 +938,12 @@ class Collection(BaseCollection):
|
|||||||
else:
|
else:
|
||||||
collection_errors += 1
|
collection_errors += 1
|
||||||
name = "collection %r" % path.strip("/")
|
name = "collection %r" % path.strip("/")
|
||||||
cls.logger.error("Invalid %s: %s", name, e, exc_info=True)
|
logger.error("Invalid %s: %s", name, e, exc_info=True)
|
||||||
|
|
||||||
remaining_paths = [""]
|
remaining_paths = [""]
|
||||||
while remaining_paths:
|
while remaining_paths:
|
||||||
path = remaining_paths.pop(0)
|
path = remaining_paths.pop(0)
|
||||||
cls.logger.debug("Verifying collection %r", path)
|
logger.debug("Verifying collection %r", path)
|
||||||
with exception_cm(path):
|
with exception_cm(path):
|
||||||
saved_item_errors = item_errors
|
saved_item_errors = item_errors
|
||||||
collection = None
|
collection = None
|
||||||
@ -955,8 +955,7 @@ class Collection(BaseCollection):
|
|||||||
if isinstance(item, BaseCollection):
|
if isinstance(item, BaseCollection):
|
||||||
remaining_paths.append(item.path)
|
remaining_paths.append(item.path)
|
||||||
else:
|
else:
|
||||||
cls.logger.debug("Verified item %r in %r",
|
logger.debug("Verified item %r in %r", item.href, path)
|
||||||
item.href, path)
|
|
||||||
if item_errors == saved_item_errors:
|
if item_errors == saved_item_errors:
|
||||||
collection.sync()
|
collection.sync()
|
||||||
return item_errors == 0 and collection_errors == 0
|
return item_errors == 0 and collection_errors == 0
|
||||||
@ -1107,7 +1106,7 @@ class Collection(BaseCollection):
|
|||||||
continue
|
continue
|
||||||
if mtime > age_limit:
|
if mtime > age_limit:
|
||||||
continue
|
continue
|
||||||
cls.logger.debug("Found expired item in cache: %r", name)
|
logger.debug("Found expired item in cache: %r", name)
|
||||||
# Race: Another process might have deleted or locked the
|
# Race: Another process might have deleted or locked the
|
||||||
# file.
|
# file.
|
||||||
try:
|
try:
|
||||||
@ -1133,7 +1132,7 @@ class Collection(BaseCollection):
|
|||||||
cache_etag, history_etag = pickle.load(f)
|
cache_etag, history_etag = pickle.load(f)
|
||||||
except (FileNotFoundError, pickle.UnpicklingError, ValueError) as e:
|
except (FileNotFoundError, pickle.UnpicklingError, ValueError) as e:
|
||||||
if isinstance(e, (pickle.UnpicklingError, ValueError)):
|
if isinstance(e, (pickle.UnpicklingError, ValueError)):
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Failed to load history cache entry %r in %r: %s",
|
"Failed to load history cache entry %r in %r: %s",
|
||||||
href, self.path, e, exc_info=True)
|
href, self.path, e, exc_info=True)
|
||||||
cache_etag = ""
|
cache_etag = ""
|
||||||
@ -1225,7 +1224,7 @@ class Collection(BaseCollection):
|
|||||||
except (FileNotFoundError, pickle.UnpicklingError,
|
except (FileNotFoundError, pickle.UnpicklingError,
|
||||||
ValueError) as e:
|
ValueError) as e:
|
||||||
if isinstance(e, (pickle.UnpicklingError, ValueError)):
|
if isinstance(e, (pickle.UnpicklingError, ValueError)):
|
||||||
self.logger.warning(
|
logger.warning(
|
||||||
"Failed to load stored sync token %r in %r: %s",
|
"Failed to load stored sync token %r in %r: %s",
|
||||||
old_token_name, self.path, e, exc_info=True)
|
old_token_name, self.path, e, exc_info=True)
|
||||||
# Delete the damaged file
|
# Delete the damaged file
|
||||||
@ -1273,8 +1272,7 @@ class Collection(BaseCollection):
|
|||||||
for href in scandir(self._filesystem_path, only_files=True):
|
for href in scandir(self._filesystem_path, only_files=True):
|
||||||
if not is_safe_filesystem_path_component(href):
|
if not is_safe_filesystem_path_component(href):
|
||||||
if not href.startswith(".Radicale"):
|
if not href.startswith(".Radicale"):
|
||||||
self.logger.debug(
|
logger.debug("Skipping item %r in %r", href, self.path)
|
||||||
"Skipping item %r in %r", href, self.path)
|
|
||||||
continue
|
continue
|
||||||
yield href
|
yield href
|
||||||
|
|
||||||
@ -1356,8 +1354,7 @@ class Collection(BaseCollection):
|
|||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
pass
|
pass
|
||||||
except (pickle.UnpicklingError, ValueError) as e:
|
except (pickle.UnpicklingError, ValueError) as e:
|
||||||
self.logger.warning(
|
logger.warning("Failed to load item cache entry %r in %r: %s",
|
||||||
"Failed to load item cache entry %r in %r: %s",
|
|
||||||
href, self.path, e, exc_info=True)
|
href, self.path, e, exc_info=True)
|
||||||
return cache_hash, uid, etag, text, name, tag, start, end
|
return cache_hash, uid, etag, text, name, tag, start, end
|
||||||
|
|
||||||
@ -1378,7 +1375,7 @@ class Collection(BaseCollection):
|
|||||||
raise UnsafePathError(href)
|
raise UnsafePathError(href)
|
||||||
path = path_to_filesystem(self._filesystem_path, href)
|
path = path_to_filesystem(self._filesystem_path, href)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.debug(
|
logger.debug(
|
||||||
"Can't translate name %r safely to filesystem in %r: %s",
|
"Can't translate name %r safely to filesystem in %r: %s",
|
||||||
href, self.path, e, exc_info=True)
|
href, self.path, e, exc_info=True)
|
||||||
return None, None
|
return None, None
|
||||||
@ -1452,7 +1449,7 @@ class Collection(BaseCollection):
|
|||||||
path = os.path.join(self._filesystem_path, href)
|
path = os.path.join(self._filesystem_path, href)
|
||||||
if (not is_safe_filesystem_path_component(href) or
|
if (not is_safe_filesystem_path_component(href) or
|
||||||
href not in files and os.path.lexists(path)):
|
href not in files and os.path.lexists(path)):
|
||||||
self.logger.debug(
|
logger.debug(
|
||||||
"Can't translate name safely to filesystem: %r", href)
|
"Can't translate name safely to filesystem: %r", href)
|
||||||
yield (href, None)
|
yield (href, None)
|
||||||
else:
|
else:
|
||||||
@ -1570,8 +1567,8 @@ class Collection(BaseCollection):
|
|||||||
if mode == "w" and hook:
|
if mode == "w" and hook:
|
||||||
folder = os.path.expanduser(cls.configuration.get(
|
folder = os.path.expanduser(cls.configuration.get(
|
||||||
"storage", "filesystem_folder"))
|
"storage", "filesystem_folder"))
|
||||||
cls.logger.debug("Running hook")
|
logger.debug("Running hook")
|
||||||
debug = cls.logger.isEnabledFor(logging.DEBUG)
|
debug = logger.isEnabledFor(logging.DEBUG)
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
hook % {"user": shlex.quote(user or "Anonymous")},
|
hook % {"user": shlex.quote(user or "Anonymous")},
|
||||||
stdin=subprocess.DEVNULL,
|
stdin=subprocess.DEVNULL,
|
||||||
@ -1580,9 +1577,9 @@ class Collection(BaseCollection):
|
|||||||
shell=True, universal_newlines=True, cwd=folder)
|
shell=True, universal_newlines=True, cwd=folder)
|
||||||
stdout_data, stderr_data = p.communicate()
|
stdout_data, stderr_data = p.communicate()
|
||||||
if stdout_data:
|
if stdout_data:
|
||||||
cls.logger.debug("Captured stdout hook:\n%s", stdout_data)
|
logger.debug("Captured stdout hook:\n%s", stdout_data)
|
||||||
if stderr_data:
|
if stderr_data:
|
||||||
cls.logger.debug("Captured stderr hook:\n%s", stderr_data)
|
logger.debug("Captured stderr hook:\n%s", stderr_data)
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
raise subprocess.CalledProcessError(p.returncode, p.args)
|
raise subprocess.CalledProcessError(p.returncode, p.args)
|
||||||
|
|
||||||
|
@ -19,24 +19,15 @@ Tests for Radicale.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
logger = logging.getLogger("radicale_test")
|
|
||||||
if not logger.hasHandlers():
|
|
||||||
handler = logging.StreamHandler(sys.stderr)
|
|
||||||
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
|
|
||||||
logger.addHandler(handler)
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTest:
|
class BaseTest:
|
||||||
"""Base class for tests."""
|
"""Base class for tests."""
|
||||||
logger = logger
|
|
||||||
|
|
||||||
def request(self, method, path, data=None, **args):
|
def request(self, method, path, data=None, **args):
|
||||||
"""Send a request."""
|
"""Send a request."""
|
||||||
|
@ -61,7 +61,7 @@ class TestBaseAuthRequests(BaseTest):
|
|||||||
self.configuration["auth"]["type"] = "htpasswd"
|
self.configuration["auth"]["type"] = "htpasswd"
|
||||||
self.configuration["auth"]["htpasswd_filename"] = htpasswd_file_path
|
self.configuration["auth"]["htpasswd_filename"] = htpasswd_file_path
|
||||||
self.configuration["auth"]["htpasswd_encryption"] = htpasswd_encryption
|
self.configuration["auth"]["htpasswd_encryption"] = htpasswd_encryption
|
||||||
self.application = Application(self.configuration, self.logger)
|
self.application = Application(self.configuration)
|
||||||
if test_matrix is None:
|
if test_matrix is None:
|
||||||
test_matrix = (
|
test_matrix = (
|
||||||
("tmp", "bepo", 207), ("tmp", "tmp", 401), ("tmp", "", 401),
|
("tmp", "bepo", 207), ("tmp", "tmp", 401), ("tmp", "", 401),
|
||||||
@ -131,7 +131,7 @@ class TestBaseAuthRequests(BaseTest):
|
|||||||
|
|
||||||
def test_remote_user(self):
|
def test_remote_user(self):
|
||||||
self.configuration["auth"]["type"] = "remote_user"
|
self.configuration["auth"]["type"] = "remote_user"
|
||||||
self.application = Application(self.configuration, self.logger)
|
self.application = Application(self.configuration)
|
||||||
status, _, answer = self.request(
|
status, _, answer = self.request(
|
||||||
"PROPFIND", "/",
|
"PROPFIND", "/",
|
||||||
"""<?xml version="1.0" encoding="utf-8"?>
|
"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
@ -145,7 +145,7 @@ class TestBaseAuthRequests(BaseTest):
|
|||||||
|
|
||||||
def test_http_x_remote_user(self):
|
def test_http_x_remote_user(self):
|
||||||
self.configuration["auth"]["type"] = "http_x_remote_user"
|
self.configuration["auth"]["type"] = "http_x_remote_user"
|
||||||
self.application = Application(self.configuration, self.logger)
|
self.application = Application(self.configuration)
|
||||||
status, _, answer = self.request(
|
status, _, answer = self.request(
|
||||||
"PROPFIND", "/",
|
"PROPFIND", "/",
|
||||||
"""<?xml version="1.0" encoding="utf-8"?>
|
"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
@ -160,7 +160,7 @@ class TestBaseAuthRequests(BaseTest):
|
|||||||
def test_custom(self):
|
def test_custom(self):
|
||||||
"""Custom authentication."""
|
"""Custom authentication."""
|
||||||
self.configuration["auth"]["type"] = "tests.custom.auth"
|
self.configuration["auth"]["type"] = "tests.custom.auth"
|
||||||
self.application = Application(self.configuration, self.logger)
|
self.application = Application(self.configuration)
|
||||||
status, _, answer = self.request(
|
status, _, answer = self.request(
|
||||||
"PROPFIND", "/tmp", HTTP_AUTHORIZATION="Basic %s" %
|
"PROPFIND", "/tmp", HTTP_AUTHORIZATION="Basic %s" %
|
||||||
base64.b64encode(("tmp:").encode()).decode())
|
base64.b64encode(("tmp:").encode()).decode())
|
||||||
|
@ -1396,7 +1396,7 @@ class BaseRequestsMixIn:
|
|||||||
self.configuration["auth"]["htpasswd_filename"] = os.devnull
|
self.configuration["auth"]["htpasswd_filename"] = os.devnull
|
||||||
self.configuration["auth"]["htpasswd_encryption"] = "plain"
|
self.configuration["auth"]["htpasswd_encryption"] = "plain"
|
||||||
self.configuration["rights"]["type"] = "owner_only"
|
self.configuration["rights"]["type"] = "owner_only"
|
||||||
self.application = Application(self.configuration, self.logger)
|
self.application = Application(self.configuration)
|
||||||
status, headers, _ = self.request("MKCOL", "/user/")
|
status, headers, _ = self.request("MKCOL", "/user/")
|
||||||
assert status in (401, 403)
|
assert status in (401, 403)
|
||||||
assert headers.get("WWW-Authenticate")
|
assert headers.get("WWW-Authenticate")
|
||||||
@ -1479,7 +1479,7 @@ class BaseFileSystemTest(BaseTest):
|
|||||||
self.configuration["storage"]["filesystem_fsync"] = "False"
|
self.configuration["storage"]["filesystem_fsync"] = "False"
|
||||||
# Required on Windows, doesn't matter on Unix
|
# Required on Windows, doesn't matter on Unix
|
||||||
self.configuration["storage"]["filesystem_close_lock_file"] = "True"
|
self.configuration["storage"]["filesystem_close_lock_file"] = "True"
|
||||||
self.application = Application(self.configuration, self.logger)
|
self.application = Application(self.configuration)
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
shutil.rmtree(self.colpath)
|
shutil.rmtree(self.colpath)
|
||||||
|
@ -53,7 +53,7 @@ class TestBaseAuthRequests(BaseTest):
|
|||||||
self.configuration["auth"]["type"] = "htpasswd"
|
self.configuration["auth"]["type"] = "htpasswd"
|
||||||
self.configuration["auth"]["htpasswd_filename"] = htpasswd_file_path
|
self.configuration["auth"]["htpasswd_filename"] = htpasswd_file_path
|
||||||
self.configuration["auth"]["htpasswd_encryption"] = "plain"
|
self.configuration["auth"]["htpasswd_encryption"] = "plain"
|
||||||
self.application = Application(self.configuration, self.logger)
|
self.application = Application(self.configuration)
|
||||||
for u in ("tmp", "other"):
|
for u in ("tmp", "other"):
|
||||||
status, _, _ = self.request(
|
status, _, _ = self.request(
|
||||||
"PROPFIND", "/%s" % u, HTTP_AUTHORIZATION="Basic %s" %
|
"PROPFIND", "/%s" % u, HTTP_AUTHORIZATION="Basic %s" %
|
||||||
|
@ -23,6 +23,7 @@ from importlib import import_module
|
|||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
from radicale import storage
|
from radicale import storage
|
||||||
|
from radicale.log import logger
|
||||||
|
|
||||||
NOT_FOUND = (
|
NOT_FOUND = (
|
||||||
client.NOT_FOUND, (("Content-Type", "text/plain"),),
|
client.NOT_FOUND, (("Content-Type", "text/plain"),),
|
||||||
@ -47,7 +48,7 @@ FALLBACK_MIMETYPE = "application/octet-stream"
|
|||||||
INTERNAL_TYPES = ("None", "none", "internal")
|
INTERNAL_TYPES = ("None", "none", "internal")
|
||||||
|
|
||||||
|
|
||||||
def load(configuration, logger):
|
def load(configuration):
|
||||||
"""Load the web module chosen in configuration."""
|
"""Load the web module chosen in configuration."""
|
||||||
web_type = configuration.get("web", "type")
|
web_type = configuration.get("web", "type")
|
||||||
if web_type in ("None", "none"): # DEPRECATED: use "none"
|
if web_type in ("None", "none"): # DEPRECATED: use "none"
|
||||||
@ -61,13 +62,12 @@ def load(configuration, logger):
|
|||||||
raise RuntimeError("Failed to load web module %r: %s" %
|
raise RuntimeError("Failed to load web module %r: %s" %
|
||||||
(web_type, e)) from e
|
(web_type, e)) from e
|
||||||
logger.info("Web type is %r", web_type)
|
logger.info("Web type is %r", web_type)
|
||||||
return web_class(configuration, logger)
|
return web_class(configuration)
|
||||||
|
|
||||||
|
|
||||||
class BaseWeb:
|
class BaseWeb:
|
||||||
def __init__(self, configuration, logger):
|
def __init__(self, configuration):
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.logger = logger
|
|
||||||
|
|
||||||
def get(self, environ, base_prefix, path, user):
|
def get(self, environ, base_prefix, path, user):
|
||||||
"""GET request.
|
"""GET request.
|
||||||
@ -90,8 +90,8 @@ class NoneWeb(BaseWeb):
|
|||||||
|
|
||||||
|
|
||||||
class Web(BaseWeb):
|
class Web(BaseWeb):
|
||||||
def __init__(self, configuration, logger):
|
def __init__(self, configuration):
|
||||||
super().__init__(configuration, logger)
|
super().__init__(configuration)
|
||||||
self.folder = pkg_resources.resource_filename(__name__, "web")
|
self.folder = pkg_resources.resource_filename(__name__, "web")
|
||||||
|
|
||||||
def get(self, environ, base_prefix, path, user):
|
def get(self, environ, base_prefix, path, user):
|
||||||
@ -99,7 +99,7 @@ class Web(BaseWeb):
|
|||||||
filesystem_path = storage.path_to_filesystem(
|
filesystem_path = storage.path_to_filesystem(
|
||||||
self.folder, path[len("/.web"):])
|
self.folder, path[len("/.web"):])
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.debug("Web content with unsafe path %r requested: %s",
|
logger.debug("Web content with unsafe path %r requested: %s",
|
||||||
path, e, exc_info=True)
|
path, e, exc_info=True)
|
||||||
return NOT_FOUND
|
return NOT_FOUND
|
||||||
if os.path.isdir(filesystem_path) and not path.endswith("/"):
|
if os.path.isdir(filesystem_path) and not path.endswith("/"):
|
||||||
|
@ -38,6 +38,7 @@ from itertools import chain
|
|||||||
from urllib.parse import quote, unquote, urlparse
|
from urllib.parse import quote, unquote, urlparse
|
||||||
|
|
||||||
from radicale import storage
|
from radicale import storage
|
||||||
|
from radicale.log import logger
|
||||||
|
|
||||||
MIMETYPES = {
|
MIMETYPES = {
|
||||||
"VADDRESSBOOK": "text/vcard",
|
"VADDRESSBOOK": "text/vcard",
|
||||||
@ -173,7 +174,7 @@ def _comp_match(item, filter_, level=0):
|
|||||||
elif level == 1:
|
elif level == 1:
|
||||||
tag = item.component_name
|
tag = item.component_name
|
||||||
else:
|
else:
|
||||||
item.collection.logger.warning(
|
logger.warning(
|
||||||
"Filters with three levels of comp-filter are not supported")
|
"Filters with three levels of comp-filter are not supported")
|
||||||
return True
|
return True
|
||||||
if not tag:
|
if not tag:
|
||||||
@ -190,7 +191,7 @@ def _comp_match(item, filter_, level=0):
|
|||||||
return False
|
return False
|
||||||
if (level == 0 and name != "VCALENDAR" or
|
if (level == 0 and name != "VCALENDAR" or
|
||||||
level == 1 and name not in ("VTODO", "VEVENT", "VJOURNAL")):
|
level == 1 and name not in ("VTODO", "VEVENT", "VJOURNAL")):
|
||||||
item.collection.logger.warning("Filtering %s is not supported" % name)
|
logger.warning("Filtering %s is not supported" % name)
|
||||||
return True
|
return True
|
||||||
# Point #3 and #4 of rfc4791-9.7.1
|
# Point #3 and #4 of rfc4791-9.7.1
|
||||||
components = ([item.item] if level == 0
|
components = ([item.item] if level == 0
|
||||||
@ -1131,7 +1132,6 @@ def report(base_prefix, path, xml_request, collection):
|
|||||||
Read rfc3253-3.6 for info.
|
Read rfc3253-3.6 for info.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger = collection.logger
|
|
||||||
multistatus = ET.Element(_tag("D", "multistatus"))
|
multistatus = ET.Element(_tag("D", "multistatus"))
|
||||||
if xml_request is None:
|
if xml_request is None:
|
||||||
return client.MULTI_STATUS, multistatus
|
return client.MULTI_STATUS, multistatus
|
||||||
|
Loading…
Reference in New Issue
Block a user