diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index 1b1af31bb0c9..900f26804b74 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -850,6 +850,7 @@ bool ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_ goto bail; } + bool pending_cr = false; while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) { if (rcvd == (size_t)-1) { goto bail; @@ -869,13 +870,30 @@ bool ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_ php_stream_write(outstream, ptr, (e - ptr)); ptr = e; #else - while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) { - php_stream_write(outstream, ptr, (s - ptr)); - if (s + 1 < e && *(s + 1) == '\n') { - s++; + if (pending_cr) { + pending_cr = false; + if (*ptr == '\n') { php_stream_putc(outstream, '\n'); + ptr++; + } else { + php_stream_putc(outstream, '\r'); + } + } + while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) { + if (s + 1 < e) { + if (*(s + 1) == '\n') { + php_stream_write(outstream, ptr, (s - ptr)); + php_stream_putc(outstream, '\n'); + ptr = s + 2; + } else { + php_stream_write(outstream, ptr, (s - ptr) + 1); + ptr = s + 1; + } + } else { + php_stream_write(outstream, ptr, (s - ptr)); + pending_cr = true; + ptr = s + 1; } - ptr = s + 1; } #endif if (ptr < e) { @@ -885,6 +903,9 @@ bool ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_ goto bail; } } + if (pending_cr) { + php_stream_putc(outstream, '\r'); + } data_close(ftp); diff --git a/ext/ftp/tests/ftp_get_ascii_bare_cr.phpt b/ext/ftp/tests/ftp_get_ascii_bare_cr.phpt new file mode 100644 index 000000000000..1fe92bf8de7e --- /dev/null +++ b/ext/ftp/tests/ftp_get_ascii_bare_cr.phpt @@ -0,0 +1,32 @@ +--TEST-- +ftp_get() ASCII mode: bare CR is preserved, CRLF folds to LF +--EXTENSIONS-- +ftp +pcntl +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/ftp/tests/server.inc b/ext/ftp/tests/server.inc index 04e2ceefa278..251536ff8202 100644 --- a/ext/ftp/tests/server.inc +++ b/ext/ftp/tests/server.inc @@ -398,6 +398,20 @@ if ($pid) { fputs($fs, str_repeat("A", 4095) . "\r\n" . str_repeat("B", 10)); fputs($s, "226 Closing data Connection.\r\n"); break; + case "bare_cr": + // A bare CR (not part of CRLF) mid-stream, plus a bare CR on + // the final byte of the first FTP_BUFSIZE (4096) read followed + // by a non-LF byte in the next read. + fputs($s, "150 File status okay; about to open data connection.\r\n"); + fputs($fs, "line1\r\nba\rre\r\nend" . str_repeat("X", 4078) . "\r" . str_repeat("Y", 10)); + fputs($s, "226 Closing data Connection.\r\n"); + break; + case "trailing_cr": + // The whole transfer ends on a bare CR. + fputs($s, "150 File status okay; about to open data connection.\r\n"); + fputs($fs, "trail\r"); + fputs($s, "226 Closing data Connection.\r\n"); + break; default: fputs($s, "550 {$matches[1]}: No such file or directory \r\n");