Merge branch 'master' of git://gitorious.org/radicale/radicale

Conflicts:
	radicale/xmlutils.py
This commit is contained in:
Corentin Le Bail 2011-02-16 15:03:20 +01:00
commit 270d98ace1
11 changed files with 108 additions and 74 deletions

9
NEWS
View File

@ -6,6 +6,15 @@
NEWS NEWS
------ ------
0.5 - *Not released yet*
========================
* Calendar depth
* MacOS and Windows support
* HEAD requests management
* htpasswd user from calendar path
0.4 - Hot Days Back 0.4 - Hot Days Back
=================== ===================

13
TODO
View File

@ -9,9 +9,16 @@
0.5 0.5
=== ===
* Calendar collections * iCal and iPhone support
* Group calendars
* [IN PROGRESS] Windows and MacOS tested support
0.6
===
* [IN PROGRESS] Group calendars
* [IN PROGRESS] LDAP and databases auth support
* CalDAV rights
* Read-only access for foreign users
1.0 1.0

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This file is part of Radicale Server - Calendar Server # This file is part of Radicale Server - Calendar Server
# Copyright © 2008-2010 Guillaume Ayoub # Copyright © 2008-2011 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel # Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter # Copyright © 2008 Pascal Halter
# #
@ -28,7 +28,7 @@
""" """
Radicale Server entry point. Radicale Server entry point.
Launch the Radicale Serve according to configuration and command-line Launch the Radicale Server according to configuration and command-line
arguments. arguments.
""" """

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This file is part of Radicale Server - Calendar Server # This file is part of Radicale Server - Calendar Server
# Copyright © 2008-2010 Guillaume Ayoub # Copyright © 2008-2011 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel # Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter # Copyright © 2008 Pascal Halter
# #
@ -56,6 +56,11 @@ def _check(request, function):
log.log(10, "Check if user has sufficient rights for performing ``request``.") log.log(10, "Check if user has sufficient rights for performing ``request``.")
# ``_check`` decorator can access ``request`` protected functions # ``_check`` decorator can access ``request`` protected functions
# pylint: disable=W0212 # pylint: disable=W0212
# If we have no calendar, don't check rights
if not request._calendar:
return function(request)
authorization = request.headers.get("Authorization", None) authorization = request.headers.get("Authorization", None)
if authorization: if authorization:
challenge = authorization.lstrip("Basic").strip().encode("ascii") challenge = authorization.lstrip("Basic").strip().encode("ascii")
@ -94,6 +99,7 @@ class HTTPServer(server.HTTPServer):
class HTTPSServer(HTTPServer): class HTTPSServer(HTTPServer):
"""HTTPS server.""" """HTTPS server."""
PROTOCOL = "https" PROTOCOL = "https"
def __init__(self, address, handler): def __init__(self, address, handler):
"""Create server by wrapping HTTP socket in an SSL socket.""" """Create server by wrapping HTTP socket in an SSL socket."""
log.log(10, "Create server by wrapping HTTP socket in an SSL socket.") log.log(10, "Create server by wrapping HTTP socket in an SSL socket.")
@ -163,6 +169,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
"""Manage GET request.""" """Manage GET request."""
log.log(10, "Manage GET request.") log.log(10, "Manage GET request.")
self.do_HEAD() self.do_HEAD()
if self._answer:
self.wfile.write(self._answer) self.wfile.write(self._answer)
@check_rights @check_rights
@ -180,6 +187,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
headers=self._calendar.headers, items=items) headers=self._calendar.headers, items=items)
etag = item.etag etag = item.etag
else: else:
self._answer = None
self.send_response(client.GONE) self.send_response(client.GONE)
return return
else: else:
@ -212,12 +220,19 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
# No item or ETag precondition not verified, do not delete item # No item or ETag precondition not verified, do not delete item
self.send_response(client.PRECONDITION_FAILED) self.send_response(client.PRECONDITION_FAILED)
@check_rights
def do_MKCALENDAR(self):
"""Manage MKCALENDAR request."""
self.send_response(client.CREATED)
self.end_headers()
def do_OPTIONS(self): def do_OPTIONS(self):
"""Manage OPTIONS request.""" """Manage OPTIONS request."""
log.log(10, "Manage OPTIONS request.") log.log(10, "Manage OPTIONS request.")
self.send_response(client.OK) self.send_response(client.OK)
self.send_header( self.send_header(
"Allow", "DELETE, HEAD, GET, OPTIONS, PROPFIND, PUT, REPORT") "Allow", "DELETE, HEAD, GET, MKCALENDAR, "
"OPTIONS, PROPFIND, PUT, REPORT")
self.send_header("DAV", "1, calendar-access") self.send_header("DAV", "1, calendar-access")
self.end_headers() self.end_headers()
@ -227,7 +242,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
xml_request = self.rfile.read(int(self.headers["Content-Length"])) xml_request = self.rfile.read(int(self.headers["Content-Length"]))
self._answer = xmlutils.propfind( self._answer = xmlutils.propfind(
self.path, xml_request, self._calendar, self.path, xml_request, self._calendar,
self.headers.get("depth", "infinity"), self) self.headers.get("depth", "infinity"))
self.send_response(client.MULTI_STATUS) self.send_response(client.MULTI_STATUS)
self.send_header("DAV", "1, calendar-access") self.send_header("DAV", "1, calendar-access")

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This file is part of Radicale Server - Calendar Server # This file is part of Radicale Server - Calendar Server
# Copyright © 2008-2010 Guillaume Ayoub # Copyright © 2008-2011 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel # Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter # Copyright © 2008 Pascal Halter
# #

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This file is part of Radicale Server - Calendar Server # This file is part of Radicale Server - Calendar Server
# Copyright © 2008-2010 Guillaume Ayoub # Copyright © 2008-2011 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel # Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter # Copyright © 2008 Pascal Halter
# #

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This file is part of Radicale Server - Calendar Server # This file is part of Radicale Server - Calendar Server
# Copyright © 2008-2010 Guillaume Ayoub # Copyright © 2008-2011 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel # Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter # Copyright © 2008 Pascal Halter
# #
@ -49,7 +49,7 @@ def _sha1(hash_value, password):
"""Check if ``hash_value`` and ``password`` match using sha1 method.""" """Check if ``hash_value`` and ``password`` match using sha1 method."""
hash_value = hash_value.replace("{SHA}", "").encode("ascii") 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() # pylint: disable=E1101
sha1.update(password) sha1.update(password)
return sha1.digest() == base64.b64decode(hash_value) return sha1.digest() == base64.b64decode(hash_value)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This file is part of Radicale Server - Calendar Server # This file is part of Radicale Server - Calendar Server
# Copyright © 2008-2010 Guillaume Ayoub # Copyright © 2008-2011 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel # Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter # Copyright © 2008 Pascal Halter
# #
@ -25,8 +25,6 @@ Give a configparser-like interface to read and write configuration.
""" """
# TODO: Use abstract filenames for other platforms
import os import os
import sys import sys
# Manage Python2/3 different modules # Manage Python2/3 different modules

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This file is part of Radicale Server - Calendar Server # This file is part of Radicale Server - Calendar Server
# Copyright © 2008-2010 Guillaume Ayoub # Copyright © 2008-2011 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel # Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter # Copyright © 2008 Pascal Halter
# #
@ -135,7 +135,6 @@ class Calendar(object):
def __init__(self, path): def __init__(self, path):
"""Initialize the calendar with ``cal`` and ``user`` parameters.""" """Initialize the calendar with ``cal`` and ``user`` parameters."""
# TODO: Use properties from the calendar configuration
self.encoding = "utf-8" self.encoding = "utf-8"
self.owner = path.split("/")[0] self.owner = path.split("/")[0]
self.path = os.path.join(FOLDER, path.replace("/", os.path.sep)) self.path = os.path.join(FOLDER, path.replace("/", os.path.sep))

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This file is part of Radicale Server - Calendar Server # This file is part of Radicale Server - Calendar Server
# Copyright © 2008-2010 Guillaume Ayoub # Copyright © 2008-2011 Guillaume Ayoub
# Copyright © 2008 Nicolas Kandel # Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter # Copyright © 2008 Pascal Halter
# #
@ -27,8 +27,6 @@ in them for XML requests (all but PUT).
""" """
# TODO: Manage depth and calendars/collections
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from radicale import client, config, ical, log from radicale import client, config, ical, log
@ -54,7 +52,8 @@ def _response(code):
def name_from_path(path): def name_from_path(path):
"""Return Radicale item name from ``path``.""" """Return Radicale item name from ``path``."""
log.log(10, "Return Radicale item name from ``path``.") log.log(10, "Return Radicale item name from ``path``.")
return path.split("/")[-1] path_parts = path.strip("/").split("/")
return path_parts[-1] if len(path_parts) > 2 else None
def delete(path, calendar): def delete(path, calendar):
@ -83,7 +82,7 @@ def delete(path, calendar):
return ET.tostring(multistatus, config.get("encoding", "request")) return ET.tostring(multistatus, config.get("encoding", "request"))
def propfind(path, xml_request, calendar, depth, request): def propfind(path, xml_request, calendar, depth):
"""Read and answer PROPFIND requests. """Read and answer PROPFIND requests.
Read rfc4918-9.1 for info. Read rfc4918-9.1 for info.
@ -100,23 +99,24 @@ def propfind(path, xml_request, calendar, depth, request):
# Writing answer # Writing answer
multistatus = ET.Element(_tag("D", "multistatus")) multistatus = ET.Element(_tag("D", "multistatus"))
if calendar:
if depth == "0": if depth == "0":
elements = [calendar] items = [calendar]
elif depth == "1":
elements = [calendar] + calendar.events + calendar.todos
else: else:
# depth is infinity or not specified # depth is 1, infinity or not specified
# we limit ourselves to depth == 1 # we limit ourselves to depth == 1
elements = [calendar] + calendar.events + calendar.todos items = [calendar] + calendar.events + calendar.todos
else:
items = []
for element in elements: for item in items:
is_calendar = isinstance(element, ical.Calendar) is_calendar = isinstance(item, ical.Calendar)
response = ET.Element(_tag("D", "response")) response = ET.Element(_tag("D", "response"))
multistatus.append(response) multistatus.append(response)
href = ET.Element(_tag("D", "href")) href = ET.Element(_tag("D", "href"))
href.text = path if is_calendar else "%s/%s" % (path, element.name) href.text = path if is_calendar else path + item.name
response.append(href) response.append(href)
propstat = ET.Element(_tag("D", "propstat")) propstat = ET.Element(_tag("D", "propstat"))
@ -127,36 +127,44 @@ def propfind(path, xml_request, calendar, depth, request):
for tag in props: for tag in props:
element = ET.Element(tag) element = ET.Element(tag)
if tag == _tag("D", "resourcetype"): if tag == _tag("D", "resourcetype") and is_calendar:
if is_calendar:
tag = ET.Element(_tag("C", "calendar")) tag = ET.Element(_tag("C", "calendar"))
element.append(tag) element.append(tag)
tag = ET.Element(_tag("D", "collection")) tag = ET.Element(_tag("D", "collection"))
element.append(tag) element.append(tag)
else:
tag = ET.Element(_tag("C", "comp"))
tag.set("name", element.tag)
element.append(tag)
elif tag == _tag("D", "owner"): elif tag == _tag("D", "owner"):
element.text = calendar.owner element.text = calendar.owner
elif tag == _tag("D", "getcontenttype"): elif tag == _tag("D", "getcontenttype"):
element.text = "text/calendar" element.text = "text/calendar"
elif tag == _tag("CS", "getctag") and is_calendar:
element.text = item.etag
elif tag == _tag("D", "getetag"): elif tag == _tag("D", "getetag"):
element.text = element.etag element.text = item.etag
elif tag == _tag("D", "displayname"): elif tag == _tag("D", "displayname") and is_calendar:
element.text = calendar.name element.text = calendar.name
elif tag == _tag("D", "supported-report-set"):
supported_report = ET.Element(_tag("D", "supported-report"))
report_set = ET.Element(_tag("D", "report"))
report_set.append(ET.Element(_tag("C", "calendar-multiget")))
supported_report.append(report_set)
element.append(supported_report)
elif tag == _tag("D", "principal-URL"): elif tag == _tag("D", "principal-URL"):
# TODO: use a real principal URL, read rfc3744-4.2 for info # TODO: use a real principal URL, read rfc3744-4.2 for info
element.text = "%s://%s%s" % ( tag = ET.Element(_tag("D", "href"))
request.server.PROTOCOL, request.headers["Host"], tag.text = path
request.path) element.append(tag)
elif tag in (
_tag("D", "principal-collection-set"),
_tag("C", "calendar-user-address-set"),
_tag("C", "calendar-home-set")):
tag = ET.Element(_tag("D", "href"))
tag.text = path
element.append(tag)
elif tag == _tag("C", "supported-calendar-component-set"):
comp = ET.Element(_tag("C", "comp"))
comp.set("name", "VTODO") # pylint: disable=W0511
element.append(comp)
comp = ET.Element(_tag("C", "comp"))
comp.set("name", "VEVENT")
element.append(comp)
elif tag == _tag("D", "current-user-privilege-set"):
privilege = ET.Element(_tag("D", "privilege"))
privilege.append(ET.Element(_tag("D", "all")))
element.append(privilege)
prop.append(element) prop.append(element)
status = ET.Element(_tag("D", "status")) status = ET.Element(_tag("D", "status"))
@ -192,12 +200,15 @@ def report(path, xml_request, calendar):
prop_list = prop_element.getchildren() prop_list = prop_element.getchildren()
props = [prop.tag for prop in prop_list] props = [prop.tag for prop in prop_list]
if calendar:
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((href_element.text for href_element hreferences = set((href_element.text for href_element
in root.findall(_tag("D", "href")))) in root.findall(_tag("D", "href"))))
else: else:
hreferences = (path,) hreferences = (path,)
else:
hreferences = ()
# Writing answer # Writing answer
multistatus = ET.Element(_tag("D", "multistatus")) multistatus = ET.Element(_tag("D", "multistatus"))
@ -228,17 +239,12 @@ def report(path, xml_request, calendar):
prop = ET.Element(_tag("D", "prop")) prop = ET.Element(_tag("D", "prop"))
propstat.append(prop) propstat.append(prop)
if _tag("D", "getetag") in props: for tag in props:
element = ET.Element(_tag("D", "getetag")) element = ET.Element(tag)
if tag == _tag("D", "getetag"):
element.text = item.etag element.text = item.etag
prop.append(element) elif tag == _tag("C", "calendar-data"):
if isinstance(item, (ical.Event, ical.Todo)):
if _tag("C", "calendar-data") in props:
element = ET.Element(_tag("C", "calendar-data"))
if isinstance(item, ical.Event):
element.text = ical.serialize(
calendar.headers, calendar.timezones + [item])
elif isinstance(item, ical.Todo):
element.text = ical.serialize( element.text = ical.serialize(
calendar.headers, calendar.timezones + [item]) calendar.headers, calendar.timezones + [item])
prop.append(element) prop.append(element)

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# This file is part of Radicale Server - Calendar Server # This file is part of Radicale Server - Calendar Server
# Copyright © 2009-2010 Guillaume Ayoub # Copyright © 2009-2011 Guillaume Ayoub
# #
# This library is free software: you can redistribute it and/or modify # This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by