Status: | Merged |
---|---|
Approved by: | Jelmer Vernooij |
Approved revision: | no longer in the source branch. |
Merge reported by: | The Breezy Bot |
Merged at revision: | not available |
Proposed branch: | lp:~jelmer/brz/medusa |
Merge into: | lp:brz |
Diff against target: |
550 lines (+56/-349) 5 files modified
breezy/tests/ftp_server/__init__.py (+6/-24) breezy/tests/ftp_server/medusa_based.py (+0/-295) breezy/tests/ftp_server/pyftpdlib_based.py (+44/-29) doc/en/release-notes/brz-3.0.txt (+6/-0) setup.py (+0/-1) |
To merge this branch: | bzr merge lp:~jelmer/brz/medusa |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Packman | Approve | ||
Review via email: mp+325457@code.launchpad.net |
Commit message
Drop support for medusa, support newer versions of pyftpdlib.
Description of the change
Drop support for medusa.
Support newer versions of pyftpdlib.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'breezy/tests/ftp_server/__init__.py' | |||
2 | --- breezy/tests/ftp_server/__init__.py 2017-05-21 18:10:28 +0000 | |||
3 | +++ breezy/tests/ftp_server/__init__.py 2017-06-11 13:38:25 +0000 | |||
4 | @@ -27,27 +27,11 @@ | |||
5 | 27 | 27 | ||
6 | 28 | 28 | ||
7 | 29 | try: | 29 | try: |
27 | 30 | from breezy.tests.ftp_server import medusa_based | 30 | import pyftpdlib |
9 | 31 | # medusa is bogus starting with python2.6, since we don't support earlier | ||
10 | 32 | # pythons anymore, it's currently useless. There is hope though that the | ||
11 | 33 | # unicode bugs get fixed in the future so we leave it disabled until | ||
12 | 34 | # then. Keeping the framework in place means that only the following line | ||
13 | 35 | # will need to be changed. The last tests were conducted with medusa-2.0 | ||
14 | 36 | # -- vila 20110607 | ||
15 | 37 | medusa_available = False | ||
16 | 38 | except ImportError: | ||
17 | 39 | medusa_available = False | ||
18 | 40 | |||
19 | 41 | |||
20 | 42 | try: | ||
21 | 43 | from breezy.tests.ftp_server import pyftpdlib_based | ||
22 | 44 | if pyftpdlib_based.pyftplib_version >= (0, 7, 0): | ||
23 | 45 | pyftpdlib_available = True | ||
24 | 46 | else: | ||
25 | 47 | # 0.6.0 breaks SITE CHMOD | ||
26 | 48 | pyftpdlib_available = False | ||
28 | 49 | except ImportError: | 31 | except ImportError: |
29 | 50 | pyftpdlib_available = False | 32 | pyftpdlib_available = False |
30 | 33 | else: | ||
31 | 34 | pyftpdlib_available = True | ||
32 | 51 | 35 | ||
33 | 52 | 36 | ||
34 | 53 | class _FTPServerFeature(features.Feature): | 37 | class _FTPServerFeature(features.Feature): |
35 | @@ -56,12 +40,11 @@ | |||
36 | 56 | Right now, the only way this is available is if one of the following is | 40 | Right now, the only way this is available is if one of the following is |
37 | 57 | installed: | 41 | installed: |
38 | 58 | 42 | ||
39 | 59 | - 'medusa': http://www.amk.ca/python/code/medusa.html | ||
40 | 60 | - 'pyftpdlib': http://code.google.com/p/pyftpdlib/ | 43 | - 'pyftpdlib': http://code.google.com/p/pyftpdlib/ |
41 | 61 | """ | 44 | """ |
42 | 62 | 45 | ||
43 | 63 | def _probe(self): | 46 | def _probe(self): |
45 | 64 | return medusa_available or pyftpdlib_available | 47 | return pyftpdlib_available |
46 | 65 | 48 | ||
47 | 66 | def feature_name(self): | 49 | def feature_name(self): |
48 | 67 | return 'FTPServer' | 50 | return 'FTPServer' |
49 | @@ -92,9 +75,8 @@ | |||
50 | 92 | raise tests.UnavailableFeature(FTPServerFeature) | 75 | raise tests.UnavailableFeature(FTPServerFeature) |
51 | 93 | 76 | ||
52 | 94 | 77 | ||
56 | 95 | if medusa_available: | 78 | if pyftpdlib_available: |
57 | 96 | FTPTestServer = medusa_based.FTPTestServer | 79 | from . import pyftpdlib_based |
55 | 97 | elif pyftpdlib_available: | ||
58 | 98 | FTPTestServer = pyftpdlib_based.FTPTestServer | 80 | FTPTestServer = pyftpdlib_based.FTPTestServer |
59 | 99 | else: | 81 | else: |
60 | 100 | FTPTestServer = UnavailableFTPTestServer | 82 | FTPTestServer = UnavailableFTPTestServer |
61 | 101 | 83 | ||
62 | === removed file 'breezy/tests/ftp_server/medusa_based.py' | |||
63 | --- breezy/tests/ftp_server/medusa_based.py 2017-05-22 00:56:52 +0000 | |||
64 | +++ breezy/tests/ftp_server/medusa_based.py 1970-01-01 00:00:00 +0000 | |||
65 | @@ -1,295 +0,0 @@ | |||
66 | 1 | # Copyright (C) 2007-2010 Canonical Ltd | ||
67 | 2 | # | ||
68 | 3 | # This program is free software; you can redistribute it and/or modify | ||
69 | 4 | # it under the terms of the GNU General Public License as published by | ||
70 | 5 | # the Free Software Foundation; either version 2 of the License, or | ||
71 | 6 | # (at your option) any later version. | ||
72 | 7 | # | ||
73 | 8 | # This program is distributed in the hope that it will be useful, | ||
74 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
75 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
76 | 11 | # GNU General Public License for more details. | ||
77 | 12 | # | ||
78 | 13 | # You should have received a copy of the GNU General Public License | ||
79 | 14 | # along with this program; if not, write to the Free Software | ||
80 | 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
81 | 16 | """ | ||
82 | 17 | FTP test server. | ||
83 | 18 | |||
84 | 19 | Based on medusa: http://www.amk.ca/python/code/medusa.html | ||
85 | 20 | """ | ||
86 | 21 | |||
87 | 22 | import asyncore | ||
88 | 23 | import errno | ||
89 | 24 | import os | ||
90 | 25 | import select | ||
91 | 26 | import stat | ||
92 | 27 | import threading | ||
93 | 28 | |||
94 | 29 | import medusa | ||
95 | 30 | import medusa.filesys | ||
96 | 31 | import medusa.ftp_server | ||
97 | 32 | |||
98 | 33 | from ... import ( | ||
99 | 34 | osutils, | ||
100 | 35 | tests, | ||
101 | 36 | trace, | ||
102 | 37 | ) | ||
103 | 38 | from .. import test_server | ||
104 | 39 | |||
105 | 40 | |||
106 | 41 | class test_filesystem(medusa.filesys.os_filesystem): | ||
107 | 42 | """A custom filesystem wrapper to add missing functionalities.""" | ||
108 | 43 | |||
109 | 44 | def chmod(self, path, mode): | ||
110 | 45 | p = self.normalize(self.path_module.join (self.wd, path)) | ||
111 | 46 | return os.chmod(self.translate(p), mode) | ||
112 | 47 | |||
113 | 48 | |||
114 | 49 | class test_authorizer(object): | ||
115 | 50 | """A custom Authorizer object for running the test suite. | ||
116 | 51 | |||
117 | 52 | The reason we cannot use dummy_authorizer, is because it sets the | ||
118 | 53 | channel to readonly, which we don't always want to do. | ||
119 | 54 | """ | ||
120 | 55 | |||
121 | 56 | def __init__(self, root): | ||
122 | 57 | self.root = root | ||
123 | 58 | # If secured_user is set secured_password will be checked | ||
124 | 59 | self.secured_user = None | ||
125 | 60 | self.secured_password = None | ||
126 | 61 | |||
127 | 62 | def authorize(self, channel, username, password): | ||
128 | 63 | """Return (success, reply_string, filesystem)""" | ||
129 | 64 | channel.persona = -1, -1 | ||
130 | 65 | if username == 'anonymous': | ||
131 | 66 | channel.read_only = 1 | ||
132 | 67 | else: | ||
133 | 68 | channel.read_only = 0 | ||
134 | 69 | |||
135 | 70 | # Check secured_user if set | ||
136 | 71 | if (self.secured_user is not None | ||
137 | 72 | and username == self.secured_user | ||
138 | 73 | and password != self.secured_password): | ||
139 | 74 | return 0, 'Password invalid.', None | ||
140 | 75 | else: | ||
141 | 76 | return 1, 'OK.', test_filesystem(self.root) | ||
142 | 77 | |||
143 | 78 | |||
144 | 79 | class ftp_channel(medusa.ftp_server.ftp_channel): | ||
145 | 80 | """Customized ftp channel""" | ||
146 | 81 | |||
147 | 82 | def log(self, message): | ||
148 | 83 | """Redirect logging requests.""" | ||
149 | 84 | trace.mutter('ftp_channel: %s', message) | ||
150 | 85 | |||
151 | 86 | def log_info(self, message, type='info'): | ||
152 | 87 | """Redirect logging requests.""" | ||
153 | 88 | trace.mutter('ftp_channel %s: %s', type, message) | ||
154 | 89 | |||
155 | 90 | def cmd_rnfr(self, line): | ||
156 | 91 | """Prepare for renaming a file.""" | ||
157 | 92 | self._renaming = line[1] | ||
158 | 93 | self.respond('350 Ready for RNTO') | ||
159 | 94 | # TODO: jam 20060516 in testing, the ftp server seems to | ||
160 | 95 | # check that the file already exists, or it sends | ||
161 | 96 | # 550 RNFR command failed | ||
162 | 97 | |||
163 | 98 | def cmd_rnto(self, line): | ||
164 | 99 | """Rename a file based on the target given. | ||
165 | 100 | |||
166 | 101 | rnto must be called after calling rnfr. | ||
167 | 102 | """ | ||
168 | 103 | if not self._renaming: | ||
169 | 104 | self.respond('503 RNFR required first.') | ||
170 | 105 | pfrom = self.filesystem.translate(self._renaming) | ||
171 | 106 | self._renaming = None | ||
172 | 107 | pto = self.filesystem.translate(line[1]) | ||
173 | 108 | if os.path.exists(pto): | ||
174 | 109 | self.respond('550 RNTO failed: file exists') | ||
175 | 110 | return | ||
176 | 111 | try: | ||
177 | 112 | os.rename(pfrom, pto) | ||
178 | 113 | except (IOError, OSError) as e: | ||
179 | 114 | # TODO: jam 20060516 return custom responses based on | ||
180 | 115 | # why the command failed | ||
181 | 116 | # (bialix 20070418) str(e) on Python 2.5 @ Windows | ||
182 | 117 | # sometimes don't provide expected error message; | ||
183 | 118 | # so we obtain such message via os.strerror() | ||
184 | 119 | self.respond('550 RNTO failed: %s' % os.strerror(e.errno)) | ||
185 | 120 | except: | ||
186 | 121 | self.respond('550 RNTO failed') | ||
187 | 122 | # For a test server, we will go ahead and just die | ||
188 | 123 | raise | ||
189 | 124 | else: | ||
190 | 125 | self.respond('250 Rename successful.') | ||
191 | 126 | |||
192 | 127 | def cmd_size(self, line): | ||
193 | 128 | """Return the size of a file | ||
194 | 129 | |||
195 | 130 | This is overloaded to help the test suite determine if the | ||
196 | 131 | target is a directory. | ||
197 | 132 | """ | ||
198 | 133 | filename = line[1] | ||
199 | 134 | if not self.filesystem.isfile(filename): | ||
200 | 135 | if self.filesystem.isdir(filename): | ||
201 | 136 | self.respond('550 "%s" is a directory' % (filename,)) | ||
202 | 137 | else: | ||
203 | 138 | self.respond('550 "%s" is not a file' % (filename,)) | ||
204 | 139 | else: | ||
205 | 140 | self.respond('213 %d' | ||
206 | 141 | % (self.filesystem.stat(filename)[stat.ST_SIZE]),) | ||
207 | 142 | |||
208 | 143 | def cmd_mkd(self, line): | ||
209 | 144 | """Create a directory. | ||
210 | 145 | |||
211 | 146 | Overloaded because default implementation does not distinguish | ||
212 | 147 | *why* it cannot make a directory. | ||
213 | 148 | """ | ||
214 | 149 | if len (line) != 2: | ||
215 | 150 | self.command_not_understood(''.join(line)) | ||
216 | 151 | else: | ||
217 | 152 | path = line[1] | ||
218 | 153 | try: | ||
219 | 154 | self.filesystem.mkdir (path) | ||
220 | 155 | self.respond ('257 MKD command successful.') | ||
221 | 156 | except (IOError, OSError) as e: | ||
222 | 157 | # (bialix 20070418) str(e) on Python 2.5 @ Windows | ||
223 | 158 | # sometimes don't provide expected error message; | ||
224 | 159 | # so we obtain such message via os.strerror() | ||
225 | 160 | self.respond ('550 error creating directory: %s' % | ||
226 | 161 | os.strerror(e.errno)) | ||
227 | 162 | except: | ||
228 | 163 | self.respond ('550 error creating directory.') | ||
229 | 164 | |||
230 | 165 | def cmd_site(self, line): | ||
231 | 166 | """Site specific commands.""" | ||
232 | 167 | command, args = line[1].split(' ', 1) | ||
233 | 168 | if command.lower() == 'chmod': | ||
234 | 169 | try: | ||
235 | 170 | mode, path = args.split() | ||
236 | 171 | mode = int(mode, 8) | ||
237 | 172 | except ValueError: | ||
238 | 173 | # We catch both malformed line and malformed mode with the same | ||
239 | 174 | # ValueError. | ||
240 | 175 | self.command_not_understood(' '.join(line)) | ||
241 | 176 | return | ||
242 | 177 | try: | ||
243 | 178 | # Yes path and mode are reversed | ||
244 | 179 | self.filesystem.chmod(path, mode) | ||
245 | 180 | self.respond('200 SITE CHMOD command successful') | ||
246 | 181 | except AttributeError: | ||
247 | 182 | # The chmod method is not available in read-only and will raise | ||
248 | 183 | # AttributeError since a different filesystem is used in that | ||
249 | 184 | # case | ||
250 | 185 | self.command_not_authorized(' '.join(line)) | ||
251 | 186 | else: | ||
252 | 187 | # Another site specific command was requested. We don't know that | ||
253 | 188 | # one | ||
254 | 189 | self.command_not_understood(' '.join(line)) | ||
255 | 190 | |||
256 | 191 | |||
257 | 192 | class ftp_server(medusa.ftp_server.ftp_server): | ||
258 | 193 | """Customize the behavior of the Medusa ftp_server. | ||
259 | 194 | |||
260 | 195 | There are a few warts on the ftp_server, based on how it expects | ||
261 | 196 | to be used. | ||
262 | 197 | """ | ||
263 | 198 | _renaming = None | ||
264 | 199 | ftp_channel_class = ftp_channel | ||
265 | 200 | |||
266 | 201 | def __init__(self, *args, **kwargs): | ||
267 | 202 | trace.mutter('Initializing ftp_server: %r, %r', args, kwargs) | ||
268 | 203 | medusa.ftp_server.ftp_server.__init__(self, *args, **kwargs) | ||
269 | 204 | |||
270 | 205 | def log(self, message): | ||
271 | 206 | """Redirect logging requests.""" | ||
272 | 207 | trace.mutter('ftp_server: %s', message) | ||
273 | 208 | |||
274 | 209 | def log_info(self, message, type='info'): | ||
275 | 210 | """Override the asyncore.log_info so we don't stipple the screen.""" | ||
276 | 211 | trace.mutter('ftp_server %s: %s', type, message) | ||
277 | 212 | |||
278 | 213 | |||
279 | 214 | class FTPTestServer(test_server.TestServer): | ||
280 | 215 | """Common code for FTP server facilities.""" | ||
281 | 216 | |||
282 | 217 | no_unicode_support = True | ||
283 | 218 | |||
284 | 219 | def __init__(self): | ||
285 | 220 | self._root = None | ||
286 | 221 | self._ftp_server = None | ||
287 | 222 | self._port = None | ||
288 | 223 | self._async_thread = None | ||
289 | 224 | # ftp server logs | ||
290 | 225 | self.logs = [] | ||
291 | 226 | |||
292 | 227 | def get_url(self): | ||
293 | 228 | """Calculate an ftp url to this server.""" | ||
294 | 229 | return 'ftp://foo:bar@localhost:%d/' % (self._port) | ||
295 | 230 | |||
296 | 231 | def get_bogus_url(self): | ||
297 | 232 | """Return a URL which cannot be connected to.""" | ||
298 | 233 | return 'ftp://127.0.0.1:1' | ||
299 | 234 | |||
300 | 235 | def log(self, message): | ||
301 | 236 | """This is used by medusa.ftp_server to log connections, etc.""" | ||
302 | 237 | self.logs.append(message) | ||
303 | 238 | |||
304 | 239 | def start_server(self, vfs_server=None): | ||
305 | 240 | if not (vfs_server is None or isinstance(vfs_server, | ||
306 | 241 | test_server.LocalURLServer)): | ||
307 | 242 | raise AssertionError( | ||
308 | 243 | "FTPServer currently assumes local transport, got %s" % vfs_server) | ||
309 | 244 | self._root = osutils.getcwd() | ||
310 | 245 | self._ftp_server = ftp_server( | ||
311 | 246 | authorizer=test_authorizer(root=self._root), | ||
312 | 247 | ip='localhost', | ||
313 | 248 | port=0, # bind to a random port | ||
314 | 249 | resolver=None, | ||
315 | 250 | logger_object=self # Use FTPServer.log() for messages | ||
316 | 251 | ) | ||
317 | 252 | self._port = self._ftp_server.getsockname()[1] | ||
318 | 253 | # Don't let it loop forever, or handle an infinite number of requests. | ||
319 | 254 | # In this case it will run for 1000s, or 10000 requests | ||
320 | 255 | self._async_thread = threading.Thread( | ||
321 | 256 | target=FTPTestServer._asyncore_loop_ignore_EBADF, | ||
322 | 257 | kwargs={'timeout':0.1, 'count':10000}) | ||
323 | 258 | if 'threads' in tests.selftest_debug_flags: | ||
324 | 259 | sys.stderr.write('Thread started: %s\n' | ||
325 | 260 | % (self._async_thread.ident,)) | ||
326 | 261 | self._async_thread.setDaemon(True) | ||
327 | 262 | self._async_thread.start() | ||
328 | 263 | |||
329 | 264 | def stop_server(self): | ||
330 | 265 | self._ftp_server.close() | ||
331 | 266 | asyncore.close_all() | ||
332 | 267 | self._async_thread.join() | ||
333 | 268 | if 'threads' in tests.selftest_debug_flags: | ||
334 | 269 | sys.stderr.write('Thread joined: %s\n' | ||
335 | 270 | % (self._async_thread.ident,)) | ||
336 | 271 | |||
337 | 272 | @staticmethod | ||
338 | 273 | def _asyncore_loop_ignore_EBADF(*args, **kwargs): | ||
339 | 274 | """Ignore EBADF during server shutdown. | ||
340 | 275 | |||
341 | 276 | We close the socket to get the server to shutdown, but this causes | ||
342 | 277 | select.select() to raise EBADF. | ||
343 | 278 | """ | ||
344 | 279 | try: | ||
345 | 280 | asyncore.loop(*args, **kwargs) | ||
346 | 281 | # FIXME: If we reach that point, we should raise an exception | ||
347 | 282 | # explaining that the 'count' parameter in setUp is too low or | ||
348 | 283 | # testers may wonder why their test just sits there waiting for a | ||
349 | 284 | # server that is already dead. Note that if the tester waits too | ||
350 | 285 | # long under pdb the server will also die. | ||
351 | 286 | except select.error as e: | ||
352 | 287 | if e.args[0] != errno.EBADF: | ||
353 | 288 | raise | ||
354 | 289 | |||
355 | 290 | def add_user(self, user, password): | ||
356 | 291 | """Add a user with write access.""" | ||
357 | 292 | authorizer = server = self._ftp_server.authorizer | ||
358 | 293 | authorizer.secured_user = user | ||
359 | 294 | authorizer.secured_password = password | ||
360 | 295 | |||
361 | 296 | 0 | ||
362 | === modified file 'breezy/tests/ftp_server/pyftpdlib_based.py' | |||
363 | --- breezy/tests/ftp_server/pyftpdlib_based.py 2017-05-22 00:56:52 +0000 | |||
364 | +++ breezy/tests/ftp_server/pyftpdlib_based.py 2017-06-11 13:38:25 +0000 | |||
365 | @@ -20,8 +20,20 @@ | |||
366 | 20 | """ | 20 | """ |
367 | 21 | 21 | ||
368 | 22 | import errno | 22 | import errno |
369 | 23 | import logging | ||
370 | 23 | import os | 24 | import os |
372 | 24 | from pyftpdlib import ftpserver | 25 | import pyftpdlib |
373 | 26 | import sys | ||
374 | 27 | from pyftpdlib.authorizers import ( | ||
375 | 28 | AuthorizerError, | ||
376 | 29 | DummyAuthorizer, | ||
377 | 30 | ) | ||
378 | 31 | from pyftpdlib.filesystems import AbstractedFS | ||
379 | 32 | from pyftpdlib.handlers import ( | ||
380 | 33 | FTPHandler, | ||
381 | 34 | proto_cmds, | ||
382 | 35 | ) | ||
383 | 36 | from pyftpdlib.servers import FTPServer | ||
384 | 25 | import select | 37 | import select |
385 | 26 | import threading | 38 | import threading |
386 | 27 | 39 | ||
387 | @@ -34,22 +46,31 @@ | |||
388 | 34 | from breezy.tests import test_server | 46 | from breezy.tests import test_server |
389 | 35 | 47 | ||
390 | 36 | 48 | ||
391 | 49 | class NullHandler(logging.Handler): | ||
392 | 50 | |||
393 | 51 | def emit(self, record): | ||
394 | 52 | pass | ||
395 | 53 | |||
396 | 54 | # Shut up very verbose pyftpdlib | ||
397 | 55 | logging.getLogger('pyftpdlib').addHandler(NullHandler()) | ||
398 | 56 | |||
399 | 57 | |||
400 | 37 | # Convert the pyftplib string version into a tuple to avoid traps in string | 58 | # Convert the pyftplib string version into a tuple to avoid traps in string |
401 | 38 | # comparison. | 59 | # comparison. |
406 | 39 | pyftplib_version = tuple(map(int, ftpserver.__ver__.split('.'))) | 60 | pyftplib_version = tuple(map(int, pyftpdlib.__ver__.split('.'))) |
407 | 40 | 61 | ||
408 | 41 | 62 | ||
409 | 42 | class AnonymousWithWriteAccessAuthorizer(ftpserver.DummyAuthorizer): | 63 | class AnonymousWithWriteAccessAuthorizer(DummyAuthorizer): |
410 | 43 | 64 | ||
411 | 44 | def _check_permissions(self, username, perm): | 65 | def _check_permissions(self, username, perm): |
412 | 45 | # Like base implementation but don't warn about write permissions | 66 | # Like base implementation but don't warn about write permissions |
413 | 46 | # assigned to anonymous, since that's exactly our purpose. | 67 | # assigned to anonymous, since that's exactly our purpose. |
414 | 47 | for p in perm: | 68 | for p in perm: |
415 | 48 | if p not in self.read_perms + self.write_perms: | 69 | if p not in self.read_perms + self.write_perms: |
420 | 49 | raise ftpserver.AuthorizerError('No such permission "%s"' %p) | 70 | raise AuthorizerError('No such permission "%s"' %p) |
421 | 50 | 71 | ||
422 | 51 | 72 | ||
423 | 52 | class BzrConformingFS(ftpserver.AbstractedFS): | 73 | class BzrConformingFS(AbstractedFS): |
424 | 53 | 74 | ||
425 | 54 | def chmod(self, path, mode): | 75 | def chmod(self, path, mode): |
426 | 55 | return os.chmod(path, mode) | 76 | return os.chmod(path, mode) |
427 | @@ -59,19 +80,20 @@ | |||
428 | 59 | return [osutils.safe_utf8(s) for s in os.listdir(path)] | 80 | return [osutils.safe_utf8(s) for s in os.listdir(path)] |
429 | 60 | 81 | ||
430 | 61 | def fs2ftp(self, fspath): | 82 | def fs2ftp(self, fspath): |
432 | 62 | p = ftpserver.AbstractedFS.fs2ftp(self, osutils.safe_unicode(fspath)) | 83 | p = AbstractedFS.fs2ftp(self, osutils.safe_unicode(fspath)) |
433 | 63 | return osutils.safe_utf8(p) | 84 | return osutils.safe_utf8(p) |
434 | 64 | 85 | ||
435 | 65 | def ftp2fs(self, ftppath): | 86 | def ftp2fs(self, ftppath): |
436 | 66 | p = osutils.safe_unicode(ftppath) | 87 | p = osutils.safe_unicode(ftppath) |
440 | 67 | return ftpserver.AbstractedFS.ftp2fs(self, p) | 88 | return AbstractedFS.ftp2fs(self, p) |
441 | 68 | 89 | ||
442 | 69 | class BzrConformingFTPHandler(ftpserver.FTPHandler): | 90 | |
443 | 91 | class BzrConformingFTPHandler(FTPHandler): | ||
444 | 70 | 92 | ||
445 | 71 | abstracted_fs = BzrConformingFS | 93 | abstracted_fs = BzrConformingFS |
446 | 72 | 94 | ||
449 | 73 | def __init__(self, conn, server): | 95 | def __init__(self, conn, server, ioloop=None): |
450 | 74 | ftpserver.FTPHandler.__init__(self, conn, server) | 96 | FTPHandler.__init__(self, conn, server) |
451 | 75 | self.authorizer = server.authorizer | 97 | self.authorizer = server.authorizer |
452 | 76 | 98 | ||
453 | 77 | def ftp_SIZE(self, path): | 99 | def ftp_SIZE(self, path): |
454 | @@ -83,7 +105,7 @@ | |||
455 | 83 | self.log('FAIL SIZE "%s". %s.' % (line, why)) | 105 | self.log('FAIL SIZE "%s". %s.' % (line, why)) |
456 | 84 | self.respond("550 %s." %why) | 106 | self.respond("550 %s." %why) |
457 | 85 | else: | 107 | else: |
459 | 86 | ftpserver.FTPHandler.ftp_SIZE(self, path) | 108 | FTPHandler.ftp_SIZE(self, path) |
460 | 87 | 109 | ||
461 | 88 | def ftp_NLST(self, path): | 110 | def ftp_NLST(self, path): |
462 | 89 | # bzr is overly picky here, but we want to make the test suite pass | 111 | # bzr is overly picky here, but we want to make the test suite pass |
463 | @@ -94,7 +116,7 @@ | |||
464 | 94 | self.log('FAIL NLST "%s". %s.' % (line, why)) | 116 | self.log('FAIL NLST "%s". %s.' % (line, why)) |
465 | 95 | self.respond("550 %s." %why) | 117 | self.respond("550 %s." %why) |
466 | 96 | else: | 118 | else: |
468 | 97 | ftpserver.FTPHandler.ftp_NLST(self, path) | 119 | FTPHandler.ftp_NLST(self, path) |
469 | 98 | 120 | ||
470 | 99 | def log_cmd(self, cmd, arg, respcode, respstr): | 121 | def log_cmd(self, cmd, arg, respcode, respstr): |
471 | 100 | # base class version choke on unicode, the alternative is to just | 122 | # base class version choke on unicode, the alternative is to just |
472 | @@ -107,12 +129,12 @@ | |||
473 | 107 | 129 | ||
474 | 108 | 130 | ||
475 | 109 | # An empty password is valid, hence the arg is neither mandatory nor forbidden | 131 | # An empty password is valid, hence the arg is neither mandatory nor forbidden |
477 | 110 | ftpserver.proto_cmds['PASS']['arg'] = None | 132 | proto_cmds['PASS']['arg'] = None |
478 | 111 | 133 | ||
480 | 112 | class ftp_server(ftpserver.FTPServer): | 134 | class ftp_server(FTPServer): |
481 | 113 | 135 | ||
482 | 114 | def __init__(self, address, handler, authorizer): | 136 | def __init__(self, address, handler, authorizer): |
484 | 115 | ftpserver.FTPServer.__init__(self, address, handler) | 137 | FTPServer.__init__(self, address, handler) |
485 | 116 | self.authorizer = authorizer | 138 | self.authorizer = authorizer |
486 | 117 | # Worth backporting upstream ? | 139 | # Worth backporting upstream ? |
487 | 118 | self.addr = self.socket.getsockname() | 140 | self.addr = self.socket.getsockname() |
488 | @@ -155,13 +177,6 @@ | |||
489 | 155 | authorizer.add_anonymous(self._root, perm='elradfmwM') | 177 | authorizer.add_anonymous(self._root, perm='elradfmwM') |
490 | 156 | self._ftp_server = ftp_server(address, BzrConformingFTPHandler, | 178 | self._ftp_server = ftp_server(address, BzrConformingFTPHandler, |
491 | 157 | authorizer) | 179 | authorizer) |
492 | 158 | # This is hacky as hell, will not work if we need two servers working | ||
493 | 159 | # at the same time, but that's the best we can do so far... | ||
494 | 160 | # FIXME: At least log and logline could be overriden in the handler ? | ||
495 | 161 | # -- vila 20090227 | ||
496 | 162 | ftpserver.log = self.log | ||
497 | 163 | ftpserver.logline = self.log | ||
498 | 164 | ftpserver.logerror = self.log | ||
499 | 165 | 180 | ||
500 | 166 | self._port = self._ftp_server.socket.getsockname()[1] | 181 | self._port = self._ftp_server.socket.getsockname()[1] |
501 | 167 | self._ftpd_starting = threading.Lock() | 182 | self._ftpd_starting = threading.Lock() |
502 | @@ -196,11 +211,11 @@ | |||
503 | 196 | self._ftpd_starting.release() | 211 | self._ftpd_starting.release() |
504 | 197 | while self._ftpd_running: | 212 | while self._ftpd_running: |
505 | 198 | try: | 213 | try: |
507 | 199 | self._ftp_server.serve_forever(timeout=0.1, count=1) | 214 | self._ftp_server.serve_forever(timeout=0.1) |
508 | 200 | except select.error as e: | 215 | except select.error as e: |
509 | 201 | if e.args[0] != errno.EBADF: | 216 | if e.args[0] != errno.EBADF: |
510 | 202 | raise | 217 | raise |
512 | 203 | self._ftp_server.close_all(ignore_all=True) | 218 | self._ftp_server.close_all() |
513 | 204 | 219 | ||
514 | 205 | def add_user(self, user, password): | 220 | def add_user(self, user, password): |
515 | 206 | """Add a user with write access.""" | 221 | """Add a user with write access.""" |
516 | 207 | 222 | ||
517 | === modified file 'doc/en/release-notes/brz-3.0.txt' | |||
518 | --- doc/en/release-notes/brz-3.0.txt 2017-06-11 01:27:46 +0000 | |||
519 | +++ doc/en/release-notes/brz-3.0.txt 2017-06-11 13:38:25 +0000 | |||
520 | @@ -41,6 +41,9 @@ | |||
521 | 41 | URL schemes "http+pycurl://" and "https+pycurl://" has been dropped. | 41 | URL schemes "http+pycurl://" and "https+pycurl://" has been dropped. |
522 | 42 | (Jelmer Vernooij, #82086, #377389, #122258, #516222, #545776, #1696602) | 42 | (Jelmer Vernooij, #82086, #377389, #122258, #516222, #545776, #1696602) |
523 | 43 | 43 | ||
524 | 44 | * Support for medusa for FTP tests has been dropped, only | ||
525 | 45 | pyftpdlib is now supported. (Jelmer Vernooij) | ||
526 | 46 | |||
527 | 44 | New Features | 47 | New Features |
528 | 45 | ************ | 48 | ************ |
529 | 46 | 49 | ||
530 | @@ -135,5 +138,8 @@ | |||
531 | 135 | which caused ``output_encoding = iso-8859-1`` to be added to the | 138 | which caused ``output_encoding = iso-8859-1`` to be added to the |
532 | 136 | users' bazaar.conf. (Jelmer Vernooij) | 139 | users' bazaar.conf. (Jelmer Vernooij) |
533 | 137 | 140 | ||
534 | 141 | * Newer versions of ``pyftpdlib`` are now supported for running FTP tests. | ||
535 | 142 | (Jelmer Vernooij) | ||
536 | 143 | |||
537 | 138 | .. | 144 | .. |
538 | 139 | vim: tw=74 ft=rst ff=unix | 145 | vim: tw=74 ft=rst ff=unix |
539 | 140 | 146 | ||
540 | === modified file 'setup.py' | |||
541 | --- setup.py 2017-06-07 00:34:37 +0000 | |||
542 | +++ setup.py 2017-06-11 13:38:25 +0000 | |||
543 | @@ -587,7 +587,6 @@ | |||
544 | 587 | excludes = """Tkinter psyco ElementPath r_hmac | 587 | excludes = """Tkinter psyco ElementPath r_hmac |
545 | 588 | ImaginaryModule cElementTree elementtree.ElementTree | 588 | ImaginaryModule cElementTree elementtree.ElementTree |
546 | 589 | Crypto.PublicKey._fastmath | 589 | Crypto.PublicKey._fastmath |
547 | 590 | medusa medusa.filesys medusa.ftp_server | ||
548 | 591 | tools | 590 | tools |
549 | 592 | resource validate""".split() | 591 | resource validate""".split() |
550 | 593 | dll_excludes = [] | 592 | dll_excludes = [] |
Looks good to me.