summaryrefslogtreecommitdiff
path: root/caldav/lib
diff options
context:
space:
mode:
authorTobias Brox <tobias@redpill-linpro.com>2017-01-15 11:25:00 +0100
committerTobias Brox <tobias@redpill-linpro.com>2017-01-15 11:25:00 +0100
commit7e933bd6e96440ba791effd6540260fbe92c4157 (patch)
tree81ea900869569198c78e3494394b434e35590b95 /caldav/lib
downloadpython-caldav-7e933bd6e96440ba791effd6540260fbe92c4157.zip
bedework.caldav-servers.tobixen.no is now up and running
Diffstat (limited to 'caldav/lib')
-rw-r--r--caldav/lib/__init__.py0
-rw-r--r--caldav/lib/error.py46
-rw-r--r--caldav/lib/namespace.py23
-rw-r--r--caldav/lib/python_utilities.py32
-rw-r--r--caldav/lib/url.py186
-rw-r--r--caldav/lib/vcal.py16
6 files changed, 303 insertions, 0 deletions
diff --git a/caldav/lib/__init__.py b/caldav/lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/caldav/lib/__init__.py
diff --git a/caldav/lib/error.py b/caldav/lib/error.py
new file mode 100644
index 0000000..9ed0561
--- /dev/null
+++ b/caldav/lib/error.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+class AuthorizationError(Exception):
+ """
+ The client encountered an HTTP 403 error and is passing it on
+ to the user. The url property will contain the url in question,
+ the reason property will contain the excuse the server sent.
+ """
+ url = None
+ reason = "PHP at work[tm]"
+
+ def __str__(self):
+ return "AuthorizationError at '%s', reason '%s'" % \
+ (self.url, self.reason)
+
+
+class PropsetError(Exception):
+ pass
+
+class PropfindError(Exception):
+ pass
+
+class ReportError(Exception):
+ pass
+
+class MkcolError(Exception):
+ pass
+
+class MkcalendarError(Exception):
+ pass
+
+class PutError(Exception):
+ pass
+
+
+class DeleteError(Exception):
+ pass
+
+class NotFoundError(Exception):
+ pass
+
+exception_by_method = {}
+for method in ('delete', 'put', 'mkcalendar', 'mkcol', 'report', 'propset', 'propfind'):
+ exception_by_method[method] = locals()[method[0].upper() + method[1:] + 'Error']
+
diff --git a/caldav/lib/namespace.py b/caldav/lib/namespace.py
new file mode 100644
index 0000000..d784629
--- /dev/null
+++ b/caldav/lib/namespace.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+## nsmap2 is ref https://bitbucket.org/cyrilrbt/caldav/issue/29/centos59-minifix
+## This looks wrong - should think more about it at a later stage.
+## -- Tobias Brox, 2014-02-16
+
+nsmap = {
+ "D": "DAV",
+ "C": "urn:ietf:params:xml:ns:caldav",
+}
+
+nsmap2 = {
+ "D": "DAV:",
+ "C": "urn:ietf:params:xml:ns:caldav",
+}
+
+
+def ns(prefix, tag=None):
+ name = "{%s}" % nsmap2[prefix]
+ if tag is not None:
+ name = "%s%s" % (name, tag)
+ return name
diff --git a/caldav/lib/python_utilities.py b/caldav/lib/python_utilities.py
new file mode 100644
index 0000000..0131fce
--- /dev/null
+++ b/caldav/lib/python_utilities.py
@@ -0,0 +1,32 @@
+import sys
+from six import string_types
+
+
+def isPython3():
+ return sys.version_info >= (3, 0)
+
+
+def to_wire(text):
+ if text and isinstance(text, string_types) and isPython3():
+ text = bytes(text, 'utf-8')
+ elif not isPython3():
+ text = to_unicode(text).encode('utf-8')
+ return text
+
+
+def to_local(text):
+ if text and not isinstance(text, string_types):
+ text = text.decode('utf-8')
+ return text
+
+
+def to_str(text):
+ if text and not isinstance(text, string_types):
+ text = text.decode('utf-8')
+ return text
+
+
+def to_unicode(text):
+ if text and isinstance(text, string_types) and not isPython3() and not isinstance(text, unicode):
+ text = unicode(text, 'utf-8')
+ return text
diff --git a/caldav/lib/url.py b/caldav/lib/url.py
new file mode 100644
index 0000000..bac1fba
--- /dev/null
+++ b/caldav/lib/url.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from caldav.lib.python_utilities import isPython3, to_unicode
+if isPython3():
+ from urllib import parse
+ from urllib.parse import ParseResult, SplitResult, urlparse, unquote
+else:
+ from urlparse import urlparse as parse
+ from urlparse import ParseResult, SplitResult
+ from urlparse import urlparse
+
+
+def uc2utf8(input):
+ ## argh! this feels wrong, but seems to be needed.
+ if not isPython3() and type(input) == unicode:
+ return input.encode('utf-8')
+ else:
+ return input
+
+class URL:
+ """
+ This class is for wrapping URLs into objects. It's used
+ internally in the library, end users should not need to know
+ anything about this class. All methods that accept URLs can be
+ fed either with an URL object, a string or an urlparse.ParsedURL
+ object.
+
+ Addresses may be one out of three:
+
+ 1) a path relative to the DAV-root, i.e. "someuser/calendar" may
+ refer to
+ "http://my.davical-server.example.com/caldav.php/someuser/calendar".
+
+ 2) an absolute path, i.e. "/caldav.php/someuser/calendar"
+
+ 3) a fully qualified URL,
+ i.e. "http://someuser:somepass@my.davical-server.example.com/caldav.php/someuser/calendar".
+ Remark that hostname, port, user, pass is typically given when
+ instantiating the DAVClient object and cannot be overridden later.
+
+ As of 2013-11, some methods in the caldav library expected strings
+ and some expected urlParseResult objects, some expected
+ fully qualified URLs and most expected absolute paths. The purpose
+ of this class is to ensure consistency and at the same time
+ maintaining backward compatibility. Basically, all methods should
+ accept any kind of URL.
+
+ """
+ def __init__(self, url):
+ if isinstance(url, ParseResult) or isinstance(url, SplitResult):
+ self.url_parsed = url
+ self.url_raw = None
+ else:
+ self.url_raw = url
+ self.url_parsed = None
+
+ def __bool__(self):
+ if self.url_raw or self.url_parsed:
+ return True
+ else:
+ return False
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ if str(self) == str(other):
+ return True
+ ## The URLs could have insignificant differences
+ me = self.canonical()
+ if hasattr(other, 'canonical'):
+ other = other.canonical()
+ return str(me) == str(other)
+
+ ## TODO: better naming? Will return url if url is already an URL
+ ## object, else will instantiate a new URL object
+ @classmethod
+ def objectify(self, url):
+ if url is None:
+ return None
+ if isinstance(url, URL):
+ return url
+ else:
+ return URL(url)
+
+ ## To deal with all kind of methods/properties in the ParseResult
+ ## class
+ def __getattr__(self, attr):
+ if self.url_parsed is None:
+ self.url_parsed = urlparse(self.url_raw)
+ if hasattr(self.url_parsed, attr):
+ return getattr(self.url_parsed, attr)
+ else:
+ return getattr(self.__unicode__(), attr)
+
+ ## returns the url in text format
+ def __str__(self):
+ if isPython3():
+ return self.__unicode__()
+ return self.__unicode__().encode('utf-8')
+
+ ## returns the url in text format
+ def __unicode__(self):
+ if self.url_raw is None:
+ self.url_raw = self.url_parsed.geturl()
+ if isinstance(self.url_raw, str):
+ return to_unicode(self.url_raw)
+ else:
+ return to_unicode(str(self.url_raw))
+
+ def __repr__(self):
+ return "URL(%s)" % str(self)
+
+ def strip_trailing_slash(self):
+ if str(self)[-1] == '/':
+ return URL.objectify(str(self)[:-1])
+ else:
+ return self
+
+ def is_auth(self):
+ return self.username is not None
+
+ def unauth(self):
+ if not self.is_auth():
+ return self
+ return URL.objectify(ParseResult(
+ self.scheme, '%s:%s' % (self.hostname, self.port or {'https': 443, 'http': 80}[self.scheme]),
+ self.path.replace('//', '/'), self.params, self.query, self.fragment))
+
+ def canonical(self):
+ """
+ a canonical URL ... remove authentication details, make sure there
+ are no double slashes, and to make sure the URL is always the same,
+ run it through the urlparser
+ """
+ url = self.unauth()
+
+ ## this is actually already done in the unauth method ...
+ if '//' in url.path:
+ raise NotImplementedError("remove the double slashes")
+
+ ## This looks like a noop - but it may have the side effect
+ ## that urlparser be run (actually not - unauth ensures we
+ ## have an urlParseResult object)
+ url.scheme
+
+ ## make sure to delete the string version
+ url.url_raw = None
+
+ return url
+
+ def join(self, path):
+ """
+ assumes this object is the base URL or base path. If the path
+ is relative, it should be appended to the base. If the path
+ is absolute, it should be added to the connection details of
+ self. If the path already contains connection details and the
+ connection details differ from self, raise an error.
+ """
+ pathAsString = str(path)
+ if not path or not pathAsString:
+ return self
+ path = URL.objectify(path)
+ if (
+ (path.scheme and self.scheme and path.scheme != self.scheme)
+ or
+ (path.hostname and self.hostname and path.hostname != self.hostname)
+ or
+ (path.port and self.port and path.port != self.port)
+ ):
+ raise ValueError("%s can't be joined with %s" % (self, path))
+
+ if path.path[0] == '/':
+ ret_path = uc2utf8(path.path)
+ else:
+ sep = "/"
+ if self.path.endswith("/"):
+ sep = ""
+ ret_path = "%s%s%s" % (self.path, sep, uc2utf8(path.path))
+ return URL(ParseResult(
+ self.scheme or path.scheme, self.netloc or path.netloc, ret_path, path.params, path.query, path.fragment))
+
+def make(url):
+ """Backward compatibility"""
+ return URL.objectify(url)
diff --git a/caldav/lib/vcal.py b/caldav/lib/vcal.py
new file mode 100644
index 0000000..e79724e
--- /dev/null
+++ b/caldav/lib/vcal.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+
+import re
+from caldav.lib.python_utilities import to_local
+
+
+def fix(event):
+ fixed = re.sub('COMPLETED:(\d+)\s', 'COMPLETED:\g<1>T120000Z', to_local(event))
+ #The following line fixes a data bug in some Google Calendar events
+ fixed = re.sub('CREATED:00001231T000000Z',
+ 'CREATED:19700101T000000Z', fixed)
+ fixed = re.sub(r"\\+('\")", r"\1", fixed)
+
+ return fixed