From e02322d75f608335a33cac28a7a55242ae03c7b9 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 27 May 2024 12:14:53 +0700 Subject: [PATCH] fix(developer): handle invalid default project path in options If a relative or otherwise invalid path is given for `default project path` in .keymandeveloper/options.json, then this will reset the path to the default of `%Documents%/Keyman Developer/Projects`. Fixes: #11554 Fixes: KEYMAN-DEVELOPER-1ZB --- .../src/tike/main/KeymanDeveloperOptions.pas | 13 +++++++++++- developer/src/tike/main/UfrmMain.pas | 21 ++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/developer/src/tike/main/KeymanDeveloperOptions.pas b/developer/src/tike/main/KeymanDeveloperOptions.pas index 3e31f276880..6eb05730964 100644 --- a/developer/src/tike/main/KeymanDeveloperOptions.pas +++ b/developer/src/tike/main/KeymanDeveloperOptions.pas @@ -82,6 +82,7 @@ TKeymanDeveloperOptions = class procedure optWriteBool(const nm: string; value: Boolean); procedure optWriteInt(const nm: string; value: Integer); procedure WriteServerConfigurationJson; + class function Default_DefaultProjectPath: string; static; public procedure Read; procedure Write; @@ -365,7 +366,12 @@ procedure TKeymanDeveloperOptions.Read; FFix183_LadderLength := optReadInt(SRegValue_IDEOpt_WebLadderLength, CRegValue_IDEOpt_WebLadderLength_Default); - FDefaultProjectPath := IncludeTrailingPathDelimiter(optReadString(SRegValue_IDEOpt_DefaultProjectPath, GetFolderPath(CSIDL_PERSONAL) + CDefaultProjectPath)); + FDefaultProjectPath := IncludeTrailingPathDelimiter(optReadString(SRegValue_IDEOpt_DefaultProjectPath, Default_DefaultProjectPath)); + if (FDefaultProjectPath = '\') or IsRelativePath(FDefaultProjectPath) then + begin + // #11554 + FDefaultProjectPath := Default_DefaultProjectPath; + end; // for consistency with Keyman.System.KeymanSentryClient, we need to use // reg.ReadInteger, as regReadInt, which in the dim dark past started @@ -613,6 +619,11 @@ class function TKeymanDeveloperOptions.IsDefaultEditorTheme(s: string): Boolean; Result := DefaultEditorThemeItemIndex(s) >= 0; end; +class function TKeymanDeveloperOptions.Default_DefaultProjectPath: string; +begin + Result := GetFolderPath(CSIDL_PERSONAL) + CDefaultProjectPath; +end; + function LoadKeymanDeveloperSentryFlags: TKeymanSentryClientFlags; begin Result := [kscfCaptureExceptions, kscfShowUI, kscfTerminate]; diff --git a/developer/src/tike/main/UfrmMain.pas b/developer/src/tike/main/UfrmMain.pas index b6468ba2222..44b58732450 100644 --- a/developer/src/tike/main/UfrmMain.pas +++ b/developer/src/tike/main/UfrmMain.pas @@ -552,11 +552,22 @@ procedure TfrmKeymanDeveloper.FormCreate(Sender: TObject); FFilesToOpen := TStringList.Create; - if not ForceDirectories(FKeymanDeveloperOptions.DefaultProjectPath) then - begin - // Fall back to Documents folder if we cannot create the default project path - // Documents folder should always exist - FKeymanDeveloperOptions.DefaultProjectPath := GetFolderPath(CSIDL_PERSONAL); + try + if not ForceDirectories(FKeymanDeveloperOptions.DefaultProjectPath) then + begin + // Fall back to Documents folder if we cannot create the default project path + // Documents folder should always exist + FKeymanDeveloperOptions.DefaultProjectPath := GetFolderPath(CSIDL_PERSONAL); + end; + except + on E:EInOutError do + begin + // If the DefaultProjectPath is a relative, invalid path, then it may + // cause EInOutError (#11554). Note that this exception should no longer + // be possible because the path is sanitized when loaded in + // KeymanDeveloperOptions.pas, but keeping this fallback just in case. + FKeymanDeveloperOptions.DefaultProjectPath := GetFolderPath(CSIDL_PERSONAL); + end; end; FFirstShow := True;