├── .gitignore ├── LICENSE ├── README.md ├── Resources └── YChanEx.user.js ├── YChanEx.sln └── src ├── Controls ├── BetterFolderBrowser.cs ├── Controls.projitems ├── Controls.shproj ├── DateTimeCounter.cs ├── Extensions.cs ├── Helpers │ └── HtmlMonkey │ │ ├── AttributeSelector.cs │ │ ├── AttributeSelectorMode.cs │ │ ├── CDataDefinition.cs │ │ ├── DefaultSelectors.cs │ │ ├── HtmlAttribute.cs │ │ ├── HtmlAttributeCollection.cs │ │ ├── HtmlDocument.Async.cs │ │ ├── HtmlDocument.Find.cs │ │ ├── HtmlDocument.cs │ │ ├── HtmlExtensionMethods.cs │ │ ├── HtmlNode.Find.cs │ │ ├── HtmlNode.cs │ │ ├── HtmlNodeCollection.cs │ │ ├── HtmlParseOptions.cs │ │ ├── HtmlParser.cs │ │ ├── HtmlRules.cs │ │ ├── HtmlUtility.cs │ │ ├── README.md │ │ ├── Selector.Async.cs │ │ ├── Selector.Find.cs │ │ ├── Selector.cs │ │ ├── SelectorCollection.Async.cs │ │ ├── SelectorCollection.Find.cs │ │ ├── SelectorCollection.cs │ │ ├── SelectorParsing.cs │ │ ├── TagStack.cs │ │ └── TextParser.cs ├── HttpException.cs ├── RequestMessage.cs ├── ThrottledStream.cs └── WebDecompress.cs ├── YChanEx ├── Arguments.cs ├── Classes │ ├── Chan Parse │ │ ├── EightChan.cs │ │ ├── EightKun.cs │ │ ├── FChan.cs │ │ ├── FoolFuuka.cs │ │ ├── FourChan.cs │ │ ├── FourTwentyChan.cs │ │ ├── ParsersShared.cs │ │ ├── SevenChan.cs │ │ └── U18Chan.cs │ ├── Chans.cs │ ├── FileHandler.cs │ ├── HtmlControl.cs │ ├── NativeMethods.cs │ ├── Networking.cs │ ├── Post Objects │ │ ├── EightChanBoard.cs │ │ ├── EightChanFile.cs │ │ ├── EightChanPost.cs │ │ ├── EightChanThread.cs │ │ ├── EightKunBoard.cs │ │ ├── EightKunFile.cs │ │ ├── EightKunPost.cs │ │ ├── EightKunThread.cs │ │ ├── FChanFile.cs │ │ ├── FChanPost.cs │ │ ├── FoolFuukaBoard.cs │ │ ├── FoolFuukaFile.cs │ │ ├── FoolFuukaPost.cs │ │ ├── FoolFuukaThread.cs │ │ ├── FourChanPost.cs │ │ ├── FourChanThread.cs │ │ ├── FourTwentyChanThread.cs │ │ ├── GenericFile.cs │ │ ├── GenericPost.cs │ │ ├── SevenChanFile.cs │ │ ├── SevenChanPost.cs │ │ ├── U18ChanFile.cs │ │ ├── U18ChanPost.cs │ │ └── U18ChanTag.cs │ ├── PreviousThread.cs │ ├── ProgramSettings.cs │ ├── Proxy.cs │ ├── Serializer.cs │ ├── SimpleCookie.cs │ ├── ThreadData.cs │ ├── ThreadInfo.cs │ └── VolatileHttpClient.cs ├── Config │ ├── Advanced.cs │ ├── Downloads.cs │ ├── General.cs │ ├── Helpers │ │ ├── Config.cs │ │ ├── Cookies.cs │ │ ├── DownloadHistory.cs │ │ ├── IniProvider.cs │ │ └── SystemRegistry.cs │ ├── Initialization.cs │ └── Saved.cs ├── Controls │ ├── ExplorerTreeView.cs │ ├── ExtendedLinkLabel.cs │ ├── ExtendedListView.cs │ ├── ExtendedTextBox.cs │ └── SplitButton.cs ├── CopyData.cs ├── Enums │ ├── ChanType.cs │ ├── FileDownloadStatus.cs │ ├── ProxyType.cs │ ├── ThreadEvent.cs │ ├── ThreadState.cs │ └── ThreadStatus.cs ├── Forms │ ├── frmAbout.cs │ ├── frmAbout.designer.cs │ ├── frmAbout.resx │ ├── frmAddCookie.Designer.cs │ ├── frmAddCookie.cs │ ├── frmAddCookie.resx │ ├── frmDownloader.Designer.cs │ ├── frmDownloader.cs │ ├── frmDownloader.resx │ ├── frmMain.Designer.cs │ ├── frmMain.cs │ ├── frmMain.resx │ ├── frmNewName.Designer.cs │ ├── frmNewName.cs │ ├── frmNewName.resx │ ├── frmSettings.Designer.cs │ ├── frmSettings.cs │ └── frmSettings.resx ├── GlobalNamespaces.cs ├── GlobalSuppressions.cs ├── Interfaces │ └── IMainForm.cs ├── Logging │ ├── DwmCompositionInfo.cs │ ├── DwmCompositionTextInfo.cs │ ├── ExceptionInfo.cs │ ├── ExceptionType.cs │ ├── Forms │ │ ├── frmException.Designer.cs │ │ ├── frmException.cs │ │ ├── frmException.resx │ │ ├── frmLog.Designer.cs │ │ ├── frmLog.cs │ │ └── frmLog.resx │ ├── Log.cs │ └── Natives │ │ ├── DwmComposition.cs │ │ └── DwmNatives.cs ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Resources │ ├── AboutImage.png │ ├── BuildDate.txt │ ├── ProgramIcon.ico │ ├── ProgramIcon_Dead.ico │ ├── SiteIcons │ │ ├── 420chan.ico │ │ ├── 4chan.ico │ │ ├── 7chan.ico │ │ ├── 8chan.ico │ │ ├── 8kun.ico │ │ ├── fchan.ico │ │ ├── foolfuuka.ico │ │ └── u18chan.ico │ └── Status │ │ ├── 404.png │ │ ├── downloading.png │ │ ├── error.png │ │ ├── finished-hashmismatch.png │ │ ├── finished.png │ │ ├── reloaded-downloaded.png │ │ ├── reloaded-hashmismatch.png │ │ ├── reloaded-missing.png │ │ ├── removed-from-thread.png │ │ └── waiting.png ├── Updater │ ├── API Data │ │ ├── GithubAsset.cs │ │ └── GithubData.cs │ ├── Form │ │ ├── frmUpdateAvailable.Designer.cs │ │ ├── frmUpdateAvailable.cs │ │ └── frmUpdateAvailable.resx │ ├── UpdateChecker.cs │ └── Version.cs └── YChanEx.csproj └── fw-runtimes ├── Networking ├── Brotli │ ├── BitReader.cs │ ├── BrotliInputStream.cs │ ├── BrotliRuntimeException.cs │ ├── Context.cs │ ├── Decode.cs │ ├── Dictionary.cs │ ├── Huffman.cs │ ├── HuffmanTreeGroup.cs │ ├── IntReader.cs │ ├── Prefix.cs │ ├── RunningState.cs │ ├── State.cs │ ├── Transform.cs │ ├── Utils.cs │ └── WordTransformType.cs ├── CookieParser.cs ├── DelegateHandlerHelpers.cs └── SocksSharp │ ├── Extensions │ ├── HttpHeadersExtensions.cs │ └── StringExtensions.cs │ ├── Handlers │ ├── Socks4ClientHandler.cs │ ├── Socks4aClientHandler.cs │ └── Socks5ClientHandler.cs │ ├── Helpers │ ├── ContentHelper.cs │ ├── ExceptionHelper.cs │ ├── HostHelper.cs │ └── ReceiveHelper.cs │ ├── Proxy │ ├── Clients │ │ ├── Socks4.cs │ │ ├── Socks4a.cs │ │ └── Socks5.cs │ ├── IProxy.cs │ ├── IProxyClient.cs │ ├── IProxySettings.cs │ ├── IProxySettingsFluent.cs │ ├── ProxyClient.cs │ ├── ProxyException.cs │ ├── ProxySettings.cs │ ├── Request │ │ └── RequestBuilder.cs │ └── Response │ │ ├── IResponseBuilder.cs │ │ └── ResponseBuilder.cs │ └── ProxyClientHandler.cs ├── System ├── Diagnostics │ ├── CodeAnalysis │ │ ├── AllowNullAttribute.cs │ │ ├── DisallowNullAttribute.cs │ │ ├── DoesNotReturnAttribute.cs │ │ ├── DoesNotReturnIfAttribute.cs │ │ ├── ExperimentalAttribute.cs │ │ ├── MaybeNullAttribute.cs │ │ ├── MaybeNullWhenAttribute.cs │ │ ├── MemberNotNullAttribute.cs │ │ ├── MemberNotNullWhenAttribute.cs │ │ ├── NotNullAttribute.cs │ │ ├── NotNullIfNotNullAttribute.cs │ │ ├── NotNullWhenAttribute.cs │ │ └── SetsRequiredMembersAttribute.cs │ └── CompilerServices │ │ ├── CollectionBuilderAttribute.cs │ │ └── StringSyntaxAttribute.cs ├── Index.cs ├── Range.cs └── Runtime │ └── CompilerServices │ ├── CallerArgumentExpressionAttribute.cs │ ├── CompilerFeatureRequiredAttribute.cs │ ├── IsExternalInit.cs │ ├── RequiredMemberAttribute.cs │ ├── RuntimeHelpers.cs │ └── UnsafeAccessorAttribute.cs ├── fw-runtimes.projitems └── fw-runtimes.shproj /README.md: -------------------------------------------------------------------------------- 1 | # YChanEx 2 | Thread downloader for some imageboards. 3 | 4 | You can get the original (and outdated) application here: 5 | https://sourceforge.net/projects/ychan/ 6 | 7 | # Prerequisites 8 | Windows & .NET 4.5 (Or WINE, for Linux. But I have not tested it on WINE. Yet.) 9 | 10 | # Protocol 11 | This supports a browser protocol ("ychanex:") for downloading through browsers. I included a very basic plugin for this (since I'm not very good at javascript it will not blend in too well) that adds a link on 4chan, 420chan, 7chan, 8chan, and u18chan (fchan is weird with their links). The protocol can be installed through the application (requires administrative permissions to install, it writes to the registry). 12 | -------------------------------------------------------------------------------- /Resources/YChanEx.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name YChanEx Download Buttons 3 | // @namespace murrty 4 | // @description Adds a download button to threads on supported boards. May not work for all sites. 5 | // @include http*://boards.4chan.org/*/thread/* 6 | // @include http*://boards.4channel.org/*/thread/* 7 | // @include http*://boards.420chan.org/*/res/* 8 | // @include https://*.7chan.org/*/res/* 9 | // @include http*://8ch.net/*/res/* 10 | // @include http*://8chan.moe/*/res/* 11 | // @include http*://u18chan.com/board/u18chan/*/topic/* 12 | // @version 1.0 13 | // @updateURL https://raw.githubusercontent.com/murrty/YChanEx/master/Resources/YChanEx.user.js 14 | // @grant none 15 | // ==/UserScript== 16 | 17 | var downloaddiv = document.createElement('div'); 18 | var downloadlink = document.createElement('a'); 19 | var divelement; 20 | var chan = -1; 21 | var url = document.URL; 22 | 23 | downloaddiv.id = "downloadDiv"; 24 | 25 | downloadlink.id = "download-thread"; 26 | downloadlink.title = "Download this thread"; 27 | downloadlink.appendChild(document.createTextNode('download thread')); 28 | 29 | if (document.URL.indexOf('4chan.org/') > -1 || document.URL.indexOf('4channel.org/') > -1) { 30 | chan = 0; 31 | } 32 | else if (document.URL.indexOf('420chan.org/') > -1) { 33 | chan = 1; 34 | } 35 | else if (document.URL.indexOf('7chan.org/') > -1) { 36 | chan = 2; 37 | } 38 | else if (document.URL.indexOf('8ch.net/') > -1 || document.URL.indexOf('8chan.moe') > -1) { 39 | chan = 3; 40 | } 41 | else if (document.URL.indexOf('fchan.us/') > -1) { 42 | chan = 4; 43 | } 44 | else if (document.URL.indexOf('u18chan.com/') > -1) { 45 | chan = 5; 46 | } 47 | 48 | switch (chan) { 49 | case 0: { 50 | // 4CHAN 51 | divelement = document.getElementById('op'); 52 | 53 | downloadlink.className = "qr-link"; 54 | } break; 55 | 56 | case 1: { 57 | // 420CHAN 58 | divelement = document.getElementById('delform'); 59 | 60 | downloadlink.className = "fg-button ui-state-default"; 61 | downloadlink.style = "padding: 2px 4px 2px;" 62 | } break; 63 | 64 | case 2: { 65 | //7chan 66 | divelement = document.getElementById('delform'); 67 | } break; 68 | 69 | case 3: { 70 | //8chan 71 | divelement = document.getElementById('thread_' + document.URL.split('/')[5].replace(".html", "")); 72 | } break; 73 | 74 | case 4: { 75 | //fchan 76 | // not viable at this time. 77 | } break; 78 | 79 | case 5: { 80 | //u18chan 81 | divelement = document.getElementById('FirstPost'); 82 | 83 | downloadlink.className = "TagBox"; 84 | url = document.URL.replace("board/u18chan/", ""); 85 | } break; 86 | } 87 | 88 | downloadlink.href = "ychanex:" + url; 89 | downloaddiv.appendChild(downloadlink); 90 | divelement.parentNode.insertBefore(downloaddiv, divelement); -------------------------------------------------------------------------------- /YChanEx.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.31911.260 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resources", "Resources", "{0BFFBED3-4512-4194-AFE0-FF802756A43E}" 7 | ProjectSection(SolutionItems) = preProject 8 | TextFile1.json = TextFile1.json 9 | Resources\YChanEx.user.js = Resources\YChanEx.user.js 10 | EndProjectSection 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YChanEx-Tests", "src\YChanEx-Tests\YChanEx-Tests.csproj", "{96B027A0-3FF7-41DB-A76B-614607C88690}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YChanEx", "src\YChanEx\YChanEx.csproj", "{74A112CE-F6CA-4384-B8A0-6D8337378897}" 15 | EndProject 16 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Controls", "src\Controls\Controls.shproj", "{538127EF-8951-4448-BB23-51DCB7968424}" 17 | EndProject 18 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "fw-runtimes", "src\fw-runtimes\fw-runtimes.shproj", "{A0FC4ACA-80C0-4151-B41C-D538B779D1A6}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Debug|x64 = Debug|x64 24 | Release|Any CPU = Release|Any CPU 25 | Release|x64 = Release|x64 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {96B027A0-3FF7-41DB-A76B-614607C88690}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {96B027A0-3FF7-41DB-A76B-614607C88690}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {96B027A0-3FF7-41DB-A76B-614607C88690}.Debug|x64.ActiveCfg = Debug|Any CPU 31 | {96B027A0-3FF7-41DB-A76B-614607C88690}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {96B027A0-3FF7-41DB-A76B-614607C88690}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {96B027A0-3FF7-41DB-A76B-614607C88690}.Release|x64.ActiveCfg = Release|Any CPU 34 | {74A112CE-F6CA-4384-B8A0-6D8337378897}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {74A112CE-F6CA-4384-B8A0-6D8337378897}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {74A112CE-F6CA-4384-B8A0-6D8337378897}.Debug|x64.ActiveCfg = Debug|x64 37 | {74A112CE-F6CA-4384-B8A0-6D8337378897}.Debug|x64.Build.0 = Debug|x64 38 | {74A112CE-F6CA-4384-B8A0-6D8337378897}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {74A112CE-F6CA-4384-B8A0-6D8337378897}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {74A112CE-F6CA-4384-B8A0-6D8337378897}.Release|x64.ActiveCfg = Release|x64 41 | {74A112CE-F6CA-4384-B8A0-6D8337378897}.Release|x64.Build.0 = Release|x64 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {B8F15A3A-3396-49B5-887A-033090AB17C8} 48 | EndGlobalSection 49 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 50 | src\Controls\Controls.projitems*{538127ef-8951-4448-bb23-51dcb7968424}*SharedItemsImports = 13 51 | src\Controls\Controls.projitems*{74a112ce-f6ca-4384-b8a0-6d8337378897}*SharedItemsImports = 4 52 | src\fw-runtimes\fw-runtimes.projitems*{74a112ce-f6ca-4384-b8a0-6d8337378897}*SharedItemsImports = 4 53 | src\fw-runtimes\fw-runtimes.projitems*{a0fc4aca-80c0-4151-b41c-d538b779d1a6}*SharedItemsImports = 13 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /src/Controls/Controls.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 8bd6be67-ae28-4abd-a278-74c35673ad3e 7 | 8 | 9 | Controls 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/Controls/Controls.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {538127EF-8951-4448-BB23-51DCB7968424} 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Controls/DateTimeCounter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System; 4 | internal struct DateTimeCounter { 5 | public DateTime Start; 6 | public DateTime End; 7 | public TimeSpan Duration; 8 | public readonly TimeSpan Elapsed => DateTime.Now - Start; 9 | public DateTimeCounter() { 10 | this.Start = DateTime.Now; 11 | } 12 | public TimeSpan Stop() { 13 | this.End = DateTime.Now; 14 | this.Duration = this.End - this.Start; 15 | return this.Duration; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/AttributeSelectorMode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2022 Jonathan Wood (www.softcircuits.com) 2 | // Licensed under the MIT license. 3 | #nullable enable 4 | namespace SoftCircuits.HtmlMonkey; 5 | /// 6 | /// Specifies the type of comparison to use on an attribute selector. 7 | /// 8 | public enum AttributeSelectorMode : byte { 9 | /// 10 | /// Matches if any word in the attribute value matches a string. 11 | /// When using CSS selectors, this is the mode used. 12 | /// 13 | /// # - Searches a nodes ID. 14 | /// . - Searches a nodes class 15 | /// : - Searches a type. 16 | /// 17 | Contains = 0x0, 18 | /// 19 | /// Matches if any value(s) in the attribute matches any of the values in the selector. 20 | /// 21 | /// Uses the selector operator '?=', div[class?="align-right"] 22 | /// 23 | ContainsAny = 0x1, 24 | /// 25 | /// Matches by comparing the attribute value to a string. 26 | /// This is absolute, so the selector must be equal-to the full value of the attribute. 27 | /// 28 | /// Uses the selector operator '==', div[class=="align-right hidden"] 29 | /// 30 | Match = 0x2, 31 | /// 32 | /// Matches by comparing the attribute value to a regular expression. 33 | /// 34 | /// Uses the selector operator ':=', div[class:="reply-"]. 35 | /// 36 | RegEx = 0x3, 37 | /// 38 | /// Matches if the attribute exists, regardless of the attribute value. 39 | /// 40 | /// The selector operator must only contain the name of the attribute, div[class] 41 | /// 42 | ExistsOnly = 0x4, 43 | /// 44 | /// Matches if the attribute exists and has a value that is not null, empty, or whitespace. 45 | /// 46 | /// The selector must only include the operator '=' with no value, div[class=] 47 | /// 48 | ExistsWithValue = 0x5, 49 | } 50 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/CDataDefinition.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2022 Jonathan Wood (www.softcircuits.com) 2 | // Licensed under the MIT license. 3 | #nullable enable 4 | namespace SoftCircuits.HtmlMonkey; 5 | using System; 6 | /// 7 | /// Defines a CDATA segment. These are segments that the library saves and stores, 8 | /// but does not parse the contents. Examples include comments, CDATA blocks and 9 | /// the content of tags with CData attribute. 10 | /// 11 | internal class CDataDefinition { 12 | /// 13 | /// Text that marks the start of the CData block. Must start with 14 | /// in order for the HTML parser to recognize this segment. 15 | /// 16 | public string StartText { get; set; } 17 | 18 | /// 19 | /// Text that marks the end of the CData block. 20 | /// 21 | public string EndText { get; set; } 22 | 23 | /// 24 | /// Gets or sets the string comparison used to compare . 25 | /// 26 | public StringComparison StartComparison { get; set; } 27 | 28 | /// 29 | /// Gets or sets the string comparison used to compare . 30 | /// 31 | public StringComparison EndComparison { get; set; } 32 | 33 | public CDataDefinition() { 34 | StartText = EndText = string.Empty; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/HtmlAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2022 Jonathan Wood (www.softcircuits.com) 2 | // Licensed under the MIT license. 3 | #nullable enable 4 | namespace SoftCircuits.HtmlMonkey; 5 | /// 6 | /// Class that represents a single element attribute. 7 | /// 8 | public class HtmlAttribute { 9 | /// 10 | /// Gets or sets the name of this attribute. 11 | /// 12 | public string Name { get; set; } 13 | 14 | /// 15 | /// Gets or sets the value of this attribute. The library sets this value to 16 | /// an empty string if the attribute has an empty value. It sets it to null 17 | /// if the attribute name was not followed by an equal sign. 18 | /// 19 | public string? Value { get; set; } 20 | 21 | /// 22 | /// Constructs an instance. 23 | /// 24 | public HtmlAttribute() { 25 | Name = string.Empty; 26 | Value = null; 27 | } 28 | 29 | /// 30 | /// Constructs an instance. 31 | /// 32 | /// Name of this attribute. 33 | /// Value of this attribute. The library sets this value 34 | /// to an empty string if the attribute has an empty value. It sets it to 35 | /// null if the attribute name was not followed by an equal sign. 36 | public HtmlAttribute(string name, string? value = null) { 37 | Name = name; 38 | Value = value; 39 | } 40 | 41 | /// 42 | /// Constructs an instance. 43 | /// 44 | /// that contains the initial value 45 | /// for this attribute. 46 | public HtmlAttribute(HtmlAttribute attribute) { 47 | Name = attribute.Name; 48 | Value = attribute.Value; 49 | } 50 | 51 | /// 52 | /// Whether this attribute contains a value. 53 | /// 54 | /// The value to check within the attribute values. 55 | /// if the attribute contains a value; otherwise, . 56 | public bool ContainsValue(string value) { 57 | if (value?.Length > 0) { 58 | string[] values = value.Split(' '); 59 | for (int i = 0; i < values.Length; i++) { 60 | if (values[i].Equals(value)) { 61 | return true; 62 | } 63 | } 64 | } 65 | return false; 66 | } 67 | 68 | /// 69 | /// Whether this attribute contains a value. 70 | /// 71 | /// The value to check within the attribute values. 72 | /// How the strings will be compared. 73 | /// if the attribute contains a value; otherwise, . 74 | public bool ContainsValue(string value, StringComparison comparison) { 75 | if (value?.Length > 0) { 76 | string[] values = value.Split(' '); 77 | for (int i = 0; i < values.Length; i++) { 78 | if (values[i].Equals(value, comparison)) { 79 | return true; 80 | } 81 | } 82 | } 83 | return false; 84 | } 85 | 86 | /// 87 | /// Converts this to a string. 88 | /// 89 | public override string ToString() { 90 | string name = Name ?? "(null)"; 91 | return (Value != null) ? $"{name ?? "(null)"}=\"{Value}\"" : name; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/HtmlDocument.Async.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2022 Jonathan Wood (www.softcircuits.com) 2 | // Licensed under the MIT license. 3 | #nullable enable 4 | namespace SoftCircuits.HtmlMonkey; 5 | using System.IO; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | /// 9 | /// Holds the nodes of a parsed HTML or XML document. Use the 10 | /// property to access these nodes. Use the method to convert the 11 | /// nodes back to markup. 12 | /// 13 | public partial class HtmlDocument { 14 | /// 15 | /// Asynchronously parses an HTML or XML string and returns an instance that 16 | /// contains the parsed nodes. 17 | /// 18 | /// The HTML or XML string to parse. 19 | /// Returns an instance that contains the parsed 20 | /// nodes. 21 | public static async Task FromHtmlAsync(string? html, HtmlParseOptions options = HtmlParseOptions.None) { 22 | return await Task.Run(() => FromHtml(html, options)) 23 | .ConfigureAwait(false); 24 | } 25 | 26 | /// 27 | /// Asynchronously parses an HTML or XML file and returns an instance that 28 | /// contains the parsed nodes. 29 | /// 30 | /// The HTML or XML file to parse. 31 | /// Returns an instance that contains the parsed 32 | /// nodes. 33 | public static async Task FromFileAsync(string path, HtmlParseOptions options = HtmlParseOptions.None) { 34 | #if NETCOREAPP 35 | return await FromHtmlAsync(await File.ReadAllTextAsync(path).ConfigureAwait(false), options) 36 | .ConfigureAwait(false); 37 | #else 38 | return await FromHtmlAsync(await Task.Run(() => File.ReadAllText(path)).ConfigureAwait(false), options) 39 | .ConfigureAwait(false); 40 | #endif 41 | } 42 | 43 | /// 44 | /// Asynchronously parses an HTML or XML file and returns an instance that 45 | /// contains the parsed nodes. 46 | /// 47 | /// The HTML or XML file to parse. 48 | /// The encoding applied to the contents of the file. 49 | /// Returns an instance that contains the parsed 50 | /// nodes. 51 | public static async Task FromFileAsync(string path, Encoding encoding, HtmlParseOptions options = HtmlParseOptions.None) { 52 | #if NETCOREAPP 53 | return await FromHtmlAsync(await File.ReadAllTextAsync(path, encoding).ConfigureAwait(false), options) 54 | .ConfigureAwait(false); 55 | #else 56 | return await FromHtmlAsync(await Task.Run(() => File.ReadAllText(path, encoding)).ConfigureAwait(false), options) 57 | .ConfigureAwait(false); 58 | #endif 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/HtmlDocument.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2022 Jonathan Wood (www.softcircuits.com) 2 | // Licensed under the MIT license. 3 | #nullable enable 4 | namespace SoftCircuits.HtmlMonkey; 5 | using System.IO; 6 | using System.Text; 7 | /// 8 | /// Holds the nodes of a parsed HTML or XML document. Use the 9 | /// property to access these nodes. Use the method to convert the 10 | /// nodes back to markup. 11 | /// 12 | public partial class HtmlDocument { 13 | /// 14 | /// Gets the source document path. May be empty or null if there was no 15 | /// source file. 16 | /// 17 | public string? Path { get; } 18 | 19 | /// 20 | /// Gets the document root nodes. Provides access to all document nodes. 21 | /// 22 | public HtmlNodeCollection RootNodes { get; } 23 | 24 | /// 25 | /// Gets or sets whether the library enforces HTML rules when parsing markup. 26 | /// This setting is global for all instances of this class. 27 | /// 28 | public static bool IgnoreHtmlRules { 29 | get => HtmlRules.IgnoreHtmlRules; 30 | set => HtmlRules.IgnoreHtmlRules = value; 31 | } 32 | 33 | /// 34 | /// Initializes an empty instance. 35 | /// 36 | public HtmlDocument() { 37 | Path = null; 38 | RootNodes = new HtmlNodeCollection(null); 39 | } 40 | 41 | /// 42 | /// Generates an HTML string from the contents of this . 43 | /// 44 | /// A string with the markup for this document. 45 | public string ToHtml() => this.RootNodes.ToHtml(); 46 | 47 | #region Static methods 48 | /// 49 | /// Parses an HTML or XML string and returns an instance that 50 | /// contains the parsed nodes. 51 | /// 52 | /// The HTML or XML string to parse. 53 | /// Returns an instance that contains the parsed 54 | /// nodes. 55 | public static HtmlDocument FromHtml(string? html, HtmlParseOptions options = HtmlParseOptions.None) { 56 | HtmlParser Parser = new(); 57 | HtmlDocument doc = Parser.Parse(html, options); 58 | return doc; 59 | } 60 | 61 | /// 62 | /// Parses an HTML or XML file and returns an instance that 63 | /// contains the parsed nodes. 64 | /// 65 | /// The HTML or XML file to parse. 66 | /// Returns an instance that contains the parsed 67 | /// nodes. 68 | public static HtmlDocument FromFile(string path, HtmlParseOptions options = HtmlParseOptions.None) { 69 | return FromHtml(File.ReadAllText(path), options); 70 | } 71 | 72 | /// 73 | /// Parses an HTML or XML file and returns an instance that 74 | /// contains the parsed nodes. 75 | /// 76 | /// The HTML or XML file to parse. 77 | /// The encoding applied to the contents of the file. 78 | /// Returns an instance that contains the parsed 79 | /// nodes. 80 | public static HtmlDocument FromFile(string path, Encoding encoding, HtmlParseOptions options = HtmlParseOptions.None) { 81 | return FromHtml(File.ReadAllText(path, encoding), options); 82 | } 83 | #endregion 84 | } 85 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/HtmlParseOptions.cs: -------------------------------------------------------------------------------- 1 | namespace SoftCircuits.HtmlMonkey; 2 | /// 3 | /// Options you can set when parsing HTML. You may 'OR' these options together. 4 | /// 5 | [System.Flags] 6 | public enum HtmlParseOptions : byte { 7 | /// 8 | /// Parses HTML using defaults. 9 | /// 10 | None = 0x0, 11 | /// 12 | /// Remove empty nodes from the resulting HTML document. 13 | /// This may remove white-space in places you may expect it. 14 | /// 15 | RemoveEmptyTextNodes = 0x1, 16 | /// 17 | /// Trims text nodes removing any whitespace characters at the start and end. 18 | /// 19 | TrimTextNodes = 0x2, 20 | /// 21 | /// Trims attribute values removing any whitespace characters at the start and end. 22 | /// 23 | TrimAttributeValues = 0x4, 24 | /// 25 | /// Whether to ignore HTML rules when parsing. 26 | /// 27 | IgnoreHtmlRules = 0x8, 28 | /// 29 | /// Whether attributes are allowed to be quoteless. 30 | /// 31 | AllowQuotelessAttributes = 0x10, 32 | /// 33 | /// Whether broken HTML nodes will be ignored. This may fix some sites, but break others. It may also affect how HTML is re-generated. 34 | /// 35 | IgnoreBrokenHtml = 0x20, 36 | /// 37 | /// Whether to enable using nested leveling when broken nodes. 38 | /// It's best to not use this and to ignore mismatched nodes, but you may find success with it. 39 | /// Ignored if is specified. 40 | /// 41 | UseNestLevels = 0x40, 42 | } 43 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/Selector.Async.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace SoftCircuits.HtmlMonkey; 3 | using System.Threading.Tasks; 4 | public partial class Selector { 5 | /// 6 | /// Parses the given selector text asynchronously and returns the corresponding data structure. 7 | /// 8 | /// The selector text to be parsed. 9 | /// 10 | /// Returns the first when the selector contains commas. 11 | /// 12 | /// The parsed selector data structure. 13 | public static Task ParseSelectorAsync(string? selectorText) { 14 | return Task.Run(() => SelectorParsing.ParseSelector(selectorText)); 15 | } 16 | 17 | /// 18 | /// Parses the given selector text asynchronously and returns the corresponding data structures. 19 | /// 20 | /// The selector text to be parsed. 21 | /// 22 | /// Returns multiple s when the selector contains commas. 23 | /// 24 | /// The task representing the work parsing the selector. 25 | public static Task ParseSelectorsAsync(string? selectorText) { 26 | return Task.Run(() => ParseSelectors(selectorText)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/Selector.Find.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace SoftCircuits.HtmlMonkey; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | public partial class Selector { 6 | #region Find 7 | /// 8 | /// Recursively searches the given root node and returns the nodes that matches this 9 | /// selector. 10 | /// 11 | /// Html document to search. 12 | /// The matching nodes. 13 | public IEnumerable Find(HtmlDocument htDoc) { 14 | return Find(htDoc.RootNodes); 15 | } 16 | 17 | /// 18 | /// Recursively searches the given root node and returns the nodes that matches this 19 | /// selector. 20 | /// 21 | /// Root node to search. 22 | /// The matching nodes. 23 | public IEnumerable Find(HtmlNode rootNode) => Find([ rootNode ]); 24 | 25 | /// 26 | /// Recursively searches the list of nodes and returns the nodes that matches this 27 | /// selector. 28 | /// 29 | /// Nodes to search. 30 | /// The matching nodes. 31 | public IEnumerable Find(IEnumerable nodes) { 32 | List? results = null; 33 | bool matchTopLevelNodes = true; 34 | 35 | // Search from this selector on down through its child selectors 36 | for (Selector? selector = this; selector != null; selector = selector.ChildSelector) { 37 | results = []; 38 | FindRecursive(nodes, selector, matchTopLevelNodes, !this.ImmediateChildOnly, results); 39 | // In next iteration, apply nodes that matched this iteration 40 | nodes = results; 41 | matchTopLevelNodes = false; 42 | } 43 | return results?.Distinct() ?? Enumerable.Empty(); 44 | } 45 | #endregion Find 46 | 47 | #region First 48 | /// 49 | /// Recursively searches the given root node and returns the first node that matches this 50 | /// selector. 51 | /// 52 | /// Html document to search. 53 | /// The matching nodes. 54 | public HtmlElementNode First(HtmlDocument htDoc) => Find(htDoc).First(); 55 | 56 | /// 57 | /// Recursively searches the given root node and returns the first node that matches this 58 | /// selector. 59 | /// 60 | /// Root node to search. 61 | /// The matching nodes. 62 | public HtmlElementNode First(HtmlNode rootNode) => Find(rootNode).First(); 63 | 64 | /// 65 | /// Recursively searches the list of nodes and returns the first node that matches this 66 | /// selector. 67 | /// 68 | /// Nodes to search. 69 | /// The matching nodes. 70 | public HtmlElementNode First(IEnumerable nodes) => Find(nodes).First(); 71 | #endregion First 72 | 73 | #region FirstOrDefault 74 | /// 75 | /// Recursively searches the given root node and returns the first node that matches this 76 | /// selector, or if none found. 77 | /// 78 | /// Html document to search. 79 | /// The matching nodes. 80 | public HtmlElementNode? FirstOrDefault(HtmlDocument htDoc) => Find(htDoc).FirstOrDefault(); 81 | 82 | /// 83 | /// Recursively searches the given root node and returns the first node that matches this 84 | /// selector, or if none found. 85 | /// 86 | /// Root node to search. 87 | /// The matching nodes. 88 | public HtmlElementNode? FirstOrDefault(HtmlNode rootNode) => Find(rootNode).FirstOrDefault(); 89 | 90 | /// 91 | /// Recursively searches the list of nodes and returns the first node that matches this 92 | /// selector, or if none found. 93 | /// 94 | /// Nodes to search. 95 | /// The matching nodes. 96 | public HtmlElementNode? FirstOrDefault(IEnumerable nodes) => Find(nodes).FirstOrDefault(); 97 | #endregion FirstOrDefault 98 | } 99 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/SelectorCollection.Async.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2022 Jonathan Wood (www.softcircuits.com) 2 | // Licensed under the MIT license. 3 | #nullable enable 4 | namespace SoftCircuits.HtmlMonkey; 5 | using System.Threading.Tasks; 6 | public partial class SelectorCollection { 7 | /// 8 | /// Parses the given selector text and inserts them into the collection asynchronously. 9 | /// 10 | /// The selector text to be parsed. 11 | /// 12 | /// Inserts multiple s when the selector contains commas. 13 | /// 14 | public Task InsertSelectorsAsync(string? selectorText) { 15 | return Task.Run(() => SelectorParsing.ParseSelectors(selectorText, this)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/SelectorCollection.Find.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace SoftCircuits.HtmlMonkey; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | public partial class SelectorCollection { 6 | #region Find 7 | /// 8 | /// Recursively searches the HtmlDocument using this list of selectors. 9 | /// Returns the matching nodes. Ensures no duplicate nodes are returned. 10 | /// 11 | /// The html document to search. 12 | /// A set of nodes that matches this selector collection. 13 | public IEnumerable Find(HtmlDocument htmlDoc) { 14 | return this.SelectMany(s => s.Find(htmlDoc.RootNodes)) 15 | .Distinct(); 16 | } 17 | 18 | /// 19 | /// Recursively searches the given root node using this list of selectors. 20 | /// Returns the matching nodes. Ensures no duplicate nodes are returned. 21 | /// 22 | /// Root node of nodes to search. 23 | /// A set of nodes that matches this selector collection. 24 | public IEnumerable Find(HtmlNode rootNode) => Find([ rootNode ]); 25 | 26 | /// 27 | /// Recursively searches the given list of nodes using this list of selectors. 28 | /// Returns the matching nodes. Ensures no duplicate nodes are returned. 29 | /// 30 | /// The set of nodes to search. 31 | /// A set of nodes that matches this selector collection. 32 | public IEnumerable Find(IEnumerable nodes) { 33 | return this.SelectMany(s => s.Find(nodes)) 34 | .Distinct(); 35 | } 36 | #endregion Find 37 | 38 | #region First 39 | /// 40 | /// Recursively searches the HtmlDocument using this list of selectors. 41 | /// Returns the first matching node. 42 | /// 43 | /// The html document to search. 44 | /// A set of nodes that matches this selector collection. 45 | public HtmlElementNode First(HtmlDocument htmlDoc) => Find(htmlDoc).First(); 46 | 47 | /// 48 | /// Recursively searches the given root node using this list of selectors. 49 | /// Returns the first matching node. 50 | /// 51 | /// Root node of nodes to search. 52 | /// A set of nodes that matches this selector collection. 53 | public HtmlElementNode First(HtmlNode rootNode) => Find(rootNode).First(); 54 | 55 | /// 56 | /// Recursively searches the given list of nodes using this list of selectors. 57 | /// Returns the first matching node. 58 | /// 59 | /// The set of nodes to search. 60 | /// A set of nodes that matches this selector collection. 61 | public HtmlElementNode First(IEnumerable nodes) => Find(nodes).First(); 62 | #endregion First 63 | 64 | #region FirstOrDefault 65 | /// 66 | /// Recursively searches the HtmlDocument using this list of selectors. 67 | /// Returns the first matching node, or if none found. 68 | /// 69 | /// The html document to search. 70 | /// A set of nodes that matches this selector collection. 71 | public HtmlElementNode? FirstOrDefault(HtmlDocument htmlDoc) => Find(htmlDoc).FirstOrDefault(); 72 | 73 | /// 74 | /// Recursively searches the given root node using this list of selectors. 75 | /// Returns the first matching node, or if none found. 76 | /// 77 | /// Root node of nodes to search. 78 | /// A set of nodes that matches this selector collection. 79 | public HtmlElementNode? FirstOrDefault(HtmlNode rootNode) => Find(rootNode).FirstOrDefault(); 80 | 81 | /// 82 | /// Recursively searches the given list of nodes using this list of selectors. 83 | /// Returns the first matching node, or if none found. 84 | /// 85 | /// The set of nodes to search. 86 | /// A set of nodes that matches this selector collection. 87 | public HtmlElementNode? FirstOrDefault(IEnumerable nodes) => Find(nodes).FirstOrDefault(); 88 | #endregion FirstOrDefault 89 | } 90 | -------------------------------------------------------------------------------- /src/Controls/Helpers/HtmlMonkey/TagStack.cs: -------------------------------------------------------------------------------- 1 | namespace SoftCircuits.HtmlMonkey; 2 | internal sealed class TagStack : System.Collections.Generic.Stack { 3 | /* 4 | private string[] _array; 5 | private int _size; 6 | private int _version; 7 | 8 | private const int DefaultCapacity = 4; 9 | 10 | public TagStack() { 11 | _array = []; 12 | } 13 | 14 | public TagStack(int capacity) { 15 | Throw.IfNegative(capacity); 16 | _array = new string[capacity]; 17 | } 18 | 19 | public TagStack(IEnumerable collection) { 20 | Throw.IfNull(collection); 21 | if (collection is ICollection ic) { 22 | int count = ic.Count; 23 | if (count != 0) { 24 | string[] arr = new string[count]; 25 | ic.CopyTo(arr, 0); 26 | _size = count; 27 | } 28 | } 29 | else { 30 | using var en = collection.GetEnumerator(); 31 | if (en.MoveNext()) { 32 | _array = new string[DefaultCapacity]; 33 | _array[0] = en.Current; 34 | _size++; 35 | while (en.MoveNext()) { 36 | if (_size == _array.Length) { 37 | int newSize = _size << 1; 38 | Throw.IfGreaterThan((uint)newSize, (uint)int.MaxValue); 39 | //if ((uint)newSize > Array.MaxLength) { 40 | // newSize = Array.MaxLength <= _size ? _size + 1 : Array.MaxLength; 41 | //} 42 | Array.Resize(ref _array, newSize); 43 | } 44 | 45 | _array[_size++] = en.Current; 46 | } 47 | 48 | } 49 | else { 50 | _size = 0; 51 | _array = []; 52 | } 53 | } 54 | } 55 | */ 56 | } 57 | -------------------------------------------------------------------------------- /src/Controls/HttpException.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.controls; 3 | using System; 4 | /// 5 | /// Represents an Http exception during a connection. 6 | /// 7 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1194:Implement exception constructors", Justification = "")] 8 | public sealed class HttpException : Exception { 9 | /// 10 | /// Gets the status code associated with the exception. 11 | /// 12 | public System.Net.HttpStatusCode StatusCode { get; } 13 | /// 14 | /// Gets a description of the status. 15 | /// 16 | public string StatusDescription => GetDescription((int)StatusCode); 17 | /// 18 | /// Initializes a new instance. 19 | /// 20 | /// The status code of the exception. 21 | public HttpException(System.Net.HttpStatusCode status) 22 | : base($"The remote server returned an error: {(int)status} - {GetDescription((int)status)}") { 23 | StatusCode = status; 24 | } 25 | 26 | private static string GetDescription(int status) { 27 | return status switch { 28 | 100 => "Continue", 29 | 101 => "Switching Protocols", 30 | 102 => "Processing", 31 | 103 => "Early Hints", 32 | 33 | 200 => "OK", 34 | 201 => "Created", 35 | 202 => "Accepted", 36 | 203 => "Non-Authoritative Information", 37 | 204 => "No Content", 38 | 205 => "Reset Content", 39 | 206 => "Partial Content", 40 | 207 => "Multi-Status", 41 | 208 => "Already Reported", 42 | 226 => "IM Used", 43 | 44 | 300 => "Multiple Choices", 45 | 301 => "Moved Permanently", 46 | 302 => "Found", 47 | 303 => "See Other", 48 | 304 => "Not Modified", 49 | 305 => "Use Proxy", 50 | 307 => "Temporary Redirect", 51 | 308 => "Permanent Redirect", 52 | 53 | 400 => "Bad Request", 54 | 401 => "Unauthorized", 55 | 402 => "Payment Required", 56 | 403 => "Forbidden", 57 | 404 => "Not Found", 58 | 405 => "Method Not Allowed", 59 | 406 => "Not Acceptable", 60 | 407 => "Proxy Authentication Required", 61 | 408 => "Request Timeout", 62 | 409 => "Conflict", 63 | 410 => "Gone", 64 | 411 => "Length Required", 65 | 412 => "Precondition Failed", 66 | 413 => "Request Entity Too Large", 67 | 414 => "Request-Uri Too Long", 68 | 415 => "Unsupported Media Type", 69 | 416 => "Requested Range Not Satisfiable", 70 | 417 => "Expectation Failed", 71 | 421 => "Misdirected Request", 72 | 422 => "Unprocessable Entity", 73 | 423 => "Locked", 74 | 424 => "Failed Dependency", 75 | 426 => "Upgrade Required", // RFC 2817 76 | 428 => "Precondition Required", 77 | 429 => "Too Many Requests", 78 | 431 => "Request Header Fields Too Large", 79 | 451 => "Unavailable For Legal Reasons", 80 | 81 | 500 => "Internal Server Error", 82 | 501 => "Not Implemented", 83 | 502 => "Bad Gateway", 84 | 503 => "Service Unavailable", 85 | 504 => "Gateway Timeout", 86 | 505 => "Http Version Not Supported", 87 | 506 => "Variant Also Negotiates", 88 | 507 => "Insufficient Storage", 89 | 508 => "Loop Detected", 90 | 510 => "Not Extended", 91 | 511 => "Network Authentication Required", 92 | 93 | _ => $"Unknown status code {status}", 94 | }; 95 | } 96 | } -------------------------------------------------------------------------------- /src/Controls/RequestMessage.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System; 4 | using System.Net.Http; 5 | using System.Reflection; 6 | internal sealed class RequestMessage : HttpRequestMessage { 7 | private const string StatusFieldName = // C# 6+ requires the field name to be "_sendStatus" instead. 8 | #if NETCOREAPP 9 | "_sendStatus"; 10 | #else 11 | "sendStatus"; 12 | #endif 13 | internal const string TimeoutKey = "RequestTimeout"; 14 | 15 | private static readonly FieldInfo RequestSentField = typeof(HttpRequestMessage).GetTypeInfo() 16 | .GetField(StatusFieldName, BindingFlags.Instance | BindingFlags.NonPublic) ?? 17 | throw new NullReferenceException("Could not find the request message sent field."); 18 | 19 | public static void ResetRequest(HttpRequestMessage message) { 20 | RequestSentField.SetValue(message, 0); 21 | } 22 | 23 | public void Reset() { 24 | ResetRequest(this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Controls/ThrottledStream.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.controls; 3 | using System; 4 | using System.IO; 5 | using System.Threading; 6 | public class ThrottledStream : Stream { 7 | public long MaxBytesPerSecond { 8 | get => maxBytes; 9 | set { 10 | if (value < 1) 11 | throw new ArgumentOutOfRangeException($"Value {value} cannot be lower than 1 byte."); 12 | 13 | maxBytes = value; 14 | } 15 | } 16 | private long maxBytes; 17 | 18 | private long processed; 19 | private readonly System.Timers.Timer resetTimer; 20 | private readonly AutoResetEvent wh; 21 | private readonly Stream parent; 22 | 23 | public ThrottledStream(Stream ParentStream, long MaxBytesPerSecond){ 24 | wh = new(true); 25 | processed = 0; 26 | resetTimer = new() { 27 | Interval = 1000 28 | }; 29 | resetTimer.Elapsed += (s, e) => { 30 | processed = 0; 31 | wh.Set(); 32 | }; 33 | resetTimer.Start(); 34 | this.MaxBytesPerSecond = MaxBytesPerSecond; 35 | parent = ParentStream; 36 | } 37 | protected void ThrottleStream(long bytes) { 38 | try { 39 | processed += bytes; 40 | if (processed >= maxBytes) 41 | wh.WaitOne(1000); 42 | } 43 | catch { } 44 | } 45 | public override bool CanRead => parent.CanRead; 46 | public override bool CanSeek => parent.CanSeek; 47 | public override bool CanWrite => parent.CanWrite; 48 | public override long Length => parent.Length; 49 | public override long Position { 50 | get => parent.Position; 51 | set => parent.Position = value; 52 | } 53 | 54 | public override void Flush() => parent.Flush(); 55 | public override void Close() { 56 | resetTimer.Stop(); 57 | resetTimer.Close(); 58 | base.Close(); 59 | } 60 | protected override void Dispose(bool disposing) { 61 | resetTimer.Dispose(); 62 | base.Dispose(disposing: disposing); 63 | } 64 | public override int Read(byte[] buffer, int offset, int count) { 65 | int read = parent.Read(buffer: buffer, offset: offset, count: count); 66 | ThrottleStream(count); 67 | return read; 68 | } 69 | public override long Seek(long offset, SeekOrigin origin) => parent.Seek(offset: offset, origin: origin); 70 | public override void SetLength(long value) => parent.SetLength(value: value); 71 | public override void Write(byte[] buffer, int offset, int count) { 72 | ThrottleStream(count); 73 | parent.Write(buffer: buffer, offset: offset, count: count); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Controls/WebDecompress.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.controls; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Threading.Tasks; 6 | internal static class WebDecompress { 7 | internal static async Task Brotli(Stream inputStream, Stream outputStream) { 8 | inputStream.Position = 0; 9 | using Org.Brotli.Dec.BrotliInputStream DecompressorStream = new(inputStream); 10 | await DecompressorStream.CopyToAsync(outputStream); 11 | } 12 | internal static async Task GZip(Stream inputStream, Stream outputStream) { 13 | inputStream.Position = 0; 14 | using GZipStream DecompressorStream = new(inputStream, CompressionMode.Decompress); 15 | await DecompressorStream.CopyToAsync(outputStream); 16 | } 17 | internal static async Task Deflate(Stream inputStream, Stream outputStream) { 18 | inputStream.Position = 0; 19 | using DeflateStream DecompressorStream = new(inputStream, CompressionMode.Decompress); 20 | await DecompressorStream.CopyToAsync(outputStream); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/YChanEx/Arguments.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | internal static class Arguments { 4 | public static string[] Argv { get; set; } = []; 5 | public static bool SetProtocol(string[] argv) { 6 | if (argv?.Length > 0) { 7 | if (argv[0].Equals("-p", StringComparison.OrdinalIgnoreCase) || argv[0].Equals("--protocol", StringComparison.OrdinalIgnoreCase)) { 8 | return true; 9 | } 10 | } 11 | return false; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Chan Parse/FourTwentyChan.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Parsers; 3 | internal static class FourTwentyChan { 4 | // https://boards.420chan.org/(board)/thread/(id)/(semantic) 5 | public static string GetOldHistoryName(string Url) { 6 | if (Url.StartsWith("ychanex:")) { 7 | Url = Url[8..]; 8 | } 9 | if (Url.StartsWith("view-source:")) { 10 | Url = Url[12..]; 11 | } 12 | Url = Networking.CleanURL(Url); 13 | 14 | string[] URLSplit = Url.Split('/'); 15 | return $"/{URLSplit[4]}/ - {URLSplit[6]}"; 16 | } 17 | public static string GetFullBoardName(string board, bool @override) { 18 | if (General.UseFullBoardNameForTitle || @override) { 19 | return board.ToLowerInvariant() switch { 20 | #region Drugs 21 | "weed" => "Cannabis Discussion", 22 | "hooch" => "Alcohol Discussion", 23 | "mdma" => "Ecstasy Discussion", 24 | "psy" => "Psychedelic Discussion", 25 | "stim" => "Stimulant Discussion", 26 | "dis" => "Dissociative Discussion", 27 | "opi" => "Opiate Discussion", 28 | "vape" => "Vaping Discussion", 29 | "tobacco" => "Tobacco Discussion", 30 | "benz" => "Benzo Discussion", 31 | "deli" => "Deliriant Discussion", 32 | "other" => "Other Drugs Discussion", 33 | "jenk" => "Jenkem Discussion", 34 | "detox" => "Detoxing & Rehabilitation", 35 | #endregion 36 | 37 | #region Lifestye 38 | "qq" => "Personal Issues", 39 | "dr" => "Dream Discussion", 40 | "ana" => "Fitness", 41 | "nom" => "Food, Munchies & Cooking", 42 | "vroom" => "Travel & Transportation", 43 | "st" => "Style & Fashion", 44 | "nra" => "Weapons Discussion", 45 | "sd" => "Sexuality Discussion", 46 | "cd" => "Transgender Discussion", 47 | #endregion 48 | 49 | #region Academia 50 | "art" => "Art & Okekai", 51 | "sagan" => "Space... the Final Frontier", 52 | "lang" => "World Languages", 53 | "stem" => "Science, Technology, Engineering & Mathematics", 54 | "his" => "History Discussion", 55 | "crops" => "Growing & Botany", 56 | "howto" => "Guides & Tutorials", 57 | "law" => "Law Discussion", 58 | "lit" => "Books & Literature", 59 | "med" => "Medicine & Health", 60 | "pss" => "Philosophy & Social Sciences", 61 | "tech" => "Computers & Tech Support", 62 | "prog" => "Programming", 63 | #endregion 64 | 65 | #region Media 66 | "1701" => "Star Trek Discussion", 67 | "sport" => "Sports", 68 | "mtv" => "Movies & Television", 69 | "f" => "Flash", 70 | "m" => "Music & Production", 71 | "mma" => "Mixed Martial Arts Discussion", 72 | "616" => "Comics & Web Comics Discussion", 73 | "a" => "Anime & Manga Discussion", 74 | "wooo" => "Professional Wrestling Discussion", 75 | "n" => "World News", 76 | "vg" => "Video Games Discussion", 77 | "po" => "Pokémon Discussion", 78 | "tg" => "Traditional Games", 79 | #endregion 80 | 81 | #region Miscellanea 82 | "420" => "420chan Discussion & Staff Interaction", 83 | "b" => "Random & High Stuff", 84 | "spooky" => "Paranormal Discussion", 85 | "dino" => "Dinosaur Discussion", 86 | "fo" => "Post-apocalyptic", 87 | "ani" => "Animal Discussion", 88 | "nj" => "Netjester AI Conversation Chamber", 89 | "nc" => "Net Characters", 90 | "tinfoil" => "Conspiracy Theories", 91 | "w" => "Dumb Wallpapers Below", 92 | #endregion 93 | 94 | #region Adult 95 | "h" => "Hentai", 96 | #endregion 97 | 98 | _ => $"{board} (Unknown board)" 99 | }; 100 | } 101 | return board; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/FileHandler.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using SoftCircuits.HtmlMonkey; 4 | /// 5 | /// Contains usability methods governing local files. 6 | /// 7 | internal static class FileHandler { 8 | /// 9 | /// The Dictionary of illegal file name characters. 10 | /// 11 | private static readonly Dictionary IllegalCharacters = new() { 12 | { "\\", "_" }, 13 | { "/", "_" }, 14 | { ":", "_" }, 15 | { "*", "_" }, 16 | { "?", "_" }, 17 | { "\"", "_" }, 18 | { "<", "_" }, 19 | { ">", "_" }, 20 | { "|", "_" } 21 | }; 22 | 23 | /// 24 | /// Replaces the illegal file name characters in a string. 25 | /// 26 | /// The string to replace bad characters. 27 | /// The string with the illegal characters filtered out. 28 | public static string ReplaceIllegalCharacters(string Input) { 29 | return IllegalCharacters.Aggregate(Input, (current, replacement) => current.Replace(replacement.Key, replacement.Value)); 30 | } 31 | 32 | /// 33 | /// Replaces the illegal file name characters in a string. 34 | /// 35 | /// The string to replace bad characters. 36 | /// The replacement character to replace it with. 37 | /// The string with the illegal characters filtered out. 38 | public static string ReplaceIllegalCharacters(string Input, string ReplacementCharacter) { 39 | return IllegalCharacters.Aggregate(Input, (current, replacement) => current.Replace(replacement.Key, ReplacementCharacter)); 40 | } 41 | 42 | /// 43 | /// Generates a short 64-char thread name for the title and main form for easier identification. 44 | /// 45 | /// The subtitle of the post, IE the main thing about it. Optional. 46 | /// The main post text, containing the message in the post. 47 | /// The fallback name that will be used if the new name isn't a usable name. 48 | /// If either the subtitle or comment contain text, returns them either grouped or solo; otherwise, the thread ID. 49 | public static string GetShortThreadName(string? Subtitle, string? Comment, string FallbackName) { 50 | string NewName = string.Empty; 51 | 52 | if (string.IsNullOrEmpty(FallbackName)) { 53 | FallbackName = "No fallback name"; 54 | } 55 | 56 | if (Subtitle is not null) { 57 | NewName = Subtitle = Subtitle.Trim(); 58 | } 59 | 60 | if (Comment is not null) { 61 | NewName += (Subtitle?.Length > 0 && Comment.Length > 0 ? " - " : "") + Comment.Trim(); 62 | } 63 | 64 | if (NewName.Length > 0) { 65 | NewName = NewName 66 | .Replace("

", " ") // New lines 67 | .Replace("
", " ") 68 | .Replace("", "") // Weird inserts between URLs 69 | .Replace("", "") // >implying text xd 70 | .Replace("", "") // links that are dead. it's obvious. 71 | .Replace("", "") // close of >implying text xd 72 | //.Replace(">", ">") // These are fixed by WebUtility.HtmlDecode. 73 | //.Replace("<", "<") // But I'm keeping them commented. 74 | //.Replace("&", "&") // Just in case. 75 | .Replace("", "") // the end of any quote-link urls. 76 | .Replace("\n", " ") 77 | .Trim(); // Cleans up any trailing spaces, new-line and the windows \n, too. 78 | 79 | NewName = System.Text.RegularExpressions.Regex.Replace(NewName, "", ""); 80 | NewName = HtmlUtility.Decode(NewName); //Uri.UnescapeDataString(NewName); 81 | 82 | if (NewName.Length > 64) { 83 | NewName = NewName[..64]; 84 | } 85 | 86 | NewName = NewName.Trim(); 87 | } 88 | 89 | return NewName.Length > 0 ? NewName : FallbackName; 90 | } 91 | } -------------------------------------------------------------------------------- /src/YChanEx/Classes/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System.Drawing; 3 | using System.Runtime.InteropServices; 4 | namespace YChanEx { 5 | public static class NativeMethods { 6 | #region HintTextBox & UAC Shield Button 7 | public const int BCM_FIRST = 0x1600; 8 | public const int BCM_SETSHIELD = (BCM_FIRST + 0x000c); 9 | [DllImport("user32.dll")] 10 | public static extern nint SendMessage(nint hWnd, int msg, nint wParam, nint lParam); 11 | #endregion 12 | 13 | #region Hand Cursor 14 | public static readonly nint IDC_HAND = (nint)32649; 15 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 16 | public static extern nint LoadCursor(nint hInstance, nint lpCursorName); 17 | #endregion 18 | 19 | [DllImport("kernel32", CharSet = CharSet.Unicode)] 20 | public static extern int WritePrivateProfileString(string? lpAppName, string lpKeyName, string? lpDefault, string lpFileName); 21 | 22 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 23 | public static extern int GetPrivateProfileString(string? lpAppName, string lpKeyName, string? lpDefault, char[] lpReturnedString, int nSize, string lpFileName); 24 | } 25 | } 26 | 27 | namespace murrty { 28 | public static class NativeMethods { 29 | public const int WM_SETCURSOR = 0x0020; 30 | public const int EM_SETMARGINS = 0xd3; 31 | public const int EC_RIGHTMARGIN = 2; 32 | public const int EC_LEFTMARGIN = 1; 33 | 34 | public static readonly nint IDC_HAND = (nint)32649; 35 | public static readonly nint HandCursor = LoadCursor(0, IDC_HAND); 36 | 37 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 38 | public static extern nint LoadCursor(nint hInstance, nint lpCursorName); 39 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 40 | public static extern nint SetCursor(nint hCursor); 41 | 42 | [DllImport("uxtheme.dll", ExactSpelling = true, CharSet = CharSet.Unicode)] 43 | public static extern int SetWindowTheme(nint hwnd, string pszSubAppName, string? pszSubIdList); 44 | 45 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 46 | public static extern nint SendMessage(nint hWnd, int msg, nint wParam, nint lParam); 47 | 48 | [DllImport("user32.dll", EntryPoint = "GetWindowDC", SetLastError = true)] 49 | [return: MarshalAs(UnmanagedType.SysInt)] 50 | internal static extern nint GetWindowDC(nint hWnd); 51 | 52 | [DllImport("user32.dll", EntryPoint = "ReleaseDC", SetLastError = true, CharSet = CharSet.Auto)] 53 | [return: MarshalAs(UnmanagedType.Bool)] 54 | internal static extern bool ReleaseDC(nint hWnd, nint hDC); 55 | 56 | [DllImport("user32.dll", EntryPoint = "GetWindowRect")] 57 | [return: MarshalAs(UnmanagedType.Bool)] 58 | internal static extern bool GetWindowRect(nint hWnd, ref RECT lpRect); 59 | 60 | [DllImport("gdi32.dll", EntryPoint = "ExcludeClipRect")] 61 | [return: MarshalAs(UnmanagedType.I4)] 62 | internal static extern int ExcludeClipRect(nint hdc, int nLeftrect, int nTopRect, int nRightRect, int nBottomRect); 63 | } 64 | [StructLayout(LayoutKind.Sequential)] 65 | internal struct NCCALCSIZE_PARAMS { 66 | public RECT rgrc0, rgrc1, rgrc2; 67 | [MarshalAs(UnmanagedType.SysInt)] 68 | public nint lppos; 69 | } 70 | 71 | [StructLayout(LayoutKind.Sequential)] 72 | internal struct RECT { 73 | public static readonly RECT Empty = new(0, 0, 0, 0); 74 | 75 | [MarshalAs(UnmanagedType.I4)] 76 | public int left; 77 | [MarshalAs(UnmanagedType.I4)] 78 | public int top; 79 | [MarshalAs(UnmanagedType.I4)] 80 | public int right; 81 | [MarshalAs(UnmanagedType.I4)] 82 | public int bottom; 83 | 84 | public RECT(int left, int top, int right, int bottom) { 85 | this.left = left; 86 | this.top = top; 87 | this.right = right; 88 | this.bottom = bottom; 89 | } 90 | 91 | public static implicit operator Rectangle(RECT rect) => new(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); 92 | } 93 | } -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/EightChanBoard.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Runtime.Serialization; 4 | using SoftCircuits.HtmlMonkey; 5 | [DataContract] 6 | internal sealed class EightChanBoard { 7 | [DataMember(Name = "id")] 8 | public string? BoardId { get; set; } 9 | 10 | [DataMember(Name = "name")] 11 | public string? BoardName { get; set; } 12 | 13 | [DataMember(Name = "description")] 14 | public string? BoardDescription { get; set; } 15 | 16 | public static EightChanBoard? ExtractBoard(string html) { 17 | var htDoc = HtmlDocument.FromHtml(html); 18 | var HeaderNode = htDoc.FirstOrDefault(EightChanBoardSelectors.BoardHeaderSelector); 19 | if (HeaderNode == null) { 20 | return null; 21 | } 22 | 23 | var NameNode = HeaderNode.FirstOrDefault(EightChanBoardSelectors.BoardNameSelector); 24 | if (NameNode == null) { 25 | return null; 26 | } 27 | 28 | string Name = NameNode.Text[(NameNode.Text.IndexOf("/ - ") + 4)..]; 29 | string Description = string.Empty; 30 | 31 | var DescriptionNode = HeaderNode.FirstOrDefault(EightChanBoardSelectors.BoardDescriptionSelector); 32 | if (DescriptionNode != null) { 33 | Description = DescriptionNode.Text; 34 | } 35 | 36 | return new EightChanBoard() { 37 | BoardName = Name, 38 | BoardDescription = Description, 39 | }; 40 | } 41 | } 42 | 43 | internal static class EightChanBoardSelectors { 44 | internal static readonly Selector BoardHeaderSelector = Selector.ParseSelector("> body header[class=boardHeader]"); 45 | internal static readonly Selector BoardNameSelector = Selector.ParseSelector("div > p[id=labelName]"); 46 | internal static readonly Selector BoardDescriptionSelector = Selector.ParseSelector("p[id=labelDescription]"); 47 | } 48 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/EightChanFile.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Drawing; 4 | using System.Runtime.Serialization; 5 | using static YChanEx.Parsers.Helpers.ParsersShared; 6 | [DataContract] 7 | internal sealed class EightChanFile { 8 | [IgnoreDataMember] 9 | public EightChanThread? ParentOp { get; set; } 10 | 11 | [IgnoreDataMember] 12 | public EightChanPost? ParentReply { get; set; } 13 | 14 | [DataMember(Name = "originalName")] 15 | public string? originalName { get; set; } 16 | 17 | [DataMember(Name = "path")] 18 | public string? path { get; set; } 19 | 20 | [DataMember(Name = "thumb")] 21 | public string? thumb { get; set; } 22 | 23 | [DataMember(Name = "mime")] 24 | public string? mime { get; set; } 25 | 26 | [DataMember(Name = "size")] 27 | public long size { get; set; } 28 | 29 | [DataMember(Name = "width")] 30 | public int width { get; set; } 31 | 32 | [DataMember(Name = "height")] 33 | public int height { get; set; } 34 | 35 | [IgnoreDataMember] 36 | public string? id { 37 | get { 38 | if (this.path.IsNullEmptyWhitespace()) { 39 | throw new Exception("Could not get id from 8chan path"); 40 | } 41 | return GetFileNameFromUrl(this.path!, 4); // /.media/... 42 | } 43 | } 44 | 45 | [IgnoreDataMember] 46 | public string? ProperThumbPath { 47 | get { 48 | if (propThumb != null) { 49 | return propThumb; 50 | } 51 | string original = id!; 52 | return propThumb = !thumb!.EndsWith(original, StringComparison.OrdinalIgnoreCase) ? 53 | ("/.media/t_" + original) : thumb; 54 | } 55 | } 56 | [IgnoreDataMember] 57 | private string? propThumb; 58 | 59 | [IgnoreDataMember] 60 | public Size ThumbnailSize { 61 | get { 62 | return GetThumbnailSize(width, height, ParentOp != null); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/EightChanPost.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Runtime.Serialization; 5 | using static YChanEx.Parsers.EightChan; 6 | [DataContract] 7 | internal sealed class EightChanPost { 8 | [IgnoreDataMember] 9 | public EightChanThread? Parent { get; set; } 10 | 11 | [DataMember(Name = "name")] 12 | public string? name { get; set; } 13 | 14 | [DataMember(Name = "signedRole")] 15 | public string? signedRole { get; set; } 16 | 17 | [DataMember(Name = "email")] 18 | public string? email { get; set; } 19 | 20 | [DataMember(Name = "id")] 21 | public string? id { get; set; } 22 | 23 | [DataMember(Name = "subject")] 24 | public string? subject { get; set; } 25 | 26 | [DataMember(Name = "markdown")] 27 | public string? markdown { get; set; } 28 | 29 | [DataMember(Name = "message")] 30 | public string? message { get; set; } 31 | 32 | [DataMember(Name = "postId")] 33 | public ulong postId { get; set; } 34 | 35 | [DataMember(Name = "creation")] 36 | public string? creation { get; set; } // UTC 0:00 37 | 38 | [DataMember(Name = "files")] 39 | public EightChanFile[]? files { get; set; } 40 | 41 | [IgnoreDataMember] 42 | public DateTimeOffset CleanedDateTime => GetPostTime(this); 43 | 44 | [IgnoreDataMember] 45 | [MemberNotNullWhen(true, nameof(files))] 46 | public bool HasFiles => files?.Length > 0; 47 | 48 | [IgnoreDataMember] 49 | [MemberNotNullWhen(true, nameof(files))] 50 | public bool MultiFilePost => HasFiles && files.Length > 1; 51 | 52 | [IgnoreDataMember] 53 | public ulong[]? Quotes => GetQuotedPosts(this); 54 | 55 | public string GetCleanMessage(ThreadInfo Thread) => CleanMessage(this, Thread); 56 | 57 | [OnDeserialized] 58 | void Deserialized(StreamingContext ctx) { 59 | if (this.files?.Length > 0) { 60 | for (int i = 0; i < this.files.Length; i++) { 61 | this.files[i].ParentReply = this; 62 | } 63 | } 64 | } 65 | 66 | public override bool Equals(object? obj) => obj is EightChanPost other && this.Equals(other); 67 | public bool Equals(EightChanPost? other) { 68 | if (other is null) { 69 | return this is null; 70 | } 71 | if (this is null) { 72 | return false; 73 | } 74 | return this.postId == other.postId; 75 | } 76 | 77 | public override int GetHashCode() => unchecked((int)(this.postId & 0x7FFFFFFF)); // Negative-bit ignored. 78 | } 79 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/EightChanThread.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Runtime.Serialization; 5 | using static YChanEx.Parsers.EightChan; 6 | [DataContract] 7 | internal sealed class EightChanThread { 8 | [DataMember(Name = "signedRole")] 9 | public string? signedRole { get; set; } 10 | 11 | [DataMember(Name = "id")] 12 | public string? id { get; set; } 13 | 14 | [DataMember(Name = "name")] 15 | public string? name { get; set; } 16 | 17 | [DataMember(Name = "email")] 18 | public string? email { get; set; } 19 | 20 | //[DataMember(Name = "boardUri")] 21 | //public string? boardUri { get; set; } 22 | 23 | [DataMember(Name = "threadId")] 24 | public ulong threadId { get; set; } 25 | 26 | [DataMember(Name = "subject")] 27 | public string? subject { get; set; } 28 | 29 | [DataMember(Name = "markdown")] 30 | public string? markdown { get; set; } 31 | 32 | [DataMember(Name = "message")] 33 | public string? message { get; set; } 34 | 35 | [DataMember(Name = "creation")] 36 | public string? creation { get; set; } 37 | 38 | [DataMember(Name = "locked")] 39 | public bool locked { get; set; } 40 | 41 | [DataMember(Name = "archived")] 42 | public bool archived { get; set; } 43 | 44 | [DataMember(Name = "pinned")] 45 | public bool pinned { get; set; } 46 | 47 | //[DataMember(Name = "cyclic")] 48 | //public bool cyclic { get; set; } 49 | 50 | [DataMember(Name = "autoSage")] 51 | public bool autoSage { get; set; } 52 | 53 | [DataMember(Name = "files")] 54 | public EightChanFile[]? files { get; set; } 55 | 56 | [DataMember(Name = "posts")] 57 | public EightChanPost[]? posts { get; set; } 58 | 59 | [DataMember(Name = "uniquePosters")] 60 | public int uniquePosters { get; set; } 61 | 62 | //[DataMember(Name = "maxMessageLength")] 63 | //public int maxMessageLength { get; set; } 64 | 65 | //[DataMember(Name = "usesCustomCss")] 66 | //public bool usesCustomCss { get; set; } 67 | 68 | //[DataMember(Name = "wssPort")] 69 | //public int wssPort { get; set; } 70 | 71 | //[DataMember(Name = "wsPort")] 72 | //public int wsPort { get; set; } 73 | 74 | //[DataMember(Name = "usesCustomJs")] 75 | //public bool usesCustomJs { get; set; } 76 | 77 | [DataMember(Name = "boardName")] 78 | public string? boardName { get; set; } 79 | 80 | [DataMember(Name = "boardDescription")] 81 | public string? boardDescription { get; set; } 82 | 83 | //[DataMember(Name = "boardMarkdown")] 84 | //public string boardMarkdown { get; set; } 85 | 86 | //[DataMember(Name = "maxFileCount")] 87 | //public int maxFileCount { get; set; } 88 | 89 | //[DataMember(Name = "maxFileSize")] 90 | //public string maxFileSize { get; set; } 91 | 92 | [IgnoreDataMember] 93 | public DateTimeOffset CleanedDateTime => GetPostTime(this); 94 | 95 | [IgnoreDataMember] 96 | [MemberNotNullWhen(true, nameof(files))] 97 | public bool HasFiles => files?.Length > 0; 98 | 99 | [IgnoreDataMember] 100 | [MemberNotNullWhen(true, nameof(files))] 101 | public bool MultiFilePost => HasFiles && files.Length > 1; 102 | 103 | [IgnoreDataMember] 104 | public ulong[]? Quotes => GetQuotedPosts(this); 105 | 106 | public string GetCleanMessage(ThreadInfo Thread) => CleanMessage(this, Thread); 107 | 108 | [OnDeserialized] 109 | void Deserialized(StreamingContext ctx) { 110 | if (this.files?.Length > 0) { 111 | for (int i = 0; i < this.files.Length; i++) { 112 | this.files[i].ParentOp = this; 113 | } 114 | } 115 | if (this.posts?.Length > 0) { 116 | for (int i = 0; i < this.posts.Length; i++) { 117 | this.posts[i].Parent = this; 118 | } 119 | } 120 | } 121 | 122 | public override bool Equals(object? obj) => obj is EightChanThread other && this.Equals(other); 123 | public bool Equals(EightChanThread? other) { 124 | if (other is null) { 125 | return this is null; 126 | } 127 | if (this is null) { 128 | return false; 129 | } 130 | return this.threadId == other.threadId; 131 | } 132 | 133 | public override int GetHashCode() => unchecked((int)(this.threadId & 0x7FFFFFFF)); // Negative-bit ignored. 134 | } 135 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/EightKunBoard.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Runtime.Serialization; 4 | [DataContract] 5 | internal sealed class EightKunBoard { 6 | [DataMember(Name = "uri")] 7 | public string? uri { get; set; } 8 | 9 | [DataMember(Name = "title")] 10 | public string? title { get; set; } 11 | 12 | [DataMember(Name = "subtitle")] 13 | public string? subtitle { get; set; } 14 | 15 | //[DataMember(Name = "indexed")] 16 | //public int indexed { get; set; } 17 | 18 | [DataMember(Name = "sfw")] 19 | public int sfw { get; set; } 20 | 21 | //[DataMember(Name = "posts_total")] 22 | //public int posts_total { get; set; } 23 | 24 | //[DataMember(Name = "time")] 25 | //public string? time { get; set; } 26 | 27 | //[DataMember(Name = "weight")] 28 | //public double weight { get; set; } 29 | 30 | //[DataMember(Name = "locale")] 31 | //public string? locale { get; set; } 32 | 33 | //[DataMember(Name = "tags")] 34 | //public string[]? tags { get; set; } 35 | 36 | //[DataMember(Name = "max")] 37 | //public uint max { get; set; } 38 | 39 | //[DataMember(Name = "active")] 40 | //public int active { get; set; } 41 | 42 | //[DataMember(Name = "pph")] 43 | //public int pph { get; set; } 44 | 45 | //[DataMember(Name = "ppd")] 46 | //public int ppd { get; set; } 47 | 48 | //[DataMember(Name = "pph_average")] 49 | //public double pph_average { get; set; } 50 | } 51 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/EightKunFile.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Runtime.Serialization; 4 | [DataContract] 5 | internal sealed class EightKunFile { 6 | [IgnoreDataMember] 7 | public EightKunPost? Parent { get; set; } 8 | 9 | [DataMember(Name = "tn_h")] 10 | public int tn_h { get; set; } 11 | 12 | [DataMember(Name = "tn_w")] 13 | public int tn_w { get; set; } 14 | 15 | [DataMember(Name = "h")] 16 | public int h { get; set; } 17 | 18 | [DataMember(Name = "w")] 19 | public int w { get; set; } 20 | 21 | [DataMember(Name = "fsize")] 22 | public int fsize { get; set; } 23 | 24 | [DataMember(Name = "filename")] 25 | public string? filename { get; set; } 26 | 27 | [DataMember(Name = "ext")] 28 | public string? ext { get; set; } 29 | 30 | [DataMember(Name = "tim")] 31 | public string? tim { get; set; } 32 | 33 | [DataMember(Name = "fpath")] 34 | public int fpath { get; set; } 35 | 36 | [DataMember(Name = "spoiler")] 37 | public int spoiler { get; set; } 38 | 39 | [DataMember(Name = "md5")] 40 | public string? md5 { get; set; } 41 | } 42 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/EightKunThread.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Runtime.Serialization; 4 | [DataContract] 5 | internal sealed class EightKunThread { 6 | [DataMember(Name = "posts")] 7 | public EightKunPost[] posts { get; set; } = [ ]; 8 | } 9 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/FChanFile.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Diagnostics; 4 | using System.Runtime.Serialization; 5 | using SoftCircuits.HtmlMonkey; 6 | using static YChanEx.Parsers.FChan; 7 | using static YChanEx.Parsers.Helpers.ParsersShared; 8 | [DataContract] 9 | [DebuggerDisplay("Ext = {Extension}")] 10 | internal sealed class FChanFile { 11 | private const string UrlPrefix = "http://fchan.us"; 12 | private static readonly Selector ThumbnailSelector = Selector.ParseSelector("a > img[class=thumb][width=][height=][src=]"); 13 | private static readonly Selector SourceSelector = Selector.ParseSelector("> small"); 14 | 15 | [IgnoreDataMember] 16 | public FChanPost Parent { get; } 17 | 18 | [DataMember(Name = "file_name")] 19 | public string? FileName { get; set; } 20 | 21 | [DataMember(Name = "extension")] 22 | public string? Extension { get; set; } 23 | 24 | [DataMember(Name = "url")] 25 | public string? Url { get; set; } 26 | 27 | [DataMember(Name = "est_size")] 28 | public long EstimatedSize { get; set; } 29 | 30 | [DataMember(Name = "width")] 31 | public int Width { get; set; } 32 | 33 | [DataMember(Name = "height")] 34 | public int Height { get; set; } 35 | 36 | [DataMember(Name = "thumb_url")] 37 | public string? ThumbnailUrl { get; set; } 38 | 39 | [DataMember(Name = "thumb_width")] 40 | public int ThumbnailWidth { get; set; } 41 | 42 | [DataMember(Name = "thumb_height")] 43 | public int ThumbnailHeight { get; set; } 44 | 45 | [DataMember(Name = "source")] 46 | public string? Source { get; set; } 47 | 48 | public FChanFile(HtmlElementNode MetadataNode, HtmlElementNode ParentNode, FChanPost Parent) { 49 | this.Parent = Parent; 50 | 51 | var FileLink = MetadataNode.Children.FirstOrDefault(DefaultSelectors.A.HrefValue) ?? 52 | throw new ArgumentNullException("Could not find link metadata."); 53 | this.Url = UrlPrefix + FileLink.Attributes["href"]!.Value!; 54 | this.Extension = GetExtension(FileLink.Text, out int extIndex); 55 | this.FileName = FileLink.Text[..extIndex]; 56 | 57 | var ExtraMetadata = MetadataNode.Children.FirstOrDefault(DefaultSelectors.em) ?? 58 | throw new ArgumentNullException("Could not find extra metadata."); 59 | 60 | string[] Metadata = ExtraMetadata.Text.Split(','); 61 | this.EstimatedSize = ConvertSizeToBytes(Metadata[0]); 62 | var Dimensions = ConvertDimensionsToSize(Metadata[1]); 63 | this.Width = Dimensions.Width; 64 | this.Height = Dimensions.Height; 65 | 66 | var ThumbnailNode = ParentNode.Children.FirstOrDefault(ThumbnailSelector) ?? 67 | throw new ArgumentNullException("Could not find thumbnail node."); 68 | this.ThumbnailUrl = UrlPrefix + ThumbnailNode.Attributes["src"]!.Value!; 69 | this.ThumbnailWidth = int.Parse(ThumbnailNode.Attributes["width"]!.Value!); 70 | this.ThumbnailHeight = int.Parse(ThumbnailNode.Attributes["height"]!.Value!); 71 | 72 | var SourceNode = ParentNode.Children.FirstOrDefault(SourceSelector); 73 | if (SourceNode != null) { 74 | this.Source = SourceNode.Attributes["href"]?.Value ?? SourceNode.Text[8..]; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/FoolFuukaBoard.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Runtime.Serialization; 4 | [DataContract] 5 | public class FoolFuukaBoard { 6 | [DataMember] 7 | public string? name { get; set; } 8 | 9 | [DataMember] 10 | public string? shortname { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/FoolFuukaFile.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Runtime.Serialization; 4 | [DataContract] 5 | public sealed class FoolFuukaFile { 6 | [DataMember] 7 | public long media_id { get; set; } 8 | 9 | [DataMember] 10 | public byte spoiler { get; set; } 11 | 12 | [DataMember] 13 | public string? preview_orig { get; set; } 14 | 15 | [DataMember] 16 | public string? media { get; set; } 17 | 18 | [DataMember] 19 | public string? preview_op { get; set; } 20 | 21 | [DataMember] 22 | public string? preview_reply { get; set; } 23 | 24 | [DataMember] 25 | public int preview_w { get; set; } 26 | 27 | [DataMember] 28 | public int preview_h { get; set; } 29 | 30 | [DataMember] 31 | public string? media_filename { get; set; } 32 | 33 | [DataMember] 34 | public int media_w { get; set; } 35 | 36 | [DataMember] 37 | public int media_h { get; set; } 38 | 39 | [DataMember] 40 | public long media_size { get; set; } 41 | 42 | [DataMember] 43 | public string? media_hash { get; set; } 44 | 45 | [DataMember] 46 | public string? media_orig { get; set; } 47 | 48 | [DataMember] 49 | public string? exif { get; set; } 50 | 51 | [DataMember] 52 | public int total { get; set; } 53 | 54 | [DataMember] 55 | public byte banned { get; set; } 56 | 57 | [DataMember] 58 | public string? media_status { get; set; } 59 | 60 | [DataMember] 61 | public string? safe_media_hash { get; set; } 62 | 63 | [DataMember] 64 | public string? remote_media_link { get; set; } 65 | 66 | [DataMember] 67 | public string? media_link { get; set; } 68 | 69 | [DataMember] 70 | public string? thumb_link { get; set; } 71 | 72 | [DataMember] 73 | public string? media_filename_processed { get; set; } 74 | } 75 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/FoolFuukaPost.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Diagnostics; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Runtime.Serialization; 6 | [DataContract] 7 | [DebuggerDisplay("{num} | HasFile = {HasFile}")] 8 | public sealed class FoolFuukaPost { 9 | [DataMember] 10 | public ulong doc_id { get; set; } 11 | 12 | [DataMember] 13 | public ulong num { get; set; } 14 | 15 | [DataMember] 16 | public int subnum { get; set; } 17 | 18 | [DataMember] 19 | public ulong thread_num { get; set; } 20 | 21 | [DataMember] 22 | public byte op { get; set; } 23 | 24 | [DataMember] 25 | public long timestamp { get; set; } 26 | 27 | [DataMember] 28 | public int timestamp_expired { get; set; } 29 | 30 | [DataMember] 31 | public string? capcode { get; set; } 32 | 33 | [DataMember] 34 | public string? email { get; set; } 35 | 36 | [DataMember] 37 | public string? name { get; set; } 38 | 39 | [DataMember] 40 | public string? trip { get; set; } 41 | 42 | [DataMember] 43 | public string? title { get; set; } 44 | 45 | [DataMember] 46 | public string? comment { get; set; } 47 | 48 | [DataMember] 49 | public string? poster_hash { get; set; } 50 | 51 | [DataMember] 52 | public string? poster_country { get; set; } 53 | 54 | [DataMember] 55 | public byte sticky { get; set; } 56 | 57 | [DataMember] 58 | public byte locked { get; set; } 59 | 60 | [DataMember] 61 | public byte deleted { get; set; } 62 | 63 | [DataMember] 64 | public object? nreplies { get; set; } 65 | 66 | [DataMember] 67 | public object? nimages { get; set; } 68 | 69 | [DataMember] 70 | public string? fourchan_date { get; set; } 71 | 72 | [DataMember] 73 | public string? comment_sanitized { get; set; } 74 | 75 | [DataMember] 76 | public string? comment_processed { get; set; } 77 | 78 | [DataMember] 79 | public bool formatted { get; set; } 80 | 81 | [DataMember] 82 | public string? title_processed { get; set; } 83 | 84 | [DataMember] 85 | public string? name_processed { get; set; } 86 | 87 | [DataMember] 88 | public string? email_processed { get; set; } 89 | 90 | [DataMember] 91 | public string? trip_processed { get; set; } 92 | 93 | [DataMember] 94 | public string? poster_hash_processed { get; set; } 95 | 96 | [DataMember] 97 | public bool poster_country_name { get; set; } 98 | 99 | [DataMember] 100 | public string? poster_country_name_processed { get; set; } 101 | 102 | [DataMember] 103 | public object[]? extra_data { get; set; } 104 | 105 | [DataMember] 106 | public string? exif { get; set; } 107 | 108 | [DataMember] 109 | public FoolFuukaFile? media { get; set; } 110 | 111 | [DataMember] 112 | public FoolFuukaBoard? board { get; set; } 113 | 114 | [IgnoreDataMember] 115 | [MemberNotNullWhen(true, nameof(media))] 116 | public bool HasFile => media != null; 117 | 118 | [IgnoreDataMember] 119 | public ulong[]? Quotes { 120 | get { 121 | if (comment_sanitized.IsNullEmptyWhitespace()) { 122 | return null; 123 | } 124 | 125 | var Matches = Parsers.Helpers.ParsersShared.RepliesSimpleRegex.Matches(comment_sanitized); 126 | if (Matches.Count < 1) { 127 | return null; 128 | } 129 | 130 | return Matches 131 | .Cast() 132 | .Select(x => { 133 | bool success = ulong.TryParse(x.Value[2..], out ulong value); 134 | return new { value, success }; 135 | }) 136 | .Where(x => x.success) 137 | .Select(x => x.value) 138 | .Distinct() 139 | .ToArray(); 140 | } 141 | } 142 | 143 | public override bool Equals(object? obj) => obj is FoolFuukaPost other && this.Equals(other); 144 | public bool Equals(FoolFuukaPost other) { 145 | if (other is null) { 146 | return this is null; 147 | } 148 | if (this is null) { 149 | return false; 150 | } 151 | return this.num == other.num; 152 | } 153 | 154 | public override int GetHashCode() => unchecked((int)(this.num & 0x7FFFFFFF)); // Negative-bit ignored. 155 | } 156 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/FoolFuukaThread.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Runtime.Serialization; 4 | [DataContract] 5 | public sealed class FoolFuukaThread { 6 | // ... /_/api/chan/thread?board={0}&num={1} 7 | [DataMember] 8 | public FoolFuukaPost? op { get; set; } 9 | 10 | [DataMember] 11 | public Dictionary? posts { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/FourChanThread.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Runtime.Serialization; 4 | [DataContract] 5 | internal sealed class FourChanThread { 6 | [DataMember(Name = "posts")] 7 | public FourChanPost[] posts { get; set; } = [ ]; 8 | } 9 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/FourTwentyChanThread.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | public class FourTwentyChanThread { 4 | } 5 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/GenericFile.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Drawing; 4 | using System.Runtime.Serialization; 5 | using System.Windows.Forms; 6 | [DataContract] 7 | public sealed class GenericFile { 8 | [DataMember(Name = "file_id")] 9 | public string? FileId { get; set; } 10 | 11 | [DataMember(Name = "file_url")] 12 | public string? FileUrl { get; set; } 13 | 14 | [DataMember(Name = "generated_file_name")] 15 | public string? GeneratedFileName { get; set; } 16 | 17 | [DataMember(Name = "original_file_name")] 18 | public string? OriginalFileName { get; set; } 19 | 20 | [DataMember(Name = "file_extension")] 21 | public string? FileExtension { get; set; } 22 | 23 | [DataMember(Name = "file_hash", EmitDefaultValue = false)] 24 | public string? FileHash { get; set; } 25 | 26 | [DataMember(Name = "file_dimensions")] 27 | public Size FileDimensions { get; set; } 28 | 29 | [DataMember(Name = "file_size")] 30 | public long FileSize { get; set; } 31 | 32 | [DataMember(Name = "thumbnail_file_url")] 33 | public string? ThumbnailFileUrl { get; set; } 34 | 35 | [DataMember(Name = "thumbnail_name")] 36 | public string? ThumbnailFileName { get; set; } 37 | 38 | [DataMember(Name = "thumbnail_extension")] 39 | public string? ThumbnailFileExtension { get; set; } 40 | 41 | [DataMember(Name = "thumbnail_dimensions")] 42 | public Size ThumbnailFileDimensions { get; set; } 43 | 44 | [DataMember(Name = "thumbnail_spoiled")] 45 | public bool ThumbnailFileSpoiled { get; set; } 46 | 47 | [DataMember(Name = "status")] 48 | public FileDownloadStatus Status { get; set; } 49 | 50 | [DataMember(Name = "saved_file")] 51 | public string? SavedFile { get; set; } 52 | 53 | [DataMember(Name = "saved_file_name")] 54 | public string? SavedFileName { get; set; } 55 | 56 | [DataMember(Name = "saved_thumbnail_file")] 57 | public string? SavedThumbnailFile { get; set; } 58 | 59 | [DataMember(Name = "saved_thumbnail_file_name")] 60 | public string? SavedThumbnailFileName { get; set; } 61 | 62 | [IgnoreDataMember] 63 | public ListViewItem ListViewItem { get; set; } = null!; 64 | 65 | [IgnoreDataMember] 66 | public GenericPost Parent { get; set; } 67 | 68 | public GenericFile(GenericPost Parent) { 69 | this.Parent = Parent; 70 | } 71 | 72 | public override bool Equals(object obj) => obj is GenericFile other && this.FileId == other.FileId; 73 | public override int GetHashCode() => this.FileId?.GetHashCode() ?? 0; 74 | } 75 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/Post Objects/U18ChanTag.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx.Posts; 3 | using System.Runtime.Serialization; 4 | using SoftCircuits.HtmlMonkey; 5 | [DataContract] 6 | internal sealed class U18ChanTag { 7 | [IgnoreDataMember] 8 | public U18ChanPost Parent { get; } 9 | 10 | [DataMember(Name = "name")] 11 | public string Name { get; set; } 12 | 13 | [DataMember(Name = "count")] 14 | public int Count { get; set; } 15 | 16 | public U18ChanTag(HtmlElementNode Node, U18ChanPost Parent) { 17 | this.Parent = Parent; 18 | this.Name = Node.Children[0].Text; 19 | if (Node.Children.Count > 1) { 20 | this.Count = int.Parse(Node.Children[1].Text[1..^1]); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/PreviousThread.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System.Runtime.Serialization; 4 | using System.Windows.Forms; 5 | [DataContract] 6 | public sealed class PreviousThread { 7 | [IgnoreDataMember] 8 | public ChanType Type { get; set; } 9 | 10 | [DataMember(Name = "url")] 11 | public string Url { get; set; } 12 | 13 | [DataMember(Name = "name")] 14 | public string ShortName { get; set; } 15 | 16 | [IgnoreDataMember] 17 | public TreeNode? Node { get; set; } 18 | 19 | [IgnoreDataMember] 20 | public bool HasValue => !Url.IsNullEmptyWhitespace(); 21 | 22 | public PreviousThread(string Url, string ShortName) { 23 | this.Url = Url; 24 | this.ShortName = ShortName; 25 | } 26 | 27 | public PreviousThread(ChanType Type, string Url, string ShortName) : this(Url, ShortName) { 28 | this.Type = Type; 29 | } 30 | 31 | [OnDeserialized] 32 | void D(StreamingContext ctx) { 33 | this.Url ??= string.Empty; 34 | this.ShortName ??= this.Url ?? string.Empty; 35 | } 36 | } 37 | 38 | public sealed class PreviousThreadCollection : List { 39 | public bool Contains(string Url) { 40 | for (int i = 0; i < this.Count; i++) { 41 | var Item = this[i]; 42 | if (Item.Url.Equals(Url, StringComparison.InvariantCultureIgnoreCase)) { 43 | return true; 44 | } 45 | } 46 | return false; 47 | } 48 | public int IndexOf(string Url) { 49 | for (int i = 0; i < this.Count; i++) { 50 | var Item = this[i]; 51 | if (Item.Url.Equals(Url)) { 52 | return i; 53 | } 54 | } 55 | return -1; 56 | } 57 | public void Remove(string Url) { 58 | for (int i = 0; i < this.Count; i++) { 59 | var Item = this[i]; 60 | if (Item.Url.Equals(Url, StringComparison.InvariantCultureIgnoreCase)) { 61 | this.RemoveAt(i--); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/ProgramSettings.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System.IO; 4 | /// 5 | /// Contains usability methods governing the application events. 6 | /// 7 | internal static class ProgramSettings { 8 | public static bool SaveThreads(this List Data){ 9 | List Files = []; 10 | if (Directory.Exists($"{Program.SavedThreadsPath}")) { 11 | DirectoryInfo ExistingFiles = new(Program.SavedThreadsPath); 12 | Files = ExistingFiles.GetFiles("*.thread.json", SearchOption.TopDirectoryOnly) 13 | .Select(x => x.FullName) 14 | .ToList(); 15 | } 16 | 17 | if (Data.Count > 0) { 18 | if (!Directory.Exists(Program.SavedThreadsPath)) { 19 | Directory.CreateDirectory(Program.SavedThreadsPath); 20 | } 21 | for (int i = 0; i < Data.Count; i++) { 22 | Files.Remove(Data[i].SavedThreadJson); 23 | if (Data[i].ThreadModified) { 24 | File.WriteAllText($"{Data[i].SavedThreadJson}", Data[i].Data.JsonSerialize()); 25 | } 26 | } 27 | 28 | if (Files.Count > 0) { 29 | for (int i = 0; i < Files.Count; i++) { 30 | if (File.Exists(Files[i])) { 31 | File.Delete(Files[i]); 32 | } 33 | } 34 | } 35 | } 36 | return true; 37 | } 38 | public static void SaveThread(this ThreadInfo Thread) { 39 | if (Thread.ThreadModified) { 40 | ForceSaveThread(Thread); 41 | } 42 | } 43 | public static void ForceSaveThread(this ThreadInfo Thread) { 44 | if (!Directory.Exists(Program.SavedThreadsPath)) { 45 | Directory.CreateDirectory(Program.SavedThreadsPath); 46 | } 47 | File.WriteAllText($"{Thread.SavedThreadJson}", Thread.Data.JsonSerialize()); 48 | Thread.ThreadModified = false; 49 | } 50 | 51 | public static List LoadThreads() { 52 | List list = []; 53 | List SavedFiles = []; 54 | DirectoryInfo ScanningDirectory; 55 | if (Directory.Exists($"{Program.SavedThreadsPath}")) { 56 | ScanningDirectory = new($"{Program.SavedThreadsPath}"); 57 | SavedFiles = 58 | [ .. SavedFiles, 59 | .. ScanningDirectory.GetFiles("*.thread.json", SearchOption.TopDirectoryOnly), ]; 60 | } 61 | 62 | if (SavedFiles.Count > 0) { 63 | for (int i = 0; i < SavedFiles.Count; i++) { 64 | try { 65 | var Thread = File.ReadAllText(SavedFiles[i].FullName).JsonDeserialize(); 66 | if (Thread == null) { 67 | continue; 68 | } 69 | Thread.JsonFilePath = SavedFiles[i].FullName; 70 | list.Add(Thread); 71 | } 72 | catch { 73 | File.Move(SavedFiles[i].FullName, SavedFiles[i].FullName + ".broken"); 74 | } 75 | } 76 | //list.Sort((x, y) => x.QueueIndex.CompareTo(y.QueueIndex)); 77 | } 78 | 79 | return list; 80 | } 81 | } -------------------------------------------------------------------------------- /src/YChanEx/Classes/Serializer.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Runtime.Serialization.Json; 6 | public static class Serializer { 7 | private static class GenericSerializer { 8 | private static readonly DataContractJsonSerializerSettings SerializerSettings = new() { 9 | UseSimpleDictionaryFormat = true, 10 | SerializeReadOnlyTypes = false, 11 | }; 12 | public static readonly DataContractJsonSerializer Serializer = new(typeof(T), SerializerSettings); 13 | } 14 | 15 | public static string JsonSerialize(this T value) { 16 | return JsonSerialize(value, System.Text.Encoding.UTF8); 17 | } 18 | public static string JsonSerialize(this T value, System.Text.Encoding Encoder) { 19 | using MemoryStream Stream = new(); 20 | //DataContractJsonSerializer Serializer = new(typeof(T), SerializerSettings); 21 | //Serializer.WriteObject(Stream, value); 22 | GenericSerializer.Serializer.WriteObject(Stream, value); 23 | byte[] json = Stream.ToArray(); 24 | return Encoder.GetString(json, 0, json.Length); 25 | } 26 | public static void JsonSerialize(this T value, Stream writeStream) { 27 | //DataContractJsonSerializer Serializer = new(typeof(T), SerializerSettings); 28 | //Serializer.WriteObject(writeStream, value); 29 | GenericSerializer.Serializer.WriteObject(writeStream, value); 30 | } 31 | 32 | public static T? JsonDeserialize(this string value) { 33 | return JsonDeserialize(value, System.Text.Encoding.UTF8); 34 | } 35 | public static T? JsonDeserialize(this string value, System.Text.Encoding Encoder) { 36 | using MemoryStream ms = new(Encoder.GetBytes(value)); 37 | //DataContractJsonSerializer ser = new(typeof(T), SerializerSettings); 38 | //object val = ser.ReadObject(ms); 39 | object? val = GenericSerializer.Serializer.ReadObject(ms); 40 | return val is T t ? t : default; 41 | } 42 | public static T? JsonDeserialize(this Stream readStream) { 43 | //DataContractJsonSerializer ser = new(typeof(T), SerializerSettings); 44 | //object val = ser.ReadObject(readStream); 45 | object? val = GenericSerializer.Serializer.ReadObject(readStream); 46 | return val is T t ? t : default; 47 | } 48 | 49 | public static bool TryJsonDeserialize(this string value, [NotNullWhen(true)] out T? result) { 50 | return TryJsonDeserialize(value, System.Text.Encoding.UTF8, out result); 51 | } 52 | public static bool TryJsonDeserialize(this string value, System.Text.Encoding Encoder, [NotNullWhen(true)] out T? result) { 53 | try { 54 | using MemoryStream ms = new(Encoder.GetBytes(value)); 55 | object? val = GenericSerializer.Serializer.ReadObject(ms); 56 | if (val is T t) { 57 | result = t; 58 | return true; 59 | } 60 | result = default; 61 | return false; 62 | } 63 | catch { 64 | result = default; 65 | return false; 66 | } 67 | } 68 | public static bool TryJsonDeserialize(this Stream readStream, [NotNullWhen(true)] out T? result) { 69 | try { 70 | object? val = GenericSerializer.Serializer.ReadObject(readStream); 71 | if (val is T t) { 72 | result = t; 73 | return true; 74 | } 75 | result = default; 76 | return false; 77 | } 78 | catch { 79 | result = default; 80 | return false; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/YChanEx/Classes/SimpleCookie.cs: -------------------------------------------------------------------------------- 1 | namespace YChanEx; 2 | using System.Net; 3 | using System.Runtime.Serialization; 4 | [DataContract] 5 | public sealed class SimpleCookie { 6 | [DataMember] 7 | public string Name { get; set; } 8 | 9 | [DataMember] 10 | public string Value { get; set; } 11 | 12 | [DataMember] 13 | public string Path { get; set; } 14 | 15 | [DataMember] 16 | public string Domain { get; set; } 17 | 18 | public SimpleCookie(string name, string value) { 19 | this.Name = name; 20 | this.Value = value; 21 | } 22 | public SimpleCookie(string name, string value, string path) : this(name, value) { 23 | this.Path = path; 24 | } 25 | public SimpleCookie(string name, string value, string path, string domain) : this(name, value, path) { 26 | this.Domain = domain; 27 | } 28 | 29 | public static implicit operator Cookie(SimpleCookie cookie) { 30 | return new Cookie(cookie.Name, cookie.Value, cookie.Path, cookie.Domain) { 31 | Expires = DateTime.MaxValue 32 | }; 33 | } 34 | public static implicit operator SimpleCookie(Cookie cookie) { 35 | return new SimpleCookie(cookie.Name, cookie.Value, cookie.Path, cookie.Domain); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/YChanEx/Config/Advanced.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | /// 4 | /// Contains advanced configuration. 5 | /// 6 | public static class Advanced { 7 | private const string ConfigName = "Advanced"; 8 | 9 | static Advanced() { 10 | fDisabledScanWhenOpeningSettings = 11 | IniProvider.Read(DisableScanWhenOpeningSettings, true, ConfigName); 12 | 13 | fSilenceErrors = 14 | IniProvider.Read(SilenceErrors, false, ConfigName); 15 | } 16 | 17 | /// 18 | /// Whether the scanner should pause while in the settings. 19 | /// 20 | public static bool DisableScanWhenOpeningSettings { 21 | get => fDisabledScanWhenOpeningSettings; 22 | internal set { 23 | if (fDisabledScanWhenOpeningSettings != value) { 24 | fDisabledScanWhenOpeningSettings = value; 25 | IniProvider.Write(DisableScanWhenOpeningSettings, ConfigName); 26 | } 27 | } 28 | } 29 | private static bool fDisabledScanWhenOpeningSettings; 30 | 31 | /// 32 | /// Whether errors should not be displayed. 33 | /// 34 | public static bool SilenceErrors { 35 | get => fSilenceErrors; 36 | internal set { 37 | if (fSilenceErrors != value) { 38 | fSilenceErrors = value; 39 | IniProvider.Write(SilenceErrors, ConfigName); 40 | } 41 | } 42 | } 43 | private static bool fSilenceErrors; 44 | 45 | /// 46 | /// Resets the config to defaults. 47 | /// 48 | public static void Reset() { 49 | DisableScanWhenOpeningSettings = true; 50 | SilenceErrors = false; 51 | } 52 | } -------------------------------------------------------------------------------- /src/YChanEx/Config/Helpers/Config.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System.Drawing; 4 | public static class Config { 5 | internal static readonly Point InvalidPoint = new(-32_000, -32_000); 6 | 7 | /// 8 | /// Checks if a point is a valid one to use. 9 | /// 10 | /// The value to validate. 11 | /// If the input is a valid point. 12 | public static bool ValidPoint(Point input) { 13 | return input.X != -32000 && input.Y != -32000; 14 | } 15 | 16 | /// 17 | /// Checks if a size is a valid one to use. 18 | /// 19 | /// The value to validate. 20 | /// If the input is a valid size. 21 | public static bool ValidSize(Size input) { 22 | return input.Width > 0 && input.Height > 0; 23 | } 24 | } -------------------------------------------------------------------------------- /src/YChanEx/Config/Helpers/Cookies.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System.IO; 4 | using System.Net; 5 | internal static class Cookies { 6 | public static List CookieList; 7 | private static readonly string CookiesPath; 8 | static Cookies() { 9 | CookiesPath = Environment.CurrentDirectory + Path.DirectorySeparatorChar + "cookies.json"; 10 | if (File.Exists(CookiesPath)) { 11 | CookieList = File.ReadAllText(CookiesPath).JsonDeserialize>() ?? []; 12 | } 13 | else { 14 | CookieList = []; 15 | } 16 | } 17 | public static void AddCookie(SimpleCookie cookie) { 18 | if (CookieList.Contains(cookie)) { 19 | return; 20 | } 21 | CookieList.Add(cookie); 22 | File.WriteAllText(CookiesPath, CookieList.JsonSerialize()); 23 | } 24 | public static void RemoveCookie(SimpleCookie cookie) { 25 | CookieList.Remove(cookie); 26 | File.WriteAllText(CookiesPath, CookieList.JsonSerialize()); 27 | } 28 | } -------------------------------------------------------------------------------- /src/YChanEx/Controls/ExtendedLinkLabel.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.controls; 3 | using System.Windows.Forms; 4 | internal class ExtendedLinkLabel : LinkLabel { 5 | /// 6 | /// Constructor 7 | /// 8 | public ExtendedLinkLabel() { 9 | this.LinkColor = System.Drawing.Color.FromArgb(0x00, 0x66, 0xCC); 10 | this.VisitedLinkColor = System.Drawing.Color.FromArgb(0x80, 0x00, 0x80); 11 | this.ActiveLinkColor = System.Drawing.Color.FromArgb(0xFF, 0x00, 0x00); 12 | } 13 | 14 | [System.Diagnostics.DebuggerStepThrough] 15 | protected override void WndProc(ref Message m) { 16 | switch (m.Msg) { 17 | case NativeMethods.WM_SETCURSOR: { 18 | NativeMethods.SetCursor(NativeMethods.HandCursor); 19 | m.Result = IntPtr.Zero; 20 | } break; 21 | 22 | default: { 23 | base.WndProc(ref m); 24 | } break; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/YChanEx/Controls/ExtendedListView.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.controls; 3 | using System.Windows.Forms; 4 | internal class ExtendedListView : ListView { 5 | protected override void OnHandleCreated(EventArgs e) { 6 | NativeMethods.SetWindowTheme(this.Handle, "explorer", null); 7 | base.OnHandleCreated(e); 8 | } 9 | public string GetColumnWidths() { 10 | System.Text.StringBuilder Output = new(); 11 | for (int i = 0; i < Columns.Count; i++) { 12 | Output.Append(Columns[i].Width).Append(','); 13 | } 14 | return Output.Remove(Output.Length - 1, 1).ToString(); 15 | } 16 | public void SetColumnWidths(string ColumnSizes) { 17 | string[] Values = ColumnSizes.Split(','); 18 | 19 | if (Values.Length > this.Columns.Count) { 20 | Array.Resize(ref Values, this.Columns.Count); 21 | } 22 | 23 | for (int i = 0; i < Values.Length; i++) { 24 | if (int.TryParse(Values[i], out int Width)) { 25 | this.Columns[i].Width = Width; 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/YChanEx/Enums/ChanType.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | /// 4 | /// Enumeration of the chan types available 5 | /// 6 | public enum ChanType { 7 | /// 8 | /// No chan was selected to download. 9 | /// 10 | Unsupported = -1, 11 | /// 12 | /// 4chan(nel) was selected to download. 13 | /// 14 | FourChan = 0, 15 | /// 16 | /// 420chan was selected to download. 17 | /// 18 | FourTwentyChan = 1, 19 | /// 20 | /// 7chan was selected to download. 21 | /// 22 | SevenChan = 2, 23 | /// 24 | /// 8chan was selected to download. 25 | /// 26 | EightChan = 3, 27 | /// 28 | /// 8kun was selected to download. 29 | /// 30 | EightKun = 4, 31 | /// 32 | /// fchan was selected to download. 33 | /// 34 | fchan = 5, 35 | /// 36 | /// u18chan was selected to download. 37 | /// 38 | u18chan = 6, 39 | /// 40 | /// An archival chan was selected to download. 41 | /// 42 | FoolFuuka = 7, 43 | } -------------------------------------------------------------------------------- /src/YChanEx/Enums/FileDownloadStatus.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | /// 4 | /// Enumeration of file download status, that determines the current state of the file. 5 | /// 6 | public enum FileDownloadStatus : byte { 7 | /// 8 | /// No attempt to download the file has occurred yet. 9 | /// A download attempt will occur. 10 | /// 11 | Undownloaded = 0, 12 | /// 13 | /// The file successfully downloaded. 14 | /// No download attempt will occur for this file again. 15 | /// 16 | Downloaded = 1, 17 | /// 18 | /// The file was given a 404, and is assumed to be deleted. 19 | /// No download attempt will occur for this file again. 20 | /// 21 | FileNotFound = 2, 22 | /// 23 | /// The file was not able to be downloaded. 24 | /// A download attempt will occur. 25 | /// 26 | Error = 3, 27 | /// 28 | /// The file was removed from the thread. 29 | /// No download attempt will occur for this file again. 30 | /// 31 | RemovedFromThread = 4, 32 | } -------------------------------------------------------------------------------- /src/YChanEx/Enums/ProxyType.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.controls; 3 | public enum ProxyType : byte { 4 | None = 0x0, 5 | HTTP = 0x1, 6 | SOCKS4 = 0x2, 7 | SOCKS4A = 0x3, 8 | SOCKS5 = 0x4, 9 | } 10 | -------------------------------------------------------------------------------- /src/YChanEx/Enums/ThreadEvent.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | /// 4 | /// Enumeration of the thread events 5 | /// 6 | public enum ThreadEvent : int { 7 | /// 8 | /// The thread should parse the given information 9 | /// 10 | ParseForInfo = 0, 11 | /// 12 | /// The thread should start to download 13 | /// 14 | StartDownload = 1, 15 | /// 16 | /// The thread should update itself and wait until next download 17 | /// 18 | AfterDownload = 2, 19 | /// 20 | /// Restarts the download from a set state, allowing the main parse and download loop to restart. 21 | /// 22 | RestartDownload = 3, 23 | /// 24 | /// The thread should abort because the user requested it 25 | /// 26 | AbortDownload = 4, 27 | /// 28 | /// The thread should retry because the user requested it 29 | /// 30 | RetryDownload = 5, 31 | /// 32 | /// The thread is being reloaded from a saved file. 33 | /// 34 | ReloadThread = 6, 35 | 36 | /// 37 | /// The thread should abort because the application is closing. 38 | /// 39 | AbortForClosing = 999 40 | } -------------------------------------------------------------------------------- /src/YChanEx/Enums/ThreadState.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | public enum ThreadState : byte { 4 | /// 5 | /// The thread was alive when it was saved. 6 | /// 7 | ThreadIsAlive = 0x0, 8 | /// 9 | /// The thread 404'd 10 | /// 11 | ThreadIs404 = 0x1, 12 | /// 13 | /// The thread was aborted. 14 | /// 15 | ThreadIsAborted = 0x2, 16 | /// 17 | /// The thread was archived. 18 | /// 19 | ThreadIsArchived = 0x3, 20 | } 21 | -------------------------------------------------------------------------------- /src/YChanEx/Enums/ThreadStatus.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | /// 4 | /// Enumeration of the various thread statuses available 5 | /// 6 | public enum ThreadStatus : ushort { 7 | /// 8 | /// The thread has an unknown status. 9 | /// 10 | NoStatusSet = 0, 11 | 12 | /// 13 | /// The thread is waiting for the delay to rescan. 14 | /// 15 | Waiting = 1, 16 | /// 17 | /// The thread us currently scanning for files. 18 | /// 19 | ThreadScanning = 2, 20 | /// 21 | /// The thread is currently downloading files. 22 | /// 23 | ThreadDownloading = 3, 24 | /// 25 | /// The thread was not modified since last scan. 26 | /// 27 | ThreadNotModified = 4, 28 | /// 29 | /// The file from the thread has 404'd. 30 | /// 31 | ThreadFile404 = 5, 32 | /// 33 | /// The thread is not allowed to view the content. 34 | /// 35 | ThreadIsNotAllowed = 6, 36 | /// 37 | /// The thread is reloading into memory. 38 | /// 39 | ThreadReloaded = 7, 40 | 41 | /// 42 | /// The thread 404'd 43 | /// 44 | ThreadIs404 = 100, 45 | /// 46 | /// The thread was aborted. 47 | /// 48 | ThreadIsAborted = 101, 49 | /// 50 | /// The thread was archived. 51 | /// 52 | ThreadIsArchived = 102, 53 | /// 54 | /// The thread is going to scan soon. 55 | /// 56 | ThreadScanningSoon = 103, 57 | /// 58 | /// Thread is requesting to update the name 59 | /// 60 | ThreadUpdateName = 104, 61 | 62 | /// 63 | /// The thread information wasn't given when the thread download started. 64 | /// 65 | ThreadInfoNotSet = 200, 66 | /// 67 | /// The thread is retrying the download. 68 | /// 69 | ThreadRetrying = 201, 70 | /// 71 | /// The thread wasn't downloaded properly. 72 | /// 73 | ThreadImproperlyDownloaded = 202, 74 | /// 75 | /// The thread does not have any posts. 76 | /// 77 | NoThreadPosts = 203, 78 | /// 79 | /// The thread could not be parseed. 80 | /// 81 | FailedToParseThreadHtml = 204, 82 | /// 83 | /// The thread encountered an unknown error. 84 | /// 85 | ThreadUnknownError = 205, 86 | /// 87 | /// The thread encountered an exception that wasn't handled. 88 | /// 89 | ThreadUnhandledException = 206, 90 | } 91 | -------------------------------------------------------------------------------- /src/YChanEx/Forms/frmAbout.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Windows.Forms; 6 | using murrty.classes; 7 | 8 | public partial class frmAbout : Form { 9 | private const string BodyText = """ 10 | ychanex by murrty 11 | build date {0} 12 | 13 | Shrim heals me 14 | 15 | do it for likulau 16 | """; 17 | private Task UpdateTask = Task.CompletedTask; 18 | 19 | public frmAbout() { 20 | InitializeComponent(); 21 | pbIcon.Image = Properties.Resources.AboutImage; 22 | pbIcon.Cursor = new Cursor(NativeMethods.LoadCursor(0, NativeMethods.IDC_HAND)); 23 | lbVersion.Text = $"v{Program.CurrentVersion}{(Program.DebugMode ? " (deubg)" : "")}"; 24 | lbBody.Text = string.Format(BodyText, Properties.Resources.BuildDate); 25 | } 26 | 27 | private async Task CheckUpdate() { 28 | try { 29 | var result = await Updater.CheckForUpdate(true); 30 | if (result == null) { 31 | Log.Warn("could not get update."); 32 | MessageBox.Show("Could not find update."); 33 | return; 34 | } 35 | 36 | if (result == true) { 37 | Updater.ShowUpdateForm(false); 38 | } 39 | else { 40 | MessageBox.Show("No update is available."); 41 | } 42 | } 43 | catch { 44 | Log.Warn("could not get update."); 45 | MessageBox.Show("Could not find update."); 46 | } 47 | } 48 | private void llbCheckForUpdates_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { 49 | if (!UpdateTask.IsCompleted) { 50 | return; 51 | } 52 | UpdateTask = CheckUpdate(); 53 | } 54 | private void pbIcon_Click(object sender, EventArgs e) { 55 | Process.Start(Program.GithubPage); 56 | } 57 | private void llbGithub_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { 58 | Process.Start(Program.GithubPage); 59 | } 60 | } -------------------------------------------------------------------------------- /src/YChanEx/Forms/frmAddCookie.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System; 4 | using System.Net; 5 | using System.Windows.Forms; 6 | public partial class frmAddCookie : Form { 7 | public SimpleCookie? Cookie { get; set; } 8 | 9 | public frmAddCookie() { 10 | InitializeComponent(); 11 | } 12 | public frmAddCookie(SimpleCookie? cookie) : this() { 13 | if (cookie != null) { 14 | txtName.Text = cookie.Name.UnlessNull(string.Empty); 15 | txtValue.Text = cookie.Value.UnlessNull(string.Empty); 16 | txtPath.Text = cookie.Path.UnlessNull(string.Empty); 17 | txtDomain.Text = cookie.Domain.UnlessNull(string.Empty); 18 | } 19 | } 20 | 21 | private void btnCancel_Click(object sender, EventArgs e) { 22 | this.DialogResult = DialogResult.Cancel; 23 | } 24 | private void btnAdd_Click(object sender, EventArgs e) { 25 | if (string.IsNullOrWhiteSpace(txtName.Text)) { 26 | txtName.Focus(); 27 | System.Media.SystemSounds.Asterisk.Play(); 28 | return; 29 | } 30 | if (string.IsNullOrWhiteSpace(txtValue.Text)) { 31 | txtValue.Focus(); 32 | System.Media.SystemSounds.Asterisk.Play(); 33 | return; 34 | } 35 | if (string.IsNullOrWhiteSpace(txtDomain.Text)) { 36 | txtDomain.Focus(); 37 | System.Media.SystemSounds.Asterisk.Play(); 38 | return; 39 | } 40 | Cookie = new(txtName.Text, txtValue.Text, txtPath.Text.UnlessNullEmptyWhiteSpace("/"), txtDomain.Text); 41 | this.DialogResult = DialogResult.OK; 42 | } 43 | } -------------------------------------------------------------------------------- /src/YChanEx/Forms/frmNewName.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System; 4 | using System.Windows.Forms; 5 | public partial class frmNewName : Form { 6 | public string SetName { get; private set; } 7 | 8 | public frmNewName(string currentName) { 9 | InitializeComponent(); 10 | txtNewName.Text = currentName; 11 | SetName = currentName; 12 | } 13 | 14 | private void txtNewName_KeyDown(object sender, KeyEventArgs e) { 15 | if (e.KeyCode == Keys.Return) { 16 | e.Handled = e.SuppressKeyPress = true; 17 | if (txtNewName.Text.IsNullEmptyWhitespace()) { 18 | System.Media.SystemSounds.Exclamation.Play(); 19 | return; 20 | } 21 | this.DialogResult = DialogResult.OK; 22 | } 23 | } 24 | private void btnSetName_Click(object sender, EventArgs e) { 25 | if (txtNewName.Text.IsNullEmptyWhitespace()) { 26 | txtNewName.Focus(); 27 | System.Media.SystemSounds.Exclamation.Play(); 28 | return; 29 | } 30 | SetName = txtNewName.Text; 31 | this.DialogResult = DialogResult.OK; 32 | } 33 | private void btnCancel_Click(object sender, EventArgs e) { 34 | this.DialogResult = DialogResult.Cancel; 35 | } 36 | private void btnReset_Click(object sender, EventArgs e) { 37 | this.DialogResult = DialogResult.No; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/YChanEx/GlobalNamespaces.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections.Generic; 3 | global using System.Linq; 4 | global using System.Threading.Tasks; 5 | global using Version = murrty.Version; 6 | -------------------------------------------------------------------------------- /src/YChanEx/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles")] 4 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor")] 5 | -------------------------------------------------------------------------------- /src/YChanEx/Interfaces/IMainForm.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | public interface IMainFom { 4 | /// 5 | /// Sets the thread status from another thread handle to change the status on the main form. 6 | /// 7 | /// The thread that was 404'd, archived, or aborted. 8 | /// The new custom status to be set onto it. 9 | void SetItemStatus(ThreadInfo Thread, ThreadStatus Status); 10 | /// 11 | /// Removes the thread if it was killed (archived or 404) 12 | /// 13 | /// The thread that was 404'd, archived, or aborted. 14 | void ThreadKilled(ThreadInfo Thread); 15 | /// 16 | /// Adds a thread to the history. 17 | /// 18 | /// The thread that will be added. 19 | void AddToHistory(PreviousThread Thread); 20 | } 21 | -------------------------------------------------------------------------------- /src/YChanEx/Logging/DwmCompositionInfo.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.classes; 3 | using System.Drawing; 4 | using System.Runtime.InteropServices; 5 | /// 6 | /// A class that contains information about how the dwm will composite on the form 7 | /// 8 | internal sealed class DwmCompositionInfo { 9 | /// 10 | /// The handle that will be written to. 11 | /// 12 | public nint hWnd { get; } 13 | 14 | /// 15 | /// The rectangle where the dwm composition will be drawn at. 16 | /// 17 | public Rectangle DwmRectangle; 18 | 19 | /// 20 | /// Contains relevant information about the positioning of the frame. 21 | /// 22 | public DwmNatives.MARGINS Margins; 23 | 24 | /// 25 | /// Contains the text that will be rendered. 26 | /// 27 | public DwmCompositionTextInfo? Text; 28 | 29 | /// 30 | /// Device context handle. 31 | /// 32 | public nint destdc; 33 | 34 | /// 35 | /// Rect struct to render composition at. 36 | /// 37 | public DwmNatives.RECT Rect; 38 | 39 | /// 40 | /// DIB bitmap for the dwm composition. 41 | /// 42 | public DwmNatives.BITMAPINFO dib; 43 | 44 | public DwmCompositionInfo(nint hWnd, DwmNatives.MARGINS Margins, Rectangle DwmRectangle) { 45 | this.hWnd = hWnd; 46 | this.Margins = Margins; 47 | this.DwmRectangle = DwmRectangle; 48 | 49 | GenerateValues(); 50 | } 51 | 52 | public DwmCompositionInfo(nint hWnd, DwmNatives.MARGINS Margins, Rectangle DwmRectangle, DwmCompositionTextInfo NewInfo) { 53 | this.hWnd = hWnd; 54 | this.Margins = Margins; 55 | this.DwmRectangle = DwmRectangle; 56 | Text = NewInfo; 57 | 58 | GenerateValues(); 59 | } 60 | 61 | /// 62 | /// Generates used-values for rendering. Created to save lines when generating dwm info with or without text. 63 | /// 64 | private void GenerateValues() { 65 | destdc = DwmNatives.GetDC(hWnd); 66 | 67 | Rect = new() { 68 | top = DwmRectangle.Top, 69 | bottom = DwmRectangle.Bottom, 70 | left = DwmRectangle.Left, 71 | right = DwmRectangle.Right 72 | }; 73 | 74 | dib = new(); 75 | dib.bmiHeader.biHeight = -(Rect.bottom - Rect.top); 76 | dib.bmiHeader.biWidth = Rect.right - Rect.left; 77 | dib.bmiHeader.biPlanes = 1; 78 | dib.bmiHeader.biSize = Marshal.SizeOf(typeof(DwmNatives.BITMAPINFOHEADER)); 79 | dib.bmiHeader.biBitCount = 32; 80 | dib.bmiHeader.biCompression = DwmNatives.BI_RGB; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/YChanEx/Logging/DwmCompositionTextInfo.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.classes; 3 | using System.Drawing; 4 | using System.Runtime.InteropServices; 5 | /// 6 | /// A class that contains information about the text when rendering in the dwm composition. 7 | /// 8 | internal sealed class DwmCompositionTextInfo { 9 | /// 10 | /// The text that will be drawn. 11 | /// 12 | public string Text { get; set; } 13 | 14 | /// 15 | /// The font of the text. 16 | /// 17 | public Font Font { get; set; } 18 | 19 | /// 20 | /// The color of the text. 21 | /// 22 | public Color Color { get; set; } 23 | 24 | /// 25 | /// The size of the glow behind the text. 0 is off. 26 | /// 27 | public int GlowSize { get; set; } 28 | 29 | /// 30 | /// The rectangle where the text will render in. 31 | /// 32 | public Rectangle RenderingRectangle { get; set; } 33 | 34 | /// 35 | /// Rect 1 based for the text 36 | /// 37 | public DwmNatives.RECT Rect1; 38 | 39 | /// 40 | /// Rext 2 based for the glow. ? 41 | /// 42 | public DwmNatives.RECT Rect2; 43 | 44 | /// 45 | /// DIB bitmap for the text. 46 | /// 47 | public DwmNatives.BITMAPINFO BitmapInfo; 48 | 49 | /// 50 | /// Text formatting options. 51 | /// 52 | public DwmNatives.DTTOPTS dttOpts; 53 | 54 | /// 55 | /// Format for the text. 56 | /// 57 | public int uFormat = DwmNatives.DT_SINGLELINE | 58 | DwmNatives.DT_CENTER | 59 | DwmNatives.DT_VCENTER | 60 | DwmNatives.DT_NOPREFIX; 61 | 62 | /// 63 | /// Renderer. 64 | /// 65 | public System.Windows.Forms.VisualStyles.VisualStyleRenderer renderer { get; } = 66 | new(System.Windows.Forms.VisualStyles.VisualStyleElement.Window.Caption.Active); 67 | 68 | public DwmCompositionTextInfo(string text, Font font, Color color, int glowsize, Rectangle rectangle) { 69 | Text = text; 70 | Font = font; 71 | Color = color; 72 | GlowSize = glowsize; 73 | RenderingRectangle = rectangle; 74 | 75 | Rect1.left = rectangle.Left; 76 | Rect1.right = rectangle.Right + 2; 77 | Rect1.top = rectangle.Top; 78 | Rect1.bottom = rectangle.Bottom + 2; 79 | 80 | Rect2.left = 0; 81 | Rect2.top = 0; 82 | Rect2.right = Rect1.right - Rect1.left; 83 | Rect2.bottom = Rect1.bottom - Rect1.top; 84 | 85 | BitmapInfo = new(); 86 | BitmapInfo.bmiHeader.biHeight = -(Rect1.bottom - Rect1.top); // negative because DrawThemeTextEx() uses a top-down DIB 87 | BitmapInfo.bmiHeader.biWidth = Rect1.right - Rect1.left; 88 | BitmapInfo.bmiHeader.biPlanes = 1; 89 | BitmapInfo.bmiHeader.biSize = Marshal.SizeOf(typeof(DwmNatives.BITMAPINFOHEADER)); 90 | BitmapInfo.bmiHeader.biBitCount = 32; 91 | BitmapInfo.bmiHeader.biCompression = DwmNatives.BI_RGB; 92 | 93 | dttOpts = new() { 94 | dwSize = (uint)Marshal.SizeOf(typeof(DwmNatives.DTTOPTS)) 95 | }; 96 | if (glowsize > 0) { 97 | dttOpts.dwFlags = DwmNatives.DTT_COMPOSITED | 98 | DwmNatives.DTT_GLOWSIZE | 99 | DwmNatives.DTT_TEXTCOLOR; 100 | } 101 | else { 102 | dttOpts.dwFlags = DwmNatives.DTT_COMPOSITED | 103 | DwmNatives.DTT_TEXTCOLOR; 104 | } 105 | dttOpts.iGlowSize = glowsize; 106 | dttOpts.crText = (uint)ColorTranslator.ToWin32(color); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/YChanEx/Logging/ExceptionType.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.logging; 3 | /// 4 | /// Enum of exception types that may occur during runtime. 5 | /// 6 | [System.ComponentModel.DefaultValue(Unknown)] 7 | internal enum ExceptionType { 8 | /// 9 | /// An unknown exception type, no way for the application to know the after-report state of the application itself. 10 | /// 11 | Unknown, 12 | /// 13 | /// A successfully caught exception which will terminate the try-catch block associated with it. 14 | /// 15 | Caught, 16 | /// 17 | /// An unhandled exception type which will most likely cause the application to exit. 18 | /// 19 | Unhandled, 20 | /// 21 | /// An unhandled thread exception that will allow the application to continue in most situations. 22 | /// 23 | ThreadException 24 | } 25 | -------------------------------------------------------------------------------- /src/YChanEx/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Resources; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("YChanEx")] 10 | [assembly: AssemblyDescription("Thread downloader for various chans")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("YChanEx")] 14 | [assembly: AssemblyCopyright("Copyright © 2021")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [assembly: Guid("f2a0f289-635f-4ec8-829f-cad1c24cb49f")] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("2.0.0.0")] 37 | [assembly: AssemblyFileVersion("2.0.0.0")] 38 | [assembly: NeutralResourcesLanguageAttribute("en-US")] 39 | -------------------------------------------------------------------------------- /src/YChanEx/Resources/AboutImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/AboutImage.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/BuildDate.txt: -------------------------------------------------------------------------------- 1 | 2024-01-17 -------------------------------------------------------------------------------- /src/YChanEx/Resources/ProgramIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/ProgramIcon.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/ProgramIcon_Dead.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/ProgramIcon_Dead.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/SiteIcons/420chan.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/SiteIcons/420chan.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/SiteIcons/4chan.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/SiteIcons/4chan.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/SiteIcons/7chan.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/SiteIcons/7chan.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/SiteIcons/8chan.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/SiteIcons/8chan.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/SiteIcons/8kun.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/SiteIcons/8kun.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/SiteIcons/fchan.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/SiteIcons/fchan.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/SiteIcons/foolfuuka.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/SiteIcons/foolfuuka.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/SiteIcons/u18chan.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/SiteIcons/u18chan.ico -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/404.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/downloading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/downloading.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/error.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/finished-hashmismatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/finished-hashmismatch.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/finished.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/finished.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/reloaded-downloaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/reloaded-downloaded.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/reloaded-hashmismatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/reloaded-hashmismatch.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/reloaded-missing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/reloaded-missing.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/removed-from-thread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/removed-from-thread.png -------------------------------------------------------------------------------- /src/YChanEx/Resources/Status/waiting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/murrty/YChanEx/00b16bfa300ea91dac303b411d817a583a779874/src/YChanEx/Resources/Status/waiting.png -------------------------------------------------------------------------------- /src/YChanEx/Updater/API Data/GithubAsset.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.updater; 3 | using System.Runtime.Serialization; 4 | /// 5 | /// Represents a structure of the data representing the version, such as the content type (x-*) and the size of the file. 6 | /// 7 | [DataContract] 8 | public readonly struct GithubAsset(string Content, long Length) { 9 | /// 10 | /// Gets the content type. 11 | /// 12 | [DataMember(Name = "content_type")] 13 | public string Content { get; init; } = Content; 14 | 15 | /// 16 | /// Gets the size of the file. 17 | /// 18 | [DataMember(Name = "size")] 19 | public long Length { get; init; } = Length; 20 | 21 | /// 22 | /// Initializes an empty asset. 23 | /// 24 | public GithubAsset() : this(string.Empty, 0) { } 25 | } 26 | -------------------------------------------------------------------------------- /src/YChanEx/Updater/API Data/GithubData.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.updater; 3 | using System.Runtime.Serialization; 4 | using System.Text.RegularExpressions; 5 | using YChanEx; 6 | [DataContract] 7 | public sealed class GithubData { 8 | /// 9 | /// The string header of the update. 10 | /// 11 | [DataMember(Name = "name")] 12 | public string? VersionHeader { get; init; } 13 | 14 | /// 15 | /// The full description of the update. 16 | /// 17 | [DataMember(Name = "body")] 18 | public string? VersionDescription { get; init; } 19 | 20 | /// 21 | /// The tag of the update, which is usually the version. 22 | /// 23 | [DataMember(Name = "tag_name")] 24 | public string? VersionTag { get; init; } 25 | 26 | /// 27 | /// Whether the version posted on Github is a pre-release. 28 | /// 29 | [DataMember(Name = "prerelease")] 30 | public bool VersionPreRelease { get; init; } 31 | 32 | /// 33 | /// The files associated with the update. 34 | /// 35 | [DataMember(Name = "assets")] 36 | public GithubAsset[]? Files { get; init; } 37 | 38 | /// 39 | /// Gets whether this is a newer version of the program. 40 | /// 41 | [IgnoreDataMember] 42 | public bool IsNewerVersion { get; internal set; } 43 | 44 | /// 45 | /// Gets the string hash of the version (if available). 46 | /// 47 | [IgnoreDataMember] 48 | public string? ExecutableHash { get; internal set; } 49 | 50 | /// 51 | /// Gets the struct representation of the version. 52 | /// 53 | [IgnoreDataMember] 54 | public Version Version { get; internal set; } 55 | 56 | /// 57 | /// Gets whether the version is a beta version (or pre-release). 58 | /// 59 | [IgnoreDataMember] 60 | public bool IsBetaVersion => Version.IsBeta; 61 | 62 | [IgnoreDataMember] 63 | public long ExecutableSize { get; private set; } 64 | 65 | /// 66 | /// Tries to parse the executable hash from the version description. 67 | /// 68 | /// 69 | private string? FindHash() { 70 | if (VersionDescription.IsNullEmptyWhitespace()) { 71 | return null; 72 | } 73 | MatchCollection Matches = Regex.Matches(VersionDescription, "(?<=exe sha([- ])256: )(`)?[0-9a-fA-F]{64}(`)?(?=)"); 74 | if (Matches.Count > 0) { 75 | return Matches[0].Value; 76 | } 77 | Matches = Regex.Matches(VersionDescription, "(?<=exe sha256: )(`)?[0-9a-fA-F]{64}(`)?(?=)"); 78 | if (Matches.Count > 0) { 79 | return Matches[0].Value; 80 | } 81 | return null; 82 | } 83 | 84 | [OnDeserialized] 85 | void Deserialized(StreamingContext ctx) { 86 | // Skip any that cannot be parsed by the version string. 87 | if (!Version.TryParse(VersionTag, out Version vers)) { 88 | return; 89 | } 90 | 91 | Version = vers; 92 | IsNewerVersion = Version > Program.CurrentVersion; 93 | ExecutableHash = FindHash() ?? string.Empty; 94 | ExecutableSize = 0; 95 | if (Files?.Length > 0) { 96 | for (int i = 0; i < Files.Length; i++) { 97 | if (Files[i].Content == "application/x-msdownload") { 98 | ExecutableSize = Files[i].Length; 99 | } 100 | } 101 | } 102 | } 103 | 104 | public static GithubData GetNewestRelease(GithubData[] Releases) { 105 | if (Releases.Length == 0) { 106 | throw new NullReferenceException("The found releases were empty."); 107 | } 108 | GithubData CurrentCheck = Releases[0]; 109 | Version NewestVersion = Version.Empty; 110 | for (int i = 0; i < Releases.Length; i++) { 111 | Version Parse = Releases[i].Version; 112 | if (Parse > NewestVersion) { 113 | CurrentCheck = Releases[i]; 114 | NewestVersion = Parse; 115 | } 116 | } 117 | return CurrentCheck; 118 | } 119 | } -------------------------------------------------------------------------------- /src/YChanEx/Updater/Form/frmUpdateAvailable.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace YChanEx; 3 | using System.Windows.Forms; 4 | using murrty.updater; 5 | internal sealed partial class frmUpdateAvailable : Form { 6 | /// 7 | /// Whether the "Skip version" button should be disabled, false if it's an automatic check. 8 | /// 9 | public bool BlockSkip { get; init; } 10 | 11 | /// 12 | /// The update that is available. 13 | /// 14 | internal GithubData UpdateData { get; } 15 | 16 | public frmUpdateAvailable(GithubData UpdateData) { 17 | InitializeComponent(); 18 | this.UpdateData = UpdateData; 19 | lbUpdateAvailableCurrentVersion.Text = $"Current version: {Program.CurrentVersion}"; 20 | } 21 | 22 | private void frmUpdateAvailable_Load(object? sender, EventArgs e) { 23 | btnUpdateAvailableSkip.Enabled = !BlockSkip; 24 | if (UpdateData.IsBetaVersion) { 25 | lbUpdateAvailableHeader.Text = "A beta update is available"; 26 | } 27 | lbUpdateAvailableUpdateVersion.Text = $"Update version: {UpdateData.Version}"; 28 | txtUpdateAvailableName.Text = UpdateData.VersionHeader ?? "No header provided"; 29 | rtbUpdateAvailableChangelog.Text = UpdateData.VersionDescription ?? "No description provided."; 30 | lbUpdateSize.Text = $"The new executable size is {HtmlControl.GetSize(UpdateData.ExecutableSize)}"; 31 | } 32 | 33 | private void btnUpdateAvailableSkip_Click(object? sender, EventArgs e) { 34 | this.DialogResult = DialogResult.Ignore; 35 | } 36 | 37 | private void btnUpdateAvailableUpdate_Click(object? sender, EventArgs e) { 38 | this.DialogResult = DialogResult.Yes; 39 | } 40 | 41 | private void btnUpdateAvailableOk_Click(object? sender, EventArgs e) { 42 | this.DialogResult = DialogResult.OK; 43 | } 44 | 45 | private void rtbUpdateAvailableChangelog_KeyPress(object? sender, KeyPressEventArgs e) { 46 | e.Handled = true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/Brotli/BrotliRuntimeException.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Google Inc. All Rights Reserved. 2 | 3 | Distributed under MIT license. 4 | See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 | */ 6 | namespace Org.Brotli.Dec; 7 | /// Unchecked exception used internally. 8 | [System.Serializable] 9 | internal sealed class BrotliRuntimeException : System.Exception { 10 | internal BrotliRuntimeException() : base() { 11 | } 12 | internal BrotliRuntimeException(string message) 13 | : base(message) { 14 | } 15 | internal BrotliRuntimeException(string message, System.Exception cause) 16 | : base(message, cause) { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/Brotli/HuffmanTreeGroup.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Google Inc. All Rights Reserved. 2 | 3 | Distributed under MIT license. 4 | See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 | */ 6 | namespace Org.Brotli.Dec; 7 | /// Contains a collection of huffman trees with the same alphabet size. 8 | internal sealed class HuffmanTreeGroup { 9 | /// The maximal alphabet size in this group. 10 | private int alphabetSize; 11 | /// Storage for Huffman lookup tables. 12 | internal int[] codes; 13 | /// 14 | /// Offsets of distinct lookup tables in 15 | /// 16 | /// storage. 17 | /// 18 | internal int[] trees; 19 | 20 | /// Initializes the Huffman tree group. 21 | /// POJO to be initialised 22 | /// the maximal alphabet size in this group 23 | /// number of Huffman codes 24 | internal static void Init(HuffmanTreeGroup group, int alphabetSize, int n) { 25 | group.alphabetSize = alphabetSize; 26 | group.codes = new int[n * Huffman.HuffmanMaxTableSize]; 27 | group.trees = new int[n]; 28 | } 29 | 30 | /// Decodes Huffman trees from input stream and constructs lookup tables. 31 | /// target POJO 32 | /// data source 33 | internal static void Decode(HuffmanTreeGroup group, BitReader br) { 34 | int next = 0; 35 | int n = group.trees.Length; 36 | for (int i = 0; i < n; i++) { 37 | group.trees[i] = next; 38 | Dec.Decode.ReadHuffmanCode(group.alphabetSize, group.codes, next, br); 39 | next += Huffman.HuffmanMaxTableSize; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/Brotli/IntReader.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. All Rights Reserved. 2 | 3 | Distributed under MIT license. 4 | See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 | */ 6 | namespace Org.Brotli.Dec; 7 | /// Byte-to-int conversion magic. 8 | internal sealed class IntReader { 9 | private byte[] byteBuffer; 10 | private int[] intBuffer; 11 | 12 | internal static void Init(IntReader ir, byte[] byteBuffer, int[] intBuffer) { 13 | ir.byteBuffer = byteBuffer; 14 | ir.intBuffer = intBuffer; 15 | } 16 | 17 | /// Translates bytes to ints. 18 | /// 19 | /// Translates bytes to ints. 20 | /// NB: intLen == 4 * byteSize! 21 | /// NB: intLen should be less or equal to intBuffer length. 22 | /// 23 | internal static void Convert(IntReader ir, int intLen) { 24 | for (int i = 0; i < intLen; ++i) { 25 | ir.intBuffer[i] = (ir.byteBuffer[i * 4] & unchecked((int)(0xFF))) 26 | | ((ir.byteBuffer[(i * 4) + 1] & unchecked((int)(0xFF))) << 8) 27 | | ((ir.byteBuffer[(i * 4) + 2] & unchecked((int)(0xFF))) << 16) 28 | | ((ir.byteBuffer[(i * 4) + 3] & unchecked((int)(0xFF))) << 24); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/Brotli/Prefix.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Google Inc. All Rights Reserved. 2 | 3 | Distributed under MIT license. 4 | See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 | */ 6 | namespace Org.Brotli.Dec; 7 | /// Lookup tables to map prefix codes to value ranges. 8 | /// 9 | /// Lookup tables to map prefix codes to value ranges. 10 | ///

This is used during decoding of the block lengths, literal insertion lengths and copy 11 | /// lengths. 12 | ///

Range represents values: [offset, offset + 2 ^ n_bits) 13 | /// 14 | internal static class Prefix { 15 | internal static readonly int[] BlockLengthOffset = [1, 5, 9, 13, 17, 25, 33, 41, 49, 65, 81, 97, 113, 145, 177, 209, 241, 305, 369, 497, 753, 1265, 2289, 4337, 8433, 16625]; 16 | 17 | internal static readonly int[] BlockLengthNBits = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 24]; 18 | 19 | internal static readonly int[] InsertLengthOffset = [0, 1, 2, 3, 4, 5, 6, 8, 10, 14, 18, 26, 34, 50, 66, 98, 130, 194, 322, 578, 1090, 2114, 6210, 22594]; 20 | 21 | internal static readonly int[] InsertLengthNBits = [0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 12, 14, 24]; 22 | 23 | internal static readonly int[] CopyLengthOffset = [2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 18, 22, 30, 38, 54, 70, 102, 134, 198, 326, 582, 1094, 2118]; 24 | 25 | internal static readonly int[] CopyLengthNBits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 24]; 26 | 27 | internal static readonly int[] InsertRangeLut = [0, 0, 8, 8, 0, 16, 8, 16, 16]; 28 | 29 | internal static readonly int[] CopyRangeLut = [0, 8, 0, 8, 16, 0, 16, 8, 16]; 30 | } 31 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/Brotli/RunningState.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Google Inc. All Rights Reserved. 2 | 3 | Distributed under MIT license. 4 | See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 | */ 6 | namespace Org.Brotli.Dec; 7 | ///

Enumeration of decoding state-machine. 8 | internal enum RunningState : byte { 9 | Uninitialized = 0, 10 | BlockStart = 1, 11 | CompressedBlockStart = 2, 12 | MainLoop = 3, 13 | ReadMetadata = 4, 14 | CopyUncompressed = 5, 15 | InsertLoop = 6, 16 | CopyLoop = 7, 17 | CopyWrapBuffer = 8, 18 | Transform = 9, 19 | Finished = 10, 20 | Closed = 11, 21 | Write = 12, 22 | } 23 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/Brotli/State.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Google Inc. All Rights Reserved. 2 | 3 | Distributed under MIT license. 4 | See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 | */ 6 | namespace Org.Brotli.Dec; 7 | internal sealed class State { 8 | internal RunningState runningState = RunningState.Uninitialized; 9 | internal RunningState nextRunningState; 10 | internal readonly BitReader br = new(); 11 | internal byte[] ringBuffer; 12 | internal readonly int[] blockTypeTrees = new int[3 * Huffman.HuffmanMaxTableSize]; 13 | internal readonly int[] blockLenTrees = new int[3 * Huffman.HuffmanMaxTableSize]; 14 | internal int metaBlockLength; 15 | internal bool inputEnd; 16 | internal bool isUncompressed; 17 | internal bool isMetadata; 18 | internal readonly HuffmanTreeGroup hGroup0 = new(); 19 | internal readonly HuffmanTreeGroup hGroup1 = new(); 20 | internal readonly HuffmanTreeGroup hGroup2 = new(); 21 | internal readonly int[] blockLength = new int[3]; 22 | internal readonly int[] numBlockTypes = new int[3]; 23 | internal readonly int[] blockTypeRb = new int[6]; 24 | internal readonly int[] distRb = [16, 15, 11, 4]; 25 | internal int pos = 0; 26 | internal int maxDistance = 0; 27 | internal int distRbIdx = 0; 28 | internal bool trivialLiteralContext = false; 29 | internal int literalTreeIndex = 0; 30 | internal int literalTree; 31 | internal int j; 32 | internal int insertLength; 33 | internal byte[] contextModes; 34 | internal byte[] contextMap; 35 | internal int contextMapSlice; 36 | internal int distContextMapSlice; 37 | internal int contextLookupOffset1; 38 | internal int contextLookupOffset2; 39 | internal int treeCommandOffset; 40 | internal int distanceCode; 41 | internal byte[] distContextMap; 42 | internal int numDirectDistanceCodes; 43 | internal int distancePostfixMask; 44 | internal int distancePostfixBits; 45 | internal int distance; 46 | internal int copyLength; 47 | internal int copyDst; 48 | internal int maxBackwardDistance; 49 | internal int maxRingBufferSize; 50 | internal int ringBufferSize = 0; 51 | internal long expectedTotalSize = 0; 52 | internal byte[] customDictionary = new byte[0]; 53 | internal int bytesToIgnore = 0; 54 | internal int outputOffset; 55 | internal int outputLength; 56 | internal int outputUsed; 57 | internal int bytesWritten; 58 | internal int bytesToWrite; 59 | internal byte[] output; 60 | 61 | // Current meta-block header information. 62 | private static int DecodeWindowBits(BitReader br) { 63 | if (BitReader.ReadBits(br, 1) == 0) { 64 | return 16; 65 | } 66 | int n = BitReader.ReadBits(br, 3); 67 | if (n != 0) { 68 | return 17 + n; 69 | } 70 | n = BitReader.ReadBits(br, 3); 71 | if (n != 0) { 72 | return 8 + n; 73 | } 74 | return 17; 75 | } 76 | 77 | /// Associate input with decoder state. 78 | /// uninitialized state without associated input 79 | /// compressed data source 80 | internal static void SetInput(State state, System.IO.Stream input) { 81 | if (state.runningState != RunningState.Uninitialized) { 82 | throw new InvalidOperationException("State MUST be uninitialized"); 83 | } 84 | BitReader.Init(state.br, input); 85 | int windowBits = DecodeWindowBits(state.br); 86 | if (windowBits == 9) { 87 | /* Reserved case for future expansion. */ 88 | throw new BrotliRuntimeException("Invalid 'windowBits' code"); 89 | } 90 | state.maxRingBufferSize = 1 << windowBits; 91 | state.maxBackwardDistance = state.maxRingBufferSize - 16; 92 | state.runningState = RunningState.BlockStart; 93 | } 94 | 95 | internal static void Close(State state, bool leaveOpen) { 96 | if (state.runningState == RunningState.Uninitialized) { 97 | throw new InvalidOperationException("State MUST be initialized"); 98 | } 99 | if (state.runningState == RunningState.Closed) { 100 | return; 101 | } 102 | state.runningState = RunningState.Closed; 103 | BitReader.Close(state.br, leaveOpen); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/Brotli/Utils.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Google Inc. All Rights Reserved. 2 | 3 | Distributed under MIT license. 4 | See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 | */ 6 | namespace Org.Brotli.Dec; 7 | /// A set of utility methods. 8 | internal sealed class Utils { 9 | private static readonly byte[] ByteZeroes = new byte[1024]; 10 | private static readonly int[] IntZeroes = new int[1024]; 11 | 12 | /// Fills byte array with zeroes. 13 | /// 14 | /// Fills byte array with zeroes. 15 | ///

Current implementation uses 16 | /// 17 | /// , so it should be used for length not 18 | /// less than 16. 19 | /// 20 | /// array to fill with zeroes 21 | /// the first byte to fill 22 | /// number of bytes to change 23 | internal static void FillWithZeroes(byte[] dest, int offset, int length) { 24 | int cursor = 0; 25 | while (cursor < length) { 26 | int step = Math.Min(cursor + 1024, length) - cursor; 27 | Array.Copy(ByteZeroes, 0, dest, offset + cursor, step); 28 | cursor += step; 29 | } 30 | } 31 | 32 | ///

Fills int array with zeroes. 33 | /// 34 | /// Fills int array with zeroes. 35 | ///

Current implementation uses 36 | /// 37 | /// , so it should be used for length not 38 | /// less than 16. 39 | /// 40 | /// array to fill with zeroes 41 | /// the first item to fill 42 | /// number of item to change 43 | internal static void FillWithZeroes(int[] dest, int offset, int length) { 44 | int cursor = 0; 45 | while (cursor < length) { 46 | int step = Math.Min(cursor + 1024, length) - cursor; 47 | Array.Copy(IntZeroes, 0, dest, offset + cursor, step); 48 | cursor += step; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/Brotli/WordTransformType.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Google Inc. All Rights Reserved. 2 | 3 | Distributed under MIT license. 4 | See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 | */ 6 | namespace Org.Brotli.Dec; 7 | ///

Enumeration of all possible word transformations. 8 | /// 9 | /// Enumeration of all possible word transformations. 10 | ///

There are two simple types of transforms: omit X first/last symbols, two character-case 11 | /// transforms and the identity transform. 12 | /// 13 | internal sealed class WordTransformType { 14 | internal const int Identity = 0; 15 | internal const int OmitLast1 = 1; 16 | internal const int OmitLast2 = 2; 17 | internal const int OmitLast3 = 3; 18 | internal const int OmitLast4 = 4; 19 | internal const int OmitLast5 = 5; 20 | internal const int OmitLast6 = 6; 21 | internal const int OmitLast7 = 7; 22 | internal const int OmitLast8 = 8; 23 | internal const int OmitLast9 = 9; 24 | internal const int UppercaseFirst = 10; 25 | internal const int UppercaseAll = 11; 26 | internal const int OmitFirst1 = 12; 27 | internal const int OmitFirst2 = 13; 28 | internal const int OmitFirst3 = 14; 29 | internal const int OmitFirst4 = 15; 30 | internal const int OmitFirst5 = 16; 31 | internal const int OmitFirst6 = 17; 32 | internal const int OmitFirst7 = 18; 33 | internal const int OmitFirst8 = 19; 34 | internal const int OmitFirst9 = 20; 35 | 36 | internal static int GetOmitFirst(int type) => type >= OmitFirst1 ? (type - OmitFirst1 + 1) : 0; 37 | internal static int GetOmitLast(int type) => type <= OmitLast9 ? (type - OmitLast1 + 1) : 0; 38 | } 39 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/CookieParser.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.networking; 3 | using System.Net; 4 | using YChanEx; 5 | internal static class CookieParser { 6 | public static Cookie? GetCookie(string? value, Uri hostUri) { 7 | if (value.IsNullEmptyWhitespace()) { 8 | return null; 9 | } 10 | 11 | string? cookieName = null; 12 | string? cookieValue = null; 13 | 14 | // Get the name and value, it's the first value in the string. 15 | int sepPos = value.IndexOf('='); 16 | int endPos = value.IndexOf(';'); 17 | 18 | // Other optionals 19 | string cookiePath = "/"; 20 | string cookieDomain = hostUri.Host; 21 | DateTime? expiresOn = null; 22 | bool httpOnly = false; 23 | bool secure = false; 24 | 25 | cookieName = value[..sepPos]; 26 | if (endPos > -1) { 27 | cookieValue = value[(sepPos + 1)..endPos]; 28 | 29 | if (endPos > value.Length - 1) { 30 | // Check for expire date 31 | int expirePos = value.IndexOf("expires=", StringComparison.OrdinalIgnoreCase); 32 | if (expirePos > -1) { 33 | 34 | } 35 | } 36 | } 37 | else { 38 | cookieValue = value[(sepPos + 1)..]; 39 | } 40 | 41 | Cookie cookie = new(cookieName, cookieValue, cookiePath, cookieDomain) { 42 | HttpOnly = httpOnly, 43 | Secure = secure 44 | }; 45 | if (expiresOn != null) { 46 | cookie.Expires = expiresOn.Value; 47 | } 48 | return cookie; 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/DelegateHandlerHelpers.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace murrty.networking; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using YChanEx; 6 | internal static class DelegateHandlerHelpers { 7 | public static CancellationTokenSource? CreateToken(HttpRequestMessage request, TimeSpan DefaultTimeout, CancellationToken cancellationToken) { 8 | TimeSpan Timeout; 9 | 10 | #if NET5_0_OR_GREATER 11 | if (request.Options.TryGetValue(RequestMessage.TimeoutKey, out TimeSpan? time) && time.HasValue) { 12 | Timeout = time.Value; 13 | } 14 | else 15 | #endif 16 | if (request.Properties?.TryGetValue(RequestMessage.TimeoutKey, out var tsp) is true && tsp is TimeSpan time) { 17 | Timeout = time; 18 | } 19 | else { 20 | Timeout = DefaultTimeout; 21 | } 22 | 23 | if (Timeout == TimeSpan.Zero) { 24 | return null; 25 | } 26 | 27 | CancellationTokenSource Token = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); 28 | Token.CancelAfter(Timeout); 29 | return Token; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Extensions/HttpHeadersExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Extensions; 2 | using System.Net.Http.Headers; 3 | internal static class HttpHeadersExtensions { 4 | public static string GetHeaderString(this HttpHeaders headers, string key) { 5 | if (headers == null) { 6 | throw new ArgumentNullException(nameof(headers)); 7 | } 8 | 9 | if (string.IsNullOrEmpty(key)) { 10 | throw new ArgumentNullException(nameof(key)); 11 | } 12 | 13 | string value = string.Empty; 14 | string separator = key.Equals("User-Agent") ? " " : ", "; 15 | 16 | if (headers.TryGetValues(key, out IEnumerable values) && values.Count() > 1) { 17 | value = string.Join(separator, values.ToArray()); 18 | } 19 | 20 | return value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Handlers/Socks4ClientHandler.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp; 2 | using SocksSharp.Proxy; 3 | internal class Socks4ClientHandler : ProxyClientHandler { 4 | public Socks4ClientHandler(ProxySettings proxySettings) : base(proxySettings) { } 5 | } 6 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Handlers/Socks4aClientHandler.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp; 2 | using SocksSharp.Proxy; 3 | internal class Socks4aClientHandler : ProxyClientHandler { 4 | public Socks4aClientHandler(ProxySettings proxySettings) : base(proxySettings) { } 5 | } 6 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Handlers/Socks5ClientHandler.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp; 2 | using SocksSharp.Proxy; 3 | internal class Socks5ClientHandler : ProxyClientHandler { 4 | public Socks5ClientHandler(ProxySettings proxySettings) : base(proxySettings) { } 5 | } 6 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Helpers/ContentHelper.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Helpers; 2 | internal static class ContentHelper { 3 | public static bool IsContentHeader(string name) { 4 | //https://github.com/dotnet/corefx/blob/3e72ee5971db5d0bd46606fa672969adde29e307/src/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs 5 | string[] contentHeaders = [ 6 | "Last-Modified", 7 | "Expires", 8 | "Content-Type", 9 | "Content-Range", 10 | "Content-MD5", 11 | "Content-Location", 12 | "Content-Length", 13 | "Content-Language", 14 | "Content-Encoding", 15 | "Allow" 16 | ]; 17 | 18 | bool isContent = false; 19 | for (int i = 0; i < contentHeaders.Length; i++) { 20 | string header = contentHeaders[i]; 21 | isContent = isContent || header.Equals(name, StringComparison.OrdinalIgnoreCase); 22 | } 23 | 24 | return isContent; 25 | } 26 | } -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Helpers/ExceptionHelper.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Helpers; 2 | internal static class ExceptionHelper { 3 | public static bool ValidateTcpPort(int port) { 4 | return port >= 1 && port <= 65535; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Helpers/HostHelper.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Core.Helpers; 2 | using SocksSharp.Proxy; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | internal static class HostHelper { 7 | public static byte[] GetPortBytes(int port) { 8 | return [ 9 | (byte)(port / 256), 10 | (byte)(port % 256)]; 11 | } 12 | 13 | public static byte[] GetIPAddressBytes(string destinationHost, bool preferIpv4 = true) { 14 | if (!IPAddress.TryParse(destinationHost, out var ipAddr)) { 15 | try { 16 | var ips = Dns.GetHostAddresses(destinationHost); 17 | if (ips.Length > 0) { 18 | if (preferIpv4) { 19 | foreach (var ip in ips) { 20 | var ipBytes = ip.GetAddressBytes(); 21 | if (ipBytes.Length == 4) { 22 | return ipBytes; 23 | } 24 | } 25 | } 26 | ipAddr = ips[0]; 27 | } 28 | } 29 | catch (Exception ex) { 30 | if (ex is SocketException || ex is ArgumentException) { 31 | throw new ProxyException("Failed to get host address", ex); 32 | } 33 | throw; 34 | } 35 | } 36 | 37 | return ipAddr.GetAddressBytes(); 38 | } 39 | 40 | public static byte[] GetHostAddressBytes(byte addressType, string host) { 41 | switch (addressType) { 42 | case Socks5Constants.AddressTypeIPV4: 43 | case Socks5Constants.AddressTypeIPV6: 44 | return IPAddress.Parse(host).GetAddressBytes(); 45 | 46 | case Socks5Constants.AddressTypeDomainName: 47 | byte[] bytes = new byte[host.Length + 1]; 48 | bytes[0] = (byte)host.Length; 49 | Encoding.ASCII.GetBytes(host).CopyTo(bytes, 1); 50 | return bytes; 51 | 52 | default: return null; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Helpers/ReceiveHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2012-2015 Ruslan Khuduev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | namespace SocksSharp.Helpers; 23 | using System.IO; 24 | using System.Text; 25 | internal sealed class ReceiveHelper { 26 | #region Fields 27 | private const int InitialLineSize = 1000; 28 | 29 | private Stream stream; 30 | 31 | private readonly byte[] buffer; 32 | private readonly int bufferSize; 33 | 34 | private int linePosition; 35 | private byte[] lineBuffer = new byte[InitialLineSize]; 36 | #endregion 37 | 38 | #region Properties 39 | public bool HasData { 40 | get { 41 | return (this.Length - this.Position) != 0; 42 | } 43 | } 44 | 45 | public int Length { get; private set; } 46 | 47 | public int Position { get; private set; } 48 | #endregion 49 | 50 | public ReceiveHelper(int bufferSize) { 51 | this.bufferSize = bufferSize; 52 | this.buffer = new byte[bufferSize]; 53 | } 54 | 55 | #region Methods 56 | public void Init(Stream stream) { 57 | this.stream = stream; 58 | this.linePosition = 0; 59 | this.Length = 0; 60 | this.Position = 0; 61 | } 62 | 63 | public string ReadLine() { 64 | linePosition = 0; 65 | 66 | while (true) { 67 | if (Position == Length) { 68 | Position = 0; 69 | Length = stream.Read(buffer, 0, bufferSize); 70 | if (Length == 0) { 71 | break; 72 | } 73 | } 74 | 75 | byte b = buffer[Position++]; 76 | lineBuffer[linePosition++] = b; 77 | 78 | // If a character is considered '\n' | Если считан символ '\n'. 79 | if (b == 10) { 80 | break; 81 | } 82 | 83 | // If the maximum line buffer size limit is reached | Если достигнут максимальный предел размера буфера линии. 84 | if (linePosition == lineBuffer.Length) { 85 | // Double the line buffer size | Увеличиваем размер буфера линии в два раза. 86 | byte[] newLineBuffer = new byte[lineBuffer.Length * 2]; 87 | lineBuffer.CopyTo(newLineBuffer, 0); 88 | lineBuffer = newLineBuffer; 89 | } 90 | } 91 | 92 | return Encoding.ASCII.GetString(lineBuffer, 0, linePosition); 93 | } 94 | 95 | public int Read(byte[] buffer, int index, int length) { 96 | int curLength = Length - Position; 97 | 98 | if (curLength > length) { 99 | curLength = length; 100 | } 101 | 102 | Array.Copy(this.buffer, Position, buffer, index, curLength); 103 | Position += curLength; 104 | return curLength; 105 | } 106 | #endregion 107 | } 108 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Proxy/Clients/Socks4a.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2012-2015 Ruslan Khuduev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | namespace SocksSharp.Proxy; 23 | using System.Text; 24 | using System.Net.Sockets; 25 | using SocksSharp.Core.Helpers; 26 | using static SocksSharp.Proxy.Socks4Constants; 27 | public class Socks4a : Socks4 { 28 | internal protected override void SendCommand(NetworkStream nStream, byte command, string destinationHost, int destinationPort) { 29 | byte[] dstPort = HostHelper.GetPortBytes(destinationPort); 30 | byte[] dstIp = [ 0, 0, 0, 1 ]; 31 | 32 | byte[] userId = []; 33 | if (Settings.Credentials != null && !string.IsNullOrEmpty(Settings.Credentials.UserName)) { 34 | userId = Encoding.ASCII.GetBytes(Settings.Credentials.UserName); 35 | } 36 | 37 | byte[] dstAddr = Encoding.ASCII.GetBytes(destinationHost); 38 | 39 | // +----+----+----+----+----+----+----+----+----+----+....+----+----+----+....+----+ 40 | // | VN | CD | DSTPORT | DSTIP | USERID |NULL| DSTADDR |NULL| 41 | // +----+----+----+----+----+----+----+----+----+----+....+----+----+----+....+----+ 42 | // 1 1 2 4 variable 1 variable 1 43 | byte[] request = new byte[10 + userId.Length + dstAddr.Length]; 44 | 45 | request[0] = VersionNumber; 46 | request[1] = command; 47 | dstPort.CopyTo(request, 2); 48 | dstIp.CopyTo(request, 4); 49 | userId.CopyTo(request, 8); 50 | request[8 + userId.Length] = 0x00; 51 | dstAddr.CopyTo(request, 9 + userId.Length); 52 | request[9 + userId.Length + dstAddr.Length] = 0x00; 53 | 54 | nStream.Write(request, 0, request.Length); 55 | 56 | // +----+----+----+----+----+----+----+----+ 57 | // | VN | CD | DSTPORT | DSTIP | 58 | // +----+----+----+----+----+----+----+----+ 59 | // 1 1 2 4 60 | byte[] response = new byte[8]; 61 | 62 | nStream.Read(response, 0, 8); 63 | 64 | byte reply = response[1]; 65 | 66 | // If the request is not fulfilled | Если запрос не выполнен. 67 | if (reply != CommandReplyRequestGranted) { 68 | HandleCommandError(reply); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Proxy/IProxy.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Proxy; 2 | using System.Net.Sockets; 3 | ///

4 | /// Provides an interface for proxy client 5 | /// 6 | public interface IProxy { 7 | /// 8 | /// Gets or sets proxy settings 9 | /// 10 | IProxySettings Settings { get; set; } 11 | 12 | /// 13 | /// Create connection to destination host via proxy server. 14 | /// 15 | /// Host 16 | /// Port 17 | /// Connection with proxy server. 18 | /// Connection to destination host 19 | TcpClient CreateConnection(string destinationHost, int destinationPort, TcpClient client); 20 | } 21 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Proxy/IProxyClient.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Proxy; 2 | using System.Net.Sockets; 3 | /// 4 | /// Provides an interface for 5 | /// 6 | /// 7 | public interface IProxyClient where T : IProxy { 8 | /// 9 | /// Gets or sets proxy settings for client 10 | /// 11 | ProxySettings Settings { get; set; } 12 | 13 | /// 14 | /// Create connection via proxy to destination host 15 | /// 16 | /// Destination 17 | NetworkStream GetDestinationStream(string destinationHost, int destinationPort); 18 | } 19 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Proxy/IProxySettings.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Proxy; 2 | using System.Net; 3 | /// 4 | /// Provides an interface for proxy settings 5 | /// 6 | public interface IProxySettings { 7 | /// 8 | /// Gets or sets the credentials to submit to the proxy server for authentication. 9 | /// 10 | NetworkCredential Credentials { get; set; } 11 | 12 | /// 13 | /// Gets or sets a value of host or IP address for the proxy server 14 | /// 15 | string Host { get; set; } 16 | 17 | /// 18 | /// Gets or sets a value of Port for the proxy server 19 | /// 20 | int Port { get; set; } 21 | 22 | /// 23 | /// Gets or sets the amount of time a 24 | /// will wait to connect to the proxy server 25 | /// 26 | int ConnectTimeout { get; set; } 27 | 28 | /// 29 | /// Gets or sets the amount of time a 30 | /// will wait for read or wait data from the proxy server 31 | /// 32 | int ReadWriteTimeOut { get; set; } 33 | } 34 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Proxy/IProxySettingsFluent.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Proxy; 2 | using System.Net; 3 | /// 4 | /// Provides an interface for fluent proxy settings 5 | /// 6 | public interface IProxySettingsFluent { 7 | /// 8 | /// Sets a value of host or IP address for the proxy server 9 | /// 10 | /// Host 11 | /// 12 | IProxySettingsFluent SetHost(string host); 13 | 14 | /// 15 | /// Sets a value of Port for the proxy server 16 | /// 17 | /// Port 18 | /// 19 | IProxySettingsFluent SetPort(int port); 20 | 21 | /// 22 | /// Gets or sets the amount of time a 23 | /// will wait for read or wait data from the proxy server 24 | /// 25 | /// Connection timeout 26 | /// 27 | IProxySettingsFluent SetConnectionTimeout(int connectionTimeout); 28 | 29 | /// 30 | /// Sets the amount of time a 31 | /// will wait to connect to the proxy server 32 | /// 33 | /// Read/Write timeout 34 | /// 35 | IProxySettingsFluent SetReadWriteTimeout(int readwriteTimeout); 36 | 37 | /// 38 | /// Sets the credentials to submit to the proxy server for authentication 39 | /// 40 | /// Credential 41 | /// 42 | IProxySettingsFluent SetCredential(NetworkCredential credential); 43 | 44 | /// 45 | /// Sets the credentials to submit to the proxy server for authentication 46 | /// 47 | /// Username 48 | /// Password 49 | /// 50 | IProxySettingsFluent SetCredential(string username, string password); 51 | } 52 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Proxy/ProxyClient.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Proxy; 2 | using System.Net.Sockets; 3 | using System.Security; 4 | using System.Threading; 5 | /// 6 | /// Represents Proxy Client to 7 | /// 8 | /// 9 | public class ProxyClient : IProxyClient where T : IProxy { 10 | private readonly T client; 11 | 12 | /// 13 | /// Gets or sets proxy settings for client 14 | /// 15 | public ProxySettings Settings { get; set; } 16 | 17 | /// 18 | /// Initialize a new instance of the with proxy handler 19 | /// 20 | public ProxyClient() { 21 | this.client = (T)Activator.CreateInstance(typeof(T)); 22 | } 23 | 24 | /// 25 | /// Create connection via proxy to destination host 26 | /// 27 | /// Destination 28 | /// 29 | /// Value of equals or empty. 30 | /// -or- 31 | /// Value of less than 1 or greater than 65535. 32 | /// -or- 33 | /// Value of username length greater than 255. 34 | /// -or- 35 | /// Value of password length greater than 255. 36 | /// 37 | public NetworkStream GetDestinationStream(string destinationHost, int destinationPort) { 38 | TcpClient tcpClient = null; 39 | client.Settings = Settings; 40 | 41 | #region Create Connection 42 | tcpClient = new TcpClient(); 43 | Exception connectException = null; 44 | var connectDoneEvent = new ManualResetEventSlim(); 45 | 46 | try { 47 | tcpClient.BeginConnect(Settings.Host, Settings.Port, new AsyncCallback( 48 | (ar) => { 49 | if (tcpClient.Client != null) { 50 | try { 51 | tcpClient.EndConnect(ar); 52 | } 53 | catch (Exception ex) { 54 | connectException = ex; 55 | } 56 | 57 | connectDoneEvent.Set(); 58 | } 59 | }), tcpClient 60 | ); 61 | } 62 | catch (Exception ex) { 63 | tcpClient.Close(); 64 | 65 | if (ex is SocketException || ex is SecurityException) { 66 | throw new ProxyException("Failed to connect to proxy-server", ex); 67 | } 68 | 69 | throw; 70 | } 71 | 72 | if (!connectDoneEvent.Wait(Settings.ConnectTimeout)) { 73 | tcpClient.Close(); 74 | throw new ProxyException("Failed to connect to proxy-server"); 75 | } 76 | 77 | if (connectException != null) { 78 | tcpClient.Close(); 79 | 80 | if (connectException is SocketException) { 81 | throw new ProxyException("Failed to connect to proxy-server", connectException); 82 | } 83 | else { 84 | throw connectException; 85 | } 86 | } 87 | 88 | if (!tcpClient.Connected) { 89 | tcpClient.Close(); 90 | throw new ProxyException("Failed to connect to proxy-server"); 91 | } 92 | #endregion 93 | 94 | tcpClient.SendTimeout = Settings.ReadWriteTimeOut; 95 | tcpClient.ReceiveTimeout = Settings.ReadWriteTimeOut; 96 | 97 | var connectedTcpClient = client.CreateConnection( 98 | destinationHost, 99 | destinationPort, 100 | tcpClient); 101 | 102 | return connectedTcpClient.GetStream(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Proxy/ProxyException.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Proxy; 2 | /// 3 | /// Represents errors that occur during proxy execution. 4 | /// 5 | public class ProxyException : Exception { 6 | public ProxyException() : base() { } 7 | /// 8 | /// Initializes a new instance of the with a specified error message 9 | /// and a reference to the inner exception that is the cause of this exception. 10 | /// 11 | /// The error message that explains the reason for the exception. 12 | public ProxyException(string message) : base(message) { } 13 | /// 14 | /// Initializes a new instance of the with a specified error message 15 | /// and a reference to the inner exception that is the cause of this exception. 16 | /// 17 | /// The error message that explains the reason for the exception. 18 | /// The exception that is the cause of the current exception, or a reference. 19 | public ProxyException(string message, Exception innerException) : base(message, innerException) { } 20 | } 21 | -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Proxy/Request/RequestBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Proxy.Request; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Net.Http.Headers; 5 | using System.Text; 6 | using SocksSharp.Extensions; 7 | internal class RequestBuilder { 8 | private readonly string newLine = "\r\n"; 9 | 10 | private readonly HttpRequestMessage request; 11 | private readonly CookieContainer cookies; 12 | 13 | public RequestBuilder(HttpRequestMessage request) : this(request, null) { } 14 | 15 | public RequestBuilder(HttpRequestMessage request, CookieContainer cookies) { 16 | this.request = request; 17 | this.cookies = cookies; 18 | } 19 | 20 | public byte[] BuildStartingLine() { 21 | var uri = request.RequestUri; 22 | 23 | var startingLine = $"{request.Method.Method} {uri.PathAndQuery} HTTP/{request.Version}" + newLine; 24 | 25 | if (string.IsNullOrEmpty(request.Headers.Host)) { 26 | startingLine += "Host: " + uri.Host + newLine; 27 | } 28 | 29 | return ToByteArray(startingLine); 30 | } 31 | 32 | public byte[] BuildHeaders(bool hasContent) { 33 | var headers = GetHeaders(request.Headers); 34 | if (hasContent) { 35 | var contentHeaders = GetHeaders(request.Content.Headers); 36 | headers = !string.IsNullOrWhiteSpace(headers) ? string.Join(newLine, headers, contentHeaders) : contentHeaders; 37 | } 38 | 39 | return ToByteArray(headers + newLine + newLine); 40 | } 41 | 42 | private string GetHeaders(HttpHeaders headers) { 43 | List headersList = []; 44 | 45 | foreach (var header in headers) { 46 | string headerKeyAndValue = string.Empty; 47 | 48 | if (header.Value is string[] values && values.Length < 2) { 49 | if (values.Length > 0 && !string.IsNullOrEmpty(values[0])) { 50 | headerKeyAndValue = header.Key + ": " + values[0]; 51 | } 52 | } 53 | else { 54 | string headerValue = headers.GetHeaderString(header.Key); 55 | if (!string.IsNullOrEmpty(headerValue)) { 56 | headerKeyAndValue = header.Key + ": " + headerValue; 57 | } 58 | } 59 | 60 | if (!string.IsNullOrEmpty(headerKeyAndValue)) { 61 | headersList.Add(headerKeyAndValue); 62 | } 63 | } 64 | 65 | if (headers is HttpContentHeaders && !headersList.Contains("Content-Length")) { 66 | var content = headers as HttpContentHeaders; 67 | if (content.ContentLength > 0) { 68 | headersList.Add($"Content-Length: {content.ContentLength}"); 69 | } 70 | } 71 | 72 | if (cookies != null) { 73 | var cookiesCollection = cookies.GetCookies(request.RequestUri); 74 | var rawCookies = "Cookie: "; 75 | 76 | foreach (var cookie in cookiesCollection) { 77 | rawCookies += cookie + "; "; 78 | } 79 | 80 | if (cookiesCollection.Count > 0) { 81 | headersList.Add(rawCookies); 82 | } 83 | } 84 | 85 | return string.Join("\r\n", [.. headersList]); 86 | } 87 | 88 | private byte[] ToByteArray(string data) { 89 | return Encoding.ASCII.GetBytes(data); 90 | } 91 | } -------------------------------------------------------------------------------- /src/fw-runtimes/Networking/SocksSharp/Proxy/Response/IResponseBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace SocksSharp.Proxy.Response; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Threading; 5 | public interface IResponseBuilder { 6 | int ReceiveTimeout { get; set; } 7 | 8 | Task GetResponseAsync(HttpRequestMessage request, Stream stream); 9 | 10 | Task GetResponseAsync(HttpRequestMessage request, Stream stream, CancellationToken cancellationToken); 11 | } -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/AllowNullAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that is allowed as an input even if the 5 | /// corresponding type disallows it. 6 | /// 7 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] 8 | [DebuggerNonUserCode] 9 | [ExcludeFromCodeCoverage] 10 | public sealed class AllowNullAttribute : Attribute { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public AllowNullAttribute() { } 15 | } 16 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/DisallowNullAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that is disallowed as an input even if the 5 | /// corresponding type allows it. 6 | /// 7 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] 8 | [DebuggerNonUserCode] 9 | [ExcludeFromCodeCoverage] 10 | public sealed class DisallowNullAttribute : Attribute { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public DisallowNullAttribute() { } 15 | } 16 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/DoesNotReturnAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that a method that will never return under any circumstance. 5 | /// 6 | [AttributeUsage(AttributeTargets.Method, Inherited = false)] 7 | [DebuggerNonUserCode] 8 | [ExcludeFromCodeCoverage] 9 | public sealed class DoesNotReturnAttribute : Attribute { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// 14 | public DoesNotReturnAttribute() { } 15 | } 16 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/DoesNotReturnIfAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that the method will not return if the associated 5 | /// parameter is passed the specified value. 6 | /// 7 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 8 | [DebuggerNonUserCode] 9 | [ExcludeFromCodeCoverage] 10 | public sealed class DoesNotReturnIfAttribute : Attribute { 11 | /// 12 | /// Gets the condition parameter value. 13 | /// Code after the method is considered unreachable by diagnostics if the argument 14 | /// to the associated parameter matches this value. 15 | /// 16 | public bool ParameterValue { get; } 17 | 18 | /// 19 | /// Initializes a new instance of the 20 | /// class with the specified parameter value. 21 | /// 22 | /// 23 | /// The condition parameter value. 24 | /// Code after the method is considered unreachable by diagnostics if the argument 25 | /// to the associated parameter matches this value. 26 | /// 27 | public DoesNotReturnIfAttribute(bool parameterValue) { 28 | ParameterValue = parameterValue; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/ExperimentalAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Indicates that an API is experimental and it may change in the future. 5 | /// 6 | /// 7 | /// This attribute allows call sites to be flagged with a diagnostic that indicates that an experimental 8 | /// feature is used. Authors can use this attribute to ship preview features in their assemblies. 9 | /// 10 | [global::System.AttributeUsage( 11 | global::System.AttributeTargets.Assembly | 12 | global::System.AttributeTargets.Module | 13 | global::System.AttributeTargets.Class | 14 | global::System.AttributeTargets.Struct | 15 | global::System.AttributeTargets.Enum | 16 | global::System.AttributeTargets.Constructor | 17 | global::System.AttributeTargets.Method | 18 | global::System.AttributeTargets.Property | 19 | global::System.AttributeTargets.Field | 20 | global::System.AttributeTargets.Event | 21 | global::System.AttributeTargets.Interface | 22 | global::System.AttributeTargets.Delegate, 23 | Inherited = false)] 24 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 25 | public sealed class ExperimentalAttribute : global::System.Attribute { 26 | /// 27 | /// Initializes a new instance of the class, 28 | /// specifying the ID that the compiler will use when reporting a use of the API the attribute applies to. 29 | /// 30 | /// The ID that the compiler will use when reporting a use of the API the attribute applies to. 31 | public ExperimentalAttribute(string diagnosticId) { 32 | DiagnosticId = diagnosticId; 33 | } 34 | 35 | /// 36 | /// Gets the ID that the compiler will use when reporting a use of the API the attribute applies to. 37 | /// 38 | /// The unique diagnostic ID. 39 | /// 40 | /// The diagnostic ID is shown in build output for warnings and errors. 41 | /// This property represents the unique ID that can be used to suppress the warnings or errors, if needed. 42 | /// 43 | public string DiagnosticId { get; } 44 | 45 | /// 46 | /// Gets or sets the URL for corresponding documentation. 47 | /// The API accepts a format string instead of an actual URL, creating a generic URL that includes the diagnostic ID. 48 | /// 49 | /// The format string that represents a URL to corresponding documentation. 50 | /// An example format string is https://contoso.com/obsoletion-warnings/{0}. 51 | public string? UrlFormat { get; set; } 52 | } 53 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/MaybeNullAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that an output may be even if the 5 | /// corresponding type disallows it. 6 | /// 7 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] 8 | [DebuggerNonUserCode] 9 | [ExcludeFromCodeCoverage] 10 | public sealed class MaybeNullAttribute : Attribute { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public MaybeNullAttribute() { } 15 | } 16 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/MaybeNullWhenAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that when a method returns , the parameter may be even if the corresponding type disallows it. 5 | /// 6 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 7 | [DebuggerNonUserCode] 8 | [ExcludeFromCodeCoverage] 9 | public sealed class MaybeNullWhenAttribute : Attribute { 10 | /// 11 | /// Gets the return value condition. 12 | /// If the method returns this value, the associated parameter may be . 13 | /// 14 | public bool ReturnValue { get; } 15 | 16 | /// 17 | /// Initializes the attribute with the specified return value condition. 18 | /// 19 | /// 20 | /// The return value condition. 21 | /// If the method returns this value, the associated parameter may be . 22 | /// 23 | public MaybeNullWhenAttribute(bool returnValue) { 24 | ReturnValue = returnValue; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/MemberNotNullAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that the method or property will ensure that the listed field and property members have 5 | /// not- values. 6 | /// 7 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] 8 | [DebuggerNonUserCode] 9 | [ExcludeFromCodeCoverage] 10 | public sealed class MemberNotNullAttribute : Attribute { 11 | /// 12 | /// Gets field or property member names. 13 | /// 14 | public string[] Members { get; } 15 | 16 | /// 17 | /// Initializes the attribute with a field or property member. 18 | /// 19 | /// 20 | /// The field or property member that is promised to be not-null. 21 | /// 22 | public MemberNotNullAttribute(string member) { 23 | Members = [ member ]; 24 | } 25 | 26 | /// 27 | /// Initializes the attribute with the list of field and property members. 28 | /// 29 | /// 30 | /// The list of field and property members that are promised to be not-null. 31 | /// 32 | public MemberNotNullAttribute(params string[] members) { 33 | Members = members; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/MemberNotNullWhenAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that the method or property will ensure that the listed field and property members have 5 | /// non- values when returning with the specified return value condition. 6 | /// 7 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] 8 | [DebuggerNonUserCode] 9 | [ExcludeFromCodeCoverage] 10 | public sealed class MemberNotNullWhenAttribute : Attribute { 11 | /// 12 | /// Gets the return value condition. 13 | /// 14 | public bool ReturnValue { get; } 15 | 16 | /// 17 | /// Gets field or property member names. 18 | /// 19 | public string[] Members { get; } 20 | 21 | /// 22 | /// Initializes the attribute with the specified return value condition and a field or property member. 23 | /// 24 | /// 25 | /// The return value condition. If the method returns this value, 26 | /// the associated parameter will not be . 27 | /// 28 | /// 29 | /// The field or property member that is promised to be not-. 30 | /// 31 | public MemberNotNullWhenAttribute(bool returnValue, string member) { 32 | ReturnValue = returnValue; 33 | Members = [ member ]; 34 | } 35 | 36 | /// 37 | /// Initializes the attribute with the specified return value condition and list 38 | /// of field and property members. 39 | /// 40 | /// 41 | /// The return value condition. If the method returns this value, 42 | /// the associated parameter will not be . 43 | /// 44 | /// 45 | /// The list of field and property members that are promised to be not-null. 46 | /// 47 | public MemberNotNullWhenAttribute(bool returnValue, params string[] members) { 48 | ReturnValue = returnValue; 49 | Members = members; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/NotNullAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that an output is not even if the 5 | /// corresponding type allows it. 6 | /// 7 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] 8 | [DebuggerNonUserCode] 9 | [ExcludeFromCodeCoverage] 10 | public sealed class NotNullAttribute : Attribute { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public NotNullAttribute() { } 15 | } 16 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/NotNullIfNotNullAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that the output will be non- if the 5 | /// named parameter is non-. 6 | /// 7 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] 8 | [DebuggerNonUserCode] 9 | [ExcludeFromCodeCoverage] 10 | public sealed class NotNullIfNotNullAttribute : Attribute { 11 | /// 12 | /// Gets the associated parameter name. 13 | /// The output will be non- if the argument to the 14 | /// parameter specified is non-. 15 | /// 16 | public string ParameterName { get; } 17 | 18 | /// 19 | /// Initializes the attribute with the associated parameter name. 20 | /// 21 | /// 22 | /// The associated parameter name. 23 | /// The output will be non- if the argument to the 24 | /// parameter specified is non-. 25 | /// 26 | public NotNullIfNotNullAttribute(string parameterName) { 27 | ParameterName = parameterName; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/NotNullWhenAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that when a method returns , 5 | /// the parameter will not be even if the corresponding type allows it. 6 | /// 7 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 8 | [ExcludeFromCodeCoverage, DebuggerNonUserCode] 9 | public sealed class NotNullWhenAttribute : Attribute { 10 | /// 11 | /// Gets the return value condition. 12 | /// If the method returns this value, the associated parameter will not be . 13 | /// 14 | public bool ReturnValue { get; } 15 | 16 | /// 17 | /// Initializes the attribute with the specified return value condition. 18 | /// 19 | /// 20 | /// The return value condition. 21 | /// If the method returns this value, the associated parameter will not be . 22 | /// 23 | public NotNullWhenAttribute(bool returnValue) { 24 | ReturnValue = returnValue; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CodeAnalysis/SetsRequiredMembersAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// 4 | /// Specifies that this constructor sets all required members for the current type, and callers 5 | /// do not need to set any required members themselves. 6 | /// 7 | [global::System.AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] 8 | [global::System.Diagnostics.DebuggerNonUserCode] 9 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 10 | public sealed class SetsRequiredMembersAttribute : Attribute { } 11 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CompilerServices/CollectionBuilderAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices; 2 | [global::System.AttributeUsage( 3 | global::System.AttributeTargets.Class | 4 | global::System.AttributeTargets.Struct | 5 | global::System.AttributeTargets.Interface, 6 | Inherited = false)] 7 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 8 | public sealed class CollectionBuilderAttribute : Attribute { 9 | /// 10 | /// Initialize the attribute to refer to the method on the type. 11 | /// 12 | /// The type of the builder to use to construct the collection. 13 | /// The name of the method on the builder to use to construct the collection. 14 | /// 15 | /// must refer to a static method that accepts a single parameter of 16 | /// type and returns an instance of the collection being built containing 17 | /// a copy of the data from that span. In future releases of .NET, additional patterns may be supported. 18 | /// 19 | public CollectionBuilderAttribute(Type builderType, string methodName) { 20 | BuilderType = builderType; 21 | MethodName = methodName; 22 | } 23 | 24 | /// 25 | /// Gets the type of the builder to use to construct the collection. 26 | /// 27 | public Type BuilderType { get; } 28 | 29 | /// 30 | /// Gets the name of the method on the builder to use to construct the collection. 31 | /// 32 | /// 33 | /// This should match the metadata name of the target method. 34 | /// For example, this might be ".ctor" if targeting the type's constructor. 35 | /// 36 | public string MethodName { get; } 37 | } 38 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Diagnostics/CompilerServices/StringSyntaxAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Diagnostics.CodeAnalysis; 3 | /// Specifies the syntax used in a string. 4 | [global::System.AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 5 | [global::System.Diagnostics.DebuggerNonUserCode] 6 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 7 | public sealed class StringSyntaxAttribute : Attribute { 8 | /// Initializes the with the identifier of the syntax used. 9 | /// The syntax identifier. 10 | public StringSyntaxAttribute(string syntax) { 11 | Syntax = syntax; 12 | Arguments = []; 13 | } 14 | 15 | /// Initializes the with the identifier of the syntax used. 16 | /// The syntax identifier. 17 | /// Optional arguments associated with the specific syntax employed. 18 | public StringSyntaxAttribute(string syntax, params object?[] arguments) { 19 | Syntax = syntax; 20 | Arguments = arguments; 21 | } 22 | 23 | /// Gets the identifier of the syntax used. 24 | public string Syntax { get; } 25 | 26 | /// Optional arguments associated with the specific syntax employed. 27 | public object?[] Arguments { get; } 28 | 29 | /// The syntax identifier for strings containing composite formats for string formatting. 30 | public const string CompositeFormat = nameof(CompositeFormat); 31 | /// The syntax identifier for strings containing date format specifiers. 32 | public const string DateOnlyFormat = nameof(DateOnlyFormat); 33 | /// The syntax identifier for strings containing date and time format specifiers. 34 | public const string DateTimeFormat = nameof(DateTimeFormat); 35 | /// The syntax identifier for strings containing format specifiers. 36 | public const string EnumFormat = nameof(EnumFormat); 37 | /// The syntax identifier for strings containing format specifiers. 38 | public const string GuidFormat = nameof(GuidFormat); 39 | /// The syntax identifier for strings containing JavaScript Object Notation (JSON). 40 | public const string Json = nameof(Json); 41 | /// The syntax identifier for strings containing numeric format specifiers. 42 | public const string NumericFormat = nameof(NumericFormat); 43 | /// The syntax identifier for strings containing regular expressions. 44 | public const string Regex = nameof(Regex); 45 | /// The syntax identifier for strings containing time format specifiers. 46 | public const string TimeOnlyFormat = nameof(TimeOnlyFormat); 47 | /// The syntax identifier for strings containing format specifiers. 48 | public const string TimeSpanFormat = nameof(TimeSpanFormat); 49 | /// The syntax identifier for strings containing URIs. 50 | public const string Uri = nameof(Uri); 51 | /// The syntax identifier for strings containing XML. 52 | public const string Xml = nameof(Xml); 53 | } -------------------------------------------------------------------------------- /src/fw-runtimes/System/Range.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | // https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Private.CoreLib/src/System/Range.cs 3 | namespace System; 4 | /// Represent a range has start and end indexes. 5 | /// 6 | /// Range is used by the C# compiler to support the range syntax. 7 | /// 8 | /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; 9 | /// int[] subArray1 = someArray[0..2]; // { 1, 2 } 10 | /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } 11 | /// 12 | /// 13 | [global::System.Diagnostics.DebuggerStepThrough] 14 | public readonly struct Range : IEquatable { 15 | /// Represent the inclusive start index of the Range. 16 | public Index Start { get; } 17 | 18 | /// Represent the exclusive end index of the Range. 19 | public Index End { get; } 20 | 21 | /// Construct a Range object using the start and end indexes. 22 | /// Represent the inclusive start index of the range. 23 | /// Represent the exclusive end index of the range. 24 | public Range(Index start, Index end) { 25 | Start = start; 26 | End = end; 27 | } 28 | 29 | #nullable enable 30 | /// Indicates whether the current Range object is equal to another object of the same type. 31 | /// An object to compare with this object 32 | public override bool Equals(object? value) => 33 | value is Range r && 34 | r.Start.Equals(Start) && 35 | r.End.Equals(End); 36 | #nullable disable 37 | 38 | /// Indicates whether the current Range object is equal to another Range object. 39 | /// An object to compare with this object 40 | public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); 41 | 42 | /// Returns the hash code for this instance. 43 | public override int GetHashCode() { 44 | return (Start.GetHashCode() * 31) + End.GetHashCode(); 45 | } 46 | 47 | /// Converts the value of the current Range object to its equivalent string representation. 48 | public override string ToString() { 49 | return Start + ".." + End; 50 | } 51 | 52 | /// Create a Range object starting from start index to the end of the collection. 53 | public static Range StartAt(Index start) => new(start, Index.End); 54 | 55 | /// Create a Range object starting from first element in the collection to the end Index. 56 | public static Range EndAt(Index end) => new(Index.Start, end); 57 | 58 | /// Create a Range object starting from first element to the end. 59 | public static Range All => new(Index.Start, Index.End); 60 | 61 | /// Calculate the start offset and length of range object using a collection length. 62 | /// The length of the collection that the range will be used with. length has to be a positive value. 63 | /// 64 | /// For performance reason, we don't validate the input length parameter against negative values. 65 | /// It is expected Range will be used with collections which always have non negative length/count. 66 | /// We validate the range is inside the length scope though. 67 | /// 68 | [Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 69 | public (int Offset, int Length) GetOffsetAndLength(int length) { 70 | int start; 71 | Index startIndex = Start; 72 | if (startIndex.IsFromEnd) 73 | start = length - startIndex.Value; 74 | else 75 | start = startIndex.Value; 76 | 77 | int end; 78 | Index endIndex = End; 79 | if (endIndex.IsFromEnd) 80 | end = length - endIndex.Value; 81 | else 82 | end = endIndex.Value; 83 | 84 | if ((uint)end > (uint)length || (uint)start > (uint)end) { 85 | throw new ArgumentOutOfRangeException(nameof(length)); 86 | } 87 | 88 | return (start, end - start); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Runtime.CompilerServices; 3 | [global::System.AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] 4 | [global::System.Diagnostics.DebuggerNonUserCode] 5 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 6 | public sealed class CallerArgumentExpressionAttribute : Attribute { 7 | public CallerArgumentExpressionAttribute(string parameterName) { 8 | ParameterName = parameterName; 9 | } 10 | public string ParameterName { get; } 11 | } 12 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Runtime/CompilerServices/CompilerFeatureRequiredAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Runtime.CompilerServices; 3 | /// 4 | /// Indicates that compiler support for a particular feature is required for the location where this attribute is applied. 5 | /// 6 | [global::System.AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] 7 | [global::System.Diagnostics.DebuggerNonUserCode] 8 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 9 | public sealed class CompilerFeatureRequiredAttribute : Attribute { 10 | public CompilerFeatureRequiredAttribute(string featureName) { 11 | FeatureName = featureName; 12 | } 13 | 14 | /// 15 | /// The name of the compiler feature. 16 | /// 17 | public string FeatureName { get; } 18 | 19 | /// 20 | /// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand . 21 | /// 22 | public bool IsOptional { get; init; } 23 | 24 | /// 25 | /// The used for the ref structs C# feature. 26 | /// 27 | public const string RefStructs = nameof(RefStructs); 28 | 29 | /// 30 | /// The used for the required members C# feature. 31 | /// 32 | public const string RequiredMembers = nameof(RequiredMembers); 33 | } 34 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Runtime/CompilerServices/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Runtime.CompilerServices; 3 | using global::System.ComponentModel; 4 | [global::System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)] 5 | [global::System.Diagnostics.DebuggerNonUserCode] 6 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 7 | public static class IsExternalInit { } 8 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Runtime/CompilerServices/RequiredMemberAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Runtime.CompilerServices; 3 | /// Specifies that a type has required members or that a member is required. 4 | [global::System.AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 5 | [global::System.Diagnostics.DebuggerNonUserCode] 6 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 7 | public sealed class RequiredMemberAttribute : Attribute { } 8 | -------------------------------------------------------------------------------- /src/fw-runtimes/System/Runtime/CompilerServices/RuntimeHelpers.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | namespace System.Runtime.CompilerServices; 3 | [global::System.Diagnostics.DebuggerNonUserCode] 4 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 5 | // This will be required to duplicated in projects that require the use of this. 6 | internal static class RuntimeHelpers { 7 | public static int OffsetToStringData { get; } 8 | public static T[] GetSubArray(T[] array, Range range) { 9 | if (array is null) { 10 | throw new ArgumentNullException(); 11 | } 12 | 13 | (int offset, int length) = range.GetOffsetAndLength(array.Length); 14 | 15 | if (default(T) is not null || typeof(T[]) == array.GetType()) { 16 | // We know the type of the array to be exactly T[]. 17 | 18 | if (length == 0) { 19 | return []; 20 | } 21 | 22 | var dest = new T[length]; 23 | Array.Copy(array, offset, dest, 0, length); 24 | return dest; 25 | } 26 | else { 27 | // The array is actually a U[] where U:T. 28 | T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length); 29 | Array.Copy(array, offset, dest, 0, length); 30 | return dest; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/fw-runtimes/fw-runtimes.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | a0fc4aca-80c0-4151-b41c-d538b779d1a6 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------