Просмотр исходного кода

Code cleaned using Pylint, fixes various minor bugs too.

Guillaume Ayoub 16 лет назад
Родитель
Сommit
21a743fcde

+ 10 - 4
radicale.py

@@ -19,16 +19,21 @@
 # You should have received a copy of the GNU General Public License
 # along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
 
+# This file is just a script, allow [a-z0-9]* variable names
+# pylint: disable-msg=C0103
+
+# ``import radicale`` refers to the ``radicale`` module, not ``radicale.py`` 
+# pylint: disable-msg=W0406
+
 """
 Radicale Server entry point.
 
 Launch the Radicale Serve according to configuration and command-line
 arguments.
+
 """
 
-# TODO: Manage depth and calendars/collections (see xmlutils)
 # TODO: Manage smart and configurable logs
-# TODO: Manage authentication
 
 import os
 import sys
@@ -62,7 +67,7 @@ parser.add_option(
     "-c", "--certificate",
     default=radicale.config.get("server", "certificate"),
     help="certificate file ")
-options, args = parser.parse_args()
+options = parser.parse_args()[0]
 
 # Update Radicale configuration according to options
 for option in parser.option_list:
@@ -79,5 +84,6 @@ if options.daemon:
 
 # Launch calendar server
 server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer
-server = server_class((options.host, options.port), radicale.CalendarHTTPHandler)
+server = server_class(
+    (options.host, options.port), radicale.CalendarHTTPHandler)
 server.serve_forever()

+ 15 - 10
radicale/__init__.py

@@ -33,8 +33,6 @@ should have been included in this package.
 
 """
 
-# TODO: Manage errors (see xmlutils)
-
 import base64
 import socket
 try:
@@ -43,9 +41,10 @@ except ImportError:
     import httplib as client
     import BaseHTTPServer as server
 
-from radicale import acl, config, support, xmlutils
+from radicale import acl, calendar, config, support, xmlutils
+
 
-def check(request, function):
+def _check(request, function):
     """Check if user has sufficient rights for performing ``request``."""
     authorization = request.headers.get("Authorization", None)
     if authorization:
@@ -64,8 +63,6 @@ def check(request, function):
             "Basic realm=\"Radicale Server - Password Required\"")
         request.end_headers()
 
-# Decorator checking rights before performing request
-check_rights = lambda function: lambda request: check(request, function)
 
 class HTTPServer(server.HTTPServer):
     """HTTP server."""
@@ -74,6 +71,7 @@ class HTTPServer(server.HTTPServer):
         server.HTTPServer.__init__(self, address, handler)
         self.acl = acl.load()
 
+
 class HTTPSServer(HTTPServer):
     """HTTPS server."""
     def __init__(self, address, handler):
@@ -91,10 +89,14 @@ class HTTPSServer(HTTPServer):
         self.server_bind()
         self.server_activate()
 
+
 class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     """HTTP requests handler for calendars."""
     _encoding = config.get("encoding", "request")
 
+    # Decorator checking rights before performing request
+    check_rights = lambda function: lambda request: _check(request, function)
+
     @property
     def calendar(self):
         """The ``calendar.Calendar`` object corresponding to the given path."""
@@ -109,9 +111,9 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
         charsets = []
 
         # First append content charset given in the request
-        contentType = self.headers["Content-Type"]
-        if contentType and "charset=" in contentType:
-            charsets.append(contentType.split("charset=")[1].strip())
+        content_type = self.headers["Content-Type"]
+        if content_type and "charset=" in content_type:
+            charsets.append(content_type.split("charset=")[1].strip())
         # Then append default Radicale charset
         charsets.append(self._encoding)
         # Then append various fallbacks
@@ -126,10 +128,13 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
                 pass
         raise UnicodeDecodeError
 
+    # Naming methods ``do_*`` is OK here
+    # pylint: disable-msg=C0103
+
     @check_rights
     def do_GET(self):
         """Manage GET request."""
-        answer = self.calendar.vcalendar.encode(_encoding)
+        answer = self.calendar.vcalendar.encode(self._encoding)
 
         self.send_response(client.OK)
         self.send_header("Content-Length", len(answer))

+ 3 - 0
radicale/acl/__init__.py

@@ -23,11 +23,14 @@ Users and rights management.
 
 This module loads a list of users with access rights, according to the acl
 configuration.
+
 """
 
 from radicale import config
 
+
 def load():
+    """Load list of available ACL managers."""
     module = __import__("radicale.acl", globals(), locals(),
                         [config.get("acl", "type")])
     return getattr(module, config.get("acl", "type"))

+ 1 - 1
radicale/acl/fake.py

@@ -25,6 +25,6 @@ No rights management.
 
 """
 
-def has_right(user, password):
+def has_right(*_):
     """Check if ``user``/``password`` couple is valid."""
     return True

+ 20 - 12
radicale/acl/htpasswd.py

@@ -33,27 +33,35 @@ import hashlib
 
 from radicale import config
 
-def _plain(hash, password):
-    return hash == password
 
-def _crypt(hash, password):
-    return crypt.crypt(password, hash) == hash
+FILENAME = config.get("acl", "filename")
+CHECK_PASSWORD = locals()["_%s" % config.get("acl", "encryption")]
 
-def _sha1(hash, password):
-    hash = hash.replace("{SHA}", "").encode("ascii")
+
+def _plain(hash_value, password):
+    """Check if ``hash_value`` and ``password`` match using plain method."""
+    return hash_value == password
+
+
+def _crypt(hash_value, password):
+    """Check if ``hash_value`` and ``password`` match using crypt method."""
+    return crypt.crypt(password, hash_value) == hash_value
+
+
+def _sha1(hash_value, password):
+    """Check if ``hash_value`` and ``password`` match using sha1 method."""
+    hash_value = hash_value.replace("{SHA}", "").encode("ascii")
     password = password.encode(config.get("encoding", "stock"))
     sha1 = hashlib.sha1()
     sha1.update(password)
-    return sha1.digest() == base64.b64decode(hash)
+    return sha1.digest() == base64.b64decode(hash_value)
 
-_filename = config.get("acl", "filename")
-_check_password = locals()["_%s" % config.get("acl", "encryption")]
 
 def has_right(user, password):
     """Check if ``user``/``password`` couple is valid."""
-    for line in open(_filename).readlines():
+    for line in open(FILENAME).readlines():
         if line.strip():
-            login, hash = line.strip().split(":")
+            login, hash_value = line.strip().split(":")
             if login == user:
-                return _check_password(hash, password)
+                return CHECK_PASSWORD(hash_value, password)
     return False

+ 11 - 2
radicale/calendar.py

@@ -22,11 +22,16 @@
 Radicale calendar classes.
 
 Define the main classes of a calendar as seen from the server.
+
 """
 
 from radicale import support
 
-hash_tag = lambda vcalendar: str(hash(vcalendar))
+
+def hash_tag(vcalendar):
+    """Hash an vcalendar string."""
+    return str(hash(vcalendar))
+
 
 class Calendar(object):
     """Internal calendar class."""
@@ -67,6 +72,7 @@ class Calendar(object):
         """Etag from calendar."""
         return '"%s"' % hash_tag(self.vcalendar)
 
+
 class Event(object):
     """Internal event class."""
     def __init__(self, vcalendar):
@@ -78,12 +84,14 @@ class Event(object):
         """Etag from event."""
         return '"%s"' % hash_tag(self.text)
 
+
 class Header(object):
     """Internal header class."""
     def __init__(self, vcalendar):
         """Initialize header from ``vcalendar``."""
         self.text = vcalendar
 
+
 class Timezone(object):
     """Internal timezone class."""
     def __init__(self, vcalendar):
@@ -91,11 +99,12 @@ class Timezone(object):
         lines = vcalendar.splitlines()
         for line in lines:
             if line.startswith("TZID:"):
-                self.tzid = line.lstrip("TZID:")
+                self.id = line.lstrip("TZID:")
                 break
 
         self.text = vcalendar
 
+
 class Todo(object):
     """Internal todo class."""
     def __init__(self, vcalendar):

+ 16 - 21
radicale/config.py

@@ -22,26 +22,21 @@
 Radicale configuration module.
 
 Give a configparser-like interface to read and write configuration.
+
 """
 
 # TODO: Use abstract filenames for other platforms
 
 import os
+import sys
 try:
     from configparser import RawConfigParser as ConfigParser
 except ImportError:
     from ConfigParser import RawConfigParser as ConfigParser
 
-_config = ConfigParser()
-get = _config.get
-set = _config.set
-getboolean = _config.getboolean
-getint = _config.getint
-getfloat = _config.getfloat
-options = _config.options
-items = _config.items
 
-_initial = {
+# Default configuration
+INITIAL_CONFIG = {
     "server": {
         "host": "",
         "port": "5232",
@@ -49,17 +44,11 @@ _initial = {
         "ssl": "False",
         "certificate": "/etc/apache2/ssl/server.crt",
         "key": "/etc/apache2/ssl/server.key",
-        #"log": "/var/www/radicale/server.log",
         },
     "encoding": {
         "request": "utf-8",
         "stock": "utf-8",
         },
-    "namespace": {
-        "C": "urn:ietf:params:xml:ns:caldav",
-        "D": "DAV:",
-        "CS": "http://calendarserver.org/ns/",
-        },
     "acl": {
         "type": "fake",
         "filename": "/etc/radicale/users",
@@ -68,14 +57,20 @@ _initial = {
     "support": {
         "type": "plain",
         "folder": os.path.expanduser("~/.config/radicale"),
-        "calendar": "radicale/calendar",
+        "calendar": "radicale/cal",
         },
     }
 
-for section, values in _initial.items():
-    _config.add_section(section)
+# Create a ConfigParser and configure it
+_CONFIG = ConfigParser()
+
+for section, values in INITIAL_CONFIG.items():
+    _CONFIG.add_section(section)
     for key, value in values.items():
-        _config.set(section, key, value)
+        _CONFIG.set(section, key, value)
+
+_CONFIG.read("/etc/radicale/config")
+_CONFIG.read(os.path.expanduser("~/.config/radicale/config"))
 
-_config.read("/etc/radicale/config")
-_config.read(os.path.expanduser("~/.config/radicale/config"))
+# Wrap config module into ConfigParser instance
+sys.modules[__name__] = _CONFIG

+ 43 - 29
radicale/ical.py

@@ -20,18 +20,19 @@
 
 """
 iCal parsing functions.
+
 """
 
 # TODO: Manage filters (see xmlutils)
 
 from radicale import calendar
 
-def write_calendar(headers=[
+
+def write_calendar(headers=(
         calendar.Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
-        calendar.Header("VERSION:2.0")],
-                  timezones=[], todos=[], events=[]):
-    """Create calendar from ``headers``, ``timezones``, ``todos``, ``events``."""
-    # TODO: Manage encoding and EOL
+        calendar.Header("VERSION:2.0")),
+                   timezones=(), todos=(), events=()):
+    """Create calendar from given parameters."""
     cal = "\n".join((
         "BEGIN:VCALENDAR",
         "\n".join([header.text for header in headers]),
@@ -41,44 +42,57 @@ def write_calendar(headers=[
         "END:VCALENDAR"))
     return "\n".join([line for line in cal.splitlines() if line])
 
-def headers(vcalendar):
-    """Find Headers items in ``vcalendar``."""
-    headers = []
-
-    lines = vcalendar.splitlines()
-    for line in lines:
-        if line.startswith("PRODID:"):
-            headers.append(calendar.Header(line))
-    for line in lines:
-        if line.startswith("VERSION:"):
-            headers.append(calendar.Header(line))
-
-    return headers
 
 def _parse(vcalendar, tag, obj):
     """Find ``tag`` items in ``vcalendar``.
     
     Return a list of items of type ``obj``.
+
     """
     items = []
 
     lines = vcalendar.splitlines()
-    inItem = False
-    itemLines = []
+    in_item = False
+    item_lines = []
 
     for line in lines:
         if line.startswith("BEGIN:%s" % tag):
-            inItem = True
-            itemLines = []
+            in_item = True
+            item_lines = []
 
-        if inItem:
-            # TODO: Manage encoding
-            itemLines.append(line)
+        if in_item:
+            item_lines.append(line)
             if line.startswith("END:%s" % tag):
-                items.append(obj("\n".join(itemLines)))
+                items.append(obj("\n".join(item_lines)))
 
     return items
 
-events = lambda vcalendar: _parse(vcalendar, "VEVENT", calendar.Event)
-todos = lambda vcalendar: _parse(vcalendar, "VTODO", calendar.Todo)
-timezones = lambda vcalendar: _parse(vcalendar, "VTIMEZONE", calendar.Timezone)
+
+def headers(vcalendar):
+    """Find Headers items in ``vcalendar``."""
+    header_lines = []
+
+    lines = vcalendar.splitlines()
+    for line in lines:
+        if line.startswith("PRODID:"):
+            header_lines.append(calendar.Header(line))
+    for line in lines:
+        if line.startswith("VERSION:"):
+            header_lines.append(calendar.Header(line))
+
+    return header_lines
+
+
+def events(vcalendar):
+    """Get list of ``Event`` from VEVENTS items in ``vcalendar``."""
+    return _parse(vcalendar, "VEVENT", calendar.Event)
+
+
+def todos(vcalendar):
+    """Get list of ``Todo`` from VTODO items in ``vcalendar``."""
+    return _parse(vcalendar, "VTODO", calendar.Todo)
+
+
+def timezones(vcalendar):
+    """Get list of ``Timezome`` from VTIMEZONE items in ``vcalendar``."""
+    return _parse(vcalendar, "VTIMEZONE", calendar.Timezone)

+ 2 - 0
radicale/support/__init__.py

@@ -20,11 +20,13 @@
 
 """
 Calendar storage support configuration.
+
 """
 
 from radicale import config
 
 def load():
+    """Load list of available storage support managers."""
     module = __import__("radicale.support", globals(), locals(),
                         [config.get("support", "type")])
     return getattr(module, config.get("support", "type"))

+ 47 - 38
radicale/support/plain.py

@@ -20,6 +20,7 @@
 
 """
 Plain text storage.
+
 """
 
 import os
@@ -28,39 +29,47 @@ import codecs
 
 from radicale import config, ical
 
-_folder = os.path.expanduser(config.get("support", "folder"))
+FOLDER = os.path.expanduser(config.get("support", "folder"))
+DEFAULT_CALENDAR = config.get("support", "calendar")
+
 
 def _open(path, mode="r"):
+    """Open file at ``path`` with ``mode``, automagically managing encoding."""
     return codecs.open(path, mode, config.get("encoding", "stock"))
 
+
 def calendars():
     """List available calendars paths."""
-    calendars = []
+    available_calendars = []
+
+    for filename in os.listdir(FOLDER):
+        if os.path.isdir(os.path.join(FOLDER, filename)):
+            for cal in os.listdir(os.path.join(FOLDER, filename)):
+                available_calendars.append(posixpath.join(filename, cal))
 
-    for folder in os.listdir(_folder):
-        for cal in os.listdir(os.path.join(_folder, folder)):
-            calendars.append(posixpath.join(folder, cal))
+    return available_calendars
 
-    return calendars
 
 def mkcalendar(name):
     """Write a new calendar called ``name``."""
     user, cal = name.split(posixpath.sep)
-    if not os.path.exists(os.path.join(_folder, user)):
-        os.makedirs(os.path.join(_folder, user))
-    fd = _open(os.path.join(_folder, user, cal), "w")
-    fd.write(ical.write_calendar())
+    if not os.path.exists(os.path.join(FOLDER, user)):
+        os.makedirs(os.path.join(FOLDER, user))
+    descriptor = _open(os.path.join(FOLDER, user, cal), "w")
+    descriptor.write(ical.write_calendar())
+
 
 def read(cal):
     """Read calendar ``cal``."""
-    path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep))
+    path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
     return _open(path).read()
 
+
 def append(cal, vcalendar):
     """Append ``vcalendar`` to ``cal``."""
     old_calendar = read(cal)
-    old_tzs = [tz.tzid for tz in ical.timezones(old_calendar)]
-    path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep))
+    old_timezones = [timezone.id for timezone in ical.timezones(old_calendar)]
+    path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
 
     old_objects = []
     old_objects.extend([event.etag for event in ical.events(old_calendar)])
@@ -70,37 +79,36 @@ def append(cal, vcalendar):
     objects.extend(ical.events(vcalendar))
     objects.extend(ical.todos(vcalendar))
 
-    for tz in ical.timezones(vcalendar):
-        if tz.tzid not in old_tzs:
-            # TODO: Manage position and EOL
-            fd = _open(path)
-            lines = [line for line in fd.readlines() if line]
-            fd.close()
+    for timezone in ical.timezones(vcalendar):
+        if timezone.id not in old_timezones:
+            descriptor = _open(path)
+            lines = [line for line in descriptor.readlines() if line]
+            descriptor.close()
 
-            for i,line in enumerate(tz.text.splitlines()):
+            for i, line in enumerate(timezone.text.splitlines()):
                 lines.insert(2 + i, line + "\n")
 
-            fd = _open(path, "w")
-            fd.writelines(lines)
-            fd.close()
+            descriptor = _open(path, "w")
+            descriptor.writelines(lines)
+            descriptor.close()
 
     for obj in objects:
         if obj.etag not in old_objects:
-            # TODO: Manage position and EOL
-            fd = _open(path)
-            lines = [line for line in fd.readlines() if line]
-            fd.close()
+            descriptor = _open(path)
+            lines = [line for line in descriptor.readlines() if line]
+            descriptor.close()
 
             for line in obj.text.splitlines():
                 lines.insert(-1, line + "\n")
 
-            fd = _open(path, "w")
-            fd.writelines(lines)
-            fd.close()
+            descriptor = _open(path, "w")
+            descriptor.writelines(lines)
+            descriptor.close()
+
 
 def remove(cal, etag):
     """Remove object named ``etag`` from ``cal``."""
-    path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep))
+    path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
 
     cal = read(cal)
 
@@ -109,11 +117,12 @@ def remove(cal, etag):
     todos = [todo for todo in ical.todos(cal) if todo.etag != etag]
     events = [event for event in ical.events(cal) if event.etag != etag]
 
-    fd = _open(path, "w")
-    fd.write(ical.write_calendar(headers, timezones, todos, events))
-    fd.close()
+    descriptor = _open(path, "w")
+    descriptor.write(ical.write_calendar(headers, timezones, todos, events))
+    descriptor.close()
+
 
-if config.get("support", "calendar"):
-    user, cal = config.get("support", "calendar").split(posixpath.sep)
-    if not os.path.exists(os.path.join(_folder, user, cal)):
-        mkcalendar(config.get("support", "calendar"))
+# Create default calendar if not present
+if DEFAULT_CALENDAR:
+    if DEFAULT_CALENDAR not in calendars():
+        mkcalendar(DEFAULT_CALENDAR)

+ 56 - 57
radicale/xmlutils.py

@@ -24,33 +24,42 @@ XML and iCal requests manager.
 Note that all these functions need to receive unicode objects for full
 iCal requests (PUT) and string objects with charset correctly defined
 in them for XML requests (all but PUT).
+
 """
 
-# TODO: Manage errors (see __init__)
-# TODO: Manage depth and calendars/collections (see main)
+# TODO: Manage depth and calendars/collections
 
 import xml.etree.ElementTree as ET
 
 from radicale import client, config, ical
 
+
 # TODO: This is a well-known and accepted hack for ET to avoid ET from renaming
 #       namespaces, which is accepted in XML norm but often not in XML
 #       readers. Is there another clean solution to force namespaces?
-for key, value in config.items("namespace"):
+PROTECTED_NAMESPACES = {
+    "C": "urn:ietf:params:xml:ns:caldav",
+    "D": "DAV:",
+    "CS": "http://calendarserver.org/ns/"}
+for key, value in PROTECTED_NAMESPACES.items():
     ET._namespace_map[value] = key
 
+
 def _tag(short_name, local):
     """Get XML Clark notation {uri(``short_name``)}``local``."""
-    return "{%s}%s" % (config.get("namespace", short_name), local)
+    return "{%s}%s" % (PROTECTED_NAMESPACES[short_name], local)
+
 
 def _response(code):
     """Return full W3C names from HTTP status codes."""
     return "HTTP/1.1 %i %s" % (code, client.responses[code])
 
+
 def delete(obj, calendar, url):
     """Read and answer DELETE requests.
 
     Read rfc4918-9.6 for info.
+
     """
     # Reading request
     calendar.remove(obj)
@@ -74,13 +83,14 @@ def propfind(xml_request, calendar, url):
     """Read and answer PROPFIND requests.
 
     Read rfc4918-9.1 for info.
+
     """
     # Reading request
     root = ET.fromstring(xml_request)
 
-    propElement = root.find(_tag("D", "prop"))
-    propList = propElement.getchildren()
-    properties = [property.tag for property in propList]
+    prop_element = root.find(_tag("D", "prop"))
+    prop_list = prop_element.getchildren()
+    props = [prop.tag for prop in prop_list]
     
     # Writing answer
     multistatus = ET.Element(_tag("D", "multistatus"))
@@ -97,30 +107,30 @@ def propfind(xml_request, calendar, url):
     prop = ET.Element(_tag("D", "prop"))
     propstat.append(prop)
 
-    if _tag("D", "resourcetype") in properties:
-        resourcetype = ET.Element(_tag("D", "resourcetype"))
-        resourcetype.append(ET.Element(_tag("C", "calendar")))
-        prop.append(resourcetype)
+    if _tag("D", "resourcetype") in props:
+        element = ET.Element(_tag("D", "resourcetype"))
+        element.append(ET.Element(_tag("C", "calendar")))
+        prop.append(element)
 
-    if _tag("D", "owner") in properties:
-        owner = ET.Element(_tag("D", "owner"))
-        owner.text = calendar.owner
-        prop.append(owner)
+    if _tag("D", "owner") in props:
+        element = ET.Element(_tag("D", "owner"))
+        element.text = calendar.owner
+        prop.append(element)
 
-    if _tag("D", "getcontenttype") in properties:
-        getcontenttype = ET.Element(_tag("D", "getcontenttype"))
-        getcontenttype.text = "text/calendar"
-        prop.append(getcontenttype)
+    if _tag("D", "getcontenttype") in props:
+        element = ET.Element(_tag("D", "getcontenttype"))
+        element.text = "text/calendar"
+        prop.append(element)
 
-    if _tag("D", "getetag") in properties:
-        getetag = ET.Element(_tag("D", "getetag"))
-        getetag.text = calendar.etag
-        prop.append(getetag)
+    if _tag("D", "getetag") in props:
+        element = ET.Element(_tag("D", "getetag"))
+        element.text = calendar.etag
+        prop.append(element)
 
-    if _tag("CS", "getctag") in properties:
-        getctag = ET.Element(_tag("CS", "getctag"))
-        getctag.text = calendar.ctag
-        prop.append(getctag)
+    if _tag("CS", "getctag") in props:
+        element = ET.Element(_tag("CS", "getctag"))
+        element.text = calendar.ctag
+        prop.append(element)
 
     status = ET.Element(_tag("D", "status"))
     status.text = _response(200)
@@ -128,40 +138,32 @@ def propfind(xml_request, calendar, url):
 
     return ET.tostring(multistatus, config.get("encoding", "request"))
 
-def put(icalRequest, calendar, url, obj):
+def put(ical_request, calendar, url, obj):
     """Read PUT requests."""
     if obj:
         # PUT is modifying obj
-        calendar.replace(obj, icalRequest)
+        calendar.replace(obj, ical_request)
     else:
         # PUT is adding a new object
-        calendar.append(icalRequest)
+        calendar.append(ical_request)
 
 def report(xml_request, calendar, url):
     """Read and answer REPORT requests.
 
     Read rfc3253-3.6 for info.
+
     """
     # Reading request
     root = ET.fromstring(xml_request)
 
-    propElement = root.find(_tag("D", "prop"))
-    propList = propElement.getchildren()
-    properties = [property.tag for property in propList]
-
-    filters = {}
-    filterElement = root.find(_tag("C", "filter"))
-    filterList = propElement.getchildren()
-    # TODO: This should be recursive
-    # TODO: Really manage filters (see ical)
-    for filter in filterList:
-        sub = filters[filter.get("name")] = {}
-        for subfilter in filter.getchildren():
-            sub[subfilter.get("name")] = {}
+    prop_element = root.find(_tag("D", "prop"))
+    prop_list = prop_element.getchildren()
+    props = [prop.tag for prop in prop_list]
 
     if root.tag == _tag("C", "calendar-multiget"):
         # Read rfc4791-7.9 for info
-        hreferences = set([hrefElement.text for hrefElement in root.findall(_tag("D", "href"))])
+        hreferences = set([href_element.text for href_element
+                           in root.findall(_tag("D", "href"))])
     else:
         hreferences = [url]
 
@@ -173,12 +175,10 @@ def report(xml_request, calendar, url):
     #       Read rfc4791-9.[6|10] for info
     for hreference in hreferences:
         headers = ical.headers(calendar.vcalendar)
-        # TODO: Define timezones by obj
         timezones = ical.timezones(calendar.vcalendar)
 
-        objects = []
-        objects.extend(ical.events(calendar.vcalendar))
-        objects.extend(ical.todos(calendar.vcalendar))
+        objects = \
+            ical.events(calendar.vcalendar) + ical.todos(calendar.vcalendar)
 
         if not objects:
             # TODO: Read rfc4791-9.[6|10] to find a right answer
@@ -209,17 +209,16 @@ def report(xml_request, calendar, url):
             prop = ET.Element(_tag("D", "prop"))
             propstat.append(prop)
 
-            if _tag("D", "getetag") in properties:
-                # TODO: Can UID and ETAG be the same?
-                getetag = ET.Element(_tag("D", "getetag"))
-                getetag.text = obj.etag
-                prop.append(getetag)
+            if _tag("D", "getetag") in props:
+                element = ET.Element(_tag("D", "getetag"))
+                element.text = obj.etag
+                prop.append(element)
 
-            if _tag("C", "calendar-data") in properties:
-                cdata = ET.Element(_tag("C", "calendar-data"))
+            if _tag("C", "calendar-data") in props:
+                element = ET.Element(_tag("C", "calendar-data"))
                 # TODO: Maybe assume that events and todos are not the same
-                cdata.text = ical.write_calendar(headers, timezones, [obj])
-                prop.append(cdata)
+                element.text = ical.write_calendar(headers, timezones, [obj])
+                prop.append(element)
 
             status = ET.Element(_tag("D", "status"))
             status.text = _response(200)