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

Reply via email to