Fix up encoding issues.
authorKeith Packard <keithp@keithp.com>
Wed, 21 Dec 2011 02:30:32 +0000 (18:30 -0800)
committerKeith Packard <keithp@keithp.com>
Wed, 21 Dec 2011 07:23:06 +0000 (23:23 -0800)
vobject serialized output is not flagged with the right encoding, so force
it to be interpreted as utf-8.

calypso/__init__.py
calypso/ical.py
calypso/xmlutils.py

index ec12bce9baf928b7e7a978ed8ab78d9a6e89b0f3..1bbc239faebcfad58c43fd08d464f6cb0635a5e5 100644 (file)
@@ -243,7 +243,9 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     def do_DELETE(self):
         """Manage DELETE request."""
         item = self._calendar.get_item(xmlutils.name_from_path(self.path))
-        if item and self.headers.get("If-Match", item.etag) == item.etag:
+
+        if item and self.headers.get("If-Match", item.etag) == '"' + item.etag + '"':
+            print "item matches"
             # No ETag precondition or precondition verified, delete item
             self._answer = xmlutils.delete(self.path, self._calendar)
 
@@ -252,6 +254,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
             self.end_headers()
             self.wfile.write(self._answer)
         else:
+            print "no item or etag"
             # No item or ETag precondition not verified, do not delete item
             self.send_response(client.PRECONDITION_FAILED)
 
@@ -278,6 +281,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
         self._answer = xmlutils.propfind(
             self.path, xml_request, self._calendar,
             self.headers.get("depth", "infinity"))
+#        print "PROPFIND %s\n%s" % (xml_request, self._answer)
 
         self.send_response(client.MULTI_STATUS)
         self.send_header("DAV", "1, calendar-access")
@@ -316,7 +320,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
             self.send_response(client.CREATED)
             self.send_header("ETag", etag)
             self.end_headers()
-
+        else:
             # PUT rejected in all other cases
             self.send_response(client.PRECONDITION_FAILED)
 
@@ -324,7 +328,9 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     def do_REPORT(self):
         """Manage REPORT request."""
         xml_request = self.rfile.read(int(self.headers["Content-Length"]))
+#        print "REPORT %s" % xml_request
         self._answer = xmlutils.report(self.path, xml_request, self._calendar)
+#        print "ANSWER %s" % self._answer
         self.send_response(client.MULTI_STATUS)
         self.send_header("Content-Length", len(self._answer))
         self.end_headers()
index a1e12062f95ca4e297909a289dd5bf25f3d0d66d..da5188815e8452e3e569a972b0b82577a5fa32fc 100644 (file)
@@ -34,17 +34,11 @@ import hashlib
 import glob
 import tempfile
 import vobject
+import string
+import re
 
 from calypso import config
 
-# This function overrides the builtin ``open`` function for this module
-# pylint: disable=W0622
-def open(path, mode="r"):
-    """Open file at ``path`` with ``mode``, automagically managing encoding."""
-    return codecs.open(path, mode, config.get("encoding", "stock"))
-# pylint: enable=W0622
-
-
 #
 # Recursive search for 'name' within 'vobject'
 #
@@ -67,7 +61,14 @@ class Item(object):
     def __init__(self, text, name=None, path=None):
         """Initialize object from ``text`` and different ``kwargs``."""
 
-        text = text.encode(encoding='UTF-8', errors='replace')
+        try:
+            text = text.encode('utf8')
+        except UnicodeDecodeError:
+            text = text.decode('latin1').encode('utf-8')
+
+        # Strip out control characters
+
+        text = re.sub(r"[\x01-\x09\x0b-\x1F\x7F]","",text)
 
         try:
             self.object = vobject.readOne(text)
@@ -113,11 +114,7 @@ class Item(object):
         Text is the serialized form of the item.
 
         """
-        try:
-            return self.object.serialize()
-        except UnicodeDecodeError, ue:
-            print "Unicode decode error in %s" % self.path
-            raise ue
+        return self.object.serialize().decode('utf-8')
 
     @property
     def length(self):
@@ -135,7 +132,7 @@ class Calendar(object):
 
     def insert_file(self, path):
         try:
-            text = open(path).read()
+            text = open(path,"rb").read()
             item = Item(text, None, path)
             if item:
                 self.my_items.append(item)
@@ -170,6 +167,10 @@ class Calendar(object):
         for file in self.files:
             if not file in files:
                 self.remove_file(file)
+        h = hashlib.sha1()
+        for item in self.my_items:
+            h.update(item.etag)
+        self._ctag = '%d-' % self.mtime + h.hexdigest()
         self.files = files
                 
     def __init__(self, path):
@@ -184,6 +185,8 @@ class Calendar(object):
         self.files = []
         self.my_items = []
         self.mtime = 0
+        self._ctag = ''
+        self.etag = hashlib.sha1(self.path).hexdigest()
         self.scan_dir()
         self.tag = "Collection"
 
@@ -207,7 +210,7 @@ class Calendar(object):
             
     def write_file(self, item):
         fd, path = tempfile.mkstemp(suffix=".ics", prefix="cal", dir=self.path)
-        file = os.fdopen(fd, 'w')
+        file = os.fdopen(fd, 'wb')
         file.write(item.text)
         file.close()
         return path
@@ -274,10 +277,13 @@ class Calendar(object):
 
         new_item = Item(text, name, None)
         if not new_item:
+            print "Cannot create new item"
             return False
         if new_item.name not in (item.name for item in self.my_items):
-                self.create_file(new_item)
-                return True
+            print "New item %s" % new_item.name
+            self.create_file(new_item)
+            return True
+        print "Item %s already present %s" % (new_item.name, self.get_item(new_item.name).path)
         return False
 
     def append_file(self, path):
@@ -285,18 +291,19 @@ class Calendar(object):
         """
 
         try:
-            text = open(path).read()
+            text = open(path,"rb").read()
             if not self.append(None, text):
                 print "Already in calendar: %s" % path
                 return True
         except Exception, ex:
             print "Failed to import: %s: %s" % (ex, path)
             return False
-        print "Imported: %s" % arg
+        print "Imported: %s" % path
         return True
         
     def remove(self, name):
         """Remove object named ``name`` from calendar."""
+        print "Remove object %s" % name
         for old_item in self.my_items:
             if old_item.name == name:
                 self.destroy_file(old_item)
@@ -330,16 +337,14 @@ class Calendar(object):
         #    os.makedirs(os.path.dirname(self.path))
         
         #text = serialize(headers=headers, items=items)
-        #return open(self.path, "w").write(text)
+        #return open(self.path, "wb").write(text)
         return True
 
     @property
-    def etag(self):
-        """Etag from calendar."""
-        h = hashlib.sha1()
-        for item in self.my_items:
-            h.update(item.etag)
-        return h.hexdigest()
+    def ctag(self):
+        self.scan_dir()
+        """Ctag from calendar."""
+        return self._ctag
 
     @property
     def name(self):
index 05a4591add11bb80bae8b27f881ab9464a994f3a..f6a41b43eacd821c61eafb4e50acde0f358b5658 100644 (file)
@@ -68,6 +68,7 @@ def delete(path, calendar):
 
     """
     # Reading request
+    print "delete name %s" % name_from_path(path)
     calendar.remove(name_from_path(path))
 
     # Writing answer
@@ -83,6 +84,7 @@ def delete(path, calendar):
     status.text = _response(200)
     response.append(status)
 
+    print "Deleted\n"
     return ET.tostring(multistatus, config.get("encoding", "request"))
 
 
@@ -130,7 +132,7 @@ def propfind(path, xml_request, calendar, depth):
 
         for tag in props:
             element = ET.Element(tag)
-            if tag == _tag("D", "resourcetype") and is_calendar:
+            if tag == _tag("D", "resourcetype"):
                 tag = ET.Element(_tag("C", "calendar"))
                 element.append(tag)
                 tag = ET.Element(_tag("D", "collection"))
@@ -143,10 +145,10 @@ def propfind(path, xml_request, calendar, depth):
                 else:
                     element.text = "text/calendar"
             elif tag == _tag("CS", "getctag") and is_calendar:
-                element.text = item.etag
+                element.text = item.ctag
             elif tag == _tag("D", "getetag"):
                 element.text = item.etag
-            elif tag == _tag("D", "displayname") and is_calendar:
+            elif tag == _tag("D", "displayname"):
                 element.text = calendar.name
             elif tag == _tag("D", "principal-URL"):
                 # TODO: use a real principal URL, read rfc3744-4.2 for info
@@ -325,7 +327,7 @@ def report(path, xml_request, calendar):
                 if tag == _tag("D", "getetag"):
                     element.text = item.etag
                 elif tag == _tag("C", "calendar-data"):
-                    element.text = item.text.decode('utf-8', errors='replace')
+                    element.text = item.text
                 prop.append(element)
 
             status = ET.Element(_tag("D", "status"))