diff --git a/Apewer .NET.sln b/Apewer .NET.sln new file mode 100644 index 0000000..cb16a2f --- /dev/null +++ b/Apewer .NET.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.35325.158 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apewer", "Apewer\Apewer.csproj", "{7C47A362-78F9-4EC2-86F1-B442658D2466}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apewer.Source", "Apewer.Source\Apewer.Source.csproj", "{AFF499E2-10D0-406B-A563-CE3327D57962}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apewer.Web", "Apewer.Web\Apewer.Web.csproj", "{0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Apewer.Windows", "Apewer.Windows\Apewer.Windows.csproj", "{5FB935E5-01E8-4CA7-9157-DC58BD731408}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7C47A362-78F9-4EC2-86F1-B442658D2466}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C47A362-78F9-4EC2-86F1-B442658D2466}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C47A362-78F9-4EC2-86F1-B442658D2466}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C47A362-78F9-4EC2-86F1-B442658D2466}.Release|Any CPU.Build.0 = Release|Any CPU + {AFF499E2-10D0-406B-A563-CE3327D57962}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFF499E2-10D0-406B-A563-CE3327D57962}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFF499E2-10D0-406B-A563-CE3327D57962}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFF499E2-10D0-406B-A563-CE3327D57962}.Release|Any CPU.Build.0 = Release|Any CPU + {0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C9CD2F9-E0C2-4BAC-84C1-33A57798D6CC}.Release|Any CPU.Build.0 = Release|Any CPU + {5FB935E5-01E8-4CA7-9157-DC58BD731408}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FB935E5-01E8-4CA7-9157-DC58BD731408}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FB935E5-01E8-4CA7-9157-DC58BD731408}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FB935E5-01E8-4CA7-9157-DC58BD731408}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7A4C1E3C-8A0A-4128-A143-627A9F76EF02} + EndGlobalSection +EndGlobal diff --git a/Apewer.Source/Apewer.Source.csproj b/Apewer.Source/Apewer.Source.csproj index c0664e0..f615842 100644 --- a/Apewer.Source/Apewer.Source.csproj +++ b/Apewer.Source/Apewer.Source.csproj @@ -1,29 +1,29 @@  - + - - SOURCE;$(DefineConstants); - MYSQL_6_10;$(DefineConstants); - MYSQL_6_9;$(DefineConstants); - netstandard2.0;net40;net20 - + + SOURCE;$(DefineConstants); + MYSQL_6_10;$(DefineConstants); + MYSQL_6_9;$(DefineConstants); + netstandard2.0;net40;net20 + - - - - + + + + - - - - - - + + + + + + - - - - + + + + diff --git a/Apewer.Web/Apewer.Web.csproj b/Apewer.Web/Apewer.Web.csproj index 3dff1d2..f7f4a18 100644 --- a/Apewer.Web/Apewer.Web.csproj +++ b/Apewer.Web/Apewer.Web.csproj @@ -1,25 +1,25 @@  - + - - WEB;$(DefineConstants); - netcoreapp3.1;net40;net20 - + + WEB;$(DefineConstants); + netcoreapp3.1;net40;net20 + - - - - - - + + + + + + - - - - - - - + + + + + + + diff --git a/Apewer.Web/WebSocket/ChatServer.cs b/Apewer.Web/WebSocket/ChatServer.cs index db99b1d..4ba3425 100644 --- a/Apewer.Web/WebSocket/ChatServer.cs +++ b/Apewer.Web/WebSocket/ChatServer.cs @@ -4,6 +4,7 @@ using Apewer; using Apewer.Network; using System; using System.Collections.Generic; +using System.Net; namespace Apewer.WebSocket { @@ -127,18 +128,19 @@ namespace Apewer.WebSocket _websocket = new GenericServer(); lock (_websocket) { - var ws = _websocket.Start(_websocketport, _websocketaddress); - if (ws) + try { + _websocket.Start(_websocketport, IPAddress.Parse(_websocketaddress)); + _websocket.OnMessage += WebSocketReceived; _websocket.OnOpen += WebsocketOnOpen; _websocket.OnClose += WebsocketOnClose; RaiseConsole("WebSocket 服务已启动。"); } - else + catch (Exception ex) { _websocket = null; - RaiseError("WebSocket 服务启动失败。"); + RaiseError($"WebSocket 服务启动失败:{ex.Message()}"); return; } @@ -426,7 +428,7 @@ namespace Apewer.WebSocket { var input = Console.ReadLine(); if (input == "exit") break; - ws.Send(ws.Address, ":", ws.Port.ToString(), "\n", input); + ws.Send(ws.Address.ToString(), ":", ws.Port.ToString(), "\n", input); } ws.Close(); diff --git a/Apewer.Web/WebSocket/GenericServer.cs b/Apewer.Web/WebSocket/GenericServer.cs index 608fba7..cba756c 100644 --- a/Apewer.Web/WebSocket/GenericServer.cs +++ b/Apewer.Web/WebSocket/GenericServer.cs @@ -3,6 +3,7 @@ using Apewer; using System; using System.Collections.Generic; +using System.Net; namespace Apewer.WebSocket { @@ -11,15 +12,12 @@ namespace Apewer.WebSocket public sealed class GenericServer : IDisposable { - const string DefaultAddress = "0.0.0.0"; - const ushort DefaultPort = 8000; - private Dictionary _connections = new Dictionary(); private WebSocketServer _server = null; private bool _running = false; - private string _address = null; - private int _port = DefaultPort; + private IPAddress _address = null; + private int _port = 0; /// public event SocketEvent OnOpen; @@ -54,7 +52,7 @@ namespace Apewer.WebSocket } /// 监听的地址。 - public string Address + public IPAddress Address { get { return _address; } } @@ -101,28 +99,46 @@ namespace Apewer.WebSocket _running = false; } + /// 启动监听,如果监听正在运行则失败。在所有 IPv4 网络接口上自动选择可用的端口号。 + /// 已监听的端口号。 + /// + /// + public void Start() => Start(0, null); + /// 启动监听,如果监听正在运行则失败。 - public bool Start(int port = DefaultPort, string address = DefaultAddress) + /// 要监听的终结点。指定为 NULL 时将在所有 IPv4 网络接口上自动选择可用的端口号。 + /// 已监听的端口号。 + /// + /// + /// + public void Start(IPEndPoint endPoint) { - if (address == null) return false; - if (_running) return false; - - _address = address ?? ""; - _port = NumberUtility.Restrict( port, 0, ushort.MaxValue); + if (endPoint == null) Start(0, null); + else Start(endPoint.Port, endPoint.Address); + } - try - { - var location = "ws://" + address + ":" + port.ToString(); - _server = new WebSocketServer(location); - _server.Start(Initialize); - _running = true; - return true; - } - catch (Exception exception) - { - RaiseExcepted(exception); - return false; - } + /// 启动监听,如果监听正在运行则失败。 + /// 要监听的端口号。指定为 0 时将自动选择可用的端口号。 + /// 要监听的网络接口。指定为 NULL 时将在所有 IPv4 网络接口监听,等同于 。 + /// 已监听的端口号。 + /// + /// + /// + public void Start(int port, IPAddress address = null) + { + if (address == null) address = IPAddress.Any; + if (port < 0) throw new ArgumentOutOfRangeException(nameof(port)); + if (port > 65535) throw new ArgumentOutOfRangeException(nameof(port)); + if (_running) throw new InvalidOperationException("示例已经在运行中,无法再次启动。"); + + _address = address; + _port = port; + + var location = "ws://" + _address.ToString() + ":" + port.ToString(); + _server = new WebSocketServer(location); + _server.Start(Initialize); + _port = _server.Port; + _running = true; } /// 对所有连接发送文本。 diff --git a/Apewer.Windows/Apewer.Windows.csproj b/Apewer.Windows/Apewer.Windows.csproj index fe58ff6..75f1a8a 100644 --- a/Apewer.Windows/Apewer.Windows.csproj +++ b/Apewer.Windows/Apewer.Windows.csproj @@ -3,77 +3,77 @@ - CS0108;CS0612 - net461;net40;net20;netcoreapp3.1 + CS0108;CS0612 + net461;net40;net20;netcoreapp3.1 - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - Form - - + + + + + + + + + + + + + + Form + + \ No newline at end of file diff --git a/Apewer.Windows/WinForm/ModifierKey.cs b/Apewer.Windows/WinForm/ModifierKey.cs new file mode 100644 index 0000000..1bdc7cf --- /dev/null +++ b/Apewer.Windows/WinForm/ModifierKey.cs @@ -0,0 +1,123 @@ +using System; + +namespace Apewer.WinForm +{ + + /// 修饰键。 + public struct ModifierKey : IEquatable + { + + byte _value; + bool _alt; + bool _ctrl; + bool _shift; + bool _win; + + /// 包含 ALT 键。 + public bool WithALT { get => _alt; } + + /// 包含 CTRL 键。 + public bool WithCTRL { get => _ctrl; } + + /// 包含 SHIFT 键。 + public bool WithSHIFT { get => _shift; } + + /// 包含 WIN 键。 + public bool WithWIN { get => _win; } + + /// 修饰键组合的值。 + public byte Value { get => _value; } + + /// + private ModifierKey(byte value) + { + _alt = value << 7 >> 7 == 1; + _ctrl = value << 6 >> 7 == 1; + _shift = value << 5 >> 7 == 1; + _win = value << 4 >> 7 == 1; + _value = Convert.ToByte(value); + if (_value != value) throw new OverflowException($"参数【{value}】含有无效的键。"); + } + + private ModifierKey(bool alt, bool ctrl, bool shift, bool win) + { + _alt = alt; + _ctrl = ctrl; + _shift = shift; + _win = win; + + var value = 0; + if (alt) value += 0x01; + if (ctrl) value += 0x02; + if (shift) value += 0x04; + if (win) value += 0x05; + _value = Convert.ToByte(value); + } + + /// + public override int GetHashCode() => _value; + + /// + public override bool Equals(object obj) + { + if (obj is ModifierKey b) return _value == b._value; + return false; + } + + /// + public bool Equals(ModifierKey another) => _value == another._value; + + /// + public static implicit operator byte(ModifierKey instance) => instance._value; + + /// + /// + public static implicit operator ModifierKey(byte value) => new ModifierKey(value); + + /// + /// + public static ModifierKey operator +(ModifierKey a, ModifierKey b) + { + if (a._alt && b._alt) throw new ArgumentException("相加的两个组合键共同包含了 ALT 键。"); + if (a._ctrl && b._ctrl) throw new ArgumentException("相加的两个组合键共同包含了 CTRL 键。"); + if (a._shift && b._shift) throw new ArgumentException("相加的两个组合键共同包含了 SHIFT 键。"); + if (a._win && b._win) throw new ArgumentException("相加的两个组合键共同包含了 WIN 键。"); + + var alt = a._alt || b._alt; + var ctrl = a._ctrl || b._ctrl; + var shift = a._shift || b._shift; + var win = a._win || b._win; + return new ModifierKey(alt, ctrl, shift, win); + } + + /// + public static ModifierKey operator -(ModifierKey a, ModifierKey b) + { + var alt = a._alt; + var ctrl = a._ctrl; + var shift = a._shift; + var win = a._win; + + if (b._alt) alt = false; + if (b._ctrl) ctrl = false; + if (b._shift) shift = false; + if (b._win) win = false; + + return new ModifierKey(alt, ctrl, shift, win); + } + + /// 表示 ALT 修饰键。 + public static ModifierKey ALT { get; } = new ModifierKey(0x01); + + /// 表示 CTRL 修饰键。 + public static ModifierKey CTRL { get; } = new ModifierKey(0x02); + + /// 表示 SHIFT 修饰键。 + public static ModifierKey SHIFT { get; } = new ModifierKey(0x04); + + /// 表示 WIN 修饰键。 + public static ModifierKey WIN { get; } = new ModifierKey(0x08); + + } + +} diff --git a/Apewer/BytesUtility.cs b/Apewer/BytesUtility.cs index 3ea3dfc..d4b2d04 100644 --- a/Apewer/BytesUtility.cs +++ b/Apewer/BytesUtility.cs @@ -767,6 +767,17 @@ namespace Apewer #endregion + #region PKCS #5 + + /// 使用密码生成密钥。 + static byte[] PKCS5(byte[] password, byte[] salt = null, int iterations = 1000, int bits = 32) + { + var rfc2898 = new Rfc2898DeriveBytes(password, salt, 1); + return rfc2898.GetBytes(32); + } + + #endregion + #region AES private static void Aes256(byte[] key, byte[] salt, Func create, Stream input, Stream output) @@ -842,6 +853,130 @@ namespace Apewer return result; } + private static readonly byte[] AesDefaultIV = new byte[16]; + + static T UseAes(byte[] key, byte[] iv, CipherMode cipherMode, PaddingMode paddingMode, Func callback) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + var keySize = key.Length * 8; + switch (keySize) + { + case 128: + case 192: + case 256: + break; + default: + throw new ArgumentException($"密钥大小【{keySize}】bits 无效。"); + } + + if (iv == null) iv = AesDefaultIV; + var ivSize = iv.Length * 8; + if (ivSize != 128) throw new ArgumentException($"初始化向量【{ivSize}】bits 无效。"); + + using (var rijndael = new RijndaelManaged()) + { + rijndael.Key = key; + rijndael.IV = iv; + rijndael.Mode = cipherMode; + rijndael.Padding = paddingMode; + + return callback.Invoke(rijndael); + } + } + + static void UseAes(Stream input, Stream output, byte[] key, byte[] iv, CipherMode cipherMode, PaddingMode paddingMode, Func create) + { + if (input == null) throw new ArgumentNullException(nameof(input)); + if (output == null) throw new ArgumentNullException(nameof(output)); + + UseAes(key, iv, cipherMode, paddingMode, rijndael => + { + using (var transformer = create.Invoke(rijndael)) + { + + using (var stream = new CryptoStream(output, transformer, CryptoStreamMode.Write)) + { + Read(input, stream); + stream.Close(); + } + } + return null; + }); + } + + static byte[] UseAes(byte[] input, byte[] key, byte[] iv, CipherMode cipherMode, PaddingMode paddingMode, Func create) + { + if (input == null) input = new byte[0]; + + var result = null as byte[]; + return UseAes(key, iv, cipherMode, paddingMode, rijndael => + { + using (var transformer = create.Invoke(rijndael)) + { + var result = transformer.TransformFinalBlock(input, 0, input.Length); + return result; + } + }); + } + + /// 执行 AES 128/192/256 加密。 + /// 明文。 + /// 密钥。 + /// 块密码模式。 + /// 填充模式。 + /// + /// + /// + /// + public static byte[] AesEncrypt(byte[] plain, byte[] key, CipherMode cipherMode = CipherMode.ECB, PaddingMode paddingMode = PaddingMode.PKCS7) + { + return UseAes(plain, key, null, cipherMode, paddingMode, (rijndael) => rijndael.CreateEncryptor(rijndael.Key, rijndael.IV)); + } + + /// 执行 AES 128/192/256 加密。 + /// 明文。 + /// 密钥,长度必须是 128 位(16 字节)、192 位(24 字节)或 256 位(32 字节)。 + /// 初始化向量,必须是 128 位(16 字节)。 + /// 块密码模式。 + /// 填充模式。 + /// + /// + /// + /// + public static byte[] AesEncrypt(byte[] plain, byte[] key, byte[] iv, CipherMode cipherMode = CipherMode.ECB, PaddingMode paddingMode = PaddingMode.PKCS7) + { + return UseAes(plain, key, iv, cipherMode, paddingMode, (rijndael) => rijndael.CreateEncryptor(rijndael.Key, rijndael.IV)); + } + + /// 执行 AES 128/192/256 解密。 + /// 密文。 + /// 密钥,长度必须是 128 位(16 字节)、192 位(24 字节)或 256 位(32 字节)。 + /// 块密码模式。 + /// 填充模式。 + /// + /// + /// + /// + public static byte[] AesDecrypt(byte[] cipher, byte[] key, CipherMode cipherMode = CipherMode.ECB, PaddingMode paddingMode = PaddingMode.PKCS7) + { + return UseAes(cipher, key, null, cipherMode, paddingMode, (rijndael) => rijndael.CreateEncryptor(rijndael.Key, rijndael.IV)); + } + + /// 执行 AES 128/192/256 解密。 + /// 密文。 + /// 密钥,长度必须是 128 位(16 字节)、192 位(24 字节)或 256 位(32 字节)。 + /// 初始化向量,必须是 128 位(16 字节)。 + /// 块密码模式。 + /// 填充模式。 + /// + /// + /// + /// + public static byte[] AesDecrypt(byte[] cipher, byte[] key, byte[] iv, CipherMode cipherMode = CipherMode.ECB, PaddingMode paddingMode = PaddingMode.PKCS7) + { + return UseAes(cipher, key, iv, cipherMode, paddingMode, (rijndael) => rijndael.CreateEncryptor(rijndael.Key, rijndael.IV)); + } + #endregion #region Hash diff --git a/Apewer/Network/HttpHeaders.cs b/Apewer/Network/HttpHeaders.cs index 1873486..358af04 100644 --- a/Apewer/Network/HttpHeaders.cs +++ b/Apewer/Network/HttpHeaders.cs @@ -328,12 +328,32 @@ namespace Apewer.Network return Add(name, value); } + /// 按名称排序。 + public void Sort() => _list.Sort(); + + /// 按指定的方式排序。 + public void Sort(IComparer comparer) => _list.Sort(comparer ?? throw new ArgumentNullException(nameof(comparer))); + + /// 按指定的方式排序。 + public void Sort(Comparison comparison) => _list.Sort(comparison ?? throw new ArgumentNullException(nameof(comparison))); + /// 每个元素组成为新数组。 public HttpHeader[] ToArray() => _list.ToArray(); + /// 生成 数组。 + public StringPairs ToStringParis() + { + var sp = new StringPairs(); + foreach (var item in _list) sp.Add(item.Name, item.Value); + return sp; + } + /// public override string ToString() => $"Count = {_list.Count}"; + /// + public static implicit operator StringPairs(HttpHeaders headers) => headers?.ToStringParis(); + #endregion #region Json diff --git a/Apewer/Web/MiniServer.cs b/Apewer/Web/MiniServer.cs index c6a5ed3..901ddae 100644 --- a/Apewer/Web/MiniServer.cs +++ b/Apewer/Web/MiniServer.cs @@ -151,7 +151,23 @@ namespace Apewer.Web { while (_socket != null) { - var socket = _socket.Accept(); + var socket = null as Socket; + try + { + socket = _socket.Accept(); + } + catch + { + if (socket != null) + { + try { socket.Close(); } catch { } + try { socket.Disconnect(false); } catch { } +#if !NET20 + try { socket.Dispose(); } catch { } +#endif + } + break; + } if (socket != null) ThreadPool.QueueUserWorkItem(Process, socket); } } diff --git a/Apewer/Web/StaticController.cs b/Apewer/Web/StaticController.cs index 40e32b3..6733cb2 100644 --- a/Apewer/Web/StaticController.cs +++ b/Apewer/Web/StaticController.cs @@ -101,14 +101,14 @@ namespace Apewer.Web if (System.IO.Directory.Exists(web)) { _root = new Class(web); - return www; + return web; } var @static = StorageUtility.CombinePath(app, "static"); if (System.IO.Directory.Exists(@static)) { _root = new Class(@static); - return www; + return @static; } _root = new Class(app);