Skip to content
Snippets Groups Projects
FileValidator.cpp 5.72 KiB
Newer Older
#include "MantidKernel/FileValidator.h"
#include <boost/algorithm/string/case_conv.hpp>
Campbell, Stuart's avatar
Campbell, Stuart committed
#include <Poco/File.h>
#include <Poco/Path.h>
namespace Mantid {
namespace Kernel {

namespace {
// Initialize the static logger
Logger g_log("FileValidator");
 *  @param extensions :: The permitted file extensions (e.g. .RAW)
 *  @param testFileExists :: Flag indicating whether to test for existence of
 * file (default: yes)
 *  @param testCanWrite :: Flag to check if file writing permissible.
FileValidator::FileValidator(const std::vector<std::string> &extensions,
                             bool testFileExists, bool testCanWrite)
    : TypedValidator<std::string>(), m_testExist(testFileExists),
      m_testCanWrite(testCanWrite) {
  for (auto it = extensions.begin(); it != extensions.end(); ++it) {
    const std::string ext = boost::to_lower_copy(*it);
    if (std::find(m_extensions.begin(), m_extensions.end(), ext) ==
        m_extensions.end()) {
      m_extensions.push_back(ext);
/// Destructor
FileValidator::~FileValidator() {}

/// Returns the set of valid values
std::vector<std::string> FileValidator::allowedValues() const {
 * Clone the validator
 * @returns A pointer to a new validator with the same properties as this one
 */
IValidator_sptr FileValidator::clone() const {
  return boost::make_shared<FileValidator>(*this);
/** If m_fullTest=true if checks that the files exists, otherwise just that path
 * syntax looks valid
 *  @returns An error message to display to users or an empty string on no error
 */
std::string FileValidator::checkValidity(const std::string &value) const {
  if (!Poco::Path().tryParse(value)) {

  // Check the extension but just issue a warning if it is not one of the
  // suggested values
  if (!(value.empty())) {
    if (!(this->endswith(value))) {
      // Dropped from warning to debug level as it was printing out on every
      // search of the archive, even when successful. re #5998
      g_log.debug() << "Unrecognised extension in file \"" << value << "\"";
        g_log.debug() << " [ ";
        for (auto it = this->m_extensions.begin();
             it != this->m_extensions.end(); ++it)
          g_log.debug() << *it << " ";
        g_log.debug() << "]";
      g_log.debug() << "\"." << std::endl;
  // create a variable for the absolute path to be used in error messages
  std::string abspath(value);
  if (!value.empty()) {
    Poco::Path path(value);
    if (path.isAbsolute())
      abspath = path.toString();
  }

  // If the file is required to exist check it is there
  if (m_testExist && (value.empty() || !Poco::File(value).exists())) {
    return "File \"" + abspath + "\" not found";
  // If the file is required to be writable...
  if (m_testCanWrite) {
    if (value.empty())
      return "Cannot write to empty filename";

    Poco::File file(value);
    // the check for writable is different for whether or not a version exists
    // this is taken from ConfigService near line 443
    if (file.exists()) {
      try {
          return "File \"" + abspath + "\" cannot be written";
      } catch (std::exception &e) {
        g_log.information()
            << "Encountered exception while checking for writable: "
            << e.what();
    } else // if the file doesn't exist try to temporarily create one
        Poco::Path direc(value);
        if (direc.isAbsolute()) {
          // see if file is writable
          if (Poco::File(direc).canWrite())
            return "";
          else
            return "Cannot write to file \"" + direc.toString() + "\"";

        g_log.debug() << "Do not have enough information to validate \""
                      << abspath << "\"\n";
      } catch (std::exception &e) {
        g_log.information()
            << "Encountered exception while checking for writable: "
            << e.what();
      } catch (...) {
        g_log.information() << "Unknown exception while checking for writable";
  // Otherwise we are okay, file extensions are just a suggestion so no
  // validation on them is necessary
/**
 * Confirm that the value string ends with then ending string.
 * @param value :: The string to check the ending for.
 * @param ending :: The ending the string should have.
bool has_ending(const std::string &value, const std::string &ending) {
  if (ending.empty()) // always match against an empty extension
    return true;
  if (value.length() < ending.length()) // filename is not long enough
    return false;
  int result =
      value.compare(value.length() - ending.length(), ending.length(), ending);
  return (result == 0); // only care if it matches
}

/**
 * Checks the extension of a filename
 * @return flag that true if the extension matches in the filename
 */
bool FileValidator::endswith(const std::string &value) const {
  if (m_extensions.empty()) // automatically match a lack of extensions
    return true;
  if ((m_extensions.size() == 1) && (m_extensions.begin()->empty()))
    return true;

  // create a lowercase copy of the filename
  std::string value_copy(value);
  std::transform(value_copy.begin(), value_copy.end(), value_copy.begin(),
                 tolower);
  for (auto it = m_extensions.begin(); it != m_extensions.end(); ++it) {
    if (has_ending(value, *it)) // original case
      return true;
    if (has_ending(value_copy, *it)) // lower case