diff --git a/.gitattributes b/.gitattributes index 8e3c91a11..237f6e762 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,5 @@ core.eol=lf *.png binary *.qm binary *.zip binary + +test/testdata/crlf.txt text eol=crlf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f135811dc..51be1d0cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,19 +33,19 @@ repos: args: ["--fix=lf"] exclude: | (?x)^( - compile_commands.json | src/webui/www/private/css/lib/.* | - src/webui/www/private/scripts/lib/.* + src/webui/www/private/scripts/lib/.* | + test/testdata/crlf.txt )$ - id: end-of-file-fixer name: Check trailing newlines exclude: | (?x)^( - compile_commands.json | configure | src/webui/www/private/css/lib/.* | - src/webui/www/private/scripts/lib/.* + src/webui/www/private/scripts/lib/.* | + test/testdata/crlf.txt )$ exclude_types: - svg diff --git a/src/base/utils/io.cpp b/src/base/utils/io.cpp index 75a3088d3..902297d6d 100644 --- a/src/base/utils/io.cpp +++ b/src/base/utils/io.cpp @@ -89,16 +89,37 @@ nonstd::expected Utils::IO::readFile(const Pat return nonstd::make_unexpected(ReadError {ReadError::ExceedSize, message}); } - // Do not use `QIODevice::readAll()` it won't stop when reading `/dev/zero` - const QByteArray data = file.read(fileSize); - if (const qint64 dataSize = data.size(); dataSize != fileSize) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) + QByteArray ret {fileSize, Qt::Uninitialized}; +#else + QByteArray ret {static_cast(fileSize), Qt::Uninitialized}; +#endif + const qint64 actualSize = file.read(ret.data(), fileSize); + + if (actualSize < 0) { - const QString message = QCoreApplication::translate("Utils::IO", "Read size mismatch. File: \"%1\". Expected: %2. Actual: %3") - .arg(file.fileName(), QString::number(fileSize), QString::number(dataSize)); - return nonstd::make_unexpected(ReadError {ReadError::SizeMismatch, message}); + const QString message = QCoreApplication::translate("Utils::IO", "File read error. File: \"%1\". Error: \"%2\"") + .arg(file.fileName(), file.errorString()); + return nonstd::make_unexpected(ReadError {ReadError::Failed, message}); } - return data; + if (actualSize < fileSize) + { + // `QIODevice::Text` will convert CRLF to LF on-the-fly and affects return value + // of `qint64 QIODevice::read(char *data, qint64 maxSize)` + if (additionalMode.testFlag(QIODevice::Text)) + { + ret.truncate(actualSize); + } + else + { + const QString message = QCoreApplication::translate("Utils::IO", "Read size mismatch. File: \"%1\". Expected: %2. Actual: %3") + .arg(file.fileName(), QString::number(fileSize), QString::number(actualSize)); + return nonstd::make_unexpected(ReadError {ReadError::SizeMismatch, message}); + } + } + + return ret; } nonstd::expected Utils::IO::saveToFile(const Path &path, const QByteArray &data) diff --git a/src/base/utils/io.h b/src/base/utils/io.h index 3ec29b1cc..1005410c5 100644 --- a/src/base/utils/io.h +++ b/src/base/utils/io.h @@ -89,6 +89,7 @@ namespace Utils::IO { NotExist, ExceedSize, + Failed, // `read()` operation failed SizeMismatch }; diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 51d61d8fb..c85bc9ca8 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -516,6 +516,7 @@ void WebApplication::sendFile(const Path &path) LogMsg(message, Log::WARNING); throw InternalServerErrorHTTPError(readResult.error().message); + case Utils::IO::ReadError::Failed: case Utils::IO::ReadError::SizeMismatch: LogMsg(message, Log::WARNING); throw InternalServerErrorHTTPError(readResult.error().message); diff --git a/test/testdata/crlf.txt b/test/testdata/crlf.txt new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/test/testdata/crlf.txt @@ -0,0 +1,2 @@ + + diff --git a/test/testutilsio.cpp b/test/testutilsio.cpp index 2b036ab03..fdc1358b1 100644 --- a/test/testutilsio.cpp +++ b/test/testutilsio.cpp @@ -77,6 +77,13 @@ private slots: QCOMPARE(readResult.value(), size10Data); } + { + const Path crlfFile = testFolder / Path(u"crlf.txt"_s); + const auto readResult = Utils::IO::readFile(crlfFile, -1, QIODevice::Text); + QCOMPARE(readResult.has_value(), true); + QCOMPARE(readResult.value(), "\n\n"); + } + { const Path nonExistFile = testFolder / Path(u".non_existent_file_1234"_s); const auto readResult = Utils::IO::readFile(nonExistFile, 1);