diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b3a5973 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Daquanne Dwight + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PRIVACY b/PRIVACY new file mode 100644 index 0000000..62fa4c3 --- /dev/null +++ b/PRIVACY @@ -0,0 +1,14 @@ +PRIVACY STATEMENT +Last updated March 25, 2019 + +Reshaper is distributed through GitHub and the Windows Store (Microsoft). The privacy policies of those entities need to be read to fully understand usage of your information. +Microsoft Privacy Statement - https://privacy.microsoft.com/en-us/privacystatement +GitHub Privacy Statemnt - https://help.github.com/en/articles/github-privacy-statement + +Reshaper does not collect any personal information for distrubtion to its developers or third parties. As a network traffic debugging tool, Reshaper allows you to establiash network connections to machines of third parties you specify in the app's configuration. While connected, transmission of information to and from our App is at your own risk. + +We will update this policy as necessary to account for app changes ans to stay compliant with relevant laws. The updated version will be indicated by an updated date and the updated version will be effective as soon as it is accessible. + +If you have questions or comments about this policy, you may email us at support@synfron.com + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef00764 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Reshaper +Traffic debugger for text based protocols over TCP/IP + +![ReshaperScreenshot](https://user-images.githubusercontent.com/48854453/54864904-00e29c00-4d34-11e9-8120-a9b6df6f6eea.png) + +## Features +- View, manipulate, and trigger actions on incoming and outgoing TCP/IP proxy traffic using configurable rules +- JavaScript engine integration +- Extensible using the Managed Extensibility Framework + +## To Do +- Improve native HTTP support +- Improve UI visuals and usability +- Add usage and dev documentation + +## Contributions +Contributions are encouraged, especially those that address the To Do items. Issues and Pull Requests welcome. Also help us spread the word. + +## License +MIT License. See [LICENSE](https://raw.githubusercontent.com/synfron/Reshaper/master/LICENSE) diff --git a/Reshaper.sln b/Reshaper.sln new file mode 100644 index 0000000..99468b3 --- /dev/null +++ b/Reshaper.sln @@ -0,0 +1,71 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28606.126 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reshaper", "Reshaper\Reshaper.csproj", "{07A60572-ED13-40B5-9E5B-7A916A6DA13E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReshaperCore", "ReshaperCore\ReshaperCore.csproj", "{1CFAFA78-E77A-4E75-8949-BE86AA620071}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReshaperTests", "ReshaperTests\ReshaperTests.csproj", "{1B8A8089-9E76-46C6-A8F9-5574B561F4BA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReshaperScript", "ReshaperScript\ReshaperScript.csproj", "{4DFE46FF-0359-4E4B-9F94-591A17629213}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReshaperUI", "ReshaperUI\ReshaperUI.csproj", "{0CD611F5-5AB4-4300-9DAA-3930AE096A30}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {07A60572-ED13-40B5-9E5B-7A916A6DA13E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07A60572-ED13-40B5-9E5B-7A916A6DA13E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07A60572-ED13-40B5-9E5B-7A916A6DA13E}.Debug|x86.ActiveCfg = Debug|Any CPU + {07A60572-ED13-40B5-9E5B-7A916A6DA13E}.Debug|x86.Build.0 = Debug|Any CPU + {07A60572-ED13-40B5-9E5B-7A916A6DA13E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07A60572-ED13-40B5-9E5B-7A916A6DA13E}.Release|Any CPU.Build.0 = Release|Any CPU + {07A60572-ED13-40B5-9E5B-7A916A6DA13E}.Release|x86.ActiveCfg = Release|Any CPU + {07A60572-ED13-40B5-9E5B-7A916A6DA13E}.Release|x86.Build.0 = Release|Any CPU + {1CFAFA78-E77A-4E75-8949-BE86AA620071}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CFAFA78-E77A-4E75-8949-BE86AA620071}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CFAFA78-E77A-4E75-8949-BE86AA620071}.Debug|x86.ActiveCfg = Debug|Any CPU + {1CFAFA78-E77A-4E75-8949-BE86AA620071}.Debug|x86.Build.0 = Debug|Any CPU + {1CFAFA78-E77A-4E75-8949-BE86AA620071}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CFAFA78-E77A-4E75-8949-BE86AA620071}.Release|Any CPU.Build.0 = Release|Any CPU + {1CFAFA78-E77A-4E75-8949-BE86AA620071}.Release|x86.ActiveCfg = Release|Any CPU + {1CFAFA78-E77A-4E75-8949-BE86AA620071}.Release|x86.Build.0 = Release|Any CPU + {1B8A8089-9E76-46C6-A8F9-5574B561F4BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B8A8089-9E76-46C6-A8F9-5574B561F4BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B8A8089-9E76-46C6-A8F9-5574B561F4BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {1B8A8089-9E76-46C6-A8F9-5574B561F4BA}.Debug|x86.Build.0 = Debug|Any CPU + {1B8A8089-9E76-46C6-A8F9-5574B561F4BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B8A8089-9E76-46C6-A8F9-5574B561F4BA}.Release|Any CPU.Build.0 = Release|Any CPU + {1B8A8089-9E76-46C6-A8F9-5574B561F4BA}.Release|x86.ActiveCfg = Release|Any CPU + {1B8A8089-9E76-46C6-A8F9-5574B561F4BA}.Release|x86.Build.0 = Release|Any CPU + {4DFE46FF-0359-4E4B-9F94-591A17629213}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DFE46FF-0359-4E4B-9F94-591A17629213}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DFE46FF-0359-4E4B-9F94-591A17629213}.Debug|x86.ActiveCfg = Debug|Any CPU + {4DFE46FF-0359-4E4B-9F94-591A17629213}.Debug|x86.Build.0 = Debug|Any CPU + {4DFE46FF-0359-4E4B-9F94-591A17629213}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DFE46FF-0359-4E4B-9F94-591A17629213}.Release|Any CPU.Build.0 = Release|Any CPU + {4DFE46FF-0359-4E4B-9F94-591A17629213}.Release|x86.ActiveCfg = Release|Any CPU + {4DFE46FF-0359-4E4B-9F94-591A17629213}.Release|x86.Build.0 = Release|Any CPU + {0CD611F5-5AB4-4300-9DAA-3930AE096A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CD611F5-5AB4-4300-9DAA-3930AE096A30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CD611F5-5AB4-4300-9DAA-3930AE096A30}.Debug|x86.ActiveCfg = Debug|Any CPU + {0CD611F5-5AB4-4300-9DAA-3930AE096A30}.Debug|x86.Build.0 = Debug|Any CPU + {0CD611F5-5AB4-4300-9DAA-3930AE096A30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CD611F5-5AB4-4300-9DAA-3930AE096A30}.Release|Any CPU.Build.0 = Release|Any CPU + {0CD611F5-5AB4-4300-9DAA-3930AE096A30}.Release|x86.ActiveCfg = Release|Any CPU + {0CD611F5-5AB4-4300-9DAA-3930AE096A30}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FDC5AFE9-A318-45B1-A8AD-16EF1689DC18} + EndGlobalSection +EndGlobal diff --git a/Reshaper/App.config b/Reshaper/App.config new file mode 100644 index 0000000..bae5d6d --- /dev/null +++ b/Reshaper/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Reshaper/App.xaml b/Reshaper/App.xaml new file mode 100644 index 0000000..6379497 --- /dev/null +++ b/Reshaper/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/Reshaper/App.xaml.cs b/Reshaper/App.xaml.cs new file mode 100644 index 0000000..3fa1cf9 --- /dev/null +++ b/Reshaper/App.xaml.cs @@ -0,0 +1,30 @@ +using System.Windows; +using ReshaperCore; + +namespace Reshaper +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + private readonly Bootstrapper bootstrapper; + + public App() + { + bootstrapper = new Bootstrapper(); + } + + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + bootstrapper.Init(); + } + + protected override void OnExit(ExitEventArgs e) + { + bootstrapper.Shutdown(); + base.OnExit(e); + } + } +} diff --git a/Reshaper/Assets/badgelogo.scale-100.ico b/Reshaper/Assets/badgelogo.scale-100.ico new file mode 100644 index 0000000..e6c5875 Binary files /dev/null and b/Reshaper/Assets/badgelogo.scale-100.ico differ diff --git a/Reshaper/Properties/Resources.Designer.cs b/Reshaper/Properties/Resources.Designer.cs new file mode 100644 index 0000000..c89a84f --- /dev/null +++ b/Reshaper/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Reshaper.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Reshaper.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Reshaper/Properties/Resources.resx b/Reshaper/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Reshaper/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Reshaper/Properties/Settings.Designer.cs b/Reshaper/Properties/Settings.Designer.cs new file mode 100644 index 0000000..f9f320a --- /dev/null +++ b/Reshaper/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Reshaper.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Reshaper/Properties/Settings.settings b/Reshaper/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/Reshaper/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Reshaper/Reshaper.csproj b/Reshaper/Reshaper.csproj new file mode 100644 index 0000000..3705ba8 --- /dev/null +++ b/Reshaper/Reshaper.csproj @@ -0,0 +1,159 @@ + + + + + Debug + AnyCPU + {07A60572-ED13-40B5-9E5B-7A916A6DA13E} + WinExe + Properties + Reshaper + Reshaper + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + Reshaper + Synfron + 4 + 1.0.0.%2a + false + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + none + true + bin\Release\ + TRACE + prompt + 4 + + + 7CCB2A41CA111B92E49713E59BE319CED08AB19B + + + Reshaper_TemporaryKey.pfx + + + true + + + false + + + Assets\badgelogo.scale-100.ico + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + False + Microsoft .NET Framework 4.6.1 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + {1CFAFA78-E77A-4E75-8949-BE86AA620071} + ReshaperCore + + + {0cd611f5-5ab4-4300-9daa-3930ae096a30} + ReshaperUI + + + + + + + + \ No newline at end of file diff --git a/Reshaper/packages.config b/Reshaper/packages.config new file mode 100644 index 0000000..38a2331 --- /dev/null +++ b/Reshaper/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ReshaperCore/Bootstrapper.cs b/ReshaperCore/Bootstrapper.cs new file mode 100644 index 0000000..14f474a --- /dev/null +++ b/ReshaperCore/Bootstrapper.cs @@ -0,0 +1,55 @@ +using System; +using System.ComponentModel.Composition.Hosting; +using ReshaperCore.Providers; + +namespace ReshaperCore +{ + public class Bootstrapper : IAssemblyLifetimeManager + { + private readonly CompositionContainerProvider _compositionContainerProvider; + private readonly TextRulesRegistryProvider _textRulesRegistryProvider; + private readonly HttpRulesRegistryProvider _httpRulesRegistryProvider; + private readonly ProxyRegistryProvider _proxyRegistryProvider; + private readonly SelfProvider _selfProvider; + private readonly SystemProxySettingsProvider _systemProxySettingsProvider; + + public Bootstrapper() + { + _selfProvider = new SelfProvider(); + _textRulesRegistryProvider = new TextRulesRegistryProvider(); + _httpRulesRegistryProvider = new HttpRulesRegistryProvider(); + _proxyRegistryProvider = new ProxyRegistryProvider(); + _compositionContainerProvider = new CompositionContainerProvider(); + _systemProxySettingsProvider = new SystemProxySettingsProvider(); + } + + public void Init() + { + CompositionContainer compositionContainer = _compositionContainerProvider.GetInstance(); + + _selfProvider.GetInstance().Init(); + _textRulesRegistryProvider.GetInstance().Init(); + _httpRulesRegistryProvider.GetInstance().Init(); + _proxyRegistryProvider.GetInstance().Init(); + + foreach (Lazy manager in compositionContainer.GetExports()) + { + manager.Value.Init(); + } + } + + public void Shutdown() + { + foreach (Lazy manager in _compositionContainerProvider.GetInstance().GetExports()) + { + manager.Value.Shutdown(); + } + + _systemProxySettingsProvider.GetInstance().ForceReset(); + _proxyRegistryProvider.GetInstance().SaveProxies(); + _textRulesRegistryProvider.GetInstance().SaveRules(); + _httpRulesRegistryProvider.GetInstance().SaveRules(); + _selfProvider.GetInstance().Shutdown(); + } + } +} diff --git a/ReshaperCore/Data/DefaultHttpRules.json b/ReshaperCore/Data/DefaultHttpRules.json new file mode 100644 index 0000000..d494ebb --- /dev/null +++ b/ReshaperCore/Data/DefaultHttpRules.json @@ -0,0 +1 @@ +[{"$type":"ReshaperCore.Rules.Rule, ReshaperCore","Whens":[{"$type":"ReshaperCore.Rules.Whens.WhenEventType, ReshaperCore","Type":2,"Negate":false,"UseOrCondition":false}],"Thens":[{"$type":"ReshaperCore.Rules.Thens.ThenDisconnect, ReshaperCore"}],"Enabled":true,"Name":"Close HTTP Connection","Locked":true,"Placement":0},{"$type":"ReshaperCore.Rules.Rule, ReshaperCore","Whens":[{"$type":"ReshaperCore.Rules.Whens.WhenEventType, ReshaperCore","Type":1,"Negate":false,"UseOrCondition":false},{"$type":"ReshaperCore.Rules.Whens.WhenEventType, ReshaperCore","Type":2,"Negate":false,"UseOrCondition":true}],"Thens":[{"$type":"ReshaperCore.Rules.Thens.ThenSkipProcessing, ReshaperCore"}],"Enabled":true,"Name":"Skip Connect/Disconnect Events","Locked":true,"Placement":0},{"$type":"ReshaperCore.Rules.Rule, ReshaperCore","Whens":[],"Thens":[{"$type":"ReshaperCore.Rules.Thens.ThenDelimitHttp, ReshaperCore"}],"Enabled":true,"Name":"Delimit HTTP Message","Locked":true,"Placement":0},{"$type":"ReshaperCore.Rules.Rule, ReshaperCore","Whens":[],"Thens":[{"$type":"ReshaperCore.Rules.Thens.ThenHttpConnect, ReshaperCore","OverrideCurrentConnection":false}],"Enabled":true,"Name":"Establish HTTP Connection","Locked":true,"Placement":1},{"$type":"ReshaperCore.Rules.Rule, ReshaperCore","Whens":[],"Thens":[{"$type":"ReshaperCore.Rules.Thens.ThenBroadcast, ReshaperCore"},{"$type":"ReshaperCore.Rules.Thens.ThenSendData, ReshaperCore"}],"Enabled":true,"Name":"Send Message","Locked":true,"Placement":2}] \ No newline at end of file diff --git a/ReshaperCore/Data/DefaultTextRules.json b/ReshaperCore/Data/DefaultTextRules.json new file mode 100644 index 0000000..e83570f --- /dev/null +++ b/ReshaperCore/Data/DefaultTextRules.json @@ -0,0 +1 @@ +[{"$type":"ReshaperCore.Rules.Rule, ReshaperCore","Whens":[{"$type":"ReshaperCore.Rules.Whens.WhenEventType, ReshaperCore","Type":1,"Negate":false,"UseOrCondition":false}],"Thens":[{"$type":"ReshaperCore.Rules.Thens.ThenConnect, ReshaperCore","DestinationHost":null,"DestinationPort":null,"OverrideCurrentConnection":false},{"$type":"ReshaperCore.Rules.Thens.ThenSkipProcessing, ReshaperCore"}],"Enabled":true,"Name":"Establish Text Connection","Locked":true,"Placement":0},{"$type":"ReshaperCore.Rules.Rule, ReshaperCore","Whens":[{"$type":"ReshaperCore.Rules.Whens.WhenEventType, ReshaperCore","Type":2,"Negate":false,"UseOrCondition":false}],"Thens":[{"$type":"ReshaperCore.Rules.Thens.ThenDisconnect, ReshaperCore"},{"$type":"ReshaperCore.Rules.Thens.ThenSkipProcessing, ReshaperCore"}],"Enabled":true,"Name":"Close Text Connection","Locked":true,"Placement":0},{"$type":"ReshaperCore.Rules.Rule, ReshaperCore","Whens":[],"Thens":[{"$type":"ReshaperCore.Rules.Thens.ThenDelimitText, ReshaperCore"}],"Enabled":true,"Name":"Process Text Message","Locked":true,"Placement":0},{"$type":"ReshaperCore.Rules.Rule, ReshaperCore","Whens":[],"Thens":[{"$type":"ReshaperCore.Rules.Thens.ThenBroadcast, ReshaperCore"},{"$type":"ReshaperCore.Rules.Thens.ThenSendData, ReshaperCore"}],"Enabled":true,"Name":"Send Message","Locked":true,"Placement":2}] \ No newline at end of file diff --git a/ReshaperCore/IAssemblyLifetimeManager.cs b/ReshaperCore/IAssemblyLifetimeManager.cs new file mode 100644 index 0000000..be3acfd --- /dev/null +++ b/ReshaperCore/IAssemblyLifetimeManager.cs @@ -0,0 +1,8 @@ +namespace ReshaperCore +{ + public interface IAssemblyLifetimeManager + { + void Init(); + void Shutdown(); + } +} diff --git a/ReshaperCore/ISelf.cs b/ReshaperCore/ISelf.cs new file mode 100644 index 0000000..9ffe2e8 --- /dev/null +++ b/ReshaperCore/ISelf.cs @@ -0,0 +1,33 @@ +using ReshaperCore.Rules; +using ReshaperCore.Vars; + +namespace ReshaperCore +{ + public delegate void NewEventBroadcastedHandler(EventInfo eventInfo); + public delegate void PromptRequestedHandler(PromptRequestedEventArgs args); + public delegate void PromptCanceledHandler(object id); + public delegate void PromptCallback(string text); + + public class PromptRequestedEventArgs + { + public string Description { get; set; } + public object Id { get; set; } + public string Text { get; set; } + public PromptCallback Callback { get; set; } + } + + public interface ISelf + { + Variables Variables { get; } + + event NewEventBroadcastedHandler NewEventBroadcasted; + event PromptCanceledHandler PromptCanceled; + event PromptRequestedHandler PromptRequested; + + void Init(); + void Shutdown(); + void BroadcastEvent(EventInfo eventInfo); + void PromptForInput(PromptRequestedEventArgs args); + void CancelPrompt(object id); + } +} \ No newline at end of file diff --git a/ReshaperCore/Messages/DataDirection.cs b/ReshaperCore/Messages/DataDirection.cs new file mode 100644 index 0000000..717f897 --- /dev/null +++ b/ReshaperCore/Messages/DataDirection.cs @@ -0,0 +1,8 @@ +namespace ReshaperCore.Messages +{ + public enum DataDirection + { + Origin, + Target + } +} diff --git a/ReshaperCore/Messages/Entities/EntityContainer.cs b/ReshaperCore/Messages/Entities/EntityContainer.cs new file mode 100644 index 0000000..b3631c4 --- /dev/null +++ b/ReshaperCore/Messages/Entities/EntityContainer.cs @@ -0,0 +1,19 @@ +using System; +using ReshaperCore.Utils; + +namespace ReshaperCore.Messages +{ + public abstract class EntityContainer : ObservableEntity + { + private static int _maxFlag = 0; + + protected static long RegisterFlag() + { + long entityFlag = (long)Math.Pow(2, _maxFlag); + _maxFlag++; + return entityFlag; + } + + public abstract long GetEntityFlag(); + } +} diff --git a/ReshaperCore/Messages/Entities/Http/HttpBody.cs b/ReshaperCore/Messages/Entities/Http/HttpBody.cs new file mode 100644 index 0000000..00a7168 --- /dev/null +++ b/ReshaperCore/Messages/Entities/Http/HttpBody.cs @@ -0,0 +1,117 @@ +using System.Text; + +namespace ReshaperCore.Messages.Entities.Http +{ + /// + /// Represents the body of an HTTP request or response + /// + public class HttpBody : EntityContainer + { + private static long _entityFlag; + private string _text; + private byte[] _rawBytes; + + public Encoding TextEncoding + { + get; + set; + } = Encoding.UTF8; + + public virtual byte[] RawBytes + { + get + { + return _rawBytes; + } + set + { + _rawBytes = value; + _text = TextEncoding.GetString(_rawBytes); + OnPropertyChanged(nameof(RawBytes)); + OnPropertyChanged(nameof(Text)); + OnPropertyChanged(nameof(ContentLength)); + } + } + + /// + /// The full text of the body + /// + public virtual string Text + { + set + { + _text = value; + _rawBytes = TextEncoding.GetBytes(_text); + OnPropertyChanged(nameof(Text)); + OnPropertyChanged(nameof(RawBytes)); + OnPropertyChanged(nameof(ContentLength)); + } + get + { + return _text; + } + } + + /// + /// The size of the body + /// + public int ContentLength + { + get + { + return this._rawBytes.Length; + } + } + + static HttpBody() + { + _entityFlag = RegisterFlag(); + } + + public override long GetEntityFlag() + { + return _entityFlag; + } + + public static long EntityFlag + { + get + { + return _entityFlag; + } + } + + /// + /// + /// + /// The text of the body + public override string ToString() + { + return Text; + } + + /// + /// + /// + /// + /// + public override bool Equals(object obj) + { + bool equals = false; + if (obj is HttpBody) + { + equals = (obj as HttpBody)?.ToString() == ToString(); + } + return equals; + } + + /// + /// + /// + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + } +} diff --git a/ReshaperCore/Messages/Entities/Http/HttpChunkedBody.cs b/ReshaperCore/Messages/Entities/Http/HttpChunkedBody.cs new file mode 100644 index 0000000..9bc2131 --- /dev/null +++ b/ReshaperCore/Messages/Entities/Http/HttpChunkedBody.cs @@ -0,0 +1,119 @@ +namespace ReshaperCore.Messages.Entities.Http +{ + class HttpChunkedBody : HttpBody + { + private static long _entityFlag; + private string _unchunkedText; + private byte[] _unchunkedBytes; + + public override byte[] RawBytes + { + get + { + return base.RawBytes; + } + set + { + base.RawBytes = value; + UnchunkedBytes = value; + } + } + + /// + /// The full text of the body + /// + public override string Text + { + set + { + base.Text = value; + UnchunkedText = value; + } + get + { + return base.Text; + } + } + + public virtual string UnchunkedText + { + set + { + _unchunkedText = value; + _unchunkedBytes = TextEncoding.GetBytes(_unchunkedText); + OnPropertyChanged(nameof(UnchunkedText)); + OnPropertyChanged(nameof(UnchunkedBytes)); + } + get + { + return _unchunkedText; + } + } + + public virtual byte[] UnchunkedBytes + { + get + { + return _unchunkedBytes; + } + set + { + _unchunkedBytes = value; + _unchunkedText = TextEncoding.GetString(_unchunkedBytes); + OnPropertyChanged(nameof(UnchunkedText)); + OnPropertyChanged(nameof(UnchunkedBytes)); + } + } + + static HttpChunkedBody() + { + _entityFlag = RegisterFlag(); + } + + public override long GetEntityFlag() + { + return _entityFlag; + } + + public static new long EntityFlag + { + get + { + return _entityFlag; + } + } + + /// + /// + /// + /// The text of the body + public override string ToString() + { + return UnchunkedText; + } + + /// + /// + /// + /// + /// + public override bool Equals(object obj) + { + bool equals = false; + if (obj is HttpChunkedBody) + { + equals = (obj as HttpChunkedBody)?.ToString() == ToString(); + } + return equals; + } + + /// + /// + /// + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + } +} diff --git a/ReshaperCore/Messages/Entities/Http/HttpHeaders.cs b/ReshaperCore/Messages/Entities/Http/HttpHeaders.cs new file mode 100644 index 0000000..3823aee --- /dev/null +++ b/ReshaperCore/Messages/Entities/Http/HttpHeaders.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ReshaperCore.Messages.Entities.Http +{ + /// + /// Represents the headers of an HTTP request or response + /// + public class HttpHeaders : EntityContainer + { + private static long _entityFlag; + private int _lastIndex = 0; + private Dictionary> _headers = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + /// + /// The number of individual HTTP headers + /// + public virtual int Count + { + get + { + return _headers.Count; + } + } + + /// + /// Get or set the value of an HTTP header + /// + /// The header's name + /// + public virtual string this[string name] + { + get + { + return _headers[name].Item2; + } + set + { + if (value != null) + { + Tuple currentVal = null; + if (_headers.TryGetValue(name, out currentVal)) + { + _headers[name] = new Tuple(currentVal.Item1, value); + } + else + { + _headers[name] = new Tuple(_lastIndex++, value); + } + } + else + { + _headers.Remove(name); + } + + } + } + + static HttpHeaders() + { + _entityFlag = RegisterFlag(); + } + + public override long GetEntityFlag() + { + return _entityFlag; + } + + /// + /// + /// + /// All headers combined into a single string + public override string ToString() + { + return string.Join(NewLine, _headers.OrderBy(pair => pair.Value.Item1).Select(pair => pair.Key + ": " + pair.Value.Item2)); + } + + /// + /// Gets the value of the HTTP header or null if the header does not exist. + /// + /// The header's name + /// The HTTP header value + public virtual string GetOrDefault(string name) + { + Tuple tupleVal = null; + _headers.TryGetValue(name, out tupleVal); + return tupleVal?.Item2; + } + + /// + /// Determines if the specified header exists + /// + /// The header's name + /// True if the header exists, false otherwise + public virtual bool Contains(string name) + { + return _headers.ContainsKey(name); + } + + /// + /// + /// + /// + /// + public override bool Equals(object obj) + { + bool equals = false; + if (obj is HttpHeaders) + { + equals = (obj as HttpHeaders)?.ToString() == ToString(); + } + return equals; + } + + /// + /// + /// + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + + public static long EntityFlag + { + get + { + return _entityFlag; + } + } + + /// + /// The string that is used a newline separating each header. + /// + public string NewLine + { + get; + set; + } + } +} diff --git a/ReshaperCore/Messages/Entities/Http/HttpMessageType.cs b/ReshaperCore/Messages/Entities/Http/HttpMessageType.cs new file mode 100644 index 0000000..3f38cc1 --- /dev/null +++ b/ReshaperCore/Messages/Entities/Http/HttpMessageType.cs @@ -0,0 +1,17 @@ +namespace ReshaperCore.Messages.Entities.Http +{ + /// + /// Enumerates the type of HTTP messages. Used to differentiate between a HTTP request and a HTTP response + /// + public enum HttpMessageType + { + /// + /// HTTP request + /// + Request, + /// + /// HTTP response + /// + Response + } +} diff --git a/ReshaperCore/Messages/Entities/Http/HttpRequestStatusLine.cs b/ReshaperCore/Messages/Entities/Http/HttpRequestStatusLine.cs new file mode 100644 index 0000000..f27484e --- /dev/null +++ b/ReshaperCore/Messages/Entities/Http/HttpRequestStatusLine.cs @@ -0,0 +1,95 @@ +namespace ReshaperCore.Messages.Entities.Http +{ + /// + /// Represents the HTTP status line of a HTTP request. + /// + public class HttpRequestStatusLine : HttpStatusLine + { + private static long _entityFlag; + private string _method; + private string _uri; + + /// + /// The HTTP method + /// + public virtual string Method + { + set + { + _method = value; + OnPropertyChanged(nameof(Method)); + } + get + { + return _method; + } + } + + /// + /// The URI of the HTTP request + /// + public virtual string Uri + { + set + { + _uri = value; + OnPropertyChanged(nameof(Uri)); + } + get + { + return _uri; + } + } + + static HttpRequestStatusLine() + { + _entityFlag = RegisterFlag(); + } + + public override long GetEntityFlag() + { + return _entityFlag; + } + + public static long EntityFlag + { + get + { + return _entityFlag; + } + } + + /// + /// + /// + /// The full status line as text + public override string ToString() + { + return $"{Method} {Uri} {Version}"; + } + + /// + /// + /// + /// + /// + public override bool Equals(object obj) + { + bool equals = false; + if (obj is HttpRequestStatusLine) + { + equals = (obj as HttpRequestStatusLine)?.ToString() == ToString(); + } + return equals; + } + + /// + /// + /// + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + } +} diff --git a/ReshaperCore/Messages/Entities/Http/HttpResponseStatusLine.cs b/ReshaperCore/Messages/Entities/Http/HttpResponseStatusLine.cs new file mode 100644 index 0000000..2349fde --- /dev/null +++ b/ReshaperCore/Messages/Entities/Http/HttpResponseStatusLine.cs @@ -0,0 +1,95 @@ +namespace ReshaperCore.Messages.Entities.Http +{ + /// + /// Represents the status line of a HTTP response + /// + public class HttpResponseStatusLine : HttpStatusLine + { + private static long _entityFlag; + private int _statusCode; + private string _statusMessage; + + /// + /// The status code of the HTTP response + /// + public virtual int StatusCode + { + set + { + _statusCode = value; + OnPropertyChanged(nameof(StatusCode)); + } + get + { + return _statusCode; + } + } + + /// + /// The status message of the HTTP response + /// + public virtual string StatusMessage + { + set + { + _statusMessage = value; + OnPropertyChanged(nameof(StatusMessage)); + } + get + { + return _statusMessage; + } + } + + public static long EntityFlag + { + get + { + return _entityFlag; + } + } + + static HttpResponseStatusLine() + { + _entityFlag = RegisterFlag(); + } + + public override long GetEntityFlag() + { + return _entityFlag; + } + + /// + /// + /// + /// The full status line as text + public override string ToString() + { + return $"{Version} {StatusCode} {StatusMessage}"; + } + + /// + /// + /// + /// + /// + public override bool Equals(object obj) + { + bool equals = false; + if (obj is HttpResponseStatusLine) + { + equals = (obj as HttpResponseStatusLine)?.ToString() == ToString(); + } + return equals; + } + + /// + /// + /// + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + } +} diff --git a/ReshaperCore/Messages/Entities/Http/HttpStatusLine.cs b/ReshaperCore/Messages/Entities/Http/HttpStatusLine.cs new file mode 100644 index 0000000..2afbf65 --- /dev/null +++ b/ReshaperCore/Messages/Entities/Http/HttpStatusLine.cs @@ -0,0 +1,26 @@ +namespace ReshaperCore.Messages.Entities.Http +{ + /// + /// Represents the status line of a HTTP message + /// + public abstract class HttpStatusLine : EntityContainer + { + private string _version; + + /// + /// The HTTP version + /// + public virtual string Version + { + set + { + _version = value; + OnPropertyChanged(nameof(Version)); + } + get + { + return _version; + } + } + } +} diff --git a/ReshaperCore/Messages/Entities/HttpMessage.cs b/ReshaperCore/Messages/Entities/HttpMessage.cs new file mode 100644 index 0000000..74b9102 --- /dev/null +++ b/ReshaperCore/Messages/Entities/HttpMessage.cs @@ -0,0 +1,173 @@ +using ReshaperCore.Messages.Entities.Http; +using ReshaperCore.Utils.Extensions; + +namespace ReshaperCore.Messages +{ + public class HttpMessage : Message + { + + private static long _entityFlag; + private HttpMessageType _type; + private HttpStatusLine _statusLine; + private HttpHeaders _headers; + private HttpBody _body; + private int _syncId; + private string _newLine; + + public virtual HttpMessageType Type + { + set + { + _type = value; + OnPropertyChanged(nameof(Type)); + } + get + { + return _type; + } + } + + public virtual HttpStatusLine StatusLine + { + set + { + RegisterOnEntityChanges(nameof(StatusLine), value, _statusLine); + _statusLine = value; + OnPropertyChanged(nameof(StatusLine)); + OnPropertyChanged(nameof(RawText)); + } + get + { + return _statusLine; + } + } + + public virtual HttpHeaders Headers + { + set + { + RegisterOnEntityChanges(nameof(Headers), value, _headers); + _headers = value; + OnPropertyChanged(nameof(Headers)); + OnPropertyChanged(nameof(RawText)); + } + get + { + return _headers; + } + } + + public virtual HttpBody Body + { + set + { + RegisterOnEntityChanges(nameof(StatusLine), value, _body); + _body = value; + OnPropertyChanged(nameof(Body)); + OnPropertyChanged(nameof(RawText)); + } + get + { + return _body; + } + } + + public virtual int SyncId + { + set + { + _syncId = value; + OnPropertyChanged(nameof(SyncId)); + } + get + { + return _syncId; + } + } + + public override string RawText + { + get + { + return $"{StatusLine}{NewLine}{Headers}{NewLine}{NewLine}{Body?.ToString() ?? string.Empty}"; + } + + set + { + } + } + + public override byte[] RawBytes + { + get + { + byte[] bytes = TextEncoding.GetBytes($"{StatusLine}{NewLine}{Headers}{NewLine}{NewLine}"); + if (Body != null) + { + bytes = bytes.Combine(Body.RawBytes); + } + return bytes; + } + } + + public override string Protocol + { + get + { + return "Http"; + } + } + + public new static long EntityFlag + { + get + { + return _entityFlag; + } + } + + public virtual string NewLine + { + set + { + _newLine = value; + OnPropertyChanged(nameof(NewLine)); + OnPropertyChanged(nameof(RawText)); + } + get + { + return _newLine; + } + } + + static HttpMessage() + { + _entityFlag = RegisterFlag(); + } + + public override long GetEntityFlag() + { + return _entityFlag; + } + + public override string ToString() + { + return RawText; + } + + public override bool Equals(object obj) + { + bool equals = false; + if (obj is HttpMessage) + { + equals = (obj as HttpMessage)?.ToString() == ToString(); + } + return equals; + } + + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + } +} diff --git a/ReshaperCore/Messages/Entities/Message.cs b/ReshaperCore/Messages/Entities/Message.cs new file mode 100644 index 0000000..290a0c9 --- /dev/null +++ b/ReshaperCore/Messages/Entities/Message.cs @@ -0,0 +1,171 @@ +using System.Text; +using ReshaperCore.Utils.Extensions; + +namespace ReshaperCore.Messages +{ + public class Message : EntityContainer + { + private long _checkedEntityFlags = 0; + private long _hasEntityFlags = 0; + private static long _entityFlag; + private string _delimiter = string.Empty; + private bool _complete; + private Encoding _textEncoding = Encoding.UTF8; + private string _rawText; + private byte[] _rawBytes; + + public virtual string Delimiter + { + set + { + _delimiter = value; + OnPropertyChanged(nameof(Delimiter)); + } + get + { + return _delimiter; + } + } + + public virtual bool Complete + { + set + { + _complete = value; + OnPropertyChanged(nameof(Complete)); + } + get + { + return _complete; + } + } + + public static long EntityFlag + { + get + { + return _entityFlag; + } + } + + public virtual Encoding TextEncoding + { + set + { + _textEncoding = value; + OnPropertyChanged(nameof(TextEncoding)); + } + get + { + return _textEncoding; + } + } + + public virtual string RawText + { + set + { + _rawText = value; + _rawBytes = TextEncoding.GetBytes(_rawText); + OnPropertyChanged(nameof(RawText)); + OnPropertyChanged(nameof(RawBytes)); + OnPropertyChanged(nameof(Size)); + } + get + { + return _rawText; + } + } + + public virtual string Protocol + { + get + { + return "Raw"; + } + } + + public virtual byte[] RawBytes + { + get + { + return _rawBytes; + } + set + { + _rawBytes = value; + _rawText = TextEncoding.GetString(_rawBytes); + OnPropertyChanged(nameof(RawText)); + OnPropertyChanged(nameof(RawBytes)); + OnPropertyChanged(nameof(Size)); + } + } + + public virtual long Size + { + get + { + return RawBytes.Length; + } + } + + static Message() + { + _entityFlag = RegisterFlag(); + } + + public Message() + { + SetEntityFlag(GetEntityFlag(), true); + } + + public bool HasEntity(long entityFlag) + { + return (_hasEntityFlags & entityFlag) != 0; + } + + public bool CheckedEntity(long entityFlag) + { + return (_checkedEntityFlags & entityFlag) != 0; + } + + public virtual void ResetCheckedEntity(long entityFlag) + { + _checkedEntityFlags &= ~entityFlag; + } + + public void SetEntityFlag(long entityFlag, bool hasEntity) + { + _checkedEntityFlags |= entityFlag; + if (hasEntity) + { + _hasEntityFlags |= entityFlag; + } + } + + public override long GetEntityFlag() + { + return _entityFlag; + } + + public override string ToString() + { + return RawText?.TrimEnd(Delimiter); + } + + public override bool Equals(object obj) + { + bool equals = false; + if (obj is Message) + { + equals = (obj as Message)?.ToString() == ToString(); + } + return equals; + } + + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + } +} diff --git a/ReshaperCore/Messages/Entities/MessageValue.cs b/ReshaperCore/Messages/Entities/MessageValue.cs new file mode 100644 index 0000000..1136c9c --- /dev/null +++ b/ReshaperCore/Messages/Entities/MessageValue.cs @@ -0,0 +1,47 @@ +using System.ComponentModel; + +namespace ReshaperCore.Messages.Entities +{ + /// + /// Enumeration of all possible values of a text or HTTP message + /// + public enum MessageValue + { + [Description("Source Remote Address")] + SourceRemoteAddress, + [Description("Destination Remote Address")] + DestinationRemoteAddress, + [Description("Local Address")] + LocalAddress, + [Description("Destination Remote Port")] + DestinationRemotePort, + [Description("Source Remote Port")] + SourceRemotePort, + [Description("Local Port")] + LocalPort, + [Description("Protocol")] + Protocol, + [Description("Data Direction")] + DataDirection, + [Description("HTTP Request URI")] + HttpRequestUri, + [Description("HTTP Method")] + HttpMethod, + [Description("HTTP Header")] + HttpHeader, + [Description("HTTP Headers")] + HttpHeaders, + [Description("HTTP Body")] + HttpBody, + [Description("Message")] + Message, + [Description("HTTP Status Line")] + HttpStatusLine, + [Description("HTTP Status Message")] + HttpStatusMessage, + [Description("HTTP Status Code")] + HttpStatusCode, + [Description("HTTP Version")] + HttpVersion, + } +} diff --git a/ReshaperCore/Messages/Entities/MessageValueType.cs b/ReshaperCore/Messages/Entities/MessageValueType.cs new file mode 100644 index 0000000..3b3c228 --- /dev/null +++ b/ReshaperCore/Messages/Entities/MessageValueType.cs @@ -0,0 +1,17 @@ +using System.ComponentModel; + +namespace ReshaperCore.Messages.Entities +{ + /// + /// Enumeration of the type of data store in a message value + /// + public enum MessageValueType + { + [Description("Text")] + Text, + [Description("JSON")] + Json, + [Description("XML")] + Xml + } +} diff --git a/ReshaperCore/Messages/MessageValueHandler.cs b/ReshaperCore/Messages/MessageValueHandler.cs new file mode 100644 index 0000000..0205328 --- /dev/null +++ b/ReshaperCore/Messages/MessageValueHandler.cs @@ -0,0 +1,317 @@ +using System; +using ReshaperCore.Messages.Entities; +using ReshaperCore.Messages.Entities.Http; +using ReshaperCore.Messages.Parsers; +using ReshaperCore.Providers; +using ReshaperCore.Rules; +using ReshaperCore.Settings; +using ReshaperCore.Utils.Extensions; +using ReshaperCore.Vars; + +namespace ReshaperCore.Messages +{ + public class MessageValueHandler + { + private readonly HttpHeadersParser _httpHeadersParser; + private readonly HttpMessageParser _httpMessageParser; + private readonly HttpStatusLineParser _httpStatusLineParser; + + public MessageValueHandler() + { + _httpHeadersParser = new HttpHeadersParser(); + _httpStatusLineParser = new HttpStatusLineParser(); + _httpMessageParser = new HttpMessageParser(); + } + + public IGeneralSettings GeneralSettings { get; set; } = new GeneralSettingsProvider().GetInstance(); + + public string GetValue(EventInfo eventInfo, MessageValue messageValue, MessageValueType valueType, VariableString identifier = null) + { + _httpMessageParser.TextEncoding = eventInfo.ProxyConnection.ProxyInfo.DefaultEncoding; + + string value = null; + switch (messageValue) + { + case MessageValue.DataDirection: + value = eventInfo.Direction.ToString(); + break; + case MessageValue.HttpBody: + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null) + { + value = httpMessage.Body.ToString(); + if (valueType != MessageValueType.Text && identifier != null) + { + switch (valueType) + { + case MessageValueType.Json: + value = value?.GetJsonValue(identifier.GetText(eventInfo.Variables)); + break; + case MessageValueType.Xml: + value = value?.GetXmlValue(identifier.GetText(eventInfo.Variables)); + break; + } + } + } + } + break; + case MessageValue.HttpHeader: + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null && identifier != null) + { + value = httpMessage.Headers.GetOrDefault(identifier.GetText(eventInfo.Variables)) ?? string.Empty; + } + } + break; + case MessageValue.HttpHeaders: + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null) + { + value = httpMessage.Headers.ToString(); + } + } + break; + case MessageValue.HttpMethod: + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null) + { + value = (httpMessage.StatusLine as HttpRequestStatusLine)?.Method; + } + } + break; + case MessageValue.HttpRequestUri: + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null) + { + value = (httpMessage.StatusLine as HttpRequestStatusLine)?.Uri; + } + } + break; + case MessageValue.LocalAddress: + value = eventInfo.ProxyConnection.OriginChannel.LocalEndpoint.Address.ToString(); + break; + case MessageValue.LocalPort: + value = eventInfo.ProxyConnection.OriginChannel.LocalEndpoint.Port.ToString(); + break; + case MessageValue.SourceRemoteAddress: + if (eventInfo.ProxyConnection.HasOriginConnection) + { + value = eventInfo.ProxyConnection.OriginChannel.RemoteEndpoint.Address.ToString(); + } + break; + case MessageValue.SourceRemotePort: + value = eventInfo.ProxyConnection.OriginChannel.RemoteEndpoint.Port.ToString(); + + break; + case MessageValue.DestinationRemoteAddress: + if (eventInfo.ProxyConnection.HasTargetConnection) + { + value = eventInfo.ProxyConnection.TargetChannel.RemoteEndpoint.Address.ToString(); + } + break; + case MessageValue.DestinationRemotePort: + if (eventInfo.ProxyConnection.HasTargetConnection) + { + value = eventInfo.ProxyConnection.TargetChannel.RemoteEndpoint.Port.ToString(); + } + break; + case MessageValue.Protocol: + value = eventInfo.Message?.Protocol?.ToString(); + break; + case MessageValue.HttpVersion: + { + HttpStatusLine requestStatusLine = (eventInfo.Message as HttpMessage)?.StatusLine; + if (requestStatusLine is HttpRequestStatusLine) + { + value = (requestStatusLine as HttpRequestStatusLine)?.Version; + } + else + { + value = (requestStatusLine as HttpResponseStatusLine)?.Version; + } + } + break; + case MessageValue.HttpStatusLine: + value = (eventInfo.Message as HttpMessage)?.StatusLine?.ToString(); + break; + case MessageValue.HttpStatusCode: + value = ((eventInfo.Message as HttpMessage)?.StatusLine as HttpResponseStatusLine)?.StatusCode.ToString(); + break; + case MessageValue.HttpStatusMessage: + value = ((eventInfo.Message as HttpMessage)?.StatusLine as HttpResponseStatusLine)?.StatusMessage.ToString(); + break; + case MessageValue.Message: + value = eventInfo.Message?.ToString(); + if (valueType != MessageValueType.Text && identifier != null) + { + switch (valueType) + { + case MessageValueType.Json: + value = value?.GetJsonValue(identifier.GetText(eventInfo.Variables)); + break; + case MessageValueType.Xml: + value = value?.GetXmlValue(identifier.GetText(eventInfo.Variables)); + break; + } + } + break; + } + return value ?? string.Empty; + } + + public void SetValue(EventInfo eventInfo, MessageValue messageValue, MessageValueType valueType, VariableString identifier, string replacementText) + { + switch (messageValue) + { + case MessageValue.DataDirection: + { + DataDirection dataDirection; + if (Enum.TryParse(replacementText, out dataDirection)) + { + eventInfo.Direction = dataDirection; + } + } + break; + case MessageValue.HttpBody: + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null) + { + if (valueType != MessageValueType.Text && identifier != null) + { + switch (valueType) + { + case MessageValueType.Json: + replacementText = httpMessage.Body.Text.SetJsonValue(identifier.GetText(eventInfo.Variables), replacementText); + break; + case MessageValueType.Xml: + replacementText = httpMessage.Body.Text.SetXmlValue(identifier.GetText(eventInfo.Variables), replacementText); + break; + } + } + httpMessage.Body = new HttpBody() { Text = replacementText }; + if (GeneralSettings.AutoUpdateContentLength) + { + if (httpMessage.Headers.Contains("Content-Length")) + { + httpMessage.Headers["Content-Length"] = httpMessage.Body.Text.Length.ToString(); + } + } + } + } + break; + case MessageValue.HttpHeader: + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null && identifier != null) + { + httpMessage.Headers[identifier.GetText(eventInfo.Variables)] = replacementText; + } + } + break; + case MessageValue.HttpHeaders: + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null) + { + _httpHeadersParser.Parse(eventInfo, replacementText, true); + } + } + break; + case MessageValue.HttpStatusLine: + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null) + { + HttpStatusLine newHttpStatusLine = _httpStatusLineParser.Parse(replacementText); + httpMessage.StatusLine = newHttpStatusLine; + } + } + break; + case MessageValue.HttpMethod: + { + HttpRequestStatusLine httpStatusLine = (eventInfo.Message as HttpMessage)?.StatusLine as HttpRequestStatusLine; + if (httpStatusLine != null) + { + httpStatusLine.Method = replacementText; + } + } + break; + case MessageValue.HttpRequestUri: + { + HttpRequestStatusLine httpStatusLine = (eventInfo.Message as HttpMessage)?.StatusLine as HttpRequestStatusLine; + if (httpStatusLine != null) + { + httpStatusLine.Uri = replacementText; + } + } + break; + case MessageValue.HttpStatusMessage: + { + HttpResponseStatusLine httpStatusLine = (eventInfo.Message as HttpMessage)?.StatusLine as HttpResponseStatusLine; + if (httpStatusLine != null) + { + httpStatusLine.StatusMessage = replacementText; + } + } + break; + case MessageValue.HttpVersion: + { + HttpStatusLine httpStatusLine = (eventInfo.Message as HttpMessage)?.StatusLine; + if (httpStatusLine != null) + { + httpStatusLine.Version = replacementText; + } + } + break; + case MessageValue.HttpStatusCode: + { + HttpResponseStatusLine httpStatusLine = (eventInfo.Message as HttpMessage)?.StatusLine as HttpResponseStatusLine; + if (httpStatusLine != null) + { + int replacementVal; + if (int.TryParse(replacementText, out replacementVal)) + { + httpStatusLine.StatusCode = replacementVal; + } + } + } + break; + case MessageValue.Message: + { + if (eventInfo.Message is HttpMessage) + { + HttpMessage newMessage = _httpMessageParser.Parse(replacementText, true).Item2; + if (newMessage != null) + { + eventInfo.Message = newMessage; + } + } + else if (eventInfo.Message != null) + { + eventInfo.Message.ResetCheckedEntity(HttpMessage.EntityFlag); + if (valueType != MessageValueType.Text && identifier != null) + { + switch (valueType) + { + case MessageValueType.Json: + replacementText = eventInfo.Message.ToString().SetJsonValue(identifier.GetText(eventInfo.Variables), replacementText); + break; + case MessageValueType.Xml: + replacementText = eventInfo.Message.ToString().SetXmlValue(identifier.GetText(eventInfo.Variables), replacementText); + break; + } + } + eventInfo.Message.RawText = replacementText + eventInfo.Message.Delimiter; + } + } + break; + } + } + } +} diff --git a/ReshaperCore/Messages/Parsers/HttpHeadersParser.cs b/ReshaperCore/Messages/Parsers/HttpHeadersParser.cs new file mode 100644 index 0000000..6688be9 --- /dev/null +++ b/ReshaperCore/Messages/Parsers/HttpHeadersParser.cs @@ -0,0 +1,112 @@ +using ReshaperCore.Messages.Entities.Http; +using ReshaperCore.Rules; +using ReshaperCore.Utils; + +namespace ReshaperCore.Messages.Parsers +{ + public class HttpHeadersParser + { + private string _text; + private int _pos; + private string _newLineStr; + private bool _acceptEofAsLine; + + public void Parse(EventInfo eventInfo, string replacementText, bool acceptEofAsLine = false) + { + this._acceptEofAsLine = acceptEofAsLine; + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null) + { + HttpHeaders headers = Parse(replacementText); + if (headers != null) + { + httpMessage.Headers = headers; + } + else + { + Log.LogError(null, "Invalid header. Could not set.", replacementText); + } + } + } + + public HttpHeaders Parse(string text, bool acceptEofAsLine = false) + { + this._text = text; + _pos = 0; + return ParseHeaders(); + } + + private HttpHeaders ParseHeaders() + { + string line; + string[] sections; + HttpHeaders headers = new HttpHeaders(); + do + { + line = ReadLine(); + + if (line != null) + { + sections = line.Split(new char[] { ':' }, 2); + if (sections.Length == 2) + { + headers[sections[0]] = sections[1].TrimStart(); + } + else + { + break; + + } + } + else + { + headers = null; + } + } + while (!string.IsNullOrWhiteSpace(line)); + if (headers != null && !string.IsNullOrEmpty(_newLineStr)) + { + headers.NewLine = _newLineStr; + } + return headers; + } + + private string ReadLine() + { + int currentPos = _pos; + char currentChar = '\r'; + char nextChar = '\n'; + while (currentPos < _text.Length) + { + switch (_text[currentPos]) + { + case '\n': + currentChar = '\n'; + nextChar = '\r'; + goto case '\r'; + case '\r': + string line = _text.Substring(_pos, currentPos - _pos); + if (++currentPos < _text.Length && _text[currentPos] == nextChar) + { + _pos = ++currentPos; + _newLineStr = currentChar.ToString() + nextChar.ToString(); + } + else + { + _pos = currentPos; + _newLineStr = currentChar.ToString(); + } + return line; + } + currentPos++; + } + string returnVal = null; + if (_acceptEofAsLine) + { + returnVal = _text.Substring(_pos, currentPos - _pos); + _pos = currentPos; + } + return returnVal; + } + } +} diff --git a/ReshaperCore/Messages/Parsers/HttpMessageParser.cs b/ReshaperCore/Messages/Parsers/HttpMessageParser.cs new file mode 100644 index 0000000..64bf5e6 --- /dev/null +++ b/ReshaperCore/Messages/Parsers/HttpMessageParser.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using ReshaperCore.Messages.Entities.Http; +using ReshaperCore.Networking; +using ReshaperCore.Providers; +using ReshaperCore.Settings; +using ReshaperCore.Utils.Extensions; + +namespace ReshaperCore.Messages.Parsers +{ + public class HttpMessageParser + { + private int _fullBytesPos; + private string _newLineStr; + private HttpMessageType _type; + private byte[] _bytes; + private HttpStatusLine _statusLine = null; + private HttpHeaders _headers = null; + private HttpBody _body = null; + private HttpMessage _message = null; + private bool _autoUpdateContentLength; + + public Encoding TextEncoding + { + get; + set; + } = Encoding.UTF8; + + public IGeneralSettings GeneralSettings { get; set; } = new GeneralSettingsProvider().GetInstance(); + + public Tuple Parse(string text, bool useFullText = false) + { + return Parse(TextEncoding.GetBytes(text), useFullText); + } + + public Tuple Parse(byte[] bytes, bool useFullBody = false) + { + _fullBytesPos = 0; + this._bytes = bytes; + bool complete = false; + useFullBody |= GeneralSettings.IgnoreContentLength; + _autoUpdateContentLength = GeneralSettings.AutoUpdateContentLength; + _statusLine = ParseStatusLine(); + if (_statusLine != null) + { + _headers = ParseHeaders(); + if (_headers != null) + { + int rawBodyByteSize = bytes.Length - _fullBytesPos; + + + string definedContentLengthStr = _headers.GetOrDefault("Content-Length"); + int definedContentLength = 0; + + if (definedContentLength == 0) + { + _autoUpdateContentLength = false; + } + + int.TryParse(definedContentLengthStr, out definedContentLength); + + if (definedContentLength <= rawBodyByteSize) + { + if (_headers.GetOrDefault("Transfer-Encoding") == "chunked") + { + _body = ParseBodyChunks(); + if (_body != null) + { + complete = true; + } + } + else + { + if (useFullBody) + { + definedContentLength = rawBodyByteSize; + } + + if (definedContentLength > 0) + { + _body = ParseBody(definedContentLength); + _fullBytesPos += definedContentLength; + if (_body != null) + { + if (_autoUpdateContentLength) + { + _headers["Content-Length"] = definedContentLength.ToString(); + } + complete = true; + } + } + else + { + complete = true; + } + } + } + } + } + if (complete) + { + _message = new HttpMessage() + { + Body = _body, + Complete = complete, + TextEncoding = TextEncoding, + Headers = _headers, + StatusLine = _statusLine, + NewLine = _newLineStr, + Type = _type + }; + } + return new Tuple(_fullBytesPos, _message); + } + + private HttpChunkedBody ParseBodyChunks() + { + int startBytesPos = _fullBytesPos; + bool valid = false; + IList chunks = new List(); + + int chunkLength = 0; + do + { + int newLineOffset = GetNextNewLineInfo()?.Item1 ?? -1; + if (newLineOffset > 2) + { + string chunkLengthStr = Encoding.ASCII.GetString(_bytes, _fullBytesPos, newLineOffset).Trim(); + if (!string.IsNullOrEmpty(chunkLengthStr)) + { + if (int.TryParse(chunkLengthStr, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out chunkLength)) + { + if (chunkLength == 0) + { + _fullBytesPos += newLineOffset; + newLineOffset = GetNextNewLineInfo()?.Item1 ?? -1; + if (newLineOffset != 2) + { + break; + } + else + { + _fullBytesPos += newLineOffset; + valid = true; + } + } + else if (chunkLength > 0) + { + _fullBytesPos += newLineOffset; + if (chunkLength + _fullBytesPos < _bytes.Length) + { + chunks.Add(new Buffer(_bytes, _fullBytesPos, chunkLength).GetBytes()); + _fullBytesPos += chunkLength; + newLineOffset = GetNextNewLineInfo()?.Item1 ?? -1; + if (newLineOffset < 2) + { + break; + } + else + { + _fullBytesPos += newLineOffset; + } + } + else + { + break; + } + } + else + { + break; + } + } + } + else + { + break; + } + } + else + { + break; + } + } + while (chunkLength > 0); + + HttpChunkedBody body = null; + if (valid) + { + body = ParseBody(_fullBytesPos - startBytesPos, startBytesPos); + body.UnchunkedBytes = chunks.Combine(); + } + return body; + } + + private Tuple GetNextNewLineInfo() + { + int offsetPos = _fullBytesPos; + while (offsetPos + 1 < _bytes.Length) + { + if (_bytes[offsetPos] == '\r' && _bytes[offsetPos + 1] == '\n') + { + offsetPos += 2; + return new Tuple(offsetPos - _fullBytesPos, "\r\n"); + } + else if (_bytes[offsetPos] == '\n') + { + offsetPos++; + return new Tuple(offsetPos - _fullBytesPos, "\n"); + } + else + { + offsetPos++; + } + } + return null; + } + + private T ParseBody(int contentSize, int? startPos = null) where T : HttpBody, new() + { + if (startPos == null) + { + startPos = _fullBytesPos; + } + byte[] bodyBytes = new byte[contentSize]; + Buffer.BlockCopy(_bytes, startPos.Value, bodyBytes, 0, contentSize); + T body = new T() + { + TextEncoding = TextEncoding, + RawBytes = bodyBytes + }; + return body; + } + + private HttpHeaders ParseHeaders() + { + string line; + string[] sections; + HttpHeaders headers = new HttpHeaders(); + headers.NewLine = _newLineStr; + do + { + line = ReadLine(); + + if (line != null) + { + sections = line.Split(new char[] { ':' }, 2); + if (sections.Length == 2) + { + headers[sections[0]] = sections[1].TrimStart(); + } + else + { + break; + + } + } + else + { + headers = null; + } + } + while (!string.IsNullOrWhiteSpace(line)); + + if (headers != null) + { + //Encodings are currently not supported. + headers["Accept-Encoding"] = null; + } + + return headers; + } + + private HttpStatusLine ParseStatusLine() + { + HttpStatusLine statusLine = null; + string line = ReadLine(); + if (!string.IsNullOrEmpty(line)) + { + string[] sections = line.Split(new char[] { ' ' }, 3); + if (sections.Length == 3) + { + statusLine = ParseResponseStatusLine(sections) ?? ParseRequestStatusLine(sections); + } + } + return statusLine; + } + + private HttpStatusLine ParseRequestStatusLine(string[] sections) + { + HttpStatusLine statusLine = new HttpRequestStatusLine + { + Method = sections[0], + Uri = sections[1], + Version = sections[2] + }; + _type = HttpMessageType.Request; + return statusLine; + } + + private HttpStatusLine ParseResponseStatusLine(string[] sections) + { + HttpStatusLine statusLine = null; + if (sections[0].StartsWith("http", System.StringComparison.InvariantCultureIgnoreCase)) + { + string version = sections[0]; + int statusCode = 0; + string statusMessage = sections[2]; + + if (int.TryParse(sections[1], out statusCode)) + { + statusLine = new HttpResponseStatusLine() + { + Version = sections[0], + StatusCode = statusCode, + StatusMessage = sections[2] + }; + _type = HttpMessageType.Response; + } + } + return statusLine; + } + + private string ReadLine() + { + string line = null; + Tuple newLineInfo = GetNextNewLineInfo(); + if (newLineInfo != null) + { + line = TextEncoding.GetString(_bytes, _fullBytesPos, newLineInfo.Item1); + _newLineStr = newLineInfo.Item2; + _fullBytesPos += newLineInfo.Item1; + } + return line?.TrimEnd(); + } + } +} diff --git a/ReshaperCore/Messages/Parsers/HttpStatusLineParser.cs b/ReshaperCore/Messages/Parsers/HttpStatusLineParser.cs new file mode 100644 index 0000000..5d8d809 --- /dev/null +++ b/ReshaperCore/Messages/Parsers/HttpStatusLineParser.cs @@ -0,0 +1,55 @@ +using ReshaperCore.Messages.Entities.Http; + +namespace ReshaperCore.Messages.Parsers +{ + public class HttpStatusLineParser + { + + public HttpStatusLine Parse(string line) + { + HttpStatusLine statusLine = null; + if (!string.IsNullOrEmpty(line)) + { + string[] sections = line.Split(new char[] { ' ' }, 3); + if (sections.Length == 3) + { + statusLine = ParseResponseStatusLine(sections) ?? ParseRequestStatusLine(sections); + } + } + return statusLine; + } + + private HttpStatusLine ParseRequestStatusLine(string[] sections) + { + HttpStatusLine statusLine = new HttpRequestStatusLine + { + Method = sections[0], + Uri = sections[1], + Version = sections[2] + }; + return statusLine; + } + + private HttpStatusLine ParseResponseStatusLine(string[] sections) + { + HttpStatusLine statusLine = null; + if (sections[0].StartsWith("http", System.StringComparison.InvariantCultureIgnoreCase)) + { + string version = sections[0]; + int statusCode = 0; + string statusMessage = sections[2]; + + if (int.TryParse(sections[1], out statusCode)) + { + statusLine = new HttpResponseStatusLine() + { + Version = sections[0], + StatusCode = statusCode, + StatusMessage = sections[2] + }; + } + } + return statusLine; + } + } +} diff --git a/ReshaperCore/Networking/Buffer.cs b/ReshaperCore/Networking/Buffer.cs new file mode 100644 index 0000000..fe25829 --- /dev/null +++ b/ReshaperCore/Networking/Buffer.cs @@ -0,0 +1,31 @@ +namespace ReshaperCore.Networking +{ + public class Buffer + { + + public T[] Array { get; set; } + public int Length { get; set; } + public int Position { get; set; } + + public Buffer(T[] array, int position, int length) + { + this.Array = array; + this.Position = position; + this.Length = length; + } + + public Buffer(T[] array) + { + this.Array = array; + this.Position = 0; + this.Length = array.Length; + } + + public byte[] GetBytes() + { + byte[] subArray = new byte[Length]; + System.Buffer.BlockCopy(Array, Position, subArray, 0, Length); + return subArray; + } + } +} diff --git a/ReshaperCore/Networking/DataReader.cs b/ReshaperCore/Networking/DataReader.cs new file mode 100644 index 0000000..0f7b828 --- /dev/null +++ b/ReshaperCore/Networking/DataReader.cs @@ -0,0 +1,39 @@ +using System.IO; +using System.Threading.Tasks; + +namespace ReshaperCore.Networking +{ + public class DataReader + { + private int _offset = 0; + private byte[] _loadBuffer = new byte[BufferSize]; + private const int BufferSize = 400382; + + public Stream Stream { get; private set; } + + private int BufferEmptyLength + { + get + { + return BufferSize - this._offset; + } + } + + public DataReader(Stream stream) + { + Stream = new BufferedStream(stream); + } + + public async Task> ReadAsync() + { + if (this.BufferEmptyLength < 1024) + { + this._loadBuffer = new byte[BufferSize]; + _offset = 0; + } + int origOffset = _offset; + _offset += await Stream.ReadAsync(_loadBuffer, _offset, BufferEmptyLength); + return new Buffer(_loadBuffer, origOffset, _offset - origOffset); + } + } +} diff --git a/ReshaperCore/Properties/Resources.Designer.cs b/ReshaperCore/Properties/Resources.Designer.cs new file mode 100644 index 0000000..335c9a2 --- /dev/null +++ b/ReshaperCore/Properties/Resources.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ReshaperCore.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ReshaperCore.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] DefaultHttpRules { + get { + object obj = ResourceManager.GetObject("DefaultHttpRules", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] DefaultTextRules { + get { + object obj = ResourceManager.GetObject("DefaultTextRules", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/ReshaperCore/Properties/Resources.resx b/ReshaperCore/Properties/Resources.resx new file mode 100644 index 0000000..de0d5c3 --- /dev/null +++ b/ReshaperCore/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Data\DefaultHttpRules.json;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Data\DefaultTextRules.json;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ReshaperCore/Providers/CompositionContainerProvider.cs b/ReshaperCore/Providers/CompositionContainerProvider.cs new file mode 100644 index 0000000..7105335 --- /dev/null +++ b/ReshaperCore/Providers/CompositionContainerProvider.cs @@ -0,0 +1,31 @@ +using ReshaperCore.Settings; +using System.ComponentModel.Composition.Hosting; +using System.IO; + +namespace ReshaperCore.Providers +{ + public class CompositionContainerProvider : SingletonProvider + { + private const string ExtensionDirectory = @"/Extensions"; + + protected override CompositionContainer CreateInstance() + { + string extensionsPath = Path.Combine(SettingsStore.StoragePath, ExtensionDirectory); + + if (!Directory.Exists(ExtensionDirectory)) + { + Directory.CreateDirectory(ExtensionDirectory); + } + + DirectoryCatalog thirdPartyCatelog = new DirectoryCatalog(ExtensionDirectory); + AggregateCatalog defaultCatalog = new AggregateCatalog(); + + defaultCatalog.Catalogs.Add(new DirectoryCatalog("./", "Reshaper*.dll")); + CatalogExportProvider defaultExportProvider = new CatalogExportProvider(defaultCatalog); + + CompositionContainer container = new CompositionContainer(thirdPartyCatelog, defaultExportProvider); + defaultExportProvider.SourceProvider = container; + return container; + } + } +} diff --git a/ReshaperCore/Providers/GeneralSettingsProvider.cs b/ReshaperCore/Providers/GeneralSettingsProvider.cs new file mode 100644 index 0000000..e7379de --- /dev/null +++ b/ReshaperCore/Providers/GeneralSettingsProvider.cs @@ -0,0 +1,12 @@ +using ReshaperCore.Settings; + +namespace ReshaperCore.Providers +{ + public class GeneralSettingsProvider : SingletonProvider + { + protected override IGeneralSettings CreateInstance() + { + return new GeneralSettings(); + } + } +} diff --git a/ReshaperCore/Providers/HttpRulesRegistryProvider.cs b/ReshaperCore/Providers/HttpRulesRegistryProvider.cs new file mode 100644 index 0000000..9d70024 --- /dev/null +++ b/ReshaperCore/Providers/HttpRulesRegistryProvider.cs @@ -0,0 +1,12 @@ +using ReshaperCore.Rules; + +namespace ReshaperCore.Providers +{ + public class HttpRulesRegistryProvider : SingletonProvider + { + protected override IHttpRulesRegistry CreateInstance() + { + return new HttpRulesRegistry(); + } + } +} diff --git a/ReshaperCore/Providers/ISingletonProvider.cs b/ReshaperCore/Providers/ISingletonProvider.cs new file mode 100644 index 0000000..8d1bbe4 --- /dev/null +++ b/ReshaperCore/Providers/ISingletonProvider.cs @@ -0,0 +1,12 @@ +namespace ReshaperCore.Providers +{ + public interface ISingletonProvider : ISingletonProvider + { + } + + + public interface ISingletonProvider + { + SubT GetInstance(); + } +} diff --git a/ReshaperCore/Providers/ProxyRegistryProvider.cs b/ReshaperCore/Providers/ProxyRegistryProvider.cs new file mode 100644 index 0000000..95607ef --- /dev/null +++ b/ReshaperCore/Providers/ProxyRegistryProvider.cs @@ -0,0 +1,12 @@ +using ReshaperCore.Proxies; + +namespace ReshaperCore.Providers +{ + public class ProxyRegistryProvider : SingletonProvider + { + protected override IProxyRegistry CreateInstance() + { + return new ProxyRegistry(); + } + } +} diff --git a/ReshaperCore/Providers/SelfProvider.cs b/ReshaperCore/Providers/SelfProvider.cs new file mode 100644 index 0000000..755dcc6 --- /dev/null +++ b/ReshaperCore/Providers/SelfProvider.cs @@ -0,0 +1,10 @@ +namespace ReshaperCore.Providers +{ + public class SelfProvider : SingletonProvider + { + protected override ISelf CreateInstance() + { + return new Self(); + } + } +} diff --git a/ReshaperCore/Providers/SingletonProvider.cs b/ReshaperCore/Providers/SingletonProvider.cs new file mode 100644 index 0000000..16332ce --- /dev/null +++ b/ReshaperCore/Providers/SingletonProvider.cs @@ -0,0 +1,35 @@ +using System; +using ReshaperCore.Utils; + +namespace ReshaperCore.Providers +{ + public abstract class SingletonProvider : SingletonProvider, ISingletonProvider + { + } + + public abstract class SingletonProvider : ISingletonProvider where SubT : BaseT + { + + private bool HasInstance() + { + bool hasInstance = false; + if (Singleton.Instance != null) + { + Type currentType = GetType(); + hasInstance = Singleton.Instance.GetType() == currentType || typeof(SubT).IsAssignableFrom(Singleton.Instance.GetType()); + } + return hasInstance; + } + + public virtual SubT GetInstance() + { + if (!HasInstance()) + { + Singleton.Instance = CreateInstance(); + } + return Singleton.Instance; + } + + protected abstract SubT CreateInstance(); + } +} diff --git a/ReshaperCore/Providers/SystemProxySettingsProvider.cs b/ReshaperCore/Providers/SystemProxySettingsProvider.cs new file mode 100644 index 0000000..8533a47 --- /dev/null +++ b/ReshaperCore/Providers/SystemProxySettingsProvider.cs @@ -0,0 +1,12 @@ +using ReshaperCore.Proxies; + +namespace ReshaperCore.Providers +{ + public class SystemProxySettingsProvider : SingletonProvider + { + protected override ISystemProxySettings CreateInstance() + { + return new SystemProxySettings(); + } + } +} diff --git a/ReshaperCore/Providers/TextRulesRegistryProvider.cs b/ReshaperCore/Providers/TextRulesRegistryProvider.cs new file mode 100644 index 0000000..d17a9be --- /dev/null +++ b/ReshaperCore/Providers/TextRulesRegistryProvider.cs @@ -0,0 +1,12 @@ +using ReshaperCore.Rules; + +namespace ReshaperCore.Providers +{ + public class TextRulesRegistryProvider : SingletonProvider + { + protected override ITextRulesRegistry CreateInstance() + { + return new TextRulesRegistry(); + } + } +} diff --git a/ReshaperCore/Proxies/Channel.cs b/ReshaperCore/Proxies/Channel.cs new file mode 100644 index 0000000..3afd8e3 --- /dev/null +++ b/ReshaperCore/Proxies/Channel.cs @@ -0,0 +1,136 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Threading.Tasks; +using ReshaperCore.Networking; + +namespace ReshaperCore.Proxies +{ + public class Channel + { + private TcpClient _client; + private NetworkStream _clientStream; + private DataReader _clientDataReader; + private bool _signaledDisconnection = false; + + public event DataReceivedHandler DataReceived; + public delegate void DataReceivedHandler(Buffer buffer); + + public event DisconnectedHandler Disconnected; + public delegate void DisconnectedHandler(); + + public bool Connected + { + get + { + bool connected = false; + try + { + IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties(); + + connected = ipProperties.GetActiveTcpConnections().FirstOrDefault(connectionInformation => connectionInformation.LocalEndPoint.Equals(_client.Client.LocalEndPoint) && connectionInformation.RemoteEndPoint.Equals(_client.Client.RemoteEndPoint))?.State == TcpState.Established; + } + catch (Exception) + { + } + return connected; + } + } + + public virtual IPEndPoint LocalEndpoint + { + get; + private set; + } + + public virtual IPEndPoint RemoteEndpoint + { + get; + private set; + } + + public Channel(TcpClient client) + { + this._client = client; + } + + public void Init() + { + try + { + if (Connected) + { + _signaledDisconnection = false; + LocalEndpoint = _client.Client.LocalEndPoint as IPEndPoint; + RemoteEndpoint = _client.Client.RemoteEndPoint as IPEndPoint; + _clientStream = _client.GetStream(); + _clientDataReader = new DataReader(_clientStream); + + _clientDataReader.ReadAsync().ContinueWith(OnClientData); + } + } + catch (Exception) + { + SignalDisconnection(); + } + } + + private void OnClientData(Task> task) + { + try + { + if (task.Status == TaskStatus.RanToCompletion && task.Result.Length > 0) + { + if (DataReceived != null) + { + DataReceived(task.Result); + } + _clientDataReader.ReadAsync().ContinueWith(OnClientData); + } + else + { + SignalDisconnection(); + } + } + catch (Exception) + { + SignalDisconnection(); + } + } + + public void SendData(Buffer rawBuffer) + { + try + { + _clientStream.WriteAsync(rawBuffer.Array, rawBuffer.Position, rawBuffer.Length); + _clientStream.FlushAsync(); + } + catch (Exception) + { + SignalDisconnection(); + } + } + + private void SignalDisconnection() + { + if (!Connected && !_signaledDisconnection && Disconnected != null) + { + Disconnected(); + _signaledDisconnection = true; + } + } + + public void Dispose() + { + try + { + _client.Close(); + } + catch (Exception) + { + } + } + } +} diff --git a/ReshaperCore/Proxies/IProxyRegistry.cs b/ReshaperCore/Proxies/IProxyRegistry.cs new file mode 100644 index 0000000..3e772a1 --- /dev/null +++ b/ReshaperCore/Proxies/IProxyRegistry.cs @@ -0,0 +1,15 @@ +using System.Collections.ObjectModel; + +namespace ReshaperCore.Proxies +{ + public interface IProxyRegistry + { + ObservableCollection Proxies { get; } + + void Add(ProxyInfo proxy); + void Init(); + void LoadProxies(); + void Remove(string name); + void SaveProxies(); + } +} \ No newline at end of file diff --git a/ReshaperCore/Proxies/ISystemProxySettings.cs b/ReshaperCore/Proxies/ISystemProxySettings.cs new file mode 100644 index 0000000..4e6785a --- /dev/null +++ b/ReshaperCore/Proxies/ISystemProxySettings.cs @@ -0,0 +1,10 @@ +namespace ReshaperCore.Proxies +{ + public interface ISystemProxySettings + { + void ForceReset(); + bool IsLastRegistered(); + void Reset(); + void SetProxy(string hostname, int port); + } +} \ No newline at end of file diff --git a/ReshaperCore/Proxies/ProxyConnection.cs b/ReshaperCore/Proxies/ProxyConnection.cs new file mode 100644 index 0000000..6d2b4dc --- /dev/null +++ b/ReshaperCore/Proxies/ProxyConnection.cs @@ -0,0 +1,290 @@ +using System; +using System.ComponentModel; +using System.Net.Sockets; +using System.Threading; +using ReshaperCore.Messages; +using ReshaperCore.Networking; +using ReshaperCore.Rules; +using ReshaperCore.Vars; + +namespace ReshaperCore.Proxies +{ + public class ProxyConnection + { + private static long _connectionId; + + public virtual long ConnectionId + { + get; + private set; + } + + private TcpClient targetClient; + private TcpClient originClient; + + public virtual ProxyInfo ProxyInfo + { + private set; + get; + } + + public virtual bool HasTargetConnection + { + get + { + return TargetChannel?.Connected ?? false; + } + } + + public virtual bool HasOriginConnection + { + get + { + return OriginChannel?.Connected ?? false; + } + } + + public virtual Variables ToTargetConnectionVariables + { + private set; + get; + } = new Variables(); + + public virtual Variables ToOriginConnectionVariables + { + private set; + get; + } = new Variables(); + + private RulesEngine rulesEngine; + private Thread connectionThread; + + public virtual Channel TargetChannel + { + private set; + get; + } + + public virtual Channel OriginChannel + { + private set; + get; + } + public ProxyHost Host + { + get; + private set; + } + + public ProxyConnection(ProxyHost host, ProxyInfo proxy, TcpClient client) + { + this.Host = host; + this.ProxyInfo = proxy; + this.originClient = client; + rulesEngine = new RulesEngine(); + ConnectionId = Interlocked.Increment(ref _connectionId); + } + + public virtual bool HasConnection(DataDirection direction) + { + return (direction == DataDirection.Origin) ? HasOriginConnection : HasTargetConnection; + } + + public virtual bool InitConnection(DataDirection direction, string hostname, int port) + { + return (direction == DataDirection.Origin) ? InitOriginConnection(hostname, port) : InitTargetConnection(hostname, port); + } + + public virtual bool InitTargetConnection(string hostname, int port) + { + bool connected = false; + try + { + targetClient = new TcpClient(); + targetClient.Connect(hostname, port); + if (TargetChannel != null) + { + TargetChannel.DataReceived -= OnTargetChannelDataReceived; + //Disconnect code + } + TargetChannel = new Channel(targetClient); + TargetChannel.Disconnected += OnTargetChannelDisconnected; + TargetChannel.DataReceived += OnTargetChannelDataReceived; + TargetChannel.Init(); + connected = true; + } + catch (Exception) + { + } + return connected; + } + + public virtual bool InitOriginConnection(string hostname, int port) + { + bool connected = false; + try + { + originClient = new TcpClient(); + originClient.Connect(hostname, port); + if (OriginChannel != null) + { + OriginChannel.DataReceived -= OnOriginChannelDataReceived; + //Disconnect code + } + OriginChannel = new Channel(originClient); + OriginChannel.Disconnected += OnOriginChannelDisconnected; + OriginChannel.DataReceived += OnOriginChannelDataReceived; + OriginChannel.Init(); + connected = true; + } + catch (Exception) + { + } + return connected; + } + + public void Disconnect() + { + DisconnectTargetChannel(); + DisconnectOriginChannel(); + } + + public void DisconnectChannel(DataDirection direction) + { + if (direction == DataDirection.Origin) + { + DisconnectOriginChannel(); + } + else + { + DisconnectTargetChannel(); + } + } + + public void DisconnectOriginChannel() + { + if (HasOriginConnection) + { + OriginChannel.Dispose(); + } + } + + public void DisconnectTargetChannel() + { + if (HasTargetConnection) + { + TargetChannel.Dispose(); + } + } + + private void OnOriginChannelDisconnected() + { + if (TargetChannel?.Connected ?? false) + { + rulesEngine.Queue.AddLast(new EventInfo { ProxyConnection = this, Engine = rulesEngine, Type = EventType.Disconnected, Direction = DataDirection.Target, Variables = ToTargetConnectionVariables }); + } + } + + private void OnTargetChannelDisconnected() + { + if (OriginChannel?.Connected ?? false) + { + rulesEngine.Queue.AddLast(new EventInfo { ProxyConnection = this, Engine = rulesEngine, Type = EventType.Disconnected, Direction = DataDirection.Origin, Variables = ToOriginConnectionVariables }); + } + } + + public virtual void Init() + { + if (ProxyInfo.Enabled) + { + connectionThread = new Thread(() => + { + OriginChannel = new Channel(originClient); + OriginChannel.Disconnected += OnOriginChannelDisconnected; + OriginChannel.DataReceived += OnOriginChannelDataReceived; + OriginChannel.Init(); + + ProxyInfo.PropertyChanged += OnProxyInfoPropertyChanged; + + rulesEngine.Queue.AddLast(new EventInfo { ProxyConnection = this, Engine = rulesEngine, Type = EventType.Connected, Direction = DataDirection.Target, Variables = ToTargetConnectionVariables }); + }); + connectionThread.Start(); + } + } + + private void OnProxyInfoPropertyChanged(object sender, PropertyChangedEventArgs e) + { + try + { + if (e.PropertyName == "Enabled") + { + if (!ProxyInfo.Enabled) + { + OriginChannel.Dispose(); + ProxyInfo.PropertyChanged -= OnProxyInfoPropertyChanged; + } + } + } + catch (Exception) + { + } + } + + private void OnTargetChannelDataReceived(Buffer buffer) + { + AddToOriginData(buffer.GetBytes()); + } + + private void OnOriginChannelDataReceived(Buffer buffer) + { + AddToTargetData(buffer.GetBytes()); + } + + public virtual void SendData(EventInfo eventInfo) + { + switch (eventInfo.Direction) + { + case Messages.DataDirection.Target: + if (eventInfo.ProxyConnection.HasTargetConnection) + { + TargetChannel.SendData(new Buffer(eventInfo.Message.RawBytes)); + } + break; + case DataDirection.Origin: + if (eventInfo.ProxyConnection.HasOriginConnection) + { + OriginChannel.SendData(new Buffer(eventInfo.Message.RawBytes)); + } + break; + } + } + + private void AddToOriginData(byte[] data) + { + if (data != null) + { + rulesEngine.Queue.AddLast(new EventInfo { ProxyConnection = this, Engine = rulesEngine, Type = EventType.Message, Direction = DataDirection.Origin, Message = new Message() { RawBytes = data }, Variables = ToOriginConnectionVariables }); + } + } + + private void AddToTargetData(byte[] data) + { + if (data != null) + { + rulesEngine.Queue.AddLast(new EventInfo { ProxyConnection = this, Engine = rulesEngine, Type = EventType.Message, Direction = DataDirection.Target, Message = new Message() { RawBytes = data }, Variables = ToTargetConnectionVariables }); + } + } + + public void AddData(DataDirection direction, byte[] data) + { + if (direction == DataDirection.Origin) + { + AddToOriginData(data); + } + else + { + AddToTargetData(data); + } + } + } +} diff --git a/ReshaperCore/Proxies/ProxyDataType.cs b/ReshaperCore/Proxies/ProxyDataType.cs new file mode 100644 index 0000000..204c6de --- /dev/null +++ b/ReshaperCore/Proxies/ProxyDataType.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; + +namespace ReshaperCore.Proxies +{ + public enum ProxyDataType + { + [Description("Text")] + Text, + [Description("HTTP")] + Http + } +} diff --git a/ReshaperCore/Proxies/ProxyHost.cs b/ReshaperCore/Proxies/ProxyHost.cs new file mode 100644 index 0000000..22d72ea --- /dev/null +++ b/ReshaperCore/Proxies/ProxyHost.cs @@ -0,0 +1,95 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using ReshaperCore.Providers; +using ReshaperCore.Utils; + +namespace ReshaperCore.Proxies +{ + public class ProxyHost + { + private TcpListener _listener; + private ProxyInfo _proxy; + + public ISystemProxySettings SystemProxySettings { get; set; } = new SystemProxySettingsProvider().GetInstance(); + + public bool IsRegisted() + { + return (SystemProxySettings?.IsLastRegistered() ?? false) && _proxy.Enabled; + } + + public ProxyHost(ProxyInfo proxy) + { + this._proxy = proxy; + } + + public void Start() + { + try + { + //foreach (IPAddress ipAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList) + //{ + try + { + _listener = new TcpListener(/*ipAddress, */_proxy.Port); + _listener.Start(); + _proxy.PropertyChanged += Proxy_PropertyChanged; + _proxy.Enabled = true; + _listener.AcceptTcpClientAsync().ContinueWith(OnClientConnected); + } + catch (Exception/* ex*/) + { + //Log.LogError(ex, $"Could not setup proxy on {ipAddress.ToString()} at {_proxy.Port}."); + } + //} + if (_proxy.RegisterAsSystemProxy && _proxy.DataType == ProxyDataType.Http) + { + SystemProxySettings.SetProxy("127.0.0.1", _proxy.Port); + } + } + catch (Exception ex) + { + Log.LogError(ex, $"Could not setup proxy on port {_proxy.Port}."); + } + } + + private void Proxy_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + try + { + if (e.PropertyName == "Enabled") + { + if (!_proxy.Enabled) + { + _listener.Stop(); + SystemProxySettings?.Reset(); + } + } + } + catch (Exception) + { + } + } + + private void OnClientConnected(Task task) + { + try + { + if (task.Status == TaskStatus.RanToCompletion) + { + ProxyConnection proxyConnection = new ProxyConnection(this, _proxy, task.Result); + proxyConnection.Init(); + _listener.AcceptTcpClientAsync().ContinueWith(OnClientConnected); + } + } + catch (Exception ex) + { + Log.LogError(ex, $"Problem accepting hosted TCP client connection."); + } + } + } +} diff --git a/ReshaperCore/Proxies/ProxyInfo.cs b/ReshaperCore/Proxies/ProxyInfo.cs new file mode 100644 index 0000000..1eafd85 --- /dev/null +++ b/ReshaperCore/Proxies/ProxyInfo.cs @@ -0,0 +1,204 @@ +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; +using ReshaperCore.Utils; + +namespace ReshaperCore.Proxies +{ + public class ProxyInfo : ObservableEntity + { + private bool _enabled; + private int _port; + private string _name; + private ProxyDataType _dataType; + private int? _destinationPort; + private string _destinationHost; + private List _delimiters; + private bool _autoActivate; + private bool _registerAsSystemProxy; + private bool _useDelimiter = true; + private Encoding _defaultEncoding = Encoding.UTF8; + + [JsonConverter(typeof(EncodingJsonConvertercs))] + public Encoding DefaultEncoding + { + get + { + return _defaultEncoding; + } + set + { + if (_defaultEncoding != value) + { + _defaultEncoding = value; + OnPropertyChanged(nameof(DefaultEncoding)); + } + } + } + + public virtual int Port + { + get + { + return _port; + } + set + { + if (_port != value) + { + _port = value; + OnPropertyChanged(nameof(Port)); + } + } + } + public virtual bool UseDelimiter + { + get + { + return _useDelimiter; + } + set + { + if (_useDelimiter != value) + { + _useDelimiter = value; + OnPropertyChanged(nameof(UseDelimiter)); + } + } + } + + [JsonIgnore] + public virtual bool Enabled + { + get + { + return _enabled; + } + set + { + if (_enabled != value) + { + _enabled = value; + OnPropertyChanged(nameof(Enabled)); + } + } + } + + public virtual string Name + { + get + { + return _name; + } + set + { + if (_name != value) + { + _name = value; + OnPropertyChanged(nameof(Name)); + } + } + } + + public virtual bool RegisterAsSystemProxy + { + get + { + return _registerAsSystemProxy; + } + set + { + if (_registerAsSystemProxy != value) + { + _registerAsSystemProxy = value; + OnPropertyChanged(nameof(RegisterAsSystemProxy)); + } + } + } + + public virtual bool AutoActivate + { + get + { + return _autoActivate; + } + set + { + if (_autoActivate != value) + { + _autoActivate = value; + OnPropertyChanged(nameof(AutoActivate)); + } + } + } + + public virtual List Delimiters + { + get + { + return _delimiters; + } + set + { + if (_delimiters != value) + { + _delimiters = value; + OnPropertyChanged(nameof(Delimiters)); + } + } + } + + public virtual int? DestinationPort + { + get + { + return _destinationPort; + } + set + { + if (_destinationPort != value) + { + _destinationPort = value; + OnPropertyChanged(nameof(DestinationPort)); + } + } + } + + public virtual string DestinationHost + { + get + { + return _destinationHost; + } + set + { + if (_destinationHost != value) + { + _destinationHost = value; + OnPropertyChanged(nameof(DestinationHost)); + } + } + } + + public virtual ProxyDataType DataType + { + get + { + return _dataType; + } + set + { + if (_dataType != value) + { + _dataType = value; + OnPropertyChanged(nameof(DataType)); + } + } + } + + public ProxyInfo() + { + Delimiters = new List(); + } + } +} diff --git a/ReshaperCore/Proxies/ProxyRegistry.cs b/ReshaperCore/Proxies/ProxyRegistry.cs new file mode 100644 index 0000000..bac8b72 --- /dev/null +++ b/ReshaperCore/Proxies/ProxyRegistry.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Linq; +using ReshaperCore.Settings; +using ReshaperCore.Utils; + +namespace ReshaperCore.Proxies +{ + public class ProxyRegistry : IProxyRegistry + { + private ObservableCollection _proxies; + private static readonly string _proxiesFile = $@"{SettingsStore.StoragePath}/Proxies.json"; + + public virtual ObservableCollection Proxies + { + get + { + return _proxies; + } + } + + public virtual void Add(ProxyInfo proxy) + { + _proxies.Add(proxy); + proxy.PropertyChanged += ProxyInfo_PropertyChanged; + if (proxy.Enabled) + { + new ProxyHost(proxy).Start(); + } + } + + public virtual void Remove(string name) + { + ProxyInfo proxyInfo = _proxies.FirstOrDefault(proxy => proxy.Name == name); + if (proxyInfo != null) + { + proxyInfo.Enabled = false; + _proxies.Remove(proxyInfo); + proxyInfo.PropertyChanged -= ProxyInfo_PropertyChanged; + } + } + + public virtual void LoadProxies() + { + try + { + if (File.Exists(_proxiesFile)) + { + string serializedProxies = File.ReadAllText(_proxiesFile); + _proxies = Serializer.Deserialize>(serializedProxies); + } + } + catch (Exception e) + { + Log.LogError(e, "Could not load Proxies"); + } + if (_proxies == null) + { + _proxies = new ObservableCollection(); + } + } + + public virtual void SaveProxies() + { + try + { + String serializedProxies = Serializer.Serialize(_proxies); + + FileInfo file = new FileInfo(_proxiesFile); + file.Directory.Create(); + File.WriteAllText(_proxiesFile, serializedProxies); + } + catch (Exception e) + { + Log.LogError(e, "Could not save Proxies"); + } + } + + public virtual void Init() + { + LoadProxies(); + + foreach (ProxyInfo proxyInfo in Proxies) + { + proxyInfo.PropertyChanged += ProxyInfo_PropertyChanged; + if (proxyInfo.AutoActivate) + { + proxyInfo.Enabled = true; + } + } + } + + private void ProxyInfo_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "Enabled") + { + ProxyInfo proxyInfo = sender as ProxyInfo; + if (proxyInfo.Enabled) + { + new ProxyHost(proxyInfo).Start(); + } + } + } + } +} diff --git a/ReshaperCore/Proxies/SelfConnector.cs b/ReshaperCore/Proxies/SelfConnector.cs new file mode 100644 index 0000000..e4b036c --- /dev/null +++ b/ReshaperCore/Proxies/SelfConnector.cs @@ -0,0 +1,54 @@ +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using ReshaperCore.Networking; + +namespace ReshaperCore.Proxies +{ + public class SelfConnector + { + private Channel _channel; + private TcpClient _client; + private Thread _connectionThread; + private ProxyInfo _proxyInfo; + + public Task Connect(ProxyInfo proxyInfo) + { + this._proxyInfo = proxyInfo; + Task connectionPromise = new Task(() => + { + if (_channel?.Connected ?? false) + { + return this; + } + else + { + throw new SocketException(); + } + }); + _connectionThread = new Thread(() => + { + _client = new TcpClient(); + _client.ConnectAsync("127.0.0.1", proxyInfo.Port).ContinueWith(OnConnected, connectionPromise); + }); + _connectionThread.Start(); + return connectionPromise; + } + + private void OnConnected(Task connectionTask, object taskObj) + { + Task connectionPromise = (Task)taskObj; + if (connectionTask.Status == TaskStatus.RanToCompletion) + { + _channel = new Channel(_client); + _channel.Init(); + } + connectionPromise.Start(); + } + + public void SendData(byte[] data) + { + _channel.SendData(new Buffer(data)); + } + } +} diff --git a/ReshaperCore/Proxies/SystemProxySettings.cs b/ReshaperCore/Proxies/SystemProxySettings.cs new file mode 100644 index 0000000..ba56e2a --- /dev/null +++ b/ReshaperCore/Proxies/SystemProxySettings.cs @@ -0,0 +1,65 @@ +namespace ReshaperCore.Proxies +{ + public class SystemProxySettings : ISystemProxySettings + { + private readonly object _proxyMutex = new object(); + private bool _changedProxy; + private int _lastIdentity; + private int _identity; + private WinINetAdapter.INTERNET_PER_CONN_OPTION_LIST _defaultProxyOptions; + + public SystemProxySettings() + { + _identity = GetIdentity(); + } + + private int GetIdentity() + { + return ++_lastIdentity; + } + + public bool IsLastRegistered() + { + return _identity == _lastIdentity; + } + + public void Reset() + { + if (_identity == _lastIdentity) + { + ForceReset(); + } + } + + public void ForceReset() + { + lock (_proxyMutex) + { + if (_changedProxy) + { + WinINetAdapter.SetConnectionProxy(_defaultProxyOptions); + + _changedProxy = false; + } + } + } + + private void SaveCurrent() + { + _defaultProxyOptions = WinINetAdapter.GetSystemProxy(); + } + + public void SetProxy(string hostname, int port) + { + lock (_proxyMutex) + { + if (!_changedProxy) + { + SaveCurrent(); + } + WinINetAdapter.SetConnectionProxy(true, $"{hostname}:{port}"); + _changedProxy = true; + } + } + } +} diff --git a/ReshaperCore/Proxies/WinINetAdapter.cs b/ReshaperCore/Proxies/WinINetAdapter.cs new file mode 100644 index 0000000..32cbf35 --- /dev/null +++ b/ReshaperCore/Proxies/WinINetAdapter.cs @@ -0,0 +1,583 @@ + + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ReshaperCore.Proxies +{ + public static class WinINetAdapter + { + /****************************** Module Header ******************************\ + Module Name: INTERNET_OPEN_TYPE.cs + Project: CSWebBrowserWithProxy + Copyright (c) Microsoft Corporation. + + This enum contains 4 WinINet constants used in InternetOpen function. + Visit http://msdn.microsoft.com/en-us/library/aa385096(VS.85).aspx to get the + whole constants list. + + This source is subject to the Microsoft Public License. + See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. + All other rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + \***************************************************************************/ + public enum INTERNET_OPEN_TYPE + { + /// + /// Retrieves the proxy or direct configuration from the registry. + /// + INTERNET_OPEN_TYPE_PRECONFIG = 0, + + /// + /// Resolves all host names locally. + /// + INTERNET_OPEN_TYPE_DIRECT = 1, + + /// + /// Passes requests to the proxy unless a proxy bypass list is supplied and the name to be resolved bypasses the proxy. + /// + INTERNET_OPEN_TYPE_PROXY = 3, + + /// + /// Retrieves the proxy or direct configuration from the registry and prevents + /// the use of a startup Microsoft JScript or Internet Setup (INS) file. + /// + INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY = 4 + } + + /****************************** Module Header ******************************\ + Module Name: INTERNET_OPTION.cs + Project: CSWebBrowserWithProxy + Copyright (c) Microsoft Corporation. + + This enum contains 4 WinINet constants used in method InternetQueryOption and + InternetSetOption functions. + Visit http://msdn.microsoft.com/en-us/library/aa385328(VS.85).aspx to get the + whole constants list. + + This source is subject to the Microsoft Public License. + See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. + All other rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + \***************************************************************************/ + public enum INTERNET_OPTION + { + // Sets or retrieves an INTERNET_PER_CONN_OPTION_LIST structure that specifies + // a list of options for a particular connection. + INTERNET_OPTION_PER_CONNECTION_OPTION = 75, + + // Notify the system that the registry settings have been changed so that + // it verifies the settings on the next call to InternetConnect. + INTERNET_OPTION_SETTINGS_CHANGED = 39, + + // Causes the proxy data to be reread from the registry for a handle. + INTERNET_OPTION_REFRESH = 37 + + } + + /****************************** Module Header ******************************\ + Module Name: INTERNET_PER_CONN_OPTION.cs + Project: CSWebBrowserWithProxy + Copyright (c) Microsoft Corporation. + + This file defines the struct INTERNET_PER_CONN_OPTION and constants used by it. + The struct INTERNET_PER_CONN_OPTION contains the value of an option that to be + set to internet settings. + Visit http://msdn.microsoft.com/en-us/library/aa385145(VS.85).aspx to get the + detailed description. + + This source is subject to the Microsoft Public License. + See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. + All other rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + \***************************************************************************/ + + /// + /// Constants used in INTERNET_PER_CONN_OPTION_OptionUnion struct. + /// + public enum INTERNET_PER_CONN_OptionEnum + { + INTERNET_PER_CONN_FLAGS = 1, + INTERNET_PER_CONN_PROXY_SERVER = 2, + INTERNET_PER_CONN_PROXY_BYPASS = 3, + INTERNET_PER_CONN_AUTOCONFIG_URL = 4, + INTERNET_PER_CONN_AUTODISCOVERY_FLAGS = 5, + INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL = 6, + INTERNET_PER_CONN_AUTOCONFIG_RELOAD_DELAY_MINS = 7, + INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_TIME = 8, + INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL = 9, + INTERNET_PER_CONN_FLAGS_UI = 10 + } + + /// + /// Constants used in INTERNET_PER_CONN_OPTON struct. + /// + public enum INTERNET_OPTION_PER_CONN_FLAGS + { + PROXY_TYPE_DIRECT = 0x00000001, // direct to net + PROXY_TYPE_PROXY = 0x00000002, // via named proxy + PROXY_TYPE_AUTO_PROXY_URL = 0x00000004, // autoproxy URL + PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection + } + + /// + /// Used in INTERNET_PER_CONN_OPTION. + /// When create a instance of OptionUnion, only one filed will be used. + /// The StructLayout and FieldOffset attributes could help to decrease the struct size. + /// + [StructLayout(LayoutKind.Explicit)] + public struct INTERNET_PER_CONN_OPTION_OptionUnion + { + // A value in INTERNET_OPTION_PER_CONN_FLAGS. + [FieldOffset(0)] + public int dwValue; + [FieldOffset(0)] + public System.IntPtr pszValue; + [FieldOffset(0)] + public System.Runtime.InteropServices.ComTypes.FILETIME ftValue; + } + + [StructLayout(LayoutKind.Sequential)] + public struct INTERNET_PER_CONN_OPTION + { + // A value in INTERNET_PER_CONN_OptionEnum. + public int dwOption; + public INTERNET_PER_CONN_OPTION_OptionUnion Value; + } + + /****************************** Module Header ******************************\ + Module Name: INTERNET_PER_CONN_OPTION_LIST.cs + Project: CSWebBrowserWithProxy + Copyright (c) Microsoft Corporation. + + The struct INTERNET_PER_CONN_OPTION contains a list of options that to be + set to internet connection. + Visit http://msdn.microsoft.com/en-us/library/aa385146(VS.85).aspx to get the + detailed description. + + This source is subject to the Microsoft Public License. + See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. + All other rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + \***************************************************************************/ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct INTERNET_PER_CONN_OPTION_LIST + { + public int Size; + + // The connection to be set. NULL means LAN. + public System.IntPtr Connection; + + public int OptionCount; + public int OptionError; + + // List of INTERNET_PER_CONN_OPTIONs. + public System.IntPtr pOptions; + } + + /****************************** Module Header ******************************\ + Module Name: NativeMethods.cs + Project: CSWebBrowserWithProxy + Copyright (c) Microsoft Corporation. + + This class is a simple .NET wrapper of wininet.dll. It contains 4 extern + methods in wininet.dll. They are InternetOpen, InternetCloseHandle, + InternetSetOption and InternetQueryOption. + + This source is subject to the Microsoft Public License. + See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. + All other rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + \***************************************************************************/ + /// + /// Initialize an application's use of the WinINet functions. + /// See + /// + [DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern IntPtr InternetOpen( + string lpszAgent, + int dwAccessType, + string lpszProxyName, + string lpszProxyBypass, + int dwFlags); + + /// + /// Close a single Internet handle. + /// + [DllImport("wininet.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool InternetCloseHandle(IntPtr hInternet); + + /// + /// Sets an Internet option. + /// + [DllImport("wininet.dll", CharSet = CharSet.Ansi, SetLastError = true)] + private static extern bool InternetSetOption( + IntPtr hInternet, + INTERNET_OPTION dwOption, + IntPtr lpBuffer, + int lpdwBufferLength); + + /// + /// Queries an Internet option on the specified handle. The Handle will be always 0. + /// + [DllImport("wininet.dll", CharSet = CharSet.Ansi, SetLastError = true)] + private extern static bool InternetQueryOption( + IntPtr hInternet, + INTERNET_OPTION dwOption, + ref INTERNET_PER_CONN_OPTION_LIST OptionList, + ref int lpdwBufferLength); + + /****************************** Module Header ******************************\ + Module Name: WinINet.cs + Project: CSWebBrowserWithProxy + Copyright (c) Microsoft Corporation. + + This class is used to set the proxy. or restore to the system proxy for the + current application + + This source is subject to the Microsoft Public License. + See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. + All other rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + \***************************************************************************/ + private static string agent = Process.GetCurrentProcess().ProcessName; + + /// + /// Set the LAN connection proxy server for current process. + /// + /// + /// The Proxy Server. + /// + /// + public static bool SetConnectionProxy(bool isMachineSetting, string proxyServer) + { + if (isMachineSetting) + { + return SetConnectionProxy(null, proxyServer); + } + else + { + return SetConnectionProxy(agent, proxyServer); + } + } + + public static bool SetConnectionProxy(INTERNET_PER_CONN_OPTION_LIST optionList) + { + IntPtr hInternet = IntPtr.Zero; + int size = Marshal.SizeOf(optionList); + + // Allocate memory for the INTERNET_PER_CONN_OPTION_LIST instance. + IntPtr intptrStruct = Marshal.AllocCoTaskMem(size); + + // Marshal data from a managed object to an unmanaged block of memory. + Marshal.StructureToPtr(optionList, intptrStruct, true); + + // Set internet settings. + bool bReturn = InternetSetOption( + hInternet, + INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION, + intptrStruct, size); + + // Free the allocated memory. + Marshal.FreeCoTaskMem(intptrStruct); + + return bReturn; + } + + /// + /// Set the LAN connection proxy server. + /// + /// + /// If agentName is null or empty, this function will set the Lan proxy for + /// the machine, else for the current process. + /// + /// The Proxy Server. + /// + public static bool SetConnectionProxy(string agentName, string proxyServer) + { + IntPtr hInternet = IntPtr.Zero; + try + { + if (!string.IsNullOrEmpty(agentName)) + { + hInternet = InternetOpen( + agentName, + (int)INTERNET_OPEN_TYPE.INTERNET_OPEN_TYPE_DIRECT, + null, + null, + 0); + } + + return SetConnectionProxyInternal(hInternet, proxyServer); + } + finally + { + if (hInternet != IntPtr.Zero) + { + InternetCloseHandle(hInternet); + } + } + } + + /// + /// Set the proxy server for LAN connection. + /// + static bool SetConnectionProxyInternal(IntPtr hInternet, string proxyServer) + { + + // Create 3 options. + INTERNET_PER_CONN_OPTION[] Options = new INTERNET_PER_CONN_OPTION[3]; + + // Set PROXY flags. + Options[0] = new INTERNET_PER_CONN_OPTION(); + Options[0].dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_FLAGS; + Options[0].Value.dwValue = (int)INTERNET_OPTION_PER_CONN_FLAGS.PROXY_TYPE_PROXY; + + // Set proxy name. + Options[1] = new INTERNET_PER_CONN_OPTION(); + Options[1].dwOption = + (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_SERVER; + Options[1].Value.pszValue = Marshal.StringToHGlobalAnsi(proxyServer); + + // Set proxy bypass. + Options[2] = new INTERNET_PER_CONN_OPTION(); + Options[2].dwOption = + (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_BYPASS; + Options[2].Value.pszValue = Marshal.StringToHGlobalAnsi("local"); + + // Allocate a block of memory of the options. + System.IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(Options[0]) + + Marshal.SizeOf(Options[1]) + Marshal.SizeOf(Options[2])); + + System.IntPtr current = buffer; + + // Marshal data from a managed object to an unmanaged block of memory. + for (int i = 0; i < Options.Length; i++) + { + Marshal.StructureToPtr(Options[i], current, false); + current = (System.IntPtr)((int)current + Marshal.SizeOf(Options[i])); + } + + // Initialize a INTERNET_PER_CONN_OPTION_LIST instance. + INTERNET_PER_CONN_OPTION_LIST option_list = new INTERNET_PER_CONN_OPTION_LIST(); + + // Point to the allocated memory. + option_list.pOptions = buffer; + + // Return the unmanaged size of an object in bytes. + option_list.Size = Marshal.SizeOf(option_list); + + // IntPtr.Zero means LAN connection. + option_list.Connection = IntPtr.Zero; + + option_list.OptionCount = Options.Length; + option_list.OptionError = 0; + int size = Marshal.SizeOf(option_list); + + // Allocate memory for the INTERNET_PER_CONN_OPTION_LIST instance. + IntPtr intptrStruct = Marshal.AllocCoTaskMem(size); + + // Marshal data from a managed object to an unmanaged block of memory. + Marshal.StructureToPtr(option_list, intptrStruct, true); + + // Set internet settings. + bool bReturn = InternetSetOption( + hInternet, + INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION, + intptrStruct, size); + + // Free the allocated memory. + Marshal.FreeCoTaskMem(buffer); + Marshal.FreeCoTaskMem(intptrStruct); + + // Throw an exception if this operation failed. + if (!bReturn) + { + throw new ApplicationException(" Set Internet Option Failed!"); + } + + // Notify the system that the registry settings have been changed and cause + // the proxy data to be reread from the registry for a handle. + InternetSetOption( + hInternet, + INTERNET_OPTION.INTERNET_OPTION_SETTINGS_CHANGED, + IntPtr.Zero, 0); + + InternetSetOption( + hInternet, + INTERNET_OPTION.INTERNET_OPTION_REFRESH, + IntPtr.Zero, 0); + + return bReturn; + } + + /// + /// Get the current system options for LAN connection. + /// Make sure free the memory after restoration. + /// + public static INTERNET_PER_CONN_OPTION_LIST GetSystemProxy() + { + + // Query following options. + INTERNET_PER_CONN_OPTION[] Options = new INTERNET_PER_CONN_OPTION[3]; + + Options[0] = new INTERNET_PER_CONN_OPTION(); + Options[0].dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_FLAGS; + Options[1] = new INTERNET_PER_CONN_OPTION(); + Options[1].dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_SERVER; + Options[2] = new INTERNET_PER_CONN_OPTION(); + Options[2].dwOption = (int)INTERNET_PER_CONN_OptionEnum.INTERNET_PER_CONN_PROXY_BYPASS; + + // Allocate a block of memory of the options. + System.IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(Options[0]) + + Marshal.SizeOf(Options[1]) + Marshal.SizeOf(Options[2])); + + System.IntPtr current = (System.IntPtr)buffer; + + // Marshal data from a managed object to an unmanaged block of memory. + for (int i = 0; i < Options.Length; i++) + { + Marshal.StructureToPtr(Options[i], current, false); + current = (System.IntPtr)((int)current + Marshal.SizeOf(Options[i])); + } + + // Initialize a INTERNET_PER_CONN_OPTION_LIST instance. + INTERNET_PER_CONN_OPTION_LIST Request = new INTERNET_PER_CONN_OPTION_LIST(); + + // Point to the allocated memory. + Request.pOptions = buffer; + + Request.Size = Marshal.SizeOf(Request); + + // IntPtr.Zero means LAN connection. + Request.Connection = IntPtr.Zero; + + Request.OptionCount = Options.Length; + Request.OptionError = 0; + int size = Marshal.SizeOf(Request); + + // Query system internet options. + bool result = InternetQueryOption( + IntPtr.Zero, + INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION, + ref Request, + ref size); + + if (!result) + { + throw new ApplicationException("Get System Internet Option Failed! "); + } + + return Request; + } + + /// + /// Restore to the system proxy settings. + /// + public static bool RestoreSystemProxy() + { + return RestoreSystemProxy(agent); + } + + /// + /// Restore to the system proxy settings. + /// + public static bool RestoreSystemProxy(string agentName) + { + if (string.IsNullOrEmpty(agentName)) + { + throw new ArgumentNullException("Agent name cannot be null or empty!"); + } + + IntPtr hInternet = IntPtr.Zero; + try + { + if (!string.IsNullOrEmpty(agentName)) + { + hInternet = InternetOpen( + agentName, + (int)INTERNET_OPEN_TYPE.INTERNET_OPEN_TYPE_DIRECT, + null, + null, + 0); + } + + return RestoreSystemProxyInternal(hInternet); + } + finally + { + if (hInternet != IntPtr.Zero) + { + InternetCloseHandle(hInternet); + } + } + } + + /// + /// Restore to the system proxy settings. + /// + static bool RestoreSystemProxyInternal(IntPtr hInternet) + { + var request = GetSystemProxy(); + + int size = Marshal.SizeOf(request); + + // Allocate memory. + IntPtr intptrStruct = Marshal.AllocCoTaskMem(size); + + // Convert structure to IntPtr + Marshal.StructureToPtr(request, intptrStruct, true); + + // Set internet options. + bool bReturn = InternetSetOption( + hInternet, + INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION, + intptrStruct, + size); + + // Free the allocated memory. + Marshal.FreeCoTaskMem(request.pOptions); + Marshal.FreeCoTaskMem(intptrStruct); + + if (!bReturn) + { + throw new ApplicationException(" Set Internet Option Failed! "); + } + + // Notify the system that the registry settings have been changed and cause + // the proxy data to be reread from the registry for a handle. + InternetSetOption( + hInternet, + INTERNET_OPTION.INTERNET_OPTION_SETTINGS_CHANGED, + IntPtr.Zero, + 0); + + InternetSetOption( + hInternet, + INTERNET_OPTION.INTERNET_OPTION_REFRESH, + IntPtr.Zero, + 0); + return bReturn; + } + } +} diff --git a/ReshaperCore/ReshaperCore.csproj b/ReshaperCore/ReshaperCore.csproj new file mode 100644 index 0000000..ebc31fe --- /dev/null +++ b/ReshaperCore/ReshaperCore.csproj @@ -0,0 +1,184 @@ + + + + + Debug + AnyCPU + {1CFAFA78-E77A-4E75-8949-BE86AA620071} + Library + Properties + ReshaperCore + ReshaperCore + v4.6.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + none + true + bin\Release\ + TRACE + prompt + 4 + + + false + + + + + + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + \ No newline at end of file diff --git a/ReshaperCore/Rules/EventInfo.cs b/ReshaperCore/Rules/EventInfo.cs new file mode 100644 index 0000000..da2afe5 --- /dev/null +++ b/ReshaperCore/Rules/EventInfo.cs @@ -0,0 +1,113 @@ +using ReshaperCore.Messages; +using ReshaperCore.Proxies; +using ReshaperCore.Utils; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules +{ + public class EventInfo : ObservableEntity + { + private EventType _type; + private DataDirection _direction; + private Message _message; + private ProxyConnection _proxyConnection; + private Variables _variables; + private RulesEngine _engine; + + public virtual EventType Type + { + set + { + _type = value; + OnPropertyChanged(nameof(Type)); + } + get + { + return _type; + } + } + + public virtual DataDirection Direction + { + set + { + _direction = value; + OnPropertyChanged(nameof(Direction)); + } + get + { + return _direction; + } + } + + public virtual Message Message + { + set + { + RegisterOnEntityChanges(nameof(Message), value, _message); + _message = value; + OnPropertyChanged(nameof(Message)); + } + get + { + return _message; + } + } + + public virtual ProxyConnection ProxyConnection + { + set + { + _proxyConnection = value; + OnPropertyChanged(nameof(ProxyConnection)); + } + get + { + return _proxyConnection; + } + } + + public virtual Variables Variables + { + set + { + _variables = value; + OnPropertyChanged(nameof(Variables)); + } + get + { + return _variables; + } + } + public virtual RulesEngine Engine + { + set + { + _engine = value; + OnPropertyChanged(nameof(Engine)); + } + get + { + return _engine; + } + } + + public EventInfo() + { + + } + + public virtual EventInfo Clone(RulesEngine engine = null, EventType? type = null, DataDirection? direction = null, Message message = null, ProxyConnection proxyConnection = null, Variables variables = null) + { + return new EventInfo() + { + Engine = engine ?? Engine, + Type = type ?? Type, + Direction = direction ?? Direction, + Message = message ?? Message, + ProxyConnection = proxyConnection ?? ProxyConnection, + Variables = variables ?? Variables + }; + } + } +} diff --git a/ReshaperCore/Rules/EventType.cs b/ReshaperCore/Rules/EventType.cs new file mode 100644 index 0000000..0a24720 --- /dev/null +++ b/ReshaperCore/Rules/EventType.cs @@ -0,0 +1,7 @@ +namespace ReshaperCore.Rules +{ + public enum EventType + { + Message, Connected, Disconnected + } +} diff --git a/ReshaperCore/Rules/HttpRulesRegistry.cs b/ReshaperCore/Rules/HttpRulesRegistry.cs new file mode 100644 index 0000000..41a2854 --- /dev/null +++ b/ReshaperCore/Rules/HttpRulesRegistry.cs @@ -0,0 +1,19 @@ +using System.Text; + +namespace ReshaperCore.Rules +{ + public class HttpRulesRegistry : RulesRegistry, IHttpRulesRegistry + { + public HttpRulesRegistry() : base("HTTP") + { + } + + protected override string DefaultRulesJson + { + get + { + return Encoding.UTF8.GetString(Properties.Resources.DefaultHttpRules); + } + } + } +} diff --git a/ReshaperCore/Rules/IHttpRulesRegistry.cs b/ReshaperCore/Rules/IHttpRulesRegistry.cs new file mode 100644 index 0000000..4b9b98f --- /dev/null +++ b/ReshaperCore/Rules/IHttpRulesRegistry.cs @@ -0,0 +1,6 @@ +namespace ReshaperCore.Rules +{ + public interface IHttpRulesRegistry : IRulesRegistry + { + } +} diff --git a/ReshaperCore/Rules/IRuleOperation.cs b/ReshaperCore/Rules/IRuleOperation.cs new file mode 100644 index 0000000..b7e093e --- /dev/null +++ b/ReshaperCore/Rules/IRuleOperation.cs @@ -0,0 +1,6 @@ +namespace ReshaperCore.Rules +{ + public interface IRuleOperation + { + } +} diff --git a/ReshaperCore/Rules/IRulesRegistry.cs b/ReshaperCore/Rules/IRulesRegistry.cs new file mode 100644 index 0000000..a0cd44b --- /dev/null +++ b/ReshaperCore/Rules/IRulesRegistry.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace ReshaperCore.Rules +{ + + public delegate void RulesListChangedHandler(); + + public interface IRulesRegistry + { + event RulesListChangedHandler RulesListChanged; + + void AddRule(Rule rule); + bool CanDelete(Rule rule); + bool CanMoveNext(Rule rule); + bool CanMovePrevious(Rule rule); + void DeleteRule(Rule rule); + IReadOnlyList GetRules(); + void Init(); + void MoveNext(Rule rule); + void MovePrevious(Rule rule); + void RestoreDefaultRules(); + void SaveRules(); + } +} \ No newline at end of file diff --git a/ReshaperCore/Rules/ITextRulesRegistry.cs b/ReshaperCore/Rules/ITextRulesRegistry.cs new file mode 100644 index 0000000..9f812d2 --- /dev/null +++ b/ReshaperCore/Rules/ITextRulesRegistry.cs @@ -0,0 +1,6 @@ +namespace ReshaperCore.Rules +{ + public interface ITextRulesRegistry : IRulesRegistry + { + } +} diff --git a/ReshaperCore/Rules/MatchType.cs b/ReshaperCore/Rules/MatchType.cs new file mode 100644 index 0000000..d4020e2 --- /dev/null +++ b/ReshaperCore/Rules/MatchType.cs @@ -0,0 +1,15 @@ +using System.ComponentModel; + +namespace ReshaperCore.Rules +{ + public enum MatchType + { + Equals, + Contains, + [Description("Begins With")] + BeginsWith, + [Description("Ends With")] + EndsWith, + Regex + } +} diff --git a/ReshaperCore/Rules/Rule.cs b/ReshaperCore/Rules/Rule.cs new file mode 100644 index 0000000..b62a787 --- /dev/null +++ b/ReshaperCore/Rules/Rule.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ReshaperCore.Rules.Thens; +using ReshaperCore.Rules.Whens; + +namespace ReshaperCore.Rules +{ + public class Rule + { + public ObservableCollection Whens { get; set; } + + public ObservableCollection Thens { get; set; } + + public bool Enabled { get; set; } + + public String Name { get; set; } + + public bool Locked + { + get; + set; + } + + public RunPosition Placement + { + get; + set; + } = RunPosition.Undefined; + + public Rule() + { + Whens = new ObservableCollection(); + Thens = new ObservableCollection(); + Enabled = true; + } + + public override string ToString() + { + return Name ?? ""; + } + } +} diff --git a/ReshaperCore/Rules/RulesEngine.cs b/ReshaperCore/Rules/RulesEngine.cs new file mode 100644 index 0000000..1e85dc7 --- /dev/null +++ b/ReshaperCore/Rules/RulesEngine.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using ReshaperCore.Providers; +using ReshaperCore.Proxies; +using ReshaperCore.Rules.Thens; +using ReshaperCore.Rules.Whens; +using ReshaperCore.Utils; + +namespace ReshaperCore.Rules +{ + public class RulesEngine : Observer + { + public virtual MessageQueue Queue + { + private set; + get; + } + + public ITextRulesRegistry TextRulesRegistry { get; set; } = new TextRulesRegistryProvider().GetInstance(); + + public IHttpRulesRegistry HttpRulesRegistry { get; set; } = new HttpRulesRegistryProvider().GetInstance(); + + public RulesEngine() + { + Queue = new MessageQueue(); + Queue.SetObserver(this); + } + + private bool MatchWhens(IEnumerable whens, EventInfo eventInfo) + { + bool isMatch = true; + bool first = true; + foreach (When when in whens) + { + if (!isMatch && !when.UseOrCondition && !first) + { + break; + } + if (when.UseOrCondition && !first) + { + isMatch |= when.IsMatch(eventInfo) == !when.Negate; + } + else + { + isMatch &= when.IsMatch(eventInfo) == !when.Negate; + } + first = false; + } + return isMatch; + } + + private ThenResponse PerformThens(IEnumerable thens, EventInfo eventInfo) + { + ThenResponse ThenResult = ThenResponse.Continue; + foreach (Then then in thens) + { + ThenResponse result = then.Perform(eventInfo); + ThenResult |= result; + if (result.HasFlag(ThenResponse.BreakThens) || result.HasFlag(ThenResponse.BreakRules)) + { + break; + } + } + return ThenResult; + } + + private ThenResponse Run(EventInfo eventInfo) + { + IReadOnlyList rules = null; + + switch (eventInfo.ProxyConnection.ProxyInfo.DataType) + { + case ProxyDataType.Http: + rules = HttpRulesRegistry.GetRules(); + break; + case ProxyDataType.Text: + rules = TextRulesRegistry.GetRules(); + break; + default: + rules = new List(); + break; + } + + + ThenResponse thenResult = ThenResponse.Continue; + foreach (Rule rule in rules) + { + try + { + if (rule.Enabled && MatchWhens(rule.Whens, eventInfo)) + { + thenResult |= PerformThens(rule.Thens, eventInfo); + if (thenResult.HasFlag(ThenResponse.BreakRules)) + { + break; + } + } + } + catch (Exception e) + { + Log.LogError(e, "Failure running rule", rule.Name); + } + } + return thenResult; + } + + public override void OnUpdate() + { + if (!Monitor.IsEntered(ObserverKey)) + { + if (Monitor.TryEnter(ObserverKey) && !ObserverWorking) + { + ObserverWorking = true; + if (Queue != null) + { + while (!Queue.IsEmpty()) + { + Run(Queue.TakeFirst()); + } + } + ObserverWorking = false; + Monitor.Exit(ObserverKey); + } + } + } + } +} diff --git a/ReshaperCore/Rules/RulesRegistry.cs b/ReshaperCore/Rules/RulesRegistry.cs new file mode 100644 index 0000000..87773fc --- /dev/null +++ b/ReshaperCore/Rules/RulesRegistry.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ReshaperCore.Settings; +using ReshaperCore.Utils; + +namespace ReshaperCore.Rules +{ + public abstract class RulesRegistry : IRulesRegistry + { + private readonly string _rulesFilePath; + private readonly string _rulesType; + + public virtual event RulesListChangedHandler RulesListChanged; + + private List Rules + { + get; + set; + } = new List(); + + protected abstract string DefaultRulesJson + { + get; + } + + public RulesRegistry(string rulesType) + { + _rulesType = rulesType; + _rulesFilePath = $@"{SettingsStore.StoragePath}/{rulesType}Rules.json"; + } + + public virtual void Init() + { + LoadRules(); + } + + public virtual IReadOnlyList GetRules() + { + return Rules.ToList(); + } + + public virtual void DeleteRule(Rule rule) + { + if (!rule.Locked) + { + Rules.Remove(rule); + OnRulesListChanged(); + } + } + + public virtual bool CanDelete(Rule rule) + { + return rule != null && !rule.Locked; + } + + public virtual void AddRule(Rule rule) + { + int index = Rules.Count; + if (Rules.Count > 0) + { + int lastOpenPlace = Rules.FindIndex(r => r.Placement == RunPosition.End); + if (lastOpenPlace >= 0) + { + index = lastOpenPlace; + } + } + Rules.Insert(index, rule); + OnRulesListChanged(); + } + + public virtual void SaveRules() + { + try + { + String serializedRules = Serializer.Serialize(Rules); + + FileInfo file = new FileInfo(_rulesFilePath); + file.Directory.Create(); + File.WriteAllText(_rulesFilePath, serializedRules); + } + catch (Exception e) + { + Log.LogError(e, $"Could not save {_rulesType} Rules"); + } + } + + public virtual void RestoreDefaultRules() + { + if (File.Exists(_rulesFilePath)) + { + File.Delete(_rulesFilePath); + } + LoadRules(); + } + + private void LoadRules() + { + try + { + String serializedRules; + if (File.Exists(_rulesFilePath)) + { + serializedRules = File.ReadAllText(_rulesFilePath); + } + else + { + serializedRules = DefaultRulesJson; + } + Rules = Serializer.Deserialize>(serializedRules); + } + catch (Exception e) + { + Log.LogError(e, $"Could not load {_rulesType} Rules"); + } + if (Rules == null) + { + Rules = new List(); + } + OnRulesListChanged(); + } + + public virtual void MovePrevious(Rule rule) + { + if (rule.Placement == RunPosition.Undefined) + { + int currentIndex = Rules.IndexOf(rule); + if (currentIndex > 0) + { + Rule previousRule = Rules[currentIndex]; + if (previousRule.Placement != RunPosition.Beginning) + { + Rules.RemoveAt(currentIndex); + Rules.Insert(--currentIndex, rule); + OnRulesListChanged(); + } + } + } + } + + public virtual bool CanMovePrevious(Rule rule) + { + bool canMove = false; + if (rule != null && rule.Placement == RunPosition.Undefined) + { + int currentIndex = Rules.IndexOf(rule); + if (currentIndex > 0) + { + Rule previousRule = Rules[currentIndex]; + if (previousRule.Placement != RunPosition.Beginning) + { + canMove = true; + } + } + } + return canMove; + } + + public virtual void MoveNext(Rule rule) + { + if (rule.Placement == RunPosition.Undefined) + { + int currentIndex = Rules.IndexOf(rule); + if (currentIndex < Rules.Count - 1) + { + Rule nextRule = Rules[currentIndex]; + if (nextRule.Placement != RunPosition.End) + { + Rules.RemoveAt(currentIndex); + Rules.Insert(++currentIndex, rule); + OnRulesListChanged(); + } + } + } + } + + public virtual bool CanMoveNext(Rule rule) + { + bool canMove = false; + if (rule != null && rule.Placement == RunPosition.Undefined) + { + int currentIndex = Rules.IndexOf(rule); + if (currentIndex >= 0 && currentIndex < Rules.Count - 1) + { + Rule nextRule = Rules[currentIndex]; + if (nextRule.Placement != RunPosition.End) + { + canMove = true; + } + } + } + return canMove; + } + + private void OnRulesListChanged() + { + if (RulesListChanged != null) + { + RulesListChanged(); + } + } + } +} diff --git a/ReshaperCore/Rules/RunPosition.cs b/ReshaperCore/Rules/RunPosition.cs new file mode 100644 index 0000000..448f012 --- /dev/null +++ b/ReshaperCore/Rules/RunPosition.cs @@ -0,0 +1,9 @@ +namespace ReshaperCore.Rules +{ + public enum RunPosition + { + Beginning, + Undefined, + End + } +} diff --git a/ReshaperCore/Rules/TextRulesRegistry.cs b/ReshaperCore/Rules/TextRulesRegistry.cs new file mode 100644 index 0000000..0341c0e --- /dev/null +++ b/ReshaperCore/Rules/TextRulesRegistry.cs @@ -0,0 +1,19 @@ +using System.Text; + +namespace ReshaperCore.Rules +{ + public class TextRulesRegistry : RulesRegistry, ITextRulesRegistry + { + public TextRulesRegistry() : base("Text") + { + } + + protected override string DefaultRulesJson + { + get + { + return Encoding.UTF8.GetString(Properties.Resources.DefaultTextRules); + } + } + } +} diff --git a/ReshaperCore/Rules/ThenResponse.cs b/ReshaperCore/Rules/ThenResponse.cs new file mode 100644 index 0000000..0a5c4c4 --- /dev/null +++ b/ReshaperCore/Rules/ThenResponse.cs @@ -0,0 +1,9 @@ +namespace ReshaperCore.Rules +{ + public enum ThenResponse + { + Continue = 1, + BreakThens = 2, + BreakRules = 4 + } +} diff --git a/ReshaperCore/Rules/Thens/Then.cs b/ReshaperCore/Rules/Thens/Then.cs new file mode 100644 index 0000000..acbc089 --- /dev/null +++ b/ReshaperCore/Rules/Thens/Then.cs @@ -0,0 +1,7 @@ +namespace ReshaperCore.Rules.Thens +{ + public abstract class Then : IRuleOperation + { + public abstract ThenResponse Perform(EventInfo eventInfo); + } +} diff --git a/ReshaperCore/Rules/Thens/ThenAddMessage.cs b/ReshaperCore/Rules/Thens/ThenAddMessage.cs new file mode 100644 index 0000000..598ac94 --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenAddMessage.cs @@ -0,0 +1,61 @@ +using ReshaperCore.Messages; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenAddMessage : Then + { + public DataDirection Direction + { + get; + set; + } + + public string Delimiter + { + get; + set; + } = string.Empty; + + public VariableString MessageText + { + get; + set; + } + + public bool InsertAtBeginning + { + get; + set; + } + + public override ThenResponse Perform(EventInfo eventInfo) + { + Variables connectionVariables; + if (Direction == DataDirection.Origin) + { + connectionVariables = eventInfo.ProxyConnection.ToOriginConnectionVariables; + } + else + { + connectionVariables = eventInfo.ProxyConnection.ToTargetConnectionVariables; + } + + EventInfo newEventInfo = eventInfo.Clone(direction: Direction, type: EventType.Message, message: new Message() + { + RawText = MessageText.GetText(eventInfo.Variables) + Delimiter, + Delimiter = Delimiter, + }, variables: connectionVariables); + + if (InsertAtBeginning) + { + eventInfo.Engine.Queue.AddFirst(newEventInfo); + } + else + { + eventInfo.Engine.Queue.AddLast(newEventInfo); + } + return ThenResponse.Continue; + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenBroadcast.cs b/ReshaperCore/Rules/Thens/ThenBroadcast.cs new file mode 100644 index 0000000..9572b3f --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenBroadcast.cs @@ -0,0 +1,18 @@ +using ReshaperCore.Providers; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenBroadcast : Then + { + public ISelf Self { get; set; } = new SelfProvider().GetInstance(); + + public override ThenResponse Perform(EventInfo eventInfo) + { + if (eventInfo.Type == EventType.Message) + { + Self.BroadcastEvent(eventInfo); + } + return ThenResponse.Continue; + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenConnect.cs b/ReshaperCore/Rules/Thens/ThenConnect.cs new file mode 100644 index 0000000..95c588f --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenConnect.cs @@ -0,0 +1,57 @@ +using System; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenConnect : Then + { + public VariableString DestinationHost + { + get; + set; + } + + public VariableString DestinationPort + { + get; + set; + } + + public bool OverrideCurrentConnection + { + get; + set; + } + + public override ThenResponse Perform(EventInfo eventInfo) + { + ThenResponse thenResponse = ThenResponse.Continue; + + string destinationHost = DestinationHost?.GetText(eventInfo.Variables) ?? eventInfo.ProxyConnection.ProxyInfo.DestinationHost; + int? destinationPort = DestinationPort?.GetInt(eventInfo.Variables) ?? eventInfo.ProxyConnection.ProxyInfo.DestinationPort; + + if (eventInfo.Direction == Messages.DataDirection.Target) + { + if (!String.IsNullOrEmpty(destinationHost) && destinationPort != null && (OverrideCurrentConnection || !eventInfo.ProxyConnection.HasTargetConnection)) + { + if (!eventInfo.ProxyConnection.InitTargetConnection(destinationHost, destinationPort.Value)) + { + thenResponse = ThenResponse.BreakRules; + } + } + } + else + { + if (!String.IsNullOrEmpty(destinationHost) && destinationPort != null && (OverrideCurrentConnection || !eventInfo.ProxyConnection.HasOriginConnection)) + { + if (!eventInfo.ProxyConnection.InitOriginConnection(destinationHost, destinationPort.Value)) + { + thenResponse = ThenResponse.BreakRules; + } + } + } + + return thenResponse; + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenDelay.cs b/ReshaperCore/Rules/Thens/ThenDelay.cs new file mode 100644 index 0000000..ac81c22 --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenDelay.cs @@ -0,0 +1,22 @@ +using System.Threading; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenDelay : Then + { + + + public VariableString Delay + { + get; + set; + } + + public override ThenResponse Perform(EventInfo eventInfo) + { + Thread.Sleep(Delay.GetInt(eventInfo.Variables) ?? 0); + return ThenResponse.Continue; + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenDelimitHttp.cs b/ReshaperCore/Rules/Thens/ThenDelimitHttp.cs new file mode 100644 index 0000000..523f7e8 --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenDelimitHttp.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using ReshaperCore.Messages; +using ReshaperCore.Messages.Parsers; +using ReshaperCore.Networking; +using ReshaperCore.Utils.Extensions; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenDelimitHttp : Then + { + public override ThenResponse Perform(EventInfo eventInfo) + { + if (eventInfo.Type == EventType.Message) + { + ThenResponse thenResult = ThenResponse.BreakRules; + if (!eventInfo.Message.CheckedEntity(HttpMessage.EntityFlag)) + { + IVariable carryOverVar = eventInfo.Variables.GetOrDefault($"ThenDelimitHttp_CarryOverBytes") ?? eventInfo.Variables.Add($"ThenDelimitHttp_CarryOverBytes"); + byte[] carryOverBytes = carryOverVar.Value ?? new byte[0]; + carryOverVar.Value = new byte[0]; + + byte[] fullBytes = carryOverBytes.Combine(eventInfo.Message.RawBytes); + + List childEvents = new List(); + + Tuple messageInfo = null; + IVariable syncId = eventInfo.Variables.GetOrDefault("ThenDelimitHttp_SyncId") ?? eventInfo.Variables.Add("ThenDelimitHttp_SyncId"); + + do + { + HttpMessageParser parser = new HttpMessageParser() + { + TextEncoding = eventInfo.ProxyConnection.ProxyInfo.DefaultEncoding + }; + messageInfo = parser.Parse(fullBytes); + if (messageInfo.Item2 != null) + { + carryOverBytes = new byte[0]; + messageInfo.Item2.SyncId = syncId.Value++; + childEvents.Add(eventInfo.Clone(message: messageInfo.Item2)); + if (messageInfo.Item1 <= fullBytes.Length) + { + fullBytes = new Buffer(fullBytes, messageInfo.Item1, fullBytes.Length - messageInfo.Item1).GetBytes(); + } + else + { + fullBytes = new byte[0]; + } + } + else + { + carryOverBytes = fullBytes; + } + } + while (fullBytes.Length > 0 && messageInfo.Item2 != null); + + carryOverVar.Value = carryOverBytes; + + eventInfo.Engine.Queue.AddFirst(childEvents); + + eventInfo.Message.SetEntityFlag(HttpMessage.EntityFlag, false); + } + else if (eventInfo.Message is HttpMessage) + { + thenResult = ThenResponse.Continue; + } + return thenResult; + } + else + { + return ThenResponse.Continue; + } + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenDelimitText.cs b/ReshaperCore/Rules/Thens/ThenDelimitText.cs new file mode 100644 index 0000000..eb6258f --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenDelimitText.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using ReshaperCore.Messages; +using ReshaperCore.Utils.Extensions; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenDelimitText : Then + { + + public override ThenResponse Perform(EventInfo eventInfo) + { + ThenResponse thenResult = ThenResponse.Continue; + if (eventInfo.Type == EventType.Message) + { + if (!eventInfo.Message.Complete && eventInfo.Type == EventType.Message) + { + IVariable carryOverVar = eventInfo.Variables.GetOrDefault($"ThenDelimitText_CarryOverText_{eventInfo.Direction}") ?? eventInfo.Variables.Add($"ThenDelimitText_CarryOverText_{eventInfo.Direction}"); + string carryOverText = carryOverVar.Value ?? string.Empty; + List> sections = null; + + if (eventInfo.ProxyConnection.ProxyInfo.UseDelimiter) + { + sections = eventInfo.Message.RawText.SplitWithDelimiters(eventInfo.ProxyConnection.ProxyInfo.Delimiters); + } + else + { + sections = new List> { new Tuple(eventInfo.Message.RawText, string.Empty) }; + } + + List childEvents = new List(); + + carryOverVar.Value = String.Empty; + string foundDelimiter = null; + if (eventInfo.Message.RawText.StartsWith(eventInfo.ProxyConnection.ProxyInfo.Delimiters, out foundDelimiter)) + { + childEvents.Add(eventInfo.Clone(message: new Message() + { + TextEncoding = eventInfo.Message.TextEncoding, + RawText = carryOverText + foundDelimiter, + Delimiter = foundDelimiter, + Complete = true + })); + } + if (sections.Count > 0) + { + for (int sectionIndex = 0; sectionIndex < sections.Count - 1; sectionIndex++) + { + childEvents.Add(eventInfo.Clone(message: new Message() + { + TextEncoding = eventInfo.Message.TextEncoding, + RawText = carryOverText + sections[sectionIndex].Item1 + sections[sectionIndex].Item2, + Delimiter = sections[sectionIndex].Item2, + Complete = true + })); + carryOverText = string.Empty; + } + int lastIndex = sections.Count - 1; + if (sections[lastIndex].Item2 != null) + { + childEvents.Add(eventInfo.Clone(message: new Message() + { + TextEncoding = eventInfo.Message.TextEncoding, + RawText = carryOverText + sections[lastIndex].Item1 + sections[lastIndex].Item2, + Delimiter = sections[lastIndex].Item2, + Complete = true + })); + carryOverText = string.Empty; + } + else + { + carryOverVar.Value = carryOverText + sections[lastIndex].Item1; + } + } + else + { + carryOverVar.Value = carryOverText; + } + thenResult = ThenResponse.BreakRules; + eventInfo.Engine.Queue.AddFirst(childEvents); + } + } + return thenResult; + } + + } +} diff --git a/ReshaperCore/Rules/Thens/ThenDisconnect.cs b/ReshaperCore/Rules/Thens/ThenDisconnect.cs new file mode 100644 index 0000000..6b5e7e6 --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenDisconnect.cs @@ -0,0 +1,11 @@ +namespace ReshaperCore.Rules.Thens +{ + public class ThenDisconnect : Then + { + public override ThenResponse Perform(EventInfo eventInfo) + { + eventInfo.ProxyConnection.DisconnectChannel(eventInfo.Direction); + return ThenResponse.Continue; + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenHttpConnect.cs b/ReshaperCore/Rules/Thens/ThenHttpConnect.cs new file mode 100644 index 0000000..ad26f94 --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenHttpConnect.cs @@ -0,0 +1,46 @@ +using ReshaperCore.Messages; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenHttpConnect : Then + { + + public bool OverrideCurrentConnection + { + get; + set; + } + + public override ThenResponse Perform(EventInfo eventInfo) + { + ThenResponse thenResponse = ThenResponse.BreakRules; + + if (eventInfo.Type == EventType.Message && (OverrideCurrentConnection || !eventInfo.ProxyConnection.HasConnection(eventInfo.Direction))) + { + HttpMessage httpMessage = eventInfo.Message as HttpMessage; + if (httpMessage != null) + { + string[] host = httpMessage.Headers.GetOrDefault("Host")?.Split(':'); + if (host != null) + { + string hostname = host[0]; + int port; + if (host.Length <= 1 || !int.TryParse(host[1], out port)) + { + port = 80; + } + if (eventInfo.ProxyConnection.InitConnection(eventInfo.Direction, hostname, port)) + { + thenResponse = ThenResponse.Continue; + } + } + } + } + else + { + thenResponse = ThenResponse.Continue; + } + return thenResponse; + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenLog.cs b/ReshaperCore/Rules/Thens/ThenLog.cs new file mode 100644 index 0000000..fdaaf6a --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenLog.cs @@ -0,0 +1,21 @@ +using ReshaperCore.Utils; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenLog : Then + { + + public VariableString Text + { + get; + set; + } + + public override ThenResponse Perform(EventInfo eventInfo) + { + Log.LogInfo(Text.GetText(eventInfo.Variables)); + return ThenResponse.Continue; + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenSendData.cs b/ReshaperCore/Rules/Thens/ThenSendData.cs new file mode 100644 index 0000000..cbdf278 --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenSendData.cs @@ -0,0 +1,16 @@ +namespace ReshaperCore.Rules.Thens +{ + public class ThenSendData : Then + { + public override ThenResponse Perform(EventInfo eventInfo) + { + switch (eventInfo.Type) + { + case EventType.Message: + eventInfo.ProxyConnection.SendData(eventInfo); + break; + } + return ThenResponse.Continue; + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenSet.cs b/ReshaperCore/Rules/Thens/ThenSet.cs new file mode 100644 index 0000000..47b2215 --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenSet.cs @@ -0,0 +1,173 @@ +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using ReshaperCore.Messages; +using ReshaperCore.Messages.Entities; +using ReshaperCore.Providers; +using ReshaperCore.Utils.Extensions; +using ReshaperCore.Vars; +using Timer = System.Timers.Timer; + +namespace ReshaperCore.Rules.Thens +{ + public abstract class ThenSet : Then + { + private MessageValueHandler messageValueRetriever = new MessageValueHandler(); + + public ISelf Self { get; set; } = new SelfProvider().GetInstance(); + + public bool UseMessageValue + { + get; + set; + } + + public bool PromptValue + { + get; + set; + } + + public VariableString PromptDescription + { + get; + set; + } + + public MessageValue SourceMessageValue + { + get; + set; + } + + public MessageValueType SourceMessageValueType + { + get; + set; + } = MessageValueType.Text; + + public VariableString SourceIdentifier + { + get; + set; + } + + public bool UseReplace + { + get; + set; + } + + public VariableString RegexPattern + { + get; + set; + } + + public VariableString Text + { + get; + set; + } + + public VariableString ReplacementText + { + get; + set; + } + + public int MaxPromptWaitTime + { + get; + set; + } = 30; + + public ThenSet() + { + + } + + private string GetValue(EventInfo eventInfo) + { + return messageValueRetriever.GetValue(eventInfo, SourceMessageValue, SourceMessageValueType, SourceIdentifier); + } + + protected virtual string GetReplacementValue(EventInfo eventInfo) + { + string text = string.Empty; + if (UseMessageValue) + { + text = GetValue(eventInfo); + } + else + { + if (SourceMessageValueType != MessageValueType.Text && SourceIdentifier != null) + { + switch (SourceMessageValueType) + { + case MessageValueType.Json: + text = (Text?.GetText(eventInfo.Variables) ?? text).GetJsonValue(SourceIdentifier.GetText(eventInfo.Variables)) ?? string.Empty; + break; + case MessageValueType.Xml: + text = (Text?.GetText(eventInfo.Variables) ?? text).GetXmlValue(SourceIdentifier.GetText(eventInfo.Variables)) ?? string.Empty; + break; + } + } + else + { + text = Text?.GetText(eventInfo.Variables) ?? text; + } + } + + if (PromptValue) + { + text = GetPromptValue(eventInfo, PromptDescription.GetText(eventInfo.Variables), text); + } + + if (UseReplace && ReplacementText != null) + { + Regex regex = new Regex(RegexPattern.GetText(eventInfo.Variables)); + text = regex.Replace(text, ReplacementText.GetText(eventInfo.Variables)); + } + return text; + } + + private string GetPromptValue(EventInfo eventInfo, string description = "", string text = "") + { + object id = new object(); + Barrier promptBarrier = new Barrier(2); + CancellationTokenSource tokenSource = new CancellationTokenSource(); + Timer timer = new Timer() + { + AutoReset = false, + Interval = MaxPromptWaitTime * 1000 + }; + timer.Elapsed += (sender, e) => + { + Self.CancelPrompt(id); + tokenSource.Cancel(); + promptBarrier.RemoveParticipants(promptBarrier.ParticipantsRemaining); + }; + timer.Start(); + Task promptTask = new Task(() => + { + Self.PromptForInput(new PromptRequestedEventArgs() + { + Description = description, + Text = text, + Id = id, + Callback = (newText) => + { + tokenSource.Token.ThrowIfCancellationRequested(); + text = newText; + timer.Dispose(); + promptBarrier.RemoveParticipants(promptBarrier.ParticipantsRemaining); + } + }); + }, tokenSource.Token); + promptTask.Start(); + promptBarrier.SignalAndWait(); + return text; + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenSetValue.cs b/ReshaperCore/Rules/Thens/ThenSetValue.cs new file mode 100644 index 0000000..8dda0ee --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenSetValue.cs @@ -0,0 +1,46 @@ +using ReshaperCore.Messages; +using ReshaperCore.Messages.Entities; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenSetValue : ThenSet + { + private MessageValueHandler messageValueHandler; + + public MessageValue DestinationMessageValue + { + get; + set; + } = MessageValue.DataDirection; + + public VariableString DestinationIdentifier + { + get; + set; + } + + public MessageValueType DestinationMessageValueType + { + get; + set; + } = MessageValueType.Text; + + public ThenSetValue() + { + messageValueHandler = new MessageValueHandler(); + } + + public override ThenResponse Perform(EventInfo eventInfo) + { + string replacementText = GetReplacementValue(eventInfo); + SetValue(eventInfo, replacementText); + return ThenResponse.Continue; + } + + private void SetValue(EventInfo eventInfo, string replacementText) + { + messageValueHandler.SetValue(eventInfo, DestinationMessageValue, DestinationMessageValueType, DestinationIdentifier, replacementText); + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenSetVariable.cs b/ReshaperCore/Rules/Thens/ThenSetVariable.cs new file mode 100644 index 0000000..0c6ced8 --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenSetVariable.cs @@ -0,0 +1,76 @@ +using ReshaperCore.Messages.Entities; +using ReshaperCore.Providers; +using ReshaperCore.Utils.Extensions; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Thens +{ + public class ThenSetVariable : ThenSet + { + + public VariableSource TargetSource + { + get; + set; + } + + public VariableString VariableName + { + get; + set; + } + + public VariableString DestinationIdentifier + { + get; + set; + } + + public MessageValueType DestinationMessageValueType + { + get; + set; + } = MessageValueType.Text; + + public override ThenResponse Perform(EventInfo eventInfo) + { + string replacementText = GetReplacementValue(eventInfo); + SetValue(eventInfo, replacementText); + return ThenResponse.Continue; + } + + private void SetValue(EventInfo eventInfo, string replacementText) + { + Variables variables = null; + switch (TargetSource) + { + case VariableSource.Channel: + variables = eventInfo.Variables; + break; + case VariableSource.Global: + variables = Self.Variables; + break; + } + if (variables != null) + { + IVariable variable = variables.GetOrDefault(VariableName.GetText(eventInfo.Variables)) ?? variables.Add(VariableName.GetText(eventInfo.Variables)); + if (DestinationIdentifier != null && DestinationMessageValueType != MessageValueType.Text && variable?.Value != null) + { + switch (DestinationMessageValueType) + { + case MessageValueType.Json: + variable.Value = variable.Value.SetJsonValue(DestinationIdentifier.GetText(eventInfo.Variables), replacementText); + break; + case MessageValueType.Xml: + variable.Value = variable.Value.SetXmlValue(DestinationIdentifier.GetText(eventInfo.Variables), replacementText); + break; + } + } + else + { + variable.Value = replacementText; + } + } + } + } +} diff --git a/ReshaperCore/Rules/Thens/ThenSkipProcessing.cs b/ReshaperCore/Rules/Thens/ThenSkipProcessing.cs new file mode 100644 index 0000000..b813b4a --- /dev/null +++ b/ReshaperCore/Rules/Thens/ThenSkipProcessing.cs @@ -0,0 +1,11 @@ +namespace ReshaperCore.Rules.Thens +{ + public class ThenSkipProcessing : Then + { + + public override ThenResponse Perform(EventInfo eventInfo) + { + return ThenResponse.BreakRules; + } + } +} diff --git a/ReshaperCore/Rules/Whens/When.cs b/ReshaperCore/Rules/Whens/When.cs new file mode 100644 index 0000000..aed9ffa --- /dev/null +++ b/ReshaperCore/Rules/Whens/When.cs @@ -0,0 +1,20 @@ +namespace ReshaperCore.Rules.Whens +{ + public abstract class When : IRuleOperation + { + + public bool Negate + { + set; + get; + } + + public bool UseOrCondition + { + set; + get; + } + + public abstract bool IsMatch(EventInfo eventInfo); + } +} diff --git a/ReshaperCore/Rules/Whens/WhenEventDirection.cs b/ReshaperCore/Rules/Whens/WhenEventDirection.cs new file mode 100644 index 0000000..11dbeaa --- /dev/null +++ b/ReshaperCore/Rules/Whens/WhenEventDirection.cs @@ -0,0 +1,19 @@ +using ReshaperCore.Messages; + +namespace ReshaperCore.Rules.Whens +{ + public class WhenEventDirection : When + { + + public DataDirection Direction + { + get; + set; + } + + public override bool IsMatch(EventInfo eventInfo) + { + return eventInfo.Direction == Direction; + } + } +} diff --git a/ReshaperCore/Rules/Whens/WhenEventType.cs b/ReshaperCore/Rules/Whens/WhenEventType.cs new file mode 100644 index 0000000..a81a5c0 --- /dev/null +++ b/ReshaperCore/Rules/Whens/WhenEventType.cs @@ -0,0 +1,17 @@ +namespace ReshaperCore.Rules.Whens +{ + public class WhenEventType : When + { + + public EventType Type + { + get; + set; + } + + public override bool IsMatch(EventInfo eventInfo) + { + return eventInfo.Type == Type; + } + } +} diff --git a/ReshaperCore/Rules/Whens/WhenHasEntity.cs b/ReshaperCore/Rules/Whens/WhenHasEntity.cs new file mode 100644 index 0000000..29b4d85 --- /dev/null +++ b/ReshaperCore/Rules/Whens/WhenHasEntity.cs @@ -0,0 +1,117 @@ +using ReshaperCore.Messages; +using ReshaperCore.Messages.Entities; +using ReshaperCore.Messages.Entities.Http; +using ReshaperCore.Utils.Extensions; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Whens +{ + public class WhenHasEntity : When + { + + public MessageValue MessageValue + { + get; + set; + } + + public VariableString Identifier + { + get; + set; + } + + public MessageValueType MessageValueType + { + get; + set; + } = MessageValueType.Text; + + public override bool IsMatch(EventInfo eventInfo) + { + bool matches = false; + switch (MessageValue) + { + case MessageValue.DataDirection: + case MessageValue.LocalAddress: + case MessageValue.LocalPort: + case MessageValue.Protocol: + case MessageValue.SourceRemoteAddress: + case MessageValue.SourceRemotePort: + matches = true; + break; + case MessageValue.DestinationRemoteAddress: + matches = eventInfo.ProxyConnection.HasTargetConnection; + break; + case MessageValue.DestinationRemotePort: + matches = eventInfo.ProxyConnection.HasTargetConnection; + break; + case MessageValue.HttpBody: + { + HttpBody httpBody = (eventInfo.Message as HttpMessage)?.Body; + if (httpBody != null) + { + if (MessageValueType != MessageValueType.Text && Identifier != null) + { + switch (MessageValueType) + { + case MessageValueType.Json: + matches = httpBody.Text.GetJsonValue(Identifier.GetText(eventInfo.Variables)) != null; + break; + case MessageValueType.Xml: + matches = httpBody.Text.GetXmlValue(Identifier.GetText(eventInfo.Variables)) != null; + break; + } + } + else + { + matches = true; + } + } + else + { + matches = false; + } + } + break; + case MessageValue.HttpHeaders: + matches = ((eventInfo.Message as HttpMessage)?.Headers?.Count ?? 0) > 0; + break; + case MessageValue.HttpHeader: + matches = (eventInfo.Message as HttpMessage)?.Headers.Contains(Identifier.GetText(eventInfo.Variables)) ?? false; + break; + case MessageValue.HttpMethod: + matches = !string.IsNullOrEmpty(((eventInfo.Message as HttpMessage)?.StatusLine as HttpRequestStatusLine)?.Method); + break; + case MessageValue.HttpRequestUri: + matches = !string.IsNullOrEmpty(((eventInfo.Message as HttpMessage)?.StatusLine as HttpRequestStatusLine)?.Uri); + break; + case MessageValue.HttpVersion: + HttpStatusLine requestStatusLine = (eventInfo.Message as HttpMessage)?.StatusLine; + if (requestStatusLine is HttpRequestStatusLine) + { + matches = !string.IsNullOrEmpty((requestStatusLine as HttpRequestStatusLine)?.Version); + } + else + { + matches = !string.IsNullOrEmpty((requestStatusLine as HttpResponseStatusLine)?.Version); + } + break; + case MessageValue.HttpStatusLine: + matches = (eventInfo.Message as HttpMessage)?.StatusLine != null; + break; + case MessageValue.HttpStatusCode: + matches = ((eventInfo.Message as HttpMessage)?.StatusLine as HttpResponseStatusLine)?.StatusCode != null; + break; + case MessageValue.HttpStatusMessage: + matches = !string.IsNullOrEmpty(((eventInfo.Message as HttpMessage)?.StatusLine as HttpResponseStatusLine)?.StatusMessage); + break; + case MessageValue.Message: + matches = !string.IsNullOrEmpty(eventInfo.Message?.ToString()); + break; + } + + return matches; + } + } +} diff --git a/ReshaperCore/Rules/Whens/WhenIsDelimited.cs b/ReshaperCore/Rules/Whens/WhenIsDelimited.cs new file mode 100644 index 0000000..cd0e5b2 --- /dev/null +++ b/ReshaperCore/Rules/Whens/WhenIsDelimited.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReshaperCore.Rules.Whens +{ + public class WhenIsDelimited : When + { + + public override bool IsMatch(EventInfo eventInfo) + { + return eventInfo.Message?.Complete ?? false; + } + } +} diff --git a/ReshaperCore/Rules/Whens/WhenIsSystemProxy.cs b/ReshaperCore/Rules/Whens/WhenIsSystemProxy.cs new file mode 100644 index 0000000..f668db9 --- /dev/null +++ b/ReshaperCore/Rules/Whens/WhenIsSystemProxy.cs @@ -0,0 +1,10 @@ +namespace ReshaperCore.Rules.Whens +{ + public class WhenIsSystemProxy : When + { + public override bool IsMatch(EventInfo eventInfo) + { + return eventInfo.ProxyConnection.Host.IsRegisted(); + } + } +} diff --git a/ReshaperCore/Rules/Whens/WhenMatchesText.cs b/ReshaperCore/Rules/Whens/WhenMatchesText.cs new file mode 100644 index 0000000..06ba18d --- /dev/null +++ b/ReshaperCore/Rules/Whens/WhenMatchesText.cs @@ -0,0 +1,117 @@ +using System; +using System.Text.RegularExpressions; +using ReshaperCore.Messages; +using ReshaperCore.Messages.Entities; +using ReshaperCore.Utils.Extensions; +using ReshaperCore.Vars; + +namespace ReshaperCore.Rules.Whens +{ + public class WhenMatchesText : When + { + private MessageValueHandler _messageValueRetriever = new MessageValueHandler(); + + public VariableString Identifier + { + get; + set; + } + public bool UseMessageValue + { + get; + set; + } + + public MessageValue MessageValue + { + get; + set; + } + + public MessageValueType MessageValueType + { + get; + set; + } = MessageValueType.Text; + + public MatchType MatchType + { + get; + set; + } + + public VariableString RegexPattern + { + get; + set; + } + + public VariableString SourceText + { + get; + set; + } + + public VariableString MatchText + { + get; + set; + } + + public override bool IsMatch(EventInfo eventInfo) + { + bool isMatch = false; + string sourceText = null; + if (UseMessageValue) + { + sourceText = _messageValueRetriever.GetValue(eventInfo, MessageValue, MessageValueType, Identifier); + } + else + { + if (MessageValueType != MessageValueType.Text && Identifier != null) + { + switch (MessageValueType) + { + case MessageValueType.Json: + sourceText = SourceText.GetText(eventInfo.Variables).GetJsonValue(Identifier.GetText(eventInfo.Variables)) ?? string.Empty; + break; + case MessageValueType.Xml: + sourceText = SourceText.GetText(eventInfo.Variables).GetXmlValue(Identifier.GetText(eventInfo.Variables)) ?? string.Empty; + break; + } + } + else + { + sourceText = SourceText.GetText(eventInfo.Variables); + } + } + string matchText = MatchText.GetText(eventInfo.Variables); + + switch (MatchType) + { + case MatchType.BeginsWith: + isMatch = sourceText.StartsWith(matchText); + break; + case MatchType.EndsWith: + isMatch = sourceText.EndsWith(matchText); + break; + case MatchType.Contains: + isMatch = sourceText.Contains(matchText); + break; + case MatchType.Equals: + isMatch = sourceText == matchText; + break; + case MatchType.Regex: + try + { + isMatch = Regex.IsMatch(sourceText, matchText); + } + catch (Exception) + { + } + break; + } + return isMatch; + } + } +} diff --git a/ReshaperCore/Rules/Whens/WhenProxyType.cs b/ReshaperCore/Rules/Whens/WhenProxyType.cs new file mode 100644 index 0000000..506bd71 --- /dev/null +++ b/ReshaperCore/Rules/Whens/WhenProxyType.cs @@ -0,0 +1,20 @@ +using ReshaperCore.Proxies; + +namespace ReshaperCore.Rules.Whens +{ + public class WhenProxyType : When + { + + + public ProxyDataType ProxyType + { + get; + set; + } + + public override bool IsMatch(EventInfo eventInfo) + { + return ProxyType == eventInfo.ProxyConnection.ProxyInfo.DataType; + } + } +} diff --git a/ReshaperCore/Self.cs b/ReshaperCore/Self.cs new file mode 100644 index 0000000..73e2326 --- /dev/null +++ b/ReshaperCore/Self.cs @@ -0,0 +1,55 @@ +using ReshaperCore.Rules; +using ReshaperCore.Settings; +using ReshaperCore.Vars; + +namespace ReshaperCore +{ + public class Self : ISelf + { + private static readonly string _globalVarPath = $@"{SettingsStore.StoragePath}/GlobalVariables.json"; + + public event NewEventBroadcastedHandler NewEventBroadcasted; + public event PromptCanceledHandler PromptCanceled; + public event PromptRequestedHandler PromptRequested; + + public virtual Variables Variables + { + private set; + get; + } = new Variables() { PersistPath = _globalVarPath }; + + public virtual void BroadcastEvent(EventInfo eventInfo) + { + if (NewEventBroadcasted != null) + { + NewEventBroadcasted(eventInfo); + } + } + + public virtual void Init() + { + Variables.LoadPersistables(); + } + + public virtual void Shutdown() + { + Variables.SavePersistables(); + } + + public void PromptForInput(PromptRequestedEventArgs args) + { + if (PromptRequested != null) + { + PromptRequested(args); + } + } + + public void CancelPrompt(object id) + { + if (PromptCanceled != null) + { + PromptCanceled(id); + } + } + } +} diff --git a/ReshaperCore/Settings/GeneralSettings.cs b/ReshaperCore/Settings/GeneralSettings.cs new file mode 100644 index 0000000..5c32e06 --- /dev/null +++ b/ReshaperCore/Settings/GeneralSettings.cs @@ -0,0 +1,60 @@ +using Newtonsoft.Json; + +namespace ReshaperCore.Settings +{ + [JsonObject("GeneralSettings")] + public class GeneralSettings : SettingsStore, IGeneralSettings + { + private bool? _autoUpdateContentLength; + private bool? _ignoreContentLength; + + public bool AutoUpdateContentLength + { + get + { + if (_autoUpdateContentLength == null) + { + _autoUpdateContentLength = GetValue(nameof(AutoUpdateContentLength)); + } + return _autoUpdateContentLength.Value; + } + set + { + _autoUpdateContentLength = value; + SetValue(nameof(AutoUpdateContentLength), value); + } + } + + public bool IgnoreContentLength + { + get + { + if (_ignoreContentLength == null) + { + _ignoreContentLength = GetValue(nameof(IgnoreContentLength)); + } + return _ignoreContentLength.Value; + } + set + { + _ignoreContentLength = value; + SetValue(nameof(IgnoreContentLength), value); + } + } + + protected override T GetDefaultValue(string propertyName) + { + object returnVal = null; + switch (propertyName) + { + case "AutoUpdateContentLength": + returnVal = true; + break; + case "IgnoreContentLength": + returnVal = false; + break; + } + return (returnVal == null) ? default(T) : (T)returnVal; + } + } +} diff --git a/ReshaperCore/Settings/IGeneralSettings.cs b/ReshaperCore/Settings/IGeneralSettings.cs new file mode 100644 index 0000000..2968332 --- /dev/null +++ b/ReshaperCore/Settings/IGeneralSettings.cs @@ -0,0 +1,10 @@ +using System.ComponentModel; + +namespace ReshaperCore.Settings +{ + public interface IGeneralSettings : INotifyPropertyChanged + { + bool AutoUpdateContentLength { get; set; } + bool IgnoreContentLength { get; set; } + } +} \ No newline at end of file diff --git a/ReshaperCore/Settings/SettingsStore.cs b/ReshaperCore/Settings/SettingsStore.cs new file mode 100644 index 0000000..e6cddf1 --- /dev/null +++ b/ReshaperCore/Settings/SettingsStore.cs @@ -0,0 +1,100 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using ReshaperCore.Utils; + +namespace ReshaperCore.Settings +{ + public abstract class SettingsStore : INotifyPropertyChanged + { + private string _filePath; + private JObject _jsonModel = null; + private bool _initializing = false; + + public static string StoragePath + { + private set; + get; + } = $@"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/Synfron/Reshaper"; + + public event PropertyChangedEventHandler PropertyChanged; + + public T GetValue(string propertyName) + { + T returnVal = default(T); + if (_jsonModel == null) + { + SetJsonModel(); + } + JToken value = null; + if (_jsonModel.TryGetValue(propertyName, out value)) + { + returnVal = value.ToObject(); + } + else + { + returnVal = GetDefaultValue(propertyName); + } + return returnVal; + } + + public void SetValue(string propertyName, T value) + { + if (_jsonModel == null) + { + SetJsonModel(); + } + _jsonModel[propertyName] = JToken.FromObject(value); + if (!_initializing) + { + SaveSettings(); + OnPropertyChanged(propertyName); + } + } + + private void SetJsonModel() + { + Type type = GetType(); + JsonObjectAttribute attr = type.GetCustomAttribute(); + if (!string.IsNullOrEmpty(attr?.Id)) + { + string filename = attr.Id; + if (!filename.EndsWith(".json")) + { + filename += ".json"; + } + this._filePath = $@"{StoragePath}/{filename}"; + if (File.Exists(this._filePath)) + { + string fileText = File.ReadAllText(this._filePath); + _jsonModel = JObject.Parse(fileText); + } + } + if (_jsonModel == null) + { + _jsonModel = new JObject(); + SaveSettings(); + } + } + + private void SaveSettings() + { + FileInfo file = new FileInfo(_filePath); + file.Directory.Create(); + File.WriteAllText(_filePath, Serializer.Serialize(_jsonModel)); + } + + protected void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + protected abstract T GetDefaultValue(string propertyName); + } +} diff --git a/ReshaperCore/Utils/Deque.cs b/ReshaperCore/Utils/Deque.cs new file mode 100644 index 0000000..e72b646 --- /dev/null +++ b/ReshaperCore/Utils/Deque.cs @@ -0,0 +1,378 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace ReshaperCore.Utils +{ + public class Deque : IList, ICollection + { + private int _firstIndex = 0; + private int _lastIndex = 0; + private T[] _storage; + + public Deque(int internalSize) + { + _storage = new T[internalSize]; + } + + public Deque() : this(50) + { + + } + + public T this[int index] + { + get + { + if (index >= 0 && index < Count) + { + return _storage[GetStorageIndex(index)]; + } + else + { + throw new IndexOutOfRangeException(index.ToString()); + } + } + set + { + if (index >= 0 && index < Count) + { + _storage[GetStorageIndex(index)] = value; + } + else + { + throw new IndexOutOfRangeException(index.ToString()); + } + } + } + + public int Capacity + { + get + { + return _storage.Length; + } + } + + public int Count + { + get; + private set; + } + + public object SyncRoot + { + get + { + return this; + } + } + + public bool IsSynchronized + { + get + { + return false; + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + private int GetStorageIndex(int index) + { + return Mod(_firstIndex + index, Capacity); + } + + public void Clear() + { + _firstIndex = 0; + _lastIndex = 0; + Count = 0; + _storage = new T[Capacity]; + } + + public void AddFirst(T item) + { + int index = Mod(_firstIndex - 1, Capacity); + if (index == _lastIndex) + { + Resize(); + index = Mod(_firstIndex - 1, Capacity); + } + _storage[index] = item; + _firstIndex = index; + if (Count == 0) + { + _lastIndex = _firstIndex; + } + Count++; + } + + public void AddLast(T item) + { + int index = Mod(_lastIndex + 1, Capacity); + if (index == _firstIndex) + { + Resize(); + index = Mod(_lastIndex + 1, Capacity); + } + _storage[index] = item; + _lastIndex = index; + if (Count == 0) + { + _firstIndex = _lastIndex; + } + Count++; + } + + private void Resize() + { + T[] newStorage = new T[_storage.Length * 2]; + if (_firstIndex <= _lastIndex) + { + Array.Copy(_storage, _firstIndex, newStorage, 0, _lastIndex - _firstIndex + 1); + } + else + { + Array.Copy(_storage, _firstIndex, newStorage, 0, _storage.Length - _firstIndex); + Array.Copy(_storage, 0, newStorage, _storage.Length - _firstIndex, _lastIndex + 1); + } + _firstIndex = 0; + _lastIndex = _storage.Length - 1; + _storage = newStorage; + } + + public T TakeFirst() + { + if (Count > 0) + { + Count--; + T item = _storage[_firstIndex]; + _storage[_firstIndex] = default(T); + if (Count == 0) + { + _lastIndex = _firstIndex; + } + else + { + _firstIndex = Mod(_firstIndex + 1, Capacity); + } + return item; + } + return default(T); + } + + public T TakeLast() + { + if (Count > 0) + { + Count--; + T item = _storage[_lastIndex]; + _storage[_lastIndex] = default(T); + if (Count == 0) + { + _firstIndex = _lastIndex; + } + else + { + _lastIndex = Mod(_lastIndex - 1, Capacity); + } + return item; + } + return default(T); + } + private int Mod(int number, int mod) + { + return (int)Mod((double)number, (double)mod); + } + + private double Mod(double number, double mod) + { + return number - (Math.Floor(number / mod) * mod); + } + + public IEnumerator GetEnumerator() + { + return new DequeEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new DequeEnumerator(this); + } + + public void CopyTo(Array array, int index) + { + if (_firstIndex <= _lastIndex) + { + Array.Copy(_storage, _firstIndex, array, index, _lastIndex - _firstIndex + 1); + } + else + { + Array.Copy(_storage, _firstIndex, array, index, _storage.Length - _firstIndex); + Array.Copy(_storage, 0, array, index + _storage.Length - _firstIndex, _lastIndex + 1); + } + } + + public int IndexOf(T item) + { + int indexOf = -1; + for (int index = 0; index < _storage.Length; index++) + { + if (this[index]?.Equals(item) ?? false) + { + indexOf = index; + break; + } + } + return indexOf; + } + + public void Insert(int index, T item) + { + if (index >= 0 && index <= Capacity) + { + if (index == 0) + { + AddFirst(item); + } + else if (index == Count - 1) + { + AddLast(item); + } + else + { + T[] newStorage = new T[_storage.Length + 1]; + int storageIndex = GetStorageIndex(index); + Array.Copy(_storage, newStorage, storageIndex); + Array.Copy(_storage, storageIndex, newStorage, storageIndex + 1, Capacity - storageIndex); + newStorage[storageIndex] = item; + _storage = newStorage; + } + } + else + { + throw new ArgumentOutOfRangeException($"Index {index} is out of range (0-{Capacity - 1})"); + } + } + + public void RemoveAt(int index) + { + if (index >= 0 && index < Capacity) + { + if (index == 0) + { + TakeFirst(); + } + else if (index == Count - 1) + { + TakeLast(); + } + else + { + T[] newStorage = new T[_storage.Length]; + int storageIndex = GetStorageIndex(index); + Array.Copy(_storage, newStorage, storageIndex); + Array.Copy(_storage, storageIndex + 1, newStorage, storageIndex, Capacity - (storageIndex + 1)); + _storage = newStorage; + } + } + else + { + throw new ArgumentOutOfRangeException($"Index {index} is out of range (0-{Capacity - 1})"); + } + } + + public void Add(T item) + { + AddLast(item); + } + + public bool Contains(T item) + { + + return IndexOf(item) != -1; + } + + public void CopyTo(T[] array, int index) + { + if (_firstIndex <= _lastIndex) + { + Array.Copy(_storage, _firstIndex, array, index, _lastIndex - _firstIndex + 1); + } + else + { + Array.Copy(_storage, _firstIndex, array, index, _storage.Length - _firstIndex); + Array.Copy(_storage, 0, array, index + _storage.Length - _firstIndex, _lastIndex + 1); + } + } + + public bool Remove(T item) + { + int indexOf = IndexOf(item); + if (indexOf != -1) + { + RemoveAt(indexOf); + return true; + } + else + { + return false; + } + } + + private class DequeEnumerator : IEnumerator + { + private int currentIndex; + private Deque deque; + + public DequeEnumerator(Deque deque) + { + this.deque = deque; + this.currentIndex = 0; + } + + public object Current + { + get + { + return deque[currentIndex]; + } + } + + I IEnumerator.Current + { + get + { + return deque[currentIndex]; + } + } + + public void Dispose() + { + deque = null; + } + + public bool MoveNext() + { + bool moved = currentIndex < deque.Count; + if (moved) + { + currentIndex++; + } + return moved; + } + + public void Reset() + { + currentIndex = 0; + } + } + } +} diff --git a/ReshaperCore/Utils/EncodingJsonConvertercs.cs b/ReshaperCore/Utils/EncodingJsonConvertercs.cs new file mode 100644 index 0000000..f077896 --- /dev/null +++ b/ReshaperCore/Utils/EncodingJsonConvertercs.cs @@ -0,0 +1,45 @@ +using System; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace ReshaperCore.Utils +{ + public class EncodingJsonConvertercs : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Encoding); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + Encoding encoding = null; + try + { + JObject jsonObj = JObject.Load(reader); + JToken value = null; + if (jsonObj.TryGetValue("Encoding", out value)) + { + string rawString = value.Value(); + encoding = Encoding.GetEncoding(rawString); + } + } + catch (Exception) + { + } + return encoding; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + Encoding encoding = value as Encoding; + writer.WriteStartObject(); + writer.WritePropertyName("$type"); + writer.WriteValue("System.Text.Encoding, mscorlib"); + writer.WritePropertyName("Encoding"); + writer.WriteValue(encoding.WebName); + writer.WriteEndObject(); + } + } +} diff --git a/ReshaperCore/Utils/Extensions/CollectionExtensions.cs b/ReshaperCore/Utils/Extensions/CollectionExtensions.cs new file mode 100644 index 0000000..10724fb --- /dev/null +++ b/ReshaperCore/Utils/Extensions/CollectionExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ReshaperCore.Utils.Extensions +{ + public static class CollectionExtensions + { + public static T GetElementAtOrDefault(this IList list, int index) + { + return (list.Count > index) ? list[index] : default(T); + } + + public static T[] Combine(this T[] array1, T[] array2) + { + T[] newArray = new T[array1.Length + array2.Length]; + Buffer.BlockCopy(array1, 0, newArray, 0, array1.Length); + Buffer.BlockCopy(array2, 0, newArray, array1.Length, array2.Length); + return newArray; + } + + public static T[] Combine(this T[] array1, params T[][] otherArrays) + { + int combinedSize = otherArrays.Sum(array => array.Length); + T[] newArray = new T[array1.Length + combinedSize]; + Buffer.BlockCopy(array1, 0, newArray, 0, array1.Length); + int combinedArrayIndex = array1.Length; + for (int otherArrayIndex = 0; otherArrayIndex < otherArrays.Length; otherArrayIndex++) + { + T[] otherArray = otherArrays[otherArrayIndex]; + Buffer.BlockCopy(otherArray, 0, newArray, combinedArrayIndex, otherArray.Length); + combinedArrayIndex += otherArray.Length; + } + return newArray; + } + + public static T[] Combine(this IList arrays) + { + int combinedSize = arrays.Sum(array => array.Length); + T[] combinedArray = new T[combinedSize]; + int combinedArrayIndex = 0; + for (int arraysIndex = 0; arraysIndex < arrays.Count; arraysIndex++) + { + T[] array = arrays[arraysIndex]; + Buffer.BlockCopy(array, 0, combinedArray, combinedArrayIndex, array.Length); + combinedArrayIndex += array.Length; + } + return combinedArray; + } + } +} diff --git a/ReshaperCore/Utils/Extensions/MefExtensions.cs b/ReshaperCore/Utils/Extensions/MefExtensions.cs new file mode 100644 index 0000000..5f2caba --- /dev/null +++ b/ReshaperCore/Utils/Extensions/MefExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition.Hosting; +using System.ComponentModel.Composition.Primitives; +using System.ComponentModel.Composition.ReflectionModel; +using System.Linq; + +namespace ReshaperCore.Utils.Extensions +{ + public static class MefExtensions + { + + public static IEnumerable GetExportsTypes(this CompositionContainer container, params Type[] types) + { + IQueryable parts = + container.Catalog.Parts.Concat(container.Providers.OfType().SelectMany(provider => provider.Catalog.Parts)); + + return parts.Select(part => ReflectionModelServices.GetPartType(part).Value).Where(type => types.All(expectedType => expectedType.IsAssignableFrom(type))); + } + + public static IEnumerable GetDistinctExportsTypes(this CompositionContainer container, params Type[] types) + { + IQueryable parts = + container.Catalog.Parts.Concat(container.Providers.OfType().SelectMany(provider => provider.Catalog.Parts)).GroupBy(part => part.ExportDefinitions.FirstOrDefault()).Select(group => group.First()); + + return parts.Select(part => ReflectionModelServices.GetPartType(part).Value).Where(type => types.All(expectedType => expectedType.IsAssignableFrom(type))); + } + + public static IEnumerable GetExportedTypes(this CompositionContainer container) + { + IQueryable parts = container.Catalog.Parts.Union(container.Providers.OfType().SelectMany(provider => provider.Catalog.Parts)); + return parts.Select(part => ReflectionModelServices.GetPartType(part).Value); + } + } +} diff --git a/ReshaperCore/Utils/Extensions/StringExtensions.cs b/ReshaperCore/Utils/Extensions/StringExtensions.cs new file mode 100644 index 0000000..dcb2e98 --- /dev/null +++ b/ReshaperCore/Utils/Extensions/StringExtensions.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using Newtonsoft.Json.Linq; + +namespace ReshaperCore.Utils.Extensions +{ + public static class StringExtensions + { + public static bool StartsWith(this string text, string subString, int startIndex) + { + bool found = false; + for (int charIndex = startIndex, subCharIndex = 0; subCharIndex < subString.Length && charIndex < text.Length; charIndex++, subCharIndex++) + { + if (text[charIndex] != subString[subCharIndex]) + { + break; + } + else if (subCharIndex == subString.Length - 1) + { + found = true; + } + } + return found; + } + + public static bool StartsWith(this string text, IEnumerable subStrings, out string foundString) + { + bool found = false; + foundString = null; + foreach (string subString in subStrings) + { + if (text.StartsWith(subString)) + { + found = true; + foundString = subString; + break; + } + } + return found; + } + + public static bool EndsWith(this string text, IEnumerable subStrings, out string foundString) + { + bool found = false; + foundString = null; + foreach (string subString in subStrings) + { + if (text.EndsWith(subString)) + { + found = true; + foundString = subString; + break; + } + } + return found; + } + + public static List> SplitWithDelimiters(this string text, List delimiters) + { + List> splitText = new List>(); + int beginningIndex = 0; + + for (int charIndex = beginningIndex; charIndex < text.Length; charIndex++) + { + foreach (string delimiter in delimiters) + { + if (delimiter == string.Empty) + { + if (text.Length - beginningIndex >= 0) + { + splitText.Add(new Tuple(text.Substring(beginningIndex, text.Length - beginningIndex), delimiter)); + } + beginningIndex = text.Length; + break; + } + else if (text.StartsWith(delimiter, charIndex)) + { + if (charIndex - beginningIndex >= 0) + { + splitText.Add(new Tuple(text.Substring(beginningIndex, charIndex - beginningIndex), delimiter)); + } + beginningIndex = charIndex + delimiter.Length; + break; + } + } + if (charIndex == text.Length - 1 && charIndex - beginningIndex >= 0) + { + splitText.Add(new Tuple(text.Substring(beginningIndex, charIndex - beginningIndex + 1), null)); + } + } + return splitText; + } + + public static string GetJsonValue(this string json, string jsonPath) + { + try + { + JToken jToken = JToken.Parse(json); + json = jToken.SelectToken(jsonPath).ToString(); + } + catch + { + + } + return json; + } + + public static string SetJsonValue(this string json, string jsonPath, string value) + { + try + { + JToken jToken = JToken.Parse(json); + double temp; + if (!(value.StartsWith("[") || value.StartsWith("{") || value.StartsWith("'") || value.StartsWith("\"") || value == "null" || double.TryParse(value, out temp))) + { + value = $"'{value}'"; + } + jToken.SelectToken(jsonPath).Replace(JToken.Parse(value)); + json = jToken.ToString(); + } + catch + { + + } + return json; + } + + public static string GetXmlValue(this string xml, string xpath) + { + try + { + XmlDocument document = new XmlDocument(); + document.LoadXml(xml); + xml = document.SelectSingleNode(xpath).InnerXml; + } + catch + { + + } + return xml; + } + + public static string SetXmlValue(this string xml, string xpath, string value) + { + try + { + XmlDocument document = new XmlDocument(); + document.LoadXml(xml); + document.SelectSingleNode(xpath).Value = value; + xml = document.OuterXml; + } + catch + { + + } + return xml; + } + + public static string TrimEnd(this string str, string endingStr) + { + if (str.EndsWith(endingStr)) + { + str = str.Substring(0, str.LastIndexOf(endingStr)); + } + return str; + } + } +} diff --git a/ReshaperCore/Utils/Extensions/TypeExtensions.cs b/ReshaperCore/Utils/Extensions/TypeExtensions.cs new file mode 100644 index 0000000..7fb79fd --- /dev/null +++ b/ReshaperCore/Utils/Extensions/TypeExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace ReshaperCore.Utils.Extensions +{ + public static class TypeExtensions + { + public static IEnumerable GetAttributes(this IEnumerable types) where T : Attribute + { + return types.Select(type => type.GetCustomAttribute()).Where(attr => attr != null); + } + } +} diff --git a/ReshaperCore/Utils/Log.cs b/ReshaperCore/Utils/Log.cs new file mode 100644 index 0000000..488d07d --- /dev/null +++ b/ReshaperCore/Utils/Log.cs @@ -0,0 +1,43 @@ +using System; + +namespace ReshaperCore.Utils +{ + public class Log + { + + public static event ErrorLoggedEventHandler ErrorLogged; + public delegate void ErrorLoggedEventHandler(Exception e, String info, String extraInfo); + public static event InfoLoggedEventHandler InfoLogged; + public delegate void InfoLoggedEventHandler(String info, String extraInfo); + + public static string LogText + { + get; + set; + } = string.Empty; + + public static void LogError(Exception e, String info = "", String extraInfo = "") + { + LogText += $"Error - {info}:\n{extraInfo}\n\n"; + try + { + ErrorLogged?.Invoke(e, info, extraInfo); + } + catch + { + } + } + + public static void LogInfo(String info = "", String extraInfo = "") + { + LogText += $"{info}:\n{extraInfo}\n\n"; + try + { + InfoLogged?.Invoke(info, extraInfo); + } + catch + { + } + } + } +} diff --git a/ReshaperCore/Utils/MessageQueue.cs b/ReshaperCore/Utils/MessageQueue.cs new file mode 100644 index 0000000..f4b63b3 --- /dev/null +++ b/ReshaperCore/Utils/MessageQueue.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; + +namespace ReshaperCore.Utils +{ + public class MessageQueue : Subject + { + private Deque _deque; + + public MessageQueue() : base() + { + _deque = new Deque(); + } + + public virtual void AddFirst(IList messages) + { + lock (this) + { + try + { + for (int index = messages.Count - 1; index >= 0; index--) + { + _deque.AddFirst(messages[index]); + } + } + catch (Exception ex) + { + Log.LogError(ex, "Core Error: Could not add message queue item"); + } + } + + Notify(); + } + + public virtual void AddFirst(T message) + { + lock (this) + { + try + { + _deque.AddFirst(message); + } + catch (Exception ex) + { + Log.LogError(ex, "Core Error: Could not add message queue item"); + } + } + + Notify(); + } + + public virtual void AddLast(T message) + { + lock (this) + { + try + { + _deque.AddLast(message); + } + catch (Exception ex) + { + Log.LogError(ex, "Core Error: Could not add message queue item"); + } + } + Notify(); + } + + public void AddLast(IList messages) + { + lock (this) + { + try + { + foreach (T message in messages) + { + _deque.AddLast(message); + } + } + catch (Exception ex) + { + Log.LogError(ex, "Core Error: Could not add message queue item"); + } + } + Notify(); + } + + public void Clear() + { + lock (this) + { + _deque.Clear(); + } + } + + public T TakeFirst() + { + lock (this) + { + T message = default(T); + try + { + if (_deque.Count > 0) + { + message = _deque.TakeFirst(); + } + } + catch (Exception ex) + { + Log.LogError(ex, "Core Error: Could not remove message queue item"); + } + return message; + } + } + + public bool IsEmpty() + { + return (_deque.Count == 0); + } + } +} diff --git a/ReshaperCore/Utils/ObservableEntity.cs b/ReshaperCore/Utils/ObservableEntity.cs new file mode 100644 index 0000000..f472e57 --- /dev/null +++ b/ReshaperCore/Utils/ObservableEntity.cs @@ -0,0 +1,31 @@ +using System.ComponentModel; + +namespace ReshaperCore.Utils +{ + public class ObservableEntity + { + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void RegisterOnEntityChanges(string propertyName, T newValue, T oldValue) where T : ObservableEntity + { + PropertyChangedEventHandler entityChangedEvent = (sender, e) => + { + OnPropertyChanged(propertyName); + }; + if (newValue != null) + { + newValue.PropertyChanged += entityChangedEvent; + } + if (oldValue != null) + { + oldValue.PropertyChanged -= entityChangedEvent; + } + } + } +} diff --git a/ReshaperCore/Utils/Observer.cs b/ReshaperCore/Utils/Observer.cs new file mode 100644 index 0000000..2723827 --- /dev/null +++ b/ReshaperCore/Utils/Observer.cs @@ -0,0 +1,26 @@ +using System; + +namespace ReshaperCore.Utils +{ + public abstract class Observer : IDisposable + { + + protected object ObserverKey + { + get; + private set; + } = new object(); + + protected bool ObserverWorking + { + get; + set; + } = false; + + public abstract void OnUpdate(); + + public void Dispose() + { + } + } +} diff --git a/ReshaperCore/Utils/SerializationBinder.cs b/ReshaperCore/Utils/SerializationBinder.cs new file mode 100644 index 0000000..dd69e27 --- /dev/null +++ b/ReshaperCore/Utils/SerializationBinder.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.Composition.Hosting; +using System.Linq; +using Newtonsoft.Json.Serialization; +using ReshaperCore.Providers; +using ReshaperCore.Utils.Extensions; + +namespace ReshaperCore.Utils +{ + public class SerializationBinder : DefaultSerializationBinder + { + public CompositionContainer Container { get; set; } = new CompositionContainerProvider().GetInstance(); + + public override Type BindToType(string assemblyName, string typeName) + { + Type type = null; + try + { + type = base.BindToType(assemblyName, typeName); + } + catch + { + type = Container.GetExportedTypes().Select(exportedType => exportedType.Assembly).Distinct().Select(assembly => assembly.DefinedTypes.FirstOrDefault(exportedType => exportedType.FullName == typeName && assembly.GetName().Name == assemblyName)).FirstOrDefault(); + if (type == null) + { + throw; + } + } + return type; + } + } +} diff --git a/ReshaperCore/Utils/Serializer.cs b/ReshaperCore/Utils/Serializer.cs new file mode 100644 index 0000000..119bf0f --- /dev/null +++ b/ReshaperCore/Utils/Serializer.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using ReshaperCore.Vars; + +namespace ReshaperCore.Utils +{ + public class Serializer + { + public static String Serialize(Object o) + { + return JsonConvert.SerializeObject(o, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Objects, + Converters = new JsonConverter[] { new VariableStringJsonConverter(), new EncodingJsonConvertercs() } + }); + } + + public static void SerializeToFile(string filePath, object o) + { + FileInfo file = new FileInfo(filePath); + file.Directory.Create(); + File.WriteAllText(filePath, Serializer.Serialize(o)); + } + + public static T Deserialize(String serialized) + { + return JsonConvert.DeserializeObject(serialized, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Objects, + Converters = new JsonConverter[] { new VariableStringJsonConverter(), new EncodingJsonConvertercs() }, + Binder = new SerializationBinder() + }); + } + + public static T DeserializeFromFile(string filePath) + { + T obj = default(T); + if (File.Exists(filePath)) + { + string fileText = File.ReadAllText(filePath); + obj = Deserialize(fileText); + } + return obj; + } + } +} diff --git a/ReshaperCore/Utils/Singleton.cs b/ReshaperCore/Utils/Singleton.cs new file mode 100644 index 0000000..1250c92 --- /dev/null +++ b/ReshaperCore/Utils/Singleton.cs @@ -0,0 +1,12 @@ +namespace ReshaperCore.Utils +{ + public static class Singleton + { + public static T Instance + { + get; + set; + } + + } +} diff --git a/ReshaperCore/Utils/Subject.cs b/ReshaperCore/Utils/Subject.cs new file mode 100644 index 0000000..989a0e0 --- /dev/null +++ b/ReshaperCore/Utils/Subject.cs @@ -0,0 +1,37 @@ +namespace ReshaperCore.Utils +{ + public class Subject + { + private Observer _observer; + + protected object SubjectKey + { + private set; + get; + } = new object(); + + protected bool SubjectWorking + { + get; + set; + } = false; + + public void Notify() + { + if (_observer != null) + { + _observer.OnUpdate(); + } + } + + public void SetObserver(Observer observer) + { + this._observer = observer; + } + + public void RemoveObserver() + { + this._observer = null; + } + } +} diff --git a/ReshaperCore/Vars/IVariable.cs b/ReshaperCore/Vars/IVariable.cs new file mode 100644 index 0000000..a3a103e --- /dev/null +++ b/ReshaperCore/Vars/IVariable.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; + +namespace ReshaperCore.Vars +{ + public interface IVariable : IVariable, INotifyPropertyChanged + { + T Value + { + get; + set; + } + } + + public interface IVariable + { + bool Persistent + { + get; + set; + } + } +} diff --git a/ReshaperCore/Vars/VariableSource.cs b/ReshaperCore/Vars/VariableSource.cs new file mode 100644 index 0000000..6edad8b --- /dev/null +++ b/ReshaperCore/Vars/VariableSource.cs @@ -0,0 +1,8 @@ +namespace ReshaperCore.Vars +{ + public enum VariableSource + { + Channel, + Global + } +} diff --git a/ReshaperCore/Vars/VariableString.cs b/ReshaperCore/Vars/VariableString.cs new file mode 100644 index 0000000..62e9eb9 --- /dev/null +++ b/ReshaperCore/Vars/VariableString.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ReshaperCore.Providers; + +namespace ReshaperCore.Vars +{ + public class VariableString + { + private string _text; + private Tuple[] _variables; + + public ISelf Self { get; set; } = new SelfProvider().GetInstance(); + + public VariableString(string text, params Tuple[] variables) + { + _text = text; + _variables = variables; + } + + public virtual string GetFormattedString() + { + return string.Format(_text, _variables.Select(variable => $"{{{variable.Item1.ToString().ToLower()}:{variable.Item2}}}").ToArray()); + } + + public static VariableString GetAsVariableString(string str, bool requiresParsing = true) + { + str = str.Replace(@"\", @"\\").Replace("{{", @"\{").Replace("}}", @"\}"); + if (requiresParsing) + { + bool inEscape = false; + bool inVar = false; + int subStringStartIndex = 0; + int varIndex = 0; + List> variables = new List>(); + StringBuilder textBuilder = new StringBuilder(); + for (int charIndex = 0; charIndex < str.Length; charIndex++) + { + if (str[charIndex] == '\\') + { + if (inEscape) + { + int subStringSize = charIndex - subStringStartIndex - 1; + if (subStringSize > 0) + { + textBuilder.Append(str.Substring(subStringStartIndex, subStringSize)); + } + subStringStartIndex = charIndex + 1; + inEscape = false; + } + else + { + inEscape = true; + } + } + else if (inEscape) + { + int subStringSize = charIndex - subStringStartIndex - 1; + if (subStringSize > 0) + { + textBuilder.Append(str.Substring(subStringStartIndex, subStringSize).Replace("{", "{{").Replace("}", "}}")); + } + subStringStartIndex = charIndex; + inEscape = false; + } + else + { + if (str[charIndex] == '{') + { + int subStringSize = charIndex - subStringStartIndex - 1; + if (subStringSize > 0) + { + textBuilder.Append(str.Substring(subStringStartIndex, subStringSize)); + } + subStringStartIndex = charIndex + 1; + inVar = true; + } + else if (str[charIndex] == '}') + { + if (!inVar) + { + throw new FormatException("'}' found at invalid location."); + } + else + { + int subStringSize = charIndex - subStringStartIndex; + string varDataStr = str.Substring(subStringStartIndex, subStringSize); + if (!string.IsNullOrEmpty(varDataStr)) + { + string[] varData = varDataStr.Split(new[] { ':' }, 2); + string varName; + VariableSource source = VariableSource.Global; + if (varData.Length > 1) + { + if (!Enum.TryParse(varData[0], true, out source)) + { + throw new FormatException("Unknown variable source."); + } + varName = varData[1]; + } + else + { + varName = varData[0]; + } + variables.Add(new Tuple(source, varName)); + textBuilder.Append($"{{{varIndex++}}}"); + subStringStartIndex = charIndex + 1; + inVar = false; + } + else + { + throw new FormatException("Variable string name cannot be empty."); + } + } + } + } + } + if (inVar) + { + throw new FormatException("Invalid variable string format."); + } + else + { + int subStringSize = str.Length - subStringStartIndex; + if (subStringSize > 0) + { + textBuilder.Append(str.Substring(subStringStartIndex, subStringSize).Replace("{", "{{").Replace("}", "}}")); + } + } + return new VariableString(textBuilder.ToString(), variables.ToArray()); + } + else + { + return new VariableString(str, new Tuple[0]); + } + } + + public virtual int? GetInt(Variables connectionVariables) + { + string text = GetText(connectionVariables); + int? nullableValue = null; + int value; + if (int.TryParse(text, out value)) + { + nullableValue = value; + } + return nullableValue; + } + + public virtual string GetText(Variables connectionVariables) + { + List variableVals = new List(); + foreach (Tuple variable in _variables) + { + string value = null; + switch (variable.Item1) + { + case VariableSource.Global: + value = Self.Variables.GetOrDefault(variable.Item2)?.Value ?? string.Empty; + break; + case VariableSource.Channel: + value = connectionVariables?.GetOrDefault(variable.Item2)?.Value ?? string.Empty; + break; + default: + value = string.Empty; + break; + } + variableVals.Add(value); + } + return string.Format(_text, variableVals.ToArray()); + } + } +} diff --git a/ReshaperCore/Vars/VariableStringJsonConverter.cs b/ReshaperCore/Vars/VariableStringJsonConverter.cs new file mode 100644 index 0000000..a31dd11 --- /dev/null +++ b/ReshaperCore/Vars/VariableStringJsonConverter.cs @@ -0,0 +1,47 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace ReshaperCore.Vars +{ + public class VariableStringJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(VariableString); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + VariableString str = null; + try + { + JObject jsonObj = JObject.Load(reader); + JToken value = null; + if (jsonObj.TryGetValue("FormattedString", out value)) + { + string rawString = value.Value(); + if (rawString != null) + { + str = VariableString.GetAsVariableString(rawString); + } + } + } + catch (Exception) + { + } + return str; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + VariableString varString = value as VariableString; + writer.WriteStartObject(); + writer.WritePropertyName("$type"); + writer.WriteValue("ReshaperCore.Vars.VariableString, ReshaperCore"); + writer.WritePropertyName("FormattedString"); + writer.WriteValue(varString?.GetFormattedString()); + writer.WriteEndObject(); + } + } +} diff --git a/ReshaperCore/Vars/Variables.cs b/ReshaperCore/Vars/Variables.cs new file mode 100644 index 0000000..19931c8 --- /dev/null +++ b/ReshaperCore/Vars/Variables.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using ReshaperCore.Utils; + +namespace ReshaperCore.Vars +{ + public class Variables : INotifyCollectionChanged + { + private ConcurrentDictionary _variables = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public string PersistPath + { + get; + set; + } + + public IEnumerable VariableNames + { + get + { + return _variables.Keys; + } + } + + public void SavePersistables() + { + if (!string.IsNullOrEmpty(PersistPath)) + { + Dictionary persistables = _variables.Where(variable => variable.Value.Persistent && variable.Value is IVariable).ToDictionary(key => key.Key, value => (value.Value as IVariable).Value); + + Serializer.SerializeToFile(PersistPath, persistables); + } + } + + public void LoadPersistables() + { + if (!string.IsNullOrEmpty(PersistPath)) + { + Dictionary persistables = Serializer.DeserializeFromFile>(PersistPath); + if (persistables != null) + { + foreach (KeyValuePair pair in persistables) + { + IVariable variable = Add(pair.Key); + variable.Value = pair.Value; + variable.Persistent = true; + } + } + } + } + + public IVariable Add(String name) + { + IVariable var = new Variable(); + if (!_variables.TryAdd(name, var)) + { + var = null; + } + else + { + OnCollectionChanged(NotifyCollectionChangedAction.Add, name); + } + return var; + } + + private void OnCollectionChanged(NotifyCollectionChangedAction action, string changedItem = null) + { + if (CollectionChanged != null) + { + NotifyCollectionChangedEventArgs args = null; + switch (action) + { + case NotifyCollectionChangedAction.Add: + case NotifyCollectionChangedAction.Remove: + args = new NotifyCollectionChangedEventArgs(action, changedItem); + break; + case NotifyCollectionChangedAction.Reset: + args = new NotifyCollectionChangedEventArgs(action); + break; + } + CollectionChanged(this, args); + } + } + + public IVariable Get(String name) + { + IVariable varObj = null; + _variables.TryGetValue(name, out varObj); + if (varObj == null) + { + throw new ArgumentOutOfRangeException("Variable does not exist."); + } + return (IVariable)varObj; + } + + public IVariable GetOrDefault(String name) + { + IVariable varObj = null; + _variables.TryGetValue(name, out varObj); + return varObj as IVariable; + } + + public bool Has(String name) + { + return GetOrDefault(name) != null; + } + + public bool Remove(String name) + { + IVariable var = null; + bool removed = false; + if (_variables.TryRemove(name, out var)) + { + OnCollectionChanged(NotifyCollectionChangedAction.Remove, name); + removed = true; + } + return removed; + } + + public void Clear() + { + _variables.Clear(); + OnCollectionChanged(NotifyCollectionChangedAction.Reset); + } + + private class Variable : IVariable + { + + private T _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public bool Persistent + { + get; + set; + } + + public T Value + { + get + { + return _value; + } + set + { + _value = value; + OnPropertyChanged(nameof(Value)); + } + } + + public Variable() + { + } + + protected virtual void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } + } +} diff --git a/ReshaperCore/packages.config b/ReshaperCore/packages.config new file mode 100644 index 0000000..7ee8c10 --- /dev/null +++ b/ReshaperCore/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ReshaperScript/Core/Functions/EventFuncs.cs b/ReshaperScript/Core/Functions/EventFuncs.cs new file mode 100644 index 0000000..d1ab025 --- /dev/null +++ b/ReshaperScript/Core/Functions/EventFuncs.cs @@ -0,0 +1,113 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using ReshaperCore.Messages; +using ReshaperCore.Messages.Entities; +using ReshaperCore.Rules.Thens; +using ReshaperCore.Vars; +using EventInfo = ReshaperCore.Rules.EventInfo; + +namespace ReshaperScript.Core.Functions +{ + public class Event + { + private EventInfo eventInfo; + + public Event(EventInfo eventInfo) + { + this.eventInfo = eventInfo; + } + + public string AddMessage(string direction, string messageText, bool insertAtBeginning) + { + ThenAddMessage then = new ThenAddMessage() + { + Direction = (DataDirection)GetStringAsEnum(direction), + MessageText = VariableString.GetAsVariableString(messageText, false), + InsertAtBeginning = insertAtBeginning + }; + return then.Perform(eventInfo).ToString(); + } + + public string Broadcast() + { + ThenBroadcast then = new ThenBroadcast(); + return then.Perform(eventInfo).ToString(); + } + + public string Connect(string destinationHost, int? destinationPort) + { + ThenConnect then = new ThenConnect() + { + DestinationHost = (destinationHost == null) ? null : VariableString.GetAsVariableString(destinationHost.ToString(), false), + DestinationPort = (destinationPort == null) ? null : VariableString.GetAsVariableString(destinationPort.ToString(), false) + }; + return then.Perform(eventInfo).ToString(); + } + + public string DelimitHttp() + { + ThenDelimitHttp then = new ThenDelimitHttp(); + return then.Perform(eventInfo).ToString(); + } + + public string DelimitText(EventInfo eventInfo) + { + ThenDelimitText then = new ThenDelimitText(); + return then.Perform(eventInfo).ToString(); + } + + public string HttpConnect(bool overrideCurrentConnection) + { + ThenHttpConnect then = new ThenHttpConnect() + { + OverrideCurrentConnection = overrideCurrentConnection + }; + return then.Perform(eventInfo).ToString(); + } + + public string SendData() + { + ThenSendData then = new ThenSendData(); + return then.Perform(eventInfo).ToString(); + } + + public string GetMessageValue(EventInfo eventInfo, string messageValue, string messageValueType, string identifier) + { + MessageValueHandler handler = new MessageValueHandler(); + MessageValue rawMessageValue = (MessageValue)GetStringAsEnum(messageValue); + MessageValueType rawMessageValueType = (MessageValueType)GetStringAsEnum(messageValueType); + VariableString rawIdentifier = (identifier == null) ? null : VariableString.GetAsVariableString(identifier.ToString(), false); + return handler.GetValue(eventInfo, rawMessageValue, rawMessageValueType, rawIdentifier); + } + + public void SetMessageValue(EventInfo eventInfo, string value, string messageValue, string messageValueType, string identifier) + { + MessageValueHandler handler = new MessageValueHandler(); + MessageValue rawMessageValue = (MessageValue)GetStringAsEnum(messageValue); + MessageValueType rawMessageValueType = (MessageValueType)GetStringAsEnum(messageValueType); + VariableString rawIdentifier = (identifier == null) ? null : VariableString.GetAsVariableString(identifier, false); + handler.SetValue(eventInfo, rawMessageValue, rawMessageValueType, rawIdentifier, value); + } + + private static object GetStringAsEnum(object value) + { + object enumValue = null; + if (value != null) + { + Type targetType = typeof(T); + FieldInfo fieldInfo = targetType.GetFields().FirstOrDefault(field => field.Name == value?.ToString() || field.GetCustomAttribute()?.Description == value.ToString()); + if (fieldInfo != null) + { + if (!targetType.IsEnum) + { + targetType = targetType.GetGenericArguments().ElementAtOrDefault(0) ?? targetType; + } + enumValue = Enum.ToObject(targetType, (int)fieldInfo.GetValue(null)); + } + } + return enumValue; + } + } +} diff --git a/ReshaperScript/Core/Functions/SystemFuncs.cs b/ReshaperScript/Core/Functions/SystemFuncs.cs new file mode 100644 index 0000000..01413a6 --- /dev/null +++ b/ReshaperScript/Core/Functions/SystemFuncs.cs @@ -0,0 +1,67 @@ +using ReshaperCore; +using ReshaperCore.Rules; +using ReshaperCore.Utils; +using ReshaperCore.Vars; +using ReshaperCore.Providers; + +namespace ReshaperScript.Core.Functions +{ + public class System + { + private readonly EventInfo _eventInfo; + + public ISelf Self { get; set; } = new SelfProvider().GetInstance(); + + public System(EventInfo eventInfo) + { + this._eventInfo = eventInfo; + } + + public void LogMessage(string text) + { + Log.LogInfo(text); + } + + public bool RemoveGlobalVariable(string name) + { + return Self.Variables.Remove(name); + } + + public bool RemoveConnectionVariable(string name) + { + return _eventInfo.Variables.Remove(name); + } + + public string GetGlobalVariable(string name) + { + IVariable value = Self.Variables.GetOrDefault(name); + return value?.Value; + } + + public string GetConnectionVariable(string name) + { + IVariable value = _eventInfo.Variables.GetOrDefault(name); + return value?.Value; + } + + public void SetConnectionVariable(string name, string value) + { + IVariable variable = _eventInfo.Variables.GetOrDefault(name); + if (variable == null) + { + variable = _eventInfo.Variables.Add(name); + } + variable.Value = value; + } + + public void SetGlobalVariable(string name, string value) + { + IVariable variable = Self.Variables.GetOrDefault(name); + if (variable == null) + { + variable = Self.Variables.Add(name); + } + variable.Value = value; + } + } +} \ No newline at end of file diff --git a/ReshaperScript/Core/IPooledEngine.cs b/ReshaperScript/Core/IPooledEngine.cs new file mode 100644 index 0000000..57d1098 --- /dev/null +++ b/ReshaperScript/Core/IPooledEngine.cs @@ -0,0 +1,17 @@ +using MsieJavaScriptEngine; + +namespace ReshaperScript.Core +{ + public interface IPooledEngine + { + IScriptEngineAdapter ScriptEngine + { + get; + } + + int UseCount + { + get; + } + } +} diff --git a/ReshaperScript/Core/IScriptEngineAdapter.cs b/ReshaperScript/Core/IScriptEngineAdapter.cs new file mode 100644 index 0000000..eb988d0 --- /dev/null +++ b/ReshaperScript/Core/IScriptEngineAdapter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ReshaperScript.Core.Functions; + +namespace ReshaperScript.Core +{ + public interface IScriptEngineAdapter : IDisposable + { + void EmbedHostObject(string name, object obj); + + object Evaluate(string code); + + void Execute(string code); + } +} diff --git a/ReshaperScript/Core/IScriptEnginePool.cs b/ReshaperScript/Core/IScriptEnginePool.cs new file mode 100644 index 0000000..41dfd63 --- /dev/null +++ b/ReshaperScript/Core/IScriptEnginePool.cs @@ -0,0 +1,9 @@ +namespace ReshaperScript.Core +{ + public interface IScriptEnginePool + { + void CheckinEngine(IPooledEngine pooledEngine); + + IPooledEngine CheckoutEngine(); + } +} \ No newline at end of file diff --git a/ReshaperScript/Core/IScriptHandler.cs b/ReshaperScript/Core/IScriptHandler.cs new file mode 100644 index 0000000..a66f97e --- /dev/null +++ b/ReshaperScript/Core/IScriptHandler.cs @@ -0,0 +1,11 @@ +using ReshaperCore.Rules; + +namespace ReshaperScript.Core +{ + public interface IScriptHandler + { + string RunNamedScript(EventInfo eventInfo, string name); + + string RunScript(EventInfo eventInfo, string command); + } +} diff --git a/ReshaperScript/Core/IScriptRegistry.cs b/ReshaperScript/Core/IScriptRegistry.cs new file mode 100644 index 0000000..21b1d8b --- /dev/null +++ b/ReshaperScript/Core/IScriptRegistry.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace ReshaperScript.Core +{ + public delegate void ScriptsListChangedHandler(); + + public interface IScriptRegistry + { + IList