如何清理字符串以用作文件名?

Mas*_*ler 26 delphi validation filenames sanitization

我有一个例程,可以将文件转换为不同的格式并保存.原始数据文件已编号,但我的例程根据原始文件中的内部名称为输出提供文件名.

我试图在一个完整的目录上批量运行它,它工作正常,直到我点击一个内部名称中有斜杠的文件.哎呀!如果它在这里,它可以很容易地在其他文件上.是否存在RTL(或WinAPI)例程,它将清理字符串并删除无效符号,以便可以安全地用作文件名?

Ale*_*lex 23

您可以使用PathGetCharType函数,PathCleanupSpec函数或以下技巧:

  function IsValidFilePath(const FileName: String): Boolean;
  var
    S: String;
    I: Integer;
  begin
    Result := False;
    S := FileName;
    repeat
      I := LastDelimiter('\/', S);
      MoveFile(nil, PChar(S));
      if (GetLastError = ERROR_ALREADY_EXISTS) or
         (
           (GetFileAttributes(PChar(Copy(S, I + 1, MaxInt))) = INVALID_FILE_ATTRIBUTES)
           and
           (GetLastError=ERROR_INVALID_NAME)
         ) then
        Exit;
      if I>0 then
        S := Copy(S,1,I-1);
    until I = 0;
    Result := True;
  end;
Run Code Online (Sandbox Code Playgroud)

此代码将字符串分成几部分,并使用MoveFile验证每个部分.MoveFile将因无效字符或保留文件名(如"COM")而失败,并返回成功或ERROR_ALREADY_EXISTS作为有效文件名.


PathCleanupSpec位于Win32API/JwaShlObj.pas下的Jedi Windows API中

  • 为PathCleanupSpec +1,有趣的东西 (2认同)
  • 使用nil作为MoveFile()的第一个参数是未记录的行为。同样,除非`MoveFile()`首先返回FALSE,否则不要检查`GetLastError()`,该代码不会检查该错误。 (2认同)

mgh*_*hie 12

关于是否有任何API函数来清理文件名称(甚至检查其有效性)的问题 - 似乎没有.引用PathSearchAndQualify()函数的注释:

似乎没有任何Windows API可以验证用户输入的路径; 这是每个应用程序的临时练习.

因此,您只能从文件名,路径和命名空间(Windows)中查阅文件名有效性的规则:

  • 使用当前代码页中的几乎任何字符作为名称,包括扩展字符集(128-255)中的Unicode字符和字符,但以下情况除外:

    • 不允许使用以下保留字符:
      <>:"/\|?*
    • 不允许整数表示在0到31范围内的字符.
    • 目标文件系统不允许的任何其他字符.
  • 不要为一个文件的名称使用下面的保留设备名称:CON,PRN,AUX,NUL,COM1..COM9,LPT1..LPT9.
    还要避免使用这些名称,然后立即进行扩展; 例如,NUL.txt不推荐.

如果您知道您的程序只会写入NTFS文件系统,您可能可以确定文件系统不允许其他字符,因此您只需要检查文件名是否太长(使用在MAX_PATH删除所有无效字符(例如,用下划线替换)之后的常量).

程序还应确保文件名清理不会导致文件名冲突,并且它会以静默方式覆盖最终具有相同名称的其他文件.


Mar*_*der 7

{
  CleanFileName
  ---------------------------------------------------------------------------

  Given an input string strip any chars that would result
  in an invalid file name.  This should just be passed the
  filename not the entire path because the slashes will be
  stripped.  The function ensures that the resulting string
  does not hae multiple spaces together and does not start
  or end with a space.  If the entire string is removed the
  result would not be a valid file name so an error is raised.

}

function CleanFileName(const InputString: string): string;
var
  i: integer;
  ResultWithSpaces: string;
begin

  ResultWithSpaces := InputString;

  for i := 1 to Length(ResultWithSpaces) do
  begin
    // These chars are invalid in file names.
    case ResultWithSpaces[i] of 
      '/', '\', ':', '*', '?', '"', '<', '>', '|', ' ', #$D, #$A, #9:
        // Use a * to indicate a duplicate space so we can remove
        // them at the end.
        {$WARNINGS OFF} // W1047 Unsafe code 'String index to var param'
        if (i > 1) and
          ((ResultWithSpaces[i - 1] = ' ') or (ResultWithSpaces[i - 1] = '*')) then
          ResultWithSpaces[i] := '*'
        else
          ResultWithSpaces[i] := ' ';

        {$WARNINGS ON}
    end;
  end;

  // A * indicates duplicate spaces.  Remove them.
  result := ReplaceStr(ResultWithSpaces, '*', '');

  // Also trim any leading or trailing spaces
  result := Trim(Result);

  if result = '' then
  begin
    raise(Exception.Create('Resulting FileName was empty Input string was: '
      + InputString));
  end;
end;
Run Code Online (Sandbox Code Playgroud)


ali*_*run 6

// for all platforms (Windows\Unix), uses IOUtils.
function ReplaceInvalidFileNameChars(const aFileName: string; const aReplaceWith: Char = '_'): string;
var
  i: integer;
begin
  Result := aFileName;
  for i := Low(Result) to High(Result) do
  begin
    if not TPath.IsValidFileNameChar(Result[i]) then
      Result[i] := aReplaceWith;
  end;
end.
Run Code Online (Sandbox Code Playgroud)


ber*_*nie 5

检查字符串是否包含无效字符; 这里的解决方案:

//test if a "fileName" is a valid Windows file name
//Delphi >= 2005 version

function IsValidFileName(const fileName : string) : boolean;
const 
  InvalidCharacters : set of char = ['\', '/', ':', '*', '?', '"', '<', '>', '|'];
var
  c : char;
begin
  result := fileName <> '';

  if result then
  begin
    for c in fileName do
    begin
      result := NOT (c in InvalidCharacters) ;
      if NOT result then break;
    end;
  end;
end; (* IsValidFileName *)
Run Code Online (Sandbox Code Playgroud)

而且,对于字符串返回FALSE,你可以做一些简单的像这样每个无效字符:

var
  before, after : string;

begin
  before := 'i am a rogue file/name';

  after  := StringReplace(before, '/', '',
                      [rfReplaceAll, rfIgnoreCase]);
  ShowMessage('Before = '+before);
  ShowMessage('After  = '+after);
end;

// Before = i am a rogue file/name
// After  = i am a rogue filename
Run Code Online (Sandbox Code Playgroud)


ser*_*tKK 5

对于阅读此书并想使用PathCleanupSpec的其他人,我编写了此测试例程,该例程似乎行得通……在网上绝对缺少示例。您需要包括ShlObj.pas(不确定何时添加PathCleanupSpec,但我在Delphi 2010中对此进行了测试)。您还需要检查XP sp2或更高版本

procedure TMainForm.btnTestClick(Sender: TObject);
var
  Path: array [0..MAX_PATH - 1] of WideChar;
  Filename: array[0..MAX_PATH - 1] of WideChar;
  ReturnValue: integer;
  DebugString: string;

begin
  StringToWideChar('a*dodgy%\filename.$&^abc',FileName, MAX_PATH);
  StringToWideChar('C:\',Path, MAX_PATH);
  ReturnValue:= PathCleanupSpec(Path,Filename);
  DebugString:= ('Cleaned up filename:'+Filename+#13+#10);
  if (ReturnValue and $80000000)=$80000000 then
    DebugString:= DebugString+'Fatal result. The cleaned path is not a valid file name'+#13+#10;
  if (ReturnValue and $00000001)=$00000001 then
    DebugString:= DebugString+'Replaced one or more invalid characters'+#13+#10;
  if (ReturnValue and $00000002)=$00000002 then
    DebugString:= DebugString+'Removed one or more invalid characters'+#13+#10;
  if (ReturnValue and $00000004)=$00000004 then
    DebugString:= DebugString+'The returned path is truncated'+#13+#10;
  if (ReturnValue and $00000008)=$00000008 then
    DebugString:= DebugString+'The input path specified at pszDir is too long to allow the formation of a valid file name from pszSpec'+#13;
  ShowMessage(DebugString);
end;
Run Code Online (Sandbox Code Playgroud)