Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b939235

Browse files
committed
fix issue #6822: ftplib's storline method doesn't work with text files
1 parent b5c2376 commit b939235

4 files changed

Lines changed: 52 additions & 12 deletions

File tree

Lib/ftplib.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -493,9 +493,15 @@ def storlines(self, cmd, fp, callback=None):
493493
while 1:
494494
buf = fp.readline()
495495
if not buf: break
496-
if buf[-2:] != B_CRLF:
497-
if buf[-1] in B_CRLF: buf = buf[:-1]
498-
buf = buf + B_CRLF
496+
if isinstance(buf, str):
497+
if not buf.endswith(CRLF):
498+
if buf[-1] in CRLF: buf = buf[:-1]
499+
buf = buf + CRLF
500+
buf = bytes(buf, self.encoding)
501+
else:
502+
if not buf.endswith(B_CRLF):
503+
if buf[-1:] in B_CRLF: buf = buf[:-1]
504+
buf = buf + B_CRLF
499505
conn.sendall(buf)
500506
if callback: callback(buf)
501507
conn.close()
@@ -771,9 +777,15 @@ def storlines(self, cmd, fp, callback=None):
771777
while 1:
772778
buf = fp.readline()
773779
if not buf: break
774-
if buf[-2:] != B_CRLF:
775-
if buf[-1] in B_CRLF: buf = buf[:-1]
776-
buf = buf + B_CRLF
780+
if isinstance(buf, str):
781+
if not buf.endswith(CRLF):
782+
if buf[-1] in CRLF: buf = buf[:-1]
783+
buf = buf + CRLF
784+
buf = bytes(buf, self.encoding)
785+
else:
786+
if not buf.endswith(B_CRLF):
787+
if buf[-1:] in B_CRLF: buf = buf[:-1]
788+
buf = buf + B_CRLF
777789
conn.sendall(buf)
778790
if callback: callback(buf)
779791
# shutdown ssl layer
@@ -783,6 +795,7 @@ def storlines(self, cmd, fp, callback=None):
783795
conn.close()
784796
return self.voidresp()
785797

798+
786799
__all__.append('FTP_TLS')
787800
all_errors = (Error, IOError, EOFError, ssl.SSLError)
788801

Lib/test/test_ftplib.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
# the dummy data returned by server over the data channel when
2525
# RETR, LIST and NLST commands are issued
2626
RETR_DATA = 'abcde12345\r\n' * 1000
27+
RETR_TEXT = 'abcd\xe912345\r\n' * 1000
2728
LIST_DATA = 'foo\r\nbar\r\n'
2829
NLST_DATA = 'foo\r\nbar\r\n'
2930

@@ -37,7 +38,7 @@ def __init__(self, conn, baseclass):
3738
self.baseclass.last_received_data = ''
3839

3940
def handle_read(self):
40-
self.baseclass.last_received_data += self.recv(1024).decode('ascii')
41+
self.baseclass.last_received_data += self.recv(1024).decode('latin-1')
4142

4243
def handle_close(self):
4344
# XXX: this method can be called many times in a row for a single
@@ -49,7 +50,7 @@ def handle_close(self):
4950
self.dtp_conn_closed = True
5051

5152
def push(self, what):
52-
super(DummyDTPHandler, self).push(what.encode('ascii'))
53+
super(DummyDTPHandler, self).push(what.encode('latin-1'))
5354

5455
def handle_error(self):
5556
raise
@@ -68,6 +69,7 @@ def __init__(self, conn):
6869
self.last_received_data = ''
6970
self.next_response = ''
7071
self.rest = None
72+
self.current_type = 'a'
7173
self.push('220 welcome')
7274

7375
def collect_incoming_data(self, data):
@@ -175,7 +177,16 @@ def cmd_pwd(self, arg):
175177
self.push('257 "pwd ok"')
176178

177179
def cmd_type(self, arg):
178-
self.push('200 type ok')
180+
# ASCII type
181+
if arg.lower() == 'a':
182+
self.current_type = 'a'
183+
self.push('200 type ok')
184+
# Binary type
185+
elif arg.lower() == 'i':
186+
self.current_type = 'i'
187+
self.push('200 type ok')
188+
else:
189+
self.push('504 unsupported type')
179190

180191
def cmd_quit(self, arg):
181192
self.push('221 quit ok')
@@ -194,7 +205,10 @@ def cmd_retr(self, arg):
194205
offset = int(self.rest)
195206
else:
196207
offset = 0
197-
self.dtp.push(RETR_DATA[offset:])
208+
if self.current_type == 'i':
209+
self.dtp.push(RETR_DATA[offset:])
210+
else:
211+
self.dtp.push(RETR_TEXT[offset:])
198212
self.dtp.close_when_done()
199213
self.rest = None
200214

@@ -511,7 +525,7 @@ def callback(data):
511525
def test_retrlines(self):
512526
received = []
513527
self.client.retrlines('retr', received.append)
514-
self.assertEqual(''.join(received), RETR_DATA.replace('\r\n', ''))
528+
self.assertEqual(''.join(received), RETR_TEXT.replace('\r\n', ''))
515529

516530
def test_storbinary(self):
517531
f = io.BytesIO(RETR_DATA.encode('ascii'))
@@ -530,7 +544,7 @@ def test_storbinary_rest(self):
530544
self.client.storbinary('stor', f, rest=r)
531545
self.assertEqual(self.server.handler_instance.rest, str(r))
532546

533-
def test_storlines(self):
547+
def test_storlines_bytes(self):
534548
f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii'))
535549
self.client.storlines('stor', f)
536550
self.assertEqual(self.server.handler_instance.last_received_data, RETR_DATA)
@@ -540,6 +554,16 @@ def test_storlines(self):
540554
self.client.storlines('stor foo', f, callback=lambda x: flag.append(None))
541555
self.assertTrue(flag)
542556

557+
def test_storlines_str(self):
558+
f = io.StringIO(RETR_TEXT.replace('\r\n', '\n'))
559+
self.client.storlines('stor', f)
560+
self.assertEqual(self.server.handler_instance.last_received_data, RETR_TEXT)
561+
# test new callback arg
562+
flag = []
563+
f.seek(0)
564+
self.client.storlines('stor foo', f, callback=lambda x: flag.append(None))
565+
self.assertTrue(flag)
566+
543567
def test_nlst(self):
544568
self.client.nlst()
545569
self.assertEqual(self.client.nlst(), NLST_DATA.split('\r\n')[:-1])

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,3 +896,4 @@ Uwe Zessin
896896
Tarek Ziadé
897897
Peter Åstrand
898898
Alexander Shigin
899+
Robert DeVaughn

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Extensions
3737
Library
3838
-------
3939

40+
- Issue #6822: ftplib's storlines method doesn't work with text files.
41+
4042
- Issue #2944: asyncore doesn't handle connection refused correctly.
4143

4244
- Issue #4184: Private attributes on smtpd.SMTPChannel made public and

0 commit comments

Comments
 (0)