Skip to content

Commit f9b2ecf

Browse files
authored
ext/ftp: preserve bare CR bytes in ftp_get() ASCII mode (#22364)
In ASCII mode ftp_get() stripped every '\r' and emitted only a following '\n', dropping bare CR bytes not part of a CRLF sequence. Fold CRLF to LF but write a lone '\r' through unchanged, carrying a '\r' on the final byte of a read into the next read and flushing it at EOF, so the buffer boundary behaves the same as the in-buffer case. ftp_nb_get() already does this via the lastch carry in ftp_nb_continue_read().
1 parent d87d0c6 commit f9b2ecf

3 files changed

Lines changed: 72 additions & 5 deletions

File tree

ext/ftp/ftp.c

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,7 @@ bool ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_
850850
goto bail;
851851
}
852852

853+
bool pending_cr = false;
853854
while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
854855
if (rcvd == (size_t)-1) {
855856
goto bail;
@@ -869,13 +870,30 @@ bool ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_
869870
php_stream_write(outstream, ptr, (e - ptr));
870871
ptr = e;
871872
#else
872-
while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) {
873-
php_stream_write(outstream, ptr, (s - ptr));
874-
if (s + 1 < e && *(s + 1) == '\n') {
875-
s++;
873+
if (pending_cr) {
874+
pending_cr = false;
875+
if (*ptr == '\n') {
876876
php_stream_putc(outstream, '\n');
877+
ptr++;
878+
} else {
879+
php_stream_putc(outstream, '\r');
880+
}
881+
}
882+
while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) {
883+
if (s + 1 < e) {
884+
if (*(s + 1) == '\n') {
885+
php_stream_write(outstream, ptr, (s - ptr));
886+
php_stream_putc(outstream, '\n');
887+
ptr = s + 2;
888+
} else {
889+
php_stream_write(outstream, ptr, (s - ptr) + 1);
890+
ptr = s + 1;
891+
}
892+
} else {
893+
php_stream_write(outstream, ptr, (s - ptr));
894+
pending_cr = true;
895+
ptr = s + 1;
877896
}
878-
ptr = s + 1;
879897
}
880898
#endif
881899
if (ptr < e) {
@@ -885,6 +903,9 @@ bool ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_
885903
goto bail;
886904
}
887905
}
906+
if (pending_cr) {
907+
php_stream_putc(outstream, '\r');
908+
}
888909

889910
data_close(ftp);
890911

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
ftp_get() ASCII mode: bare CR is preserved, CRLF folds to LF
3+
--EXTENSIONS--
4+
ftp
5+
pcntl
6+
--FILE--
7+
<?php
8+
require 'server.inc';
9+
10+
$ftp = ftp_connect('127.0.0.1', $port);
11+
ftp_login($ftp, 'user', 'pass');
12+
$ftp or die("Couldn't connect to the server");
13+
14+
$local = __DIR__ . "/bare_cr_out.txt";
15+
16+
$expected = "line1\nba\rre\nend" . str_repeat("X", 4078) . "\r" . str_repeat("Y", 10);
17+
18+
var_dump(ftp_get($ftp, $local, 'bare_cr', FTP_ASCII));
19+
var_dump(file_get_contents($local) === $expected);
20+
21+
var_dump(ftp_get($ftp, $local, 'trailing_cr', FTP_ASCII));
22+
var_dump(file_get_contents($local) === "trail\r");
23+
?>
24+
--CLEAN--
25+
<?php
26+
@unlink(__DIR__ . "/bare_cr_out.txt");
27+
?>
28+
--EXPECT--
29+
bool(true)
30+
bool(true)
31+
bool(true)
32+
bool(true)

ext/ftp/tests/server.inc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,20 @@ if ($pid) {
398398
fputs($fs, str_repeat("A", 4095) . "\r\n" . str_repeat("B", 10));
399399
fputs($s, "226 Closing data Connection.\r\n");
400400
break;
401+
case "bare_cr":
402+
// A bare CR (not part of CRLF) mid-stream, plus a bare CR on
403+
// the final byte of the first FTP_BUFSIZE (4096) read followed
404+
// by a non-LF byte in the next read.
405+
fputs($s, "150 File status okay; about to open data connection.\r\n");
406+
fputs($fs, "line1\r\nba\rre\r\nend" . str_repeat("X", 4078) . "\r" . str_repeat("Y", 10));
407+
fputs($s, "226 Closing data Connection.\r\n");
408+
break;
409+
case "trailing_cr":
410+
// The whole transfer ends on a bare CR.
411+
fputs($s, "150 File status okay; about to open data connection.\r\n");
412+
fputs($fs, "trail\r");
413+
fputs($s, "226 Closing data Connection.\r\n");
414+
break;
401415

402416
default:
403417
fputs($s, "550 {$matches[1]}: No such file or directory \r\n");

0 commit comments

Comments
 (0)