Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validating filenames before opening files for read / write #1367

Merged
merged 3 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions avogadro/io/fileformat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "fileformat.h"

#include <algorithm>
#include <fstream>
#include <locale>
#include <sstream>
Expand All @@ -15,22 +16,54 @@ using std::ifstream;
using std::locale;
using std::ofstream;

FileFormat::FileFormat() : m_mode(None), m_in(nullptr), m_out(nullptr)
{
}
FileFormat::FileFormat() : m_mode(None), m_in(nullptr), m_out(nullptr) {}

FileFormat::~FileFormat()
{
delete m_in;
delete m_out;
}

bool FileFormat::validateFileName(const std::string& fileName)
{
bool valid = !fileName.empty();

if (valid) {
// check if the filename contains invalid characters
static std::string forbiddenChars(",^@={}[]~!?:&*\"|#%<>$\"'();`'");
valid = fileName.find_first_of(forbiddenChars) == std::string::npos;

// check if the filename contains ".." which we should not allow
valid = valid && fileName.find("..") == std::string::npos;
}

// Finally check against Windows names
// .. we do this on all platforms because CON.cif, for example
// is problematic to send to a Windows user.
if (valid) {
static std::string forbiddenNames(
"CON PRN AUX NUL COM1 COM2 COM3 COM4 COM5 "
"COM6 COM7 COM8 COM9 LPT1 LPT2 LPT3 LPT4 "
"LPT5 LPT6 LPT7 LPT8 LPT9");
// case insensitive search, since con.txt is also a problem
// https://stackoverflow.com/a/19839371/131896
auto it = std::search(fileName.begin(), fileName.end(),
forbiddenNames.begin(), forbiddenNames.end(),
[](unsigned char ch1, unsigned char ch2) {
return std::toupper(ch1) == std::toupper(ch2);
});
valid = (it == fileName.end());
}

return valid;
}

bool FileFormat::open(const std::string& fileName_, Operation mode_)
{
close();
m_fileName = fileName_;
m_mode = mode_;
if (!m_fileName.empty()) {
if (!m_fileName.empty() && validateFileName(m_fileName)) {
// Imbue the standard C locale.
locale cLocale("C");
if (m_mode & Read) {
Expand Down Expand Up @@ -143,4 +176,4 @@ void FileFormat::appendError(const std::string& errorString, bool newLine)
m_error += "\n";
}

} // namespace Avogadro
} // namespace Avogadro::Io
16 changes: 14 additions & 2 deletions avogadro/io/fileformat.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ class AVOGADROIO_EXPORT FileFormat
*/
virtual Operations supportedOperations() const = 0;

/**
* @brief Validates the given file name.
*
* Checks if the filename contains any invalid characters (e.g. ..)
* Also checks if the filename contains a restricted name on Windows.
* e.g., CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, etc.
*
* @param fileName The name of the file to be validated.
* @return true if the file name is valid, false otherwise.
*/
static bool validateFileName(const std::string& fileName);

/**
* @brief Open the specified file in Read or Write mode.
* @return True on success, false on failure.
Expand Down Expand Up @@ -252,7 +264,7 @@ inline FileFormat::Operation operator|(FileFormat::Operation a,
static_cast<int>(b));
}

} // end Io namespace
} // end Avogadro namespace
} // namespace Io
} // namespace Avogadro

#endif // AVOGADRO_IO_FILEFORMAT_H
8 changes: 8 additions & 0 deletions avogadro/qtgui/fileformatdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ FileFormatDialog::FormatFilePair FileFormatDialog::fileToRead(
if (fileName.isEmpty()) // user cancel
return result;

if (FileFormat::validateFileName(fileName.toStdString()) == false) {
QMessageBox::warning(
parent, caption,
tr("The file name contains invalid characters. Please choose another "
"file name."));
continue;
}

const Io::FileFormat* format = findFileFormat(
parent, caption, fileName, FileFormat::File | FileFormat::Read);

Expand Down
Loading