1
0
Эх сурвалжийг харах

Extensions adding

Source commit: e006aa218c2b473f24cb08347b4d45b96800acd1
Martin Prikryl 9 жил өмнө
parent
commit
b9fc2ce751

+ 35 - 1
source/core/Common.cpp

@@ -2367,6 +2367,22 @@ UnicodeString __fastcall AppendUrlParams(UnicodeString AURL, UnicodeString Param
   return URL;
 }
 //---------------------------------------------------------------------------
+UnicodeString __fastcall ExtractFileNameFromUrl(const UnicodeString & Url)
+{
+  UnicodeString Result = Url;
+  int P = Result.Pos(L"?");
+  if (P > 0)
+  {
+    Result.SetLength(P - 1);
+  }
+  P = Result.LastDelimiter("/");
+  if (DebugAlwaysTrue(P > 0))
+  {
+    Result.Delete(1, P);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 UnicodeString __fastcall EscapeHotkey(const UnicodeString & Caption)
 {
   return ReplaceStr(Caption, L"&", L"&&");
@@ -2911,9 +2927,27 @@ void __fastcall CheckCertificate(const UnicodeString & Path)
   }
 }
 //---------------------------------------------------------------------------
+const UnicodeString HttpProtocol(L"http");
+const UnicodeString HttpsProtocol(L"https");
+const UnicodeString ProtocolSeparator(L"://");
+//---------------------------------------------------------------------------
 bool __fastcall IsHttpUrl(const UnicodeString & S)
 {
-  return SameText(S.SubString(1, 4), L"http");
+  return StartsText(HttpProtocol + ProtocolSeparator, S);
+}
+//---------------------------------------------------------------------------
+bool __fastcall IsHttpOrHttpsUrl(const UnicodeString & S)
+{
+  return
+    IsHttpUrl(S) ||
+    StartsText(HttpsProtocol + ProtocolSeparator, S);
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall ChangeUrlProtocol(const UnicodeString & S, const UnicodeString & Protocol)
+{
+  int P = S.Pos(ProtocolSeparator);
+  DebugAssert(P > 0);
+  return Protocol + ProtocolSeparator + RightStr(S, S.Length() - P - ProtocolSeparator.Length() + 1);
 }
 //---------------------------------------------------------------------------
 const UnicodeString RtfPara = L"\\par\n";

+ 7 - 0
source/core/Common.h

@@ -24,6 +24,10 @@ extern const wchar_t TokenReplacement;
 extern const UnicodeString LocalInvalidChars;
 extern const UnicodeString PasswordMask;
 //---------------------------------------------------------------------------
+extern const UnicodeString HttpProtocol;
+extern const UnicodeString HttpsProtocol;
+extern const UnicodeString ProtocolSeparator;
+//---------------------------------------------------------------------------
 UnicodeString ReplaceChar(UnicodeString Str, wchar_t A, wchar_t B);
 UnicodeString DeleteChar(UnicodeString Str, wchar_t C);
 void PackStr(UnicodeString & Str);
@@ -96,6 +100,7 @@ UnicodeString __fastcall DecodeUrlChars(UnicodeString S);
 UnicodeString __fastcall EncodeUrlString(UnicodeString S);
 UnicodeString __fastcall EncodeUrlPath(UnicodeString S);
 UnicodeString __fastcall AppendUrlParams(UnicodeString URL, UnicodeString Params);
+UnicodeString __fastcall ExtractFileNameFromUrl(const UnicodeString & Url);
 bool __fastcall RecursiveDeleteFile(const UnicodeString & FileName, bool ToRecycleBin);
 void __fastcall RecursiveDeleteFileChecked(const UnicodeString & FileName, bool ToRecycleBin);
 void __fastcall DeleteFileChecked(const UnicodeString & FileName);
@@ -141,6 +146,8 @@ void __fastcall ParseCertificate(const UnicodeString & Path,
   const UnicodeString & Passphrase, X509 *& Certificate, EVP_PKEY *& PrivateKey,
   bool & WrongPassphrase);
 bool __fastcall IsHttpUrl(const UnicodeString & S);
+bool __fastcall IsHttpOrHttpsUrl(const UnicodeString & S);
+UnicodeString __fastcall ChangeUrlProtocol(const UnicodeString & S, const UnicodeString & Protocol);
 void __fastcall LoadScriptFromFile(UnicodeString FileName, TStrings * Lines);
 //---------------------------------------------------------------------------
 typedef void __fastcall (__closure* TProcessLocalFileEvent)

+ 21 - 0
source/core/Http.cpp

@@ -14,10 +14,14 @@ THttp::THttp()
   FProxyPort = 0;
   FOnDownload = NULL;
   FResponseLimit = -1;
+
+  FRequestHeaders = NULL;
+  FResponseHeaders = new TStringList();
 }
 //---------------------------------------------------------------------------
 THttp::~THttp()
 {
+  delete FResponseHeaders;
 }
 //---------------------------------------------------------------------------
 void THttp::SendRequest(const char * Method, const UnicodeString & Request)
@@ -78,6 +82,15 @@ void THttp::SendRequest(const char * Method, const UnicodeString & Request)
       ne_request_s * NeonRequest = ne_request_create(NeonSession, Method, StrToNeon(Uri));
       try
       {
+        if (FRequestHeaders != NULL)
+        {
+          for (int Index = 0; Index < FRequestHeaders->Count; Index++)
+          {
+            ne_add_request_header(
+              NeonRequest, StrToNeon(FRequestHeaders->Names[Index]), StrToNeon(FRequestHeaders->Values[Index]));
+          }
+        }
+
         UTF8String RequestUtf;
         if (!Request.IsEmpty())
         {
@@ -112,6 +125,14 @@ void THttp::SendRequest(const char * Method, const UnicodeString & Request)
           {
             throw Exception(FMTLOAD(HTTP_ERROR, (NeonStatus->code, StrFromNeon(NeonStatus->reason_phrase), FHostName)));
           }
+
+          void * Cursor = NULL;
+          const char * HeaderName;
+          const char * HeaderValue;
+          while ((Cursor = ne_response_header_iterate(NeonRequest, Cursor, &HeaderName, &HeaderValue)) != NULL)
+          {
+            FResponseHeaders->Values[StrFromNeon(HeaderName)] = StrFromNeon(HeaderValue);
+          }
         }
       }
       __finally

+ 4 - 0
source/core/Http.h

@@ -24,8 +24,10 @@ public:
   __property UnicodeString URL = { read = FURL, write = FURL };
   __property UnicodeString ProxyHost = { read = FProxyHost, write = FProxyHost };
   __property int ProxyPort = { read = FProxyPort, write = FProxyPort };
+  __property TStrings * RequestHeaders = { read = FRequestHeaders, write = FRequestHeaders };
   __property UnicodeString Response = { read = GetResponse };
   __property RawByteString ResponseRaw = { read = FResponse };
+  __property TStrings * ResponseHeaders = { read = FResponseHeaders };
   __property __int64 ResponseLength = { read = GetResponseLength };
   __property __int64 ResponseLimit = { read = FResponseLimit, write = FResponseLimit };
   __property THttpDownloadEvent OnDownload = { read = FOnDownload, write = FOnDownload};
@@ -40,6 +42,8 @@ private:
   THttpDownloadEvent FOnDownload;
   UnicodeString FHostName;
   UnicodeString FCertificateError;
+  TStrings * FRequestHeaders;
+  TStrings * FResponseHeaders;
 
   static int NeonBodyReader(void * UserData, const char * Buf, size_t Len);
   int NeonBodyReaderImpl(const char * Buf, size_t Len);

+ 0 - 3
source/core/SessionData.cpp

@@ -49,10 +49,7 @@ const UnicodeString ScpProtocol(L"scp");
 const UnicodeString FtpProtocol(L"ftp");
 const UnicodeString FtpsProtocol(L"ftps");
 const UnicodeString FtpesProtocol(L"ftpes");
-const UnicodeString WebDAVProtocol(L"http");
-const UnicodeString WebDAVSProtocol(L"https");
 const UnicodeString SshProtocol(L"ssh");
-const UnicodeString ProtocolSeparator(L"://");
 const UnicodeString WinSCPProtocolPrefix(L"winscp-");
 const wchar_t UrlParamSeparator = L';';
 const wchar_t UrlParamValueSeparator = L'=';

+ 2 - 3
source/core/SessionData.h

@@ -68,10 +68,9 @@ extern const UnicodeString ScpProtocol;
 extern const UnicodeString FtpProtocol;
 extern const UnicodeString FtpsProtocol;
 extern const UnicodeString FtpesProtocol;
-extern const UnicodeString WebDAVProtocol;
-extern const UnicodeString WebDAVSProtocol;
+#define WebDAVProtocol HttpProtocol
+#define WebDAVSProtocol HttpsProtocol
 extern const UnicodeString SshProtocol;
-extern const UnicodeString ProtocolSeparator;
 extern const UnicodeString WinSCPProtocolPrefix;
 extern const wchar_t UrlParamSeparator;
 extern const wchar_t UrlParamValueSeparator;

+ 212 - 24
source/forms/Preferences.cpp

@@ -15,12 +15,14 @@
 #include "VCLCommon.h"
 #include "GUITools.h"
 #include "Tools.h"
+#include "TextsCore.h"
 #include "TextsWin.h"
 #include "HelpWin.h"
 #include "WinInterface.h"
 #include "WinConfiguration.h"
 #include "Setup.h"
 #include "ProgParams.h"
+#include "Http.h"
 //---------------------------------------------------------------------
 #pragma link "CopyParams"
 #pragma link "UpDownEdit"
@@ -1030,6 +1032,29 @@ int __fastcall TPreferencesDialog::GetCommandIndex(int Index)
   return Index;
 }
 //---------------------------------------------------------------------------
+int __fastcall TPreferencesDialog::GetListCommandIndex(TCustomCommandList * List)
+{
+  int Index;
+  if (GetCommandList(CustomCommandsView->ItemIndex) == List)
+  {
+    Index = GetCommandIndex(CustomCommandsView->ItemIndex);
+  }
+  else
+  {
+    Index = -1;
+  }
+  return Index;
+}
+//---------------------------------------------------------------------------
+int __fastcall TPreferencesDialog::GetCommandListIndex(TCustomCommandList * List, int Index)
+{
+  if (List == FExtensionList)
+  {
+    Index += FCustomCommandList->Count;
+  }
+  return Index;
+}
+//---------------------------------------------------------------------------
 void __fastcall TPreferencesDialog::UpdateControls()
 {
   if (FNoUpdate == 0)
@@ -1403,7 +1428,7 @@ void __fastcall TPreferencesDialog::CustomCommandsViewKeyDown(
 
   if (AddCommandButton->Enabled && (Key == VK_INSERT))
   {
-    AddEditCommandButtonClick(AddCommandButton);
+    AddEditCommand(false);
   }
 }
 //---------------------------------------------------------------------------
@@ -1412,13 +1437,26 @@ void __fastcall TPreferencesDialog::CustomCommandsViewDblClick(
 {
   if (EditCommandButton->Enabled)
   {
-    AddEditCommandButtonClick(EditCommandButton);
+    AddEditCommand(true);
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TPreferencesDialog::AddEditCommandButtonClick(TObject * Sender)
+static int __fastcall AddCommandToList(TCustomCommandList * List, int Index, TCustomCommandType * Command)
+{
+  if (Index >= 0)
+  {
+    List->Insert(Index, Command);
+  }
+  else
+  {
+    List->Add(Command);
+    Index = List->Count - 1;
+  }
+  return Index;
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::AddEditCommand(bool Edit)
 {
-  bool Edit = (Sender == EditCommandButton);
   TCustomCommandType Command;
 
   if (Edit)
@@ -1440,15 +1478,7 @@ void __fastcall TPreferencesDialog::AddEditCommandButtonClick(TObject * Sender)
   if (DoCustomCommandDialog(Command, FCustomCommandList,
         (Edit ? ccmEdit : ccmAdd), 0, NULL, &ShortCuts))
   {
-    int Index;
-    if (GetCommandList(CustomCommandsView->ItemIndex) == FCustomCommandList)
-    {
-      Index = GetCommandIndex(CustomCommandsView->ItemIndex);
-    }
-    else
-    {
-      Index = -1;
-    }
+    int Index = GetListCommandIndex(FCustomCommandList);
     TCustomCommandType * ACommand = new TCustomCommandType(Command);
     if (Edit)
     {
@@ -1457,20 +1487,11 @@ void __fastcall TPreferencesDialog::AddEditCommandButtonClick(TObject * Sender)
     }
     else
     {
-      if (Index >= 0)
-      {
-        FCustomCommandList->Insert(Index, ACommand);
-      }
-      else
-      {
-        FCustomCommandList->Add(ACommand);
-        Index = FCustomCommandList->Count - 1;
-      }
+      Index = AddCommandToList(FCustomCommandList, Index, ACommand);
     }
 
     UpdateCustomCommandsView();
-    // assumes that the custom command are the first
-    CustomCommandsView->ItemIndex = Index;
+    CustomCommandsView->ItemIndex = GetCommandListIndex(FCustomCommandList, Index);
     UpdateControls();
   }
 }
@@ -2378,3 +2399,170 @@ void __fastcall TPreferencesDialog::CustomCommandsViewWindowProc(TMessage & Mess
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::AddExtension()
+{
+  const UnicodeString HistoryKey(L"ExtensionPath");
+  std::unique_ptr<TStrings> History(CloneStrings(CustomWinConfiguration->History[HistoryKey]));
+  UnicodeString Path;
+  if (InputDialog(LoadStr(ADD_EXTENSION_CAPTION), LoadStr(ADD_EXTENSION_PROMPT), Path,
+        HELP_NONE, History.get(), true) &&
+      !Path.IsEmpty())
+  {
+    CustomWinConfiguration->History[HistoryKey] = History.get();
+
+    bool Trusted;
+    UnicodeString FileName;
+    UnicodeString ExtensionPath;
+    std::unique_ptr<TStringList> Lines(new TStringList());
+    if (IsHttpOrHttpsUrl(Path))
+    {
+      bool WinSCPURL = IsWinSCPUrl(Path);
+      if (WinSCPURL)
+      {
+        if (IsHttpUrl(Path))
+        {
+          Path = ChangeUrlProtocol(Path, HttpsProtocol);
+        }
+        Path = CampaignUrl(ProgramUrl(Path));
+      }
+
+      std::unique_ptr<THttp> Http(CreateHttp());
+      Http->URL = Path;
+      std::unique_ptr<TStrings> Headers(new TStringList());
+      Headers->Values[L"Accept"] = L"text/winscpextension,text/plain";
+      Http->RequestHeaders = Headers.get();
+      Http->Get();
+
+      UnicodeString TrustedStr = Http->ResponseHeaders->Values["WinSCP-Extension-Trusted"];
+      Trusted = WinSCPURL && (StrToIntDef(TrustedStr, 0) != 0);
+
+      FileName = MakeValidFileName(Http->ResponseHeaders->Values["WinSCP-Extension-Id"]);
+      if (FileName.IsEmpty())
+      {
+        FileName = MakeValidFileName(ExtractFileNameFromUrl(Path));
+      }
+      Lines->Text = Http->Response;
+    }
+    else
+    {
+      if (!FileExists(Path))
+      {
+        throw Exception(MainInstructions(FMTLOAD(FILE_NOT_EXISTS, (Path))));
+      }
+
+      Trusted = true;
+
+      int Index = FExtensionList->FindIndexByFileName(Path);
+      if (Index > 0)
+      {
+        CustomCommandsView->ItemIndex = GetCommandListIndex(FExtensionList, Index);
+        CustomCommandsView->SetFocus();
+        throw Exception(MainInstructions(LoadStr(EXTENSION_INSTALLED_ALREADY)));
+      }
+
+      UnicodeString Id = WinConfiguration->GetExtensionId(Path);
+      FileName = ExtractFileName(Path);
+      if (!Id.IsEmpty())
+      {
+        ExtensionPath = Path;
+      }
+
+      LoadScriptFromFile(Path, Lines.get());
+    }
+
+    // validate syntax
+    std::unique_ptr<TCustomCommandType> CustomCommand(new TCustomCommandType());
+    CustomCommand->LoadExtension(Lines.get());
+
+    if (FExtensionList->Find(CustomCommand->Name) != NULL)
+    {
+      throw Exception(MainInstructions(FMTLOAD(EXTENSION_DUPLICATE, (StripHotkey(CustomCommand->Name)))));
+    }
+
+    if (ExtensionPath.IsEmpty())
+    {
+      if (TCustomCommandType::GetExtensionId(FileName).IsEmpty())
+      {
+        UnicodeString FileNameOnly = ExtractFileNameOnly(FileName);
+        if (FileNameOnly.IsEmpty())
+        {
+          FileName = MakeValidFileName(StripHotkey(CustomCommand->Name)) + WinSCPExtensionExt;
+        }
+        else
+        {
+          FileName = ExtractFileNameOnly(FileName) + WinSCPExtensionExt + ExtractFileExt(FileName);
+        }
+      }
+    }
+
+    if (Trusted ||
+        (MessageDialog(MainInstructions(LoadStr(EXTENSION_UNTRUSTED)), qtWarning, qaOK | qaCancel) == qaOK))
+    {
+      if (ExtensionPath.IsEmpty())
+      {
+        UnicodeString UserExtensionsPath = WinConfiguration->GetUserExtensionsPath();
+        if (!DirectoryExists(UserExtensionsPath) &&
+            !ForceDirectories(UserExtensionsPath))
+        {
+          throw EOSExtException(MainInstructions(FMTLOAD(CREATE_LOCAL_DIR_ERROR, (UserExtensionsPath))));
+        }
+
+        ExtensionPath = IncludeTrailingBackslash(UserExtensionsPath) + FileName;
+
+        if (FileExists(ExtensionPath) &&
+            (MessageDialog(MainInstructions(FMTLOAD(FILE_OVERWRITE, (ExtensionPath))), qtConfirmation, qaOK | qaCancel) == qaCancel))
+        {
+          Abort();
+        }
+        Lines->SaveToFile(ExtensionPath);
+      }
+
+      int Index = GetListCommandIndex(FExtensionList);
+
+      std::unique_ptr<TCustomCommandType> CustomCommand(new TCustomCommandType());
+      CustomCommand->Id = WinConfiguration->GetExtensionId(ExtensionPath);
+      CustomCommand->LoadExtension(ExtensionPath);
+
+      Index = AddCommandToList(FExtensionList, Index, CustomCommand.release());
+
+      UpdateCustomCommandsView();
+      CustomCommandsView->ItemIndex = GetCommandListIndex(FExtensionList, Index);
+      UpdateControls();
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::AddCommandButtonClick(TObject * /*Sender*/)
+{
+  if (GetCommandList(CustomCommandsView->ItemIndex) == FCustomCommandList)
+  {
+    AddEditCommand(false);
+  }
+  else
+  {
+    AddExtension();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::AddCustomCommandMenuItemClick(TObject * /*Sender*/)
+{
+  AddEditCommand(false);
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::AddExtensionMenuItemClick(TObject * /*Sender*/)
+{
+  AddExtension();
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::EditCommandButtonClick(TObject * /*Sender*/)
+{
+  AddEditCommand(true);
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::AddCommandButtonDropDownClick(TObject * /*Sender*/)
+{
+  AddCustomCommandMenuItem->Default = (GetCommandList(CustomCommandsView->ItemIndex) == FCustomCommandList);
+  AddExtensionMenuItem->Default = (GetCommandList(CustomCommandsView->ItemIndex) == FExtensionList);
+  MenuPopup(AddCommandMenu, AddCommandButton);
+}
+//---------------------------------------------------------------------------

+ 16 - 2
source/forms/Preferences.dfm

@@ -1207,8 +1207,10 @@ object PreferencesDialog: TPreferencesDialog
             Height = 25
             Anchors = [akLeft, akBottom]
             Caption = '&Add...'
+            Style = bsSplitButton
             TabOrder = 1
-            OnClick = AddEditCommandButtonClick
+            OnClick = AddCommandButtonClick
+            OnDropDownClick = AddCommandButtonDropDownClick
           end
           object RemoveCommandButton: TButton
             Left = 16
@@ -1248,7 +1250,7 @@ object PreferencesDialog: TPreferencesDialog
             Anchors = [akLeft, akBottom]
             Caption = '&Edit...'
             TabOrder = 2
-            OnClick = AddEditCommandButtonClick
+            OnClick = EditCommandButtonClick
           end
         end
       end
@@ -3095,4 +3097,16 @@ object PreferencesDialog: TPreferencesDialog
       OnClick = UnregisterForDefaultProtocolsItemClick
     end
   end
+  object AddCommandMenu: TPopupMenu
+    Left = 208
+    Top = 443
+    object AddCustomCommandMenuItem: TMenuItem
+      Caption = 'Add &Custom Command...'
+      OnClick = AddCustomCommandMenuItemClick
+    end
+    object AddExtensionMenuItem: TMenuItem
+      Caption = 'Add &Extension...'
+      OnClick = AddExtensionMenuItemClick
+    end
+  end
 end

+ 12 - 1
source/forms/Preferences.h

@@ -309,6 +309,9 @@ __published:
   TStaticText *UpdatesLink;
   TCheckBox *ShowTipsCheck;
   TPanel *ComponentsPanel;
+  TPopupMenu *AddCommandMenu;
+  TMenuItem *AddCustomCommandMenuItem;
+  TMenuItem *AddExtensionMenuItem;
   void __fastcall FormShow(TObject *Sender);
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall EditorFontButtonClick(TObject *Sender);
@@ -319,7 +322,6 @@ __published:
           TListItem *Item, bool Selected);
   void __fastcall CustomCommandsViewKeyDown(TObject *Sender, WORD &Key,
           TShiftState Shift);
-  void __fastcall AddEditCommandButtonClick(TObject *Sender);
   void __fastcall RemoveCommandButtonClick(TObject *Sender);
   void __fastcall UpDownCommandButtonClick(TObject *Sender);
   void __fastcall ListViewStartDrag(TObject *Sender,
@@ -390,6 +392,11 @@ __published:
   void __fastcall UpdatesAuthenticationEmailEditExit(TObject *Sender);
   void __fastcall UpdatesLinkClick(TObject *Sender);
   void __fastcall CustomCommandsViewDragOver(TObject *Sender, TObject *Source, int X, int Y, TDragState State, bool &Accept);
+  void __fastcall AddCommandButtonClick(TObject *Sender);
+  void __fastcall AddCustomCommandMenuItemClick(TObject *Sender);
+  void __fastcall AddExtensionMenuItemClick(TObject *Sender);
+  void __fastcall EditCommandButtonClick(TObject *Sender);
+  void __fastcall AddCommandButtonDropDownClick(TObject *Sender);
 
 private:
   TPreferencesMode FPreferencesMode;
@@ -427,6 +434,8 @@ private:
   void __fastcall CustomCommandsViewWindowProc(TMessage & Message);
   TCustomCommandList * __fastcall GetCommandList(int Index);
   int __fastcall GetCommandIndex(int Index);
+  int __fastcall GetCommandListIndex(TCustomCommandList * List, int Index);
+  int __fastcall GetListCommandIndex(TCustomCommandList * List);
 public:
   virtual __fastcall ~TPreferencesDialog();
   bool __fastcall Execute(TPreferencesDialogData * DialogData);
@@ -453,6 +462,8 @@ protected:
     UnicodeString Message, TStrings * RecryptPasswordErrors);
   void __fastcall EditorFontColorChange(TColor Color);
   void __fastcall EditorBackgroundColorChange(TColor Color);
+  void __fastcall AddEditCommand(bool Edit);
+  void __fastcall AddExtension();
 };
 //----------------------------------------------------------------------------
 #endif

+ 7 - 0
source/resource/TextsWin.h

@@ -87,6 +87,9 @@
 #define EXTENSION_DEPENDENCY_ERROR 1201
 #define EXTENSION_DIRECTIVE_ERROR 1202
 #define EXTENSION_DIRECTIVE_MISSING 1203
+#define EXTENSION_NOT_FOUND     1204
+#define EXTENSION_DUPLICATE     1205
+#define EXTENSION_INSTALLED_ALREADY 1206
 
 #define WIN_CONFIRMATION_STRINGS 1300
 #define CONFIRM_OVERWRITE_SESSION 1301
@@ -149,6 +152,7 @@
 #define CONFIRM_UNREGISTER_URL  1364
 #define INTERNAL_EDITOR_LARGE_FILE 1365
 #define CLOSE_BUTTON            1366
+#define EXTENSION_UNTRUSTED     1367
 
 #define WIN_INFORMATION_STRINGS 1400
 #define COMPARE_NO_DIFFERENCES  1402
@@ -575,6 +579,9 @@
 #define GENERATE_URL_TRANSFER_FILES 1970
 #define GENERATE_URL_FILE_SAMPLE 1971
 #define CHECK_FOR_UPDATES_TITLE 1972
+#define ADD_EXTENSION_CAPTION   1973
+#define ADD_EXTENSION_PROMPT    1974
+#define EXTENSION_INSTALLED     1975
 
 // 2xxx is reserved for TextsFileZilla.h
 

+ 10 - 3
source/resource/TextsWin1.rc

@@ -90,6 +90,12 @@ BEGIN
         TIPS_NONE, "No tips."
         KEYGEN_PUBLIC, "Converting public keys is not supported."
         FINGERPRINTSCAN_NEED_SECURE_SESSION, "Secure session (SSH or TLS/SSL) not specified."
+        EXTENSION_NOT_FOUND, "No extension found in the document."
+        EXTENSION_DEPENDENCY_ERROR, "The extension requires %s."
+        EXTENSION_DIRECTIVE_ERROR, "Invalid value \"%s\" of extension directive %s."
+        EXTENSION_DIRECTIVE_MISSING, "Missing mandatory extension directive %s."
+        EXTENSION_DUPLICATE, "There is already an extension with the name \"%s\"."
+        EXTENSION_INSTALLED_ALREADY, "The extension is installed already."
 
         WIN_CONFIRMATION_STRINGS, "WIN_CONFIRMATION"
         CONFIRM_OVERWRITE_SESSION, "Site with name '%s' already exists. Overwrite?"
@@ -148,6 +154,7 @@ BEGIN
         CONFIRM_UNREGISTER_URL, "Do you want to unregister WinSCP from handling all URL addresses?"
         INTERNAL_EDITOR_LARGE_FILE, "**Try to open large file?**\n\nThe file you are trying to open in an internal editor is too large (%s). WinSCP internal editor is not designed for editing large files. Consider using an external editor capable of editing large files.\n\nYou may try to open the file anyway, but you may run out of memory."
         CLOSE_BUTTON, "Close"
+        EXTENSION_UNTRUSTED, "The extension does not come from a trusted source or has not been reviewed yet. Are you sure you want to install it?"
 
         WIN_INFORMATION_STRINGS, "WIN_INFORMATION"
         COMPARE_NO_DIFFERENCES, "No differences found."
@@ -575,9 +582,9 @@ BEGIN
         GENERATE_URL_TRANSFER_FILES, "Transfer files"
         GENERATE_URL_FILE_SAMPLE, "A sample file list is used only, consider using a file mask to select files for the transfer."
         CHECK_FOR_UPDATES_TITLE, "Check for Updates"
-        EXTENSION_DEPENDENCY_ERROR, "The extension requires %s"
-        EXTENSION_DIRECTIVE_ERROR, "Invalid value \"%s\" of extension directive %s"
-        EXTENSION_DIRECTIVE_MISSING, "Missing mandatory extension directive %s"
+        ADD_EXTENSION_CAPTION, "Add Extension"
+        ADD_EXTENSION_PROMPT, "Enter URL or path to the &extension:"
+        EXTENSION_INSTALLED, "Extension was installed. Please restart WinSCP."
 
         WIN_VARIABLE_STRINGS, "WIN_VARIABLE"
         WINSCP_COPYRIGHT, "Copyright © 2000-2016 Martin Prikryl"

+ 6 - 11
source/windows/Setup.cpp

@@ -858,6 +858,11 @@ static THttp * __fastcall CreateHttp(const TUpdatesConfiguration & Updates)
   return Http.release();
 }
 //---------------------------------------------------------------------------
+THttp * __fastcall CreateHttp()
+{
+  return CreateHttp(WinConfiguration->Updates);
+}
+//---------------------------------------------------------------------------
 static bool __fastcall DoQueryUpdates(TUpdatesConfiguration & Updates, bool CollectUsage)
 {
   bool Complete = false;
@@ -1297,17 +1302,7 @@ void __fastcall TUpdateDownloadThread::UpdateDownloaded()
     DownloadNotVerified();
   }
 
-  UnicodeString FileName = FUpdates.Results.DownloadUrl;
-  int P = FileName.Pos(L"?");
-  if (P > 0)
-  {
-    FileName.SetLength(P - 1);
-  }
-  P = FileName.LastDelimiter("/");
-  if (DebugAlwaysTrue(P > 0))
-  {
-    FileName.Delete(1, P);
-  }
+  UnicodeString FileName = ExtractFileNameFromUrl(FUpdates.Results.DownloadUrl);
   UnicodeString TemporaryDirectory = WinConfiguration->ExpandedTemporaryDirectory();
   UnicodeString SetupPathBase = IncludeTrailingBackslash(TemporaryDirectory) + FileName;
   UnicodeString SetupPath = SetupPathBase;

+ 2 - 0
source/windows/Setup.h

@@ -8,6 +8,8 @@
 void __fastcall SetupInitialize();
 void __fastcall AddSearchPath(const UnicodeString Path);
 void __fastcall RemoveSearchPath(const UnicodeString Path);
+class THttp;
+THttp * __fastcall CreateHttp();
 void __fastcall GetUpdatesMessage(UnicodeString & Message, bool & New, TQueryType & Type, bool Force);
 bool __fastcall CheckForUpdates(bool CachedResults);
 bool __fastcall QueryUpdates(TUpdatesConfiguration & Updates);

+ 11 - 4
source/windows/Tools.cpp

@@ -525,14 +525,21 @@ void __fastcall ExitActiveControl(TForm * Form)
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall OpenBrowser(UnicodeString URL)
+bool __fastcall IsWinSCPUrl(const UnicodeString & Url)
 {
   UnicodeString HomePageUrl = LoadStr(HOMEPAGE_URL);
-  UnicodeString HttpHomePageUrl = ReplaceStr(L"http://", L"https://", HomePageUrl);
+  UnicodeString HttpHomePageUrl = ChangeUrlProtocol(HomePageUrl, HttpProtocol);
   DebugAssert(HomePageUrl != HttpHomePageUrl);
-  if (SameText(URL.SubString(1, HomePageUrl.Length()), HomePageUrl) ||
-      DebugAlwaysFalse(SameText(URL.SubString(1, HttpHomePageUrl.Length()), HttpHomePageUrl)))
+  return
+    StartsText(HomePageUrl, Url) ||
+    StartsText(HttpHomePageUrl, Url);
+}
+//---------------------------------------------------------------------------
+void __fastcall OpenBrowser(UnicodeString URL)
+{
+  if (IsWinSCPUrl(URL))
   {
+    DebugAssert(!IsHttpUrl(URL));
     URL = CampaignUrl(URL);
   }
   ShellExecute(Application->Handle, L"open", URL.c_str(), NULL, NULL, SW_SHOWNORMAL);

+ 1 - 0
source/windows/Tools.h

@@ -37,6 +37,7 @@ TColor __fastcall GetNonZeroColor(TColor Color);
 void __fastcall ValidateMaskEdit(TComboBox * Edit);
 void __fastcall ValidateMaskEdit(TEdit * Edit);
 void __fastcall ValidateMaskEdit(TMemo * Edit, bool Directory);
+bool __fastcall IsWinSCPUrl(const UnicodeString & Url);
 void __fastcall OpenBrowser(UnicodeString URL);
 void __fastcall ShowHelp(const UnicodeString & HelpKeyword);
 bool __fastcall IsFormatInClipboard(unsigned int Format);

+ 1 - 1
source/windows/VCLCommon.cpp

@@ -1813,7 +1813,7 @@ void __fastcall LinkLabel(TStaticText * StaticText, UnicodeString Url,
     StaticText->Caption = Url;
   }
 
-  bool IsUrl = IsHttpUrl(StaticText->Caption);
+  bool IsUrl = IsHttpOrHttpsUrl(StaticText->Caption);
   if (IsUrl)
   {
     DebugAssert(StaticText->PopupMenu == NULL);

+ 127 - 47
source/windows/WinConfiguration.cpp

@@ -1182,7 +1182,8 @@ void __fastcall TWinConfiguration::LoadFrom(THierarchicalStorage * Storage)
   AddVersionToHistory();
 }
 //---------------------------------------------------------------------------
-void __fastcall TWinConfiguration::DoLoadExtensionList(const UnicodeString & Path, const UnicodeString & PathId, TStringList * DeletedExtensions)
+void __fastcall TWinConfiguration::DoLoadExtensionList(
+  const UnicodeString & Path, const UnicodeString & PathId, TStringList * DeletedExtensions)
 {
   TSearchRecChecked SearchRec;
   int FindAttrs = faReadOnly | faArchive;
@@ -1190,16 +1191,18 @@ void __fastcall TWinConfiguration::DoLoadExtensionList(const UnicodeString & Pat
   {
     try
     {
-      const UnicodeString Ext(UpperCase(".WinSCPextension"));
       do
       {
-        int P = Pos(Ext, UpperCase(SearchRec.Name));
-        // Ends with Ext or there's another extension after it
-        if ((P > 1) &&
-            ((SearchRec.Name.Length() == (P + Ext.Length() - 1)) || (SearchRec.Name[P + Ext.Length()] == L'.')))
+        UnicodeString Id = TCustomCommandType::GetExtensionId(SearchRec.Name);
+        if (!Id.IsEmpty())
         {
-          UnicodeString Id = IncludeTrailingBackslash(PathId) + SearchRec.Name.SubString(1, P - 1);
-          if (DeletedExtensions->IndexOf(Id) < 0)
+          Id = IncludeTrailingBackslash(PathId) + Id;
+          if (DeletedExtensions->IndexOf(Id) >= 0)
+          {
+            // reconstruct the list, so that we remove the commands that no longer exists
+            AddToList(FExtensionsDeleted, Id, L"|");
+          }
+          else
           {
             std::unique_ptr<TCustomCommandType> CustomCommand(new TCustomCommandType());
             CustomCommand->Id = Id;
@@ -1207,13 +1210,6 @@ void __fastcall TWinConfiguration::DoLoadExtensionList(const UnicodeString & Pat
             try
             {
               CustomCommand->LoadExtension(IncludeTrailingBackslash(Path) + SearchRec.Name);
-
-              int Index = FExtensionList->FindIndex(CustomCommand->Name, false);
-              if (Index >= 0)
-              {
-                FExtensionList->Delete(Index);
-              }
-
               FExtensionList->Add(CustomCommand.release());
             }
             catch (...)
@@ -1241,20 +1237,57 @@ void __fastcall ParseExtensionList(TStrings * Strings, UnicodeString S)
   }
 }
 //---------------------------------------------------------------------------
+const UnicodeString ExtensionsSubFolder(L"Extensions");
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TWinConfiguration::GetUserExtensionsPath()
+{
+  return IncludeTrailingBackslash(GetShellFolderPath(CSIDL_APPDATA)) + L"WinSCP\\" + ExtensionsSubFolder;
+}
+//---------------------------------------------------------------------------
+TStrings * __fastcall TWinConfiguration::GetExtensionsPaths()
+{
+  std::unique_ptr<TStrings> Result(new TStringList());
+  UnicodeString ExeParentPath = ExcludeTrailingBackslash(ExtractFilePath(Application->ExeName));
+  Result->Values[L"common"] = ExeParentPath;
+  UnicodeString CommonExtensions = IncludeTrailingBackslash(ExeParentPath) + ExtensionsSubFolder;
+  Result->Values[L"commonext"] = CommonExtensions;
+  Result->Values[L"userext"] = GetUserExtensionsPath();
+  return Result.release();
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TWinConfiguration::GetExtensionId(const UnicodeString & ExtensionPath)
+{
+  UnicodeString Path = ExcludeTrailingBackslash(ExtractFilePath(ExtensionPath));
+
+  UnicodeString NameId = TCustomCommandType::GetExtensionId(ExtractFileName(ExtensionPath));
+  if (!NameId.IsEmpty())
+  {
+    std::unique_ptr<TStrings> ExtensionsPaths(GetExtensionsPaths());
+    for (int Index = 0; Index < ExtensionsPaths->Count; Index++)
+    {
+      if (CompareFileName(Path, ExtensionsPaths->ValueFromIndex[Index]))
+      {
+        return IncludeTrailingBackslash(ExtensionsPaths->Names[Index]) + NameId;
+      }
+    }
+  }
+  return L"";
+}
+//---------------------------------------------------------------------------
 void __fastcall TWinConfiguration::LoadExtensionList()
 {
   FExtensionList->Clear();
 
   std::unique_ptr<TStringList> DeletedExtensions(CreateSortedStringList());
   ParseExtensionList(DeletedExtensions.get(), FExtensionsDeleted);
+  // will reconstruct the list in DoLoadExtensionList
+  FExtensionsDeleted = L"";
 
-  const UnicodeString ExtensionsSubFolder(L"Extensions");
-  UnicodeString ExeParentPath = ExcludeTrailingBackslash(ExtractFilePath(Application->ExeName));
-  DoLoadExtensionList(ExeParentPath, L"common", DeletedExtensions.get());
-  UnicodeString CommonExtensions = IncludeTrailingBackslash(ExeParentPath) + ExtensionsSubFolder;
-  DoLoadExtensionList(CommonExtensions, L"commonext", DeletedExtensions.get());
-  UnicodeString UserExtensions = IncludeTrailingBackslash(GetShellFolderPath(CSIDL_APPDATA)) + L"WinSCP\\" + ExtensionsSubFolder;
-  DoLoadExtensionList(UserExtensions, L"userext", DeletedExtensions.get());
+  std::unique_ptr<TStrings> ExtensionsPaths(GetExtensionsPaths());
+  for (int Index = 0; Index < ExtensionsPaths->Count; Index++)
+  {
+    DoLoadExtensionList(ExtensionsPaths->ValueFromIndex[Index], ExtensionsPaths->Names[Index], DeletedExtensions.get());
+  }
 
   std::unique_ptr<TStringList> OrderedExtensions(new TStringList());
   ParseExtensionList(OrderedExtensions.get(), FExtensionsOrder);
@@ -2092,6 +2125,9 @@ void __fastcall TWinConfiguration::SetExtensionList(TCustomCommandList * value)
 {
   if (!ExtensionList->Equals(value))
   {
+    std::unique_ptr<TStringList> DeletedExtensions(CreateSortedStringList());
+    ParseExtensionList(DeletedExtensions.get(), FExtensionsDeleted);
+
     for (int Index = 0; Index < ExtensionList->Count; Index++)
     {
       const TCustomCommandType * CustomCommand = ExtensionList->Commands[Index];
@@ -2100,7 +2136,7 @@ void __fastcall TWinConfiguration::SetExtensionList(TCustomCommandList * value)
         if (FileExists(CustomCommand->FileName) &&
             !DeleteFile(CustomCommand->FileName))
         {
-          AddToList(FExtensionsDeleted, CustomCommand->Id, L"|");
+          DeletedExtensions->Add(CustomCommand->Id);
         }
       }
     }
@@ -2110,10 +2146,22 @@ void __fastcall TWinConfiguration::SetExtensionList(TCustomCommandList * value)
     {
       const TCustomCommandType * CustomCommand = value->Commands[Index];
       AddToList(FExtensionsOrder, CustomCommand->Id, L"|");
+
+      int DeletedIndex = DeletedExtensions->IndexOf(CustomCommand->Id);
+      if (DeletedIndex >= 0)
+      {
+        DeletedExtensions->Delete(DeletedIndex);
+      }
     }
 
     FExtensionList->Assign(value);
 
+    FExtensionsDeleted = L"";
+    for (int Index = 0; Index < DeletedExtensions->Count; Index++)
+    {
+      AddToList(FExtensionsDeleted, DeletedExtensions->Strings[Index], L"|");
+    }
+
     Changed();
   }
 }
@@ -2583,14 +2631,34 @@ bool __fastcall TCustomCommandType::Equals(const TCustomCommandType * Other) con
 const UnicodeString ExtensionNameDirective(L"name");
 const UnicodeString ExtensionCommandDirective(L"command");
 const wchar_t ExtensionMark = L'@';
+const UnicodeString WinSCPExtensionExt(".WinSCPextension");
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TCustomCommandType::GetExtensionId(const UnicodeString & Name)
+{
+  UnicodeString Result;
+  int P = Pos(UpperCase(WinSCPExtensionExt), UpperCase(Name));
+  // Ends with Ext or there's another extension after it
+  if ((P > 1) &&
+      ((Name.Length() == (P + WinSCPExtensionExt.Length() - 1)) || (Name[P + WinSCPExtensionExt.Length()] == L'.')))
+  {
+    Result = Name.SubString(1, P - 1);
+  }
+  return Result;
+}
 //---------------------------------------------------------------------------
 void __fastcall TCustomCommandType::LoadExtension(const UnicodeString & Path)
 {
   std::unique_ptr<TStringList> Lines(new TStringList());
+  FileName = Path;
   LoadScriptFromFile(Path, Lines.get());
-
+  LoadExtension(Lines.get());
+  Command = ReplaceStr(Command, L"%EXTENSION_PATH%", Path);
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomCommandType::LoadExtension(TStrings * Lines)
+{
   Params = ccLocal;
-  FileName = Path;
+  bool AnythingFound = false;
 
   for (int Index = 0; Index < Lines->Count; Index++)
   {
@@ -2627,13 +2695,13 @@ void __fastcall TCustomCommandType::LoadExtension(const UnicodeString & Path)
           UnicodeString Key = Line.SubString(2, P - 2).LowerCase();
           UnicodeString Directive = UnicodeString(ExtensionMark) + Key;
           UnicodeString Value = Line.SubString(P + 1, Line.Length() - P).Trim();
+          bool KnownKey = true;
           if (Key == ExtensionNameDirective)
           {
             Name = Value;
           }
           else if (Key == ExtensionCommandDirective)
           {
-            Value = ReplaceStr(Value, L"%EXTENSION_PATH%", Path);
             Command = Value;
           }
           else if (Key == L"require")
@@ -2653,7 +2721,7 @@ void __fastcall TCustomCommandType::LoadExtension(const UnicodeString & Path)
 
             if (Failed)
             {
-              throw Exception(FMTLOAD(EXTENSION_DEPENDENCY_ERROR, (DependencyVersion)));
+              throw Exception(MainInstructions(FMTLOAD(EXTENSION_DEPENDENCY_ERROR, (DependencyVersion))));
             }
           }
           else if (Key == L"side")
@@ -2668,7 +2736,7 @@ void __fastcall TCustomCommandType::LoadExtension(const UnicodeString & Path)
             }
             else
             {
-              throw Exception(FMTLOAD(EXTENSION_DIRECTIVE_ERROR, (Directive, Value)));
+              throw Exception(MainInstructions(FMTLOAD(EXTENSION_DIRECTIVE_ERROR, (Value, Directive))));
             }
           }
           else if (Key == L"flag")
@@ -2695,7 +2763,7 @@ void __fastcall TCustomCommandType::LoadExtension(const UnicodeString & Path)
             }
             else
             {
-              throw Exception(FMTLOAD(EXTENSION_DIRECTIVE_ERROR, (Directive, Value)));
+              throw Exception(MainInstructions(FMTLOAD(EXTENSION_DIRECTIVE_ERROR, (Value, Directive))));
             }
           }
           else if (Key == L"shortcut")
@@ -2707,7 +2775,7 @@ void __fastcall TCustomCommandType::LoadExtension(const UnicodeString & Path)
             }
             else
             {
-              throw Exception(FMTLOAD(EXTENSION_DIRECTIVE_ERROR, (Key, Value)));
+              throw Exception(MainInstructions(FMTLOAD(EXTENSION_DIRECTIVE_ERROR, (Value, Directive))));
             }
           }
           else if (Key == L"description")
@@ -2728,7 +2796,12 @@ void __fastcall TCustomCommandType::LoadExtension(const UnicodeString & Path)
           }
           else
           {
-            // noop
+            KnownKey = false;
+          }
+
+          if (KnownKey)
+          {
+            AnythingFound = true;
           }
         }
       }
@@ -2739,14 +2812,19 @@ void __fastcall TCustomCommandType::LoadExtension(const UnicodeString & Path)
     }
   }
 
+  if (!AnythingFound)
+  {
+    throw Exception(MainInstructions(LoadStr(EXTENSION_NOT_FOUND)));
+  }
+
   if (Name.IsEmpty())
   {
-    throw Exception(FMTLOAD(EXTENSION_DIRECTIVE_MISSING, (UnicodeString(ExtensionMark) + ExtensionNameDirective)));
+    throw Exception(MainInstructions(FMTLOAD(EXTENSION_DIRECTIVE_MISSING, (UnicodeString(ExtensionMark) + ExtensionNameDirective))));
   }
 
   if (Command.IsEmpty())
   {
-    throw Exception(FMTLOAD(EXTENSION_DIRECTIVE_MISSING, (UnicodeString(ExtensionMark) + ExtensionCommandDirective)));
+    throw Exception(MainInstructions(FMTLOAD(EXTENSION_DIRECTIVE_MISSING, (UnicodeString(ExtensionMark) + ExtensionCommandDirective))));
   }
 }
 //---------------------------------------------------------------------------
@@ -3002,26 +3080,16 @@ void __fastcall TCustomCommandList::Assign(const TCustomCommandList * Other)
   Modify();
 }
 //---------------------------------------------------------------------------
-int TCustomCommandList::FindIndex(const UnicodeString & Name, bool CaseSensitive) const
+const TCustomCommandType * TCustomCommandList::Find(const UnicodeString Name) const
 {
   for (int Index = 0; Index < FCommands->Count; Index++)
   {
-    if (CaseSensitive && (Commands[Index]->Name == Name))
-    {
-      return Index;
-    }
-    if (!CaseSensitive && SameText(Commands[Index]->Name, Name))
+    if (Commands[Index]->Name == Name)
     {
-      return Index;
+      return Commands[Index];
     }
   }
-  return -1;
-}
-//---------------------------------------------------------------------------
-const TCustomCommandType * TCustomCommandList::Find(const UnicodeString Name) const
-{
-  int Index = FindIndex(Name, true);
-  return (Index >= 0) ? Commands[Index] : NULL;
+  return NULL;
 }
 //---------------------------------------------------------------------------
 const TCustomCommandType * TCustomCommandList::Find(TShortCut ShortCut) const
@@ -3036,6 +3104,18 @@ const TCustomCommandType * TCustomCommandList::Find(TShortCut ShortCut) const
   return NULL;
 }
 //---------------------------------------------------------------------------
+int TCustomCommandList::FindIndexByFileName(const UnicodeString & FileName) const
+{
+  for (int Index = 0; Index < FCommands->Count; Index++)
+  {
+    if (CompareFileName(Commands[Index]->FileName, FileName))
+    {
+      return Index;
+    }
+  }
+  return NULL;
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomCommandList::ShortCuts(TShortCuts & ShortCuts) const
 {
   for (int Index = 0; Index < FCommands->Count; Index++)

+ 7 - 1
source/windows/WinConfiguration.h

@@ -566,6 +566,7 @@ protected:
   bool __fastcall DoIsBeta(const UnicodeString & ReleaseType);
   void __fastcall AskForMasterPassword();
   void __fastcall DoLoadExtensionList(const UnicodeString & Path, const UnicodeString & PathId, TStringList * DeletedExtensions);
+  TStrings * __fastcall GetExtensionsPaths();
 
 public:
   __fastcall TWinConfiguration();
@@ -598,6 +599,8 @@ public:
   void __fastcall MinimizeToTrayOnce();
   void __fastcall LoadExtensionList();
   void __fastcall CustomCommandShortCuts(TShortCuts & ShortCuts) const;
+  UnicodeString __fastcall GetUserExtensionsPath();
+  UnicodeString __fastcall GetExtensionId(const UnicodeString & ExtensionPath);
 
   static void __fastcall RestoreFont(const TFontConfiguration & Configuration, TFont * Font);
   static void __fastcall StoreFont(TFont * Font, TFontConfiguration & Configuration);
@@ -707,6 +710,8 @@ public:
   bool __fastcall Equals(const TCustomCommandType * Other) const;
 
   void __fastcall LoadExtension(const UnicodeString & Path);
+  void __fastcall LoadExtension(TStrings * Lines);
+  static UnicodeString __fastcall GetExtensionId(const UnicodeString & Name);
 
   __property UnicodeString Name = { read = FName, write = FName };
   __property UnicodeString Command = { read = FCommand, write = FCommand };
@@ -744,9 +749,9 @@ public:
   void __fastcall Delete(int Index);
   void __fastcall SortBy(TStrings * Ids);
 
-  int FindIndex(const UnicodeString & Name, bool CaseSensitive) const;
   const TCustomCommandType * Find(const UnicodeString Name) const;
   const TCustomCommandType * Find(TShortCut ShortCut) const;
+  int FindIndexByFileName(const UnicodeString & FileName) const;
 
   bool __fastcall Equals(const TCustomCommandList * Other) const;
   void __fastcall Assign(const TCustomCommandList * Other);
@@ -766,5 +771,6 @@ private:
 };
 //---------------------------------------------------------------------------
 extern TWinConfiguration * WinConfiguration;
+extern const UnicodeString WinSCPExtensionExt;
 //---------------------------------------------------------------------------
 #endif

+ 1 - 1
source/windows/WinHelp.cpp

@@ -99,7 +99,7 @@ void __fastcall TWebHelpSystem::ShowTableOfContents()
 //---------------------------------------------------------------------------
 void __fastcall TWebHelpSystem::ShowHelp(const UnicodeString AHelpString)
 {
-  if (IsHttpUrl(AHelpString))
+  if (IsHttpOrHttpsUrl(AHelpString))
   {
     OpenBrowser(AHelpString);
   }