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

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

Conflicts:
	radicale/xmlutils.py
Corentin Le Bail 15 лет назад
Родитель
Сommit
270d98ace1
11 измененных файлов с 108 добавлено и 74 удалено
  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
 ------
 
+0.5 - *Not released yet*
+========================
+
+* Calendar depth
+* MacOS and Windows support
+* HEAD requests management
+* htpasswd user from calendar path
+
+
 0.4 - Hot Days Back
 ===================
 

+ 10 - 3
TODO

@@ -9,9 +9,16 @@
 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

+ 2 - 2
radicale.py

@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 #
 # 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 Pascal Halter
 #
@@ -28,7 +28,7 @@
 """
 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.
 
 """

+ 19 - 4
radicale/__init__.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 #
 # 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 Pascal Halter
 #
@@ -56,6 +56,11 @@ def _check(request, function):
     log.log(10, "Check if user has sufficient rights for performing ``request``.")
     # ``_check`` decorator can access ``request`` protected functions
     # 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)
     if authorization:
         challenge = authorization.lstrip("Basic").strip().encode("ascii")
@@ -94,6 +99,7 @@ class HTTPServer(server.HTTPServer):
 class HTTPSServer(HTTPServer):
     """HTTPS server."""
     PROTOCOL = "https"
+
     def __init__(self, address, handler):
         """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."""
         log.log(10, "Manage GET request.")
         self.do_HEAD()
-        self.wfile.write(self._answer)
+        if self._answer:
+            self.wfile.write(self._answer)
 
     @check_rights
     def do_HEAD(self):
@@ -180,6 +187,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
                     headers=self._calendar.headers, items=items)
                 etag = item.etag
             else:
+                self._answer = None
                 self.send_response(client.GONE)
                 return
         else:
@@ -212,12 +220,19 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
             # No item or ETag precondition not verified, do not delete item
             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):
         """Manage OPTIONS request."""
         log.log(10, "Manage OPTIONS request.")
         self.send_response(client.OK)
         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.end_headers()
 
@@ -227,7 +242,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
         xml_request = self.rfile.read(int(self.headers["Content-Length"]))
         self._answer = xmlutils.propfind(
             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_header("DAV", "1, calendar-access")

+ 1 - 1
radicale/acl/__init__.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 #
 # 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 Pascal Halter
 #

+ 1 - 1
radicale/acl/fake.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 #
 # 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 Pascal Halter
 #

+ 2 - 2
radicale/acl/htpasswd.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 #
 # 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 Pascal Halter
 #
@@ -49,7 +49,7 @@ 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 = hashlib.sha1() # pylint: disable=E1101
     sha1.update(password)
     return sha1.digest() == base64.b64decode(hash_value)
 

+ 1 - 3
radicale/config.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 #
 # 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 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 sys
 # Manage Python2/3 different modules

+ 1 - 2
radicale/ical.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 #
 # 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 Pascal Halter
 #
@@ -135,7 +135,6 @@ class Calendar(object):
 
     def __init__(self, path):
         """Initialize the calendar with ``cal`` and ``user`` parameters."""
-        # TODO: Use properties from the calendar configuration
         self.encoding = "utf-8"
         self.owner = path.split("/")[0]
         self.path = os.path.join(FOLDER, path.replace("/", os.path.sep))

+ 61 - 55
radicale/xmlutils.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 #
 # 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 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
 
 from radicale import client, config, ical, log
@@ -54,7 +52,8 @@ def _response(code):
 def name_from_path(path):
     """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):
@@ -83,7 +82,7 @@ def delete(path, calendar):
     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 rfc4918-9.1 for info.
@@ -100,23 +99,24 @@ def propfind(path, xml_request, calendar, depth, request):
     # Writing answer
     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:
-        # 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"))
         multistatus.append(response)
 
         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)
 
         propstat = ET.Element(_tag("D", "propstat"))
@@ -127,36 +127,44 @@ def propfind(path, xml_request, calendar, depth, request):
 
         for tag in props:
             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"):
                 element.text = calendar.owner
             elif tag == _tag("D", "getcontenttype"):
                 element.text = "text/calendar"
+            elif tag == _tag("CS", "getctag") and is_calendar:
+                element.text = item.etag
             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
-            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"):
                 # 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)
 
         status = ET.Element(_tag("D", "status"))
@@ -192,12 +200,15 @@ def report(path, xml_request, calendar):
     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((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:
-        hreferences = (path,)
+        hreferences = ()
 
     # Writing answer
     multistatus = ET.Element(_tag("D", "multistatus"))
@@ -228,19 +239,14 @@ def report(path, xml_request, calendar):
             prop = ET.Element(_tag("D", "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)
 
             status = ET.Element(_tag("D", "status"))

+ 1 - 1
setup.py

@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 #
 # 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
 # it under the terms of the GNU General Public License as published by