Comment puis-je désinfecter une chaîne pour l'utiliser comme nom de fichier?

J'ai une routine qui convertit un fichier dans un autre format et l'enregistre. Les fichiers de données d'origine étaient numérotés, mais ma routine donne à la sortie un nom de fichier basé sur un Nom interne trouvé dans l'original.

J'ai essayé de l'exécuter par lots sur un répertoire entier, et cela a bien fonctionné jusqu'à ce que je frappe un fichier dont le Nom interne avait une barre oblique. Oups! Et si il le fait ici, il pourrait facilement le faire sur d'autres fichiers. Y a-t-il une routine RTL (ou WinAPI) quelque part qui va désinfecter un string et supprimer les symboles invalides de sorte qu'il est sûr d'utiliser comme un nom de fichier?

24
demandé sur Alex 2009-06-07 03:11:31

9 réponses

Vous pouvez utiliser PathGetCharType fonction, PathCleanupSpec fonction, ou l'astuce suivante:

  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;

CE code divise la chaîne en parties et utilise MoveFile pour vérifier chaque partie. MoveFile échouera pour les caractères invalides ou les noms de Fichiers réservés (COMME 'COM') et retournera success ou ERROR_ALREADY_EXISTS pour le nom de fichier valide.


PathCleanupSpec se trouve dans Jedi Windows API sous Win32API / JwaShlObj.pas

21
répondu Alex 2012-02-20 20:53:15

En ce qui concerne la question de savoir s'il existe une fonction API pour désinfecter un fichier un nom (ou même vérifier sa validité) - il semble n'y en avoir aucun. Citant le commentaire sur le PathSearchAndQualify () fonction :

Il ne semble pas y avoir D'API Windows qui validera un chemin entré par l'utilisateur; ceci est laissé comme un exercice ad hoc pour chaque application.

Vous ne pouvez donc consulter les règles de validité des noms de fichiers qu'à partir de noms de fichiers, chemins et espaces de noms (Windows):

  • Utilisez presque tous les caractères de la page de code en cours pour un nom, y compris les caractères Unicode et les caractères du jeu de caractères étendu (128-255), à l'exception des éléments suivants:

    • caractères réservés suivants ne sont pas autorisés:
      : "/ \ | ? *
    • les caractères dont les représentations entières sont comprises entre zéro et 31 ne sont pas autorisés.
    • Tout autre caractère qui le système de fichiers cible ne permet pas.
  • Ne l'utilisez pas les noms de périphérique réservé pour le nom d'un fichier: CON, PRN, AUX, NUL, COM1..COM9, LPT1..LPT9.
    Évitez également ces noms suivis immédiatement d'une extension; par exemple, NUL.txt n'est pas recommandé.

Si vous savez que votre programme n'écrira que sur les systèmes de fichiers NTFS, vous pouvez probablement être sûr qu'il n'y a pas d'autres caractères que le système de fichiers n'autorise pas, donc vous le feriez il suffit de vérifier que le nom du fichier n'est pas trop long (utilisez la constante MAX_PATH) après que tous les caractères non valides ont été supprimés (ou remplacés par des traits de soulignement, par exemple).

Un programme doit également s'assurer que l'assainissement du nom de fichier n'a pas entraîné de conflits de noms de fichiers et qu'il écrase silencieusement les autres fichiers qui ont fini par porter le même nom.

10
répondu mghie 2009-06-07 05:19:04
{
  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
répondu Mark Elder 2015-06-16 15:13:36

Vérifiez si la chaîne a des caractères invalides; solution de ici:

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

Et, pour les chaînes renvoyant False, Vous pouvez faire quelque chose de simple comme this pour chaque caractère invalide:

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
répondu bernie 2009-06-06 23:46:26

Pour tous ceux qui lisent ceci et qui veulent utiliser PathCleanupSpec, j'ai écrit cette routine de test qui semble fonctionner... il y a un manque défini d'exemples sur le net. Vous devez inclure ShlObj.pas (pas sûr quand PathCleanupSpec a été ajouté mais j'ai testé cela dans Delphi 2010) Vous devrez également vérifier XP sp2 ou supérieur

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;
4
répondu sergeantKK 2012-06-13 10:01:07

Eh bien, le plus simple est d'utiliser une expression régulière et la version de gsub de votre langue préférée pour remplacer tout ce qui n'est pas un caractère "word"."Cette classe de caractères serait" \w "dans la plupart des langues avec des expressions rationnelles de type Perl, ou" [A-Za-z0-9] " comme une option simple sinon.

En particulier, contrairement à certains exemples d'autres réponses, vous ne voulez pas rechercher de caractères invalides à supprimer, mais rechercher des caractères valides à conserver. Si vous cherchez des caractères non valides, vous êtes toujours vulnérable à l'introduction de nouveaux personnages, mais si vous ne cherchez que des personnages valides, vous pourriez être légèrement moins inefficace (en ce sens que vous avez remplacé un personnage dont vous n'aviez pas vraiment besoin), mais au moins vous ne vous tromperez jamais.

Maintenant, si vous voulez que la nouvelle version ressemble autant que possible à l'ancienne, vous pourriez envisager un remplacement. Au lieu de supprimer, vous pouvez remplacer un caractère ou des caractères que vous savez être ok. Mais faire cela est un problème assez intéressant que c'est probablement un bon sujet pour une autre question.

3
répondu Curt J. Sampson 2009-06-07 01:44:27

J'ai fait ceci:

// 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
répondu John Weldon 2009-06-06 23:19:47

Essayez ceci sur un delphi moderne:

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

Je permet également d'avoir des umlauts allemands ou d'autres caractères comme -,_,.. dans un nom de fichier.

0
répondu brenkdar 2016-11-18 11:42:31
// 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
répondu alitrun 2017-04-26 21:34:35