web-dev-qa-db-ja.com

ファイル名として使用するために文字列をサニタイズするにはどうすればよいですか?

ファイルを別の形式に変換して保存するルーチンがあります。元のデータファイルには番号が付けられていましたが、私のルーチンは、元のデータファイルにある内部名に基づいて出力にファイル名を付けます。

ディレクトリ全体でバッチ実行しようとしましたが、内部名にスラッシュが含まれているファイルを1つヒットするまで、正常に機能しました。おっと!そして、ここでそれを行うと、他のファイルでも簡単に行うことができます。文字列をサニタイズして無効な記号を削除し、ファイル名として安全に使用できるRTL(またはWinAPI)ルーチンはどこかにありますか?

26
Mason Wheeler

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;

このコードは文字列を部分に分割し、MoveFileを使用して各部分を検証します。 MoveFileは、無効な文字または予約済みファイル名( 'COM'など)の場合は失敗し、有効なファイル名の場合は成功またはERROR_ALREADY_EXISTSを返します。


PathCleanupSpecは、Win32API/JwaShlObj.pasの下の Jedi Windows API にあります。

23
Alex

ファイルに名前をサニタイズする(またはその有効性をチェックする)API関数があるかどうかという質問に関しては、ないようです。 PathSearchAndQualify()関数 に関するコメントからの引用:

ユーザーが入力したパスを検証するWindowsAPIはないようです。これは、各アプリケーションのアドホック演習として残されています。

したがって、ファイル名の有効性に関するルールは、 ファイル名、パス、および名前空間(Windows) からのみ参照できます。

  • 以下を除いて、名前には現在のコードページのほぼすべての文字を使用します。これには、Unicode文字と拡張文字セット(128〜255)の文字が含まれます。

    • 次の予約文字は使用できません。
      <>: "/\|?*
    • 整数表現が0から31の範囲にある文字は許可されません。
    • ターゲットファイルシステムで許可されていないその他の文字。
  • ファイルの名前に次の予約済みデバイス名を使用しないでください:CONPRNAUXNULCOM1..COM9LPT1..LPT9
    また、これらの名前の直後に拡張子が続くことは避けてください。例えば、 NUL.txtはお勧めしません。

プログラムがNTFSファイルシステムにのみ書き込むことがわかっている場合は、ファイルシステムで許可されていない文字が他にないことを確認できる可能性があるため、ファイル名が長すぎないことを確認するだけで済みます( MAX_PATH定数)すべての無効な文字が削除された後(または、たとえばアンダースコアに置き換えられた後)。

プログラムは、ファイル名のサニタイズによってファイル名の競合が発生していないこと、および同じ名前になってしまった他のファイルをサイレントに上書きすることも確認する必要があります。

12
mghie
{
  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;
7
Mark Elder

文字列に無効な文字が含まれていないかどうかを確認してください。 ここ からの解決策:

//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 *)

また、Falseを返す文字列の場合、無効な文字ごとに this のような単純な操作を実行できます。

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
5
bernie

これを読んで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;
5
sergeantKK

簡単なことは、正規表現とお気に入りの言語バージョンのgsubを使用して、「単語文字」以外のものを置き換えることです。この文字クラスは、Perlのような正規表現を使用するほとんどの言語では「\w」、それ以外の場合は単純なオプションとして「[A-Za-z0-9]」になります。

特に、他の回答のいくつかの例とは対照的に、削除する無効な文字を探すのではなく、保持する有効な文字を探します。無効な文字を探している場合は、常に新しい文字の導入に対して脆弱ですが、有効な文字のみを探している場合は、効率が少し低下する可能性があります(実際にはそうではなかった文字を置き換えたという点で)する必要があります)、しかし少なくともあなたは決して間違っていることはありません。

さて、新しいバージョンをできるだけ古いバージョンに近づけたい場合は、交換を検討してください。削除する代わりに、問題がないことがわかっている1つまたは複数の文字に置き換えることができます。しかし、それを行うことは十分に興味深い問題であるため、おそらく別の質問の良いトピックになります。

3
Curt J. Sampson

これは私がしました:

// Initialized elsewhere...
string folder;
string name;
var prepl = System.IO.Path.GetInvalidPathChars();
var frepl = System.IO.Path.GetInvalidFileNameChars();
foreach (var c in prepl)
{
    folder = folder.Replace(c,'_');
    name = name.Replace(c, '_');
}
foreach (var c in frepl)
{
    folder = folder.Replace(c, '_');
    name = name.Replace(c, '_');
}
1
John Weldon

現代のデルファイでこれを試してください:

 use System.IOUtils;
 ...
 result := TPath.HasValidFileNameChars(FileName, False)

また、ファイル名にドイツ語のウムラウトや-、_、..などの他の文字を含めることもできます。

0
brenkdar
// 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
    if not TPath.IsValidFileNameChar(Result[i]) then
      Result[i] := aReplaceWith;
  end;
end.
0
alitrun