[Python-modules-commits] [python-sievelib] 01/01: New upstream version 1.0.0

Michael Fladischer fladi at moszumanska.debian.org
Wed Nov 1 15:25:13 UTC 2017


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch upstream
in repository python-sievelib.

commit 1eb07f88802090a69f1f13485152fca6b04e8268
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Wed Nov 1 14:02:15 2017 +0100

    New upstream version 1.0.0
---
 MANIFEST.in                         |   1 +
 PKG-INFO                            |  16 ++--
 README.rst                          |  14 ++-
 setup.py                            |   2 +-
 sievelib.egg-info/PKG-INFO          |  16 ++--
 sievelib.egg-info/SOURCES.txt       |   5 +-
 sievelib/commands.py                |   6 +-
 sievelib/managesieve.py             | 182 ++++++++++++++++++++----------------
 sievelib/parser.py                  |  73 ++++++++-------
 sievelib/tests/files/utf8_sieve.txt |   7 ++
 sievelib/tests/test_managesieve.py  | 166 ++++++++++++++++++++++++++++++++
 sievelib/tests/test_parser.py       | 111 +++++++++++-----------
 sievelib/tools.py                   |  12 +++
 13 files changed, 415 insertions(+), 196 deletions(-)

diff --git a/MANIFEST.in b/MANIFEST.in
index c6b0605..f76c187 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1 +1,2 @@
 include README.rst COPYING requirements.txt
+include sievelib/tests/files/utf8_sieve.txt
diff --git a/PKG-INFO b/PKG-INFO
index 8eb49ce..cadc3f4 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: sievelib
-Version: 0.9.2
+Version: 1.0.0
 Summary: Client-side SIEVE library
 Home-page: https://github.com/tonioo/sievelib
 Author: Antoine Nguyen
@@ -9,7 +9,7 @@ License: MIT
 Description: sievelib
         ========
         
-        |travis| |latest-version| |downloads|
+        |travis| |codecov| |latest-version|
         
         Client-side Sieve and Managesieve library written in Python.
         
@@ -55,7 +55,7 @@ Description: sievelib
         
           import sievelib
         
-          def MyCommand(sievelib.commands.ActionCommand):
+          class MyCommand(sievelib.commands.ActionCommand):
               args_definition = [
                   {"name": "testtag",
                       "type": ["tag"],
@@ -152,14 +152,12 @@ Description: sievelib
         
         Additional documentation is available with source code.
         
-        .. |latest-version| image:: https://pypip.in/v/sievelib/badge.png
-           :alt: Latest version on Pypi
-           :target: https://crate.io/packages/sievelib/
-        .. |downloads| image:: https://pypip.in/d/sievelib/badge.png
-           :alt: Downloads from Pypi
-           :target: https://crate.io/packages/sievelib/
+        .. |latest-version| image:: https://badge.fury.io/py/sievelib.svg
+           :target: https://badge.fury.io/py/sievelib
         .. |travis| image:: https://travis-ci.org/tonioo/sievelib.png?branch=master
            :target: https://travis-ci.org/tonioo/sievelib
+        .. |codecov| image:: http://codecov.io/github/tonioo/sievelib/coverage.svg?branch=master
+           :target: http://codecov.io/github/tonioo/sievelib?branch=master
         
 Keywords: sieve,managesieve,parser,client
 Platform: UNKNOWN
diff --git a/README.rst b/README.rst
index 431c0c5..4583eab 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,7 @@
 sievelib
 ========
 
-|travis| |latest-version| |downloads|
+|travis| |codecov| |latest-version|
 
 Client-side Sieve and Managesieve library written in Python.
 
@@ -47,7 +47,7 @@ commands. For example::
 
   import sievelib
 
-  def MyCommand(sievelib.commands.ActionCommand):
+  class MyCommand(sievelib.commands.ActionCommand):
       args_definition = [
           {"name": "testtag",
               "type": ["tag"],
@@ -144,11 +144,9 @@ application (there isn't any shell provided)::
 
 Additional documentation is available with source code.
 
-.. |latest-version| image:: https://pypip.in/v/sievelib/badge.png
-   :alt: Latest version on Pypi
-   :target: https://crate.io/packages/sievelib/
-.. |downloads| image:: https://pypip.in/d/sievelib/badge.png
-   :alt: Downloads from Pypi
-   :target: https://crate.io/packages/sievelib/
+.. |latest-version| image:: https://badge.fury.io/py/sievelib.svg
+   :target: https://badge.fury.io/py/sievelib
 .. |travis| image:: https://travis-ci.org/tonioo/sievelib.png?branch=master
    :target: https://travis-ci.org/tonioo/sievelib
+.. |codecov| image:: http://codecov.io/github/tonioo/sievelib/coverage.svg?branch=master
+   :target: http://codecov.io/github/tonioo/sievelib?branch=master
diff --git a/setup.py b/setup.py
index e20614b..79c3703 100644
--- a/setup.py
+++ b/setup.py
@@ -54,7 +54,7 @@ setup(
     name="sievelib",
     packages=find_packages(),
     include_package_data=True,
-    version="0.9.2",
+    version="1.0.0",
     description="Client-side SIEVE library",
     author="Antoine Nguyen",
     author_email="tonio at ngyn.org",
diff --git a/sievelib.egg-info/PKG-INFO b/sievelib.egg-info/PKG-INFO
index 8eb49ce..cadc3f4 100644
--- a/sievelib.egg-info/PKG-INFO
+++ b/sievelib.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: sievelib
-Version: 0.9.2
+Version: 1.0.0
 Summary: Client-side SIEVE library
 Home-page: https://github.com/tonioo/sievelib
 Author: Antoine Nguyen
@@ -9,7 +9,7 @@ License: MIT
 Description: sievelib
         ========
         
-        |travis| |latest-version| |downloads|
+        |travis| |codecov| |latest-version|
         
         Client-side Sieve and Managesieve library written in Python.
         
@@ -55,7 +55,7 @@ Description: sievelib
         
           import sievelib
         
-          def MyCommand(sievelib.commands.ActionCommand):
+          class MyCommand(sievelib.commands.ActionCommand):
               args_definition = [
                   {"name": "testtag",
                       "type": ["tag"],
@@ -152,14 +152,12 @@ Description: sievelib
         
         Additional documentation is available with source code.
         
-        .. |latest-version| image:: https://pypip.in/v/sievelib/badge.png
-           :alt: Latest version on Pypi
-           :target: https://crate.io/packages/sievelib/
-        .. |downloads| image:: https://pypip.in/d/sievelib/badge.png
-           :alt: Downloads from Pypi
-           :target: https://crate.io/packages/sievelib/
+        .. |latest-version| image:: https://badge.fury.io/py/sievelib.svg
+           :target: https://badge.fury.io/py/sievelib
         .. |travis| image:: https://travis-ci.org/tonioo/sievelib.png?branch=master
            :target: https://travis-ci.org/tonioo/sievelib
+        .. |codecov| image:: http://codecov.io/github/tonioo/sievelib/coverage.svg?branch=master
+           :target: http://codecov.io/github/tonioo/sievelib?branch=master
         
 Keywords: sieve,managesieve,parser,client
 Platform: UNKNOWN
diff --git a/sievelib.egg-info/SOURCES.txt b/sievelib.egg-info/SOURCES.txt
index 60762ce..3cb5ad9 100644
--- a/sievelib.egg-info/SOURCES.txt
+++ b/sievelib.egg-info/SOURCES.txt
@@ -9,6 +9,7 @@ sievelib/digest_md5.py
 sievelib/factory.py
 sievelib/managesieve.py
 sievelib/parser.py
+sievelib/tools.py
 sievelib.egg-info/PKG-INFO
 sievelib.egg-info/SOURCES.txt
 sievelib.egg-info/dependency_links.txt
@@ -16,4 +17,6 @@ sievelib.egg-info/requires.txt
 sievelib.egg-info/top_level.txt
 sievelib/tests/__init__.py
 sievelib/tests/test_factory.py
-sievelib/tests/test_parser.py
\ No newline at end of file
+sievelib/tests/test_managesieve.py
+sievelib/tests/test_parser.py
+sievelib/tests/files/utf8_sieve.txt
\ No newline at end of file
diff --git a/sievelib/commands.py b/sievelib/commands.py
index 9f8aa76..4b482ba 100644
--- a/sievelib/commands.py
+++ b/sievelib/commands.py
@@ -320,7 +320,7 @@ class Command(object):
             return False
 
         if self.curarg is not None and "extra_arg" in self.curarg:
-            if atype == self.curarg["extra_arg"]["type"]:
+            if atype in self.curarg["extra_arg"]["type"]:
                 if "values" not in self.curarg["extra_arg"] \
                    or avalue in self.curarg["extra_arg"]["values"]:
                     if add:
@@ -406,7 +406,7 @@ class RequireCommand(ControlCommand):
     loaded_extensions = []
 
     def complete_cb(self):
-        if type(self.arguments["capabilities"]) == str:
+        if type(self.arguments["capabilities"]) != list:
             exts = [self.arguments["capabilities"]]
         else:
             exts = self.arguments["capabilities"]
@@ -630,7 +630,7 @@ class VacationCommand(ActionCommand):
          "type": ["tag"],
          "write_tag": True,
          "values": [":addresses"],
-         "extra_arg": {"type": "stringlist"},
+         "extra_arg": {"type": ["string", "stringlist"]},
          "required": False},
         {"name": "handle",
          "type": ["tag"],
diff --git a/sievelib/managesieve.py b/sievelib/managesieve.py
index 977efe6..20d1c6d 100644
--- a/sievelib/managesieve.py
+++ b/sievelib/managesieve.py
@@ -1,14 +1,13 @@
-#!/usr/bin/env python
 # coding: utf-8
 
 """
-A MANAGESIEVE client
+A MANAGESIEVE client.
 
 A protocol for securely managing Sieve scripts on a remote server.
 This protocol allows a user to have multiple scripts, and also alerts
 a user to syntactically flawed scripts.
 
-Implementation based on <draft-martin-managesieve-12>.
+Implementation based on RFC 5804.
 """
 from __future__ import print_function
 
@@ -21,14 +20,16 @@ from future.utils import python_2_unicode_compatible
 import six
 
 from .digest_md5 import DigestMD5
+from . import tools
 
-CRLF = '\r\n'
 
-KNOWN_CAPABILITIES = ["IMPLEMENTATION", "SASL", "SIEVE",
-                      "STARTTLS", "NOTIFY", "LANGUAGE",
-                      "RENAME"]
+CRLF = b"\r\n"
 
-SUPPORTED_AUTH_MECHS = ["DIGEST-MD5", "PLAIN", "LOGIN"]
+KNOWN_CAPABILITIES = [u"IMPLEMENTATION", u"SASL", u"SIEVE",
+                      u"STARTTLS", u"NOTIFY", u"LANGUAGE",
+                      u"VERSION"]
+
+SUPPORTED_AUTH_MECHS = [u"DIGEST-MD5", u"PLAIN", u"LOGIN"]
 
 
 class Error(Exception):
@@ -79,15 +80,15 @@ class Client(object):
         self.srvport = srvport
         self.__debug = debug
         self.sock = None
-        self.__read_buffer = ""
+        self.__read_buffer = b""
         self.authenticated = False
         self.errcode = None
 
         self.__capabilities = {}
-        self.__respcode_expr = re.compile(r"(OK|NO|BYE)\s*(.+)?")
-        self.__error_expr = re.compile(r'(\([\w/-]+\))?\s*(".+")')
-        self.__size_expr = re.compile(r"\{(\d+)\+?\}")
-        self.__active_expr = re.compile("ACTIVE", re.IGNORECASE)
+        self.__respcode_expr = re.compile(br"(OK|NO|BYE)\s*(.+)?")
+        self.__error_expr = re.compile(br'(\([\w/-]+\))?\s*(".+")')
+        self.__size_expr = re.compile(br"\{(\d+)\+?\}")
+        self.__active_expr = re.compile(br"ACTIVE", re.IGNORECASE)
 
     def __del__(self):
         if self.sock is not None:
@@ -114,7 +115,7 @@ class Client(object):
         :rtype: string
         :returns: the read block (can be empty)
         """
-        buf = ""
+        buf = b""
         if len(self.__read_buffer):
             limit = (
                 size if size <= len(self.__read_buffer) else
@@ -129,6 +130,7 @@ class Client(object):
             buf += self.sock.recv(size)
         except (socket.timeout, ssl.SSLError):
             raise Error("Failed to read %d bytes from the server" % size)
+        self.__dprint(buf)
         return buf
 
     def __read_line(self):
@@ -146,17 +148,18 @@ class Client(object):
         :rtype: string
         :return: the read line
         """
-        ret = ""
+        ret = b""
         while True:
             try:
                 pos = self.__read_buffer.index(CRLF)
-                ret = self.__read_buffer[0:pos]
+                ret = self.__read_buffer[:pos]
                 self.__read_buffer = self.__read_buffer[pos + len(CRLF):]
                 break
             except ValueError:
                 pass
             try:
                 nval = self.sock.recv(self.read_size)
+                self.__dprint(nval)
                 if not len(nval):
                     break
                 self.__read_buffer += nval
@@ -170,9 +173,9 @@ class Client(object):
 
             m = self.__respcode_expr.match(ret)
             if m:
-                if m.group(1) == "BYE":
+                if m.group(1) == b"BYE":
                     raise Error("Connection closed by server")
-                if m.group(1) == "NO":
+                if m.group(1) == b"NO":
                     self.__parse_error(m.group(2))
                 raise Response(m.group(1), m.group(2))
         return ret
@@ -190,7 +193,7 @@ class Client(object):
         :return: a tuple of the form (code, data, response). If
         nblines is provided, code and data can be equal to None.
         """
-        resp, code, data = ("", None, None)
+        resp, code, data = (b"", None, None)
         cpt = 0
         while True:
             try:
@@ -201,6 +204,8 @@ class Client(object):
                 break
             except Literal as inst:
                 resp += self.__read_block(inst.value)
+                if not resp.endswith(CRLF):
+                    resp += self.__read_line() + CRLF
                 continue
             if not len(line):
                 continue
@@ -222,14 +227,18 @@ class Client(object):
         """
         ret = []
         for a in args:
-            if type(a) in [str, unicode] and self.__size_expr.match(a) is None:
-                ret += ['"%s"' % a.encode('utf-8')]
+            if isinstance(a, six.binary_type):
+                if self.__size_expr.match(a):
+                    ret += [a]
+                else:
+                    ret += [b'"' + a + b'"']
                 continue
-            ret += ["%s" % str(a)]
+            ret += [bytes(str(a).encode("utf-8"))]
         return ret
 
     def __send_command(
-            self, name, args=[], withcontent=False, extralines=[], nblines=-1):
+            self, name, args=None, withcontent=False, extralines=None,
+            nblines=-1):
         """Send a command to the server.
 
         If args is not empty, we concatenate the given command with
@@ -249,15 +258,20 @@ class Client(object):
         :returns: a tuple of the form (code, data[, response])
 
         """
-        tosend = name
-        if len(args):
-            tosend += " " + " ".join(self.__prepare_args(args))
-        self.__dprint("Command: %s" % tosend)
-        self.sock.sendall("%s%s" % (tosend, CRLF))
-        for l in extralines:
-            self.sock.sendall("%s%s" % (l, CRLF))
+        tosend = name.encode("utf-8")
+        if args:
+            tosend += b" " + b" ".join(self.__prepare_args(args))
+        self.__dprint(b"Command: " + tosend)
+        self.sock.sendall(tosend + CRLF)
+        if extralines:
+            for l in extralines:
+                self.sock.sendall(l + CRLF)
         code, data, content = self.__read_response(nblines)
 
+        if isinstance(code, six.binary_type):
+            code = code.decode("utf-8")
+        data = data.decode("utf-8")
+
         if withcontent:
             return (code, data, content)
         return (code, data)
@@ -269,11 +283,13 @@ class Client(object):
 
         for l in capabilities.splitlines():
             parts = l.split(None, 1)
-            cname = parts[0].strip('"')
+            cname = parts[0].strip(b'"').decode("utf-8")
             if cname not in KNOWN_CAPABILITIES:
                 continue
-            self.__capabilities[cname] = \
-                parts[1].strip('"') if len(parts) > 1 else None
+            self.__capabilities[cname] = (
+                parts[1].strip(b'"').decode("utf-8")
+                if len(parts) > 1 else None
+            )
         return True
 
     def __parse_error(self, text):
@@ -291,7 +307,7 @@ class Client(object):
         """
         m = self.__size_expr.match(text)
         if m is not None:
-            self.errcode = ""
+            self.errcode = b""
             self.errmsg = self.__read_block(int(m.group(1)) + 2)
             return
 
@@ -299,12 +315,12 @@ class Client(object):
         if m is None:
             raise Error("Bad error message")
         if m.group(1) is not None:
-            self.errcode = m.group(1).strip("()")
+            self.errcode = m.group(1).strip(b"()")
         else:
-            self.errcode = ""
-        self.errmsg = m.group(2).strip('"')
+            self.errcode = b""
+        self.errmsg = m.group(2).strip(b'"')
 
-    def _plain_authentication(self, login, password, authz_id=""):
+    def _plain_authentication(self, login, password, authz_id=b""):
         """SASL PLAIN authentication
 
         :param login: username
@@ -315,8 +331,8 @@ class Client(object):
             login = login.encode("utf-8")
         if isinstance(login, six.text_type):
             password = password.encode("utf-8")
-        params = base64.b64encode('\0'.join([authz_id, login, password]))
-        code, data = self.__send_command("AUTHENTICATE", ["PLAIN", params])
+        params = base64.b64encode(b'\0'.join([authz_id, login, password]))
+        code, data = self.__send_command("AUTHENTICATE", [b"PLAIN", params])
         if code == "OK":
             return True
         return False
@@ -328,9 +344,9 @@ class Client(object):
         :param password: clear password
         :return: True on success, False otherwise.
         """
-        extralines = ['"%s"' % base64.b64encode(login),
-                      '"%s"' % base64.b64encode(password)]
-        code, data = self.__send_command("AUTHENTICATE", ["LOGIN"],
+        extralines = [b'"%s"' % base64.b64encode(login.encode("utf-8")),
+                      b'"%s"' % base64.b64encode(password.encode("utf-8"))]
+        code, data = self.__send_command("AUTHENTICATE", [b"LOGIN"],
                                          extralines=extralines)
         if code == "OK":
             return True
@@ -344,7 +360,7 @@ class Client(object):
         :return: True on success, False otherwise.
         """
         code, data, challenge = \
-            self.__send_command("AUTHENTICATE", ["DIGEST-MD5"],
+            self.__send_command("AUTHENTICATE", [b"DIGEST-MD5"],
                                 withcontent=True, nblines=1)
         dmd5 = DigestMD5(challenge, "sieve/%s" % self.srvaddr)
 
@@ -362,7 +378,7 @@ class Client(object):
             return True
         return False
 
-    def __authenticate(self, login, password, authz_id="", authmech=None):
+    def __authenticate(self, login, password, authz_id=b"", authmech=None):
         """AUTHENTICATE command
 
         Actually, it is just a wrapper to the real commands (one by
@@ -397,7 +413,7 @@ class Client(object):
                 return True
             return False
 
-        self.errmsg = "No suitable mechanism found"
+        self.errmsg = b"No suitable mechanism found"
         return False
 
     def __starttls(self, keyfile=None, certfile=None):
@@ -461,12 +477,13 @@ class Client(object):
 
         :rtype: string
         """
-        if type(self.__capabilities["SIEVE"]) == str:
+        if isinstance(self.__capabilities["SIEVE"], six.string_types):
             self.__capabilities["SIEVE"] = self.__capabilities["SIEVE"].split()
         return self.__capabilities["SIEVE"]
 
     def connect(
-            self, login, password, authz_id="", starttls=False, authmech=None):
+            self, login, password, authz_id=b"", starttls=False,
+            authmech=None):
         """Establish a connection with the server.
 
         This function must be used. It read the server capabilities
@@ -508,8 +525,8 @@ class Client(object):
 
         :rtype: string
         """
-        code, data, capabilities = \
-            self.__send_command("CAPABILITY", withcontent=True)
+        code, data, capabilities = (
+            self.__send_command("CAPABILITY", withcontent=True))
         if code == "OK":
             return capabilities
         return None
@@ -524,7 +541,8 @@ class Client(object):
         :param scriptsize: script's size
         :rtype: boolean
         """
-        code, data = self.__send_command("HAVESPACE", [scriptname, scriptsize])
+        code, data = self.__send_command(
+            "HAVESPACE", [scriptname.encode("utf-8"), scriptsize])
         if code == "OK":
             return True
         return False
@@ -546,14 +564,15 @@ class Client(object):
         for l in listing.splitlines():
             if self.__size_expr.match(l):
                 continue
-            m = re.match(r'"([^"]+)"\s*(.+)', l)
+            m = re.match(br'"([^"]+)"\s*(.+)', l)
             if m is None:
-                ret += [l.strip('"')]
-            else:
-                if self.__active_expr.match(m.group(2)):
-                    active_script = m.group(1)
-                else:
-                    ret += [m.group(1)]
+                ret += [l.strip(b'"').decode("utf-8")]
+                continue
+            script = m.group(1).decode("utf-8")
+            if self.__active_expr.match(m.group(2)):
+                active_script = script
+                continue
+            ret += [script]
         self.__dprint(ret)
         return (active_script, ret)
 
@@ -568,12 +587,12 @@ class Client(object):
         :returns: the script's content on succes, None otherwise
         """
         code, data, content = self.__send_command(
-            "GETSCRIPT", [name], withcontent=True)
+            "GETSCRIPT", [name.encode("utf-8")], withcontent=True)
         if code == "OK":
             lines = content.splitlines()
             if self.__size_expr.match(lines[0]) is not None:
                 lines = lines[1:]
-            return "\n".join(lines)
+            return u"\n".join([line.decode("utf-8") for line in lines])
         return None
 
     @authentication_required
@@ -586,12 +605,10 @@ class Client(object):
         :param content: script's content
         :rtype: boolean
         """
-        if type(content) is unicode:
-            content = content.encode("utf-8")
-
-        content = "{%d+}%s%s" % (len(content), CRLF, content)
-        code, data = \
-            self.__send_command("PUTSCRIPT", [name, content])
+        content = tools.to_bytes(content)
+        content = tools.to_bytes("{%d+}" % len(content)) + CRLF + content
+        code, data = (
+            self.__send_command("PUTSCRIPT", [name.encode("utf-8"), content]))
         if code == "OK":
             return True
         return False
@@ -605,7 +622,8 @@ class Client(object):
         :param name: script's name
         :rtype: boolean
         """
-        code, data = self.__send_command("DELETESCRIPT", [name])
+        code, data = self.__send_command(
+            "DELETESCRIPT", [name.encode("utf-8")])
         if code == "OK":
             return True
         return False
@@ -623,19 +641,24 @@ class Client(object):
         :param newname: new script's name
         :rtype: boolean
         """
-        if "RENAMESCRIPT" in self.__capabilities:
+        if "VERSION" in self.__capabilities:
             code, data = self.__send_command(
-                "RENAMESCRIPT", [oldname, newname])
+                "RENAMESCRIPT",
+                [oldname.encode("utf-8"), newname.encode("utf-8")])
             if code == "OK":
                 return True
             return False
 
         (active_script, scripts) = self.listscripts()
-        if scripts is None or oldname not in scripts:
-            self.errmsg = "Old script does not exist"
+        condition = (
+            oldname != active_script and
+            (scripts is None or oldname not in scripts)
+        )
+        if condition:
+            self.errmsg = b"Old script does not exist"
             return False
         if newname in scripts:
-            self.errmsg = "New script already exists"
+            self.errmsg = b"New script already exists"
             return False
         oldscript = self.getscript(oldname)
         if oldscript is None:
@@ -661,7 +684,8 @@ class Client(object):
         :param scriptname: script's name
         :rtype: boolean
         """
-        code, data = self.__send_command("SETACTIVE", [scriptname])
+        code, data = self.__send_command(
+            "SETACTIVE", [scriptname.encode("utf-8")])
         if code == "OK":
             return True
         return False
@@ -675,12 +699,12 @@ class Client(object):
         :param name: script's content
         :rtype: boolean
         """
-        if type(content) is unicode:
-            content = content.encode("utf-8")
-
-        content = "{%d+}%s%s" % (len(content), CRLF, content)
-        code, data = \
-            self.__send_command("CHECKSCRIPT", [content])
+        if "VERSION" not in self.__capabilities:
+            raise NotImplementedError(
+                "server does not support CHECKSCRIPT command")
+        content = tools.to_bytes(
+            u"{%d+}%s%s" % (len(content), str(CRLF), content))
+        code, data = self.__send_command("CHECKSCRIPT", [content])
         if code == "OK":
             return True
         return False
diff --git a/sievelib/parser.py b/sievelib/parser.py
index d75a188..137a9f9 100755
--- a/sievelib/parser.py
+++ b/sievelib/parser.py
@@ -10,11 +10,11 @@ This implementation is based on RFC 5228 (http://tools.ietf.org/html/rfc5228)
 """
 from __future__ import print_function
 
-import codecs
 import re
 import sys
 
-from future.utils import python_2_unicode_compatible
+from future.utils import python_2_unicode_compatible, text_type
+import six
 
 from sievelib.commands import (
     get_command_instance, UnknownCommand, BadArgument, BadValue
@@ -42,21 +42,24 @@ class Lexer(object):
     Patterns are provided into a list of 2-uple. Each 2-uple consists
     of a token name and an associated pattern, example:
 
-      [("left_bracket", r'\['),]
+      [(b"left_bracket", br'\['),]
     """
 
     def __init__(self, definitions):
         self.definitions = definitions
         parts = []
         for name, part in definitions:
-            parts.append("(?P<%s>%s)" % (name, part))
-        self.regexpString = "|".join(parts)
+            param = "(?P<%s>%s)" % (name.decode(), part.decode())
+            if six.PY3:
+                param = bytes(param, "utf-8")
+            parts.append(param)
+        self.regexpString = b"|".join(parts)
         self.regexp = re.compile(self.regexpString, re.MULTILINE)
-        self.wsregexp = re.compile(r'\s+', re.M)
+        self.wsregexp = re.compile(br'\s+', re.M)
 
     def curlineno(self):
         """Return the current line number"""
-        return self.text[:self.pos].count('\n') + 1
+        return self.text[:self.pos].count(b'\n') + 1
 
     def scan(self, text):
         """Analyse some data
@@ -67,7 +70,7 @@ class Lexer(object):
 
         On error, a ParseError exception is raised.
 
-        :param text: a string containing the data to parse
+        :param text: a binary string containing the data to parse
         """
         self.pos = 0
         self.text = text
@@ -92,21 +95,21 @@ class Parser(object):
     works with a Lexer object in order to check for grammar validity.
     """
     lrules = [
-        ("left_bracket", r'\['),
-        ("right_bracket", r'\]'),
-        ("left_parenthesis", r'\('),
-        ("right_parenthesis", r'\)'),
-        ("left_cbracket", r'{'),
-        ("right_cbracket", r'}'),
-        ("semicolon", r';'),
-        ("comma", r','),
-        ("hash_comment", r'#.*$'),
-        ("bracket_comment", r'/\*[\s\S]*?\*/'),
-        ("multiline", r'text:[^$]*?[\r\n]+\.$'),
-        ("string", r'"([^"\\]|\\.)*"'),
-        ("identifier", r'[a-zA-Z_][\w]*'),
-        ("tag", r':[a-zA-Z_][\w]*'),
-        ("number", r'[0-9]+[KMGkmg]?'),
+        (b"left_bracket", br'\['),
+        (b"right_bracket", br'\]'),
+        (b"left_parenthesis", br'\('),
+        (b"right_parenthesis", br'\)'),
+        (b"left_cbracket", br'{'),
+        (b"right_cbracket", br'}'),
+        (b"semicolon", br';'),
+        (b"comma", br','),
+        (b"hash_comment", br'#.*$'),
+        (b"bracket_comment", br'/\*[\s\S]*?\*/'),
+        (b"multiline", br'text:[^$]*?[\r\n]+\.$'),
+        (b"string", br'"([^"\\]|\\.)*"'),
+        (b"identifier", br'[a-zA-Z_][\w]*'),
+        (b"tag", br':[a-zA-Z_][\w]*'),
+        (b"number", br'[0-9]+[KMGkmg]?'),
     ]
 
     def __init__(self, debug=False):
@@ -223,7 +226,7 @@ class Parser(object):
                             ; are optional
         """
         if ttype == "string":
-            self.__curstringlist += [tvalue]
+            self.__curstringlist += [tvalue.decode("utf-8")]
             self.__set_expected("comma", "right_bracket")
             return True
         if ttype == "comma":
@@ -247,11 +250,11 @@ class Parser(object):
         :param tvalue: current token value
         :return: False if an error is encountered, True otherwise
         """
-        if ttype == "multiline":
-            return self.__curcommand.check_next_arg("string", tvalue)
+        if ttype in ["multiline", "string"]:
+            return self.__curcommand.check_next_arg("string", tvalue.decode("utf-8"))
 
-        if ttype in ["number", "tag", "string"]:
-            return self.__curcommand.check_next_arg(ttype, tvalue)
+        if ttype in ["number", "tag"]:
+            return self.__curcommand.check_next_arg(ttype, tvalue.decode("ascii"))
 
         if ttype == "left_bracket":
             self.__cstate = self.__stringlist
@@ -275,7 +278,7 @@ class Parser(object):
         :return: False if an error is encountered, True otherwise
         """
         if ttype == "identifier":
-            test = get_command_instance(tvalue, self.__curcommand)
+            test = get_command_instance(tvalue.decode("ascii"), self.__curcommand)
             self.__curcommand.check_next_arg("test", test)
             self.__expected = test.get_expected_first()
             self.__curcommand = test
@@ -323,7 +326,7 @@ class Parser(object):
 
             if ttype != "identifier":
                 return False
-            command = get_command_instance(tvalue, self.__curcommand)
+            command = get_command_instance(tvalue.decode("ascii"), self.__curcommand)
             if command.get_type() == "test":
                 raise ParseError("%s may not appear as a first command" % command.name)
             if command.get_type() == "control" and command.accept_children \
@@ -370,6 +373,9 @@ class Parser(object):
         :param text: a string containing the data to parse
         :return: True on success (no error detected), False otherwise
         """
+        if isinstance(text, text_type):
+            text = text.encode("utf-8")
+
         self.__reset_parser()
         try:
             for ttype, tvalue in self.lexer.scan(text):
@@ -412,10 +418,9 @@ class Parser(object):
         :param name: the pathname of the file to parse
         :return: True on success (no error detected), False otherwise
         """
-        fp = codecs.open(name, encoding='utf8')
-        content = fp.read()
-        fp.close()
-        return self.parse(content)
+        with open(name, "rb") as fp:
+            return self.parse(fp.read())
+        
 
     def dump(self, target=sys.stdout):
         """Dump the parsing tree.
diff --git a/sievelib/tests/files/utf8_sieve.txt b/sievelib/tests/files/utf8_sieve.txt
new file mode 100644
index 0000000..2dcc959
--- /dev/null
+++ b/sievelib/tests/files/utf8_sieve.txt
@@ -0,0 +1,7 @@
+require ["fileinto", "reject"];
+
+# Filter: UTF8 Test Filter äöüß 汉语/漢語 Hànyǔ
+if allof (header :contains ["Subject"] ["€ 300"]) {
+    fileinto "Spam";
+    stop;
+}
diff --git a/sievelib/tests/test_managesieve.py b/sievelib/tests/test_managesieve.py
new file mode 100644
index 0000000..fe9acbe
--- /dev/null
+++ b/sievelib/tests/test_managesieve.py
@@ -0,0 +1,166 @@
+# coding: utf-8
+
+"""Managesieve test cases."""
+
+import unittest
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+from sievelib import managesieve
+
+CAPABILITIES = (
+    b'"IMPLEMENTATION" "Example1 ManageSieved v001"\r\n'
+    b'"VERSION" "1.0"\r\n'
+    b'"SASL" "PLAIN SCRAM-SHA-1 GSSAPI"\r\n'
+    b'"SIEVE" "fileinto vacation"\r\n'
+    b'"STARTTLS"\r\n'
+)
+
+CAPABILITIES_WITHOUT_VERSION = (
+    b'"IMPLEMENTATION" "Example1 ManageSieved v001"\r\n'
+    b'"SASL" "PLAIN SCRAM-SHA-1 GSSAPI"\r\n'
+    b'"SIEVE" "fileinto vacation"\r\n'
+    b'"STARTTLS"\r\n'
+)
+
+AUTHENTICATION = (
+    CAPABILITIES +
+    b'OK "Dovecot ready."\r\n'
+    b'OK "Logged in."\r\n'
+)
+
+LISTSCRIPTS = (
+    b'"summer_script"\r\n'
+    b'"vac\xc3\xa0tion_script"\r\n'
+    b'{13}\r\n'
+    b'clever"script\r\n'
+    b'"main_script" ACTIVE\r\n'
+    b'OK "Listscripts completed."\r\n'
+)
+
+GETSCRIPT = (
+    b'{54}\r\n'
+    b'#this is my wonderful script\r\n'
+    b'reject "I reject all";\r\n'
+    b'OK "Getscript completed."\r\n'
+)
+
+
+ at mock.patch("socket.socket")
+class ManageSieveTestCase(unittest.TestCase):
+    """Managesieve test cases."""
+
+    def setUp(self):
+        """Create client."""
+        self.client = managesieve.Client("127.0.0.1")
+
+    def authenticate(self, mock_socket):
+        """Authenticate client."""
+        mock_socket.return_value.recv.side_effect = (AUTHENTICATION, )
+        self.client.connect(b"user", b"password")
+
+    def test_connection(self, mock_socket):
+        """Test connection."""
+        self.authenticate(mock_socket)
+        self.assertEqual(
+            self.client.get_sieve_capabilities(), ["fileinto", "vacation"])
+        mock_socket.return_value.recv.side_effect = (b"OK test\r\n", )
+        self.client.logout()
+
+    def test_capabilities(self, mock_socket):
+        """Test capabilities command."""
+        self.authenticate(mock_socket)
+        mock_socket.return_value.recv.side_effect = (
+            CAPABILITIES + b'OK "Capability completed."\r\n', )
+        capabilities = self.client.capability()
+        self.assertEqual(capabilities, CAPABILITIES)
+
+    def test_listscripts(self, mock_socket):
+        """Test listscripts command."""
+        self.authenticate(mock_socket)
+        mock_socket.return_value.recv.side_effect = (LISTSCRIPTS, )
+        active_script, others = self.client.listscripts()
+        self.assertEqual(active_script, "main_script")
+        self.assertEqual(
+            others, [u'summer_script', u'vacàtion_script', u'clever"script'])
+
+    def test_getscript(self, mock_socket):
+        """Test getscript command."""
+        self.authenticate(mock_socket)
+        mock_socket.return_value.recv.side_effect = (GETSCRIPT, )
+        content = self.client.getscript("main_script")
+        self.assertEqual(
+            content, u'#this is my wonderful script\nreject "I reject all";')
+
+    def test_putscript(self, mock_socket):
+        """Test putscript command."""
+        self.authenticate(mock_socket)
+        script = """require ["fileinto"];
+
+if envelope :contains "to" "tmartin+sent" {
+  fileinto "INBOX.sent";
+}
+"""
+        mock_socket.return_value.recv.side_effect = (
+            b'OK "putscript completed."\r\n', )
+        self.assertTrue(self.client.putscript(u"test_script", script))
+
+    def test_deletescript(self, mock_socket):
+        """Test deletescript command."""
+        self.authenticate(mock_socket)
+        mock_socket.return_value.recv.side_effect = (
+            b'OK "deletescript completed."\r\n', )
+        self.assertTrue(self.client.deletescript(u"test_script"))
+
+    def test_checkscript(self, mock_socket):
+        """Test checkscript command."""
+        self.authenticate(mock_socket)
+        mock_socket.return_value.recv.side_effect = (
+            b'OK "checkscript completed."\r\n', )
+        script = "#comment\r\nInvalidSieveCommand\r\n"
+        self.assertTrue(self.client.checkscript(script))
+
+    def test_setactive(self, mock_socket):
+        """Test setactive command."""
+        self.authenticate(mock_socket)
+        mock_socket.return_value.recv.side_effect = (
+            b'OK "setactive completed."\r\n', )
+        self.assertTrue(self.client.setactive(u"test_script"))
+
+    def test_havespace(self, mock_socket):
+        """Test havespace command."""
+        self.authenticate(mock_socket)
+        mock_socket.return_value.recv.side_effect = (
+            b'OK "havespace completed."\r\n', )
+        self.assertTrue(self.client.havespace(u"test_script", 1000))
+
+    def test_renamescript(self, mock_socket):
+        """Test renamescript command."""
+        self.authenticate(mock_socket)
+        mock_socket.return_value.recv.side_effect = (
+            b'OK "renamescript completed."\r\n', )
+        self.assertTrue(self.client.renamescript(u"old_script", u"new_script"))
+
+    def test_renamescript_simulated(self, mock_socket):
+        """Test renamescript command simulation."""
+        mock_socket.return_value.recv.side_effect = (
+            CAPABILITIES_WITHOUT_VERSION +
+            b'OK "Dovecot ready."\r\n'
+            b'OK "Logged in."\r\n',
+        )
... 459 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-sievelib.git



More information about the Python-modules-commits mailing list