Merge branch 'master' of git://gitorious.org/radicale/radicale
Conflicts: radicale/xmlutils.py
This commit is contained in:
commit
270d98ace1
9
NEWS
9
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
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
13
TODO
13
TODO
@ -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
|
||||||
|
@ -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.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -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,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,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,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,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,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))
|
||||||
|
@ -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":
|
if calendar:
|
||||||
elements = [calendar]
|
if depth == "0":
|
||||||
elif depth == "1":
|
items = [calendar]
|
||||||
elements = [calendar] + calendar.events + calendar.todos
|
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
|
items = []
|
||||||
# we limit ourselves to depth == 1
|
|
||||||
elements = [calendar] + calendar.events + calendar.todos
|
|
||||||
|
|
||||||
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 root.tag == _tag("C", "calendar-multiget"):
|
if calendar:
|
||||||
# Read rfc4791-7.9 for info
|
if root.tag == _tag("C", "calendar-multiget"):
|
||||||
hreferences = set((href_element.text for href_element
|
# Read rfc4791-7.9 for info
|
||||||
in root.findall(_tag("D", "href"))))
|
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:
|
for tag in props:
|
||||||
element = ET.Element(_tag("D", "getetag"))
|
element = ET.Element(tag)
|
||||||
element.text = item.etag
|
if tag == _tag("D", "getetag"):
|
||||||
prop.append(element)
|
element.text = item.etag
|
||||||
|
elif tag == _tag("C", "calendar-data"):
|
||||||
if _tag("C", "calendar-data") in props:
|
if isinstance(item, (ical.Event, ical.Todo)):
|
||||||
element = ET.Element(_tag("C", "calendar-data"))
|
element.text = ical.serialize(
|
||||||
if isinstance(item, ical.Event):
|
calendar.headers, calendar.timezones + [item])
|
||||||
element.text = ical.serialize(
|
|
||||||
calendar.headers, calendar.timezones + [item])
|
|
||||||
elif isinstance(item, 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"))
|
||||||
|
2
setup.py
2
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
|
||||||
|
Loading…
Reference in New Issue
Block a user