Code cleaned using Pylint, fixes various minor bugs too.

This commit is contained in:
Guillaume Ayoub 2010-02-10 18:57:21 +01:00
parent a75bb261ed
commit 21a743fcde
11 changed files with 225 additions and 175 deletions

View File

@ -19,16 +19,21 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>. # 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. Radicale Server entry point.
Launch the Radicale Serve according to configuration and command-line Launch the Radicale Serve according to configuration and command-line
arguments. arguments.
""" """
# TODO: Manage depth and calendars/collections (see xmlutils)
# TODO: Manage smart and configurable logs # TODO: Manage smart and configurable logs
# TODO: Manage authentication
import os import os
import sys import sys
@ -62,7 +67,7 @@ parser.add_option(
"-c", "--certificate", "-c", "--certificate",
default=radicale.config.get("server", "certificate"), default=radicale.config.get("server", "certificate"),
help="certificate file ") help="certificate file ")
options, args = parser.parse_args() options = parser.parse_args()[0]
# Update Radicale configuration according to options # Update Radicale configuration according to options
for option in parser.option_list: for option in parser.option_list:
@ -79,5 +84,6 @@ if options.daemon:
# Launch calendar server # Launch calendar server
server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer 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() server.serve_forever()

View File

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

View File

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

View File

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

View File

@ -33,27 +33,35 @@ import hashlib
from radicale import config from radicale import config
def _plain(hash, password):
return hash == password
def _crypt(hash, password): FILENAME = config.get("acl", "filename")
return crypt.crypt(password, hash) == hash 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")) password = password.encode(config.get("encoding", "stock"))
sha1 = hashlib.sha1() sha1 = hashlib.sha1()
sha1.update(password) 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): def has_right(user, password):
"""Check if ``user``/``password`` couple is valid.""" """Check if ``user``/``password`` couple is valid."""
for line in open(_filename).readlines(): for line in open(FILENAME).readlines():
if line.strip(): if line.strip():
login, hash = line.strip().split(":") login, hash_value = line.strip().split(":")
if login == user: if login == user:
return _check_password(hash, password) return CHECK_PASSWORD(hash_value, password)
return False return False

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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