Inside Man
Inside Man

Reputation: 4309

Validate directory path in Inno Setup

If you enter an invalid path or empty path, the Inno Setup will give you this error:

enter image description here

I need to create a function which does this for me. Currently I'm using DirExists function, but I think it is not covering what I'm looking for.

Upvotes: 2

Views: 447

Answers (1)

Martin Prikryl
Martin Prikryl

Reputation: 202642

The validation that Inno Setup does is relatively complex. Check the source code for ValidateCustomDirEdit function to see what it does.

A very rouch equivalent of that code for default Inno Setup settings (AllowUNCPath=yes, AllowRootDirectory=no, AllowNetworkDrive=yes) translated to Pascal Script is below. Note that it might not work correctly with MBCS.

procedure TidyUpDirName(var Path: String);
var
  Prefix: string;
  Prev: string;
begin
  StringChangeEx(Path, '/', '\', True);
  repeat
    Prev := Path;
    Path := Trim(Path);
    Prefix := '';
    if Copy(Path, 1, 2) = '\\' then
    begin
      Prefix := '\\';
      Path := Copy(Path, 3, Length(Path) - 2);
    end;
    StringChangeEx(Path, '\\', '\', True);
    Path := Prefix + Path;
    Path := RemoveBackslashUnlessRoot(Path);
  until Prev = Path;
end;

function ValidatePath(Path: string): Boolean;
var
  I, P: Integer;
  S, S2: string;
begin
  TidyUpDirName(Path);
  Result := False;
  if Length(Path) > 240 then Exit;

  if Copy(Path, 1, 2) <> '\\' then
  begin
    if (Length(Path) < 4) or
       (Uppercase(Path[1])[1] < 'A') or (Uppercase(Path[1])[1] > 'Z') or
       (Path[2] <> ':')
       or (Path[3] <> '\') then Exit;
  end
    else
  begin
    if Pos('\', Copy(Path, 3, Length(Path) - 2)) = 0 then Exit;
  end;

  for I := 1 to Length(Path) do
  begin
    if Path[I] <= #31 then Exit;

    if (Path[I] = ' ') and
       ((I = Length(Path)) or (Path[I + 1] = '\')) then Exit;
  end;

  S := Path;
  while Length(S) > 0 do
  begin
    P := Pos('\', S);
    if P > 0 then
    begin
      S2 := Copy(S, 1, P - 1);
      S := Copy(S, P + 1, Length(S) - P);
    end
      else
    begin
      S2 := S;
      S := '';
    end;
    S2 := Trim(S2);
    if (Length(S2) > 0) and (S2 = StringOfChar('.', Length(S2))) then Exit;
  end;

  for I := 3 to Length(Path) do
  begin
    if Pos(Path[I], '/:*?"<>|') > 0 then Exit;
  end;

  Result := True;
end;

Tests:

procedure DoTest(Path: string; Expected: Boolean);
var
  Actual: Boolean;
  M: string;
begin
  Actual := ValidatePath(Path);
  M := Format('Path: "%s"; Result: %d; Expected: %d', [
         Path, Integer(Actual), Integer(Expected)]);
  Log(M);
  if Actual <> Expected then RaiseException(M);
end;

procedure Test;
begin
  DoTest('C:\path', True);
  DoTest('\\server\path', True);
  DoTest('c:\path', True);
  DoTest('C:/path', True);
  DoTest('//server/path', True);
  DoTest('C:\path ', True);
  DoTest(' C:\path ', True);
  DoTest('C:\\path', True);
  DoTest('\\server\\path', True);
  DoTest('C:', False);
  DoTest('C:\', False);
  DoTest('1:\path', False);
  DoTest('|:\path', False);
  DoTest('C;\path', False);
  DoTest('C:|path\sub', False);
  DoTest('\\server', False);
  DoTest('C:\\pa'#27'th', False);
  DoTest('C:\\path \sub', False);
  DoTest('C:\\path\sub \sub', False);
  DoTest('C:\\.', False);
  DoTest('C:\\..\sub', False);
  DoTest('C:\\path\.\sub', False);
  DoTest('C:\\path\ .\sub', False);
  DoTest('C:\\path\.. \sub', False);
  DoTest('C:\\path\su:b', False);
  DoTest('C:\\pa?th\sub', False);
end;

Inno Setup additionally tests for existence of the drive:

DirExists(ExtractFileDrive(Path))

If you want your own path edit box with the same validation as the standard Select directory page, use CreateInputDirPage/TInputDirWizardPage, instead of a completely custom page. The TInputDirWizardPage does the validation for you.

Or you can call a hidden TInputDirWizardPage to do just the validation.

Upvotes: 3

Related Questions