Ankush Sharma has proposed merging
lp:~black-perl/mailman.client/handling-special-chars-in-email into
lp:mailman.client.
Requested reviews:
Mailman Coders (mailman-coders)
Related bugs:
Bug #1429366 in GNU Mailman: "Anatomy of list ids does not keep with that of
urls causes some REST end points to return 404 always"
https://bugs.launchpad.net/mailman/+bug/1429366
For more details, see:
https://code.launchpad.net/~black-perl/mailman.client/handling-special-chars-in-email/+merge/252854
As discussed in the bug report, use of emails with special character set is
valid as per email RFCs but postorius crashes on using them giving 404 or
KeyError always.
As discussed on the mailing list a possible solution would be to percent encode
these special characters when they appear in the list_id or fqdn_listname
before sending a request to the REST server and decoding on the other end.
Added utils.py which has to functions `encode` and `encode_url` to faciliate
encoding of list_id's in URLs when required.
The use of special characters in list_ids is working properly
http://oi58.tinypic.com/33u689i.jpg.
--
Your team Mailman Coders is requested to review the proposed merge of
lp:~black-perl/mailman.client/handling-special-chars-in-email into
lp:mailman.client.
=== modified file 'src/mailmanclient/_client.py'
--- src/mailmanclient/_client.py 2015-01-04 22:43:40 +0000
+++ src/mailmanclient/_client.py 2015-03-13 03:38:44 +0000
@@ -35,6 +35,8 @@
from six.moves.urllib_error import HTTPError
from six.moves.urllib_parse import urlencode, urljoin
+from .utils import encode,encode_url
+
DEFAULT_PAGE_ITEM_COUNT = 50
@@ -247,13 +249,15 @@
return _Address(self._connection, content)
def get_list(self, fqdn_listname):
+ enc_fqdn_listname = encode(fqdn_listname)
response, content = self._connection.call(
- 'lists/{0}'.format(fqdn_listname))
+ 'lists/{0}'.format(enc_fqdn_listname))
return _List(self._connection, content['self_link'], content)
def delete_list(self, fqdn_listname):
+ enc_fqdn_listname = encode(fqdn_listname)
response, content = self._connection.call(
- 'lists/{0}'.format(fqdn_listname), None, 'DELETE')
+ 'lists/{0}'.format(enc_fqdn_listname), None, 'DELETE')
class _Domain:
@@ -319,7 +323,7 @@
def __init__(self, connection, url, data=None):
self._connection = connection
- self._url = url
+ self._url = encode_url(url)
self._info = data
def __repr__(self):
@@ -330,6 +334,13 @@
response, content = self._connection.call(self._url)
self._info = content
+ def _get_fqdn_listname(self):
+ """
+ Returns fqdn_listname in encoded form
+ """
+ self._get_info()
+ return encode(self._info['fqdn_listname'])
+
@property
def owners(self):
url = self._url + '/roster/owner'
@@ -375,7 +386,7 @@
@property
def members(self):
- url = 'lists/{0}/roster/member'.format(self.fqdn_listname)
+ url = 'lists/{0}/roster/member'.format(self._get_fqdn_listname())
response, content = self._connection.call(url)
if 'entries' not in content:
return []
@@ -398,19 +409,19 @@
key=itemgetter('address'))]
def get_member_page(self, count=50, page=1):
- url = 'lists/{0}/roster/member'.format(self.fqdn_listname)
+ url = 'lists/{0}/roster/member'.format(self._get_fqdn_listname())
return _Page(self._connection, url, _Member, count, page)
@property
def settings(self):
return _Settings(self._connection,
- 'lists/{0}/config'.format(self.fqdn_listname))
+ 'lists/{0}/config'.format(self._get_fqdn_listname()))
@property
def held(self):
"""Return a list of dicts with held message information."""
response, content = self._connection.call(
- 'lists/{0}/held'.format(self.fqdn_listname), None, 'GET')
+ 'lists/{0}/held'.format(self._get_fqdn_listname()), None, 'GET')
if 'entries' not in content:
return []
else:
@@ -429,7 +440,7 @@
def requests(self):
"""Return a list of dicts with subscription requests."""
response, content = self._connection.call(
- 'lists/{0}/requests'.format(self.fqdn_listname), None, 'GET')
+ 'lists/{0}/requests'.format(self._get_fqdn_listname()), None, 'GET')
if 'entries' not in content:
return []
else:
@@ -465,7 +476,7 @@
self.remove_role('moderator', address)
def remove_role(self, role, address):
- url = 'lists/%s/%s/%s' % (self.fqdn_listname, role, address)
+ url = 'lists/%s/%s/%s' % (self._get_fqdn_listname(), role, address)
self._connection.call(url, method='DELETE')
def moderate_message(self, request_id, action):
@@ -477,7 +488,7 @@
:type action: String.
"""
path = 'lists/{0}/held/{1}'.format(
- self.fqdn_listname, str(request_id))
+ self._get_fqdn_listname(), str(request_id))
response, content = self._connection.call(
path, dict(action=action), 'POST')
return response
@@ -548,7 +559,7 @@
def delete(self):
response, content = self._connection.call(
- 'lists/{0}'.format(self.fqdn_listname), None, 'DELETE')
+ 'lists/{0}'.format(self._get_fqdn_listname()), None, 'DELETE')
class _Member:
=== added file 'src/mailmanclient/utils.py'
--- src/mailmanclient/utils.py 1970-01-01 00:00:00 +0000
+++ src/mailmanclient/utils.py 2015-03-13 03:38:44 +0000
@@ -0,0 +1,37 @@
+"""
+ Utility function for percent encoding and decoding data
+"""
+
+# codepoints to encode
+_codepoints = ['!','#','$','%','&','*','+','-','=','?','^','_','`','{','|','}','~']
+# percent encoded value lookup for these codepoints
+_value_lookup = {'!': '%21', '`': '%60', '#': '%23', '%': '%25', '$': '%24', '&': '%26', '}': '%7D', '+': '%2B', '*': '%2A', '-': '%2D', '_': '%5F', '{': '%7B', '~': '%7E', '=': '%3D', '|': '%7C', '?': '%3F', '^': '%5E'}
+
+
+def encode(string):
+ """
+ Takes a string and return its encoded form
+ :param string: string to be percent encoded
+ :return: Percent encoded of `string`
+ """
+ for c in string:
+ if c in _codepoints:
+ string = string.replace(c,_value_lookup[c])
+ return string
+
+def encode_url(url):
+ """
+ Takes a url as parameter and percent encode the list_id only and
+ returns the percent encoded url
+ :param url: URL to be encoded
+ :return: Percent encoded form of `url`
+ """
+ # url segments after splitting
+ segments = url.split('/')
+ # segment to keep same
+ segment1 = '/'.join(segments[:5])
+ # segment to encode
+ segment2 = '/'.join(segments[5:])
+ segment2 = encode(segment2)
+ # make the url back from the segments
+ return ( segment1 + '/' + segment2)
\ No newline at end of file
_______________________________________________
Mailman-coders mailing list
[email protected]
https://mail.python.org/mailman/listinfo/mailman-coders