How do you check if string is valid filename?


#1

I could write a function for myself but would prefer to use JUCE which already has cross-platform file handling. Is there any way to check if string is a valid filename?

example on windows:

"D:/desktop/" == false
"D:/desktop/file.ext" == false
"file" == true
"file.ext.ext.ext" == true
"fil|*e" == false because of illegal characters

isDirectory() doesn’t help because the directory being checked must exist.

getFileName() doesn’t help because it can turn an invalid file name into a valid one

I could do soemthing like

if (File::createLegalFileName(new_filename).compare(new_filename) != 0)
    return false;

:neutral_face:

Hmm actually the function looks pretty simple, I could recreate this to return false as soon as it finds something wrong.

String File::createLegalFileName (const String& original)
{
    String s (original.removeCharacters ("\"#@,;:<>*^|?\\/"));

    const int maxLength = 128; // only the length of the filename, not the whole path
    const int len = s.length();

    if (len > maxLength)
    {
        const int lastDot = s.lastIndexOfChar ('.');

        if (lastDot > jmax (0, len - 12))
        {
            s = s.substring (0, maxLength - (len - lastDot))
                 + s.substring (lastDot);
        }
        else
        {
            s = s.substring (0, maxLength);
        }
    }

    return s;
}

#2

https://www.juce.com/doc/classFile#a40c39e2a1ce3dc25baea509eb40fe97c

Just create a Valid File name with File::createLegalPathName(myPath) and check if the output is the same as the input.

Something like:
return File::createLegalPathName(myPath) == myPath;

Or if you’re only interested in the filename and not a legal path:
return File::createLegalFileName(myPath) == myPath;


#3

Hmm… just noticed something, createLegalFileName removes the hashtag character which is used extensively in sample libraries… cello_sustain_G#3.wav for example


#4

The purpose of createLegalFileName is to just clean up some random bit of text into a form that’s definitely safe as a filename. If you try to use it like this, you’ll get false negatives.

It’d be pretty easy just to check your string for illegal characters, right? There must be an official list somewhere from Microsoft.


#5

Oh right, if we are dealing with filenames just check for an illegal character… checking if a string is a legal directory is a bit more complicated, which I might need to do. Example ‘:’ is illegal unless it’s part of the drive letter, but only for windows. Actually, I want my program only to allow the creation of cross-platform-compatible directories, so on mac it would be illegal as well as my users will be sharing files between mac and windows.

Created this really fast for checking valid filename:

bool FileHelper::isValidName(const String& filename)
{
    if (filename.isEmpty()) return false;

    if (filename.trim() != filename) return false;

    return filename.removeCharacters("\\/:*?|<>\"") == filename;
}

Edit: May need to also check for \0, newline, and others, since it will be possible for user to attempt to create such strings.
Edit: Yep, need to check for those as well. Looks like FILE::moveFileTo() does not throw error for newlines and I think it fails silently (on Windows) to move the file. I’ll just continue to refine my “valid filename” checker.

Ok made this function

myfile .ext == illegal because of space before extension (yes I’m very strict)
.myfile.ext == legal
" myfile.ext" == illegal because of space at beginning
"myfile.ext " == illegal because of space at end
"my\nfile.ext" == illegal because of newspace character (char value 31 and less is illegal)

bool FileHelper::isValidName(const String& filename)
{
    if (filename.isEmpty()) return false;

    enum MODE { begin, middle, end };
    MODE mode = begin;

    int last_dot_pos = 0;
    int len = filename.length();
    int i = 0;
    auto p = filename.getCharPointer();

    while (i < len)
    {
        switch (mode)
        {
        case begin:
            if (FileHelper::isIllegalChar(*p)) return false;
            if (*p == ' ') return false;
            if (*p == '.') last_dot_pos = i;
            mode = middle;
            ++i;
            ++p;
            continue;
        case middle:
            if (FileHelper::isIllegalChar(*p)) return false;
            if (*p == '.') last_dot_pos = i;
            if (i == len -1) { mode = end; continue; }
            ++i;
            ++p;
            continue;
        case end:
            if (FileHelper::isIllegalChar(*p)) return false;
            if (*p == ' ') return false;
            if (*p == '.') last_dot_pos = i;
            ++i;
            ++p;
            continue;
        }
    }

    // make sure no space before extension
    return filename[std::max(last_dot_pos - 1, 0)] != ' ';
}

I should make a class that can hold the current character and position of character so I just increment once.