Merge lp:~black-perl/mailman.client/handling-special-chars-in-email into lp:mailman.client

Proposed by Ankush Sharma
Status: Needs review
Proposed branch: lp:~black-perl/mailman.client/handling-special-chars-in-email
Merge into: lp:mailman.client
Diff against target: 157 lines (+47/-12)
2 files modified
src/mailmanclient/_client.py (+25/-12)
src/mailmanclient/utils.py (+22/-0)
To merge this branch: bzr merge lp:~black-perl/mailman.client/handling-special-chars-in-email
Reviewer Review Type Date Requested Status
Mailman Coders Pending
Review via email: mp+252899@code.launchpad.net

Description of the change

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.

To post a comment you must log in.

Unmerged revisions

63. By black-perl <email address hidden>

Added utils.py containing functions for encoding urls. Added support for encoding the list_id or fqdn_listname as per the issue #1429366 in _client.py.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/mailmanclient/_client.py'
--- src/mailmanclient/_client.py 2015-01-04 22:43:40 +0000
+++ src/mailmanclient/_client.py 2015-03-13 14:16:16 +0000
@@ -33,7 +33,9 @@
33from mailmanclient import __version__33from mailmanclient import __version__
34from operator import itemgetter34from operator import itemgetter
35from six.moves.urllib_error import HTTPError35from six.moves.urllib_error import HTTPError
36from six.moves.urllib_parse import urlencode, urljoin36from six.moves.urllib_parse import urlencode, urljoin, quote
37
38from .utils import encode_url
3739
3840
39DEFAULT_PAGE_ITEM_COUNT = 5041DEFAULT_PAGE_ITEM_COUNT = 50
@@ -247,13 +249,15 @@
247 return _Address(self._connection, content)249 return _Address(self._connection, content)
248250
249 def get_list(self, fqdn_listname):251 def get_list(self, fqdn_listname):
252 encd_fqdn_listname = quote(fqdn_listname)
250 response, content = self._connection.call(253 response, content = self._connection.call(
251 'lists/{0}'.format(fqdn_listname))254 'lists/{0}'.format(encd_fqdn_listname))
252 return _List(self._connection, content['self_link'], content)255 return _List(self._connection, content['self_link'], content)
253256
254 def delete_list(self, fqdn_listname):257 def delete_list(self, fqdn_listname):
258 encd_fqdn_listname = quote(fqdn_listname)
255 response, content = self._connection.call(259 response, content = self._connection.call(
256 'lists/{0}'.format(fqdn_listname), None, 'DELETE')260 'lists/{0}'.format(encd_fqdn_listname), None, 'DELETE')
257261
258262
259class _Domain:263class _Domain:
@@ -319,7 +323,7 @@
319323
320 def __init__(self, connection, url, data=None):324 def __init__(self, connection, url, data=None):
321 self._connection = connection325 self._connection = connection
322 self._url = url326 self._url = encode_url(url)
323 self._info = data327 self._info = data
324328
325 def __repr__(self):329 def __repr__(self):
@@ -331,6 +335,14 @@
331 self._info = content335 self._info = content
332336
333 @property337 @property
338 def enc_fqdn_listname(self):
339 """
340 Returns fqdn_listname in encoded form
341 """
342 self._get_info()
343 return quote(self._info['fqdn_listname'])
344
345 @property
334 def owners(self):346 def owners(self):
335 url = self._url + '/roster/owner'347 url = self._url + '/roster/owner'
336 response, content = self._connection.call(url)348 response, content = self._connection.call(url)
@@ -375,7 +387,7 @@
375387
376 @property388 @property
377 def members(self):389 def members(self):
378 url = 'lists/{0}/roster/member'.format(self.fqdn_listname)390 url = 'lists/{0}/roster/member'.format(self.enc_fqdn_listname)
379 response, content = self._connection.call(url)391 response, content = self._connection.call(url)
380 if 'entries' not in content:392 if 'entries' not in content:
381 return []393 return []
@@ -398,19 +410,19 @@
398 key=itemgetter('address'))]410 key=itemgetter('address'))]
399411
400 def get_member_page(self, count=50, page=1):412 def get_member_page(self, count=50, page=1):
401 url = 'lists/{0}/roster/member'.format(self.fqdn_listname)413 url = 'lists/{0}/roster/member'.format(self.enc_fqdn_listname)
402 return _Page(self._connection, url, _Member, count, page)414 return _Page(self._connection, url, _Member, count, page)
403415
404 @property416 @property
405 def settings(self):417 def settings(self):
406 return _Settings(self._connection,418 return _Settings(self._connection,
407 'lists/{0}/config'.format(self.fqdn_listname))419 'lists/{0}/config'.format(self.enc_fqdn_listname))
408420
409 @property421 @property
410 def held(self):422 def held(self):
411 """Return a list of dicts with held message information."""423 """Return a list of dicts with held message information."""
412 response, content = self._connection.call(424 response, content = self._connection.call(
413 'lists/{0}/held'.format(self.fqdn_listname), None, 'GET')425 'lists/{0}/held'.format(self.enc_fqdn_listname), None, 'GET')
414 if 'entries' not in content:426 if 'entries' not in content:
415 return []427 return []
416 else:428 else:
@@ -429,7 +441,7 @@
429 def requests(self):441 def requests(self):
430 """Return a list of dicts with subscription requests."""442 """Return a list of dicts with subscription requests."""
431 response, content = self._connection.call(443 response, content = self._connection.call(
432 'lists/{0}/requests'.format(self.fqdn_listname), None, 'GET')444 'lists/{0}/requests'.format(self.enc_fqdn_listname), None, 'GET')
433 if 'entries' not in content:445 if 'entries' not in content:
434 return []446 return []
435 else:447 else:
@@ -465,7 +477,7 @@
465 self.remove_role('moderator', address)477 self.remove_role('moderator', address)
466478
467 def remove_role(self, role, address):479 def remove_role(self, role, address):
468 url = 'lists/%s/%s/%s' % (self.fqdn_listname, role, address)480 url = 'lists/%s/%s/%s' % (self.enc_fqdn_listname, role, address)
469 self._connection.call(url, method='DELETE')481 self._connection.call(url, method='DELETE')
470482
471 def moderate_message(self, request_id, action):483 def moderate_message(self, request_id, action):
@@ -477,7 +489,7 @@
477 :type action: String.489 :type action: String.
478 """490 """
479 path = 'lists/{0}/held/{1}'.format(491 path = 'lists/{0}/held/{1}'.format(
480 self.fqdn_listname, str(request_id))492 self.enc_fqdn_listname, str(request_id))
481 response, content = self._connection.call(493 response, content = self._connection.call(
482 path, dict(action=action), 'POST')494 path, dict(action=action), 'POST')
483 return response495 return response
@@ -548,7 +560,7 @@
548560
549 def delete(self):561 def delete(self):
550 response, content = self._connection.call(562 response, content = self._connection.call(
551 'lists/{0}'.format(self.fqdn_listname), None, 'DELETE')563 'lists/{0}'.format(self.enc_fqdn_listname), None, 'DELETE')
552564
553565
554class _Member:566class _Member:
@@ -1002,3 +1014,4 @@
1002 def files(self):1014 def files(self):
1003 response, content = self._connection.call(self.url)1015 response, content = self._connection.call(self.url)
1004 return content['files']1016 return content['files']
1017
10051018
=== added file 'src/mailmanclient/utils.py'
--- src/mailmanclient/utils.py 1970-01-01 00:00:00 +0000
+++ src/mailmanclient/utils.py 2015-03-13 14:16:16 +0000
@@ -0,0 +1,22 @@
1"""
2 Utility function for percent encoding and decoding url parts
3"""
4
5from six.moves.urllib_parse import quote
6
7def encode_url(url):
8 """
9 Takes a url as parameter and percent encode the list_id only and
10 returns the percent encoded url
11 :param url: URL to be encoded
12 :return: Percent encoded form of `url`
13 """
14 # url segments after splitting
15 segments = url.split('/')
16 # segment to keep same
17 segment1 = '/'.join(segments[:5])
18 # segment to encode
19 segment2 = '/'.join(segments[5:])
20 segment2 = quote(segment2)
21 # make the url back from the segments
22 return ( segment1 + '/' + segment2)
0\ No newline at end of file23\ No newline at end of file

Subscribers

People subscribed via source and target branches