[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