Browse Source

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

Conflicts:
	radicale/xmlutils.py
Corentin Le Bail 15 years ago
parent
commit
270d98ace1
11 changed files with 108 additions and 74 deletions
  1. 9 0
      NEWS
  2. 10 3
      TODO
  3. 2 2
      radicale.py
  4. 19 4
      radicale/__init__.py
  5. 1 1
      radicale/acl/__init__.py
  6. 1 1
      radicale/acl/fake.py
  7. 2 2
      radicale/acl/htpasswd.py
  8. 1 3
      radicale/config.py
  9. 1 2
      radicale/ical.py
  10. 61 55
      radicale/xmlutils.py
  11. 1 1
      setup.py

+ 9 - 0
NEWS

@@ -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
 ===================
 ===================
 
 

+ 10 - 3
TODO

@@ -9,9 +9,16 @@
 0.5
 0.5
 ===
 ===
 
 
-* Calendar collections
-* Group calendars
-* [IN PROGRESS] Windows and MacOS tested support
+* iCal and iPhone 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

+ 2 - 2
radicale.py

@@ -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.
 
 
 """
 """

+ 19 - 4
radicale/__init__.py

@@ -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,7 +169,8 @@ 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()
-        self.wfile.write(self._answer)
+        if self._answer:
+            self.wfile.write(self._answer)
 
 
     @check_rights
     @check_rights
     def do_HEAD(self):
     def do_HEAD(self):
@@ -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")

+ 1 - 1
radicale/acl/__init__.py

@@ -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
 #
 #

+ 1 - 1
radicale/acl/fake.py

@@ -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
 #
 #

+ 2 - 2
radicale/acl/htpasswd.py

@@ -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)
 
 

+ 1 - 3
radicale/config.py

@@ -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

+ 1 - 2
radicale/ical.py

@@ -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))

+ 61 - 55
radicale/xmlutils.py

@@ -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 depth == "0":
-        elements = [calendar]
-    elif depth == "1":
-        elements = [calendar] + calendar.events + calendar.todos
+    if calendar:
+        if depth == "0":
+            items = [calendar]
+        else:
+            # depth is 1, infinity or not specified
+            # we limit ourselves to depth == 1
+            items = [calendar] + calendar.events + calendar.todos
     else:
     else:
-        # depth is infinity or not specified
-        # we limit ourselves to depth == 1
-        elements = [calendar] + calendar.events + calendar.todos
+        items = []
 
 
-    for element in elements:
-        is_calendar = isinstance(element, ical.Calendar)
+    for item in items:
+        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 is_calendar:
-                    tag = ET.Element(_tag("C", "calendar"))
-                    element.append(tag)
-                    tag = ET.Element(_tag("D", "collection"))
-                    element.append(tag)
-                else:
-                    tag = ET.Element(_tag("C", "comp"))
-                    tag.set("name", element.tag)
-                    element.append(tag)
+            if tag == _tag("D", "resourcetype") and is_calendar:
+                tag = ET.Element(_tag("C", "calendar"))
+                element.append(tag)
+                tag = ET.Element(_tag("D", "collection"))
+                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
-            elif tag == _tag("D", "displayname"):
+                element.text = item.etag
+            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" % (
-                    request.server.PROTOCOL, request.headers["Host"],
-                    request.path)
-
+                tag = ET.Element(_tag("D", "href"))
+                tag.text = 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 root.tag == _tag("C", "calendar-multiget"):
-        # Read rfc4791-7.9 for info
-        hreferences = set((href_element.text for href_element
-                           in root.findall(_tag("D", "href"))))
+    if calendar:
+        if root.tag == _tag("C", "calendar-multiget"):
+            # Read rfc4791-7.9 for info
+            hreferences = set((href_element.text for href_element
+                               in root.findall(_tag("D", "href"))))
+        else:
+            hreferences = (path,)
     else:
     else:
-        hreferences = (path,)
+        hreferences = ()
 
 
     # Writing answer
     # Writing answer
     multistatus = ET.Element(_tag("D", "multistatus"))
     multistatus = ET.Element(_tag("D", "multistatus"))
@@ -228,19 +239,14 @@ 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:
-                element = ET.Element(_tag("D", "getetag"))
-                element.text = item.etag
-                prop.append(element)
-
-            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(
-                        calendar.headers, calendar.timezones + [item])
+            for tag in props:
+                element = ET.Element(tag)
+                if tag == _tag("D", "getetag"):
+                    element.text = item.etag
+                elif tag == _tag("C", "calendar-data"):
+                    if isinstance(item, (ical.Event, ical.Todo)):
+                        element.text = ical.serialize(
+                            calendar.headers, calendar.timezones + [item])
                 prop.append(element)
                 prop.append(element)
 
 
             status = ET.Element(_tag("D", "status"))
             status = ET.Element(_tag("D", "status"))

+ 1 - 1
setup.py

@@ -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