Branch 'pykolab-0.5' - 13 commits - configure.ac pykolab/cli pykolab/imap pykolab/imap_utf7.py pykolab/xml wallace/module_resources.py
Jeroen van Meeuwen
vanmeeuwen at kolabsys.com
Mon Jul 15 18:31:17 CEST 2013
configure.ac | 4 -
pykolab/cli/cmd_create_mailbox.py | 4 -
pykolab/cli/cmd_delete_mailbox.py | 4 +
pykolab/cli/cmd_list_mailbox_acls.py | 2
pykolab/cli/cmd_list_mailbox_metadata.py | 2
pykolab/cli/cmd_list_mailboxes.py | 3 -
pykolab/cli/cmd_list_quota.py | 12 +++-
pykolab/cli/cmd_set_mailbox_acl.py | 2
pykolab/cli/cmd_undelete_mailbox.py | 2
pykolab/imap/__init__.py | 51 +++++++++++------
pykolab/imap/cyrus.py | 10 +++
pykolab/imap_utf7.py | 91 +++++++++++++++++++++++++++++++
pykolab/xml/event.py | 1
wallace/module_resources.py | 2
14 files changed, 158 insertions(+), 32 deletions(-)
New commits:
commit fa65938b3b1e8efac5a01987fce44eae20a1079c
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Tue Jul 9 14:49:04 2013 +0100
Make sure events hold the required X-Kolab-MIME-Version: 3.0 header
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index b92339a..80cee77 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -799,6 +799,7 @@ class Event(object):
msg['To'] = ', '.join([x.__str__() for x in self.get_attendees()])
msg['Date'] = formatdate(localtime=True)
+ msg.add_header('X-Kolab-MIME-Version', '3.0')
msg.add_header('X-Kolab-Type', 'application/x-vnd.kolab.event')
text = utils.multiline_message("""
commit 84f8fc974e7847ab325fbefa40041d97fe5cc216
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Sat Jul 6 11:06:48 2013 +0100
Use utf-7 names when deleting folders by wildcard match as well
diff --git a/pykolab/imap/cyrus.py b/pykolab/imap/cyrus.py
index 079c4dc..9a8e97c 100644
--- a/pykolab/imap/cyrus.py
+++ b/pykolab/imap/cyrus.py
@@ -193,6 +193,14 @@ class Cyrus(cyruslib.CYRUS):
return server
+ def folder_utf7(self, folder):
+ from pykolab import imap_utf7
+ return imap_utf7.encode(folder)
+
+ def folder_utf8(self, folder):
+ from pykolab import imap_utf7
+ return imap_utf7.decode(folder)
+
def _setquota(self, mailfolder, quota):
"""
Login to the actual backend server.
@@ -355,7 +363,7 @@ class Cyrus(cyruslib.CYRUS):
if not mbox['domain'] == None:
verify_folder_search = "%s@%s" % (verify_folder_search, mbox['domain'])
- folders = self.lm(verify_folder_search)
+ folders = self.lm(self.folder_utf7(verify_folder_search))
# NOTE: Case also covered is valid hexadecimal folders; won't be the
# actual check as intended, but doesn't give you anyone else's data
commit a35735afa63e61fd49d7cc80f0001988414edbaa
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 21:48:22 2013 +0100
0.5.14
diff --git a/configure.ac b/configure.ac
index 032cd8d..6468fc5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,4 @@
-AC_INIT([pykolab], 0.5.13)
+AC_INIT([pykolab], 0.5.14)
AC_SUBST([RELEASE], 1)
AC_CONFIG_SRCDIR(pykolab/constants.py.in)
commit 3491b74df4ef6010f2f3df223ca2c3d421cb3d51
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 20:22:33 2013 +0100
Fix the utf7 backend versus preferred utf8 display
diff --git a/pykolab/cli/cmd_delete_mailbox.py b/pykolab/cli/cmd_delete_mailbox.py
index 51cbe69..47352d2 100644
--- a/pykolab/cli/cmd_delete_mailbox.py
+++ b/pykolab/cli/cmd_delete_mailbox.py
@@ -53,5 +53,5 @@ def execute(*args, **kw):
delete_folders = imap.list_folders(delete_folder)
for delete_folder in delete_folders:
- imap.delete_mailfolder(imap.folder_utf8(delete_folder))
+ imap.delete_mailfolder(delete_folder)
diff --git a/pykolab/cli/cmd_list_mailbox_acls.py b/pykolab/cli/cmd_list_mailbox_acls.py
index 62bac4f..5d7e04e 100644
--- a/pykolab/cli/cmd_list_mailbox_acls.py
+++ b/pykolab/cli/cmd_list_mailbox_acls.py
@@ -55,7 +55,7 @@ def execute(*args, **kw):
else:
acls = []
- folders = imap.lm(folder)
+ folders = imap.list_folders(folder)
for folder in folders:
print "Folder", folder
acls = imap.list_acls(folder)
diff --git a/pykolab/cli/cmd_list_mailbox_metadata.py b/pykolab/cli/cmd_list_mailbox_metadata.py
index b430896..17b8c02 100644
--- a/pykolab/cli/cmd_list_mailbox_metadata.py
+++ b/pykolab/cli/cmd_list_mailbox_metadata.py
@@ -81,7 +81,7 @@ def execute(*args, **kw):
else:
metadata = []
- folders = imap.lm(folder)
+ folders = imap.list_folders(folder)
for folder in folders:
print "Folder", folder
diff --git a/pykolab/cli/cmd_list_quota.py b/pykolab/cli/cmd_list_quota.py
index c81725a..1916631 100644
--- a/pykolab/cli/cmd_list_quota.py
+++ b/pykolab/cli/cmd_list_quota.py
@@ -50,7 +50,7 @@ def execute(*args, **kw):
folders = []
- quota_folders = imap.lm(quota_folder)
+ quota_folders = imap.list_folders(quota_folder)
for quota_folder in quota_folders:
try:
(used, quota) = imap.get_quota(quota_folder)
@@ -62,7 +62,10 @@ def execute(*args, **kw):
percentage = round(((float)(used)/(float)(quota)) * 100.0, 1)
print "%d (Used: %d, Percentage: %d)" % (quota, used, percentage)
else:
- print "No quota"
+ if used == None:
+ print "%d (Used: %d, Percentage: %d)" % (quota, 0, 0)
+ else:
+ print "No quota"
except:
try:
(quota_root, used, quota) = imap.get_quota_root(quota_folder)
@@ -74,7 +77,10 @@ def execute(*args, **kw):
percentage = round(((float)(used)/(float)(quota)) * 100.0, 1)
print "%d (Root: %s, Used: %d, Percentage: %d)" % (quota, quota_root, used, percentage)
else:
- print "No quota"
+ if used == None and not quota_root == None:
+ print "%d (Root: %s, Used: %d, Percentage: %d)" % (quota, quota_root, 0, 0)
+ else:
+ print "No quota"
except:
print "No quota root"
diff --git a/pykolab/cli/cmd_set_mailbox_acl.py b/pykolab/cli/cmd_set_mailbox_acl.py
index 8ba4567..0c76667 100644
--- a/pykolab/cli/cmd_set_mailbox_acl.py
+++ b/pykolab/cli/cmd_set_mailbox_acl.py
@@ -67,6 +67,6 @@ def execute(*args, **kw):
print >> sys.stderr, _("No such folder %r") % (folder)
else:
- folders = imap.lm(folder)
+ folders = imap.list_folders(folder)
for folder in folders:
imap.set_acl(folder, identifier, acl)
diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index a9ab38d..15eca90 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -225,8 +225,14 @@ class IMAP(object):
"""
Obtain all metadata entries on a folder
"""
+ metadata = {}
- return self.imap.getannotation(folder, '*')
+ _metadata = self.imap.getannotation(self.folder_utf7(folder), '*')
+
+ for (k,v) in _metadata.items():
+ metadata[self.folder_utf8(k)] = v
+
+ return metadata
def namespaces(self):
"""
@@ -269,7 +275,7 @@ class IMAP(object):
Set an ACL entry on a folder.
"""
- self.imap.sam(folder, identifier, acl)
+ self.imap.sam(self.folder_utf7(folder), identifier, acl)
def set_metadata(self, folder, metadata_path, metadata_value, shared=True):
"""
@@ -541,7 +547,7 @@ class IMAP(object):
Check if the environment has a folder named folder.
"""
folders = self.imap.lm(self.folder_utf7(folder))
- log.debug(_("Looking for folder '%s', we found folders: %r") % (folder,folders), level=8)
+ log.debug(_("Looking for folder '%s', we found folders: %r") % (folder,[self.folder_utf8(x) for x in folders]), level=8)
# Greater then one, this folder may have subfolders.
if len(folders) > 0:
return True
@@ -811,10 +817,10 @@ class IMAP(object):
"""
List the ACL entries on a folder
"""
- return self.imap.lam(folder)
+ return self.imap.lam(self.folder_utf7(folder))
def list_folders(self, pattern):
- return self.lm(self.folder_utf7(pattern))
+ return [self.folder_utf8(x) for x in self.lm(self.folder_utf7(pattern))]
def list_user_folders(self, primary_domain=None, secondary_domains=[]):
"""
commit 6ffd3d291d73e778338525c48ba2572d5b4f969a
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 19:50:38 2013 +0100
Make sure the pattern for deleting mailboxes is passed on right between various function calls
diff --git a/pykolab/cli/cmd_delete_mailbox.py b/pykolab/cli/cmd_delete_mailbox.py
index 0978b7e..51cbe69 100644
--- a/pykolab/cli/cmd_delete_mailbox.py
+++ b/pykolab/cli/cmd_delete_mailbox.py
@@ -49,7 +49,9 @@ def execute(*args, **kw):
imap = IMAP()
imap.connect()
- delete_folders = imap.lm(delete_folder)
+
+ delete_folders = imap.list_folders(delete_folder)
+
for delete_folder in delete_folders:
- imap.delete_mailfolder(delete_folder)
+ imap.delete_mailfolder(imap.folder_utf8(delete_folder))
diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index 6933adb..a9ab38d 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -217,6 +217,10 @@ class IMAP(object):
from pykolab import imap_utf7
return imap_utf7.encode(folder)
+ def folder_utf8(self, folder):
+ from pykolab import imap_utf7
+ return imap_utf7.decode(folder)
+
def get_metadata(self, folder):
"""
Obtain all metadata entries on a folder
@@ -792,16 +796,16 @@ class IMAP(object):
log.info(_("Deleting folder %s") % (mailfolder_path))
- self.imap.dm(mailfolder_path)
+ self.imap.dm(self.folder_utf7(mailfolder_path))
def get_quota(self, mailfolder_path):
try:
- return self.lq(mailfolder_path)
+ return self.lq(self.folder_utf7(mailfolder_path))
except:
return
def get_quota_root(self, mailfolder_path):
- return self.lqr(mailfolder_path)
+ return self.lqr(self.folder_utf7(mailfolder_path))
def list_acls(self, folder):
"""
@@ -809,6 +813,9 @@ class IMAP(object):
"""
return self.imap.lam(folder)
+ def list_folders(self, pattern):
+ return self.lm(self.folder_utf7(pattern))
+
def list_user_folders(self, primary_domain=None, secondary_domains=[]):
"""
List the INBOX folders in the IMAP backend. Returns a list of unique
commit 26a08fdcc8746650e279770ecf52077d96a0821f
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 19:38:56 2013 +0100
If a string isn't unicode, attempt to make it so
diff --git a/pykolab/imap_utf7.py b/pykolab/imap_utf7.py
index 6a670b5..038623b 100644
--- a/pykolab/imap_utf7.py
+++ b/pykolab/imap_utf7.py
@@ -29,7 +29,10 @@ class FolderNameError(ValueError):
def encode(s):
if isinstance(s, str) and sum(n for n in (ord(c) for c in s) if n > 127):
- raise FolderNameError("%r contains characters not valid in a str folder name. "
+ try:
+ s = unicode(s, "UTF-8")
+ except Exception, errmsg:
+ raise FolderNameError("%r contains characters not valid in a str folder name. "
"Convert to unicode first?" % s)
r = []
@@ -49,6 +52,7 @@ def encode(s):
_in.append(c)
if _in:
r.extend(['&', modified_base64(''.join(_in)), '-'])
+
return ''.join(r)
commit ae26c04fefaf11e16601348099952b90bdf5bfc8
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 19:38:34 2013 +0100
Decode the mailbox names in kolab list-mailboxes
diff --git a/pykolab/cli/cmd_list_mailboxes.py b/pykolab/cli/cmd_list_mailboxes.py
index 3451e7c..13d5fd7 100644
--- a/pykolab/cli/cmd_list_mailboxes.py
+++ b/pykolab/cli/cmd_list_mailboxes.py
@@ -21,6 +21,7 @@ import commands
import pykolab
+from pykolab import imap_utf7
from pykolab.imap import IMAP
from pykolab.translate import _
@@ -76,4 +77,4 @@ def execute(*args, **kw):
folders.extend(imap.lm(search))
for folder in folders:
- print folder
+ print imap_utf7.decode(folder)
commit f654d9d51a245a6fc3f6b0ff5412f442cbe8c99a
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 19:26:32 2013 +0100
Use imap.create_folder() for kolab cm
diff --git a/pykolab/cli/cmd_create_mailbox.py b/pykolab/cli/cmd_create_mailbox.py
index 5510e56..ff1bc6a 100644
--- a/pykolab/cli/cmd_create_mailbox.py
+++ b/pykolab/cli/cmd_create_mailbox.py
@@ -63,9 +63,7 @@ def execute(*args, **kw):
imap = IMAP()
imap.connect()
- admin_login = conf.get('cyrus-imap', 'admin_login')
-
- imap.cm(mailbox)
+ imap.create_folder(mailbox)
if not conf.metadata == None:
imap.set_metadata(mailbox, conf.metadata.split('=')[0], conf.metadata.split('=')[1])
commit cac4578a364762b7b3240acc1e04f23c14d02ade
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 18:08:52 2013 +0100
pykolab 0.5.13
diff --git a/configure.ac b/configure.ac
index f02ee59..032cd8d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,5 +1,5 @@
-AC_INIT([pykolab], 0.5.12)
-AC_SUBST([RELEASE], 2)
+AC_INIT([pykolab], 0.5.13)
+AC_SUBST([RELEASE], 1)
AC_CONFIG_SRCDIR(pykolab/constants.py.in)
commit 67813db2f3fce7ae37c763fd1baf64f38a1709c8
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 18:07:48 2013 +0100
Correct IMAP not being loaded / initialized
diff --git a/pykolab/cli/cmd_undelete_mailbox.py b/pykolab/cli/cmd_undelete_mailbox.py
index 316e164..6cac2b0 100644
--- a/pykolab/cli/cmd_undelete_mailbox.py
+++ b/pykolab/cli/cmd_undelete_mailbox.py
@@ -21,6 +21,7 @@ import commands
import pykolab
+from pykolab.imap import IMAP
from pykolab.translate import _
log = pykolab.getLogger('pykolab.cli')
@@ -40,6 +41,7 @@ def execute(*args, **kw):
if len(conf.cli_args) > 0:
target_folder = conf.cli_args.pop(0)
+ imap = IMAP()
imap.connect()
imap.undelete_mailfolder(undelete_folder, target_folder)
commit 64196a670ccd3efbbf968d64a7b6fce00b8c5bdf
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 18:06:16 2013 +0100
Correct the resulting message in message_from_string not detecting the message is multipart
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index 5a66dda..3f34105 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -127,7 +127,7 @@ def execute(*args, **kw):
_message = json.load(open(filepath, 'r'))
log.debug("Loaded message %r" % (_message), level=9)
- message = message_from_string(_message['data'])
+ message = message_from_string(str(_message['data']))
recipients = _message['to']
any_itips = False
commit 9c5a9c6d076317b4ab12a8c7da0a44984bf65dd1
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 18:03:08 2013 +0100
Correct conversion to utf7
diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index 5e72769..6933adb 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -197,6 +197,8 @@ class IMAP(object):
log.warning(_("Called imap.disconnect() on a server that we had no connection to."))
def create_folder(self, folder_path, server=None):
+ folder_path = self.folder_utf7(folder_path)
+
if not server == None:
if not self._imap.has_key(server):
self.connect(server=server)
@@ -213,7 +215,7 @@ class IMAP(object):
def folder_utf7(self, folder):
from pykolab import imap_utf7
- return imap_utf7.encode(folder_path)
+ return imap_utf7.encode(folder)
def get_metadata(self, folder):
"""
commit db06665be4e8356208c975b87fb1f6750a1e842f
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date: Fri Jul 5 17:09:53 2013 +0100
Make sure folder names are utf-7 encoded
Conflicts:
pykolab/imap/__init__.py
diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index c91d84f..5e72769 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -211,6 +211,10 @@ class IMAP(object):
else:
raise AttributeError, _("%r has no attribute %s") % (self,name)
+ def folder_utf7(self, folder):
+ from pykolab import imap_utf7
+ return imap_utf7.encode(folder_path)
+
def get_metadata(self, folder):
"""
Obtain all metadata entries on a folder
@@ -275,10 +279,7 @@ class IMAP(object):
shared = False
metadata_path = metadata_path.replace('/private/', '/')
- if not shared:
- log.warning(_("Private annotations need to be set using the appropriate user account."))
-
- self.imap._setannotation(folder, metadata_path, metadata_value, shared)
+ self.imap._setannotation(self.folder_utf7(folder), metadata_path, metadata_value, shared)
def shared_folder_create(self, folder_path, server=None):
"""
@@ -402,9 +403,10 @@ class IMAP(object):
folder_name = additional_folder
try:
- self.imap.cm(folder_name)
+ self.create_folder(folder_name)
except:
log.warning(_("Mailbox already exists: %s") % (folder_name))
+ continue
if additional_folders[additional_folder].has_key("annotations"):
for annotation in additional_folders[additional_folder]["annotations"].keys():
@@ -532,7 +534,7 @@ class IMAP(object):
"""
Check if the environment has a folder named folder.
"""
- folders = self.imap.lm(folder)
+ folders = self.imap.lm(self.folder_utf7(folder))
log.debug(_("Looking for folder '%s', we found folders: %r") % (folder,folders), level=8)
# Greater then one, this folder may have subfolders.
if len(folders) > 0:
@@ -559,8 +561,8 @@ class IMAP(object):
_("Setting ACL rights %s for subject %s on folder " + \
"%s") % (rights,subject,folder), level=8)
- self.imap.sam(
- folder,
+ self.set_acl(
+ self.folder_utf7(folder),
"%s" % (subject),
"%s" % (rights)
)
@@ -570,8 +572,8 @@ class IMAP(object):
_("Removing ACL rights %s for subject %s on folder " + \
"%s") % (rights,subject,folder), level=8)
- self.imap.sam(
- folder,
+ self.set_acl(
+ self.folder_utf7(folder),
"%s" % (subject),
""
)
diff --git a/pykolab/imap_utf7.py b/pykolab/imap_utf7.py
new file mode 100644
index 0000000..6a670b5
--- /dev/null
+++ b/pykolab/imap_utf7.py
@@ -0,0 +1,87 @@
+# The contents of this file has been derived code from the Twisted project
+# (http://twistedmatrix.com/). The original author is Jp Calderone.
+
+# Twisted project license follows:
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+class FolderNameError(ValueError):
+ pass
+
+
+def encode(s):
+ if isinstance(s, str) and sum(n for n in (ord(c) for c in s) if n > 127):
+ raise FolderNameError("%r contains characters not valid in a str folder name. "
+ "Convert to unicode first?" % s)
+
+ r = []
+ _in = []
+ for c in s:
+ if ord(c) in (range(0x20, 0x26) + range(0x27, 0x7f)):
+ if _in:
+ r.extend(['&', modified_base64(''.join(_in)), '-'])
+ del _in[:]
+ r.append(str(c))
+ elif c == '&':
+ if _in:
+ r.extend(['&', modified_base64(''.join(_in)), '-'])
+ del _in[:]
+ r.append('&-')
+ else:
+ _in.append(c)
+ if _in:
+ r.extend(['&', modified_base64(''.join(_in)), '-'])
+ return ''.join(r)
+
+
+def decode(s):
+ r = []
+ decode = []
+ for c in s:
+ if c == '&' and not decode:
+ decode.append('&')
+ elif c == '-' and decode:
+ if len(decode) == 1:
+ r.append('&')
+ else:
+ r.append(modified_unbase64(''.join(decode[1:])))
+ decode = []
+ elif decode:
+ decode.append(c)
+ else:
+ r.append(c)
+ if decode:
+ r.append(modified_unbase64(''.join(decode[1:])))
+ out = ''.join(r)
+
+ if not isinstance(out, unicode):
+ out = unicode(out, 'latin-1')
+ return out
+
+
+def modified_base64(s):
+ s_utf7 = s.encode('utf-7')
+ return s_utf7[1:-1].replace('/', ',')
+
+
+def modified_unbase64(s):
+ s_utf7 = '+' + s.replace(',', '/') + '-'
+ return s_utf7.decode('utf-7')
More information about the commits
mailing list