├── .gitattributes ├── .github ├── FUNDING.yml ├── release.yml └── workflows │ ├── azure-static-web-apps.yml │ ├── build.yml │ └── nightly-lists.yml ├── .gitignore ├── CHANGELOG.md ├── Code └── IPFilter │ ├── App.ico │ ├── App.xaml │ ├── App.xaml.cs │ ├── Apps │ ├── ApplicationEnumerator.cs │ ├── BitTorrentApplication.cs │ ├── DelugeApplication.cs │ ├── EmuleApplication.cs │ ├── IApplication.cs │ ├── QBitTorrent.cs │ └── UTorrentApplication.cs │ ├── Cli │ ├── CachingFileFetcher.cs │ ├── CryptographicAlgorithmExtensions.cs │ ├── FileFetcher.cs │ ├── FileNode.cs │ ├── GZipNode.cs │ ├── INode.cs │ ├── INodeVisitor.cs │ ├── JsonNode.cs │ ├── MultiFilterWriter.cs │ ├── NodeVisitor.cs │ ├── Options.cs │ ├── TextFilterWriter.cs │ ├── TextNode.cs │ ├── UriNode.cs │ └── ZipNode.cs │ ├── Commands │ ├── ScheduledTaskCommand.cs │ └── Service.cs │ ├── Core │ ├── Benchmark.cs │ ├── Blocklist.cs │ ├── BlocklistBundle.cs │ ├── BlocklistProvider.cs │ ├── DataFormat.cs │ ├── FileInfoExtensions.cs │ ├── FileSystem.cs │ ├── FilterCollection.cs │ ├── FilterContext.cs │ ├── FormatDetector.cs │ ├── IFileSystem.cs │ ├── IFilterEnumerator.cs │ ├── IFilterWriter.cs │ ├── IPAddressComparer.cs │ ├── IUriResolver.cs │ ├── NullWriter.cs │ ├── StreamExtensions.cs │ ├── TempFile.cs │ ├── TempStream.cs │ ├── UriResolver.cs │ └── Win32Api.cs │ ├── EntryPoint.cs │ ├── FilterService.Designer.cs │ ├── FilterService.cs │ ├── FilterServiceInstaller.cs │ ├── Formats │ ├── Address.cs │ ├── DatParser.cs │ ├── EmuleWriter.cs │ ├── FilterFileFormat.cs │ ├── IFormatWriter.cs │ ├── IpAddress.cs │ └── P2pWriter.cs │ ├── IPFilter.csproj │ ├── ListProviders │ ├── DefaultList.cs │ ├── IMirrorProvider.cs │ └── MirrorProvidersFactory.cs │ ├── Logging │ └── FileTraceListener.cs │ ├── Models │ ├── ApplicationDetectionResult.cs │ ├── CompressionFormat.cs │ ├── FileMirror.cs │ ├── FilterDownloadResult.cs │ ├── FilterEntry.cs │ ├── FilterSource.cs │ ├── FilterUpdateResult.cs │ ├── ProgressModel.cs │ ├── ProgressModelExtensions.cs │ ├── UpdateInfo.cs │ ├── UpdateModel.cs │ └── UpdateState.cs │ ├── Native │ ├── PathHelper.cs │ ├── ProcessInformation.cs │ ├── ProcessManager.cs │ ├── SafeLocalMemHandle.cs │ ├── SecurityAttributes.cs │ ├── ShellLinkHelper.cs │ ├── StartupInfo.cs │ └── Win32Api.cs │ ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ └── launchSettings.json │ ├── Services │ ├── CacheProvider.cs │ ├── Config.cs │ ├── CryptoHelper.cs │ ├── DelegateTraceListener.cs │ ├── Deployment │ │ ├── ClickOnce │ │ │ ├── ClickOnceRegistry.cs │ │ │ ├── IUninstallStep.cs │ │ │ ├── RemoveFiles.cs │ │ │ ├── RemoveRegistryKeys.cs │ │ │ ├── RemoveStartMenuEntry.cs │ │ │ ├── RemoveUninstallEntry.cs │ │ │ ├── UninstallInfo.cs │ │ │ └── Uninstaller.cs │ │ ├── DeploymentHelper.cs │ │ ├── SemanticVersion.cs │ │ └── Updater.cs │ ├── DestinationPathsProvider.cs │ ├── FilterDownloader.cs │ ├── ICacheProvider.cs │ └── PathSetting.cs │ ├── ViewModels │ ├── DelegateCommand.cs │ ├── Interval.cs │ ├── MainWindowViewModel.cs │ ├── MessageBoxHelper.cs │ └── OptionsViewModel.cs │ ├── Views │ ├── InverseConverter.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── OptionsWindow.xaml │ └── OptionsWindow.xaml.cs │ ├── ipfilter.json │ └── lists.json ├── Directory.Build.props ├── IPFilter.sln ├── IPFilter.vsmdi ├── IPFilterUpdater.png ├── LICENSE.txt ├── Local.testsettings ├── README.md ├── Setup ├── Setup.IPFilter.CustomActions │ ├── ClickOnceRegistry.cs │ ├── CustomAction.config │ ├── CustomAction.cs │ ├── DeleteHelper.cs │ ├── IO │ │ ├── ErrorHelper.cs │ │ ├── FileAttributes.cs │ │ ├── FileSystemEnumerator.cs │ │ ├── FileSystemInfoResultHandler.cs │ │ ├── FileTime.cs │ │ ├── FindData.cs │ │ ├── Iterator.cs │ │ ├── PathHelper.cs │ │ ├── PathHelperMethods.cs │ │ ├── SafeFindHandle.cs │ │ ├── SearchData.cs │ │ ├── SearchResult.cs │ │ ├── SearchResultHandler.cs │ │ ├── StringExtensions.cs │ │ └── StringResultHandler.cs │ ├── IUninstallStep.cs │ ├── LoadLibraryExFlags.cs │ ├── LoadLibraryException.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── RemoveFiles.cs │ ├── RemoveRegistryKeys.cs │ ├── RemoveStartMenuEntry.cs │ ├── RemoveUninstallEntry.cs │ ├── SafeLibraryHandle.cs │ ├── Setup.IPFilter.CustomActions.csproj │ ├── UninstallInfo.cs │ ├── Uninstaller.cs │ ├── Win32Api.cs │ └── Win32Error.cs └── Setup.IPFilter │ ├── Product.wxs │ └── Setup.IPFilter.wixproj ├── Tests └── UnitTests │ └── IPFilter.Tests │ ├── AddressTests.cs │ ├── Apps │ └── DelugeTests.cs │ ├── DestinationPathsProviderTests.cs │ ├── FormatDetectorTests.cs │ ├── Formats │ ├── DatParserTests.cs │ ├── EmuleWriterTests.cs │ ├── FilterCollectionTests.cs │ ├── FilterEntryComparerTests.cs │ ├── IPAddressComparerTests.cs │ ├── IpAddressTests.cs │ └── P2pWriterTests.cs │ ├── IPFilter.Tests.csproj │ ├── ListMergerTests.cs │ ├── PathHelperTests.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Services │ ├── ConfigTests.cs │ └── SemanticVersionTests.cs │ ├── ShellLinkHelperTests.cs │ ├── StreamHelper.cs │ ├── TestFilterData.cs │ ├── UriResolverTests.cs │ ├── ZipEnumeratorTests.cs │ ├── ipfilter.dat │ └── lists.json ├── TraceAndTestImpact.testsettings └── install └── Application Files └── IPFilter.UI_1_0_0_10 ├── IPFilter.UI.application ├── IPFilter.UI.exe.config.deploy ├── IPFilter.UI.exe.deploy ├── IPFilter.UI.exe.manifest ├── IPFilter.UI.pdb.deploy ├── IPFilter.dll.deploy ├── IPFilter.pdb.deploy └── Ionic.Zip.Reduced.dll.deploy /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [DavidMoore] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: davidmoore # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - dependencies 5 | authors: 6 | - octocat 7 | categories: 8 | - title: Fixed 🛠 9 | labels: 10 | - bug 11 | - title: Added 🎉 12 | labels: 13 | - feature 14 | - title: Changed 15 | labels: 16 | - "*" 17 | -------------------------------------------------------------------------------- /.github/workflows/azure-static-web-apps.yml: -------------------------------------------------------------------------------- 1 | name: Azure Static Web Apps CI/CD 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - docs 8 | pull_request: 9 | types: [opened, synchronize, reopened, closed] 10 | branches: 11 | - docs 12 | release: 13 | types: [published, edited] 14 | workflow_run: 15 | workflows: 16 | ["Nightly Lists"] 17 | types: [completed] 18 | branches: 19 | - 'master' 20 | - 'main' 21 | 22 | jobs: 23 | build_and_deploy_job: 24 | if: github.event_name == 'release' || github.event_name == 'push' || github.event_name == 'workflow_run' || (github.event_name == 'pull_request' && github.event.action != 'closed') 25 | runs-on: ubuntu-latest 26 | name: Build and Deploy Job 27 | steps: 28 | - uses: actions/checkout@v3 29 | with: 30 | submodules: true 31 | ref: docs 32 | - name: Build And Deploy 33 | id: builddeploy 34 | uses: Azure/static-web-apps-deploy@v1 35 | with: 36 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} 37 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) 38 | action: "upload" 39 | app_location: "/" # App source code path 40 | api_location: "" # Api source code path - optional 41 | output_location: "public" # Built app content directory - optional 42 | 43 | close_pull_request_job: 44 | if: github.event_name == 'pull_request' && github.event.action == 'closed' 45 | runs-on: ubuntu-latest 46 | name: Close Pull Request Job 47 | steps: 48 | - name: Close Pull Request 49 | id: closepullrequest 50 | uses: Azure/static-web-apps-deploy@v1 51 | with: 52 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} 53 | action: "close" 54 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: 'Build & release' 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version' 8 | required: false 9 | type: string 10 | 11 | push: 12 | #branches: 13 | # - main 14 | tags: 15 | - '*' 16 | paths-ignore: 17 | - '.github/**' 18 | 19 | jobs: 20 | build: 21 | runs-on: windows-latest 22 | env: 23 | VERSION: ${{inputs.version || github.ref_name}} 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@main 27 | 28 | - name: Restore 29 | run: | 30 | & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\msbuild.exe" "/t:Restore" "/p:Configuration=Release" "/p:Version=$env:VERSION" 31 | 32 | - name: Build 33 | run: | 34 | & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\msbuild.exe" "/p:Configuration=Release" "/p:Version=$env:VERSION" 35 | 36 | - name: Create Release 37 | env: 38 | GH_TOKEN: ${{ github.token }} 39 | run: | 40 | gh release create $env:VERSION --draft --prerelease --title "v$env:VERSION" --notes "Download and run **IPFilter.msi** to install. If you wish to just download and run the self-contained executable, you can download IPFilter.exe" ./Bin/IPFilter.msi ./Bin/IPFilter.exe 41 | -------------------------------------------------------------------------------- /.github/workflows/nightly-lists.yml: -------------------------------------------------------------------------------- 1 | name: Nightly Lists 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 5 * * *' 7 | 8 | jobs: 9 | generate-list: 10 | runs-on: windows-latest 11 | name: Generate and upload lists 12 | env: 13 | GH_TOKEN: ${{ github.token }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Download release 17 | id: download-release 18 | shell: powershell 19 | run: | 20 | gh release download --pattern '*.exe' 21 | gh release download lists --pattern '*.json' 22 | 23 | Write-Host "Launching ipfilter..." 24 | .\IPFilter.exe https://raw.githubusercontent.com/DavidMoore/ipfilter/lists/default/lists.json '-o:ipfilter.dat' 25 | 26 | Write-Host "Waiting for ipfilter to finish..." 27 | Wait-Process -Name ipfilter 28 | 29 | ls 30 | 31 | Write-Host "Finished" 32 | exit 0 33 | 34 | - name: Archive 35 | shell: powershell 36 | run: | 37 | 38 | Write-Host "Creating ipfilter.dat.gz..." 39 | 7z a ipfilter.dat.gz ipfilter.dat 40 | 41 | Write-Host "Creating ipfilter.zip..." 42 | 7z a ipfilter.zip ipfilter.dat 43 | 44 | - name: Upload Release 45 | run: | 46 | gh release upload lists ipfilter.dat ipfilter.dat.gz ipfilter.zip --clobber -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | /Code/*/[Bb]in/ 3 | /[Bb]in/ 4 | [Oo]bj/ 5 | 6 | # mstest test results 7 | TestResults 8 | 9 | ## Ignore Visual Studio temporary files, build results, and 10 | ## files generated by popular Visual Studio add-ons. 11 | 12 | # User-specific files 13 | *.suo 14 | *.user 15 | *.sln.docstates 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Rr]elease/ 20 | x64/ 21 | *_i.c 22 | *_p.c 23 | *.ilk 24 | *.meta 25 | *.obj 26 | *.pch 27 | *.pdb 28 | *.pgc 29 | *.pgd 30 | *.rsp 31 | *.sbr 32 | *.tlb 33 | *.tli 34 | *.tlh 35 | *.tmp 36 | *.log 37 | *.vspscc 38 | *.vssscc 39 | .builds 40 | 41 | # Visual C++ cache files 42 | ipch/ 43 | *.aps 44 | *.ncb 45 | *.opensdf 46 | *.sdf 47 | 48 | # Visual Studio profiler 49 | *.psess 50 | *.vsp 51 | *.vspx 52 | 53 | # Guidance Automation Toolkit 54 | *.gpState 55 | 56 | # ReSharper is a .NET coding add-in 57 | _ReSharper* 58 | 59 | # Mindbench SASS cache 60 | .sass-cache/ 61 | 62 | # NCrunch 63 | *.ncrunch* 64 | .*crunch*.local.xml 65 | 66 | # Installshield output folder 67 | [Ee]xpress 68 | 69 | # DocProject is a documentation generator add-in 70 | DocProject/buildhelp/ 71 | DocProject/Help/*.HxT 72 | DocProject/Help/*.HxC 73 | DocProject/Help/*.hhc 74 | DocProject/Help/*.hhk 75 | DocProject/Help/*.hhp 76 | DocProject/Help/Html2 77 | DocProject/Help/html 78 | 79 | # Click-Once directory 80 | publish 81 | 82 | # Publish Web Output 83 | *.Publish.xml 84 | 85 | # NuGet Packages Directory 86 | packages/ 87 | 88 | # Windows Azure Build Output 89 | csx 90 | *.build.csdef 91 | 92 | # Windows Store app package directory 93 | AppPackages/ 94 | 95 | # Others 96 | sql 97 | TestResults 98 | [Tt]est[Rr]esult* 99 | *.Cache 100 | ClientBin 101 | [Ss]tyle[Cc]op.* 102 | ~$* 103 | *.dbmdl 104 | Generated_Code #added for RIA/Silverlight projects 105 | 106 | # Backup & report files from converting an old project file to a newer 107 | # Visual Studio version. Backup files are not needed, because we have git ;-) 108 | _UpgradeReport_Files/ 109 | Backup*/ 110 | UpgradeLog*.XML 111 | 112 | *.DotSettings 113 | /install 114 | /.vs 115 | /.idea 116 | *.binlog 117 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [3.0.5-beta] - 2023-08-21 6 | 7 | ### Fixed 8 | 9 | * Options window should always enable OK and Cancel buttons (#74) 10 | * Fixed issue with deleting scheduled task after it's been disabled in options (#97) 11 | 12 | ## [3.0.4-beta] - 2023-08-19 13 | 14 | ### Fixed 15 | 16 | * Task Scheduler and Options fixes by @DavidMoore in https://github.com/DavidMoore/ipfilter/pull/95 17 | - Renamed "Save Settings" button in Options to "OK", and it automatically closes the window after saving (#74) 18 | - Scheduled task should be created or deleted according to the chosen option (#72) 19 | * The status / error message should auto-size to fit in the window (#67) 20 | * Corrected the URL in Add/Remove Programs entry 21 | 22 | ## [3.0.3-beta] - 2023-08-18 23 | 24 | ### Changed 25 | * Build & installer no longer signed with code signing certificate 26 | * Updated to .NET Framework 4.8 27 | 28 | ### Fixed 29 | * Fixed eMule alternative config locations #68 30 | * Fixed saving / loading of options including the scheduled task #89 31 | * Fix startup crash by @yfdyh000 in https://github.com/DavidMoore/ipfilter/pull/66 32 | 33 | ## Contributors 34 | * @DavidMoore 35 | * @yfdyh000 made their first contribution in https://github.com/DavidMoore/ipfilter/pull/66 36 | 37 | ## [3.0.2.9-beta] 38 | 39 | ### Changed 40 | 41 | - Changing the new upgrade logic 42 | 43 | ## [3.0.2.7-beta] 44 | 45 | ## [3.0.2.4-beta] 46 | 47 | ### Fixed 48 | 49 | - Fixing problem with empty paths when trying to detect installed torrent clients (#59) 50 | 51 | ## [3.0.2.3-beta] - 2019-08-30 52 | 53 | ### Fixed 54 | 55 | - Silent updating always installs 0 byte ipfilter.dat file (#58) 56 | 57 | ## [3.0.1.4-beta] 58 | 59 | [Unreleased]: https://github.com/DavidMoore/ipfilter/compare/3.0.5-beta...HEAD 60 | [3.0.5-beta]: https://github.com/DavidMoore/ipfilter/compare/3.0.4-beta...3.0.5-beta 61 | [3.0.4-beta]: https://github.com/DavidMoore/ipfilter/compare/3.0.3-beta...3.0.4-beta 62 | [3.0.3-beta]: https://github.com/DavidMoore/ipfilter/compare/3.0.2.9-beta...3.0.3-beta 63 | [3.0.2.9-beta]: https://github.com/DavidMoore/ipfilter/compare/3.0.2.7-beta...3.0.2.9-beta 64 | [3.0.2.7-beta]: https://github.com/DavidMoore/ipfilter/compare/3.0.2.4-beta...3.0.2.7-beta 65 | [3.0.2.4-beta]: https://github.com/DavidMoore/ipfilter/compare/3.0.2.3-beta...3.0.2.4-beta 66 | [3.0.2.3-beta]: https://github.com/DavidMoore/ipfilter/compare/3.0.1.4-beta...3.0.2.3-beta 67 | [3.0.1.4-beta]: https://github.com/DavidMoore/ipfilter/compare/3.0.0-beta1...3.0.1.4-beta 68 | [3.0.0-beta1]: https://github.com/DavidMoore/ipfilter/releases/tag/3.0.0-beta1 -------------------------------------------------------------------------------- /Code/IPFilter/App.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidMoore/ipfilter/96f15428b4fa4f21cc2bc4c8fe7d18f16ba3f381/Code/IPFilter/App.ico -------------------------------------------------------------------------------- /Code/IPFilter/App.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | -------------------------------------------------------------------------------- /Code/IPFilter/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter 2 | { 3 | using System.Windows; 4 | 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | /// 11 | /// Raises the event. 12 | /// 13 | /// A that contains the event data. 14 | protected override void OnStartup(StartupEventArgs e) 15 | { 16 | base.OnStartup(e); 17 | 18 | 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Code/IPFilter/Apps/ApplicationEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Apps 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using Models; 6 | 7 | public class ApplicationEnumerator 8 | { 9 | public async Task> GetInstalledApplications() 10 | { 11 | var results = new List(); 12 | 13 | var applications = new List 14 | { 15 | new QBitTorrent(), 16 | new UTorrentApplication(), 17 | new BitTorrentApplication(), 18 | new EmuleApplication() 19 | }; 20 | 21 | foreach (var application in applications) 22 | { 23 | var installed = await application.DetectAsync(); 24 | 25 | if (installed != null && installed.IsPresent) 26 | { 27 | results.Add(installed); 28 | } 29 | } 30 | 31 | return results; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Code/IPFilter/Apps/BitTorrentApplication.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidMoore/ipfilter/96f15428b4fa4f21cc2bc4c8fe7d18f16ba3f381/Code/IPFilter/Apps/BitTorrentApplication.cs -------------------------------------------------------------------------------- /Code/IPFilter/Apps/EmuleApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using IPFilter.Formats; 7 | using IPFilter.Models; 8 | using Microsoft.Win32; 9 | 10 | namespace IPFilter.Apps 11 | { 12 | class EmuleApplication : IApplication 13 | { 14 | Environment.SpecialFolder configFolder; 15 | 16 | protected virtual string RegistryKeyName => @"Software\eMule"; 17 | 18 | protected virtual string DefaultDisplayName => "eMule"; 19 | 20 | protected virtual string FolderName => "eMule"; 21 | 22 | public Task DetectAsync() 23 | { 24 | using var key = Registry.CurrentUser.OpenSubKey(RegistryKeyName, false); 25 | if (key == null) return Task.FromResult(ApplicationDetectionResult.NotFound()); 26 | 27 | var installLocation = (string)key.GetValue("Install Path"); 28 | if (string.IsNullOrWhiteSpace(installLocation)) return Task.FromResult(ApplicationDetectionResult.NotFound()); 29 | 30 | var result = ApplicationDetectionResult.Create(this, DefaultDisplayName, installLocation); 31 | 32 | var exe = new FileInfo(Path.Combine(installLocation, "emule.exe")); 33 | if (!exe.Exists) 34 | { 35 | result.IsPresent = false; 36 | } 37 | else 38 | { 39 | var version = FileVersionInfo.GetVersionInfo(exe.FullName); 40 | result.Description = version.ProductName; 41 | result.Version = version.FileVersion; 42 | } 43 | 44 | // eMule can be configured to store config in the application folder or program data, instead of app data 45 | var useSharedConfigValue = key.GetValue("UsePublicUserDirectories") ?? 0; 46 | configFolder = (int) useSharedConfigValue switch 47 | { 48 | 1 => Environment.SpecialFolder.CommonApplicationData, 49 | 2 => Environment.SpecialFolder.ProgramFilesX86, 50 | _ => Environment.SpecialFolder.LocalApplicationData 51 | }; 52 | 53 | return Task.FromResult(result); 54 | } 55 | 56 | public async Task UpdateFilterAsync(FilterDownloadResult filter, CancellationToken cancellationToken, IProgress progress) 57 | { 58 | var basePath = Environment.GetFolderPath( configFolder, Environment.SpecialFolderOption.Create); 59 | var destinationPath = Path.Combine(basePath, FolderName, "config", "ipfilter.dat"); 60 | 61 | Trace.TraceInformation("Writing filter to " + destinationPath); 62 | using var destination = File.Open(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None); 63 | using var writer = new EmuleWriter(destination); 64 | await writer.Write(filter.Entries, progress); 65 | 66 | return new FilterUpdateResult { FilterTimestamp = filter.FilterTimestamp }; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Code/IPFilter/Apps/IApplication.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Apps 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Models; 7 | 8 | public interface IApplication 9 | { 10 | Task DetectAsync(); 11 | 12 | Task UpdateFilterAsync(FilterDownloadResult filter, CancellationToken cancellationToken, IProgress progress); 13 | } 14 | } -------------------------------------------------------------------------------- /Code/IPFilter/Apps/UTorrentApplication.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidMoore/ipfilter/96f15428b4fa4f21cc2bc4c8fe7d18f16ba3f381/Code/IPFilter/Apps/UTorrentApplication.cs -------------------------------------------------------------------------------- /Code/IPFilter/Cli/CachingFileFetcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | using System.Threading.Tasks; 5 | using IPFilter.Core; 6 | 7 | namespace IPFilter.Cli 8 | { 9 | class CachingFileFetcher : FileFetcher 10 | { 11 | static readonly MD5 hasher = MD5.Create(); 12 | 13 | readonly DirectoryInfo path; 14 | 15 | public CachingFileFetcher() 16 | { 17 | var basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"ipfilter\cache\"); 18 | path = new DirectoryInfo(basePath ); 19 | if (!path.Exists) path.Create(); 20 | } 21 | 22 | public override async Task Get(Uri uri, FilterContext context) 23 | { 24 | // The path to the cached version of the file (which may or may not exist, depending on if it's 25 | // already been downloaded and cached before). 26 | var cachePath = GetCachePath(uri); 27 | 28 | if (uri.IsFile) 29 | { 30 | return await GetLocalFile(uri, context, cachePath); 31 | } 32 | 33 | return await GetRemoteFile(uri, context, cachePath); 34 | } 35 | 36 | FileInfo GetCachePath(Uri uri) 37 | { 38 | var hash = hasher.ComputeHash(uri); 39 | var cachePath = Path.Combine(path.FullName, hash); 40 | return new FileInfo(cachePath); 41 | } 42 | 43 | public override void Dispose() 44 | { 45 | base.Dispose(); 46 | hasher?.Dispose(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/CryptographicAlgorithmExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace IPFilter.Cli 6 | { 7 | static class CryptographicAlgorithmExtensions 8 | { 9 | public static string ComputeHash(this HashAlgorithm hashAlgorithm, object value) 10 | { 11 | return ComputeHash(hashAlgorithm, value.ToString()); 12 | } 13 | 14 | public static string ComputeHash(this HashAlgorithm hashAlgorithm, string value) 15 | { 16 | return BitConverter.ToString(hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(value))) 17 | .Replace("-", "") 18 | .ToLowerInvariant(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/FileNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using IPFilter.Core; 6 | 7 | namespace IPFilter.Cli 8 | { 9 | class FileNode : INode 10 | { 11 | readonly FileInfo file; 12 | 13 | public FileNode(FileInfo file) 14 | { 15 | this.file = new FileInfo(file.FullName); 16 | } 17 | 18 | public string FullName => file.FullName; 19 | 20 | internal async Task DetectFormat() 21 | { 22 | using (var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) 23 | { 24 | return await FormatDetector.DetectFormat(stream); 25 | } 26 | } 27 | 28 | public async Task Accept(INodeVisitor visitor) 29 | { 30 | // Find the file type 31 | var fileType = await DetectFormat(); 32 | 33 | switch (fileType) 34 | { 35 | case DataFormat.GZip: 36 | await visitor.Visit(new GZipNode(file)); 37 | break; 38 | 39 | case DataFormat.Zip: 40 | await visitor.Visit(new ZipNode(file)); 41 | break; 42 | 43 | case DataFormat.Json: 44 | await visitor.Visit(new JsonNode(file)); 45 | break; 46 | 47 | case DataFormat.Text: 48 | await visitor.Visit(new TextNode(file)); 49 | break; 50 | 51 | case DataFormat.Binary: 52 | default: 53 | break; 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/GZipNode.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Compression; 3 | using System.Threading.Tasks; 4 | 5 | namespace IPFilter.Cli 6 | { 7 | /// 8 | /// Decompresses a GZipped file and processes the contents. 9 | /// 10 | class GZipNode : INode 11 | { 12 | readonly FileInfo file; 13 | 14 | public GZipNode(FileInfo file) 15 | { 16 | this.file = new FileInfo(file.FullName); 17 | } 18 | 19 | public async Task Accept(INodeVisitor visitor) 20 | { 21 | // Extract the file to a temporary file, and then parse that. 22 | using (var tempFile = new TempFile()) 23 | { 24 | // Extract to the temporary file 25 | using (var tempStream = tempFile.OpenWrite()) 26 | using (var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) 27 | using (var gzipFile = new GZipStream(stream, CompressionMode.Decompress, false)) 28 | { 29 | await gzipFile.CopyToAsync(tempStream); 30 | } 31 | 32 | // Process the extracted file 33 | await visitor.Visit(new FileNode(tempFile.File)); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/INode.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace IPFilter.Cli 4 | { 5 | public interface INode 6 | { 7 | Task Accept(INodeVisitor visitor); 8 | } 9 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/INodeVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using IPFilter.Core; 4 | 5 | namespace IPFilter.Cli 6 | { 7 | public interface INodeVisitor : IDisposable 8 | { 9 | FilterContext Context { get; } 10 | 11 | Task Visit(T node) where T : INode; 12 | } 13 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/JsonNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Runtime.Serialization.Json; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using IPFilter.Core; 8 | 9 | namespace IPFilter.Cli 10 | { 11 | class JsonNode : INode 12 | { 13 | readonly FileInfo file; 14 | static readonly DataContractJsonSerializer serializer; 15 | 16 | static JsonNode() 17 | { 18 | var settings = new DataContractJsonSerializerSettings 19 | { 20 | KnownTypes = new []{typeof(BlocklistBundle), typeof(BlocklistProvider), typeof(Blocklist)} 21 | }; 22 | 23 | serializer = new DataContractJsonSerializer(typeof(BlocklistBundle), settings); 24 | } 25 | 26 | public JsonNode(FileInfo file) 27 | { 28 | this.file = new FileInfo(file.FullName); 29 | } 30 | 31 | public async Task Accept(INodeVisitor visitor) 32 | { 33 | BlocklistBundle bundle = null; 34 | 35 | var json = await file.ReadAllText(); 36 | 37 | using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) 38 | { 39 | try 40 | { 41 | bundle = (BlocklistBundle) serializer.ReadObject(stream); 42 | } 43 | catch (Exception ex) 44 | { 45 | Trace.TraceWarning("Couldn't read list json: " + ex); 46 | } 47 | } 48 | 49 | if (bundle == null) 50 | { 51 | Trace.TraceWarning($"Couldn't read JSON file {file.FullName}"); 52 | return; 53 | } 54 | 55 | foreach (var provider in bundle.Lists) 56 | { 57 | if (visitor.Context.CancellationToken.IsCancellationRequested) return; 58 | 59 | if (provider?.Lists == null) continue; 60 | 61 | foreach (var list in provider.Lists) 62 | { 63 | var uri = ResolveUri(list, provider); 64 | if (uri == null) continue; 65 | if (visitor.Context.CancellationToken.IsCancellationRequested) return; 66 | 67 | // Process this list 68 | await visitor.Visit(new UriNode(uri)); 69 | } 70 | } 71 | } 72 | 73 | 74 | Uri ResolveUri(Blocklist list, BlocklistProvider provider) 75 | { 76 | var uri = list.Uri; 77 | 78 | if (uri != null && uri.IsAbsoluteUri) return uri; 79 | 80 | if (provider.BaseUri == null) 81 | { 82 | Trace.TraceWarning("Skipping {0} as it doesn't have a full URI, and parent {1} has no base URI we can use.", list.Name, provider.Name); 83 | return null; 84 | } 85 | 86 | var resolved = string.Format(provider.BaseUri.ToString(), uri?.ToString() ?? list.Id); 87 | uri = new Uri(provider.BaseUri, new Uri(resolved)); 88 | 89 | return uri; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/MultiFilterWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using IPFilter.Core; 5 | 6 | namespace IPFilter.Cli 7 | { 8 | class MultiFilterWriter : IFilterWriter 9 | { 10 | readonly IList writers; 11 | 12 | public MultiFilterWriter(IEnumerable writers) 13 | { 14 | this.writers = writers.ToList(); 15 | } 16 | 17 | public void Dispose() 18 | { 19 | foreach (var writer in writers) 20 | { 21 | writer.Dispose(); 22 | } 23 | } 24 | 25 | public Task WriteLineAsync(string line) 26 | { 27 | foreach (var writer in writers) 28 | { 29 | writer.WriteLineAsync(line); 30 | } 31 | 32 | return Task.FromResult(1); 33 | } 34 | 35 | public async Task Flush() 36 | { 37 | foreach (var writer in writers) 38 | { 39 | await writer.Flush(); 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/NodeVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using IPFilter.Core; 4 | 5 | namespace IPFilter.Cli 6 | { 7 | class NodeVisitor : INodeVisitor 8 | { 9 | public NodeVisitor() : this(new FilterContext()) {} 10 | 11 | public NodeVisitor(FilterContext context) 12 | { 13 | Context = context; 14 | } 15 | 16 | public FilterContext Context { get; } 17 | 18 | public Task Visit(T node) where T : INode 19 | { 20 | Console.WriteLine("Visiting " + node); 21 | return node.Accept(this); 22 | } 23 | 24 | public void Dispose() 25 | { 26 | Context?.Dispose(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/Options.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace IPFilter.Cli 5 | { 6 | class Options 7 | { 8 | public Options() 9 | { 10 | Inputs = new List(); 11 | Outputs = new List(); 12 | } 13 | 14 | public static Options Parse(params string[] args) 15 | { 16 | var options = new Options(); 17 | 18 | if (args != null) 19 | { 20 | foreach (var arg in args) 21 | { 22 | // Skip empty arguments 23 | if (arg == null) continue; 24 | var trimmed = arg.Trim(); 25 | if( trimmed.Length == 0) continue; 26 | 27 | if (arg.StartsWith("o:", StringComparison.OrdinalIgnoreCase)) 28 | { 29 | options.Outputs.Add(arg.Substring(2)); 30 | } 31 | else 32 | { 33 | options.Inputs.Add(arg); 34 | } 35 | } 36 | } 37 | 38 | return options; 39 | } 40 | 41 | public IList Inputs { get; set; } 42 | 43 | public IList Outputs { get; set; } 44 | } 45 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/TextFilterWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using IPFilter.Core; 6 | using IPFilter.Formats; 7 | using IPFilter.Models; 8 | 9 | namespace IPFilter.Cli 10 | { 11 | class TextFilterWriter : IFilterWriter 12 | { 13 | static readonly Task success = Task.FromResult(1); 14 | 15 | readonly FileInfo file; 16 | readonly TextWriter writer; 17 | readonly TempFile temp; 18 | 19 | public TextFilterWriter(string path) 20 | { 21 | file = new FileInfo(path); 22 | temp = new TempFile(); 23 | writer = new StreamWriter( temp.File.Open(FileMode.Create, FileAccess.Write, FileShare.Read)); 24 | } 25 | 26 | public void Dispose() 27 | { 28 | writer?.Dispose(); 29 | temp?.Dispose(); 30 | } 31 | 32 | public Task WriteLineAsync(string line) 33 | { 34 | var parsed = DatParser.ParseLine(line); 35 | if (parsed == null) 36 | { 37 | if(!line.StartsWith("#")) Trace.TraceWarning("Invalid line: " + line); 38 | return success; 39 | } 40 | 41 | writer.WriteLine(parsed); 42 | return success; 43 | } 44 | 45 | public async Task Flush() 46 | { 47 | await writer.FlushAsync(); 48 | writer.Dispose(); 49 | 50 | var filters = new List(); 51 | 52 | using(var input = new StreamReader(temp.OpenShareableRead())) 53 | { 54 | string line; 55 | while ((line = await input.ReadLineAsync()) != null) 56 | { 57 | var filter = DatParser.ParseEntry(line); 58 | if( filter != null) filters.Add(filter); 59 | } 60 | } 61 | 62 | // Sort and merge the list 63 | var list = FilterCollection.Merge(filters); 64 | 65 | // Flush the list out 66 | using var stream = file.Open(FileMode.Create, FileAccess.Write, FileShare.Read); 67 | 68 | // Determine the desired format from the file extension 69 | var format = file.Extension.StartsWith(".p2p") ? FilterFileFormat.P2p : FilterFileFormat.Emule; 70 | //using var listWriter = (format == FilterFileFormat.Emule ? new EmuleWriter(stream) : (IFormatWriter)new BitTorrentWriter(stream)); 71 | using var listWriter = new P2pWriter(stream); 72 | await listWriter.Write(list, null); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/TextNode.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | 5 | namespace IPFilter.Cli 6 | { 7 | class TextNode : INode 8 | { 9 | readonly FileInfo file; 10 | 11 | public TextNode(FileInfo file) 12 | { 13 | this.file = new FileInfo(file.FullName); 14 | } 15 | 16 | public async Task Accept(INodeVisitor visitor) 17 | { 18 | using(var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) 19 | using(var reader = new StreamReader(stream, Encoding.UTF8)) 20 | { 21 | if (visitor.Context.CancellationToken.IsCancellationRequested) return; 22 | 23 | var line = await reader.ReadLineAsync(); 24 | while (line != null) 25 | { 26 | var trimmed = line.Trim(); 27 | if (trimmed.Length > 0) 28 | { 29 | // If we find any binary characters, skip this entire file. 30 | // foreach (var character in trimmed) 31 | // { 32 | // if (char.IsControl(character)) return; 33 | // } 34 | 35 | await visitor.Context.Filter.WriteLineAsync(line); 36 | } 37 | 38 | if (visitor.Context.CancellationToken.IsCancellationRequested) return; 39 | line = await reader.ReadLineAsync(); 40 | } 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/UriNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Threading.Tasks; 4 | 5 | namespace IPFilter.Cli 6 | { 7 | class UriNode : INode 8 | { 9 | static readonly MD5 hasher = MD5.Create(); 10 | readonly Uri uri; 11 | string uriHash; 12 | readonly FileFetcher fileFetcher; 13 | 14 | public UriNode(Uri uri) 15 | { 16 | this.uri = uri; 17 | this.uriHash = hasher.ComputeHash(uri); 18 | fileFetcher = new CachingFileFetcher(); 19 | } 20 | 21 | public async Task Accept(INodeVisitor visitor) 22 | { 23 | var node = await fileFetcher.Get(uri, visitor.Context); 24 | if (node == null) return; 25 | await visitor.Visit(node); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Code/IPFilter/Cli/ZipNode.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Compression; 3 | using System.Threading.Tasks; 4 | 5 | namespace IPFilter.Cli 6 | { 7 | class ZipNode : INode 8 | { 9 | readonly FileInfo file; 10 | 11 | public ZipNode(FileInfo file) 12 | { 13 | this.file = new FileInfo(file.FullName); 14 | } 15 | 16 | public async Task Accept(INodeVisitor visitor) 17 | { 18 | using(var zipSource = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) 19 | using (var zipFile = new ZipArchive(zipSource, ZipArchiveMode.Read, true)) 20 | { 21 | foreach (var entry in zipFile.Entries) 22 | { 23 | using (var entryStream = entry.Open()) 24 | using (var tempFile = new TempFile()) 25 | { 26 | using (var tempStream = tempFile.OpenWrite()) 27 | { 28 | await entryStream.CopyToAsync(tempStream, visitor.Context.CancellationToken); 29 | } 30 | 31 | await visitor.Visit(new FileNode(tempFile.File)); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Code/IPFilter/Commands/Service.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration.Install; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace IPFilter.Commands 9 | { 10 | class ServiceCommand 11 | { 12 | public static async Task ExecuteAsync() 13 | { 14 | var installer = new TransactedInstaller(); 15 | 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Code/IPFilter/Core/Benchmark.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Core 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | 6 | /// 7 | /// Helper class for timing an operation with a descriptive name. Traces 8 | /// the start and completion, with elapsed time. 9 | /// 10 | /// 11 | /// 12 | /// using(Benchmark.New("My operation name")) 13 | /// { 14 | /// // Do stuff 15 | /// } 16 | /// 17 | /// Result: 18 | /// 19 | /// Starting [My operation name] 20 | /// Finished [My operation name] in 00:00:01.2002472 21 | /// 22 | /// 23 | class Benchmark : IDisposable 24 | { 25 | readonly string operation; 26 | readonly Stopwatch stopwatch; 27 | 28 | public static Benchmark New(string operation, params object[] args) 29 | { 30 | return New(string.Format(operation, args)); 31 | } 32 | 33 | public static Benchmark New(string operation) 34 | { 35 | return new Benchmark(operation); 36 | } 37 | 38 | Benchmark(string operation) 39 | { 40 | this.operation = operation; 41 | stopwatch = new Stopwatch(); 42 | stopwatch.Start(); 43 | } 44 | 45 | public void Dispose() 46 | { 47 | stopwatch.Stop(); 48 | Trace.TraceInformation($"Finished [{operation}] in {stopwatch.Elapsed}"); 49 | GC.SuppressFinalize(this); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Code/IPFilter/Core/Blocklist.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace IPFilter.Core 5 | { 6 | [DataContract] 7 | public class Blocklist 8 | { 9 | [DataMember(Name="id")] 10 | public string Id { get; set; } 11 | 12 | [DataMember(Name="name")] 13 | public string Name { get; set; } 14 | 15 | [DataMember(Name="description")] 16 | public string Description { get; set; } 17 | 18 | [DataMember(Name="uri")] 19 | public Uri Uri { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/BlocklistBundle.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace IPFilter.Core 5 | { 6 | [DataContract] 7 | public class BlocklistBundle 8 | { 9 | [DataMember(Name = "name", IsRequired = false)] 10 | public string Name { get; set; } 11 | 12 | [DataMember(Name="description", IsRequired = false)] 13 | public string Description { get; set; } 14 | 15 | [DataMember(Name="lists")] 16 | public IList Lists { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/BlocklistProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace IPFilter.Core 6 | { 7 | [DataContract] 8 | public class BlocklistProvider 9 | { 10 | [DataMember(Name="id")] 11 | public string Id { get; set; } 12 | [DataMember(Name="name")] 13 | public string Name { get; set; } 14 | [DataMember(Name="description")] 15 | public string Description { get; set; } 16 | [DataMember(Name="baseUri")] 17 | public Uri BaseUri { get; set; } 18 | 19 | [DataMember(Name="uri")] 20 | public Uri Uri { get; set; } 21 | 22 | [DataMember(Name="lists")] 23 | public IList Lists { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/DataFormat.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Core 2 | { 3 | public enum DataFormat 4 | { 5 | None = 0, 6 | GZip, 7 | Zip, 8 | Json, 9 | Text, 10 | Binary 11 | } 12 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/FileInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace IPFilter.Cli 8 | { 9 | public static class FileInfoExtensions 10 | { 11 | public static void SafeDelete(this FileInfo file) 12 | { 13 | if (file == null) return; 14 | 15 | try 16 | { 17 | file.Refresh(); 18 | if (!file.Exists) return; 19 | if (file.IsReadOnly) file.IsReadOnly = false; 20 | file.Delete(); 21 | } 22 | catch (Exception ex) 23 | { 24 | Trace.TraceWarning($"Couldn't delete file {file.FullName}: {ex}"); 25 | } 26 | } 27 | 28 | public static async Task ReadAllText(this FileInfo file) 29 | { 30 | using (var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) 31 | using(var reader = new StreamReader(stream, Encoding.UTF8, true)) 32 | { 33 | return await reader.ReadToEndAsync(); 34 | } 35 | } 36 | 37 | /// 38 | /// Writes the passed string to the file using UTF-8 encoding, creating the file if it doesn't exist, 39 | /// and overwriting if it does. 40 | /// 41 | /// 42 | /// 43 | /// 44 | public static async Task WriteAllText(this FileInfo file, string value) 45 | { 46 | using (var stream = file.Open(FileMode.Create, FileAccess.Write, FileShare.Read)) 47 | using(var writer = new StreamWriter(stream, Encoding.UTF8)) 48 | { 49 | await writer.WriteAsync(value); 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/FileSystem.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace IPFilter.Core 4 | { 5 | public class FileSystem : IFileSystem 6 | { 7 | public TempStream GetTempStream() 8 | { 9 | var file = new FileInfo(Path.GetTempFileName()); 10 | return new TempStream(file); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/FilterCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using IPFilter.Models; 5 | 6 | namespace IPFilter.Core 7 | { 8 | #pragma warning disable 618 9 | public class FilterCollection : SortedSet 10 | { 11 | static readonly IComparer comparer = new MutableFilterEntryComparer(); 12 | 13 | public static IList Sort(IEnumerable source) 14 | { 15 | var list = new List(source); 16 | list.Sort(FilterEntry.Comparer); 17 | return list; 18 | } 19 | 20 | public static IList Merge(IEnumerable source) 21 | { 22 | var list = new List(source); 23 | list.Sort(FilterEntry.Comparer); 24 | 25 | for (var i = 0; i < list.Count; i++) 26 | { 27 | var filter = list[i]; 28 | 29 | // Keep peeking at the next entries to see if they fit in range, in which case we will merge them. 30 | while ( i + 1 < list.Count ) 31 | { 32 | var next = list[i + 1]; 33 | 34 | if( comparer.Compare(filter, next) == 0) 35 | { 36 | list.RemoveAt(i + 1); 37 | } 38 | else 39 | { 40 | break; 41 | } 42 | } 43 | } 44 | 45 | return list.ToList(); 46 | } 47 | 48 | class MutableFilterEntryComparer : IComparer 49 | { 50 | public int Compare(FilterEntry x, FilterEntry y) 51 | { 52 | if (x == null || y == null) 53 | { 54 | if (x == null && y == null) return 0; 55 | return x == null ? -1 : 1; 56 | } 57 | 58 | if ( FilterEntry.AddressComparer.Compare(x.From, y.From) < 1 && FilterEntry.AddressComparer.Compare(y.From, x.To) <= 1) 59 | { 60 | // The two ranges overlap, so merge them. 61 | x.From = Math.Min(x.From, y.From); 62 | x.To = Math.Max(x.To, y.To); 63 | return 0; 64 | } 65 | 66 | return FilterEntry.Comparer.Compare(x, y); 67 | } 68 | } 69 | } 70 | #pragma warning restore 618 71 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/FilterContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace IPFilter.Core 5 | { 6 | public class FilterContext : IDisposable 7 | { 8 | public FilterContext() 9 | { 10 | UriResolver = new UriResolver(); 11 | Filter = new NullWriter(); 12 | FileSystem = new FileSystem(); 13 | } 14 | 15 | public virtual CancellationToken CancellationToken { get; set; } 16 | 17 | //public IProgress Progress { get; set; } 18 | 19 | public IUriResolver UriResolver { get; set; } 20 | 21 | public IFilterWriter Filter { get; set; } 22 | 23 | public IFileSystem FileSystem { get; set; } 24 | 25 | public void Dispose() 26 | { 27 | Filter?.Dispose(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/IFileSystem.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Core 2 | { 3 | public interface IFileSystem 4 | { 5 | TempStream GetTempStream(); 6 | } 7 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/IFilterEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace IPFilter.Core 4 | { 5 | /// 6 | /// Contract for a type that reads in a list of filters 7 | /// and writes them to a destination. 8 | /// 9 | public interface IFilterEnumerator 10 | { 11 | /// 12 | /// Writes a list of filters to the specified . 13 | /// 14 | /// 15 | /// 16 | /// 17 | Task GetFilters(IFilterWriter writer, FilterContext context); 18 | } 19 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/IFilterWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace IPFilter.Core 5 | { 6 | public interface IFilterWriter : IDisposable 7 | { 8 | Task WriteLineAsync(string line); 9 | 10 | /// 11 | /// Finalizes and flushes the list to output. Typically this will 12 | /// be the file system, and will involve sorting the list and removing duplicates. 13 | /// 14 | Task Flush(); 15 | } 16 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/IPAddressComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IPFilter.Core 4 | { 5 | public class IPAddressComparer : IComparer, IEqualityComparer 6 | { 7 | public int Compare(uint x, uint y) 8 | { 9 | var xAddress = (long) x; 10 | var yAddress = (long) y; 11 | 12 | if (xAddress == yAddress) return 0; 13 | 14 | var distance = xAddress - yAddress; 15 | 16 | if (distance > int.MaxValue) return int.MaxValue; 17 | if (distance < int.MinValue) return int.MinValue; 18 | 19 | return (int)distance; 20 | } 21 | 22 | public bool Equals(uint x, uint y) 23 | { 24 | return x == y; 25 | } 26 | 27 | public int GetHashCode(uint obj) 28 | { 29 | return obj.GetHashCode(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/IUriResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IPFilter.Core 4 | { 5 | public interface IUriResolver 6 | { 7 | Uri Resolve(string url); 8 | } 9 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/NullWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace IPFilter.Core 4 | { 5 | public class NullWriter : IFilterWriter 6 | { 7 | static readonly Task empty = Task.FromResult(1); 8 | 9 | public Task WriteLineAsync(string line) 10 | { 11 | return empty; 12 | } 13 | 14 | public void Flush() 15 | { 16 | 17 | } 18 | 19 | public void Dispose() 20 | { 21 | } 22 | 23 | Task IFilterWriter.Flush() 24 | { 25 | return empty; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Security.Cryptography; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace IPFilter.Cli 9 | { 10 | public static class StreamExtensions 11 | { 12 | /// 13 | /// The default copy buffer size, used in the .NET source, is reputedly : 14 | /// "... the largest multiple of 4096 that is still smaller than the large object heap threshold (85K). 15 | /// The CopyTo/CopyToAsync buffer is short-lived and is likely to be collected at Gen0, and it offers a significant 16 | /// improvement in Copy performance". 17 | /// 18 | public const int DefaultCopyBufferSize = 81920; 19 | 20 | public static async Task ReadAndHashAsync(this Stream source, byte[] buffer, int offset, int count, HashAlgorithm algorithm, CancellationToken cancellationToken) 21 | { 22 | var bytes = await source.ReadAsync(buffer, offset, count, cancellationToken); 23 | 24 | if (bytes == 0) 25 | { 26 | algorithm.TransformFinalBlock(buffer, 0, 0); 27 | } 28 | else 29 | { 30 | algorithm.TransformBlock(buffer, 0, bytes, null, 0); 31 | } 32 | 33 | return bytes; 34 | } 35 | 36 | public static async Task CopyToAsync(this Stream source, Stream destination, CancellationToken cancellationToken) 37 | { 38 | if (source == null) throw new ArgumentNullException(nameof(source)); 39 | if (destination == null) throw new ArgumentNullException(nameof(destination)); 40 | 41 | Debug.Assert(source.CanRead); 42 | Debug.Assert(destination.CanWrite); 43 | 44 | byte[] buffer = new byte[DefaultCopyBufferSize]; 45 | int bytesRead; 46 | while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) 47 | { 48 | await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); 49 | } 50 | } 51 | 52 | public static Task CopyToAndHashAsync(this Stream source, Stream destination, HashAlgorithm hasher, CancellationToken cancellationToken) 53 | { 54 | return CopyToAndHashAsync(source, destination, DefaultCopyBufferSize, hasher, cancellationToken); 55 | } 56 | 57 | public static async Task CopyToAndHashAsync(this Stream source, Stream destination, int bufferSize, HashAlgorithm hasher, CancellationToken cancellationToken) 58 | { 59 | if (source == null) throw new ArgumentNullException(nameof(source)); 60 | if (destination == null) throw new ArgumentNullException(nameof(destination)); 61 | if (hasher == null) throw new ArgumentNullException(nameof(hasher)); 62 | if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize)); 63 | 64 | Debug.Assert(source.CanRead); 65 | Debug.Assert(destination.CanWrite); 66 | 67 | byte[] buffer = new byte[bufferSize]; 68 | int bytesRead; 69 | while ((bytesRead = await source.ReadAndHashAsync(buffer, 0, buffer.Length, hasher, cancellationToken).ConfigureAwait(false)) != 0) 70 | { 71 | await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); 72 | } 73 | 74 | return hasher.Hash; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/TempStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using IPFilter.Cli; 3 | 4 | namespace IPFilter.Core 5 | { 6 | public class TempStream : FileStream 7 | { 8 | readonly FileInfo file; 9 | 10 | public FileInfo File => file; 11 | 12 | public TempStream(FileInfo file) : base(file.FullName, FileMode.Create, FileAccess.Write, FileShare.Read) 13 | { 14 | this.file = new FileInfo(file.FullName); 15 | } 16 | 17 | ~TempStream() 18 | { 19 | Dispose(false); 20 | } 21 | 22 | protected override void Dispose(bool disposing) 23 | { 24 | base.Dispose(disposing); 25 | file.SafeDelete(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Code/IPFilter/Core/UriResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace IPFilter.Core 8 | { 9 | /// 10 | /// Resolves and validates the URIs for various list sources. 11 | /// 12 | public class UriResolver : IUriResolver 13 | { 14 | public Uri Resolve(string url) 15 | { 16 | if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out Uri uri)) return null; 17 | if (!uri.IsAbsoluteUri) return null; 18 | 19 | var builder = new UriBuilder(uri); 20 | 21 | switch (uri.Scheme) 22 | { 23 | case "http": 24 | case "https": 25 | return uri; 26 | 27 | case "file": 28 | return new Uri(Path.GetFullPath(uri.LocalPath)); 29 | 30 | default: 31 | return null; 32 | } 33 | 34 | return null; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Code/IPFilter/FilterService.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter 2 | { 3 | partial class FilterService 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | components = new System.ComponentModel.Container(); 32 | this.ServiceName = "IPFilter"; 33 | } 34 | 35 | #endregion 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Code/IPFilter/FilterService.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceProcess; 2 | 3 | namespace IPFilter 4 | { 5 | partial class FilterService : ServiceBase 6 | { 7 | public FilterService() 8 | { 9 | InitializeComponent(); 10 | } 11 | 12 | protected override void OnStart(string[] args) {} 13 | 14 | protected override void OnStop() {} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Code/IPFilter/FilterServiceInstaller.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Configuration.Install; 3 | using System.ServiceProcess; 4 | 5 | namespace IPFilter 6 | { 7 | [RunInstaller(true)] 8 | public class FilterServiceInstaller : Installer 9 | { 10 | readonly ServiceProcessInstaller installer; 11 | ServiceInstaller serviceInstaller; 12 | 13 | public FilterServiceInstaller() 14 | { 15 | installer = new ServiceProcessInstaller 16 | { 17 | Account = ServiceAccount.User 18 | }; 19 | 20 | serviceInstaller = new ServiceInstaller 21 | { 22 | DelayedAutoStart = true, 23 | Description = "IPFilter Updater", 24 | DisplayName = "IPFilter", 25 | ServiceName = "ipfilter", 26 | StartType = ServiceStartMode.Automatic 27 | }; 28 | 29 | Installers.Add(serviceInstaller); 30 | Installers.Add(installer); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Code/IPFilter/Formats/EmuleWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using IPFilter.Core; 7 | using IPFilter.Models; 8 | 9 | namespace IPFilter.Formats 10 | { 11 | /// 12 | /// Writes out ipfilter.dat for eMule, which aligns the data in space-padded columns e.g."1.2.8.0 - 1.2.8.255 , 0 , Some organization" 13 | /// 14 | class EmuleWriter : IFormatWriter 15 | { 16 | readonly Stream stream; 17 | 18 | public void Dispose() 19 | { 20 | stream.Dispose(); 21 | } 22 | 23 | public EmuleWriter(Stream stream) 24 | { 25 | this.stream = stream; 26 | } 27 | 28 | public async Task Write(IList entries, IProgress progress) 29 | { 30 | var sb = new StringBuilder(255); 31 | var address = new StringBuilder(15); 32 | 33 | using(Benchmark.New("Writing {0} entries", entries.Count)) 34 | using (var writer = new StreamWriter(stream, Encoding.ASCII)) 35 | { 36 | for (var i = 1; i <= entries.Count; i++) 37 | { 38 | var entry = entries[i-1]; 39 | sb.Clear(); 40 | 41 | var from = BitConverter.GetBytes(entry.From); 42 | address.Clear(); 43 | address.Append(from[3]).Append(".").Append(from[2]).Append(".").Append(from[1]).Append(".").Append(from[0]); 44 | sb.Append(address.ToString().PadRight(16)); 45 | 46 | sb.Append("- "); 47 | 48 | var to = BitConverter.GetBytes(entry.To); 49 | address.Clear(); 50 | address.Append(to[3]).Append(".").Append(to[2]).Append(".").Append(to[1]).Append(".").Append(to[0]); 51 | sb.Append(address.ToString().PadRight(16)); 52 | 53 | sb.Append(", ").Append(entry.Level.ToString().PadLeft(3)).Append(" , "); 54 | 55 | sb.Append(entry.Description); 56 | 57 | writer.WriteLine(sb.ToString()); 58 | 59 | if (progress == null) continue; 60 | var percent = (int) Math.Floor((double) (i / entries.Count * 100)); 61 | progress.Report(new ProgressModel(UpdateState.Decompressing, "Updating eMule...", percent)); 62 | } 63 | 64 | progress?.Report(new ProgressModel(UpdateState.Decompressing, "Flushing...", 100)); 65 | await writer.FlushAsync(); 66 | 67 | progress?.Report(new ProgressModel(UpdateState.Decompressing, "Updated eMule.", 100)); 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Code/IPFilter/Formats/FilterFileFormat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IPFilter.Formats 4 | { 5 | [Flags] 6 | enum FilterFileFormat 7 | { 8 | None = 0, 9 | Emule = 1, 10 | P2p = 2, 11 | P2b = 4 12 | } 13 | } -------------------------------------------------------------------------------- /Code/IPFilter/Formats/IFormatWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using IPFilter.Models; 5 | 6 | namespace IPFilter.Formats 7 | { 8 | interface IFormatWriter : IDisposable 9 | { 10 | Task Write(IList entries, IProgress progress); 11 | } 12 | } -------------------------------------------------------------------------------- /Code/IPFilter/Formats/P2pWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using IPFilter.Core; 7 | using IPFilter.Models; 8 | 9 | namespace IPFilter.Formats 10 | { 11 | /// 12 | /// Writes out eMule DAT format, 0-padding integers to 3 digits. e.g."001.009.096.105 - 001.009.096.105 , 000 , Some organization" 13 | /// 14 | class P2pWriter : IFormatWriter 15 | { 16 | readonly Stream stream; 17 | 18 | public void Dispose() 19 | { 20 | stream.Dispose(); 21 | } 22 | 23 | public P2pWriter(Stream stream) 24 | { 25 | this.stream = stream; 26 | } 27 | 28 | public async Task Write(IList entries, IProgress progress) 29 | { 30 | var sb = new StringBuilder(255); 31 | var address = new StringBuilder(15); 32 | 33 | var currentPercentage = -1; 34 | 35 | using (Benchmark.New("Writing {0} entries", entries.Count)) 36 | using (var writer = new StreamWriter(stream, Encoding.ASCII)) 37 | { 38 | for (var i = 1; i <= entries.Count; i++) 39 | { 40 | sb.Clear(); 41 | 42 | var from = BitConverter.GetBytes(entries[i - 1].From); 43 | address.Clear(); 44 | address.Append(from[3].ToString("D3")).Append(".").Append(from[2].ToString("D3")).Append(".").Append(from[1].ToString("D3")).Append(".").Append(from[0].ToString("D3")); 45 | sb.Append(address); 46 | 47 | sb.Append(" - "); 48 | 49 | var to = BitConverter.GetBytes(entries[i - 1].To); 50 | address.Clear(); 51 | address.Append(to[3].ToString("D3")).Append(".").Append(to[2].ToString("D3")).Append(".").Append(to[1].ToString("D3")).Append(".").Append(to[0].ToString("D3")); 52 | sb.Append(address); 53 | 54 | sb.Append(" , ").Append(entries[i - 1].Level.ToString("D3").PadLeft(3)).Append(" , "); 55 | 56 | sb.Append(entries[i - 1].Description); 57 | 58 | writer.WriteLine(sb.ToString()); 59 | 60 | if (progress == null) continue; 61 | var percent = (int)Math.Floor((double)i / entries.Count * 100); 62 | 63 | if (percent > currentPercentage) 64 | { 65 | progress.Report(new ProgressModel(UpdateState.Downloading, "Writing...", percent)); 66 | } 67 | 68 | currentPercentage = percent; 69 | } 70 | 71 | await writer.FlushAsync(); 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Code/IPFilter/IPFilter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net48 6 | true 7 | {34A454A5-5453-424A-81CE-30FF0A2B004B} 8 | Properties 9 | IPFilter 10 | IPFilter 11 | ..\..\Bin\ 12 | ..\..\Bin\ 13 | 14 | 15 | IPFilter.EntryPoint 16 | 17 | 18 | App.ico 19 | false 20 | preview 21 | True 22 | enable 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | MSBuild:Compile 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | MSBuild:Compile 45 | 46 | 47 | MSBuild:Compile 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 4.0 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | Component 72 | 73 | 74 | Component 75 | 76 | 77 | Component 78 | 79 | 80 | 81 | 82 | Never 83 | 84 | 85 | PreserveNewest 86 | 87 | 88 | -------------------------------------------------------------------------------- /Code/IPFilter/ListProviders/DefaultList.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.ListProviders 2 | { 3 | public class DefaultList : IMirrorProvider 4 | { 5 | public string Name => "Default"; 6 | 7 | public string GetUrlForMirror() 8 | { 9 | return "https://github.com/DavidMoore/ipfilter/releases/download/lists/ipfilter.dat.gz"; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Code/IPFilter/ListProviders/IMirrorProvider.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.ListProviders 2 | { 3 | /// 4 | /// Contract for a source that provides mirrors of the file 5 | /// 6 | public interface IMirrorProvider 7 | { 8 | /// 9 | /// The name of the mirror provider 10 | /// 11 | string Name { get; } 12 | 13 | string GetUrlForMirror(); 14 | } 15 | } -------------------------------------------------------------------------------- /Code/IPFilter/ListProviders/MirrorProvidersFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IPFilter.ListProviders 4 | { 5 | static class MirrorProvidersFactory 6 | { 7 | private static readonly IList list = new List {new DefaultList()}; 8 | 9 | public static IList Get() 10 | { 11 | return list; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/ApplicationDetectionResult.cs: -------------------------------------------------------------------------------- 1 | using IPFilter.Native; 2 | 3 | namespace IPFilter.Models 4 | { 5 | using System.IO; 6 | using Apps; 7 | 8 | public class ApplicationDetectionResult 9 | { 10 | public bool IsPresent { get; set; } 11 | 12 | public string Description { get; set; } 13 | 14 | public DirectoryInfo InstallLocation { get; set; } 15 | 16 | public string Version { get; set; } 17 | 18 | public IApplication Application { get; set; } 19 | 20 | public static ApplicationDetectionResult NotFound() 21 | { 22 | return new ApplicationDetectionResult(); 23 | } 24 | 25 | public static ApplicationDetectionResult Create(IApplication application, string description, string installLocation) 26 | { 27 | var directory = PathHelper.GetDirectoryInfo(installLocation); 28 | 29 | return new ApplicationDetectionResult 30 | { 31 | Application = application, 32 | Description = description, 33 | InstallLocation = directory, 34 | IsPresent = true 35 | }; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/CompressionFormat.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Models 2 | { 3 | public enum CompressionFormat 4 | { 5 | None = 0, 6 | GZip = 1, 7 | Zip = 2 8 | } 9 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/FileMirror.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Models 2 | { 3 | public class FileMirror 4 | { 5 | public FileMirror(string id, string name) 6 | { 7 | Id = id; 8 | Name = name; 9 | } 10 | 11 | /// 12 | /// Descriptive name of the mirror e.g. Transact (Canberra, Australia) 13 | /// 14 | public string Name { get; set; } 15 | 16 | /// 17 | /// The id of the mirror e.g. transact 18 | /// 19 | public string Id { get; set; } 20 | 21 | /// 22 | /// The name of the mirror provider 23 | /// 24 | public string Provider { get; set; } 25 | 26 | /// 27 | /// Is the list selected 28 | /// 29 | public bool IsSelected { get; set; } 30 | 31 | public override string ToString() 32 | { 33 | return Name; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/FilterDownloadResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IPFilter.Models 4 | { 5 | using System; 6 | using System.IO; 7 | using System.Net.Http.Headers; 8 | using ListProviders; 9 | 10 | public class FilterDownloadResult : IDisposable 11 | { 12 | public FilterDownloadResult() 13 | { 14 | Entries = new List(100000); 15 | } 16 | 17 | public DateTimeOffset? FilterTimestamp { get; set; } 18 | 19 | public IMirrorProvider MirrorProvider { get; set; } 20 | 21 | public string Uri { get; set; } 22 | 23 | public long? Length { get; set; } 24 | 25 | public Exception Exception { get; set; } 26 | 27 | public MemoryStream Stream { get; set; } 28 | 29 | public CompressionFormat CompressionFormat { get; set; } 30 | 31 | public EntityTagHeaderValue Etag { get; set; } 32 | 33 | public IList Entries { get; set; } 34 | 35 | /// 36 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 37 | /// 38 | public void Dispose() 39 | { 40 | Dispose(true); 41 | } 42 | 43 | void Dispose(bool disposing) 44 | { 45 | if (!disposing || Stream == null) return; 46 | Stream.Dispose(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/FilterSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IPFilter.Models 4 | { 5 | public class FilterSource 6 | { 7 | public bool IsSelected { get; set; } 8 | 9 | public string Provider { get; set; } 10 | 11 | public string Id { get; set; } 12 | 13 | public string Name { get; set; } 14 | 15 | public string Url { get; set; } 16 | 17 | 18 | public DateTimeOffset? LastModified { get; set; } 19 | 20 | public string Username { get; set; } 21 | 22 | public string Password { get; set; } 23 | 24 | private bool Equals(FilterSource other) 25 | { 26 | return string.Equals(Provider, other.Provider, StringComparison.OrdinalIgnoreCase) && string.Equals(Id, other.Id, StringComparison.OrdinalIgnoreCase); 27 | } 28 | 29 | public override bool Equals(object obj) 30 | { 31 | if (ReferenceEquals(null, obj)) return false; 32 | if (ReferenceEquals(this, obj)) return true; 33 | if (obj.GetType() != this.GetType()) return false; 34 | return Equals((FilterSource) obj); 35 | } 36 | 37 | public override int GetHashCode() 38 | { 39 | if (Provider == null || Id == null) return 0; 40 | return (StringComparer.OrdinalIgnoreCase.GetHashCode(Provider) * 397) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(Id); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/FilterUpdateResult.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Models 2 | { 3 | using System; 4 | 5 | public class FilterUpdateResult 6 | { 7 | public TimeSpan? UpdateTime { get; set; } 8 | 9 | public DateTimeOffset? FilterTimestamp { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/ProgressModel.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Models 2 | { 3 | public class ProgressModel 4 | { 5 | /// 6 | /// Initializes a new instance of the class. 7 | /// 8 | public ProgressModel(UpdateState state, string caption, int value) 9 | { 10 | State = state; 11 | Caption = caption; 12 | Value = value; 13 | } 14 | 15 | public UpdateState State { get; set; } 16 | public string Caption { get; set; } 17 | public int Value { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/ProgressModelExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IPFilter.Models 4 | { 5 | public static class ProgressModelExtensions 6 | { 7 | public static void Report(this IProgress progress, UpdateState state, string caption) 8 | { 9 | progress.Report(state, caption, -1); 10 | } 11 | 12 | public static void Report(this IProgress progress, UpdateState state, string caption, int value) 13 | { 14 | progress.Report(new ProgressModel(state, caption, value)); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/UpdateInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace IPFilter.Models 4 | { 5 | [DataContract] 6 | public class UpdateInfo 7 | { 8 | [DataMember(Name="version")] 9 | public string Version { get; set; } 10 | 11 | [DataMember(Name="uri")] 12 | public string Uri { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /Code/IPFilter/Models/UpdateState.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Models 2 | { 3 | public enum UpdateState 4 | { 5 | /// 6 | /// Ready to download 7 | /// 8 | Ready, 9 | 10 | /// 11 | /// The download is in progress 12 | /// 13 | Downloading, 14 | 15 | /// 16 | /// The download is done 17 | /// 18 | Done, 19 | 20 | /// 21 | /// Is download, but the user has clicked "Cancel" and the 22 | /// download should be stopped as soon as possible 23 | /// 24 | Cancelling, 25 | 26 | /// 27 | /// The download was cancelled 28 | /// 29 | Cancelled, 30 | 31 | Decompressing 32 | } 33 | } -------------------------------------------------------------------------------- /Code/IPFilter/Native/PathHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace IPFilter.Native 8 | { 9 | static class PathHelper 10 | { 11 | public static DirectoryInfo GetDirectoryInfo(string path) 12 | { 13 | return GetDirectoryInfo(path, CultureInfo.CurrentCulture); 14 | } 15 | 16 | public static DirectoryInfo GetDirectoryInfo(string path, CultureInfo cultureInfo) 17 | { 18 | if (string.IsNullOrWhiteSpace(path)) return null; 19 | 20 | try 21 | { 22 | // On some cultures, the backslash character (0x5C / 92) is displayed differently, so we will use the culture's codepage 23 | // to read in the directory, and then convert it back, which should fix the backslashes. 24 | 25 | // For example, in Japanese codepage 932, the backslash character is displayed as the Yen symbol e.g C:¥Program Files¥qBittorrent 26 | // If we read in the path bytes using the Japanese codepage of 932, then convert those bytes to a string using the same codepage, 27 | // the yen character will actually get corrected to the backslash. 28 | 29 | // Get the culture's codepage 30 | var encoding = Encoding.GetEncoding(cultureInfo.TextInfo.ANSICodePage); 31 | 32 | var normalizedPath = encoding.GetString(encoding.GetBytes(path)); 33 | 34 | return new DirectoryInfo(normalizedPath); 35 | } 36 | catch (Exception ex) 37 | { 38 | Trace.TraceWarning($"Couldn't normalize the path '{path}'. Culture: {cultureInfo}, CodePage: {cultureInfo.TextInfo.ANSICodePage}"); 39 | Trace.TraceWarning("Error: " + ex); 40 | Trace.TraceInformation($"Falling back to un-normalized path of '{path}'"); 41 | 42 | return new DirectoryInfo(path); 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Code/IPFilter/Native/ProcessInformation.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Native 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | [StructLayout(LayoutKind.Sequential)] 7 | public class ProcessInformation 8 | { 9 | public IntPtr hProcess = IntPtr.Zero; 10 | public IntPtr hThread = IntPtr.Zero; 11 | public int dwProcessId; 12 | public int dwThreadId; 13 | } 14 | } -------------------------------------------------------------------------------- /Code/IPFilter/Native/ProcessManager.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Native 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | public class ProcessManager 8 | { 9 | [DllImport("Kernel32", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)] 10 | public static extern bool CreateProcess( 11 | [MarshalAs(UnmanagedType.LPTStr)]string applicationName, 12 | StringBuilder commandLine, 13 | SecurityAttributes processAttributes, 14 | SecurityAttributes threadAttributes, 15 | bool inheritHandles, 16 | int creationFlags, 17 | IntPtr environment, 18 | [MarshalAs(UnmanagedType.LPTStr)]string currentDirectory, 19 | StartupInfo startupInfo, 20 | ProcessInformation processInformation 21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Code/IPFilter/Native/SafeLocalMemHandle.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Native 2 | { 3 | using System; 4 | using System.Runtime.ConstrainedExecution; 5 | using System.Runtime.InteropServices; 6 | using System.Security; 7 | using System.Security.Permissions; 8 | using Microsoft.Win32.SafeHandles; 9 | 10 | [SuppressUnmanagedCodeSecurity, HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)] 11 | public sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid 12 | { 13 | internal SafeLocalMemHandle() : base(true) {} 14 | 15 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] 16 | internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) 17 | { 18 | base.SetHandle(existingHandle); 19 | } 20 | 21 | [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] 22 | internal static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(string stringSecurityDescriptor, 23 | int stringSDRevision, out SafeLocalMemHandle pSecurityDescriptor, IntPtr securityDescriptorSize); 24 | 25 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll")] 26 | private static extern IntPtr LocalFree(IntPtr hMem); 27 | 28 | protected override bool ReleaseHandle() 29 | { 30 | return (LocalFree(base.handle) == IntPtr.Zero); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Code/IPFilter/Native/SecurityAttributes.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Native 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | [StructLayout(LayoutKind.Sequential)] 7 | public class SecurityAttributes 8 | { 9 | public int nLength = 12; 10 | public SafeLocalMemHandle lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false); 11 | public bool bInheritHandle; 12 | } 13 | } -------------------------------------------------------------------------------- /Code/IPFilter/Native/StartupInfo.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Native 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using Microsoft.Win32.SafeHandles; 6 | 7 | [StructLayout(LayoutKind.Sequential)] 8 | public class StartupInfo 9 | { 10 | public int cb; 11 | public IntPtr lpReserved = IntPtr.Zero; 12 | public IntPtr lpDesktop = IntPtr.Zero; 13 | public IntPtr lpTitle = IntPtr.Zero; 14 | public int dwX; 15 | public int dwY; 16 | public int dwXSize; 17 | public int dwYSize; 18 | public int dwXCountChars; 19 | public int dwYCountChars; 20 | public int dwFillAttribute; 21 | public int dwFlags; 22 | public short wShowWindow; 23 | public short cbReserved2; 24 | public IntPtr lpReserved2 = IntPtr.Zero; 25 | public SafeFileHandle hStdInput = new SafeFileHandle(IntPtr.Zero, false); 26 | public SafeFileHandle hStdOutput = new SafeFileHandle(IntPtr.Zero, false); 27 | public SafeFileHandle hStdError = new SafeFileHandle(IntPtr.Zero, false); 28 | 29 | public StartupInfo() 30 | { 31 | this.dwY = 0; 32 | this.cb = Marshal.SizeOf(this); 33 | } 34 | 35 | public void Dispose() 36 | { 37 | // close the handles created for child process 38 | if (this.hStdInput != null && !this.hStdInput.IsInvalid) 39 | { 40 | this.hStdInput.Close(); 41 | this.hStdInput = null; 42 | } 43 | 44 | if (this.hStdOutput != null && !this.hStdOutput.IsInvalid) 45 | { 46 | this.hStdOutput.Close(); 47 | this.hStdOutput = null; 48 | } 49 | 50 | if (this.hStdError == null || this.hStdError.IsInvalid) return; 51 | 52 | this.hStdError.Close(); 53 | this.hStdError = null; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Code/IPFilter/Native/Win32Api.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace IPFilter.Native 5 | { 6 | public static class Win32Api 7 | { 8 | [DllImport("user32.dll")] 9 | [return: MarshalAs(UnmanagedType.Bool)] 10 | public static extern bool SetForegroundWindow(IntPtr hWnd); 11 | 12 | [DllImport("user32.dll")] 13 | static extern IntPtr GetForegroundWindow(); 14 | 15 | [DllImport("user32.dll")] 16 | static extern bool AttachThreadInput(uint threadToBeAttached, uint threadToBeAttachedTo, bool attach); 17 | 18 | [DllImport("user32.dll")] 19 | static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId); 20 | 21 | [DllImport("kernel32.dll")] 22 | static extern uint GetCurrentThreadId(); 23 | 24 | [DllImport("user32.dll")] 25 | static extern IntPtr SetFocus(IntPtr hWnd); 26 | 27 | public static void BringToFront(IntPtr handle) 28 | { 29 | // Get the handle of the current foreground window 30 | var foregroundWindow = GetForegroundWindow(); 31 | 32 | if (foregroundWindow == IntPtr.Zero) 33 | { 34 | // If the foreground window can't be found (which can happen in some circumstances, 35 | // such as when focus is being switched, or screensaver is active), then the best 36 | // we can do is try to bring the requested window to the front anyway. 37 | SetForegroundWindow(handle); 38 | SetFocus(handle); 39 | return; 40 | } 41 | 42 | // If we're already the foreground window, set focus to make sure. 43 | if (foregroundWindow == handle) 44 | { 45 | SetFocus(handle); 46 | return; 47 | } 48 | 49 | // Get the thread handle for the window that's currently in the foreground. 50 | var windowThread = GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero); 51 | 52 | // Get our current thread handle. 53 | var currentThread = GetCurrentThreadId(); 54 | 55 | try 56 | { 57 | if (currentThread != windowThread) 58 | { 59 | // Attach our thread to the window that holds the thread input, so that 60 | // we now have authority over the foreground window and focus. 61 | AttachThreadInput(currentThread, windowThread, true); 62 | } 63 | 64 | // Move the requested window into the foreground, with focus. 65 | SetForegroundWindow(handle); 66 | SetFocus(handle); 67 | } 68 | finally 69 | { 70 | if (currentThread != windowThread) 71 | { 72 | // Don't forget to detach our thread from the thread input afterwards. 73 | AttachThreadInput(currentThread, windowThread, false); 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Code/IPFilter/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Windows; 4 | 5 | using System; 6 | using System.Runtime.InteropServices; 7 | using System.Runtime.CompilerServices; 8 | 9 | [assembly: AssemblyTrademark("")] 10 | [assembly: AssemblyCulture("")] 11 | [assembly: NeutralResourcesLanguage("en-US")] 12 | [assembly: ComVisible(false)] 13 | [assembly: CLSCompliant(false)] 14 | [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] 15 | [assembly: InternalsVisibleTo("IPFilter.Tests")] -------------------------------------------------------------------------------- /Code/IPFilter/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34014 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace IPFilter.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IPFilter.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Code/IPFilter/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "IPFilter": { 4 | "commandName": "Project", 5 | "commandLineArgs": "" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Code/IPFilter/Services/CacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Headers; 2 | using IPFilter.Cli; 3 | 4 | namespace IPFilter.Services 5 | { 6 | using System; 7 | using System.Deployment.Application; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Threading.Tasks; 11 | using Models; 12 | 13 | class CacheProvider : ICacheProvider 14 | { 15 | static readonly string filterPath; 16 | 17 | static CacheProvider() 18 | { 19 | string dataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "IPFilter"); 20 | 21 | if (ApplicationDeployment.IsNetworkDeployed) 22 | { 23 | dataPath = ApplicationDeployment.CurrentDeployment.DataDirectory; 24 | } 25 | 26 | filterPath = Path.Combine(dataPath, "ipfilter.dat"); 27 | } 28 | 29 | public static string FilterPath 30 | { 31 | get { return filterPath; } 32 | } 33 | 34 | public async Task GetAsync(FilterDownloadResult filter) 35 | { 36 | var file = new FileInfo(filterPath); 37 | if (!file.Exists) return null; 38 | 39 | // Find the Etag 40 | var etagFile = new FileInfo(file.FullName + ".etag"); 41 | if (!etagFile.Exists) return null; 42 | 43 | var result = new FilterDownloadResult 44 | { 45 | FilterTimestamp = file.LastWriteTimeUtc, 46 | Etag = EntityTagHeaderValue.Parse(File.ReadAllText(etagFile.FullName)), 47 | Stream = new MemoryStream((int) file.Length) 48 | }; 49 | 50 | using (var stream = file.OpenRead()) 51 | { 52 | await stream.CopyToAsync(result.Stream); 53 | } 54 | 55 | result.Length = result.Stream.Length; 56 | 57 | return result; 58 | } 59 | 60 | public async Task SetAsync(FilterDownloadResult filter) 61 | { 62 | try 63 | { 64 | if (filter == null || filter.Exception != null) return; 65 | 66 | var file = new FileInfo(filterPath); 67 | 68 | if (file.Directory != null && !file.Directory.Exists) 69 | { 70 | file.Directory.Create(); 71 | } 72 | 73 | Trace.TraceInformation("Writing cached ipfilter to " + filterPath); 74 | filter.Stream.Seek(0, SeekOrigin.Begin); 75 | using (var cacheFile = File.Open(filterPath, FileMode.Create, FileAccess.Write,FileShare.Read)) 76 | { 77 | await filter.Stream.CopyToAsync(cacheFile); 78 | } 79 | 80 | if (filter.FilterTimestamp != null) 81 | { 82 | file.LastWriteTimeUtc = filter.FilterTimestamp.Value.UtcDateTime; 83 | } 84 | 85 | File.WriteAllText(file.FullName + ".etag", filter.Etag.ToString()); 86 | } 87 | catch (Exception ex) 88 | { 89 | Trace.TraceWarning("Couldn't write the cached ipfilter: " + ex.Message); 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /Code/IPFilter/Services/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Web.Script.Serialization; 6 | 7 | namespace IPFilter.Services 8 | { 9 | class Config 10 | { 11 | static readonly Lazy defaultConfig = new Lazy(LoadDefault); 12 | 13 | public static Configuration Default => defaultConfig.Value; 14 | 15 | internal const string DefaultSettings = "ipfilter.json"; 16 | 17 | internal static readonly JavaScriptSerializer serializer = new JavaScriptSerializer(); 18 | 19 | static Configuration LoadDefault() 20 | { 21 | try 22 | { 23 | if (File.Exists(DefaultSettings)) 24 | { 25 | var json = File.ReadAllText(DefaultSettings); 26 | return Parse(json); 27 | } 28 | } 29 | catch (Exception ex) 30 | { 31 | Trace.TraceError($"Couldn't load {DefaultSettings}: " + ex); 32 | } 33 | 34 | return new Configuration(); 35 | } 36 | 37 | public static void Reload() 38 | { 39 | var config = LoadDefault(); 40 | 41 | Config.Default.settings.update.isPreReleaseEnabled = config.settings.update.isPreReleaseEnabled; 42 | Config.Default.settings.update.isDisabled = config.settings.update.isDisabled; 43 | Config.Default.settings.task.isEnabled = config.settings.task.isEnabled; 44 | Config.Default.settings.cache.isEnabled = config.settings.cache.isEnabled; 45 | 46 | Config.Default.outputs.Clear(); 47 | foreach (var output in config.outputs) 48 | { 49 | Config.Default.outputs.Add(output); 50 | } 51 | } 52 | 53 | public static void Save(Configuration config, string path) 54 | { 55 | File.WriteAllText(path,serializer.Serialize(config)); 56 | } 57 | 58 | public static Configuration Parse(string json) 59 | { 60 | return serializer.Deserialize(json); 61 | } 62 | 63 | public class Configuration 64 | { 65 | public Configuration() 66 | { 67 | settings = new Settings(); 68 | outputs = new List(); 69 | } 70 | 71 | public Settings settings { get; set; } 72 | 73 | public ICollection outputs { get; set; } 74 | } 75 | 76 | public class Settings 77 | { 78 | public Settings() 79 | { 80 | update = new UpdateSettings(); 81 | task = new TaskSettings(); 82 | cache = new CacheSettings(); 83 | } 84 | 85 | public UpdateSettings update { get; set; } 86 | 87 | public TaskSettings task { get; set; } 88 | 89 | public CacheSettings cache { get; set; } 90 | } 91 | 92 | public class UpdateSettings 93 | { 94 | public bool isDisabled { get; set; } 95 | 96 | public bool isPreReleaseEnabled { get; set; } 97 | 98 | public bool isCleanupDisabled { get; set; } 99 | } 100 | 101 | public class TaskSettings 102 | { 103 | public bool isEnabled { get; set; } 104 | } 105 | public class CacheSettings 106 | { 107 | public bool isEnabled { get; set; } 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /Code/IPFilter/Services/CryptoHelper.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using System.Security; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | 9 | class CryptoHelper 10 | { 11 | static readonly byte[] entropy = Encoding.Unicode.GetBytes("Salt Is Not A Password"); 12 | 13 | public static string EncryptString(SecureString input) 14 | { 15 | byte[] encryptedData = ProtectedData.Protect(Encoding.Unicode.GetBytes(ToInsecureString(input)), entropy, DataProtectionScope.CurrentUser); 16 | return Convert.ToBase64String(encryptedData); 17 | } 18 | 19 | public static SecureString DecryptString(string encryptedData) 20 | { 21 | try 22 | { 23 | byte[] decryptedData = ProtectedData.Unprotect(Convert.FromBase64String(encryptedData), entropy, DataProtectionScope.CurrentUser); 24 | return ToSecureString(Encoding.Unicode.GetString(decryptedData)); 25 | } 26 | catch 27 | { 28 | return new SecureString(); 29 | } 30 | } 31 | 32 | public static SecureString ToSecureString(string input) 33 | { 34 | var secure = new SecureString(); 35 | foreach (char c in input) 36 | { 37 | secure.AppendChar(c); 38 | } 39 | secure.MakeReadOnly(); 40 | return secure; 41 | } 42 | 43 | public static string ToInsecureString(SecureString input) 44 | { 45 | string returnValue; 46 | IntPtr ptr = Marshal.SecureStringToBSTR(input); 47 | try 48 | { 49 | returnValue = Marshal.PtrToStringBSTR(ptr); 50 | } 51 | finally 52 | { 53 | Marshal.ZeroFreeBSTR(ptr); 54 | } 55 | return returnValue; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Code/IPFilter/Services/DelegateTraceListener.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | 6 | public class DelegateTraceListener : TraceListener 7 | { 8 | readonly Action writeAction; 9 | readonly Action writeLineAction; 10 | 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public DelegateTraceListener(Action writeAction, Action writeLineAction) 15 | { 16 | this.writeAction = writeAction; 17 | this.writeLineAction = writeLineAction; 18 | } 19 | 20 | /// 21 | /// When overridden in a derived class, writes the specified message to the listener you create in the derived class. 22 | /// 23 | /// A message to write. 24 | public override void Write(string message) 25 | { 26 | writeAction?.Invoke(message); 27 | } 28 | 29 | /// 30 | /// When overridden in a derived class, writes a message to the listener you create in the derived class, followed by a line terminator. 31 | /// 32 | /// A message to write. 33 | public override void WriteLine(string message) 34 | { 35 | writeLineAction?.Invoke(message); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Code/IPFilter/Services/Deployment/ClickOnce/IUninstallStep.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services.Deployment 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | public interface IUninstallStep : IDisposable 7 | { 8 | void Prepare(List componentsToRemove); 9 | 10 | void PrintDebugInformation(); 11 | 12 | void Execute(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Code/IPFilter/Services/Deployment/ClickOnce/RemoveStartMenuEntry.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services.Deployment 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | public class RemoveStartMenuEntry : IUninstallStep 9 | { 10 | private readonly UninstallInfo _uninstallInfo; 11 | private List _foldersToRemove; 12 | private List _filesToRemove; 13 | 14 | public RemoveStartMenuEntry(UninstallInfo uninstallInfo) 15 | { 16 | _uninstallInfo = uninstallInfo; 17 | } 18 | 19 | public void Prepare(List componentsToRemove) 20 | { 21 | var programsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Programs); 22 | var folder = Path.Combine(programsFolder, _uninstallInfo.ShortcutFolderName); 23 | var suiteFolder = Path.Combine(folder, _uninstallInfo.ShortcutSuiteName ?? string.Empty); 24 | var shortcut = Path.Combine(suiteFolder, _uninstallInfo.ShortcutFileName + ".appref-ms"); 25 | var supportShortcut = Path.Combine(suiteFolder, _uninstallInfo.SupportShortcutFileName + ".url"); 26 | 27 | var desktopFolder = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); 28 | var desktopShortcut = Path.Combine(desktopFolder, _uninstallInfo.ShortcutFileName + ".appref-ms"); 29 | 30 | _filesToRemove = new List(); 31 | if (File.Exists(shortcut)) _filesToRemove.Add(shortcut); 32 | if (File.Exists(supportShortcut)) _filesToRemove.Add(supportShortcut); 33 | if (File.Exists(desktopShortcut)) _filesToRemove.Add(desktopShortcut); 34 | 35 | _foldersToRemove = new List(); 36 | if (Directory.Exists(suiteFolder) && Directory.GetFiles(suiteFolder).All(d => _filesToRemove.Contains(d))) 37 | { 38 | _foldersToRemove.Add(suiteFolder); 39 | 40 | if (Directory.GetDirectories(folder).Count() == 1 && !Directory.GetFiles(folder).Any()) 41 | _foldersToRemove.Add(folder); 42 | } 43 | } 44 | 45 | public void PrintDebugInformation() 46 | { 47 | if (_foldersToRemove == null) 48 | throw new InvalidOperationException("Call Prepare() first."); 49 | 50 | var programsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Programs); 51 | Console.WriteLine("Remove start menu entries from " + programsFolder); 52 | 53 | foreach (var file in _filesToRemove) 54 | { 55 | Console.WriteLine("Delete file " + file); 56 | } 57 | 58 | foreach (var folder in _foldersToRemove) 59 | { 60 | Console.WriteLine("Delete folder " + folder); 61 | } 62 | 63 | Console.WriteLine(); 64 | } 65 | 66 | public void Execute() 67 | { 68 | if (_foldersToRemove == null) 69 | throw new InvalidOperationException("Call Prepare() first."); 70 | 71 | try 72 | { 73 | foreach (var file in _filesToRemove) 74 | { 75 | File.Delete(file); 76 | } 77 | 78 | foreach (var folder in _foldersToRemove) 79 | { 80 | Directory.Delete(folder, false); 81 | } 82 | } 83 | catch (IOException) 84 | { 85 | } 86 | } 87 | 88 | public void Dispose() 89 | { 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Code/IPFilter/Services/Deployment/ClickOnce/RemoveUninstallEntry.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services.Deployment 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Microsoft.Win32; 6 | 7 | public class RemoveUninstallEntry : IUninstallStep 8 | { 9 | private readonly UninstallInfo _uninstallInfo; 10 | private RegistryKey _uninstall; 11 | 12 | public RemoveUninstallEntry(UninstallInfo uninstallInfo) 13 | { 14 | _uninstallInfo = uninstallInfo; 15 | } 16 | 17 | public void Prepare(List componentsToRemove) 18 | { 19 | _uninstall = Registry.CurrentUser.OpenSubKey(UninstallInfo.UninstallRegistryPath, true); 20 | } 21 | 22 | public void PrintDebugInformation() 23 | { 24 | if (_uninstall == null) 25 | throw new InvalidOperationException("Call Prepare() first."); 26 | 27 | Console.WriteLine("Remove uninstall info from " + _uninstall.OpenSubKey(_uninstallInfo.Key).Name); 28 | 29 | Console.WriteLine(); 30 | } 31 | 32 | public void Execute() 33 | { 34 | if (_uninstall == null) 35 | throw new InvalidOperationException("Call Prepare() first."); 36 | 37 | _uninstall.DeleteSubKey(_uninstallInfo.Key); 38 | } 39 | 40 | public void Dispose() 41 | { 42 | if (_uninstall != null) 43 | { 44 | _uninstall.Close(); 45 | _uninstall = null; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Code/IPFilter/Services/Deployment/ClickOnce/UninstallInfo.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services.Deployment 2 | { 3 | using System; 4 | using System.Linq; 5 | using Microsoft.Win32; 6 | 7 | public class UninstallInfo 8 | { 9 | public const string UninstallRegistryPath = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; 10 | 11 | UninstallInfo() {} 12 | 13 | public static UninstallInfo Find(string appName) 14 | { 15 | var uninstall = Registry.CurrentUser.OpenSubKey(UninstallRegistryPath); 16 | if (uninstall == null) return null; 17 | 18 | return (from app in uninstall.GetSubKeyNames() let sub = uninstall.OpenSubKey(app) 19 | where sub != null && sub.GetValue("DisplayName") as string == appName 20 | select new UninstallInfo 21 | { 22 | Key = app, 23 | UninstallString = sub.GetValue("UninstallString") as string, 24 | ShortcutFolderName = sub.GetValue("ShortcutFolderName") as string, 25 | ShortcutSuiteName = sub.GetValue("ShortcutSuiteName") as string, 26 | ShortcutFileName = sub.GetValue("ShortcutFileName") as string, 27 | SupportShortcutFileName = sub.GetValue("SupportShortcutFileName") as string, 28 | Version = sub.GetValue("DisplayVersion") as string 29 | }).FirstOrDefault(); 30 | } 31 | 32 | public string Key { get; set; } 33 | 34 | public string UninstallString { get; private set; } 35 | 36 | public string ShortcutFolderName { get; set; } 37 | 38 | public string ShortcutSuiteName { get; set; } 39 | 40 | public string ShortcutFileName { get; set; } 41 | 42 | public string SupportShortcutFileName { get; set; } 43 | 44 | public string Version { get; set; } 45 | 46 | public string GetPublicKeyToken() 47 | { 48 | var token = UninstallString.Split(',') 49 | .First(s => s.Trim().StartsWith("PublicKeyToken=", StringComparison.Ordinal)).Substring(16); 50 | if (token.Length != 16) throw new ArgumentException(); 51 | return token; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Code/IPFilter/Services/Deployment/ClickOnce/Uninstaller.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services.Deployment 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | public class Uninstaller 8 | { 9 | private readonly ClickOnceRegistry _registry; 10 | 11 | public Uninstaller() 12 | : this(new ClickOnceRegistry()) 13 | { 14 | } 15 | 16 | public Uninstaller(ClickOnceRegistry registry) 17 | { 18 | _registry = registry; 19 | } 20 | 21 | public void Uninstall(UninstallInfo uninstallInfo) 22 | { 23 | var toRemove = FindComponentsToRemove(uninstallInfo.GetPublicKeyToken()); 24 | 25 | Console.WriteLine("Components to remove:"); 26 | toRemove.ForEach(Console.WriteLine); 27 | Console.WriteLine(); 28 | 29 | var steps = new List 30 | { 31 | new RemoveFiles(), 32 | new RemoveStartMenuEntry(uninstallInfo), 33 | new RemoveRegistryKeys(_registry, uninstallInfo), 34 | new RemoveUninstallEntry(uninstallInfo) 35 | }; 36 | 37 | steps.ForEach(s => s.Prepare(toRemove)); 38 | steps.ForEach(s => s.PrintDebugInformation()); 39 | steps.ForEach(s => s.Execute()); 40 | 41 | steps.ForEach(s => s.Dispose()); 42 | } 43 | 44 | private List FindComponentsToRemove(string token) 45 | { 46 | var components = _registry.Components.Where(c => c.Key.Contains(token)).ToList(); 47 | 48 | var toRemove = new List(); 49 | foreach (var component in components) 50 | { 51 | toRemove.Add(component.Key); 52 | 53 | foreach (var dependency in component.Dependencies) 54 | { 55 | if (toRemove.Contains(dependency)) continue; // already in the list 56 | if (_registry.Components.All(c => c.Key != dependency)) continue; // not a public component 57 | 58 | var mark = _registry.Marks.FirstOrDefault(m => m.Key == dependency); 59 | if (mark != null && mark.Implications.Any(i => components.All(c => c.Key != i.Name))) 60 | { 61 | // don't remove because other apps depend on this 62 | continue; 63 | } 64 | 65 | toRemove.Add(dependency); 66 | } 67 | } 68 | 69 | return toRemove; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Code/IPFilter/Services/Deployment/Updater.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Net.Http.Headers; 5 | using System.Web.Script.Serialization; 6 | 7 | namespace IPFilter.Services 8 | { 9 | using System; 10 | using System.Net.Http; 11 | using System.Threading.Tasks; 12 | using Models; 13 | 14 | class Updater 15 | { 16 | public async Task CheckForUpdateAsync(bool isPreReleaseEnabled = false) 17 | { 18 | const string latestReleases = "https://api.github.com/repos/DavidMoore/IPFilter/releases"; 19 | 20 | using(var handler = new WebRequestHandler()) 21 | using (var client = new HttpClient(handler)) 22 | { 23 | client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json")); 24 | client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("DavidMooreIPFilter", EntryPoint.Version.ToString())); 25 | 26 | // Get the latest releases from GitHub 27 | using (var response = await client.GetAsync(latestReleases,HttpCompletionOption.ResponseHeadersRead)) 28 | { 29 | var content = await response.Content.ReadAsStringAsync(); 30 | response.EnsureSuccessStatusCode(); 31 | 32 | var serializer = new JavaScriptSerializer(); 33 | var results = serializer.Deserialize>(content); 34 | 35 | var latest = results.FirstOrDefault(x => (isPreReleaseEnabled || !x.prerelease) && !x.tag_name.Equals("lists", StringComparison.OrdinalIgnoreCase)); 36 | if (latest == null) 37 | { 38 | Trace.TraceWarning("Couldn't find a release from the list returned by GitHub"); 39 | return null; 40 | } 41 | 42 | var asset = latest.assets.FirstOrDefault(x => x.name.Equals("IPFilter.msi", StringComparison.OrdinalIgnoreCase)); 43 | if (asset == null) 44 | { 45 | Trace.TraceWarning("Couldn't find installer in the release assets for " + latest.name); 46 | return null; 47 | } 48 | 49 | var info = new UpdateInfo 50 | { 51 | Version = latest.tag_name, 52 | Uri = asset.browser_download_url.ToString() 53 | }; 54 | 55 | return info; 56 | } 57 | } 58 | } 59 | 60 | class GitHubRelease 61 | { 62 | public string name { get; set; } 63 | 64 | public string tag_name { get; set; } 65 | 66 | public ICollection assets { get; set; } 67 | 68 | public bool prerelease { get; set; } 69 | } 70 | 71 | class GitHubAsset 72 | { 73 | public long id { get; set; } 74 | 75 | public string name { get; set; } 76 | 77 | public Uri browser_download_url { get; set; } 78 | 79 | public string content_type { get; set; } 80 | 81 | public long size { get; set; } 82 | 83 | public DateTimeOffset created_at { get; set; } 84 | 85 | public DateTimeOffset updated_at { get; set; } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Code/IPFilter/Services/DestinationPathsProvider.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using Properties; 9 | 10 | public class DestinationPathsProvider 11 | { 12 | public IEnumerable GetDestinations(params string[] values) 13 | { 14 | if (values == null) return Enumerable.Empty(); 15 | 16 | return values.Select(Environment.ExpandEnvironmentVariables) 17 | .Select(TrimSeparatorsAndWhitespace) 18 | .Distinct(StringComparer.OrdinalIgnoreCase); 19 | } 20 | 21 | string TrimSeparatorsAndWhitespace(string value) 22 | { 23 | // TODO: Handle unicode whitespace? 24 | char[] trimChars = {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, ' ', '\t'}; 25 | 26 | return value.TrimStart().TrimEnd(trimChars); 27 | } 28 | 29 | public IEnumerable GetDestinations() 30 | { 31 | try 32 | { 33 | // Try to combine our defaults with the custom ones 34 | if (Config.Default.outputs == null) 35 | { 36 | return Enumerable.Empty(); 37 | } 38 | 39 | return Config.Default.outputs.Select(ParseCustomPath); 40 | } 41 | catch (Exception ex) 42 | { 43 | Trace.TraceWarning("Had trouble getting the custom paths: " + ex); 44 | return Enumerable.Empty(); 45 | } 46 | } 47 | 48 | PathSetting ParseCustomPath(string arg) 49 | { 50 | var separatorIndex = arg.IndexOf(';'); 51 | var name = "(Untitled)"; 52 | string path; 53 | 54 | if (separatorIndex > -1) 55 | { 56 | name = arg.Substring(0, separatorIndex); 57 | path = arg.Substring(separatorIndex); 58 | } 59 | else 60 | { 61 | path = arg; 62 | } 63 | 64 | path = TrimSeparatorsAndWhitespace(path); 65 | 66 | return new PathSetting(name, path); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Code/IPFilter/Services/ICacheProvider.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services 2 | { 3 | using System.Threading.Tasks; 4 | using Models; 5 | 6 | public interface ICacheProvider 7 | { 8 | Task GetAsync(FilterDownloadResult filter); 9 | Task SetAsync(FilterDownloadResult filter); 10 | } 11 | } -------------------------------------------------------------------------------- /Code/IPFilter/Services/PathSetting.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.Services 2 | { 3 | public class PathSetting 4 | { 5 | public PathSetting(string name, string path) 6 | { 7 | Name = name; 8 | Path = path; 9 | } 10 | 11 | public string Name { get; set; } 12 | public string Path { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /Code/IPFilter/ViewModels/DelegateCommand.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.ViewModels 2 | { 3 | using System; 4 | using System.Windows.Input; 5 | 6 | /// 7 | /// Implements a command that when executed runs a delegate action. 8 | /// 9 | public class DelegateCommand : ICommand 10 | { 11 | readonly Action action; 12 | readonly Func canExecute; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The action to run when the command is executed. 18 | public DelegateCommand(Action action) 19 | { 20 | if (action == null) throw new ArgumentNullException("action"); 21 | this.action = action; 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The action to run when the command is executed. 28 | /// The function to call to evaluate when the command can be executed. 29 | public DelegateCommand(Action action, Func canExecute) 30 | : this(action) 31 | { 32 | this.canExecute = canExecute; 33 | } 34 | 35 | /// 36 | /// Defines the method that determines whether the command can execute in its current state. 37 | /// 38 | /// 39 | /// true if this command can be executed; otherwise, false. 40 | /// 41 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 42 | public bool CanExecute(object parameter) 43 | { 44 | return canExecute == null || canExecute(parameter); 45 | } 46 | 47 | /// 48 | /// Defines the method to be called when the command is invoked. 49 | /// 50 | /// Data used by the command. If the command does not require data to be passed, this object can be set to null. 51 | public void Execute(object parameter) 52 | { 53 | if (CanExecute(parameter)) action(parameter); 54 | } 55 | 56 | public event EventHandler CanExecuteChanged; 57 | 58 | public void OnCanExecuteChanged() 59 | { 60 | var handler = CanExecuteChanged; 61 | if (handler != null) handler(this, EventArgs.Empty); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Code/IPFilter/ViewModels/Interval.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.ViewModels 2 | { 3 | using System; 4 | 5 | public class Interval 6 | { 7 | /// Initializes a new instance of the class. 8 | public Interval(TimeSpan value, string description) 9 | { 10 | Value = value; 11 | Description = description; 12 | } 13 | 14 | public TimeSpan Value { get; set; } 15 | 16 | public string Description { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Code/IPFilter/ViewModels/MessageBoxHelper.cs: -------------------------------------------------------------------------------- 1 | namespace IPFilter.ViewModels 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Windows; 6 | using System.Windows.Threading; 7 | 8 | class MessageBoxHelper 9 | { 10 | public static MessageBoxResult Show(Dispatcher parent, string title, MessageBoxButton buttons, MessageBoxImage image, MessageBoxResult defaultButton, string message, params object[] args) 11 | { 12 | var formattedMessage = args == null || args.Length == 0 ? message : string.Format(CultureInfo.CurrentCulture, message, args); 13 | 14 | var options = CultureInfo.CurrentCulture.TextInfo.IsRightToLeft ? MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading : 0; 15 | 16 | Window window = null; 17 | 18 | var result = parent.Invoke( DispatcherPriority.Normal, new Func(delegate 19 | { 20 | if (window == null) 21 | { 22 | window = Application.Current.MainWindow; 23 | 24 | //return MessageBox.Show(formattedMessage, title, buttons, image, defaultButton, options); 25 | } 26 | return MessageBox.Show(window, formattedMessage, title, buttons, image, defaultButton, options); 27 | })); 28 | 29 | return (MessageBoxResult)result; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Code/IPFilter/Views/InverseConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace IPFilter.Views 6 | { 7 | [ValueConversion(typeof(bool?), typeof(bool))] 8 | public class InverseBoolConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | return !((bool?) value ?? false); 13 | } 14 | 15 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 16 | { 17 | return !(value as bool?); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Code/IPFilter/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |