Search code examples
inno-setuppascalscript

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.


Solution

  • 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.