views:

248

answers:

6

I figured this has to be in the RTL somewhere, but I looked and I can't find it.

function IsValidFilename(filename: string): boolean;
//returns True if it would be possible to create or open a file with
//this name, without modifying the current directory structure

In other words, it has to point to an existing folder on a valid local or network drive, and not contain any invalid characters. Do we have anything like that? (Bonus points if it checks the current user's access rights to be sure you can get at the folder in question.)

+3  A: 

Is this too crude, Mason?

function CanCreateFile(const FileName: string): Boolean;
var
  FileName: PWideChar;
  H: THandle;
begin
  H := CreateFile(FileName, GENERIC_READ or GENERIC_WRITE, 0, nil,
       CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0);

  Result := H <> INVALID_HANDLE_VALUE;

  DeleteFile(FileName);
end;
Robert Frank
I guess that might work, if I called that `or FileExists(filename)`.
Mason Wheeler
For the full discussion: http://stackoverflow.com/questions/3599256/how-can-i-use-delphi-to-test-if-a-directory-is-writeable
Jørn E. Angeltveit
@Mason Wheeler: If the file exists, then CanCreateFile(FileName) or FileExists(FileName) will delete the existing file(!) and return false. If you use FileExists(FileName) or CanCreateFile(FileName) then the existing file will still be around and the result will be true. I would recommend to NEVER depend on lazy boolean expressions (i.e. unchecked "Complete boolean eval" in the project options), use an extra if ... then instead.
Jørn E. Angeltveit
@Robert Frank: You have an error in code, BTW. You can't define FileName as a variable, as it is already the name of the parameter. Delete the variable, and use PChar(FileName) instead.
Jørn E. Angeltveit
+1  A: 

Checking if Directory Exists:

In SysUtils, you have: DirectoryExists

Checking the filename:

The invalid file name characters are: \ / : * ? " < > | so you could check the filename like this:

for c in AFileName do
begin
  OK := NOT (C in ['\', '/', ':', '*', '?', '"', '<', '>', '|']);
  if not OK then Break;
end;

Checking if the folder is writable:

Duplicate of: http://stackoverflow.com/questions/3599256/how-can-i-use-delphi-to-test-if-a-directory-is-writeable

Jørn E. Angeltveit
+8  A: 

As far as I know, there is not an RTL or Windows API function to validate a filename, so you must write your own function following the Windows File Naming Conventions:

The following fundamental rules enable applications to create and process valid names for files and directories, regardless of the file system:

  • Use a period to separate the base file name from the extension in the name of a directory or file.
  • Use a backslash () to separate the components of a path. The backslash divides the file name from the path to it, and one directory name from another directory name in a path. You cannot use a backslash in the name for the actual file or directory because it is a reserved character that separates the names into components.
  • Use a backslash as required as part of volume names, for example, the "C:\" in "C:\path\file" or the "\server\share" in "\server\share\path\file" for Universal Naming Convention (UNC) names. For more information about UNC names, see the Maximum Path Length Limitation section.
  • Do not assume case sensitivity. For example, consider the names OSCAR, Oscar, and oscar to be the same, even though some file systems (such as a POSIX-compliant file system) may consider them as different. Note that NTFS supports POSIX semantics for case sensitivity but this is not the default behavior. For more information, see CreateFile.
  • Volume designators (drive letters) are similarly case-insensitive. For example, "D:\" and "d:\" refer to the same volume.
  • Use any character in the current code page for a name, including Unicode characters and characters in the extended character set (128–255), except for the following:

    • The following reserved characters:
      • < (less than)
      • > (greater than)
      • : (colon)
      • " (double quote)
      • / (forward slash)
      • \ (backslash)
      • | (vertical bar or pipe)
      • ? (question mark)
      • * (asterisk)
    • Integer value zero, sometimes referred to as the ASCII NUL character.
    • Characters whose integer representations are in the range from 1 through 31, except for alternate streams where these characters are allowed. For more information about file streams, see File Streams.
    • Any other character that the target file system does not allow.
  • Use a period as a directory component in a path to represent the current directory, for example ".\temp.txt". For more information, see Paths.
  • Use two consecutive periods (..) as a directory component in a path to represent the parent of the current directory, for example "..\temp.txt". For more information, see Paths.
  • Do not use the following reserved device names for the name of a file:

    CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9. Also avoid these names followed immediately by an extension; for example, NUL.txt is not recommended. For more information, see Namespaces.

  • Do not end a file or directory name with a space or a period. Although the underlying file system may support such names, the Windows shell and user interface does not. However, it is acceptable to specify a period as the first character of a name. For example, ".temp".

you can check this C++ article Validating file names for an complete example function to validate the windows file names.

RRUZ
+3  A: 

To see if it exists, use DirectoryExists() and FileExists(). To see if you can create/edit the file, use FileOpen() with ReadWrite and see if it succeeds.

Marcus Adams
+1  A: 

try this code (taken from delphi faq)

const
  { for short 8.3 file names }
  ShortForbiddenChars : set of Char = [';', '=', '+', '<', '>', '|',
                                       '"', '[', ']', '\', '/', ''''];
  { for long file names }
  LongForbiddenChars  : set of Char = ['<', '>', '|', '"', '\', '/', ':', '*', '?'];

function TestFilename(Filename: String; islong: Boolean) : Boolean;
var
  I: integer;
begin
  Result := Filename <> '';
  if islong then
  begin
    for I := 1 to Length(Filename) do
      Result := Result and not (Filename[I] in LongForbiddenChars);
  end
  else
  begin
    for I := 1 to Length(Filename) do
      Result := Result and not (Filename[I] in ShortForbiddenChars);
  end;
end;
Omair Iqbal
The `begin`...`end` are superfluous. Also, you should make `isLong: boolean = true` (or remove the 8.3 code completely).
Andreas Rejbrand
The code does not test for forbidden filenames like con, prn, aux
mjustin
+2  A: 

I do not have access to a Delphi compiler right now, but you could try

function IsValidFilename(const FileName: string): boolean;
begin
  result := DirectoryExists(ExtractFilePath(FileName)) and TPath.HasValidFileNameChars(ExtractFileName(FileName), false);
end;
Andreas Rejbrand