Merge lp:~jelmer/brz/no-more-ftp into lp:brz
- no-more-ftp
- Merge into trunk
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Vincent Ladeuil | Approve | ||
Martin Packman | Approve | ||
Review via email:
|
Commit message
Drop support for FTP.
Description of the change
Drop support for FTP.
* Our testing situation for FTP isn't great
* FTP is not as commonly used anymore as when Bazaar was started
* Breezy operations over FTP are slower than over other transports, so we
should steer users away from it
* FTP isn't well defined in some areas, resulting in lots of annoying corner case bugs
* FTP connections are almost always insecure
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
The Breezy Bot (the-breezy-bot) wrote : | # |
Merging failed
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Vincent Ladeuil (vila) wrote : | # |
Die ftp die, like Martin, a bit sad, but meh, ftp needs to die anyway.
Plus that will make fullermd happy.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
Preview Diff
1 | === modified file 'breezy/help_topics/en/authentication.txt' |
2 | --- breezy/help_topics/en/authentication.txt 2017-05-21 14:47:52 +0000 |
3 | +++ breezy/help_topics/en/authentication.txt 2018-05-11 10:19:44 +0000 |
4 | @@ -24,16 +24,16 @@ |
5 | |
6 | Instead of using:: |
7 | |
8 | - brz branch ftp://joe:secret@host.com/path/to/my/branch |
9 | + brz branch sftp://joe:secret@host.com/path/to/my/branch |
10 | |
11 | you simply use:: |
12 | |
13 | - brz branch ftp://host.com/path/to/my/branch |
14 | + brz branch sftp://host.com/path/to/my/branch |
15 | |
16 | provided you have created the following ``authentication.conf`` file:: |
17 | |
18 | [myprojects] |
19 | - scheme=ftp |
20 | + scheme=sftp |
21 | host=host.com |
22 | user=joe |
23 | password=secret |
24 | |
25 | === modified file 'breezy/lockdir.py' |
26 | --- breezy/lockdir.py 2017-10-28 12:56:32 +0000 |
27 | +++ breezy/lockdir.py 2018-05-11 10:19:44 +0000 |
28 | @@ -374,9 +374,6 @@ |
29 | # another locker within the 'held' directory. do a slower |
30 | # deletion where we list the directory and remove everything |
31 | # within it. |
32 | - # |
33 | - # Maybe this should be broader to allow for ftp servers with |
34 | - # non-specific error messages? |
35 | self._trace("doing recursive deletion of non-empty directory " |
36 | "%s", tmpname) |
37 | self.transport.delete_tree(tmpname) |
38 | |
39 | === modified file 'breezy/plugins/git/tests/test_dir.py' |
40 | --- breezy/plugins/git/tests/test_dir.py 2018-03-28 01:51:03 +0000 |
41 | +++ breezy/plugins/git/tests/test_dir.py 2018-05-11 10:19:44 +0000 |
42 | @@ -91,5 +91,5 @@ |
43 | self.assertEquals(self.format, format2) |
44 | self.assertEquals(self.format, self.format) |
45 | bzr_format = controldir.format_registry.make_controldir("default") |
46 | - self.assertNotEquals(self.format, bzr_format) |
47 | + self.assertNotEqual(self.format, bzr_format) |
48 | |
49 | |
50 | === modified file 'breezy/plugins/upload/__init__.py' |
51 | --- breezy/plugins/upload/__init__.py 2017-07-30 16:59:50 +0000 |
52 | +++ breezy/plugins/upload/__init__.py 2018-05-11 10:19:44 +0000 |
53 | @@ -107,7 +107,7 @@ |
54 | It is possible to upload to a remote location from another remote location by |
55 | specifying it with the --directory option: |
56 | |
57 | - brz upload ftp://public.example.com --directory sftp://private.example.com |
58 | + brz upload sftp://public.example.com --directory sftp://private.example.com |
59 | |
60 | This, together with --auto, can be used to upload when you push to your |
61 | central branch, rather than when you commit to your local branch. |
62 | |
63 | === modified file 'breezy/plugins/upload/tests/test_upload.py' |
64 | --- breezy/plugins/upload/tests/test_upload.py 2018-02-18 15:21:06 +0000 |
65 | +++ breezy/plugins/upload/tests/test_upload.py 2018-05-11 10:19:44 +0000 |
66 | @@ -34,9 +34,6 @@ |
67 | per_branch, |
68 | per_transport, |
69 | ) |
70 | -from ....transport import ( |
71 | - ftp, |
72 | - ) |
73 | from .. import ( |
74 | cmds, |
75 | ) |
76 | @@ -46,7 +43,7 @@ |
77 | result = [] |
78 | basis = per_transport.transport_test_permutations() |
79 | # Keep only the interesting ones for upload |
80 | - usable_classes = {ftp.FtpTransport} |
81 | + usable_classes = set() |
82 | if features.paramiko.available(): |
83 | from ....transport import sftp |
84 | usable_classes.add(sftp.SFTPTransport) |
85 | @@ -54,27 +51,6 @@ |
86 | t_class = d['transport_class'] |
87 | if t_class in usable_classes: |
88 | result.append((name, d)) |
89 | - try: |
90 | - import breezy.plugins.local_test_server |
91 | - from breezy.plugins.local_test_server import test_server |
92 | - if False: |
93 | - # XXX: Disable since we can't get chmod working for anonymous |
94 | - # user |
95 | - scenario = ('vsftpd', |
96 | - {'transport_class': test_server.FtpTransport, |
97 | - 'transport_server': test_server.Vsftpd, |
98 | - }) |
99 | - result.append(scenario) |
100 | - from test_server import ProftpdFeature |
101 | - if ProftpdFeature().available(): |
102 | - scenario = ('proftpd', |
103 | - {'transport_class': test_server.FtpTransport, |
104 | - 'transport_server': test_server.Proftpd, |
105 | - }) |
106 | - result.append(scenario) |
107 | - # XXX: add support for pyftpdlib |
108 | - except ImportError: |
109 | - pass |
110 | return result |
111 | |
112 | |
113 | |
114 | === modified file 'breezy/tests/__init__.py' |
115 | --- breezy/tests/__init__.py 2018-05-07 17:17:22 +0000 |
116 | +++ breezy/tests/__init__.py 2018-05-11 10:19:44 +0000 |
117 | @@ -179,11 +179,6 @@ |
118 | 'NO_PROXY': None, |
119 | 'all_proxy': None, |
120 | 'ALL_PROXY': None, |
121 | - # Nobody cares about ftp_proxy, FTP_PROXY AFAIK. So far at |
122 | - # least. If you do (care), please update this comment |
123 | - # -- vila 20080401 |
124 | - 'ftp_proxy': None, |
125 | - 'FTP_PROXY': None, |
126 | 'BZR_REMOTE_PATH': None, |
127 | # Generally speaking, we don't want apport reporting on crashes in |
128 | # the test envirnoment unless we're specifically testing apport, |
129 | @@ -3948,7 +3943,6 @@ |
130 | 'breezy.tests.test_fifo_cache', |
131 | 'breezy.tests.test_filters', |
132 | 'breezy.tests.test_filter_tree', |
133 | - 'breezy.tests.test_ftp_transport', |
134 | 'breezy.tests.test_foreign', |
135 | 'breezy.tests.test_generate_docs', |
136 | 'breezy.tests.test_generate_ids', |
137 | |
138 | === modified file 'breezy/tests/blackbox/test_help.py' |
139 | --- breezy/tests/blackbox/test_help.py 2017-05-21 18:10:28 +0000 |
140 | +++ breezy/tests/blackbox/test_help.py 2018-05-11 10:19:44 +0000 |
141 | @@ -66,11 +66,9 @@ |
142 | def test_help_urlspec(self): |
143 | """Smoke test for 'brz help urlspec'""" |
144 | out, err = self.run_bzr('help urlspec') |
145 | - self.assertContainsRe(out, 'aftp://') |
146 | self.assertContainsRe(out, 'bzr://') |
147 | self.assertContainsRe(out, 'bzr\+ssh://') |
148 | self.assertContainsRe(out, 'file://') |
149 | - self.assertContainsRe(out, 'ftp://') |
150 | self.assertContainsRe(out, 'http://') |
151 | self.assertContainsRe(out, 'https://') |
152 | self.assertContainsRe(out, 'sftp://') |
153 | |
154 | === removed directory 'breezy/tests/ftp_server' |
155 | === removed file 'breezy/tests/ftp_server/__init__.py' |
156 | --- breezy/tests/ftp_server/__init__.py 2017-06-11 13:22:31 +0000 |
157 | +++ breezy/tests/ftp_server/__init__.py 1970-01-01 00:00:00 +0000 |
158 | @@ -1,82 +0,0 @@ |
159 | -# Copyright (C) 2009, 2010 Canonical Ltd |
160 | -# |
161 | -# This program is free software; you can redistribute it and/or modify |
162 | -# it under the terms of the GNU General Public License as published by |
163 | -# the Free Software Foundation; either version 2 of the License, or |
164 | -# (at your option) any later version. |
165 | -# |
166 | -# This program is distributed in the hope that it will be useful, |
167 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
168 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
169 | -# GNU General Public License for more details. |
170 | -# |
171 | -# You should have received a copy of the GNU General Public License |
172 | -# along with this program; if not, write to the Free Software |
173 | -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
174 | - |
175 | -""" |
176 | -Facilities to use ftp test servers. |
177 | -""" |
178 | - |
179 | -import sys |
180 | - |
181 | -from breezy import tests |
182 | -from breezy.tests import ( |
183 | - features, |
184 | - ) |
185 | - |
186 | - |
187 | -try: |
188 | - import pyftpdlib |
189 | -except ImportError: |
190 | - pyftpdlib_available = False |
191 | -else: |
192 | - pyftpdlib_available = True |
193 | - |
194 | - |
195 | -class _FTPServerFeature(features.Feature): |
196 | - """Some tests want an FTP Server, check if one is available. |
197 | - |
198 | - Right now, the only way this is available is if one of the following is |
199 | - installed: |
200 | - |
201 | - - 'pyftpdlib': http://code.google.com/p/pyftpdlib/ |
202 | - """ |
203 | - |
204 | - def _probe(self): |
205 | - return pyftpdlib_available |
206 | - |
207 | - def feature_name(self): |
208 | - return 'FTPServer' |
209 | - |
210 | - |
211 | -FTPServerFeature = _FTPServerFeature() |
212 | - |
213 | - |
214 | -class UnavailableFTPTestServer(object): |
215 | - """Dummy ftp test server. |
216 | - |
217 | - This allows the test suite report the number of tests needing that |
218 | - feature. We raise UnavailableFeature from methods before the test server is |
219 | - being used. Doing so in the setUp method has bad side-effects (tearDown is |
220 | - never called). |
221 | - """ |
222 | - |
223 | - def start_server(self, vfs_server=None): |
224 | - pass |
225 | - |
226 | - def stop_server(self): |
227 | - pass |
228 | - |
229 | - def get_url(self): |
230 | - raise tests.UnavailableFeature(FTPServerFeature) |
231 | - |
232 | - def get_bogus_url(self): |
233 | - raise tests.UnavailableFeature(FTPServerFeature) |
234 | - |
235 | - |
236 | -if pyftpdlib_available: |
237 | - from . import pyftpdlib_based |
238 | - FTPTestServer = pyftpdlib_based.FTPTestServer |
239 | -else: |
240 | - FTPTestServer = UnavailableFTPTestServer |
241 | |
242 | === removed file 'breezy/tests/ftp_server/pyftpdlib_based.py' |
243 | --- breezy/tests/ftp_server/pyftpdlib_based.py 2017-06-11 13:36:02 +0000 |
244 | +++ breezy/tests/ftp_server/pyftpdlib_based.py 1970-01-01 00:00:00 +0000 |
245 | @@ -1,223 +0,0 @@ |
246 | -# Copyright (C) 2009, 2010 Canonical Ltd |
247 | -# |
248 | -# This program is free software; you can redistribute it and/or modify |
249 | -# it under the terms of the GNU General Public License as published by |
250 | -# the Free Software Foundation; either version 2 of the License, or |
251 | -# (at your option) any later version. |
252 | -# |
253 | -# This program is distributed in the hope that it will be useful, |
254 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
255 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
256 | -# GNU General Public License for more details. |
257 | -# |
258 | -# You should have received a copy of the GNU General Public License |
259 | -# along with this program; if not, write to the Free Software |
260 | -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
261 | -""" |
262 | -FTP test server. |
263 | - |
264 | -Based on pyftpdlib: http://code.google.com/p/pyftpdlib/ |
265 | -""" |
266 | - |
267 | -import errno |
268 | -import logging |
269 | -import os |
270 | -import pyftpdlib |
271 | -import sys |
272 | -from pyftpdlib.authorizers import ( |
273 | - AuthorizerError, |
274 | - DummyAuthorizer, |
275 | - ) |
276 | -from pyftpdlib.filesystems import AbstractedFS |
277 | -from pyftpdlib.handlers import ( |
278 | - FTPHandler, |
279 | - proto_cmds, |
280 | - ) |
281 | -from pyftpdlib.servers import FTPServer |
282 | -import select |
283 | -import threading |
284 | - |
285 | - |
286 | -from breezy import ( |
287 | - osutils, |
288 | - tests, |
289 | - trace, |
290 | - ) |
291 | -from breezy.tests import test_server |
292 | - |
293 | - |
294 | -class NullHandler(logging.Handler): |
295 | - |
296 | - def emit(self, record): |
297 | - pass |
298 | - |
299 | -# Shut up very verbose pyftpdlib |
300 | -logging.getLogger('pyftpdlib').addHandler(NullHandler()) |
301 | - |
302 | - |
303 | -# Convert the pyftplib string version into a tuple to avoid traps in string |
304 | -# comparison. |
305 | -pyftplib_version = tuple(map(int, pyftpdlib.__ver__.split('.'))) |
306 | - |
307 | - |
308 | -class AnonymousWithWriteAccessAuthorizer(DummyAuthorizer): |
309 | - |
310 | - def _check_permissions(self, username, perm): |
311 | - # Like base implementation but don't warn about write permissions |
312 | - # assigned to anonymous, since that's exactly our purpose. |
313 | - for p in perm: |
314 | - if p not in self.read_perms + self.write_perms: |
315 | - raise AuthorizerError('No such permission "%s"' %p) |
316 | - |
317 | - |
318 | -class BzrConformingFS(AbstractedFS): |
319 | - |
320 | - def chmod(self, path, mode): |
321 | - return os.chmod(path, mode) |
322 | - |
323 | - def listdir(self, path): |
324 | - """List the content of a directory.""" |
325 | - return [osutils.safe_utf8(s) for s in os.listdir(path)] |
326 | - |
327 | - def fs2ftp(self, fspath): |
328 | - p = AbstractedFS.fs2ftp(self, osutils.safe_unicode(fspath)) |
329 | - return osutils.safe_utf8(p) |
330 | - |
331 | - def ftp2fs(self, ftppath): |
332 | - p = osutils.safe_unicode(ftppath) |
333 | - return AbstractedFS.ftp2fs(self, p) |
334 | - |
335 | - |
336 | -class BzrConformingFTPHandler(FTPHandler): |
337 | - |
338 | - abstracted_fs = BzrConformingFS |
339 | - |
340 | - def __init__(self, conn, server, ioloop=None): |
341 | - FTPHandler.__init__(self, conn, server) |
342 | - self.authorizer = server.authorizer |
343 | - |
344 | - def ftp_SIZE(self, path): |
345 | - # bzr is overly picky here, but we want to make the test suite pass |
346 | - # first. This may need to be revisited -- vila 20090226 |
347 | - line = self.fs.fs2ftp(path) |
348 | - if self.fs.isdir(self.fs.realpath(path)): |
349 | - why = "%s is a directory" % line |
350 | - self.log('FAIL SIZE "%s". %s.' % (line, why)) |
351 | - self.respond("550 %s." %why) |
352 | - else: |
353 | - FTPHandler.ftp_SIZE(self, path) |
354 | - |
355 | - def ftp_NLST(self, path): |
356 | - # bzr is overly picky here, but we want to make the test suite pass |
357 | - # first. This may need to be revisited -- vila 20090226 |
358 | - line = self.fs.fs2ftp(path) |
359 | - if self.fs.isfile(self.fs.realpath(path)): |
360 | - why = "Not a directory: %s" % line |
361 | - self.log('FAIL NLST "%s". %s.' % (line, why)) |
362 | - self.respond("550 %s." %why) |
363 | - else: |
364 | - FTPHandler.ftp_NLST(self, path) |
365 | - |
366 | - def log_cmd(self, cmd, arg, respcode, respstr): |
367 | - # base class version choke on unicode, the alternative is to just |
368 | - # provide an empty implementation and relies on the client to do |
369 | - # the logging for debugging purposes. Not worth the trouble so far |
370 | - # -- vila 20110607 |
371 | - if cmd in ("DELE", "RMD", "RNFR", "RNTO", "MKD"): |
372 | - line = '"%s" %s' % (' '.join([cmd, unicode(arg)]).strip(), respcode) |
373 | - self.log(line) |
374 | - |
375 | - |
376 | -# An empty password is valid, hence the arg is neither mandatory nor forbidden |
377 | -proto_cmds['PASS']['arg'] = None |
378 | - |
379 | -class ftp_server(FTPServer): |
380 | - |
381 | - def __init__(self, address, handler, authorizer): |
382 | - FTPServer.__init__(self, address, handler) |
383 | - self.authorizer = authorizer |
384 | - # Worth backporting upstream ? |
385 | - self.addr = self.socket.getsockname() |
386 | - |
387 | - |
388 | -class FTPTestServer(test_server.TestServer): |
389 | - """Common code for FTP server facilities.""" |
390 | - |
391 | - def __init__(self): |
392 | - self._root = None |
393 | - self._ftp_server = None |
394 | - self._port = None |
395 | - self._async_thread = None |
396 | - # ftp server logs |
397 | - self.logs = [] |
398 | - self._ftpd_running = False |
399 | - |
400 | - def get_url(self): |
401 | - """Calculate an ftp url to this server.""" |
402 | - return 'ftp://anonymous@localhost:%d/' % (self._port) |
403 | - |
404 | - def get_bogus_url(self): |
405 | - """Return a URL which cannot be connected to.""" |
406 | - return 'ftp://127.0.0.1:1/' |
407 | - |
408 | - def log(self, message): |
409 | - """This is used by ftp_server to log connections, etc.""" |
410 | - self.logs.append(message) |
411 | - |
412 | - def start_server(self, vfs_server=None): |
413 | - if not (vfs_server is None or isinstance(vfs_server, |
414 | - test_server.LocalURLServer)): |
415 | - raise AssertionError( |
416 | - "FTPServer currently assumes local transport, got %s" |
417 | - % vfs_server) |
418 | - self._root = osutils.getcwd() |
419 | - |
420 | - address = ('localhost', 0) # bind to a random port |
421 | - authorizer = AnonymousWithWriteAccessAuthorizer() |
422 | - authorizer.add_anonymous(self._root, perm='elradfmwM') |
423 | - self._ftp_server = ftp_server(address, BzrConformingFTPHandler, |
424 | - authorizer) |
425 | - |
426 | - self._port = self._ftp_server.socket.getsockname()[1] |
427 | - self._ftpd_starting = threading.Lock() |
428 | - self._ftpd_starting.acquire() # So it can be released by the server |
429 | - self._ftpd_thread = threading.Thread(target=self._run_server,) |
430 | - self._ftpd_thread.start() |
431 | - if 'threads' in tests.selftest_debug_flags: |
432 | - sys.stderr.write('Thread started: %s\n' |
433 | - % (self._ftpd_thread.ident,)) |
434 | - # Wait for the server thread to start (i.e release the lock) |
435 | - self._ftpd_starting.acquire() |
436 | - self._ftpd_starting.release() |
437 | - |
438 | - def stop_server(self): |
439 | - """See breezy.transport.Server.stop_server.""" |
440 | - # Tell the server to stop, but also close the server socket for tests |
441 | - # that start the server but never initiate a connection. Closing the |
442 | - # socket should be done first though, to avoid further connections. |
443 | - self._ftp_server.close() |
444 | - self._ftpd_running = False |
445 | - self._ftpd_thread.join() |
446 | - if 'threads' in tests.selftest_debug_flags: |
447 | - sys.stderr.write('Thread joined: %s\n' |
448 | - % (self._ftpd_thread.ident,)) |
449 | - |
450 | - def _run_server(self): |
451 | - """Run the server until stop_server is called. |
452 | - |
453 | - Shut it down properly then. |
454 | - """ |
455 | - self._ftpd_running = True |
456 | - self._ftpd_starting.release() |
457 | - while self._ftpd_running: |
458 | - try: |
459 | - self._ftp_server.serve_forever(timeout=0.1) |
460 | - except select.error as e: |
461 | - if e.args[0] != errno.EBADF: |
462 | - raise |
463 | - self._ftp_server.close_all() |
464 | - |
465 | - def add_user(self, user, password): |
466 | - """Add a user with write access.""" |
467 | - self._ftp_server.authorizer.add_user(user, password, self._root, |
468 | - perm='elradfmwM') |
469 | |
470 | === removed file 'breezy/tests/test_ftp_transport.py' |
471 | --- breezy/tests/test_ftp_transport.py 2017-05-22 00:56:52 +0000 |
472 | +++ breezy/tests/test_ftp_transport.py 1970-01-01 00:00:00 +0000 |
473 | @@ -1,151 +0,0 @@ |
474 | -# Copyright (C) 2006, 2007, 2009, 2010, 2011 Canonical Ltd |
475 | -# |
476 | -# This program is free software; you can redistribute it and/or modify |
477 | -# it under the terms of the GNU General Public License as published by |
478 | -# the Free Software Foundation; either version 2 of the License, or |
479 | -# (at your option) any later version. |
480 | -# |
481 | -# This program is distributed in the hope that it will be useful, |
482 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
483 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
484 | -# GNU General Public License for more details. |
485 | -# |
486 | -# You should have received a copy of the GNU General Public License |
487 | -# along with this program; if not, write to the Free Software |
488 | -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
489 | - |
490 | -import ftplib |
491 | -import getpass |
492 | - |
493 | -from .. import ( |
494 | - config, |
495 | - errors, |
496 | - tests, |
497 | - transport, |
498 | - ui, |
499 | - urlutils, |
500 | - ) |
501 | - |
502 | -from ..transport import ftp |
503 | - |
504 | -from . import ftp_server |
505 | - |
506 | - |
507 | -class TestCaseWithFTPServer(tests.TestCaseWithTransport): |
508 | - |
509 | - _test_needs_features = [ftp_server.FTPServerFeature] |
510 | - |
511 | - def setUp(self): |
512 | - self.transport_server = ftp_server.FTPTestServer |
513 | - super(TestCaseWithFTPServer, self).setUp() |
514 | - |
515 | - |
516 | -class TestCaseAFTP(tests.TestCaseWithTransport): |
517 | - """Test aftp transport.""" |
518 | - |
519 | - def test_aftp_degrade(self): |
520 | - t = transport.get_transport_from_url('aftp://host/path') |
521 | - self.assertTrue(t.is_active) |
522 | - parent = t.clone('..') |
523 | - self.assertTrue(parent.is_active) |
524 | - |
525 | - self.assertEqual('aftp://host/path', t.abspath('')) |
526 | - |
527 | - |
528 | -class TestFTPTestServer(TestCaseWithFTPServer): |
529 | - |
530 | - def test_basic_exists(self): |
531 | - url = self.get_url() |
532 | - self.assertStartsWith(url, 'ftp://') |
533 | - |
534 | - t = self.get_transport() |
535 | - t.put_bytes('foo', 'test bytes\n') |
536 | - self.assertEqual('test bytes\n', t.get_bytes('foo')) |
537 | - |
538 | - def test__reconnect(self): |
539 | - t = self.get_transport() |
540 | - t.put_bytes('foo', 'test bytes\n') |
541 | - self.assertEqual('test bytes\n', t.get_bytes('foo')) |
542 | - t._reconnect() |
543 | - t.put_bytes('foo', 'test more bytes\n') |
544 | - self.assertEqual('test more bytes\n', t.get_bytes('foo')) |
545 | - |
546 | - |
547 | -class TestFTPTestServerUI(TestCaseWithFTPServer): |
548 | - |
549 | - def setUp(self): |
550 | - super(TestFTPTestServerUI, self).setUp() |
551 | - self.user = 'joe' |
552 | - self.password = 'secret' |
553 | - self.get_server().add_user(self.user, self.password) |
554 | - |
555 | - def get_url(self, relpath=None): |
556 | - """Overrides get_url to inject our user.""" |
557 | - base = super(TestFTPTestServerUI, self).get_url(relpath) |
558 | - parsed_url = transport.ConnectedTransport._split_url(base) |
559 | - new_url = parsed_url.clone() |
560 | - new_url.user = self.user |
561 | - new_url.quoted_user = urlutils.quote(self.user) |
562 | - new_url.password = self.password |
563 | - new_url.quoted_password = urlutils.quote(self.password) |
564 | - return str(new_url) |
565 | - |
566 | - def test_no_prompt_for_username(self): |
567 | - """ensure getpass.getuser() is used if there's no username in the |
568 | - configuration.""", |
569 | - self.get_server().add_user(getpass.getuser(), self.password) |
570 | - t = self.get_transport() |
571 | - ui.ui_factory = ui.CannedInputUIFactory([self.password]) |
572 | - # Issue a request to the server to connect |
573 | - t.put_bytes('foo', 'test bytes\n') |
574 | - self.assertEqual('test bytes\n', t.get_bytes('foo')) |
575 | - # Only the password should've been read |
576 | - ui.ui_factory.assert_all_input_consumed() |
577 | - |
578 | - def test_prompt_for_password(self): |
579 | - t = self.get_transport() |
580 | - ui.ui_factory = ui.CannedInputUIFactory([self.password]) |
581 | - # Issue a request to the server to connect |
582 | - t.has('whatever/not/existing') |
583 | - # stdin should be empty (the provided password have been consumed) |
584 | - ui.ui_factory.assert_all_input_consumed() |
585 | - |
586 | - def test_no_prompt_for_password_when_using_auth_config(self): |
587 | - t = self.get_transport() |
588 | - ui.ui_factory = ui.CannedInputUIFactory([]) |
589 | - # Create a config file with the right password |
590 | - conf = config.AuthenticationConfig() |
591 | - conf._get_config().update({'ftptest': {'scheme': 'ftp', |
592 | - 'user': self.user, |
593 | - 'password': self.password}}) |
594 | - conf._save() |
595 | - # Issue a request to the server to connect |
596 | - t.put_bytes('foo', 'test bytes\n') |
597 | - self.assertEqual('test bytes\n', t.get_bytes('foo')) |
598 | - |
599 | - def test_empty_password(self): |
600 | - # Override the default user/password from setUp |
601 | - self.user = 'jim' |
602 | - self.password = '' |
603 | - self.get_server().add_user(self.user, self.password) |
604 | - t = self.get_transport() |
605 | - ui.ui_factory = ui.CannedInputUIFactory([self.password]) |
606 | - # Issue a request to the server to connect |
607 | - t.has('whatever/not/existing') |
608 | - # stdin should be empty (the provided password have been consumed), |
609 | - # even if the password is empty, it's followed by a newline. |
610 | - ui.ui_factory.assert_all_input_consumed() |
611 | - |
612 | - |
613 | -class TestFTPErrorTranslation(tests.TestCase): |
614 | - |
615 | - def test_translate_directory_not_empty(self): |
616 | - # https://bugs.launchpad.net/bugs/528722 |
617 | - |
618 | - t = ftp.FtpTransport("ftp://none/") |
619 | - |
620 | - try: |
621 | - raise ftplib.error_temp("Rename/move failure: Directory not empty") |
622 | - except Exception as e: |
623 | - e = self.assertRaises(errors.DirectoryNotEmpty, |
624 | - t._translate_ftp_error, e, "/path") |
625 | |
626 | === modified file 'breezy/tests/test_http.py' |
627 | --- breezy/tests/test_http.py 2018-04-01 02:54:41 +0000 |
628 | +++ breezy/tests/test_http.py 2018-05-11 10:19:44 +0000 |
629 | @@ -1895,9 +1895,9 @@ |
630 | def test_redirected_to_same_host_different_protocol(self): |
631 | t = self._transport('http://www.example.com/foo') |
632 | r = t._redirected_to('http://www.example.com/foo', |
633 | - 'ftp://www.example.com/foo') |
634 | + 'bzr://www.example.com/foo') |
635 | self.assertNotEqual(type(r), type(t)) |
636 | - self.assertEqual('ftp://www.example.com/foo/', r.external_url()) |
637 | + self.assertEqual('bzr://www.example.com/foo/', r.external_url()) |
638 | |
639 | def test_redirected_to_same_host_specific_implementation(self): |
640 | t = self._transport('http://www.example.com/foo') |
641 | |
642 | === modified file 'breezy/tests/test_smart_transport.py' |
643 | --- breezy/tests/test_smart_transport.py 2018-03-25 11:34:51 +0000 |
644 | +++ breezy/tests/test_smart_transport.py 2018-05-11 10:19:44 +0000 |
645 | @@ -4310,7 +4310,7 @@ |
646 | def test_redirected_to_same_host_different_protocol(self): |
647 | t = remote.RemoteHTTPTransport('bzr+http://joe@www.example.com/foo') |
648 | r = t._redirected_to('http://www.example.com/foo', |
649 | - 'ftp://www.example.com/foo') |
650 | + 'bzr://www.example.com/foo') |
651 | self.assertNotEqual(type(r), type(t)) |
652 | |
653 | |
654 | |
655 | === modified file 'breezy/tests/transport_util.py' |
656 | --- breezy/tests/transport_util.py 2017-05-22 00:56:52 +0000 |
657 | +++ breezy/tests/transport_util.py 2018-05-11 10:19:44 +0000 |
658 | @@ -15,24 +15,14 @@ |
659 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
660 | |
661 | from . import features |
662 | - |
663 | -# SFTPTransport offers better performances but relies on paramiko, if paramiko |
664 | -# is not available, we fallback to FtpTransport |
665 | -if features.paramiko.available(): |
666 | - from . import test_sftp_transport |
667 | - from ..transport import sftp, Transport |
668 | - _backing_scheme = 'sftp' |
669 | - _backing_transport_class = sftp.SFTPTransport |
670 | - _backing_test_class = test_sftp_transport.TestCaseWithSFTPServer |
671 | -else: |
672 | - from ..transport import ftp, Transport |
673 | - from . import test_ftp_transport |
674 | - _backing_scheme = 'ftp' |
675 | - _backing_transport_class = ftp.FtpTransport |
676 | - _backing_test_class = test_ftp_transport.TestCaseWithFTPServer |
677 | - |
678 | - |
679 | -class TestCaseWithConnectionHookedTransport(_backing_test_class): |
680 | +from . import TestCaseWithTransport |
681 | +from ..transport import Transport |
682 | + |
683 | +# SFTPTransport is the only bundled transport that properly counts connections |
684 | +# at the moment. |
685 | +from . import test_sftp_transport |
686 | + |
687 | +class TestCaseWithConnectionHookedTransport(test_sftp_transport.TestCaseWithSFTPServer): |
688 | |
689 | def setUp(self): |
690 | super(TestCaseWithConnectionHookedTransport, self).setUp() |
691 | @@ -44,4 +34,3 @@ |
692 | |
693 | def reset_connections(self): |
694 | self.connections = [] |
695 | - |
696 | |
697 | === modified file 'breezy/transport/__init__.py' |
698 | --- breezy/transport/__init__.py 2018-05-05 20:39:37 +0000 |
699 | +++ breezy/transport/__init__.py 2018-05-11 10:19:44 +0000 |
700 | @@ -1686,33 +1686,10 @@ |
701 | register_lazy_transport('https://', 'breezy.transport.http', |
702 | 'HttpTransport') |
703 | |
704 | -register_transport_proto('ftp://', help="Access using passive FTP.") |
705 | -register_lazy_transport('ftp://', 'breezy.transport.ftp', 'FtpTransport') |
706 | -register_transport_proto('aftp://', help="Access using active FTP.") |
707 | -register_lazy_transport('aftp://', 'breezy.transport.ftp', 'FtpTransport') |
708 | register_transport_proto('gio+', help="Access using any GIO supported protocols.") |
709 | register_lazy_transport('gio+', 'breezy.transport.gio_transport', 'GioTransport') |
710 | |
711 | |
712 | -# Default to trying GSSAPI authentication (if the kerberos module is |
713 | -# available) |
714 | -register_transport_proto('ftp+gssapi://', register_netloc=True) |
715 | -register_transport_proto('aftp+gssapi://', register_netloc=True) |
716 | -register_transport_proto('ftp+nogssapi://', register_netloc=True) |
717 | -register_transport_proto('aftp+nogssapi://', register_netloc=True) |
718 | -register_lazy_transport('ftp+gssapi://', 'breezy.transport.ftp._gssapi', |
719 | - 'GSSAPIFtpTransport') |
720 | -register_lazy_transport('aftp+gssapi://', 'breezy.transport.ftp._gssapi', |
721 | - 'GSSAPIFtpTransport') |
722 | -register_lazy_transport('ftp://', 'breezy.transport.ftp._gssapi', |
723 | - 'GSSAPIFtpTransport') |
724 | -register_lazy_transport('aftp://', 'breezy.transport.ftp._gssapi', |
725 | - 'GSSAPIFtpTransport') |
726 | -register_lazy_transport('ftp+nogssapi://', 'breezy.transport.ftp', |
727 | - 'FtpTransport') |
728 | -register_lazy_transport('aftp+nogssapi://', 'breezy.transport.ftp', |
729 | - 'FtpTransport') |
730 | - |
731 | register_transport_proto('memory://') |
732 | register_lazy_transport('memory://', 'breezy.transport.memory', |
733 | 'MemoryTransport') |
734 | |
735 | === removed directory 'breezy/transport/ftp' |
736 | === removed file 'breezy/transport/ftp/__init__.py' |
737 | --- breezy/transport/ftp/__init__.py 2017-11-12 13:53:51 +0000 |
738 | +++ breezy/transport/ftp/__init__.py 1970-01-01 00:00:00 +0000 |
739 | @@ -1,638 +0,0 @@ |
740 | -# Copyright (C) 2005-2010 Canonical Ltd |
741 | -# |
742 | -# This program is free software; you can redistribute it and/or modify |
743 | -# it under the terms of the GNU General Public License as published by |
744 | -# the Free Software Foundation; either version 2 of the License, or |
745 | -# (at your option) any later version. |
746 | -# |
747 | -# This program is distributed in the hope that it will be useful, |
748 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
749 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
750 | -# GNU General Public License for more details. |
751 | -# |
752 | -# You should have received a copy of the GNU General Public License |
753 | -# along with this program; if not, write to the Free Software |
754 | -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
755 | - |
756 | -"""Implementation of Transport over ftp. |
757 | - |
758 | -Written by Daniel Silverstone <dsilvers@digital-scurf.org> with serious |
759 | -cargo-culting from the sftp transport and the http transport. |
760 | - |
761 | -It provides the ftp:// and aftp:// protocols where ftp:// is passive ftp |
762 | -and aftp:// is active ftp. Most people will want passive ftp for traversing |
763 | -NAT and other firewalls, so it's best to use it unless you explicitly want |
764 | -active, in which case aftp:// will be your friend. |
765 | -""" |
766 | - |
767 | -from __future__ import absolute_import |
768 | - |
769 | -import ftplib |
770 | -import getpass |
771 | -import os |
772 | -import random |
773 | -import socket |
774 | -import stat |
775 | -import time |
776 | - |
777 | -from ... import ( |
778 | - config, |
779 | - errors, |
780 | - osutils, |
781 | - urlutils, |
782 | - ) |
783 | -from ...sixish import ( |
784 | - BytesIO, |
785 | - ) |
786 | -from ...trace import mutter, warning |
787 | -from ...transport import ( |
788 | - AppendBasedFileStream, |
789 | - ConnectedTransport, |
790 | - _file_streams, |
791 | - register_urlparse_netloc_protocol, |
792 | - Server, |
793 | - ) |
794 | - |
795 | - |
796 | -register_urlparse_netloc_protocol('aftp') |
797 | - |
798 | - |
799 | -class FtpPathError(errors.PathError): |
800 | - """FTP failed for path: %(path)s%(extra)s""" |
801 | - |
802 | - |
803 | -class FtpStatResult(object): |
804 | - |
805 | - def __init__(self, f, abspath): |
806 | - try: |
807 | - self.st_size = f.size(abspath) |
808 | - self.st_mode = stat.S_IFREG |
809 | - except ftplib.error_perm: |
810 | - pwd = f.pwd() |
811 | - try: |
812 | - f.cwd(abspath) |
813 | - self.st_mode = stat.S_IFDIR |
814 | - finally: |
815 | - f.cwd(pwd) |
816 | - |
817 | - |
818 | -_number_of_retries = 2 |
819 | -_sleep_between_retries = 5 |
820 | - |
821 | -# FIXME: there are inconsistencies in the way temporary errors are |
822 | -# handled. Sometimes we reconnect, sometimes we raise an exception. Care should |
823 | -# be taken to analyze the implications for write operations (read operations |
824 | -# are safe to retry). Overall even some read operations are never |
825 | -# retried. --vila 20070720 (Bug #127164) |
826 | -class FtpTransport(ConnectedTransport): |
827 | - """This is the transport agent for ftp:// access.""" |
828 | - |
829 | - def __init__(self, base, _from_transport=None): |
830 | - """Set the base path where files will be stored.""" |
831 | - if not (base.startswith('ftp://') or base.startswith('aftp://')): |
832 | - raise ValueError(base) |
833 | - super(FtpTransport, self).__init__(base, |
834 | - _from_transport=_from_transport) |
835 | - self._unqualified_scheme = 'ftp' |
836 | - if self._parsed_url.scheme == 'aftp': |
837 | - self.is_active = True |
838 | - else: |
839 | - self.is_active = False |
840 | - |
841 | - # Most modern FTP servers support the APPE command. If ours doesn't, we |
842 | - # (re)set this flag accordingly later. |
843 | - self._has_append = True |
844 | - |
845 | - def _get_FTP(self): |
846 | - """Return the ftplib.FTP instance for this object.""" |
847 | - # Ensures that a connection is established |
848 | - connection = self._get_connection() |
849 | - if connection is None: |
850 | - # First connection ever |
851 | - connection, credentials = self._create_connection() |
852 | - self._set_connection(connection, credentials) |
853 | - return connection |
854 | - |
855 | - connection_class = ftplib.FTP |
856 | - |
857 | - def _create_connection(self, credentials=None): |
858 | - """Create a new connection with the provided credentials. |
859 | - |
860 | - :param credentials: The credentials needed to establish the connection. |
861 | - |
862 | - :return: The created connection and its associated credentials. |
863 | - |
864 | - The input credentials are only the password as it may have been |
865 | - entered interactively by the user and may be different from the one |
866 | - provided in base url at transport creation time. The returned |
867 | - credentials are username, password. |
868 | - """ |
869 | - if credentials is None: |
870 | - user, password = self._user, self._password |
871 | - else: |
872 | - user, password = credentials |
873 | - |
874 | - auth = config.AuthenticationConfig() |
875 | - if user is None: |
876 | - user = auth.get_user('ftp', self._host, port=self._port, |
877 | - default=getpass.getuser()) |
878 | - mutter("Constructing FTP instance against %r" % |
879 | - ((self._host, self._port, user, '********', |
880 | - self.is_active),)) |
881 | - try: |
882 | - connection = self.connection_class() |
883 | - connection.connect(host=self._host, port=self._port) |
884 | - self._login(connection, auth, user, password) |
885 | - connection.set_pasv(not self.is_active) |
886 | - # binary mode is the default |
887 | - connection.voidcmd('TYPE I') |
888 | - except socket.error as e: |
889 | - raise errors.SocketConnectionError(self._host, self._port, |
890 | - msg='Unable to connect to', |
891 | - orig_error= e) |
892 | - except ftplib.error_perm as e: |
893 | - raise errors.TransportError(msg="Error setting up connection:" |
894 | - " %s" % str(e), orig_error=e) |
895 | - return connection, (user, password) |
896 | - |
897 | - def _login(self, connection, auth, user, password): |
898 | - # '' is a valid password |
899 | - if user and user != 'anonymous' and password is None: |
900 | - password = auth.get_password('ftp', self._host, |
901 | - user, port=self._port) |
902 | - connection.login(user=user, passwd=password) |
903 | - |
904 | - def _reconnect(self): |
905 | - """Create a new connection with the previously used credentials""" |
906 | - credentials = self._get_credentials() |
907 | - connection, credentials = self._create_connection(credentials) |
908 | - self._set_connection(connection, credentials) |
909 | - |
910 | - def disconnect(self): |
911 | - connection = self._get_connection() |
912 | - if connection is not None: |
913 | - connection.close() |
914 | - |
915 | - def _translate_ftp_error(self, err, path, extra=None, |
916 | - unknown_exc=FtpPathError): |
917 | - """Try to translate an ftplib exception to a breezy exception. |
918 | - |
919 | - :param err: The error to translate into a bzr error |
920 | - :param path: The path which had problems |
921 | - :param extra: Extra information which can be included |
922 | - :param unknown_exc: If None, we will just raise the original exception |
923 | - otherwise we raise unknown_exc(path, extra=extra) |
924 | - """ |
925 | - # ftp error numbers are very generic, like "451: Requested action aborted, |
926 | - # local error in processing" so unfortunately we have to match by |
927 | - # strings. |
928 | - s = str(err).lower() |
929 | - if not extra: |
930 | - extra = str(err) |
931 | - else: |
932 | - extra += ': ' + str(err) |
933 | - if ('no such file' in s |
934 | - or 'could not open' in s |
935 | - or 'no such dir' in s |
936 | - or 'could not create file' in s # vsftpd |
937 | - or 'file doesn\'t exist' in s |
938 | - or 'rnfr command failed.' in s # vsftpd RNFR reply if file not found |
939 | - or 'file/directory not found' in s # filezilla server |
940 | - # Microsoft FTP-Service RNFR reply if file not found |
941 | - or (s.startswith('550 ') and 'unable to rename to' in extra) |
942 | - # if containing directory doesn't exist, suggested by |
943 | - # <https://bugs.launchpad.net/bzr/+bug/224373> |
944 | - or (s.startswith('550 ') and "can't find folder" in s) |
945 | - ): |
946 | - raise errors.NoSuchFile(path, extra=extra) |
947 | - elif ('file exists' in s): |
948 | - raise errors.FileExists(path, extra=extra) |
949 | - elif ('not a directory' in s): |
950 | - raise errors.PathError(path, extra=extra) |
951 | - elif 'directory not empty' in s: |
952 | - raise errors.DirectoryNotEmpty(path, extra=extra) |
953 | - |
954 | - mutter('unable to understand error for path: %s: %s', path, err) |
955 | - |
956 | - if unknown_exc: |
957 | - raise unknown_exc(path, extra=extra) |
958 | - # TODO: jam 20060516 Consider re-raising the error wrapped in |
959 | - # something like TransportError, but this loses the traceback |
960 | - # Also, 'sftp' has a generic 'Failure' mode, which we use failure_exc |
961 | - # to handle. Consider doing something like that here. |
962 | - #raise TransportError(msg='Error for path: %s' % (path,), orig_error=e) |
963 | - raise |
964 | - |
965 | - def has(self, relpath): |
966 | - """Does the target location exist?""" |
967 | - # FIXME jam 20060516 We *do* ask about directories in the test suite |
968 | - # We don't seem to in the actual codebase |
969 | - # XXX: I assume we're never asked has(dirname) and thus I use |
970 | - # the FTP size command and assume that if it doesn't raise, |
971 | - # all is good. |
972 | - abspath = self._remote_path(relpath) |
973 | - try: |
974 | - f = self._get_FTP() |
975 | - mutter('FTP has check: %s => %s', relpath, abspath) |
976 | - s = f.size(abspath) |
977 | - mutter("FTP has: %s", abspath) |
978 | - return True |
979 | - except ftplib.error_perm as e: |
980 | - if ('is a directory' in str(e).lower()): |
981 | - mutter("FTP has dir: %s: %s", abspath, e) |
982 | - return True |
983 | - mutter("FTP has not: %s: %s", abspath, e) |
984 | - return False |
985 | - |
986 | - def get(self, relpath, retries=0): |
987 | - """Get the file at the given relative path. |
988 | - |
989 | - :param relpath: The relative path to the file |
990 | - :param retries: Number of retries after temporary failures so far |
991 | - for this operation. |
992 | - |
993 | - We're meant to return a file-like object which bzr will |
994 | - then read from. For now we do this via the magic of BytesIO |
995 | - """ |
996 | - try: |
997 | - mutter("FTP get: %s", self._remote_path(relpath)) |
998 | - f = self._get_FTP() |
999 | - ret = BytesIO() |
1000 | - f.retrbinary('RETR '+self._remote_path(relpath), ret.write, 8192) |
1001 | - ret.seek(0) |
1002 | - return ret |
1003 | - except ftplib.error_perm as e: |
1004 | - raise errors.NoSuchFile(self.abspath(relpath), extra=str(e)) |
1005 | - except ftplib.error_temp as e: |
1006 | - if retries > _number_of_retries: |
1007 | - raise errors.TransportError(msg="FTP temporary error during GET %s. Aborting." |
1008 | - % self.abspath(relpath), |
1009 | - orig_error=e) |
1010 | - else: |
1011 | - warning("FTP temporary error: %s. Retrying.", str(e)) |
1012 | - self._reconnect() |
1013 | - return self.get(relpath, retries+1) |
1014 | - except EOFError as e: |
1015 | - if retries > _number_of_retries: |
1016 | - raise errors.TransportError("FTP control connection closed during GET %s." |
1017 | - % self.abspath(relpath), |
1018 | - orig_error=e) |
1019 | - else: |
1020 | - warning("FTP control connection closed. Trying to reopen.") |
1021 | - time.sleep(_sleep_between_retries) |
1022 | - self._reconnect() |
1023 | - return self.get(relpath, retries+1) |
1024 | - |
1025 | - def put_file(self, relpath, fp, mode=None, retries=0): |
1026 | - """Copy the file-like or string object into the location. |
1027 | - |
1028 | - :param relpath: Location to put the contents, relative to base. |
1029 | - :param fp: File-like or string object. |
1030 | - :param retries: Number of retries after temporary failures so far |
1031 | - for this operation. |
1032 | - |
1033 | - TODO: jam 20051215 ftp as a protocol seems to support chmod, but |
1034 | - ftplib does not |
1035 | - """ |
1036 | - abspath = self._remote_path(relpath) |
1037 | - tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(), |
1038 | - os.getpid(), random.randint(0, 0x7FFFFFFF)) |
1039 | - bytes = None |
1040 | - if getattr(fp, 'read', None) is None: |
1041 | - # hand in a string IO |
1042 | - bytes = fp |
1043 | - fp = BytesIO(bytes) |
1044 | - else: |
1045 | - # capture the byte count; .read() may be read only so |
1046 | - # decorate it. |
1047 | - class byte_counter(object): |
1048 | - def __init__(self, fp): |
1049 | - self.fp = fp |
1050 | - self.counted_bytes = 0 |
1051 | - def read(self, count): |
1052 | - result = self.fp.read(count) |
1053 | - self.counted_bytes += len(result) |
1054 | - return result |
1055 | - fp = byte_counter(fp) |
1056 | - try: |
1057 | - mutter("FTP put: %s", abspath) |
1058 | - f = self._get_FTP() |
1059 | - try: |
1060 | - f.storbinary('STOR '+tmp_abspath, fp) |
1061 | - self._rename_and_overwrite(tmp_abspath, abspath, f) |
1062 | - self._setmode(relpath, mode) |
1063 | - if bytes is not None: |
1064 | - return len(bytes) |
1065 | - else: |
1066 | - return fp.counted_bytes |
1067 | - except (ftplib.error_temp, EOFError) as e: |
1068 | - warning("Failure during ftp PUT of %s: %s. Deleting temporary file." |
1069 | - % (tmp_abspath, e, )) |
1070 | - try: |
1071 | - f.delete(tmp_abspath) |
1072 | - except: |
1073 | - warning("Failed to delete temporary file on the" |
1074 | - " server.\nFile: %s", tmp_abspath) |
1075 | - raise e |
1076 | - raise |
1077 | - except ftplib.error_perm as e: |
1078 | - self._translate_ftp_error(e, abspath, extra='could not store', |
1079 | - unknown_exc=errors.NoSuchFile) |
1080 | - except ftplib.error_temp as e: |
1081 | - if retries > _number_of_retries: |
1082 | - raise errors.TransportError( |
1083 | - "FTP temporary error during PUT %s: %s. Aborting." |
1084 | - % (self.abspath(relpath), e), orig_error=e) |
1085 | - else: |
1086 | - warning("FTP temporary error: %s. Retrying.", str(e)) |
1087 | - self._reconnect() |
1088 | - self.put_file(relpath, fp, mode, retries+1) |
1089 | - except EOFError: |
1090 | - if retries > _number_of_retries: |
1091 | - raise errors.TransportError("FTP control connection closed during PUT %s." |
1092 | - % self.abspath(relpath), orig_error=e) |
1093 | - else: |
1094 | - warning("FTP control connection closed. Trying to reopen.") |
1095 | - time.sleep(_sleep_between_retries) |
1096 | - self._reconnect() |
1097 | - self.put_file(relpath, fp, mode, retries+1) |
1098 | - |
1099 | - def mkdir(self, relpath, mode=None): |
1100 | - """Create a directory at the given path.""" |
1101 | - abspath = self._remote_path(relpath) |
1102 | - try: |
1103 | - mutter("FTP mkd: %s", abspath) |
1104 | - f = self._get_FTP() |
1105 | - try: |
1106 | - f.mkd(abspath) |
1107 | - except ftplib.error_reply as e: |
1108 | - # <https://bugs.launchpad.net/bzr/+bug/224373> Microsoft FTP |
1109 | - # server returns "250 Directory created." which is kind of |
1110 | - # reasonable, 250 meaning "requested file action OK", but not what |
1111 | - # Python's ftplib expects. |
1112 | - if e[0][:3] == '250': |
1113 | - pass |
1114 | - else: |
1115 | - raise |
1116 | - self._setmode(relpath, mode) |
1117 | - except ftplib.error_perm as e: |
1118 | - self._translate_ftp_error(e, abspath, |
1119 | - unknown_exc=errors.FileExists) |
1120 | - |
1121 | - def open_write_stream(self, relpath, mode=None): |
1122 | - """See Transport.open_write_stream.""" |
1123 | - self.put_bytes(relpath, "", mode) |
1124 | - result = AppendBasedFileStream(self, relpath) |
1125 | - _file_streams[self.abspath(relpath)] = result |
1126 | - return result |
1127 | - |
1128 | - def recommended_page_size(self): |
1129 | - """See Transport.recommended_page_size(). |
1130 | - |
1131 | - For FTP we suggest a large page size to reduce the overhead |
1132 | - introduced by latency. |
1133 | - """ |
1134 | - return 64 * 1024 |
1135 | - |
1136 | - def rmdir(self, rel_path): |
1137 | - """Delete the directory at rel_path""" |
1138 | - abspath = self._remote_path(rel_path) |
1139 | - try: |
1140 | - mutter("FTP rmd: %s", abspath) |
1141 | - f = self._get_FTP() |
1142 | - f.rmd(abspath) |
1143 | - except ftplib.error_perm as e: |
1144 | - self._translate_ftp_error(e, abspath, unknown_exc=errors.PathError) |
1145 | - |
1146 | - def append_file(self, relpath, f, mode=None): |
1147 | - """Append the text in the file-like object into the final |
1148 | - location. |
1149 | - """ |
1150 | - text = f.read() |
1151 | - abspath = self._remote_path(relpath) |
1152 | - if self.has(relpath): |
1153 | - ftp = self._get_FTP() |
1154 | - result = ftp.size(abspath) |
1155 | - else: |
1156 | - result = 0 |
1157 | - |
1158 | - if self._has_append: |
1159 | - mutter("FTP appe to %s", abspath) |
1160 | - self._try_append(relpath, text, mode) |
1161 | - else: |
1162 | - self._fallback_append(relpath, text, mode) |
1163 | - |
1164 | - return result |
1165 | - |
1166 | - def _try_append(self, relpath, text, mode=None, retries=0): |
1167 | - """Try repeatedly to append the given text to the file at relpath. |
1168 | - |
1169 | - This is a recursive function. On errors, it will be called until the |
1170 | - number of retries is exceeded. |
1171 | - """ |
1172 | - try: |
1173 | - abspath = self._remote_path(relpath) |
1174 | - mutter("FTP appe (try %d) to %s", retries, abspath) |
1175 | - ftp = self._get_FTP() |
1176 | - cmd = "APPE %s" % abspath |
1177 | - conn = ftp.transfercmd(cmd) |
1178 | - conn.sendall(text) |
1179 | - conn.close() |
1180 | - self._setmode(relpath, mode) |
1181 | - ftp.getresp() |
1182 | - except ftplib.error_perm as e: |
1183 | - # Check whether the command is not supported (reply code 502) |
1184 | - if str(e).startswith('502 '): |
1185 | - warning("FTP server does not support file appending natively. " |
1186 | - "Performance may be severely degraded! (%s)", e) |
1187 | - self._has_append = False |
1188 | - self._fallback_append(relpath, text, mode) |
1189 | - else: |
1190 | - self._translate_ftp_error(e, abspath, extra='error appending', |
1191 | - unknown_exc=errors.NoSuchFile) |
1192 | - except ftplib.error_temp as e: |
1193 | - if retries > _number_of_retries: |
1194 | - raise errors.TransportError( |
1195 | - "FTP temporary error during APPEND %s. Aborting." |
1196 | - % abspath, orig_error=e) |
1197 | - else: |
1198 | - warning("FTP temporary error: %s. Retrying.", str(e)) |
1199 | - self._reconnect() |
1200 | - self._try_append(relpath, text, mode, retries+1) |
1201 | - |
1202 | - def _fallback_append(self, relpath, text, mode = None): |
1203 | - remote = self.get(relpath) |
1204 | - remote.seek(0, os.SEEK_END) |
1205 | - remote.write(text) |
1206 | - remote.seek(0) |
1207 | - return self.put_file(relpath, remote, mode) |
1208 | - |
1209 | - def _setmode(self, relpath, mode): |
1210 | - """Set permissions on a path. |
1211 | - |
1212 | - Only set permissions if the FTP server supports the 'SITE CHMOD' |
1213 | - extension. |
1214 | - """ |
1215 | - if mode: |
1216 | - try: |
1217 | - mutter("FTP site chmod: setting permissions to %s on %s", |
1218 | - oct(mode), self._remote_path(relpath)) |
1219 | - ftp = self._get_FTP() |
1220 | - cmd = "SITE CHMOD %s %s" % (oct(mode), |
1221 | - self._remote_path(relpath)) |
1222 | - ftp.sendcmd(cmd) |
1223 | - except ftplib.error_perm as e: |
1224 | - # Command probably not available on this server |
1225 | - warning("FTP Could not set permissions to %s on %s. %s", |
1226 | - oct(mode), self._remote_path(relpath), str(e)) |
1227 | - |
1228 | - # TODO: jam 20060516 I believe ftp allows you to tell an ftp server |
1229 | - # to copy something to another machine. And you may be able |
1230 | - # to give it its own address as the 'to' location. |
1231 | - # So implement a fancier 'copy()' |
1232 | - |
1233 | - def rename(self, rel_from, rel_to): |
1234 | - abs_from = self._remote_path(rel_from) |
1235 | - abs_to = self._remote_path(rel_to) |
1236 | - mutter("FTP rename: %s => %s", abs_from, abs_to) |
1237 | - f = self._get_FTP() |
1238 | - return self._rename(abs_from, abs_to, f) |
1239 | - |
1240 | - def _rename(self, abs_from, abs_to, f): |
1241 | - try: |
1242 | - f.rename(abs_from, abs_to) |
1243 | - except (ftplib.error_temp, ftplib.error_perm) as e: |
1244 | - self._translate_ftp_error(e, abs_from, |
1245 | - ': unable to rename to %r' % (abs_to)) |
1246 | - |
1247 | - def move(self, rel_from, rel_to): |
1248 | - """Move the item at rel_from to the location at rel_to""" |
1249 | - abs_from = self._remote_path(rel_from) |
1250 | - abs_to = self._remote_path(rel_to) |
1251 | - try: |
1252 | - mutter("FTP mv: %s => %s", abs_from, abs_to) |
1253 | - f = self._get_FTP() |
1254 | - self._rename_and_overwrite(abs_from, abs_to, f) |
1255 | - except ftplib.error_perm as e: |
1256 | - self._translate_ftp_error(e, abs_from, |
1257 | - extra='unable to rename to %r' % (rel_to,), |
1258 | - unknown_exc=errors.PathError) |
1259 | - |
1260 | - def _rename_and_overwrite(self, abs_from, abs_to, f): |
1261 | - """Do a fancy rename on the remote server. |
1262 | - |
1263 | - Using the implementation provided by osutils. |
1264 | - """ |
1265 | - osutils.fancy_rename(abs_from, abs_to, |
1266 | - rename_func=lambda p1, p2: self._rename(p1, p2, f), |
1267 | - unlink_func=lambda p: self._delete(p, f)) |
1268 | - |
1269 | - def delete(self, relpath): |
1270 | - """Delete the item at relpath""" |
1271 | - abspath = self._remote_path(relpath) |
1272 | - f = self._get_FTP() |
1273 | - self._delete(abspath, f) |
1274 | - |
1275 | - def _delete(self, abspath, f): |
1276 | - try: |
1277 | - mutter("FTP rm: %s", abspath) |
1278 | - f.delete(abspath) |
1279 | - except ftplib.error_perm as e: |
1280 | - self._translate_ftp_error(e, abspath, 'error deleting', |
1281 | - unknown_exc=errors.NoSuchFile) |
1282 | - |
1283 | - def external_url(self): |
1284 | - """See breezy.transport.Transport.external_url.""" |
1285 | - # FTP URL's are externally usable. |
1286 | - return self.base |
1287 | - |
1288 | - def listable(self): |
1289 | - """See Transport.listable.""" |
1290 | - return True |
1291 | - |
1292 | - def list_dir(self, relpath): |
1293 | - """See Transport.list_dir.""" |
1294 | - basepath = self._remote_path(relpath) |
1295 | - mutter("FTP nlst: %s", basepath) |
1296 | - f = self._get_FTP() |
1297 | - try: |
1298 | - try: |
1299 | - paths = f.nlst(basepath) |
1300 | - except ftplib.error_perm as e: |
1301 | - self._translate_ftp_error(e, relpath, |
1302 | - extra='error with list_dir') |
1303 | - except ftplib.error_temp as e: |
1304 | - # xs4all's ftp server raises a 450 temp error when listing an |
1305 | - # empty directory. Check for that and just return an empty list |
1306 | - # in that case. See bug #215522 |
1307 | - if str(e).lower().startswith('450 no files found'): |
1308 | - mutter('FTP Server returned "%s" for nlst.' |
1309 | - ' Assuming it means empty directory', |
1310 | - str(e)) |
1311 | - return [] |
1312 | - raise |
1313 | - finally: |
1314 | - # Restore binary mode as nlst switch to ascii mode to retrieve file |
1315 | - # list |
1316 | - f.voidcmd('TYPE I') |
1317 | - |
1318 | - # If FTP.nlst returns paths prefixed by relpath, strip 'em |
1319 | - if paths and paths[0].startswith(basepath): |
1320 | - entries = [path[len(basepath)+1:] for path in paths] |
1321 | - else: |
1322 | - entries = paths |
1323 | - # Remove . and .. if present |
1324 | - return [urlutils.escape(entry) for entry in entries |
1325 | - if entry not in ('.', '..')] |
1326 | - |
1327 | - def iter_files_recursive(self): |
1328 | - """See Transport.iter_files_recursive. |
1329 | - |
1330 | - This is cargo-culted from the SFTP transport""" |
1331 | - mutter("FTP iter_files_recursive") |
1332 | - queue = list(self.list_dir(".")) |
1333 | - while queue: |
1334 | - relpath = queue.pop(0) |
1335 | - st = self.stat(relpath) |
1336 | - if stat.S_ISDIR(st.st_mode): |
1337 | - for i, basename in enumerate(self.list_dir(relpath)): |
1338 | - queue.insert(i, relpath+"/"+basename) |
1339 | - else: |
1340 | - yield relpath |
1341 | - |
1342 | - def stat(self, relpath): |
1343 | - """Return the stat information for a file.""" |
1344 | - abspath = self._remote_path(relpath) |
1345 | - try: |
1346 | - mutter("FTP stat: %s", abspath) |
1347 | - f = self._get_FTP() |
1348 | - return FtpStatResult(f, abspath) |
1349 | - except ftplib.error_perm as e: |
1350 | - self._translate_ftp_error(e, abspath, extra='error w/ stat') |
1351 | - |
1352 | - def lock_read(self, relpath): |
1353 | - """Lock the given file for shared (read) access. |
1354 | - :return: A lock object, which should be passed to Transport.unlock() |
1355 | - """ |
1356 | - # The old RemoteBranch ignore lock for reading, so we will |
1357 | - # continue that tradition and return a bogus lock object. |
1358 | - class BogusLock(object): |
1359 | - def __init__(self, path): |
1360 | - self.path = path |
1361 | - def unlock(self): |
1362 | - pass |
1363 | - return BogusLock(relpath) |
1364 | - |
1365 | - def lock_write(self, relpath): |
1366 | - """Lock the given file for exclusive (write) access. |
1367 | - WARNING: many transports do not support this, so trying avoid using it |
1368 | - |
1369 | - :return: A lock object, which should be passed to Transport.unlock() |
1370 | - """ |
1371 | - return self.lock_read(relpath) |
1372 | - |
1373 | - |
1374 | -def get_test_permutations(): |
1375 | - """Return the permutations to be used in testing.""" |
1376 | - from ...tests import ftp_server |
1377 | - return [(FtpTransport, ftp_server.FTPTestServer)] |
1378 | |
1379 | === removed file 'breezy/transport/ftp/_gssapi.py' |
1380 | --- breezy/transport/ftp/_gssapi.py 2018-05-07 12:48:12 +0000 |
1381 | +++ breezy/transport/ftp/_gssapi.py 1970-01-01 00:00:00 +0000 |
1382 | @@ -1,128 +0,0 @@ |
1383 | -# Copyright (C) 2008, 2009, 2010 Canonical Ltd |
1384 | -# |
1385 | -# This program is free software; you can redistribute it and/or modify |
1386 | -# it under the terms of the GNU General Public License as published by |
1387 | -# the Free Software Foundation; either version 2 of the License, or |
1388 | -# (at your option) any later version. |
1389 | -# |
1390 | -# This program is distributed in the hope that it will be useful, |
1391 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1392 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1393 | -# GNU General Public License for more details. |
1394 | -# |
1395 | -# You should have received a copy of the GNU General Public License |
1396 | -# along with this program; if not, write to the Free Software |
1397 | -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
1398 | - |
1399 | -"""Support for secure authentication using GSSAPI over FTP. |
1400 | - |
1401 | -See RFC2228 for details. |
1402 | -""" |
1403 | - |
1404 | -from __future__ import absolute_import |
1405 | - |
1406 | -import base64, ftplib |
1407 | - |
1408 | -from ... import ( |
1409 | - errors, |
1410 | - ) |
1411 | -from ...i18n import gettext |
1412 | -from ...trace import ( |
1413 | - mutter, |
1414 | - note, |
1415 | - ) |
1416 | -from ..ftp import FtpTransport |
1417 | - |
1418 | -try: |
1419 | - import kerberos |
1420 | -except ImportError as e: |
1421 | - mutter('failed to import kerberos lib: %s', str(e)) |
1422 | - raise errors.DependencyNotPresent('kerberos', e) |
1423 | - |
1424 | -if getattr(kerberos, "authGSSClientWrap", None) is None: |
1425 | - raise errors.DependencyNotPresent('kerberos', |
1426 | - "missing encryption function authGSSClientWrap") |
1427 | - |
1428 | - |
1429 | -class GSSAPIFtp(ftplib.FTP): |
1430 | - """Extended version of ftplib.FTP that can authenticate using GSSAPI.""" |
1431 | - |
1432 | - def mic_putcmd(self, line): |
1433 | - rc = kerberos.authGSSClientWrap(self.vc, base64.b64encode(line)) |
1434 | - wrapped = kerberos.authGSSClientResponse(self.vc) |
1435 | - ftplib.FTP.putcmd(self, "MIC " + wrapped) |
1436 | - |
1437 | - def mic_getline(self): |
1438 | - resp = ftplib.FTP.getline(self) |
1439 | - if resp[:4] != '631 ': |
1440 | - raise AssertionError |
1441 | - rc = kerberos.authGSSClientUnwrap(self.vc, resp[4:].strip("\r\n")) |
1442 | - response = base64.b64decode(kerberos.authGSSClientResponse(self.vc)) |
1443 | - return response |
1444 | - |
1445 | - def gssapi_login(self, user): |
1446 | - # Try GSSAPI login first |
1447 | - |
1448 | - # Used FTP response codes: |
1449 | - # 235 [ADAT=base64data] - indicates that the security data exchange |
1450 | - # completed successfully. |
1451 | - # 334 [ADAT=base64data] - indicates that the requested security |
1452 | - # mechanism is ok, and includes security data to be used by the |
1453 | - # client to construct the next command. |
1454 | - # 335 [ADAT=base64data] - indicates that the security data is |
1455 | - # acceptable, and more is required to complete the security |
1456 | - # data exchange. |
1457 | - |
1458 | - resp = self.sendcmd('AUTH GSSAPI') |
1459 | - if resp.startswith('334 '): |
1460 | - rc, self.vc = kerberos.authGSSClientInit("ftp@%s" % self.host) |
1461 | - if kerberos.authGSSClientStep(self.vc, "") != 1: |
1462 | - while resp[:4] in ('334 ', '335 '): |
1463 | - authdata = kerberos.authGSSClientResponse(self.vc) |
1464 | - resp = self.sendcmd('ADAT ' + authdata) |
1465 | - if resp[:9] in ('235 ADAT=', '335 ADAT='): |
1466 | - rc = kerberos.authGSSClientStep(self.vc, resp[9:]) |
1467 | - if not ((resp.startswith('235 ') and rc == 1) or |
1468 | - (resp.startswith('335 ') and rc == 0)): |
1469 | - raise ftplib.error_reply(resp) |
1470 | - note(gettext("Authenticated as %s") % |
1471 | - kerberos.authGSSClientUserName(self.vc)) |
1472 | - |
1473 | - # Monkey patch ftplib |
1474 | - self.putcmd = self.mic_putcmd |
1475 | - self.getline = self.mic_getline |
1476 | - self.sendcmd('USER ' + user) |
1477 | - return resp |
1478 | - mutter("Unable to use GSSAPI authentication: %s", resp) |
1479 | - |
1480 | - |
1481 | -class GSSAPIFtpTransport(FtpTransport): |
1482 | - """FTP transport implementation that will try to use GSSAPI authentication. |
1483 | - |
1484 | - """ |
1485 | - |
1486 | - connection_class = GSSAPIFtp |
1487 | - |
1488 | - def _login(self, connection, auth, user, password): |
1489 | - """Login with GSSAPI Authentication. |
1490 | - |
1491 | - The password is used if GSSAPI Authentication is not available. |
1492 | - |
1493 | - The username and password can both be None, in which case the |
1494 | - credentials specified in the URL or provided by the |
1495 | - AuthenticationConfig() are used. |
1496 | - """ |
1497 | - try: |
1498 | - connection.gssapi_login(user=user) |
1499 | - except ftplib.error_perm as e: |
1500 | - super(GSSAPIFtpTransport, self)._login(connection, auth, |
1501 | - user, password) |
1502 | - |
1503 | - |
1504 | -def get_test_permutations(): |
1505 | - """Return the permutations to be used in testing.""" |
1506 | - from ...tests import ftp_server |
1507 | - if ftp_server.FTPServerFeature.available(): |
1508 | - return [(GSSAPIFtpTransport, ftp_server.FTPTestServer)] |
1509 | - else: |
1510 | - return [] |
I'm a little sad about this (my first patch to bzr was fixing some dumb ftp thing), but I agree with your reasoning. Along with ftp being discouraged as a whole (see also https:/ /lwn.net/ Articles/ 720856/ for instance), removing support altogether seems correct.