652 lines
21 KiB
ObjectPascal
652 lines
21 KiB
ObjectPascal
unit PathFunc;
|
|
|
|
{
|
|
Inno Setup
|
|
Copyright (C) 1997-2025 Jordan Russell
|
|
Portions by Martijn Laan
|
|
For conditions of distribution and use, see LICENSE.TXT.
|
|
|
|
This unit provides some path-related functions.
|
|
}
|
|
|
|
interface
|
|
|
|
function AddBackslash(const S: String): String;
|
|
function PathChangeExt(const Filename, Extension: String): String;
|
|
function PathCharCompare(const S1, S2: PChar): Boolean;
|
|
function PathCharIsSlash(const C: Char): Boolean;
|
|
function PathCharIsTrailByte(const S: String; const Index: Integer): Boolean;
|
|
function PathCharLength(const S: String; const Index: Integer): Integer;
|
|
function PathCombine(const Dir, Filename: String): String;
|
|
function PathCompare(const S1, S2: String): Integer;
|
|
function PathDrivePartLength(const Filename: String): Integer;
|
|
function PathDrivePartLengthEx(const Filename: String;
|
|
const IncludeSignificantSlash: Boolean): Integer;
|
|
function PathExpand(const Filename: String): String; overload;
|
|
function PathExpand(const Filename: String; out ExpandedFilename: String): Boolean; overload;
|
|
function PathExtensionPos(const Filename: String): Integer;
|
|
function PathExtractDir(const Filename: String): String;
|
|
function PathExtractDrive(const Filename: String): String;
|
|
function PathExtractExt(const Filename: String): String;
|
|
function PathExtractName(const Filename: String): String;
|
|
function PathExtractPath(const Filename: String): String;
|
|
function PathHasInvalidCharacters(const S: String;
|
|
const AllowDriveLetterColon: Boolean): Boolean;
|
|
function PathIsRooted(const Filename: String): Boolean;
|
|
function PathLastChar(const S: String): PChar;
|
|
function PathLastDelimiter(const Delimiters, S: string): Integer;
|
|
function PathLowercase(const S: String): String;
|
|
function PathNormalizeSlashes(const S: String): String;
|
|
function PathPathPartLength(const Filename: String;
|
|
const IncludeSlashesAfterPath: Boolean): Integer;
|
|
function PathPos(Ch: Char; const S: String): Integer;
|
|
function PathStartsWith(const S, AStartsWith: String): Boolean;
|
|
function PathStrNextChar(const S: PChar): PChar;
|
|
function PathStrPrevChar(const Start, Current: PChar): PChar;
|
|
function PathStrScan(const S: PChar; const C: Char): PChar;
|
|
function RemoveBackslash(const S: String): String;
|
|
function RemoveBackslashUnlessRoot(const S: String): String;
|
|
function ValidateAndCombinePath(const ADestDir, AFilename: String;
|
|
out AResultingPath: String): Boolean; overload;
|
|
function ValidateAndCombinePath(const ADestDir, AFilename: String): Boolean; overload;
|
|
|
|
implementation
|
|
|
|
{$ZEROBASEDSTRINGS OFF}
|
|
|
|
uses
|
|
Windows, SysUtils;
|
|
|
|
function AddBackslash(const S: String): String;
|
|
{ Returns S plus a trailing backslash, unless S is an empty string or already
|
|
ends in a backslash/slash. }
|
|
begin
|
|
if (S <> '') and not PathCharIsSlash(PathLastChar(S)^) then
|
|
Result := S + '\'
|
|
else
|
|
Result := S;
|
|
end;
|
|
|
|
function PathCharLength(const S: String; const Index: Integer): Integer;
|
|
{ Returns the length in characters of the character at Index in S. }
|
|
begin
|
|
Result := 1;
|
|
end;
|
|
|
|
function PathCharIsSlash(const C: Char): Boolean;
|
|
{ Returns True if C is a backslash or slash. }
|
|
begin
|
|
Result := (C = '\') or (C = '/');
|
|
end;
|
|
|
|
function PathCharIsTrailByte(const S: String; const Index: Integer): Boolean;
|
|
{ Returns False if S[Index] is a single byte character or a lead byte.
|
|
Returns True otherwise (i.e. it must be a trail byte). }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := 1;
|
|
while I <= Index do begin
|
|
if I = Index then begin
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
Inc(I, PathCharLength(S, I));
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
function PathCharCompare(const S1, S2: PChar): Boolean;
|
|
{ Compares two first characters, and returns True if they are equal. }
|
|
var
|
|
N, I: Integer;
|
|
begin
|
|
N := PathStrNextChar(S1) - S1;
|
|
if N = PathStrNextChar(S2) - S2 then begin
|
|
for I := 0 to N-1 do begin
|
|
if S1[I] <> S2[I] then begin
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
end;
|
|
Result := True;
|
|
end else
|
|
Result := False;
|
|
end;
|
|
|
|
function PathChangeExt(const Filename, Extension: String): String;
|
|
{ Takes Filename, removes any existing extension, then adds the extension
|
|
specified by Extension and returns the resulting string. }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := PathExtensionPos(Filename);
|
|
if I = 0 then
|
|
Result := Filename + Extension
|
|
else
|
|
Result := Copy(Filename, 1, I - 1) + Extension;
|
|
end;
|
|
|
|
function PathCombine(const Dir, Filename: String): String;
|
|
{ Combines a directory and filename into a path.
|
|
If Dir is empty, it just returns Filename.
|
|
If Filename is empty, it returns an empty string (ignoring Dir).
|
|
If Filename begins with a drive letter or slash, it returns Filename
|
|
(ignoring Dir).
|
|
If Dir specifies only a drive letter and colon ('c:'), it returns
|
|
Dir + Filename.
|
|
Otherwise, it returns the equivalent of AddBackslash(Dir) + Filename. }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if (Dir = '') or (Filename = '') or PathIsRooted(Filename) then
|
|
Result := Filename
|
|
else begin
|
|
I := PathCharLength(Dir, 1) + 1;
|
|
if ((I = Length(Dir)) and (Dir[I] = ':')) or
|
|
PathCharIsSlash(PathLastChar(Dir)^) then
|
|
Result := Dir + Filename
|
|
else
|
|
Result := Dir + '\' + Filename;
|
|
end;
|
|
end;
|
|
|
|
function PathCompare(const S1, S2: String): Integer;
|
|
{ Compares two filenames, and returns 0 if they are equal. }
|
|
begin
|
|
Result := CompareStr(PathLowercase(S1), PathLowercase(S2));
|
|
end;
|
|
|
|
function PathDrivePartLength(const Filename: String): Integer;
|
|
begin
|
|
Result := PathDrivePartLengthEx(Filename, False);
|
|
end;
|
|
|
|
function PathDrivePartLengthEx(const Filename: String;
|
|
const IncludeSignificantSlash: Boolean): Integer;
|
|
{ Returns length of the drive portion of Filename, or 0 if there is no drive
|
|
portion.
|
|
If IncludeSignificantSlash is True, the drive portion can include a trailing
|
|
slash if it is significant to the meaning of the path (i.e. 'x:' and 'x:\'
|
|
are not equivalent, nor are '\' and '').
|
|
If IncludeSignificantSlash is False, the function works as follows:
|
|
'x:file' -> 2 ('x:')
|
|
'x:\file' -> 2 ('x:')
|
|
'\\server\share\file' -> 14 ('\\server\share')
|
|
'\file' -> 0 ('')
|
|
If IncludeSignificantSlash is True, the function works as follows:
|
|
'x:file' -> 2 ('x:')
|
|
'x:\file' -> 3 ('x:\')
|
|
'\\server\share\file' -> 14 ('\\server\share')
|
|
'\file' -> 1 ('\')
|
|
}
|
|
var
|
|
Len, I, C: Integer;
|
|
begin
|
|
Len := Length(Filename);
|
|
|
|
{ \\server\share }
|
|
if (Len >= 2) and PathCharIsSlash(Filename[1]) and PathCharIsSlash(Filename[2]) then begin
|
|
I := 3;
|
|
C := 0;
|
|
while I <= Len do begin
|
|
if PathCharIsSlash(Filename[I]) then begin
|
|
Inc(C);
|
|
if C >= 2 then
|
|
Break;
|
|
repeat
|
|
Inc(I);
|
|
{ And skip any additional consecutive slashes: }
|
|
until (I > Len) or not PathCharIsSlash(Filename[I]);
|
|
end
|
|
else
|
|
Inc(I, PathCharLength(Filename, I));
|
|
end;
|
|
Result := I - 1;
|
|
Exit;
|
|
end;
|
|
|
|
{ \ }
|
|
{ Note: Test this before 'x:' since '\:stream' means access stream 'stream'
|
|
on the root directory of the current drive, not access drive '\:' }
|
|
if (Len >= 1) and PathCharIsSlash(Filename[1]) then begin
|
|
if IncludeSignificantSlash then
|
|
Result := 1
|
|
else
|
|
Result := 0;
|
|
Exit;
|
|
end;
|
|
|
|
{ x: }
|
|
if Len > 0 then begin
|
|
I := PathCharLength(Filename, 1) + 1;
|
|
if (I <= Len) and (Filename[I] = ':') then begin
|
|
if IncludeSignificantSlash and (I < Len) and PathCharIsSlash(Filename[I+1]) then
|
|
Result := I+1
|
|
else
|
|
Result := I;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
Result := 0;
|
|
end;
|
|
|
|
function PathIsRooted(const Filename: String): Boolean;
|
|
{ Returns True if Filename begins with a slash or drive ('x:').
|
|
Equivalent to: PathDrivePartLengthEx(Filename, True) <> 0 }
|
|
var
|
|
Len, I: Integer;
|
|
begin
|
|
Result := False;
|
|
Len := Length(Filename);
|
|
if Len > 0 then begin
|
|
{ \ or \\ }
|
|
if PathCharIsSlash(Filename[1]) then
|
|
Result := True
|
|
else begin
|
|
{ x: }
|
|
I := PathCharLength(Filename, 1) + 1;
|
|
if (I <= Len) and (Filename[I] = ':') then
|
|
Result := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function PathPathPartLength(const Filename: String;
|
|
const IncludeSlashesAfterPath: Boolean): Integer;
|
|
{ Returns length of the path portion of Filename, or 0 if there is no path
|
|
portion.
|
|
Note these differences from Delphi's ExtractFilePath function:
|
|
- The result will never be less than what PathDrivePartLength returns.
|
|
If you pass a UNC root path, e.g. '\\server\share', it will return the
|
|
length of the entire string, NOT the length of '\\server\'.
|
|
- If you pass in a filename with a reference to an NTFS alternate data
|
|
stream, e.g. 'abc:def', it will return the length of the entire string,
|
|
NOT the length of 'abc:'. }
|
|
var
|
|
LastCharToKeep, Len, I: Integer;
|
|
begin
|
|
Result := PathDrivePartLengthEx(Filename, True);
|
|
LastCharToKeep := Result;
|
|
Len := Length(Filename);
|
|
I := Result + 1;
|
|
while I <= Len do begin
|
|
if PathCharIsSlash(Filename[I]) then begin
|
|
if IncludeSlashesAfterPath then
|
|
Result := I
|
|
else
|
|
Result := LastCharToKeep;
|
|
Inc(I);
|
|
end
|
|
else begin
|
|
Inc(I, PathCharLength(Filename, I));
|
|
LastCharToKeep := I-1;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function PathExpand(const Filename: String; out ExpandedFilename: String): Boolean;
|
|
{ Like Delphi's ExpandFileName, but does proper error checking. }
|
|
var
|
|
Res: Integer;
|
|
FilePart: PChar;
|
|
Buf: array[0..4095] of Char;
|
|
begin
|
|
DWORD(Res) := GetFullPathName(PChar(Filename), SizeOf(Buf) div SizeOf(Buf[0]),
|
|
Buf, FilePart);
|
|
Result := (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0]));
|
|
if Result then
|
|
SetString(ExpandedFilename, Buf, Res)
|
|
end;
|
|
|
|
function PathExpand(const Filename: String): String;
|
|
begin
|
|
if not PathExpand(Filename, Result) then
|
|
Result := Filename;
|
|
end;
|
|
|
|
function PathExtensionPos(const Filename: String): Integer;
|
|
{ Returns index of the last '.' character in the filename portion of Filename,
|
|
or 0 if there is no '.' in the filename portion.
|
|
Note: Filename is assumed to NOT include an NTFS alternate data stream name
|
|
(i.e. 'filename:stream'). }
|
|
var
|
|
Len, I: Integer;
|
|
begin
|
|
Result := 0;
|
|
Len := Length(Filename);
|
|
I := PathPathPartLength(Filename, True) + 1;
|
|
while I <= Len do begin
|
|
if Filename[I] = '.' then begin
|
|
Result := I;
|
|
Inc(I);
|
|
end
|
|
else
|
|
Inc(I, PathCharLength(Filename, I));
|
|
end;
|
|
end;
|
|
|
|
function PathExtractDir(const Filename: String): String;
|
|
{ Like PathExtractPath, but strips any trailing slashes, unless the resulting
|
|
path is the root directory of a drive (i.e. 'C:\' or '\'). }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := PathPathPartLength(Filename, False);
|
|
Result := Copy(Filename, 1, I);
|
|
end;
|
|
|
|
function PathExtractDrive(const Filename: String): String;
|
|
{ Returns the drive portion of Filename (either 'x:' or '\\server\share'),
|
|
or an empty string if there is no drive portion. }
|
|
var
|
|
L: Integer;
|
|
begin
|
|
L := PathDrivePartLength(Filename);
|
|
if L = 0 then
|
|
Result := ''
|
|
else
|
|
Result := Copy(Filename, 1, L);
|
|
end;
|
|
|
|
function PathExtractExt(const Filename: String): String;
|
|
{ Returns the extension portion of the last component of Filename (e.g. '.txt')
|
|
or an empty string if there is no extension. }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := PathExtensionPos(Filename);
|
|
if I = 0 then
|
|
Result := ''
|
|
else
|
|
Result := Copy(Filename, I, Maxint);
|
|
end;
|
|
|
|
function PathExtractName(const Filename: String): String;
|
|
{ Returns the filename portion of Filename (e.g. 'filename.txt'). If Filename
|
|
ends in a slash or consists only of a drive part or is empty, the result will
|
|
be an empty string.
|
|
This function is essentially the opposite of PathExtractPath. }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := PathPathPartLength(Filename, True);
|
|
Result := Copy(Filename, I + 1, Maxint);
|
|
end;
|
|
|
|
function PathExtractPath(const Filename: String): String;
|
|
{ Returns the path portion of Filename (e.g. 'c:\dir\'). If Filename contains
|
|
no drive part or slash, the result will be an empty string.
|
|
This function is essentially the opposite of PathExtractName. }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := PathPathPartLength(Filename, True);
|
|
Result := Copy(Filename, 1, I);
|
|
end;
|
|
|
|
function PathHasInvalidCharacters(const S: String;
|
|
const AllowDriveLetterColon: Boolean): Boolean;
|
|
{ Checks the specified path for characters that are never allowed in paths,
|
|
or characters and path components that are accepted by the system but might
|
|
present a security problem (such as '..' and sometimes ':').
|
|
Specifically, True is returned if S includes any of the following:
|
|
- Control characters (0-31)
|
|
- One of these characters: /*?"<>|
|
|
(This means forward slashes and the prefixes '\\?\' and '\??\' are never
|
|
allowed.)
|
|
- Colons (':'), except when AllowDriveLetterColon=True and the string's
|
|
first character is a letter and the second character is the only colon.
|
|
(This blocks NTFS alternate data stream names.)
|
|
- A component with a trailing dot or space
|
|
|
|
Due to the last rule above, '.' and '..' components are never allowed, nor
|
|
are components like these:
|
|
'file '
|
|
'file.'
|
|
'file. . .'
|
|
'file . . '
|
|
When expanding paths (with no '\\?\' prefix used), Windows 11 23H2 silently
|
|
removes all trailing dots and spaces from the end of the string. Therefore,
|
|
if used at the end of a path, all of the above cases yield just 'file'.
|
|
On preceding components of the path, nothing is done with spaces; if there
|
|
is exactly one dot at the end, it is removed (e.g., 'dir.\file' becomes
|
|
'dir\file'), while multiple dots are left untouched ('dir..\file' doesn't
|
|
change).
|
|
By rejecting trailing dots and spaces up front, we avoid all that weirdness
|
|
and the problems that could arise from it.
|
|
|
|
Since ':' is considered invalid (except in the one case noted above), it's
|
|
not possible to sneak in disallowed dots/spaces by including an NTFS
|
|
alternate data stream name. The function will return True in these cases:
|
|
'..:streamname'
|
|
'file :streamname'
|
|
}
|
|
begin
|
|
Result := True;
|
|
for var I := Low(S) to High(S) do begin
|
|
var C := S[I];
|
|
if Ord(C) < 32 then
|
|
Exit;
|
|
case C of
|
|
#32, '.':
|
|
begin
|
|
if (I = High(S)) or PathCharIsSlash(S[I+1]) then
|
|
Exit;
|
|
end;
|
|
':':
|
|
begin
|
|
{ The A-Z check ensures that '.:streamname', ' :streamname', and
|
|
'\:streamname' are disallowed. }
|
|
if not AllowDriveLetterColon or (I <> Low(S)+1) or
|
|
not CharInSet(S[Low(S)], ['A'..'Z', 'a'..'z']) then
|
|
Exit;
|
|
end;
|
|
'/', '*', '?', '"', '<', '>', '|': Exit;
|
|
end;
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
function PathLastChar(const S: String): PChar;
|
|
{ Returns pointer to last character in the string. Returns nil if the string is
|
|
empty. }
|
|
begin
|
|
if S = '' then
|
|
Result := nil
|
|
else
|
|
Result := @S[High(S)];
|
|
end;
|
|
|
|
function PathLastDelimiter(const Delimiters, S: string): Integer;
|
|
{ Returns the index of the last occurrence in S of one of the characters in
|
|
Delimiters, or 0 if none were found.
|
|
Note: S is allowed to contain null characters. }
|
|
var
|
|
P, E: PChar;
|
|
begin
|
|
Result := 0;
|
|
if (S = '') or (Delimiters = '') then
|
|
Exit;
|
|
P := Pointer(S);
|
|
E := @P[Length(S)];
|
|
while P < E do begin
|
|
if P^ <> #0 then begin
|
|
if StrScan(PChar(Pointer(Delimiters)), P^) <> nil then
|
|
Result := (P - PChar(Pointer(S))) + 1;
|
|
P := PathStrNextChar(P);
|
|
end
|
|
else
|
|
Inc(P);
|
|
end;
|
|
end;
|
|
|
|
function PathLowercase(const S: String): String;
|
|
{ Converts the specified path name to lowercase }
|
|
begin
|
|
Result := AnsiLowerCase(S);
|
|
end;
|
|
|
|
function PathPos(Ch: Char; const S: String): Integer;
|
|
var
|
|
Len, I: Integer;
|
|
begin
|
|
Len := Length(S);
|
|
I := 1;
|
|
while I <= Len do begin
|
|
if S[I] = Ch then begin
|
|
Result := I;
|
|
Exit;
|
|
end;
|
|
Inc(I, PathCharLength(S, I));
|
|
end;
|
|
Result := 0;
|
|
end;
|
|
|
|
function PathNormalizeSlashes(const S: String): String;
|
|
{ Returns S minus any superfluous slashes, and with any forward slashes
|
|
converted to backslashes. For example, if S is 'C:\\\some//path', it returns
|
|
'C:\some\path'. Does not remove a double backslash at the beginning of the
|
|
string, since that signifies a UNC path. }
|
|
var
|
|
Len, I: Integer;
|
|
begin
|
|
Result := S;
|
|
Len := Length(Result);
|
|
I := 1;
|
|
while I <= Len do begin
|
|
if Result[I] = '/' then
|
|
Result[I] := '\';
|
|
Inc(I, PathCharLength(Result, I));
|
|
end;
|
|
I := 1;
|
|
while I < Length(Result) do begin
|
|
if (Result[I] = '\') and (Result[I+1] = '\') and (I > 1) then
|
|
Delete(Result, I+1, 1)
|
|
else
|
|
Inc(I, PathCharLength(Result, I));
|
|
end;
|
|
end;
|
|
|
|
function PathStartsWith(const S, AStartsWith: String): Boolean;
|
|
{ Returns True if S starts with (or is equal to) AStartsWith. Uses path casing
|
|
rules. }
|
|
var
|
|
AStartsWithLen: Integer;
|
|
begin
|
|
AStartsWithLen := Length(AStartsWith);
|
|
if Length(S) = AStartsWithLen then
|
|
Result := (PathCompare(S, AStartsWith) = 0)
|
|
else if (Length(S) > AStartsWithLen) and not PathCharIsTrailByte(S, AStartsWithLen+1) then
|
|
Result := (PathCompare(Copy(S, 1, AStartsWithLen), AStartsWith) = 0)
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function PathStrNextChar(const S: PChar): PChar;
|
|
{ Returns pointer to the character after S, unless S points to a null (#0). }
|
|
begin
|
|
Result := S;
|
|
if Result^ <> #0 then
|
|
Inc(Result);
|
|
end;
|
|
|
|
function PathStrPrevChar(const Start, Current: PChar): PChar;
|
|
{ Returns pointer to the character before Current, unless Current = Start. }
|
|
begin
|
|
Result := Current;
|
|
if Result > Start then
|
|
Dec(Result);
|
|
end;
|
|
|
|
function PathStrScan(const S: PChar; const C: Char): PChar;
|
|
{ Returns pointer to first occurrence of C in S, or nil if there are no
|
|
occurrences. As with StrScan, specifying #0 for the search character is legal. }
|
|
begin
|
|
Result := S;
|
|
while Result^ <> C do begin
|
|
if Result^ = #0 then begin
|
|
Result := nil;
|
|
Break;
|
|
end;
|
|
Result := PathStrNextChar(Result);
|
|
end;
|
|
end;
|
|
|
|
function RemoveBackslash(const S: String): String;
|
|
{ Returns S minus any trailing slashes. Use of this function is discouraged;
|
|
use RemoveBackslashUnlessRoot instead when working with file system paths. }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := Length(S);
|
|
while (I > 0) and PathCharIsSlash(PathStrPrevChar(Pointer(S), @S[I+1])^) do
|
|
Dec(I);
|
|
if I = Length(S) then
|
|
Result := S
|
|
else
|
|
Result := Copy(S, 1, I);
|
|
end;
|
|
|
|
function RemoveBackslashUnlessRoot(const S: String): String;
|
|
{ Returns S minus any trailing slashes, unless S specifies the root directory
|
|
of a drive (i.e. 'C:\' or '\'), in which case it leaves 1 slash. }
|
|
var
|
|
DrivePartLen, I: Integer;
|
|
begin
|
|
DrivePartLen := PathDrivePartLengthEx(S, True);
|
|
I := Length(S);
|
|
while (I > DrivePartLen) and PathCharIsSlash(PathStrPrevChar(Pointer(S), @S[I+1])^) do
|
|
Dec(I);
|
|
if I = Length(S) then
|
|
Result := S
|
|
else
|
|
Result := Copy(S, 1, I);
|
|
end;
|
|
|
|
function ValidateAndCombinePath(const ADestDir, AFilename: String;
|
|
out AResultingPath: String): Boolean;
|
|
{ Combines ADestDir and AFilename without allowing a result outside of
|
|
ADestDir and without allowing other security problems.
|
|
Returns True if all security checks pass, with the combination of ADestDir
|
|
and AFilename in AResultingPath.
|
|
ADestDir is assumed to be normalized already and have a trailing backslash.
|
|
AFilename may be a file or directory name. }
|
|
begin
|
|
{ - Don't allow empty names
|
|
- Don't allow forward slashes or repeated slashes
|
|
- Don't allow rooted (non-relative to current directory) names
|
|
- Don't allow trailing slash
|
|
- Don't allow invalid characters/dots/spaces (this catches '..') }
|
|
Result := False;
|
|
if (AFilename <> '') and
|
|
(AFilename = PathNormalizeSlashes(AFilename)) and
|
|
not PathIsRooted(AFilename) and
|
|
not PathCharIsSlash(AFilename[High(AFilename)]) and
|
|
not PathHasInvalidCharacters(AFilename, False) then begin
|
|
{ Our validity checks passed. Now pass the combined path to PathExpand
|
|
(GetFullPathName) to see if it thinks the path needs normalization.
|
|
If the returned path isn't exactly what was passed in, then consider
|
|
the name invalid.
|
|
One way that can happen is if the path ends in an MS-DOS device name:
|
|
PathExpand('c:\path\NUL') returns '\\.\NUL'. Obviously we don't want
|
|
devices being opened, so that must be rejected. }
|
|
var CombinedPath := ADestDir + AFilename;
|
|
var TestExpandedPath: String;
|
|
if PathExpand(CombinedPath, TestExpandedPath) and
|
|
(CombinedPath = TestExpandedPath) then begin
|
|
AResultingPath := CombinedPath;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function ValidateAndCombinePath(const ADestDir, AFilename: String): Boolean;
|
|
begin
|
|
var ResultingPath: String;
|
|
Result := ValidateAndCombinePath(ADestDir, AFilename, ResultingPath);
|
|
end;
|
|
|
|
end.
|