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
1=== modified file 'src/mailmanclient/_client.py'
2--- src/mailmanclient/_client.py 2015-01-04 22:43:40 +0000
3+++ src/mailmanclient/_client.py 2015-03-13 14:16:16 +0000
4@@ -33,7 +33,9 @@
5 from mailmanclient import __version__
6 from operator import itemgetter
7 from six.moves.urllib_error import HTTPError
8-from six.moves.urllib_parse import urlencode, urljoin
9+from six.moves.urllib_parse import urlencode, urljoin, quote
10+
11+from .utils import encode_url
12
13
14 DEFAULT_PAGE_ITEM_COUNT = 50
15@@ -247,13 +249,15 @@
16 return _Address(self._connection, content)
17
18 def get_list(self, fqdn_listname):
19+ encd_fqdn_listname = quote(fqdn_listname)
20 response, content = self._connection.call(
21- 'lists/{0}'.format(fqdn_listname))
22+ 'lists/{0}'.format(encd_fqdn_listname))
23 return _List(self._connection, content['self_link'], content)
24
25 def delete_list(self, fqdn_listname):
26+ encd_fqdn_listname = quote(fqdn_listname)
27 response, content = self._connection.call(
28- 'lists/{0}'.format(fqdn_listname), None, 'DELETE')
29+ 'lists/{0}'.format(encd_fqdn_listname), None, 'DELETE')
30
31
32 class _Domain:
33@@ -319,7 +323,7 @@
34
35 def __init__(self, connection, url, data=None):
36 self._connection = connection
37- self._url = url
38+ self._url = encode_url(url)
39 self._info = data
40
41 def __repr__(self):
42@@ -331,6 +335,14 @@
43 self._info = content
44
45 @property
46+ def enc_fqdn_listname(self):
47+ """
48+ Returns fqdn_listname in encoded form
49+ """
50+ self._get_info()
51+ return quote(self._info['fqdn_listname'])
52+
53+ @property
54 def owners(self):
55 url = self._url + '/roster/owner'
56 response, content = self._connection.call(url)
57@@ -375,7 +387,7 @@
58
59 @property
60 def members(self):
61- url = 'lists/{0}/roster/member'.format(self.fqdn_listname)
62+ url = 'lists/{0}/roster/member'.format(self.enc_fqdn_listname)
63 response, content = self._connection.call(url)
64 if 'entries' not in content:
65 return []
66@@ -398,19 +410,19 @@
67 key=itemgetter('address'))]
68
69 def get_member_page(self, count=50, page=1):
70- url = 'lists/{0}/roster/member'.format(self.fqdn_listname)
71+ url = 'lists/{0}/roster/member'.format(self.enc_fqdn_listname)
72 return _Page(self._connection, url, _Member, count, page)
73
74 @property
75 def settings(self):
76 return _Settings(self._connection,
77- 'lists/{0}/config'.format(self.fqdn_listname))
78+ 'lists/{0}/config'.format(self.enc_fqdn_listname))
79
80 @property
81 def held(self):
82 """Return a list of dicts with held message information."""
83 response, content = self._connection.call(
84- 'lists/{0}/held'.format(self.fqdn_listname), None, 'GET')
85+ 'lists/{0}/held'.format(self.enc_fqdn_listname), None, 'GET')
86 if 'entries' not in content:
87 return []
88 else:
89@@ -429,7 +441,7 @@
90 def requests(self):
91 """Return a list of dicts with subscription requests."""
92 response, content = self._connection.call(
93- 'lists/{0}/requests'.format(self.fqdn_listname), None, 'GET')
94+ 'lists/{0}/requests'.format(self.enc_fqdn_listname), None, 'GET')
95 if 'entries' not in content:
96 return []
97 else:
98@@ -465,7 +477,7 @@
99 self.remove_role('moderator', address)
100
101 def remove_role(self, role, address):
102- url = 'lists/%s/%s/%s' % (self.fqdn_listname, role, address)
103+ url = 'lists/%s/%s/%s' % (self.enc_fqdn_listname, role, address)
104 self._connection.call(url, method='DELETE')
105
106 def moderate_message(self, request_id, action):
107@@ -477,7 +489,7 @@
108 :type action: String.
109 """
110 path = 'lists/{0}/held/{1}'.format(
111- self.fqdn_listname, str(request_id))
112+ self.enc_fqdn_listname, str(request_id))
113 response, content = self._connection.call(
114 path, dict(action=action), 'POST')
115 return response
116@@ -548,7 +560,7 @@
117
118 def delete(self):
119 response, content = self._connection.call(
120- 'lists/{0}'.format(self.fqdn_listname), None, 'DELETE')
121+ 'lists/{0}'.format(self.enc_fqdn_listname), None, 'DELETE')
122
123
124 class _Member:
125@@ -1002,3 +1014,4 @@
126 def files(self):
127 response, content = self._connection.call(self.url)
128 return content['files']
129+
130
131=== added file 'src/mailmanclient/utils.py'
132--- src/mailmanclient/utils.py 1970-01-01 00:00:00 +0000
133+++ src/mailmanclient/utils.py 2015-03-13 14:16:16 +0000
134@@ -0,0 +1,22 @@
135+"""
136+ Utility function for percent encoding and decoding url parts
137+"""
138+
139+from six.moves.urllib_parse import quote
140+
141+def encode_url(url):
142+ """
143+ Takes a url as parameter and percent encode the list_id only and
144+ returns the percent encoded url
145+ :param url: URL to be encoded
146+ :return: Percent encoded form of `url`
147+ """
148+ # url segments after splitting
149+ segments = url.split('/')
150+ # segment to keep same
151+ segment1 = '/'.join(segments[:5])
152+ # segment to encode
153+ segment2 = '/'.join(segments[5:])
154+ segment2 = quote(segment2)
155+ # make the url back from the segments
156+ return ( segment1 + '/' + segment2)
157\ No newline at end of file

Subscribers

People subscribed via source and target branches