├── HdmiExtender
├── HdmiExtenderLib
│ ├── PcapDotNet
│ │ ├── PcapDotNet.Base.dll
│ │ ├── PcapDotNet.Base.pdb
│ │ ├── PcapDotNet.Core.dll
│ │ ├── PcapDotNet.Core.pdb
│ │ ├── PcapDotNet.Packets.dll
│ │ ├── PcapDotNet.Packets.pdb
│ │ ├── PcapDotNet.Core.Extensions.dll
│ │ ├── PcapDotNet.Core.Extensions.pdb
│ │ └── PcapDotNet.Core.Extensions.XML
│ ├── HdmiExtenderLib.csproj.vspscc
│ ├── HttpServer
│ │ ├── Crypto
│ │ │ ├── KeyType.cs
│ │ │ ├── SignatureKey.cs
│ │ │ ├── KeyExchangeKey.cs
│ │ │ ├── Win32ErrorHelper.cs
│ │ │ ├── CertProperties.cs
│ │ │ ├── CryptKey.cs
│ │ │ ├── DisposableObject.cs
│ │ │ ├── Win32Native.cs
│ │ │ └── CryptContext.cs
│ │ ├── Mime.cs
│ │ └── SimpleHttpServer.cs
│ ├── HdmiExtenderSender.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Logger.cs
│ ├── VideoWebServer.cs
│ ├── HdmiExtenderLib.csproj
│ ├── JpegAssembler.cs
│ └── HdmiExtenderReceiver.cs
├── HdmiExtenderService
│ ├── App.config
│ ├── HdmiExtenderService.csproj.vspscc
│ ├── MainService.cs
│ ├── MainService.Designer.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Program.cs
│ └── HdmiExtenderService.csproj
├── HdmiExtenderTest
│ ├── App.config
│ ├── Properties
│ │ ├── Settings.settings
│ │ ├── Settings.Designer.cs
│ │ ├── AssemblyInfo.cs
│ │ ├── Resources.Designer.cs
│ │ └── Resources.resx
│ ├── HdmiExtenderTest.csproj.vspscc
│ ├── Program.cs
│ ├── MainForm.cs
│ ├── MainForm.Designer.cs
│ ├── HdmiExtenderTest.csproj
│ └── MainForm.resx
├── HdmiExtender.vssscc
└── HdmiExtender.sln
├── LICENSE
└── README.md
/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Base.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bp2008/HdmiExtender/HEAD/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Base.dll
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Base.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bp2008/HdmiExtender/HEAD/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Base.pdb
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Core.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bp2008/HdmiExtender/HEAD/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Core.dll
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Core.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bp2008/HdmiExtender/HEAD/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Core.pdb
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Packets.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bp2008/HdmiExtender/HEAD/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Packets.dll
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Packets.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bp2008/HdmiExtender/HEAD/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Packets.pdb
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Core.Extensions.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bp2008/HdmiExtender/HEAD/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Core.Extensions.dll
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Core.Extensions.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bp2008/HdmiExtender/HEAD/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Core.Extensions.pdb
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderService/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtender.vssscc:
--------------------------------------------------------------------------------
1 | ""
2 | {
3 | "FILE_VERSION" = "9237"
4 | "ENLISTMENT_CHOICE" = "NEVER"
5 | "PROJECT_FILE_RELATIVE_PATH" = ""
6 | "NUMBER_OF_EXCLUDED_FILES" = "0"
7 | "ORIGINAL_PROJECT_FILE_PATH" = ""
8 | "NUMBER_OF_NESTED_PROJECTS" = "0"
9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROJECT"
10 | }
11 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HdmiExtenderLib.csproj.vspscc:
--------------------------------------------------------------------------------
1 | ""
2 | {
3 | "FILE_VERSION" = "9237"
4 | "ENLISTMENT_CHOICE" = "NEVER"
5 | "PROJECT_FILE_RELATIVE_PATH" = ""
6 | "NUMBER_OF_EXCLUDED_FILES" = "0"
7 | "ORIGINAL_PROJECT_FILE_PATH" = ""
8 | "NUMBER_OF_NESTED_PROJECTS" = "0"
9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
10 | }
11 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/HdmiExtenderTest.csproj.vspscc:
--------------------------------------------------------------------------------
1 | ""
2 | {
3 | "FILE_VERSION" = "9237"
4 | "ENLISTMENT_CHOICE" = "NEVER"
5 | "PROJECT_FILE_RELATIVE_PATH" = ""
6 | "NUMBER_OF_EXCLUDED_FILES" = "0"
7 | "ORIGINAL_PROJECT_FILE_PATH" = ""
8 | "NUMBER_OF_NESTED_PROJECTS" = "0"
9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
10 | }
11 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderService/HdmiExtenderService.csproj.vspscc:
--------------------------------------------------------------------------------
1 | ""
2 | {
3 | "FILE_VERSION" = "9237"
4 | "ENLISTMENT_CHOICE" = "NEVER"
5 | "PROJECT_FILE_RELATIVE_PATH" = ""
6 | "NUMBER_OF_EXCLUDED_FILES" = "0"
7 | "ORIGINAL_PROJECT_FILE_PATH" = ""
8 | "NUMBER_OF_NESTED_PROJECTS" = "0"
9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
10 | }
11 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Crypto/KeyType.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This code was written by Keith Brown, and may be freely used.
3 | // Want to learn more about .NET? Visit pluralsight.com today!
4 | //
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace Pluralsight.Crypto
11 | {
12 | public enum KeyType : int
13 | {
14 | Exchange = 1,
15 | Signature = 2,
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using System.Windows.Forms;
6 |
7 | namespace HdmiExtenderLib
8 | {
9 | static class Program
10 | {
11 | ///
12 | /// The main entry point for the application.
13 | ///
14 | [STAThread]
15 | static void Main()
16 | {
17 | Application.EnableVisualStyles();
18 | Application.SetCompatibleTextRenderingDefault(false);
19 | Application.Run(new MainForm());
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Crypto/SignatureKey.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This code was written by Keith Brown, and may be freely used.
3 | // Want to learn more about .NET? Visit pluralsight.com today!
4 | //
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace Pluralsight.Crypto
11 | {
12 | public class SignatureKey : CryptKey
13 | {
14 | internal SignatureKey(CryptContext ctx, IntPtr handle) : base(ctx, handle) { }
15 |
16 | public override KeyType Type
17 | {
18 | get { return KeyType.Signature; }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Crypto/KeyExchangeKey.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This code was written by Keith Brown, and may be freely used.
3 | // Want to learn more about .NET? Visit pluralsight.com today!
4 | //
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace Pluralsight.Crypto
11 | {
12 | public class KeyExchangeKey : CryptKey
13 | {
14 | internal KeyExchangeKey(CryptContext ctx, IntPtr handle) : base(ctx, handle) {}
15 |
16 | public override KeyType Type
17 | {
18 | get { return KeyType.Exchange; }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderService/MainService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Data;
5 | using System.Diagnostics;
6 | using System.Linq;
7 | using System.ServiceProcess;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using HdmiExtenderLib;
11 |
12 | namespace HdmiExtenderService
13 | {
14 | public partial class MainService : ServiceBase
15 | {
16 | VideoWebServer server;
17 |
18 | public MainService()
19 | {
20 | InitializeComponent();
21 | }
22 |
23 | protected override void OnStart(string[] args)
24 | {
25 | server = new VideoWebServer(18080, -1, "192.168.168.55", 1);
26 | server.Start();
27 | }
28 |
29 | protected override void OnStop()
30 | {
31 | server.Stop();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Crypto/Win32ErrorHelper.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This code was written by Keith Brown, and may be freely used.
3 | // Want to learn more about .NET? Visit pluralsight.com today!
4 | //
5 | using System;
6 | using System.Runtime.InteropServices;
7 |
8 | namespace Pluralsight.Crypto
9 | {
10 | internal static class Win32ErrorHelper
11 | {
12 | internal static void ThrowExceptionIfGetLastErrorIsNotZero()
13 | {
14 | int win32ErrorCode = Marshal.GetLastWin32Error();
15 | if (0 != win32ErrorCode)
16 | Marshal.ThrowExceptionForHR(HResultFromWin32(win32ErrorCode));
17 | }
18 |
19 | private static int HResultFromWin32(int win32ErrorCode)
20 | {
21 | if (win32ErrorCode > 0)
22 | return (int)((((uint)win32ErrorCode) & 0x0000FFFF) | 0x80070000U);
23 | else return win32ErrorCode;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Crypto/CertProperties.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This code was written by Keith Brown, and may be freely used.
3 | // Want to learn more about .NET? Visit pluralsight.com today!
4 | //
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Security.Cryptography.X509Certificates;
10 |
11 | namespace Pluralsight.Crypto
12 | {
13 | public class SelfSignedCertProperties
14 | {
15 | public DateTime ValidFrom { get; set; }
16 | public DateTime ValidTo { get; set; }
17 | public X500DistinguishedName Name { get; set; }
18 | public int KeyBitLength { get; set; }
19 | public bool IsPrivateKeyExportable { get; set; }
20 |
21 | public SelfSignedCertProperties()
22 | {
23 | DateTime today = DateTime.Today;
24 | ValidFrom = today.AddDays(-1);
25 | ValidTo = today.AddYears(10);
26 | Name = new X500DistinguishedName("cn=self");
27 | KeyBitLength = 4096;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderService/MainService.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace HdmiExtenderService
2 | {
3 | partial class MainService
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Component Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | components = new System.ComponentModel.Container();
32 | this.ServiceName = "Service1";
33 | }
34 |
35 | #endregion
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 bp2008
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Crypto/CryptKey.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This code was written by Keith Brown, and may be freely used.
3 | // Want to learn more about .NET? Visit pluralsight.com today!
4 | //
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace Pluralsight.Crypto
11 | {
12 | public abstract class CryptKey : DisposeableObject
13 | {
14 | CryptContext ctx;
15 | IntPtr handle;
16 |
17 | internal IntPtr Handle { get { return handle; } }
18 |
19 | internal CryptKey(CryptContext ctx, IntPtr handle)
20 | {
21 | this.ctx = ctx;
22 | this.handle = handle;
23 | }
24 |
25 | public abstract KeyType Type { get; }
26 |
27 | protected override void CleanUp(bool viaDispose)
28 | {
29 | // keys are invalid once CryptContext is closed,
30 | // so the only time I try to close an individual key is if a user
31 | // explicitly disposes of the key.
32 | if (viaDispose)
33 | ctx.DestroyKey(this);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.18444
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace HdmiExtenderTest.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HdmiExtenderSender.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace HdmiExtenderLib
8 | {
9 | //public class HdmiExtenderSender
10 | //{
11 | /////
12 | ///// Broadcasts a Sender Control Packet, imitating an HDMI Extender Sender device. This packet should be broadcast once per second.
13 | /////
14 | //public void BroadcastSenderControlPacket()
15 | //{
16 | // try
17 | // {
18 | // using (Socket s = new Socket(SocketType.Dgram, ProtocolType.Udp))
19 | // {
20 | // string hexPacketNumber = (senderControlPacketCounter++).ToString("X").PadLeft(4, '0');
21 | // // This number needs to be little-endian.
22 | // hexPacketNumber = hexPacketNumber.Substring(2, 2) + hexPacketNumber.Substring(0, 2);
23 |
24 | // string hexControlPacket = "5446367a63010000" + hexPacketNumber + "00030303002400000000000000000000001000000000000000000000007800d1a6300001";
25 | // byte[] controlPacket = HexStringToByteArray(hexControlPacket);
26 | // s.Bind(new IPEndPoint(IPAddress.Parse("192.168.168.57"), 48689));
27 | // s.SendTo(controlPacket, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 48689));
28 | // }
29 | // }
30 | // catch (Exception ex)
31 | // {
32 | // Console.WriteLine(ex.ToString());
33 | // }
34 | //}
35 | //}
36 | }
37 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("HdmiExtender")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("HdmiExtender")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("e8663c9f-5ad5-43f3-9b92-5c840f4b2e42")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("HdmiExtenderLib")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("HdmiExtenderLib")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("227e07fa-b734-4188-946c-c2b9ee882c82")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderService/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("HdmiExtenderService")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("HdmiExtenderService")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("201826ca-9b82-4631-9b42-5dcad7e6eb4f")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Crypto/DisposableObject.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This code was written by Keith Brown, and may be freely used.
3 | // Want to learn more about .NET? Visit pluralsight.com today!
4 | //
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Runtime.InteropServices;
10 |
11 | namespace Pluralsight.Crypto
12 | {
13 | [StructLayout(LayoutKind.Sequential)]
14 | public abstract class DisposeableObject : IDisposable
15 | {
16 | private bool disposed = false;
17 |
18 | ~DisposeableObject()
19 | {
20 | CleanUp(false);
21 | }
22 |
23 | public void Dispose()
24 | {
25 | // note this method does not throw ObjectDisposedException
26 | if (!disposed)
27 | {
28 | CleanUp(true);
29 |
30 | disposed = true;
31 |
32 | GC.SuppressFinalize(this);
33 | }
34 | }
35 |
36 | protected abstract void CleanUp(bool viaDispose);
37 |
38 | ///
39 | /// Typical check for derived classes
40 | ///
41 | protected void ThrowIfDisposed()
42 | {
43 | ThrowIfDisposed(this.GetType().FullName);
44 | }
45 |
46 | ///
47 | /// Typical check for derived classes
48 | ///
49 | protected void ThrowIfDisposed(string objectName)
50 | {
51 | if (disposed)
52 | throw new ObjectDisposedException(objectName);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/MainForm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.ComponentModel;
5 | using System.Data;
6 | using System.Drawing;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Net;
10 | using System.Net.Sockets;
11 | using System.Text;
12 | using System.Threading;
13 | using System.Threading.Tasks;
14 | using System.Windows.Forms;
15 |
16 | namespace HdmiExtenderLib
17 | {
18 | public partial class MainForm : Form
19 | {
20 | System.Windows.Forms.Timer timer;
21 | HdmiExtenderReceiver receiver;
22 |
23 | byte[] previousImage;
24 | byte[] currentImage;
25 | int fpsCalc = 0;
26 | DateTime nextFpsSecond = DateTime.Now;
27 |
28 | public MainForm()
29 | {
30 | InitializeComponent();
31 | }
32 |
33 | private void MainForm_Load(object sender, EventArgs e)
34 | {
35 | timer = new System.Windows.Forms.Timer();
36 | timer.Interval = 1;
37 | timer.Tick += timer_Tick;
38 | timer.Start();
39 |
40 | receiver = new HdmiExtenderReceiver("192.168.168.55", 1);
41 | receiver.Start();
42 | }
43 |
44 | void timer_Tick(object sender, EventArgs e)
45 | {
46 | currentImage = receiver.LatestImage;
47 | if(currentImage != previousImage)
48 | {
49 | fpsCalc++;
50 | previousImage = currentImage;
51 | if (pb.Image != null)
52 | pb.Image.Dispose();
53 | MemoryStream ms = new MemoryStream(currentImage);
54 | Image img = Bitmap.FromStream(ms);
55 | lblVideoData.Text = img.Width + "x" + img.Height;
56 | pb.Image = img;
57 | }
58 | if (nextFpsSecond < DateTime.Now)
59 | {
60 | lblFrameRate.Text = fpsCalc.ToString();
61 | fpsCalc = 0;
62 | nextFpsSecond = DateTime.Now.AddSeconds(1);
63 | }
64 | // Unfortunately, I could not get audio to play using the NAudio library, so this test application only plays back video.
65 | }
66 |
67 | private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
68 | {
69 | receiver.Stop();
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderService/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.ServiceProcess;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using HdmiExtenderLib;
8 |
9 | namespace HdmiExtenderService
10 | {
11 | static class Program
12 | {
13 | ///
14 | /// The main entry point for the application.
15 | ///
16 | static void Main(string[] args)
17 | {
18 | // If the user starts this program with the argument "cmd", we will run as a console application. Otherwise we will run as a Windows Service.
19 | if (args.Length == 1 && args[0] == "cmd")
20 | {
21 | ushort port = 18080;
22 | MainService svc = new MainService();
23 | VideoWebServer server = new VideoWebServer(port, -1, "192.168.168.55", 1);
24 | server.Start();
25 | Console.WriteLine("This service was run with the command line argument \"cmd\".");
26 | Console.WriteLine("When run without arguments, this application acts as a Windows Service.");
27 | Console.WriteLine();
28 | Console.WriteLine("Jpeg still image:");
29 | Console.ForegroundColor = ConsoleColor.White;
30 | Console.WriteLine("\thttp://localhost:" + port + "/image.jpg");
31 | Console.ResetColor();
32 | Console.WriteLine();
33 | Console.WriteLine("Motion JPEG:");
34 | Console.ForegroundColor = ConsoleColor.White;
35 | Console.WriteLine("\thttp://localhost:" + port + "/image.mjpg");
36 | Console.ResetColor();
37 | Console.WriteLine();
38 | Console.WriteLine("PCM 48kHz, Signed 32 bit, Big Endian");
39 | Console.ForegroundColor = ConsoleColor.White;
40 | Console.WriteLine("\thttp://localhost:" + port + "/audio.wav");
41 | Console.ResetColor();
42 | Console.WriteLine();
43 | Console.Write("When you see ");
44 | Console.ForegroundColor = ConsoleColor.White;
45 | Console.Write("netdrop1");
46 | Console.ResetColor();
47 | Console.Write(" or ");
48 | Console.ForegroundColor = ConsoleColor.White;
49 | Console.Write("netdrop2");
50 | Console.ResetColor();
51 | Console.WriteLine(" in the console, this means a frame was dropped due to data loss between the Sender device and this program.");
52 | Console.WriteLine();
53 | Console.WriteLine("Http server running on port " + port + ". Press ENTER to exit.");
54 | Console.ReadLine();
55 | Console.WriteLine("Shutting down...");
56 | server.Stop();
57 | }
58 | else
59 | {
60 | ServiceBase[] ServicesToRun;
61 | ServicesToRun = new ServiceBase[]
62 | {
63 | new MainService()
64 | };
65 | ServiceBase.Run(ServicesToRun);
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtender.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.31101.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HdmiExtenderTest", "HdmiExtenderTest\HdmiExtenderTest.csproj", "{1219112D-78D0-4CA9-91FF-EACE56E89616}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HdmiExtenderLib", "HdmiExtenderLib\HdmiExtenderLib.csproj", "{DAAF93E3-E592-4639-92A6-1C72EA65EC6C}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HdmiExtenderService", "HdmiExtenderService\HdmiExtenderService.csproj", "{0A584C1B-5EE5-4569-BBFF-3BA36C9A0FAD}"
11 | EndProject
12 | Global
13 | GlobalSection(TeamFoundationVersionControl) = preSolution
14 | SccNumberOfProjects = 4
15 | SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
16 | SccTeamFoundationServer = https://tfs.codeplex.com/tfs/tfs36
17 | SccLocalPath0 = .
18 | SccProjectUniqueName1 = HdmiExtenderLib\\HdmiExtenderLib.csproj
19 | SccProjectName1 = HdmiExtenderLib
20 | SccLocalPath1 = HdmiExtenderLib
21 | SccProjectUniqueName2 = HdmiExtenderService\\HdmiExtenderService.csproj
22 | SccProjectName2 = HdmiExtenderService
23 | SccLocalPath2 = HdmiExtenderService
24 | SccProjectUniqueName3 = HdmiExtenderTest\\HdmiExtenderTest.csproj
25 | SccProjectName3 = HdmiExtenderTest
26 | SccLocalPath3 = HdmiExtenderTest
27 | EndGlobalSection
28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
29 | Debug|x64 = Debug|x64
30 | Release|x64 = Release|x64
31 | EndGlobalSection
32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
33 | {1219112D-78D0-4CA9-91FF-EACE56E89616}.Debug|x64.ActiveCfg = Debug|x64
34 | {1219112D-78D0-4CA9-91FF-EACE56E89616}.Debug|x64.Build.0 = Debug|x64
35 | {1219112D-78D0-4CA9-91FF-EACE56E89616}.Release|x64.ActiveCfg = Release|x64
36 | {1219112D-78D0-4CA9-91FF-EACE56E89616}.Release|x64.Build.0 = Release|x64
37 | {DAAF93E3-E592-4639-92A6-1C72EA65EC6C}.Debug|x64.ActiveCfg = Debug|x64
38 | {DAAF93E3-E592-4639-92A6-1C72EA65EC6C}.Debug|x64.Build.0 = Debug|x64
39 | {DAAF93E3-E592-4639-92A6-1C72EA65EC6C}.Release|x64.ActiveCfg = Release|x64
40 | {DAAF93E3-E592-4639-92A6-1C72EA65EC6C}.Release|x64.Build.0 = Release|x64
41 | {0A584C1B-5EE5-4569-BBFF-3BA36C9A0FAD}.Debug|x64.ActiveCfg = Debug|x64
42 | {0A584C1B-5EE5-4569-BBFF-3BA36C9A0FAD}.Debug|x64.Build.0 = Debug|x64
43 | {0A584C1B-5EE5-4569-BBFF-3BA36C9A0FAD}.Release|x64.ActiveCfg = Release|x64
44 | {0A584C1B-5EE5-4569-BBFF-3BA36C9A0FAD}.Release|x64.Build.0 = Release|x64
45 | EndGlobalSection
46 | GlobalSection(SolutionProperties) = preSolution
47 | HideSolutionNode = FALSE
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.18444
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace HdmiExtenderTest.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HdmiExtenderTest.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HdmiExtender
2 |
3 | Captures the output of a Lenkeng LKV373 HDMI Extender TX (Sender) device, making the audio and video streams usable by 3rd party media players.
4 |
5 | ## Credits
6 |
7 | This HdmiExtender project is based on the reverse-engineering efforts of danman, which he detailed here, in his blog: https://danman.eu/blog/reverse-engineering-lenkeng-hdmi-over-ip-extender/ His detailed descriptions were easy to follow, and led to a relatively pain-free implementation. For full details of how the communication protocol works, check his blog!
8 |
9 | It turns out that .NET's sockets are not quite low-level enough to read the full length of the malformed UDP packets generated by a Sender device. So this project uses Pcap.NET to sniff the packets in their entirety. You can find Pcap.NET here: https://github.com/PcapDotNet/Pcap.Net
10 |
11 | ## Who is it for?
12 |
13 | This project is intended for *computer literate* people, preferably with C# programming experience, who want to capture video and audio data from a [Lenkeng LKV373 HDMI Extender TX (Sender) device](https://github.com/bp2008/HdmiExtender/wiki/Lenkeng-LKV373-HDMI-Extender-TX-%28Sender%29-device).
14 |
15 | ## Requirements
16 |
17 | * One [Lenkeng LKV373 HDMI Extender TX (Sender) device](https://github.com/bp2008/HdmiExtender/wiki/Lenkeng-LKV373-HDMI-Extender-TX-%28Sender%29-device). This was designed for the model with "V2.0" printed on the back, but it may work with the older model too.
18 | * A Windows PC **with WinPCap installed**
19 | * (Optional; Recommended) A second Network Interface adapter for the PC, so you can isolate the HDMI Extender Sender device from your LAN. This will keep the video **broadcast** from the Sender device under control.
20 | * (Optional; Recommended) A fast CPU for real-time transcoding of the video data using FFMPEG.
21 |
22 | I have prepared some [ffmpeg sample configurations](https://github.com/bp2008/HdmiExtender/wiki/ffmpeg-sample-configurations) to demonstrate some of the ways to consume the video and audio streams produced by HdmiExtender.
23 |
24 | ## Other thoughts
25 |
26 | This project is currently very unpolished. I have not built a binary distribution because the network settings are hard-coded. You will need to download the source code, open it in Visual Studio (2013 community edition is what I recommend), and modify the network settings before building.
27 |
28 | ## Disclaimer
29 |
30 | The Lenkeng HDMI extender is not designed to be an HDMI capture device. Seriously, you should probably buy a real HDMI capture device if that is what you need. It will cost more, but it should work better.
31 |
32 | Here are just some deficiencies I have noticed:
33 |
34 | * You get no support from the manufacturer for this type of usage.
35 | * Audio gets out of sync with the video. Even with manual sync correction, it will often go out of sync again within minutes. This is only tested using FFMPEG to combine and transcode the streams. It is possible another method exists to keep the streams in sync, but I do not know of it.
36 | * Surround sound gets downsampled to stereo.
37 | * The JPEG images are encoded using the 16-235 "TV" range of luminance instead of the full 0-255 range, so you have reduced contrast in the output. (it can be corrected, with a bit of quality loss, using 3rd party tools or video players)
38 | * A high input refresh rate can cause tearing. For example 60hz video from my laptop causes very bad tearing in the output from the Sender device, but with my Fire TV stick, I see no tearing.
39 | * When active, the sender **broadcasts** between 30 and 90 Mbps depending on the input, so it may be ideal to isolate it from your network by using a second network adapter (I use a USB 3.0 network adapter).
40 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderService/HdmiExtenderService.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {0A584C1B-5EE5-4569-BBFF-3BA36C9A0FAD}
8 | Exe
9 | Properties
10 | HdmiExtenderService
11 | HdmiExtenderService
12 | v4.5
13 | 512
14 | SAK
15 | SAK
16 | SAK
17 | SAK
18 |
19 |
20 | true
21 | bin\x64\Debug\
22 | DEBUG;TRACE
23 | full
24 | x64
25 | prompt
26 | MinimumRecommendedRules.ruleset
27 | true
28 |
29 |
30 | bin\x64\Release\
31 | TRACE
32 | true
33 | pdbonly
34 | x64
35 | prompt
36 | MinimumRecommendedRules.ruleset
37 | true
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Component
55 |
56 |
57 | MainService.cs
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | {daaf93e3-e592-4639-92a6-1c72ea65ec6c}
68 | HdmiExtenderLib
69 |
70 |
71 |
72 |
79 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/MainForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace HdmiExtenderLib
2 | {
3 | partial class MainForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.pb = new System.Windows.Forms.PictureBox();
32 | this.lblVideoData = new System.Windows.Forms.Label();
33 | this.lblFrameRate = new System.Windows.Forms.Label();
34 | this.label1 = new System.Windows.Forms.Label();
35 | ((System.ComponentModel.ISupportInitialize)(this.pb)).BeginInit();
36 | this.SuspendLayout();
37 | //
38 | // pb
39 | //
40 | this.pb.Location = new System.Drawing.Point(12, 44);
41 | this.pb.Name = "pb";
42 | this.pb.Size = new System.Drawing.Size(640, 360);
43 | this.pb.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
44 | this.pb.TabIndex = 0;
45 | this.pb.TabStop = false;
46 | //
47 | // lblVideoData
48 | //
49 | this.lblVideoData.AutoSize = true;
50 | this.lblVideoData.Location = new System.Drawing.Point(12, 9);
51 | this.lblVideoData.Name = "lblVideoData";
52 | this.lblVideoData.Size = new System.Drawing.Size(24, 13);
53 | this.lblVideoData.TabIndex = 2;
54 | this.lblVideoData.Text = "0x0";
55 | //
56 | // lblFrameRate
57 | //
58 | this.lblFrameRate.AutoSize = true;
59 | this.lblFrameRate.Location = new System.Drawing.Point(158, 9);
60 | this.lblFrameRate.Name = "lblFrameRate";
61 | this.lblFrameRate.Size = new System.Drawing.Size(13, 13);
62 | this.lblFrameRate.TabIndex = 3;
63 | this.lblFrameRate.Text = "0";
64 | //
65 | // label1
66 | //
67 | this.label1.AutoSize = true;
68 | this.label1.Location = new System.Drawing.Point(122, 9);
69 | this.label1.Name = "label1";
70 | this.label1.Size = new System.Drawing.Size(30, 13);
71 | this.label1.TabIndex = 4;
72 | this.label1.Text = "FPS:";
73 | //
74 | // MainForm
75 | //
76 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
77 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
78 | this.ClientSize = new System.Drawing.Size(664, 412);
79 | this.Controls.Add(this.label1);
80 | this.Controls.Add(this.lblFrameRate);
81 | this.Controls.Add(this.lblVideoData);
82 | this.Controls.Add(this.pb);
83 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
84 | this.Name = "MainForm";
85 | this.Text = "HDMI Extender Test";
86 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing);
87 | this.Load += new System.EventHandler(this.MainForm_Load);
88 | ((System.ComponentModel.ISupportInitialize)(this.pb)).EndInit();
89 | this.ResumeLayout(false);
90 | this.PerformLayout();
91 |
92 | }
93 |
94 | #endregion
95 |
96 | private System.Windows.Forms.PictureBox pb;
97 | private System.Windows.Forms.Label lblVideoData;
98 | private System.Windows.Forms.Label lblFrameRate;
99 | private System.Windows.Forms.Label label1;
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Crypto/Win32Native.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This code was written by Keith Brown, and may be freely used.
3 | // Want to learn more about .NET? Visit pluralsight.com today!
4 | //
5 | using System;
6 | using System.Runtime.InteropServices;
7 |
8 | namespace Pluralsight.Crypto
9 | {
10 | internal class Win32Native
11 | {
12 | [DllImport("AdvApi32.dll", ExactSpelling = true, SetLastError = true)]
13 | [return: MarshalAs(UnmanagedType.Bool)]
14 | internal static extern bool CryptReleaseContext(IntPtr ctx, int flags);
15 |
16 | [DllImport("AdvApi32.dll", EntryPoint="CryptAcquireContextW", ExactSpelling = true, CharSet=CharSet.Unicode, SetLastError = true)]
17 | [return: MarshalAs(UnmanagedType.Bool)]
18 | internal static extern bool CryptAcquireContext(
19 | out IntPtr providerContext,
20 | string containerName,
21 | string providerName,
22 | int providerType,
23 | int flags);
24 |
25 | [DllImport("AdvApi32.dll", ExactSpelling = true, SetLastError = true)]
26 | [return: MarshalAs(UnmanagedType.Bool)]
27 | internal static extern bool CryptDestroyKey(IntPtr cryptKeyHandle);
28 |
29 | [DllImport("AdvApi32.dll", ExactSpelling = true, SetLastError = true)]
30 | [return: MarshalAs(UnmanagedType.Bool)]
31 | internal static extern bool CryptGenKey(
32 | IntPtr providerContext,
33 | int algorithmId,
34 | uint flags,
35 | out IntPtr cryptKeyHandle);
36 |
37 | [DllImport("Crypt32.dll", ExactSpelling = true, SetLastError = true)]
38 | internal static extern IntPtr CertCreateSelfSignCertificate(
39 | IntPtr providerHandle,
40 | [In] CryptoApiBlob subjectIssuerBlob,
41 | int flags,
42 | [In] CryptKeyProviderInformation keyProviderInfo,
43 | IntPtr signatureAlgorithm,
44 | [In] SystemTime startTime,
45 | [In] SystemTime endTime,
46 | IntPtr extensions);
47 |
48 | [DllImport("Crypt32.dll", ExactSpelling = true, SetLastError = true)]
49 | [return: MarshalAs(UnmanagedType.Bool)]
50 | internal static extern bool CertFreeCertificateContext(IntPtr certContext);
51 |
52 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
53 | [return: MarshalAs(UnmanagedType.Bool)]
54 | public static extern bool FileTimeToSystemTime(
55 | [In] ref long fileTime,
56 | [Out] SystemTime systemTime);
57 |
58 | [StructLayout(LayoutKind.Sequential)]
59 | internal class CryptoApiBlob
60 | {
61 | public int DataLength;
62 | public IntPtr Data;
63 |
64 | public CryptoApiBlob(int dataLength, IntPtr data)
65 | {
66 | this.DataLength = dataLength;
67 | this.Data = data;
68 | }
69 | }
70 |
71 | [StructLayout(LayoutKind.Sequential)]
72 | internal class SystemTime
73 | {
74 | public short Year;
75 | public short Month;
76 | public short DayOfWeek;
77 | public short Day;
78 | public short Hour;
79 | public short Minute;
80 | public short Second;
81 | public short Milliseconds;
82 | }
83 |
84 | [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
85 | internal class CryptKeyProviderInformation
86 | {
87 | public string ContainerName;
88 | public string ProviderName;
89 | public int ProviderType;
90 | public int Flags;
91 | public int ProviderParameterCount;
92 | public IntPtr ProviderParameters;
93 | public int KeySpec;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/Logger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.IO;
6 |
7 | namespace HdmiExtenderLib
8 | {
9 | public enum LogType
10 | {
11 | HttpServer
12 | }
13 | public enum LoggingMode
14 | {
15 | None = 0,
16 | Console = 1,
17 | File = 2,
18 | Email = 4
19 | }
20 | public class Logger
21 | {
22 | public static LoggingMode logType = LoggingMode.Console | LoggingMode.File;
23 | public static string logFilePath;
24 | private static object lockObj = new object();
25 | static Logger()
26 | {
27 | string applicationDirectory = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).Directory.FullName;
28 | logFilePath = Path.Combine(applicationDirectory, "Errors.txt");
29 | }
30 | public static void Debug(Exception ex, string additionalInformation = "")
31 | {
32 | if (additionalInformation == null)
33 | additionalInformation = "";
34 | lock (lockObj)
35 | {
36 | if ((logType & LoggingMode.Console) > 0)
37 | {
38 | if (ex != null)
39 | Console.Write("Exception thrown at ");
40 | Console.ForegroundColor = ConsoleColor.Green;
41 | Console.WriteLine(DateTime.Now.ToString());
42 | if (ex != null)
43 | {
44 | Console.ForegroundColor = ConsoleColor.Red;
45 | Console.WriteLine(ex.ToString());
46 | }
47 | if (!string.IsNullOrEmpty(additionalInformation))
48 | {
49 | Console.ForegroundColor = ConsoleColor.DarkYellow;
50 | if (ex != null)
51 | Console.Write("Additional information: ");
52 | Console.WriteLine(additionalInformation);
53 | }
54 | Console.ResetColor();
55 | }
56 | if ((logType & LoggingMode.File) > 0 && (ex != null || !string.IsNullOrEmpty(additionalInformation)))
57 | {
58 | StringBuilder debugMessage = new StringBuilder();
59 | debugMessage.Append("-------------").Append(Environment.NewLine);
60 | if (ex != null)
61 | debugMessage.Append("Exception thrown at ");
62 | debugMessage.Append(DateTime.Now.ToString()).Append(Environment.NewLine);
63 | if (!string.IsNullOrEmpty(additionalInformation))
64 | {
65 | if (ex != null)
66 | debugMessage.Append("Additional information: ");
67 | debugMessage.Append(additionalInformation).Append(Environment.NewLine);
68 | }
69 | if (ex != null)
70 | debugMessage.Append(ex.ToString()).Append(Environment.NewLine);
71 | debugMessage.Append("-------------").Append(Environment.NewLine);
72 | int attempts = 0;
73 | while (attempts < 5)
74 | {
75 | try
76 | {
77 | File.AppendAllText(logFilePath, debugMessage.ToString());
78 | attempts = 10;
79 | }
80 | catch (Exception)
81 | {
82 | attempts++;
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
89 | public static void Debug(string message)
90 | {
91 | Debug(null, message);
92 | }
93 | public static void Info(string message)
94 | {
95 | if (message == null)
96 | return;
97 | lock (lockObj)
98 | {
99 | if ((logType & LoggingMode.Console) > 0)
100 | {
101 | Console.ForegroundColor = ConsoleColor.Green;
102 | Console.WriteLine(DateTime.Now.ToString());
103 | Console.ResetColor();
104 | Console.WriteLine(message);
105 | }
106 | if ((logType & LoggingMode.File) > 0)
107 | {
108 | int attempts = 0;
109 | while (attempts < 5)
110 | {
111 | try
112 | {
113 | File.AppendAllText(logFilePath, DateTime.Now.ToString() + Environment.NewLine + message + Environment.NewLine);
114 | attempts = 10;
115 | }
116 | catch (Exception)
117 | {
118 | attempts++;
119 | }
120 | }
121 | }
122 | }
123 | }
124 |
125 | public static void Special(LogType logType, string message)
126 | {
127 | if (logType == LogType.HttpServer)
128 | {
129 | //Info(message);
130 | }
131 | }
132 |
133 | public static SimpleHttp.ILogger httpLogger = new HttpLogger();
134 | }
135 | class HttpLogger : SimpleHttp.ILogger
136 | {
137 | void SimpleHttp.ILogger.Log(Exception ex, string additionalInformation)
138 | {
139 | Logger.Debug(ex, additionalInformation);
140 | }
141 |
142 | void SimpleHttp.ILogger.Log(string str)
143 | {
144 | Logger.Debug(str);
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/VideoWebServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading;
6 | using System.IO;
7 | using System.Diagnostics;
8 | using SimpleHttp;
9 | using System.Text.RegularExpressions;
10 | using System.Web;
11 | using System.Net.Sockets;
12 | using System.Collections.Concurrent;
13 |
14 | namespace HdmiExtenderLib
15 | {
16 | public class VideoWebServer : HttpServer
17 | {
18 | private HdmiExtenderReceiver receiver;
19 |
20 | public VideoWebServer(int port, int port_https, string senderIPAddress, int networkInterfaceIndex)
21 | : base(port, port_https)
22 | {
23 | receiver = new HdmiExtenderReceiver(senderIPAddress, networkInterfaceIndex);
24 | receiver.Start();
25 | }
26 | public override void handleGETRequest(HttpProcessor p)
27 | {
28 | try
29 | {
30 | string requestedPage = Uri.UnescapeDataString(p.request_url.AbsolutePath.TrimStart('/'));
31 | if (requestedPage == "image.jpg")
32 | {
33 | byte[] latestImage = receiver.LatestImage;
34 | if (latestImage == null)
35 | latestImage = new byte[0];
36 | p.writeSuccess("image/jpeg", latestImage.Length);
37 | p.outputStream.Flush();
38 | p.rawOutputStream.Write(latestImage, 0, latestImage.Length);
39 | }
40 | else if (requestedPage.EndsWith("image.mjpg"))
41 | {
42 | p.tcpClient.ReceiveBufferSize = 4;
43 | p.tcpClient.SendBufferSize = 4;
44 | Console.WriteLine("Beginning mjpg stream");
45 | p.writeSuccess("multipart/x-mixed-replace;boundary=hdmiextender");
46 | byte[] previousImage = null;
47 | byte[] currentImage;
48 | while (!this.stopRequested)
49 | {
50 | try
51 | {
52 | currentImage = receiver.LatestImage;
53 | if(currentImage == previousImage)
54 | Thread.Sleep(1);
55 | else
56 | {
57 | previousImage = currentImage;
58 |
59 | p.outputStream.WriteLine("--hdmiextender");
60 | p.outputStream.WriteLine("Content-Type: image/jpeg");
61 | p.outputStream.WriteLine("Content-Length: " + currentImage.Length);
62 | p.outputStream.WriteLine();
63 | p.outputStream.Flush();
64 | p.rawOutputStream.Write(currentImage, 0, currentImage.Length);
65 | p.rawOutputStream.Flush();
66 | p.outputStream.WriteLine();
67 | p.outputStream.Flush();
68 | }
69 | }
70 | catch (Exception ex)
71 | {
72 | if (!p.isOrdinaryDisconnectException(ex))
73 | Logger.Debug(ex);
74 | break;
75 | }
76 | }
77 |
78 | Console.WriteLine("Ending mjpg stream");
79 | }
80 | else if (requestedPage == "audio.wav")
81 | {
82 | Console.WriteLine("Beginning audio stream");
83 | int? audioRegistrationId = null;
84 | try
85 | {
86 | ConcurrentQueue audioData = new ConcurrentQueue();
87 | audioRegistrationId = receiver.RegisterAudioListener(audioData);
88 |
89 | p.writeSuccess("audio/x-wav");
90 | p.outputStream.Flush();
91 | byte[] buffer;
92 | while (!this.stopRequested)
93 | {
94 | while (audioData.TryDequeue(out buffer))
95 | p.rawOutputStream.Write(buffer, 0, buffer.Length);
96 | Thread.Sleep(1);
97 | }
98 | }
99 | catch (Exception ex)
100 | {
101 | if (!p.isOrdinaryDisconnectException(ex))
102 | Logger.Debug(ex);
103 | }
104 | finally
105 | {
106 | Console.WriteLine("Ending audio stream");
107 | if(audioRegistrationId != null)
108 | receiver.UnregisterAudioListener(audioRegistrationId.Value);
109 | }
110 | }
111 | else if (requestedPage == "raw.html")
112 | {
113 | p.writeSuccess();
114 | p.outputStream.Write(@"
115 |
116 | Raw MJPEG view
117 |
123 |
124 |
125 |
126 |
127 | ");
128 | }
129 | }
130 | catch (Exception ex)
131 | {
132 | if (!p.isOrdinaryDisconnectException(ex))
133 | Logger.Debug(ex);
134 | }
135 | }
136 |
137 | public override void handlePOSTRequest(HttpProcessor p, StreamReader inputData)
138 | {
139 | try
140 | {
141 | string requestedPage = p.request_url.AbsolutePath.TrimStart('/');
142 |
143 | }
144 | catch (Exception ex)
145 | {
146 | Logger.Debug(ex);
147 | }
148 | }
149 |
150 | public override void stopServer()
151 | {
152 | receiver.Stop();
153 | }
154 | }
155 | }
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/HdmiExtenderTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {1219112D-78D0-4CA9-91FF-EACE56E89616}
8 | WinExe
9 | Properties
10 | HdmiExtenderTest
11 | HdmiExtenderTest
12 | v4.5
13 | 512
14 | SAK
15 | SAK
16 | SAK
17 | SAK
18 |
19 |
20 | true
21 | bin\x64\Debug\
22 | DEBUG;TRACE
23 | full
24 | x64
25 | prompt
26 | MinimumRecommendedRules.ruleset
27 | true
28 |
29 |
30 | bin\x64\Release\
31 | TRACE
32 | true
33 | pdbonly
34 | x64
35 | prompt
36 | MinimumRecommendedRules.ruleset
37 | true
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | Form
54 |
55 |
56 | MainForm.cs
57 |
58 |
59 |
60 |
61 | MainForm.cs
62 |
63 |
64 | ResXFileCodeGenerator
65 | Resources.Designer.cs
66 | Designer
67 |
68 |
69 | True
70 | Resources.resx
71 | True
72 |
73 |
74 | SettingsSingleFileGenerator
75 | Settings.Designer.cs
76 |
77 |
78 | True
79 | Settings.settings
80 | True
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | {daaf93e3-e592-4639-92a6-1c72ea65ec6c}
89 | HdmiExtenderLib
90 |
91 |
92 |
93 |
100 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HdmiExtenderLib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {DAAF93E3-E592-4639-92A6-1C72EA65EC6C}
8 | Library
9 | Properties
10 | HdmiExtenderLib
11 | HdmiExtenderLib
12 | v4.5
13 | 512
14 | SAK
15 | SAK
16 | SAK
17 | SAK
18 |
19 |
20 | true
21 | bin\x64\Debug\
22 | DEBUG;TRACE
23 | full
24 | x64
25 | prompt
26 | MinimumRecommendedRules.ruleset
27 |
28 |
29 | bin\x64\Release\
30 | TRACE
31 | true
32 | pdbonly
33 | x64
34 | prompt
35 | MinimumRecommendedRules.ruleset
36 |
37 |
38 |
39 | False
40 | PcapDotNet\PcapDotNet.Base.dll
41 |
42 |
43 | False
44 | PcapDotNet\PcapDotNet.Core.dll
45 |
46 |
47 | False
48 | PcapDotNet\PcapDotNet.Core.Extensions.dll
49 |
50 |
51 | False
52 | PcapDotNet\PcapDotNet.Packets.dll
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
103 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/JpegAssembler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace HdmiExtenderLib
8 | {
9 | ///
10 | /// Accepts data blocks from Lenkeng HDMI Extender Sender devices, and returns completed images when available.
11 | ///
12 | public class JpegAssembler
13 | {
14 | private FragmentedJpeg currentFrame;
15 | ///
16 | /// This temporarily holds an unfinished frame in the event of UDP packets getting mixed up.
17 | ///
18 | private FragmentedJpeg previousFrame;
19 |
20 | public byte[] AddChunkAndGetNextImage(byte[] data)
21 | {
22 | if(data.Length <= 4)
23 | return null;
24 |
25 | ushort frameNumber = BitConverter.ToUInt16(data.Take(2).Reverse().ToArray(), 0);
26 | if (currentFrame == null)
27 | {
28 | // We have no current frame, so either we just started or we just finished a current frame and need to create a new one.
29 | currentFrame = new FragmentedJpeg(frameNumber);
30 | currentFrame.AddFragment(data);
31 | }
32 | else if (currentFrame.frameNumber == frameNumber)
33 | {
34 | // This is the current frame, so simply add the chunk to it.
35 | currentFrame.AddFragment(data);
36 | }
37 | else
38 | {
39 | // This frame number is not our current frame.
40 | if (previousFrame != null && previousFrame.frameNumber == frameNumber)
41 | {
42 | // This chunk belongs to the previous frame (i.e. a chunk arrived out of order)
43 | previousFrame.AddFragment(data);
44 | }
45 | else
46 | {
47 | // Our current frame is not yet finished, but we need to move on to a new frame.
48 | // Shift the current frame to the previous frame position, and make this chunk belong to the new current frame.
49 | if (previousFrame != null)
50 | Console.WriteLine("netdrop2");
51 | previousFrame = currentFrame;
52 | currentFrame = new FragmentedJpeg(frameNumber);
53 | currentFrame.AddFragment(data);
54 | }
55 | }
56 | if (currentFrame.isComplete)
57 | {
58 | byte[] imgData = currentFrame.finalData;
59 | currentFrame = null; // The current frame just finished
60 | if (previousFrame != null)
61 | Console.WriteLine("netdrop1");
62 | previousFrame = null; // the previous frame is now too old as well
63 | return imgData;
64 | }
65 | if (previousFrame != null && previousFrame.isComplete)
66 | {
67 | byte[] imgData = previousFrame.finalData;
68 | previousFrame = null;
69 | return imgData;
70 | }
71 | return null;
72 | }
73 | }
74 | ///
75 | /// Stores and assembles chunks of a jpeg image that may arrive out of order from a Lenkeng HDMI over IP Extender.
76 | ///
77 | public class FragmentedJpeg
78 | {
79 | public ushort frameNumber;
80 | public bool isComplete = false;
81 | public byte[] finalData;
82 |
83 | private int totalLength = 0;
84 |
85 | private bool hasReceivedFinalChunk = false;
86 | ///
87 | /// This is set when the final chunk of the image is received. This does not mean that the image is complete; merely that the chunk marked as final has arrived. Some chunks may be lost or out of order, so this FragmentedJpeg may be completed later, or never.
88 | ///
89 | private ushort? finalChunkNumber = null;
90 |
91 | ///
92 | /// Ensures we do not add the same chunk twice due to duplication for any reason.
93 | ///
94 | private HashSet receivedChunks = new HashSet();
95 | private List chunks = new List();
96 |
97 | public FragmentedJpeg(ushort frameNumber)
98 | {
99 | this.frameNumber = frameNumber;
100 | }
101 |
102 | ///
103 | /// Adds the specified data fragment and returns true if this FragmentedJpeg is complete.
104 | ///
105 | ///
106 | ///
107 | public bool AddFragment(byte[] data)
108 | {
109 | if(!isComplete)
110 | {
111 | JpegChunk chunk = new JpegChunk(data);
112 | if (!receivedChunks.Contains(chunk.chunkNumber))
113 | {
114 | receivedChunks.Add(chunk.chunkNumber);
115 | chunks.Add(chunk);
116 | totalLength += chunk.data.Length - 4;
117 | if (chunk.isFinalChunk)
118 | {
119 | finalChunkNumber = chunk.chunkNumber;
120 | //Console.WriteLine("Got final chunk for " + frameNumber);
121 | }
122 | if (finalChunkNumber != null)
123 | {
124 | if (chunks.Count == finalChunkNumber + 1)
125 | {
126 | chunks.Sort();
127 | int idx = 0;
128 | finalData = new byte[totalLength];
129 | foreach (JpegChunk c in chunks)
130 | {
131 | Array.Copy(c.data, 4, finalData, idx, c.data.Length - 4);
132 | idx += c.data.Length - 4;
133 | }
134 | isComplete = true;
135 | }
136 | }
137 | }
138 | }
139 | return isComplete;
140 | }
141 |
142 | private class JpegChunk : IComparable
143 | {
144 | private static ushort maskMSB = Convert.ToUInt16("1000000000000000", 2);
145 | private static ushort maskMSB_Inverse = Convert.ToUInt16("0111111111111111", 2);
146 |
147 | public ushort chunkNumber;
148 | public byte[] data;
149 | public bool isFinalChunk;
150 |
151 | public JpegChunk(byte[] data)
152 | {
153 | this.data = data;
154 | this.chunkNumber = BitConverter.ToUInt16(data.Skip(2).Take(2).Reverse().ToArray(), 0);
155 | // The most significant bit of the chunk number will be set if this is the final chunk.
156 | this.isFinalChunk = (chunkNumber & maskMSB) > 0;
157 | if (isFinalChunk)
158 | chunkNumber = (ushort)(chunkNumber & maskMSB_Inverse);
159 | }
160 |
161 | public override string ToString()
162 | {
163 | return chunkNumber.ToString();
164 | }
165 |
166 | public int CompareTo(JpegChunk other)
167 | {
168 | return this.chunkNumber.CompareTo(other.chunkNumber);
169 | }
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Crypto/CryptContext.cs:
--------------------------------------------------------------------------------
1 | //
2 | // This code was written by Keith Brown, and may be freely used.
3 | // Want to learn more about .NET? Visit pluralsight.com today!
4 | //
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Runtime.InteropServices;
10 | using System.Security.Cryptography.X509Certificates;
11 |
12 | namespace Pluralsight.Crypto
13 | {
14 | // This class has just enough functionality for generating self-signed certs.
15 | // In the future, I may expand it to do other things.
16 | public class CryptContext : DisposeableObject
17 | {
18 | private IntPtr handle = IntPtr.Zero;
19 |
20 | public IntPtr Handle { get { return handle; } }
21 |
22 | public string ContainerName { get; set; }
23 | public string ProviderName { get; set; }
24 | public int ProviderType { get; set; }
25 | public int Flags { get; set; }
26 |
27 | ///
28 | /// By default, sets up to create a new randomly named key container
29 | ///
30 | public CryptContext()
31 | {
32 | ContainerName = Guid.NewGuid().ToString();
33 | ProviderType = 1; // default RSA provider
34 | Flags = 8; // create new keyset
35 | }
36 |
37 | public void Open()
38 | {
39 | ThrowIfDisposed();
40 | if (!Win32Native.CryptAcquireContext(out handle, ContainerName, ProviderName, ProviderType, Flags))
41 | Win32ErrorHelper.ThrowExceptionIfGetLastErrorIsNotZero();
42 | }
43 |
44 | public X509Certificate2 CreateSelfSignedCertificate(SelfSignedCertProperties properties)
45 | {
46 | ThrowIfDisposedOrNotOpen();
47 |
48 | GenerateKeyExchangeKey(properties.IsPrivateKeyExportable, properties.KeyBitLength);
49 | //GenerateSignatureKey(properties.IsPrivateKeyExportable, properties.KeyBitLength);
50 |
51 | byte[] asnName = properties.Name.RawData;
52 | GCHandle asnNameHandle = GCHandle.Alloc(asnName, GCHandleType.Pinned);
53 |
54 | var kpi = new Win32Native.CryptKeyProviderInformation
55 | {
56 | ContainerName = this.ContainerName,
57 | KeySpec = (int)KeyType.Exchange,
58 | ProviderType = 1, // default RSA provider
59 | };
60 |
61 | IntPtr certContext = Win32Native.CertCreateSelfSignCertificate(
62 | handle,
63 | new Win32Native.CryptoApiBlob(asnName.Length, asnNameHandle.AddrOfPinnedObject()),
64 | 0, kpi, IntPtr.Zero,
65 | ToSystemTime(properties.ValidFrom),
66 | ToSystemTime(properties.ValidTo),
67 | IntPtr.Zero);
68 |
69 | asnNameHandle.Free();
70 |
71 | if (IntPtr.Zero == certContext)
72 | Win32ErrorHelper.ThrowExceptionIfGetLastErrorIsNotZero();
73 |
74 | X509Certificate2 cert = new X509Certificate2(certContext); // dups the context (increasing it's refcount)
75 |
76 | if (!Win32Native.CertFreeCertificateContext(certContext))
77 | Win32ErrorHelper.ThrowExceptionIfGetLastErrorIsNotZero();
78 |
79 | return cert;
80 | }
81 |
82 | private Win32Native.SystemTime ToSystemTime(DateTime dateTime)
83 | {
84 | long fileTime = dateTime.ToFileTime();
85 | var systemTime = new Win32Native.SystemTime();
86 | if (!Win32Native.FileTimeToSystemTime(ref fileTime, systemTime))
87 | Win32ErrorHelper.ThrowExceptionIfGetLastErrorIsNotZero();
88 | return systemTime;
89 | }
90 |
91 | public KeyExchangeKey GenerateKeyExchangeKey(bool exportable, int keyBitLength)
92 | {
93 | ThrowIfDisposedOrNotOpen();
94 |
95 | uint flags = (exportable ? 1U : 0U) | ((uint)keyBitLength) << 16;
96 |
97 | IntPtr keyHandle;
98 | bool result = Win32Native.CryptGenKey(handle, (int)KeyType.Exchange, flags, out keyHandle);
99 | if (!result)
100 | Win32ErrorHelper.ThrowExceptionIfGetLastErrorIsNotZero();
101 |
102 | return new KeyExchangeKey(this, keyHandle);
103 | }
104 |
105 | private SignatureKey GenerateSignatureKey(bool exportable, int keyBitLength)
106 | {
107 | ThrowIfDisposedOrNotOpen();
108 |
109 | uint flags = (exportable ? 1U : 0U) | ((uint)keyBitLength) << 16;
110 |
111 | IntPtr keyHandle;
112 | bool result = Win32Native.CryptGenKey(handle, (int)KeyType.Signature, flags, out keyHandle);
113 | if (!result)
114 | Win32ErrorHelper.ThrowExceptionIfGetLastErrorIsNotZero();
115 |
116 | return new SignatureKey(this, keyHandle);
117 | }
118 |
119 | internal void DestroyKey(CryptKey key)
120 | {
121 | ThrowIfDisposedOrNotOpen();
122 | if (!Win32Native.CryptDestroyKey(key.Handle))
123 | Win32ErrorHelper.ThrowExceptionIfGetLastErrorIsNotZero();
124 | }
125 |
126 | protected override void CleanUp(bool viaDispose)
127 | {
128 | if (handle != IntPtr.Zero)
129 | {
130 | if (!Win32Native.CryptReleaseContext(handle, 0))
131 | {
132 | // only throw exceptions if we're NOT in a finalizer
133 | if (viaDispose)
134 | Win32ErrorHelper.ThrowExceptionIfGetLastErrorIsNotZero();
135 | }
136 | }
137 | }
138 |
139 | private void ThrowIfDisposedOrNotOpen()
140 | {
141 | ThrowIfDisposed();
142 | ThrowIfNotOpen();
143 | }
144 |
145 | private void ThrowIfNotOpen()
146 | {
147 | if (IntPtr.Zero == handle)
148 | throw new InvalidOperationException("You must call CryptContext.Open first.");
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderTest/MainForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HdmiExtenderReceiver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Net.Sockets;
7 | using System.Text;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using PcapDotNet.Core;
11 | using PcapDotNet.Core.Extensions;
12 | using PcapDotNet.Packets;
13 | using PcapDotNet.Packets.Ethernet;
14 | using PcapDotNet.Packets.IpV4;
15 | using PcapDotNet.Packets.Transport;
16 |
17 | namespace HdmiExtenderLib
18 | {
19 | public class HdmiExtenderReceiver
20 | {
21 | private IPAddress addressSenderDevice;
22 | private int networkAdapterIndex = 1;
23 |
24 | private Thread thrKeepAlive;
25 | private Thread thrDataStream;
26 |
27 | private volatile bool abort = false;
28 |
29 | private byte[] latestImage = null;
30 | private int audioListenerIDCounter = 0;
31 | private ConcurrentDictionary> registeredAudioListeners = new ConcurrentDictionary>();
32 |
33 | ///
34 | /// Gets a the latest image data, encoded as image/jpeg.
35 | ///
36 | public byte[] LatestImage
37 | {
38 | get
39 | {
40 | return latestImage;
41 | }
42 | }
43 |
44 | public HdmiExtenderReceiver(string ipAddressOfSenderDevice, int networkAdapterIndex)
45 | {
46 | this.addressSenderDevice = IPAddress.Parse(ipAddressOfSenderDevice);
47 | this.networkAdapterIndex = networkAdapterIndex;
48 | }
49 |
50 | public void Start()
51 | {
52 | if (thrKeepAlive != null)
53 | Stop();
54 |
55 | thrDataStream = new Thread(doDataStream);
56 | thrDataStream.Name = "Video Streaming Thread";
57 | thrDataStream.Start();
58 |
59 | thrKeepAlive = new Thread(doKeepAlive);
60 | thrKeepAlive.Name = "Stream KeepAlive Thread";
61 | thrKeepAlive.Start();
62 | }
63 |
64 | public void Stop()
65 | {
66 | abort = true;
67 | try
68 | {
69 | thrKeepAlive.Join(300);
70 | thrDataStream.Join(300);
71 |
72 | thrKeepAlive.Abort();
73 | thrDataStream.Abort();
74 |
75 | thrKeepAlive = null;
76 | thrDataStream = null;
77 | }
78 | catch (Exception) { }
79 | abort = false;
80 | }
81 |
82 | public int RegisterAudioListener(ConcurrentQueue audioData)
83 | {
84 | int myKey = Interlocked.Increment(ref audioListenerIDCounter);
85 | registeredAudioListeners.AddOrUpdate(myKey, audioData, (key, existingValue) => { return audioData; });
86 | return myKey;
87 | }
88 |
89 | public void UnregisterAudioListener(int registrationId)
90 | {
91 | ConcurrentQueue tmp;
92 | registeredAudioListeners.TryRemove(registrationId, out tmp);
93 | }
94 |
95 | ///
96 | /// Processes incoming audio and video data. This method should be run on a new thread.
97 | ///
98 | private void doDataStream()
99 | {
100 | try
101 | {
102 | JpegAssembler jpgAssembler = new JpegAssembler();
103 |
104 | PacketDevice selectedDevice = LivePacketDevice.AllLocalMachine[networkAdapterIndex];
105 |
106 | // Open the device
107 | using (PacketCommunicator communicator =
108 | selectedDevice.Open(65536, // portion of the packet to capture
109 | // 65536 guarantees that the whole packet will be captured on all the link layers
110 | PacketDeviceOpenAttributes.Promiscuous, // promiscuous mode
111 | 1000)) // read timeout
112 | {
113 | // Check the link layer. We support only Ethernet for simplicity.
114 | if (communicator.DataLink.Kind != DataLinkKind.Ethernet)
115 | {
116 | Console.WriteLine("This program works only on Ethernet networks.");
117 | return;
118 | }
119 |
120 | // Compile and set the filter
121 | communicator.SetFilter("ip and udp");
122 |
123 | // start the capture
124 | var query = from packet in communicator.ReceivePackets()
125 | // where !packet.IsValid
126 | select packet;
127 |
128 |
129 | foreach (Packet packet in query)
130 | {
131 | if (abort)
132 | return;
133 | if (packet.Ethernet.EtherType == EthernetType.IpV4 &&
134 | packet.Ethernet.IpV4.Protocol == IpV4Protocol.Udp)
135 | {
136 | if (packet.Ethernet.IpV4.Udp.DestinationPort == 2068)
137 | {
138 | // Video Data
139 | // Add lengths of all headers together: ethernet, ipv4, and udp
140 | int headerLength = packet.Ethernet.HeaderLength + packet.Ethernet.IpV4.HeaderLength + 8;
141 | byte[] data = new byte[packet.Length - headerLength];
142 | Array.Copy(packet.Buffer, headerLength, data, 0, data.Length);
143 | byte[] img = jpgAssembler.AddChunkAndGetNextImage(data);
144 | if (img != null)
145 | latestImage = img;
146 | }
147 | else if (packet.Ethernet.IpV4.Udp.DestinationPort == 2066)
148 | {
149 | // Audio Data
150 | // Add lengths of all headers together: ethernet, ipv4, and udp
151 | int headerLength = packet.Ethernet.HeaderLength + packet.Ethernet.IpV4.HeaderLength + 8;
152 | // These Sender devices put 16 bytes of garbage at the beginning of each audio packet.
153 | headerLength += 16;
154 | byte[] data = new byte[packet.Length - headerLength];
155 | Array.Copy(packet.Buffer, headerLength, data, 0, data.Length);
156 | foreach (ConcurrentQueue audioQueue in registeredAudioListeners.Values)
157 | audioQueue.Enqueue(data);
158 | }
159 | }
160 | }
161 | }
162 | }
163 | catch (Exception ex)
164 | {
165 | Console.WriteLine(ex.ToString());
166 | }
167 | }
168 |
169 | ///
170 | /// Processes incoming control packets from a Sender device, and responds with control packets that keep the audio and video streaming active. This method should be run on a new thread.
171 | ///
172 | private void doKeepAlive()
173 | {
174 | try
175 | {
176 | using (UdpClient udp = new UdpClient(new IPEndPoint(IPAddress.Any, 48689)))
177 | {
178 | IPEndPoint ep = null;
179 | while (!abort)
180 | {
181 | byte[] data = udp.Receive(ref ep);
182 | if (ep.Address.Equals(addressSenderDevice))
183 | {
184 | byte[] packetNumberBytes = data.Skip(8).Take(2).ToArray();
185 | UInt16 controlPacketNumber = BitConverter.ToUInt16(packetNumberBytes, 0);
186 |
187 | controlPacketNumber += 3;
188 | string hexPacketNumber = controlPacketNumber.ToString("X").PadLeft(4, '0');
189 | // This number needs to be little-endian.
190 | hexPacketNumber = hexPacketNumber.Substring(2, 2) + hexPacketNumber.Substring(0, 2);
191 | string hexControlPacket = "5446367a60020000" + hexPacketNumber + "000303010026000000000d2fd8";
192 | byte[] controlPacket = HexStringToByteArray(hexControlPacket);
193 | udp.Send(controlPacket, controlPacket.Length, new IPEndPoint(addressSenderDevice, 48689));
194 | }
195 | }
196 | }
197 | }
198 | catch (Exception ex)
199 | {
200 | Console.WriteLine(ex.ToString());
201 | }
202 | }
203 |
204 | #region Util
205 | public static byte[] HexStringToByteArray(string hex)
206 | {
207 | return Enumerable.Range(0, hex.Length)
208 | .Where(x => x % 2 == 0)
209 | .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
210 | .ToArray();
211 | }
212 | #endregion
213 |
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/PcapDotNet/PcapDotNet.Core.Extensions.XML:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PcapDotNet.Core.Extensions
5 |
6 |
7 |
8 |
9 | Different extension methods for PacketCommunicator class.
10 |
11 |
12 |
13 |
14 |
15 | Collect a group of packets.
16 | Similar to ReceivePackets() except instead of calling a callback the packets are returned as an IEnumerable.
17 |
18 |
19 |
20 | The PacketCommunicator to work on
21 | Number of packets to process. A negative count causes ReceivePackets() to loop until the IEnumerable break (or until an error occurs).
22 | An IEnumerable of Packets to process.
23 | Thrown if the mode is not Capture or an error occurred.
24 |
25 | Only the first bytes of data from the packet might be in the received packet (which won't necessarily be the entire packet; to capture the entire packet, you will have to provide a value for snapshortLength in your call to PacketDevice.Open() that is sufficiently large to get all of the packet's data - a value of 65536 should be sufficient on most if not all networks).
26 | If a break is called on the returned Enumerable before the number of packets asked for received, the packet that was handled while breaking the enumerable may not be the last packet read. More packets might be read. This is because this method actually loops over calls to ReceiveSomePackets()
27 |
28 |
29 |
30 |
31 | Collect a group of packets.
32 | Similar to ReceivePackets() except instead of calling a callback the packets are returned as an IEnumerable.
33 | Loops until the IEnumerable break (or until an error occurs).
34 |
35 |
36 |
37 | The PacketCommunicator to work on
38 | An IEnumerable of Packets to process.
39 | Thrown if the mode is not Capture or an error occurred.
40 |
41 | Only the first bytes of data from the packet might be in the received packet (which won't necessarily be the entire packet; to capture the entire packet, you will have to provide a value for snapshortLength in your call to PacketDevice.Open() that is sufficiently large to get all of the packet's data - a value of 65536 should be sufficient on most if not all networks).
42 | If a break is called on the returned Enumerable, the packet that was handled while breaking the enumerable may not be the last packet read. More packets might be read. This is because this method actually loops over calls to ReceiveSomePackets()
43 |
44 |
45 |
46 |
47 | Extension methods for LivePacketDevice class.
48 |
49 |
50 |
51 |
52 | Returns the GUID (NetCfgInstanceId) for a instance.
53 | The GUID is parsed from the property.
54 |
55 | The instance.
56 | The GUID (NetCfgInstanceId) of the instance.
57 | When the doesn't match the expectations.
58 |
59 |
60 |
61 | Returns the PNPDeviceID for a instance.
62 | The PNPDeviceID is retrieved by querying the registry.
63 |
64 | The instance.
65 | The PNPDeviceID of the instance.
66 | When the PNPDeviceID cannot be retrieved from the registry.
67 |
68 |
69 |
70 | Returns the network interface of the packet device.
71 | The interface is found using its id.
72 | If no interface is found, null is returned.
73 |
74 | The LivePacketDevice to look for a matching network interface for.
75 | The network interface found according to the given device or null if none is found.
76 |
77 |
78 |
79 | Returns the of the network interface of the given device.
80 | If no interface matches the given packet device, an exception is thrown.
81 | We first look for the device using and if that fails we look for them using WMI.
82 |
83 | The packet device to look for the matching interface.
84 | The of the given device's matching interface.
85 |
86 |
87 |
88 | Returns the for a instance.
89 | The is retrieved through using WMI.
90 |
91 | The instance.
92 | The of the instance.
93 | When the cannot be retrieved using WMI.
94 |
95 |
96 |
97 | Extension methods for NetworkInterface class.
98 |
99 |
100 |
101 |
102 | Returns the LivePacketDevice of the given NetworkInterface.
103 | The LivePacketDevice is found using the NetworkInterface's id and the LivePacketDevice's name.
104 | If no interface is found, null is returned.
105 |
106 | The NetworkInterface to look for a matching LivePacketDevice for.
107 | The LivePacketDevice found according to the given NetworkInterface or null if none is found.
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/Mime.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace SimpleHttp
7 | {
8 | public static class Mime
9 | {
10 | private static IDictionary _mappings = new Dictionary(StringComparer.InvariantCultureIgnoreCase) {
11 |
12 | #region Big freaking list of mime types
13 | // combination of values from Windows 7 Registry and
14 | // from C:\Windows\System32\inetsrv\config\applicationHost.config
15 | // some added, including .7z and .dat
16 | {".323", "text/h323"},
17 | {".3g2", "video/3gpp2"},
18 | {".3gp", "video/3gpp"},
19 | {".3gp2", "video/3gpp2"},
20 | {".3gpp", "video/3gpp"},
21 | {".7z", "application/x-7z-compressed"},
22 | {".aa", "audio/audible"},
23 | {".AAC", "audio/aac"},
24 | {".aaf", "application/octet-stream"},
25 | {".aax", "audio/vnd.audible.aax"},
26 | {".ac3", "audio/ac3"},
27 | {".aca", "application/octet-stream"},
28 | {".accda", "application/msaccess.addin"},
29 | {".accdb", "application/msaccess"},
30 | {".accdc", "application/msaccess.cab"},
31 | {".accde", "application/msaccess"},
32 | {".accdr", "application/msaccess.runtime"},
33 | {".accdt", "application/msaccess"},
34 | {".accdw", "application/msaccess.webapplication"},
35 | {".accft", "application/msaccess.ftemplate"},
36 | {".acx", "application/internet-property-stream"},
37 | {".AddIn", "text/xml"},
38 | {".ade", "application/msaccess"},
39 | {".adobebridge", "application/x-bridge-url"},
40 | {".adp", "application/msaccess"},
41 | {".ADT", "audio/vnd.dlna.adts"},
42 | {".ADTS", "audio/aac"},
43 | {".afm", "application/octet-stream"},
44 | {".ai", "application/postscript"},
45 | {".aif", "audio/x-aiff"},
46 | {".aifc", "audio/aiff"},
47 | {".aiff", "audio/aiff"},
48 | {".air", "application/vnd.adobe.air-application-installer-package+zip"},
49 | {".amc", "application/x-mpeg"},
50 | {".application", "application/x-ms-application"},
51 | {".art", "image/x-jg"},
52 | {".asa", "application/xml"},
53 | {".asax", "application/xml"},
54 | {".ascx", "application/xml"},
55 | {".asd", "application/octet-stream"},
56 | {".asf", "video/x-ms-asf"},
57 | {".ashx", "application/xml"},
58 | {".asi", "application/octet-stream"},
59 | {".asm", "text/plain"},
60 | {".asmx", "application/xml"},
61 | {".aspx", "application/xml"},
62 | {".asr", "video/x-ms-asf"},
63 | {".asx", "video/x-ms-asf"},
64 | {".atom", "application/atom+xml"},
65 | {".au", "audio/basic"},
66 | {".avi", "video/x-msvideo"},
67 | {".axs", "application/olescript"},
68 | {".bas", "text/plain"},
69 | {".bcpio", "application/x-bcpio"},
70 | {".bin", "application/octet-stream"},
71 | {".bmp", "image/bmp"},
72 | {".c", "text/plain"},
73 | {".cab", "application/octet-stream"},
74 | {".caf", "audio/x-caf"},
75 | {".calx", "application/vnd.ms-office.calx"},
76 | {".cat", "application/vnd.ms-pki.seccat"},
77 | {".cc", "text/plain"},
78 | {".cd", "text/plain"},
79 | {".cdda", "audio/aiff"},
80 | {".cdf", "application/x-cdf"},
81 | {".cer", "application/x-x509-ca-cert"},
82 | {".chm", "application/octet-stream"},
83 | {".class", "application/x-java-applet"},
84 | {".clp", "application/x-msclip"},
85 | {".cmx", "image/x-cmx"},
86 | {".cnf", "text/plain"},
87 | {".cod", "image/cis-cod"},
88 | {".config", "application/xml"},
89 | {".contact", "text/x-ms-contact"},
90 | {".coverage", "application/xml"},
91 | {".cpio", "application/x-cpio"},
92 | {".cpp", "text/plain"},
93 | {".crd", "application/x-mscardfile"},
94 | {".crl", "application/pkix-crl"},
95 | {".crt", "application/x-x509-ca-cert"},
96 | {".cs", "text/plain"},
97 | {".csdproj", "text/plain"},
98 | {".csh", "application/x-csh"},
99 | {".csproj", "text/plain"},
100 | {".css", "text/css"},
101 | {".csv", "text/csv"},
102 | {".cur", "application/octet-stream"},
103 | {".cxx", "text/plain"},
104 | {".dat", "application/octet-stream"},
105 | {".datasource", "application/xml"},
106 | {".dbproj", "text/plain"},
107 | {".dcr", "application/x-director"},
108 | {".def", "text/plain"},
109 | {".deploy", "application/octet-stream"},
110 | {".der", "application/x-x509-ca-cert"},
111 | {".dgml", "application/xml"},
112 | {".dib", "image/bmp"},
113 | {".dif", "video/x-dv"},
114 | {".dir", "application/x-director"},
115 | {".disco", "text/xml"},
116 | {".dll", "application/x-msdownload"},
117 | {".dll.config", "text/xml"},
118 | {".dlm", "text/dlm"},
119 | {".doc", "application/msword"},
120 | {".docm", "application/vnd.ms-word.document.macroEnabled.12"},
121 | {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
122 | {".dot", "application/msword"},
123 | {".dotm", "application/vnd.ms-word.template.macroEnabled.12"},
124 | {".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
125 | {".dsp", "application/octet-stream"},
126 | {".dsw", "text/plain"},
127 | {".dtd", "text/xml"},
128 | {".dtsConfig", "text/xml"},
129 | {".dv", "video/x-dv"},
130 | {".dvi", "application/x-dvi"},
131 | {".dwf", "drawing/x-dwf"},
132 | {".dwp", "application/octet-stream"},
133 | {".dxr", "application/x-director"},
134 | {".eml", "message/rfc822"},
135 | {".emz", "application/octet-stream"},
136 | {".eot", "application/octet-stream"},
137 | {".eps", "application/postscript"},
138 | {".etl", "application/etl"},
139 | {".etx", "text/x-setext"},
140 | {".evy", "application/envoy"},
141 | {".exe", "application/octet-stream"},
142 | {".exe.config", "text/xml"},
143 | {".fdf", "application/vnd.fdf"},
144 | {".fif", "application/fractals"},
145 | {".filters", "Application/xml"},
146 | {".fla", "application/octet-stream"},
147 | {".flr", "x-world/x-vrml"},
148 | {".flv", "video/x-flv"},
149 | {".fsscript", "application/fsharp-script"},
150 | {".fsx", "application/fsharp-script"},
151 | {".generictest", "application/xml"},
152 | {".gif", "image/gif"},
153 | {".group", "text/x-ms-group"},
154 | {".gsm", "audio/x-gsm"},
155 | {".gtar", "application/x-gtar"},
156 | {".gz", "application/x-gzip"},
157 | {".h", "text/plain"},
158 | {".hdf", "application/x-hdf"},
159 | {".hdml", "text/x-hdml"},
160 | {".hhc", "application/x-oleobject"},
161 | {".hhk", "application/octet-stream"},
162 | {".hhp", "application/octet-stream"},
163 | {".hlp", "application/winhlp"},
164 | {".hpp", "text/plain"},
165 | {".hqx", "application/mac-binhex40"},
166 | {".hta", "application/hta"},
167 | {".htc", "text/x-component"},
168 | {".htm", "text/html"},
169 | {".html", "text/html"},
170 | {".htt", "text/webviewhtml"},
171 | {".hxa", "application/xml"},
172 | {".hxc", "application/xml"},
173 | {".hxd", "application/octet-stream"},
174 | {".hxe", "application/xml"},
175 | {".hxf", "application/xml"},
176 | {".hxh", "application/octet-stream"},
177 | {".hxi", "application/octet-stream"},
178 | {".hxk", "application/xml"},
179 | {".hxq", "application/octet-stream"},
180 | {".hxr", "application/octet-stream"},
181 | {".hxs", "application/octet-stream"},
182 | {".hxt", "text/html"},
183 | {".hxv", "application/xml"},
184 | {".hxw", "application/octet-stream"},
185 | {".hxx", "text/plain"},
186 | {".i", "text/plain"},
187 | {".ico", "image/x-icon"},
188 | {".ics", "application/octet-stream"},
189 | {".idl", "text/plain"},
190 | {".ief", "image/ief"},
191 | {".iii", "application/x-iphone"},
192 | {".inc", "text/plain"},
193 | {".inf", "application/octet-stream"},
194 | {".inl", "text/plain"},
195 | {".ins", "application/x-internet-signup"},
196 | {".ipa", "application/x-itunes-ipa"},
197 | {".ipg", "application/x-itunes-ipg"},
198 | {".ipproj", "text/plain"},
199 | {".ipsw", "application/x-itunes-ipsw"},
200 | {".iqy", "text/x-ms-iqy"},
201 | {".isp", "application/x-internet-signup"},
202 | {".ite", "application/x-itunes-ite"},
203 | {".itlp", "application/x-itunes-itlp"},
204 | {".itms", "application/x-itunes-itms"},
205 | {".itpc", "application/x-itunes-itpc"},
206 | {".IVF", "video/x-ivf"},
207 | {".jar", "application/java-archive"},
208 | {".java", "application/octet-stream"},
209 | {".jck", "application/liquidmotion"},
210 | {".jcz", "application/liquidmotion"},
211 | {".jfif", "image/pjpeg"},
212 | {".jnlp", "application/x-java-jnlp-file"},
213 | {".jpb", "application/octet-stream"},
214 | {".jpe", "image/jpeg"},
215 | {".jpeg", "image/jpeg"},
216 | {".jpg", "image/jpeg"},
217 | {".js", "application/x-javascript"},
218 | {".jsx", "text/jscript"},
219 | {".jsxbin", "text/plain"},
220 | {".latex", "application/x-latex"},
221 | {".library-ms", "application/windows-library+xml"},
222 | {".lit", "application/x-ms-reader"},
223 | {".loadtest", "application/xml"},
224 | {".lpk", "application/octet-stream"},
225 | {".lsf", "video/x-la-asf"},
226 | {".lst", "text/plain"},
227 | {".lsx", "video/x-la-asf"},
228 | {".lzh", "application/octet-stream"},
229 | {".m13", "application/x-msmediaview"},
230 | {".m14", "application/x-msmediaview"},
231 | {".m1v", "video/mpeg"},
232 | {".m2t", "video/vnd.dlna.mpeg-tts"},
233 | {".m2ts", "video/vnd.dlna.mpeg-tts"},
234 | {".m2v", "video/mpeg"},
235 | {".m3u", "audio/x-mpegurl"},
236 | {".m3u8", "audio/x-mpegurl"},
237 | {".m4a", "audio/m4a"},
238 | {".m4b", "audio/m4b"},
239 | {".m4p", "audio/m4p"},
240 | {".m4r", "audio/x-m4r"},
241 | {".m4v", "video/x-m4v"},
242 | {".mac", "image/x-macpaint"},
243 | {".mak", "text/plain"},
244 | {".man", "application/x-troff-man"},
245 | {".manifest", "application/x-ms-manifest"},
246 | {".map", "text/plain"},
247 | {".master", "application/xml"},
248 | {".mda", "application/msaccess"},
249 | {".mdb", "application/x-msaccess"},
250 | {".mde", "application/msaccess"},
251 | {".mdp", "application/octet-stream"},
252 | {".me", "application/x-troff-me"},
253 | {".mfp", "application/x-shockwave-flash"},
254 | {".mht", "message/rfc822"},
255 | {".mhtml", "message/rfc822"},
256 | {".mid", "audio/mid"},
257 | {".midi", "audio/mid"},
258 | {".mix", "application/octet-stream"},
259 | {".mk", "text/plain"},
260 | {".mmf", "application/x-smaf"},
261 | {".mno", "text/xml"},
262 | {".mny", "application/x-msmoney"},
263 | {".mod", "video/mpeg"},
264 | {".mov", "video/quicktime"},
265 | {".movie", "video/x-sgi-movie"},
266 | {".mp2", "video/mpeg"},
267 | {".mp2v", "video/mpeg"},
268 | {".mp3", "audio/mpeg"},
269 | {".mp4", "video/mp4"},
270 | {".mp4v", "video/mp4"},
271 | {".mpa", "video/mpeg"},
272 | {".mpe", "video/mpeg"},
273 | {".mpeg", "video/mpeg"},
274 | {".mpf", "application/vnd.ms-mediapackage"},
275 | {".mpg", "video/mpeg"},
276 | {".mpp", "application/vnd.ms-project"},
277 | {".mpv2", "video/mpeg"},
278 | {".mqv", "video/quicktime"},
279 | {".ms", "application/x-troff-ms"},
280 | {".msi", "application/octet-stream"},
281 | {".mso", "application/octet-stream"},
282 | {".mts", "video/vnd.dlna.mpeg-tts"},
283 | {".mtx", "application/xml"},
284 | {".mvb", "application/x-msmediaview"},
285 | {".mvc", "application/x-miva-compiled"},
286 | {".mxp", "application/x-mmxp"},
287 | {".nc", "application/x-netcdf"},
288 | {".nsc", "video/x-ms-asf"},
289 | {".nws", "message/rfc822"},
290 | {".ocx", "application/octet-stream"},
291 | {".oda", "application/oda"},
292 | {".odc", "text/x-ms-odc"},
293 | {".odh", "text/plain"},
294 | {".odl", "text/plain"},
295 | {".odp", "application/vnd.oasis.opendocument.presentation"},
296 | {".ods", "application/oleobject"},
297 | {".odt", "application/vnd.oasis.opendocument.text"},
298 | {".one", "application/onenote"},
299 | {".onea", "application/onenote"},
300 | {".onepkg", "application/onenote"},
301 | {".onetmp", "application/onenote"},
302 | {".onetoc", "application/onenote"},
303 | {".onetoc2", "application/onenote"},
304 | {".orderedtest", "application/xml"},
305 | {".osdx", "application/opensearchdescription+xml"},
306 | {".p10", "application/pkcs10"},
307 | {".p12", "application/x-pkcs12"},
308 | {".p7b", "application/x-pkcs7-certificates"},
309 | {".p7c", "application/pkcs7-mime"},
310 | {".p7m", "application/pkcs7-mime"},
311 | {".p7r", "application/x-pkcs7-certreqresp"},
312 | {".p7s", "application/pkcs7-signature"},
313 | {".pbm", "image/x-portable-bitmap"},
314 | {".pcast", "application/x-podcast"},
315 | {".pct", "image/pict"},
316 | {".pcx", "application/octet-stream"},
317 | {".pcz", "application/octet-stream"},
318 | {".pdf", "application/pdf"},
319 | {".pfb", "application/octet-stream"},
320 | {".pfm", "application/octet-stream"},
321 | {".pfx", "application/x-pkcs12"},
322 | {".pgm", "image/x-portable-graymap"},
323 | {".pic", "image/pict"},
324 | {".pict", "image/pict"},
325 | {".pkgdef", "text/plain"},
326 | {".pkgundef", "text/plain"},
327 | {".pko", "application/vnd.ms-pki.pko"},
328 | {".pls", "audio/scpls"},
329 | {".pma", "application/x-perfmon"},
330 | {".pmc", "application/x-perfmon"},
331 | {".pml", "application/x-perfmon"},
332 | {".pmr", "application/x-perfmon"},
333 | {".pmw", "application/x-perfmon"},
334 | {".png", "image/png"},
335 | {".pnm", "image/x-portable-anymap"},
336 | {".pnt", "image/x-macpaint"},
337 | {".pntg", "image/x-macpaint"},
338 | {".pnz", "image/png"},
339 | {".pot", "application/vnd.ms-powerpoint"},
340 | {".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"},
341 | {".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"},
342 | {".ppa", "application/vnd.ms-powerpoint"},
343 | {".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"},
344 | {".ppm", "image/x-portable-pixmap"},
345 | {".pps", "application/vnd.ms-powerpoint"},
346 | {".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
347 | {".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
348 | {".ppt", "application/vnd.ms-powerpoint"},
349 | {".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
350 | {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
351 | {".prf", "application/pics-rules"},
352 | {".prm", "application/octet-stream"},
353 | {".prx", "application/octet-stream"},
354 | {".ps", "application/postscript"},
355 | {".psc1", "application/PowerShell"},
356 | {".psd", "application/octet-stream"},
357 | {".psess", "application/xml"},
358 | {".psm", "application/octet-stream"},
359 | {".psp", "application/octet-stream"},
360 | {".pub", "application/x-mspublisher"},
361 | {".pwz", "application/vnd.ms-powerpoint"},
362 | {".qht", "text/x-html-insertion"},
363 | {".qhtm", "text/x-html-insertion"},
364 | {".qt", "video/quicktime"},
365 | {".qti", "image/x-quicktime"},
366 | {".qtif", "image/x-quicktime"},
367 | {".qtl", "application/x-quicktimeplayer"},
368 | {".qxd", "application/octet-stream"},
369 | {".ra", "audio/x-pn-realaudio"},
370 | {".ram", "audio/x-pn-realaudio"},
371 | {".rar", "application/octet-stream"},
372 | {".ras", "image/x-cmu-raster"},
373 | {".rat", "application/rat-file"},
374 | {".rc", "text/plain"},
375 | {".rc2", "text/plain"},
376 | {".rct", "text/plain"},
377 | {".rdlc", "application/xml"},
378 | {".resx", "application/xml"},
379 | {".rf", "image/vnd.rn-realflash"},
380 | {".rgb", "image/x-rgb"},
381 | {".rgs", "text/plain"},
382 | {".rm", "application/vnd.rn-realmedia"},
383 | {".rmi", "audio/mid"},
384 | {".rmp", "application/vnd.rn-rn_music_package"},
385 | {".roff", "application/x-troff"},
386 | {".rpm", "audio/x-pn-realaudio-plugin"},
387 | {".rqy", "text/x-ms-rqy"},
388 | {".rtf", "application/rtf"},
389 | {".rtx", "text/richtext"},
390 | {".ruleset", "application/xml"},
391 | {".s", "text/plain"},
392 | {".safariextz", "application/x-safari-safariextz"},
393 | {".scd", "application/x-msschedule"},
394 | {".sct", "text/scriptlet"},
395 | {".sd2", "audio/x-sd2"},
396 | {".sdp", "application/sdp"},
397 | {".sea", "application/octet-stream"},
398 | {".searchConnector-ms", "application/windows-search-connector+xml"},
399 | {".setpay", "application/set-payment-initiation"},
400 | {".setreg", "application/set-registration-initiation"},
401 | {".settings", "application/xml"},
402 | {".sgimb", "application/x-sgimb"},
403 | {".sgml", "text/sgml"},
404 | {".sh", "application/x-sh"},
405 | {".shar", "application/x-shar"},
406 | {".shtml", "text/html"},
407 | {".sit", "application/x-stuffit"},
408 | {".sitemap", "application/xml"},
409 | {".skin", "application/xml"},
410 | {".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"},
411 | {".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"},
412 | {".slk", "application/vnd.ms-excel"},
413 | {".sln", "text/plain"},
414 | {".slupkg-ms", "application/x-ms-license"},
415 | {".smd", "audio/x-smd"},
416 | {".smi", "application/octet-stream"},
417 | {".smx", "audio/x-smd"},
418 | {".smz", "audio/x-smd"},
419 | {".snd", "audio/basic"},
420 | {".snippet", "application/xml"},
421 | {".snp", "application/octet-stream"},
422 | {".sol", "text/plain"},
423 | {".sor", "text/plain"},
424 | {".spc", "application/x-pkcs7-certificates"},
425 | {".spl", "application/futuresplash"},
426 | {".src", "application/x-wais-source"},
427 | {".srf", "text/plain"},
428 | {".SSISDeploymentManifest", "text/xml"},
429 | {".ssm", "application/streamingmedia"},
430 | {".sst", "application/vnd.ms-pki.certstore"},
431 | {".stl", "application/vnd.ms-pki.stl"},
432 | {".sv4cpio", "application/x-sv4cpio"},
433 | {".sv4crc", "application/x-sv4crc"},
434 | {".svc", "application/xml"},
435 | {".swf", "application/x-shockwave-flash"},
436 | {".t", "application/x-troff"},
437 | {".tar", "application/x-tar"},
438 | {".tcl", "application/x-tcl"},
439 | {".testrunconfig", "application/xml"},
440 | {".testsettings", "application/xml"},
441 | {".tex", "application/x-tex"},
442 | {".texi", "application/x-texinfo"},
443 | {".texinfo", "application/x-texinfo"},
444 | {".tgz", "application/x-compressed"},
445 | {".thmx", "application/vnd.ms-officetheme"},
446 | {".thn", "application/octet-stream"},
447 | {".tif", "image/tiff"},
448 | {".tiff", "image/tiff"},
449 | {".tlh", "text/plain"},
450 | {".tli", "text/plain"},
451 | {".toc", "application/octet-stream"},
452 | {".tr", "application/x-troff"},
453 | {".trm", "application/x-msterminal"},
454 | {".trx", "application/xml"},
455 | {".ts", "video/vnd.dlna.mpeg-tts"},
456 | {".tsv", "text/tab-separated-values"},
457 | {".ttf", "application/octet-stream"},
458 | {".tts", "video/vnd.dlna.mpeg-tts"},
459 | {".txt", "text/plain"},
460 | {".u32", "application/octet-stream"},
461 | {".uls", "text/iuls"},
462 | {".user", "text/plain"},
463 | {".ustar", "application/x-ustar"},
464 | {".vb", "text/plain"},
465 | {".vbdproj", "text/plain"},
466 | {".vbk", "video/mpeg"},
467 | {".vbproj", "text/plain"},
468 | {".vbs", "text/vbscript"},
469 | {".vcf", "text/x-vcard"},
470 | {".vcproj", "Application/xml"},
471 | {".vcs", "text/plain"},
472 | {".vcxproj", "Application/xml"},
473 | {".vddproj", "text/plain"},
474 | {".vdp", "text/plain"},
475 | {".vdproj", "text/plain"},
476 | {".vdx", "application/vnd.ms-visio.viewer"},
477 | {".vml", "text/xml"},
478 | {".vscontent", "application/xml"},
479 | {".vsct", "text/xml"},
480 | {".vsd", "application/vnd.visio"},
481 | {".vsi", "application/ms-vsi"},
482 | {".vsix", "application/vsix"},
483 | {".vsixlangpack", "text/xml"},
484 | {".vsixmanifest", "text/xml"},
485 | {".vsmdi", "application/xml"},
486 | {".vspscc", "text/plain"},
487 | {".vss", "application/vnd.visio"},
488 | {".vsscc", "text/plain"},
489 | {".vssettings", "text/xml"},
490 | {".vssscc", "text/plain"},
491 | {".vst", "application/vnd.visio"},
492 | {".vstemplate", "text/xml"},
493 | {".vsto", "application/x-ms-vsto"},
494 | {".vsw", "application/vnd.visio"},
495 | {".vsx", "application/vnd.visio"},
496 | {".vtx", "application/vnd.visio"},
497 | {".wav", "audio/wav"},
498 | {".wave", "audio/wav"},
499 | {".wax", "audio/x-ms-wax"},
500 | {".wbk", "application/msword"},
501 | {".wbmp", "image/vnd.wap.wbmp"},
502 | {".wcm", "application/vnd.ms-works"},
503 | {".wdb", "application/vnd.ms-works"},
504 | {".wdp", "image/vnd.ms-photo"},
505 | {".webarchive", "application/x-safari-webarchive"},
506 | {".webtest", "application/xml"},
507 | {".wiq", "application/xml"},
508 | {".wiz", "application/msword"},
509 | {".wks", "application/vnd.ms-works"},
510 | {".WLMP", "application/wlmoviemaker"},
511 | {".wlpginstall", "application/x-wlpg-detect"},
512 | {".wlpginstall3", "application/x-wlpg3-detect"},
513 | {".wm", "video/x-ms-wm"},
514 | {".wma", "audio/x-ms-wma"},
515 | {".wmd", "application/x-ms-wmd"},
516 | {".wmf", "application/x-msmetafile"},
517 | {".wml", "text/vnd.wap.wml"},
518 | {".wmlc", "application/vnd.wap.wmlc"},
519 | {".wmls", "text/vnd.wap.wmlscript"},
520 | {".wmlsc", "application/vnd.wap.wmlscriptc"},
521 | {".wmp", "video/x-ms-wmp"},
522 | {".wmv", "video/x-ms-wmv"},
523 | {".wmx", "video/x-ms-wmx"},
524 | {".wmz", "application/x-ms-wmz"},
525 | {".wpl", "application/vnd.ms-wpl"},
526 | {".wps", "application/vnd.ms-works"},
527 | {".wri", "application/x-mswrite"},
528 | {".wrl", "x-world/x-vrml"},
529 | {".wrz", "x-world/x-vrml"},
530 | {".wsc", "text/scriptlet"},
531 | {".wsdl", "text/xml"},
532 | {".wvx", "video/x-ms-wvx"},
533 | {".x", "application/directx"},
534 | {".xaf", "x-world/x-vrml"},
535 | {".xaml", "application/xaml+xml"},
536 | {".xap", "application/x-silverlight-app"},
537 | {".xbap", "application/x-ms-xbap"},
538 | {".xbm", "image/x-xbitmap"},
539 | {".xdr", "text/plain"},
540 | {".xht", "application/xhtml+xml"},
541 | {".xhtml", "application/xhtml+xml"},
542 | {".xla", "application/vnd.ms-excel"},
543 | {".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"},
544 | {".xlc", "application/vnd.ms-excel"},
545 | {".xld", "application/vnd.ms-excel"},
546 | {".xlk", "application/vnd.ms-excel"},
547 | {".xll", "application/vnd.ms-excel"},
548 | {".xlm", "application/vnd.ms-excel"},
549 | {".xls", "application/vnd.ms-excel"},
550 | {".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
551 | {".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"},
552 | {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
553 | {".xlt", "application/vnd.ms-excel"},
554 | {".xltm", "application/vnd.ms-excel.template.macroEnabled.12"},
555 | {".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
556 | {".xlw", "application/vnd.ms-excel"},
557 | {".xml", "text/xml"},
558 | {".xmta", "application/xml"},
559 | {".xof", "x-world/x-vrml"},
560 | {".XOML", "text/plain"},
561 | {".xpm", "image/x-xpixmap"},
562 | {".xps", "application/vnd.ms-xpsdocument"},
563 | {".xrm-ms", "text/xml"},
564 | {".xsc", "application/xml"},
565 | {".xsd", "text/xml"},
566 | {".xsf", "text/xml"},
567 | {".xsl", "text/xml"},
568 | {".xslt", "text/xml"},
569 | {".xsn", "application/octet-stream"},
570 | {".xss", "application/xml"},
571 | {".xtp", "application/octet-stream"},
572 | {".xwd", "image/x-xwindowdump"},
573 | {".z", "application/x-compress"},
574 | {".zip", "application/x-zip-compressed"},
575 | #endregion
576 |
577 | };
578 |
579 | public static string GetMimeType(string extension)
580 | {
581 | if (extension == null)
582 | {
583 | throw new ArgumentNullException("extension");
584 | }
585 |
586 | if (!extension.StartsWith("."))
587 | {
588 | extension = "." + extension;
589 | }
590 |
591 | string mime;
592 |
593 | return _mappings.TryGetValue(extension, out mime) ? mime : "application/octet-stream";
594 | }
595 | }
596 | }
597 |
--------------------------------------------------------------------------------
/HdmiExtender/HdmiExtenderLib/HttpServer/SimpleHttpServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.IO;
4 | using System.Net;
5 | using System.Net.Sockets;
6 | using System.Threading;
7 | using System.Collections.Generic;
8 | using System.Web;
9 | using System.Text;
10 | using System.Security.Cryptography.X509Certificates;
11 | using System.Net.NetworkInformation;
12 |
13 | // This file has been modified continuously since Nov 10, 2012 by Brian Pearce.
14 | // Based on http://www.codeproject.com/Articles/137979/Simple-HTTP-Server-in-C
15 |
16 | // offered to the public domain for any use with no restriction
17 | // and also with no warranty of any kind, please enjoy. - David Jeske.
18 |
19 | // simple HTTP explanation
20 | // http://www.jmarshall.com/easy/http/
21 |
22 |
23 | namespace SimpleHttp
24 | {
25 | public class HttpProcessor
26 | {
27 | private const int BUF_SIZE = 4096;
28 | private static int MAX_POST_SIZE = 10 * 1024 * 1024; // 10MB
29 |
30 | #region Fields and Properties
31 | ///
32 | /// The underlying tcpClient which handles the network connection.
33 | ///
34 | public TcpClient tcpClient;
35 |
36 | ///
37 | /// The HttpServer instance that accepted this request.
38 | ///
39 | public HttpServer srv;
40 |
41 | private Stream inputStream;
42 |
43 | ///
44 | /// Be careful to flush each output stream before using a different one!!
45 | ///
46 | /// This stream is for writing text data.
47 | ///
48 | public StreamWriter outputStream;
49 | ///
50 | /// Be careful to flush each output stream before using a different one!!
51 | ///
52 | /// This stream is for writing binary data.
53 | ///
54 | public Stream rawOutputStream;
55 |
56 | ///
57 | /// The cookies sent by the remote client.
58 | ///
59 | public Cookies requestCookies;
60 |
61 | ///
62 | /// The cookies to send to the remote client.
63 | ///
64 | public Cookies responseCookies = new Cookies();
65 |
66 | ///
67 | /// The Http method used. i.e. "POST" or "GET"
68 | ///
69 | public string http_method;
70 | ///
71 | /// The base Uri for this server, containing its host name and port.
72 | ///
73 | public Uri base_uri_this_server;
74 | ///
75 | /// The requested url.
76 | ///
77 | public Uri request_url;
78 | ///
79 | /// The protocol version string sent by the client. e.g. "HTTP/1.1"
80 | ///
81 | public string http_protocol_versionstring;
82 | ///
83 | /// The path to and name of the requested page, not including the first '/'
84 | ///
85 | /// For example, if the URL was "/articles/science/moon.html?date=2011-10-21", requestedPage would be "articles/science/moon.html"
86 | ///
87 | public string requestedPage;
88 | ///
89 | /// A string array containing the directories and the page name.
90 | ///
91 | /// For example, if the URL was "/articles/science/moon.html?date=2011-10-21", pathParts would be { "articles", "science", "moon.html" }
92 | ///
93 | public string[] pathParts;
94 | ///
95 | /// A Dictionary mapping http header names to values. Names are all converted to lower case before being added to this Dictionary.
96 | ///
97 | public Dictionary httpHeaders = new Dictionary();
98 |
99 | ///
100 | /// A SortedList mapping lower-case keys to values of parameters. This list is populated if and only if the request was a POST request with mimetype "application/x-www-form-urlencoded".
101 | ///
102 | public SortedList PostParams = new SortedList();
103 | ///
104 | /// A SortedList mapping keys to values of parameters. No character case conversion is applied in this list. This list is populated if and only if the request was a POST request with mimetype "application/x-www-form-urlencoded".
105 | ///
106 | public SortedList RawPostParams = new SortedList();
107 | ///
108 | /// A SortedList mapping lower-case keys to values of parameters. This list is populated parameters that were appended to the url (the query string). e.g. if the url is "mypage.html?arg1=value1&arg2=value2", then there will be two parameters ("arg1" with value "value1" and "arg2" with value "value2"
109 | ///
110 | public SortedList QueryString = new SortedList();
111 | ///
112 | /// A SortedList mapping keys to values of parameters. No character case conversion is applied in this list. This list is populated parameters that were appended to the url (the query string). e.g. if the url is "mypage.html?arg1=value1&arg2=value2", then there will be two parameters ("arg1" with value "value1" and "arg2" with value "value2"
113 | ///
114 | public SortedList RawQueryString = new SortedList();
115 |
116 | ///
117 | /// A flag that is set when WriteSuccess(), WriteFailure(), or WriteRedirect() is called. If the
118 | ///
119 | private bool responseWritten = false;
120 |
121 | private int isLanConnection = -1;
122 | ///
123 | /// Returns true if the remote client's ipv4 address is in the same class C range as any of the server's ipv4 addresses.
124 | ///
125 | public bool IsLanConnection
126 | {
127 | get
128 | {
129 | if (isLanConnection == -1)
130 | {
131 | byte[] remoteBytes = RemoteIPAddressBytes;
132 | if (remoteBytes == null || remoteBytes.Length != 4)
133 | isLanConnection = 0;
134 | else if (remoteBytes[0] == 127 && remoteBytes[1] == 0 && remoteBytes[2] == 0 && remoteBytes[3] == 1)
135 | isLanConnection = 1;
136 | else
137 | {
138 | // If the first 3 bytes of any local address matches the first 3 bytes of the local address, then the remote address is in the same class C range as this address.
139 | foreach (byte[] localBytes in srv.localIPv4Addresses)
140 | {
141 | bool addressIsMatch = true;
142 | for (int i = 0; i < 3; i++)
143 | if (localBytes[i] != remoteBytes[i])
144 | {
145 | addressIsMatch = false;
146 | break;
147 | }
148 | if (addressIsMatch)
149 | {
150 | isLanConnection = 1;
151 | break;
152 | }
153 | }
154 | if (isLanConnection != 1)
155 | isLanConnection = 0;
156 | }
157 | }
158 | return isLanConnection == 1;
159 | }
160 | }
161 | private byte[] remoteIPAddressBytes = null;
162 | private byte[] RemoteIPAddressBytes
163 | {
164 | get
165 | {
166 | if (remoteIPAddressBytes != null)
167 | return remoteIPAddressBytes;
168 | try
169 | {
170 | if (tcpClient != null)
171 | {
172 | IPAddress remoteAddress;
173 | if (IPAddress.TryParse(RemoteIPAddress, out remoteAddress) && remoteAddress.AddressFamily == AddressFamily.InterNetwork)
174 | remoteIPAddressBytes = remoteAddress.GetAddressBytes();
175 | }
176 | }
177 | catch (Exception ex)
178 | {
179 | SimpleHttpLogger.Log(ex);
180 | }
181 | return remoteIPAddressBytes;
182 | }
183 | }
184 | private string remoteIPAddress = null;
185 | ///
186 | /// Returns the remote client's IP address, or an empty string if the remote IP address is somehow not available.
187 | ///
188 | public string RemoteIPAddress
189 | {
190 | get
191 | {
192 | if (!string.IsNullOrEmpty(remoteIPAddress))
193 | return remoteIPAddress;
194 | try
195 | {
196 | if (tcpClient != null)
197 | remoteIPAddress = tcpClient.Client.RemoteEndPoint.ToString().Split(':')[0];
198 | }
199 | catch (Exception ex)
200 | {
201 | SimpleHttpLogger.Log(ex);
202 | }
203 | return remoteIPAddress;
204 | }
205 | }
206 |
207 | public readonly bool secure_https;
208 | private X509Certificate2 ssl_certificate;
209 | #endregion
210 |
211 | public HttpProcessor(TcpClient s, HttpServer srv, X509Certificate2 ssl_certificate = null)
212 | {
213 | this.ssl_certificate = ssl_certificate;
214 | this.secure_https = ssl_certificate != null;
215 | this.tcpClient = s;
216 | this.base_uri_this_server = new Uri("http" + (this.secure_https ? "s" : "") + "://" + s.Client.LocalEndPoint.ToString(), UriKind.Absolute);
217 | this.srv = srv;
218 | }
219 |
220 | private string streamReadLine(Stream inputStream)
221 | {
222 | int next_char;
223 | StringBuilder data = new StringBuilder();
224 | while (true)
225 | {
226 | next_char = inputStream.ReadByte();
227 | if (next_char == '\n') { break; }
228 | if (next_char == '\r') { continue; }
229 | if (next_char == -1) { break; };
230 | data.Append(Convert.ToChar(next_char));
231 | }
232 | return data.ToString();
233 | }
234 |
235 | ///
236 | /// Processes the request.
237 | ///
238 | internal void process(object objParameter)
239 | {
240 | Stream tcpStream = null;
241 | try
242 | {
243 | tcpStream = tcpClient.GetStream();
244 | if (this.secure_https)
245 | {
246 | try
247 | {
248 | tcpStream = new System.Net.Security.SslStream(tcpStream, false, null, null);
249 | ((System.Net.Security.SslStream)tcpStream).AuthenticateAsServer(ssl_certificate);
250 | }
251 | catch (Exception ex)
252 | {
253 | SimpleHttpLogger.LogVerbose(ex);
254 | return;
255 | }
256 | }
257 | inputStream = new BufferedStream(tcpStream);
258 | rawOutputStream = tcpStream;
259 | outputStream = new StreamWriter(rawOutputStream);
260 | try
261 | {
262 | parseRequest();
263 | readHeaders();
264 | RawQueryString = ParseQueryStringArguments(this.request_url.Query, preserveKeyCharacterCase: true);
265 | QueryString = ParseQueryStringArguments(this.request_url.Query);
266 | requestCookies = Cookies.FromString(GetHeaderValue("Cookie", ""));
267 | try
268 | {
269 | if (http_method.Equals("GET"))
270 | handleGETRequest();
271 | else if (http_method.Equals("POST"))
272 | handlePOSTRequest();
273 | }
274 | catch (Exception e)
275 | {
276 | if (!isOrdinaryDisconnectException(e))
277 | SimpleHttpLogger.Log(e);
278 | writeFailure("500 Internal Server Error");
279 | }
280 | }
281 | catch (Exception e)
282 | {
283 | if (!isOrdinaryDisconnectException(e))
284 | SimpleHttpLogger.LogVerbose(e);
285 | this.writeFailure("400 Bad Request", "The request cannot be fulfilled due to bad syntax.");
286 | }
287 | outputStream.Flush();
288 | rawOutputStream.Flush();
289 | inputStream = null; outputStream = null; rawOutputStream = null;
290 | }
291 | catch (Exception ex)
292 | {
293 | if (!isOrdinaryDisconnectException(ex))
294 | SimpleHttpLogger.LogVerbose(ex);
295 | }
296 | finally
297 | {
298 | try
299 | {
300 | if (tcpClient != null)
301 | tcpClient.Close();
302 | }
303 | catch (Exception ex) { SimpleHttpLogger.LogVerbose(ex); }
304 | try
305 | {
306 | if (tcpStream != null)
307 | tcpStream.Close();
308 | }
309 | catch (Exception ex) { SimpleHttpLogger.LogVerbose(ex); }
310 | }
311 | }
312 |
313 | public bool isOrdinaryDisconnectException(Exception ex)
314 | {
315 | if (ex is IOException)
316 | {
317 | if (ex.InnerException != null && ex.InnerException is SocketException)
318 | {
319 | if (ex.InnerException.Message.Contains("An established connection was aborted by the software in your host machine")
320 | || ex.InnerException.Message.Contains("An existing connection was forcibly closed by the remote host")
321 | || ex.InnerException.Message.Contains("The socket has been shut down") /* Mono/Linux */)
322 | return true; // Connection aborted. This happens often enough that reporting it can be excessive.
323 | }
324 | }
325 | return false;
326 | }
327 | // The following function was the start of an attempt to support basic authentication, but I have since decided against it as basic authentication is very insecure.
328 | //private NetworkCredential ParseAuthorizationCredentials()
329 | //{
330 | // string auth = this.httpHeaders["Authorization"].ToString();
331 | // if (auth != null && auth.StartsWith("Basic "))
332 | // {
333 | // byte[] bytes = System.Convert.FromBase64String(auth.Substring(6));
334 | // string creds = ASCIIEncoding.ASCII.GetString(bytes);
335 |
336 | // }
337 | // return new NetworkCredential();
338 | //}
339 |
340 | ///
341 | /// Parses the first line of the http request to get the request method, url, and protocol version.
342 | ///
343 | private void parseRequest()
344 | {
345 | string request = streamReadLine(inputStream);
346 | string[] tokens = request.Split(' ');
347 | if (tokens.Length != 3)
348 | throw new Exception("invalid http request line: " + request);
349 | http_method = tokens[0].ToUpper();
350 |
351 | if (tokens[1].StartsWith("http://") || tokens[1].StartsWith("https://"))
352 | request_url = new Uri(tokens[1]);
353 | else
354 | request_url = new Uri(base_uri_this_server, tokens[1]);
355 |
356 | http_protocol_versionstring = tokens[2];
357 | }
358 |
359 | ///
360 | /// Parses the http headers
361 | ///
362 | private void readHeaders()
363 | {
364 | String line;
365 | while ((line = streamReadLine(inputStream)) != "")
366 | {
367 | int separator = line.IndexOf(':');
368 | if (separator == -1)
369 | throw new Exception("invalid http header line: " + line);
370 | String name = line.Substring(0, separator);
371 | int pos = separator + 1;
372 | while (pos < line.Length && line[pos] == ' ')
373 | pos++; // strip any spaces
374 |
375 | string value = line.Substring(pos, line.Length - pos);
376 | httpHeaders[name.ToLower()] = value;
377 | }
378 | }
379 |
380 | ///
381 | /// Asks the HttpServer to handle this request as a GET request. If the HttpServer does not write a response code header, this will write a generic failure header.
382 | ///
383 | private void handleGETRequest()
384 | {
385 | try
386 | {
387 | srv.handleGETRequest(this);
388 | }
389 | finally
390 | {
391 | if (!responseWritten)
392 | this.writeFailure();
393 | }
394 | }
395 | ///
396 | /// This post data processing just reads everything into a memory stream.
397 | /// This is fine for smallish things, but for large stuff we should really
398 | /// hand an input stream to the request processor. However, the input stream
399 | /// we hand to the user's code needs to see the "end of the stream" at this
400 | /// content length, because otherwise it won't know where the end is!
401 | ///
402 | /// If the HttpServer does not write a response code header, this will write a generic failure header.
403 | ///
404 | private void handlePOSTRequest()
405 | {
406 | try
407 | {
408 | int content_len = 0;
409 | MemoryStream ms = new MemoryStream();
410 | string content_length_str = GetHeaderValue("Content-Length");
411 | if (!string.IsNullOrWhiteSpace(content_length_str))
412 | {
413 | if (int.TryParse(content_length_str, out content_len))
414 | {
415 | if (content_len > MAX_POST_SIZE)
416 | {
417 | this.writeFailure("413 Request Entity Too Large", "Request Too Large");
418 | SimpleHttpLogger.LogVerbose("POST Content-Length(" + content_len + ") too big for this simple server. Server can handle up to " + MAX_POST_SIZE);
419 | return;
420 | }
421 | byte[] buf = new byte[BUF_SIZE];
422 | int to_read = content_len;
423 | while (to_read > 0)
424 | {
425 | int numread = this.inputStream.Read(buf, 0, Math.Min(BUF_SIZE, to_read));
426 | if (numread == 0)
427 | {
428 | if (to_read == 0)
429 | break;
430 | else
431 | {
432 | SimpleHttpLogger.LogVerbose("client disconnected during post");
433 | return;
434 | }
435 | }
436 | to_read -= numread;
437 | ms.Write(buf, 0, numread);
438 | }
439 | ms.Seek(0, SeekOrigin.Begin);
440 | }
441 | }
442 | else
443 | {
444 | this.writeFailure("411 Length Required", "The request did not specify the length of its content.");
445 | SimpleHttpLogger.LogVerbose("The request did not specify the length of its content. This server requires that all POST requests include a Content-Length header.");
446 | return;
447 | }
448 |
449 | string contentType = GetHeaderValue("Content-Type");
450 | if (contentType != null && contentType.Contains("application/x-www-form-urlencoded"))
451 | {
452 | StreamReader sr = new StreamReader(ms);
453 | string all = sr.ReadToEnd();
454 | sr.Close();
455 |
456 | RawPostParams = ParseQueryStringArguments(all, false, true);
457 | PostParams = ParseQueryStringArguments(all, false);
458 |
459 | srv.handlePOSTRequest(this, null);
460 | }
461 | else
462 | {
463 | srv.handlePOSTRequest(this, new StreamReader(ms));
464 | }
465 | }
466 | finally
467 | {
468 | try
469 | {
470 | if (!responseWritten)
471 | this.writeFailure();
472 | }
473 | catch (Exception) { }
474 | }
475 | }
476 |
477 | ///
478 | /// Writes the response headers for a successful response. Call this one time before writing your response, after you have determined that the request is valid.
479 | ///
480 | /// The MIME type of your response.
481 | /// (OPTIONAL) The length of your response, in bytes, if you know it.
482 | public void writeSuccess(string contentType = "text/html", long contentLength = -1, string responseCode = "200 OK", List> additionalHeaders = null)
483 | {
484 | responseWritten = true;
485 | outputStream.WriteLine("HTTP/1.1 " + responseCode);
486 | if (!string.IsNullOrEmpty(contentType))
487 | outputStream.WriteLine("Content-Type: " + contentType);
488 | if (contentLength > -1)
489 | outputStream.WriteLine("Content-Length: " + contentLength);
490 | string cookieStr = responseCookies.ToString();
491 | if (!string.IsNullOrEmpty(cookieStr))
492 | outputStream.WriteLine(cookieStr);
493 | if (additionalHeaders != null)
494 | foreach (KeyValuePair header in additionalHeaders)
495 | outputStream.WriteLine(header.Key + ": " + header.Value);
496 | outputStream.WriteLine("Connection: close");
497 | outputStream.WriteLine("");
498 | }
499 |
500 | ///
501 | /// Writes a failure response header. Call this one time to return an error response.
502 | ///
503 | /// (OPTIONAL) The http error code (including explanation entity). For example: "404 Not Found" where 404 is the error code and "Not Found" is the explanation.
504 | /// (OPTIONAL) A description string to send after the headers as the response. This is typically shown to the remote user in his browser. If null, the code string is sent here. If "", no response body is sent by this function, and you may or may not want to write your own.
505 | public void writeFailure(string code = "404 Not Found", string description = null)
506 | {
507 | responseWritten = true;
508 | outputStream.WriteLine("HTTP/1.1 " + code);
509 | outputStream.WriteLine("Connection: close");
510 | outputStream.WriteLine("");
511 | if (description == null)
512 | outputStream.WriteLine(code);
513 | else if (description != "")
514 | outputStream.WriteLine(description);
515 | }
516 |
517 | ///
518 | /// Writes a redirect header instructing the remote user's browser to load the URL you specify. Call this one time and do not write any other data to the response stream.
519 | ///
520 | /// URL to redirect to.
521 | public void writeRedirect(string redirectToUrl)
522 | {
523 | responseWritten = true;
524 | outputStream.WriteLine("HTTP/1.1 302 Found");
525 | outputStream.WriteLine("Location: " + redirectToUrl);
526 | outputStream.WriteLine("Connection: close");
527 | outputStream.WriteLine("");
528 | }
529 |
530 | ///
531 | /// Gets the value of the header, or null if the header does not exist. The name is case insensitive.
532 | ///
533 | /// The case insensitive name of the header to get the value of.
534 | /// The value of the header, or null if the header did not exist.
535 | public string GetHeaderValue(string name, string defaultValue = null)
536 | {
537 | name = name.ToLower();
538 | string value;
539 | if (!httpHeaders.TryGetValue(name, out value))
540 | value = defaultValue;
541 | return value;
542 | }
543 |
544 | #region Parameter parsing
545 | ///
546 | /// Parses the specified query string and returns a sorted list containing the arguments found in the specified query string. Can also be used to parse the POST request body if the mimetype is "application/x-www-form-urlencoded".
547 | ///
548 | ///
549 | ///
550 | ///
551 | private static SortedList ParseQueryStringArguments(string queryString, bool requireQuestionMark = true, bool preserveKeyCharacterCase = false)
552 | {
553 | SortedList arguments = new SortedList();
554 | int idx = queryString.IndexOf('?');
555 | if (idx > -1)
556 | queryString = queryString.Substring(idx + 1);
557 | else if (requireQuestionMark)
558 | return arguments;
559 | idx = queryString.LastIndexOf('#');
560 | string hash = null;
561 | if (idx > -1)
562 | {
563 | hash = queryString.Substring(idx + 1);
564 | queryString = queryString.Remove(idx);
565 | }
566 | string[] parts = queryString.Split(new char[] { '&' });
567 | for (int i = 0; i < parts.Length; i++)
568 | {
569 | string[] argument = parts[i].Split(new char[] { '=' });
570 | if (argument.Length == 2)
571 | {
572 | string key = Uri.UnescapeDataString(argument[0]);
573 | if (!preserveKeyCharacterCase)
574 | key = key.ToLower();
575 | string existingValue;
576 | if (arguments.TryGetValue(key, out existingValue))
577 | arguments[key] += "," + Uri.UnescapeDataString(argument[1]);
578 | else
579 | arguments[key] = Uri.UnescapeDataString(argument[1]);
580 | }
581 | }
582 | if (hash != null)
583 | arguments["#"] = hash;
584 | return arguments;
585 | }
586 |
587 | ///
588 | /// Returns the value of the Query String parameter with the specified key.
589 | ///
590 | /// A case insensitive key.
591 | /// The value of the key, or empty string if the key does not exist or has no value.
592 | public string GetParam(string key)
593 | {
594 | return GetQSParam(key);
595 | }
596 | ///
597 | /// Returns the value of the Query String parameter with the specified key.
598 | ///
599 | /// A case insensitive key.
600 | /// The value of the key, or [defaultValue] if the key does not exist or has no suitable value.
601 | public int GetIntParam(string key, int defaultValue = 0)
602 | {
603 | return GetQSIntParam(key, defaultValue);
604 | }
605 | ///
606 | /// Returns the value of the Query String parameter with the specified key.
607 | ///
608 | /// A case insensitive key.
609 | /// The value of the key, or [defaultValue] if the key does not exist or has no suitable value.
610 | public double GetDoubleParam(string key, int defaultValue = 0)
611 | {
612 | return GetQSDoubleParam(key, defaultValue);
613 | }
614 | ///
615 | /// Returns the value of the Query String parameter with the specified key.
616 | ///
617 | /// A case insensitive key.
618 | /// The value of the key, or [defaultValue] if the key does not exist or has no suitable value. This function interprets a value of "1" or "true" (case insensitive) as being true. Any other parameter value is interpreted as false.
619 | public bool GetBoolParam(string key)
620 | {
621 | return GetQSBoolParam(key);
622 | }
623 | ///
624 | /// Returns the value of the Query String parameter with the specified key.
625 | ///
626 | /// A case insensitive key.
627 | /// The value of the key, or empty string if the key does not exist or has no value.
628 | public string GetQSParam(string key)
629 | {
630 | if (key == null)
631 | return "";
632 | string value;
633 | if (QueryString.TryGetValue(key.ToLower(), out value))
634 | return value;
635 | return "";
636 | }
637 | ///
638 | /// Returns the value of the Query String parameter with the specified key.
639 | ///
640 | /// A case insensitive key.
641 | /// The value of the key, or [defaultValue] if the key does not exist or has no suitable value.
642 | public int GetQSIntParam(string key, int defaultValue = 0)
643 | {
644 | if (key == null)
645 | return defaultValue;
646 | int value;
647 | if (int.TryParse(GetQSParam(key.ToLower()), out value))
648 | return value;
649 | return defaultValue;
650 | }
651 | ///
652 | /// Returns the value of the Query String parameter with the specified key.
653 | ///
654 | /// A case insensitive key.
655 | /// The value of the key, or [defaultValue] if the key does not exist or has no suitable value.
656 | public double GetQSDoubleParam(string key, double defaultValue = 0)
657 | {
658 | if (key == null)
659 | return defaultValue;
660 | double value;
661 | if (double.TryParse(GetQSParam(key.ToLower()), out value))
662 | return value;
663 | return defaultValue;
664 | }
665 | ///
666 | /// Returns the value of the Query String parameter with the specified key.
667 | ///
668 | /// A case insensitive key.
669 | /// The value of the key, or [defaultValue] if the key does not exist or has no suitable value. This function interprets a value of "1" or "true" (case insensitive) as being true. Any other parameter value is interpreted as false.
670 | public bool GetQSBoolParam(string key)
671 | {
672 | string param = GetQSParam(key);
673 | if (param == "1" || param.ToLower() == "true")
674 | return true;
675 | return false;
676 | }
677 | ///
678 | /// Returns the value of a parameter sent via POST with MIME type "application/x-www-form-urlencoded".
679 | ///
680 | /// A case insensitive key.
681 | /// The value of the key, or empty string if the key does not exist or has no value.
682 | public string GetPostParam(string key)
683 | {
684 | if (key == null)
685 | return "";
686 | string value;
687 | if (PostParams.TryGetValue(key.ToLower(), out value))
688 | return value;
689 | return "";
690 | }
691 | ///
692 | /// Returns the value of a parameter sent via POST with MIME type "application/x-www-form-urlencoded".
693 | ///
694 | /// A case insensitive key.
695 | /// The value of the key, or [defaultValue] if the key does not exist or has no suitable value.
696 | public int GetPostIntParam(string key, int defaultValue = 0)
697 | {
698 | if (key == null)
699 | return defaultValue;
700 | int value;
701 | if (int.TryParse(GetPostParam(key.ToLower()), out value))
702 | return value;
703 | return defaultValue;
704 | }
705 | ///
706 | /// Returns the value of a parameter sent via POST with MIME type "application/x-www-form-urlencoded".
707 | ///
708 | /// A case insensitive key.
709 | /// The value of the key, or [defaultValue] if the key does not exist or has no suitable value.
710 | public double GetPostDoubleParam(string key, double defaultValue = 0)
711 | {
712 | if (key == null)
713 | return defaultValue;
714 | double value;
715 | if (double.TryParse(GetPostParam(key.ToLower()), out value))
716 | return value;
717 | return defaultValue;
718 | }
719 | ///
720 | /// Returns the value of a parameter sent via POST with MIME type "application/x-www-form-urlencoded".
721 | ///
722 | /// A case insensitive key.
723 | /// The value of the key, or [defaultValue] if the key does not exist or has no suitable value. This function interprets a value of "1" or "true" (case insensitive) as being true. Any other parameter value is interpreted as false.
724 | public bool GetPostBoolParam(string key)
725 | {
726 | string param = GetPostParam(key);
727 | if (param == "1" || param.ToLower() == "true")
728 | return true;
729 | return false;
730 | }
731 | #endregion
732 | }
733 |
734 | public abstract class HttpServer
735 | {
736 | ///
737 | /// If > -1, the server is listening for http connections on this port.
738 | ///
739 | protected readonly int port;
740 | ///
741 | /// If > -1, the server is listening for https connections on this port.
742 | ///
743 | protected readonly int secure_port;
744 | protected volatile bool stopRequested = false;
745 | private X509Certificate2 ssl_certificate;
746 | private Thread thrHttp;
747 | private Thread thrHttps;
748 | private TcpListener unsecureListener = null;
749 | private TcpListener secureListener = null;
750 |
751 | internal List localIPv4Addresses = new List();
752 |
753 | ///
754 | ///
755 | ///
756 | /// The port number on which to accept regular http connections. If -1, the server will not listen for http connections.
757 | /// (Optional) The port number on which to accept https connections. If -1, the server will not listen for https connections.
758 | /// (Optional) Certificate to use for https connections. If null and an httpsPort was specified, a certificate is automatically created if necessary and loaded from "SimpleHttpServer-SslCert.pfx" in the same directory that the current executable is located in.
759 | public HttpServer(int port, int httpsPort = -1, X509Certificate2 cert = null)
760 | {
761 | this.port = port;
762 | this.secure_port = httpsPort;
763 | this.ssl_certificate = cert;
764 |
765 | if (this.port > 65535 || this.port < -1) this.port = -1;
766 | if (this.secure_port > 65535 || this.secure_port < -1) this.secure_port = -1;
767 |
768 | if (this.port > -1)
769 | {
770 | thrHttp = new Thread(listen);
771 | thrHttp.Name = "HttpServer Thread";
772 | }
773 |
774 | if (this.secure_port > -1)
775 | {
776 | if (ssl_certificate == null)
777 | ssl_certificate = HttpServer.GetSelfSignedCertificate();
778 | thrHttps = new Thread(listen);
779 | thrHttps.Name = "HttpsServer Thread";
780 | }
781 |
782 | foreach (IPAddress addr in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
783 | {
784 | if (addr.AddressFamily == AddressFamily.InterNetwork)
785 | {
786 | byte[] bytes = addr.GetAddressBytes();
787 | if (bytes != null && bytes.Length == 4)
788 | localIPv4Addresses.Add(bytes);
789 | }
790 | }
791 | }
792 |
793 | private static object certCreateLock = new object();
794 | public static X509Certificate2 GetSelfSignedCertificate()
795 | {
796 | lock (certCreateLock)
797 | {
798 | X509Certificate2 ssl_certificate;
799 | string exePath = System.Reflection.Assembly.GetExecutingAssembly().Location;
800 | FileInfo fiExe = new FileInfo(exePath);
801 | FileInfo fiCert = new FileInfo(fiExe.Directory.FullName + "\\SimpleHttpServer-SslCert.pfx");
802 | if (fiCert.Exists)
803 | ssl_certificate = new X509Certificate2(fiCert.FullName, "N0t_V3ry-S3cure#lol");
804 | else
805 | {
806 | using (Pluralsight.Crypto.CryptContext ctx = new Pluralsight.Crypto.CryptContext())
807 | {
808 | ctx.Open();
809 |
810 | ssl_certificate = ctx.CreateSelfSignedCertificate(
811 | new Pluralsight.Crypto.SelfSignedCertProperties
812 | {
813 | IsPrivateKeyExportable = true,
814 | KeyBitLength = 4096,
815 | Name = new X500DistinguishedName("cn=localhost"),
816 | ValidFrom = DateTime.Today.AddDays(-1),
817 | ValidTo = DateTime.Today.AddYears(100),
818 | });
819 |
820 | byte[] certData = ssl_certificate.Export(X509ContentType.Pfx, "N0t_V3ry-S3cure#lol");
821 | File.WriteAllBytes(fiCert.FullName, certData);
822 | }
823 | }
824 | return ssl_certificate;
825 | }
826 | }
827 |
828 | ///
829 | /// Listens for connections, somewhat robustly. Does not return until the server is stopped or until more than 100 listener restarts occur in a single day.
830 | ///
831 | private void listen(object param)
832 | {
833 | bool isSecureListener = (bool)param;
834 |
835 | int errorCount = 0;
836 | DateTime lastError = DateTime.Now;
837 |
838 | TcpListener listener = null;
839 |
840 | while (!stopRequested)
841 | {
842 | bool threwExceptionOuter = false;
843 | try
844 | {
845 | listener = new TcpListener(IPAddress.Any, isSecureListener ? secure_port : port);
846 | if (isSecureListener)
847 | secureListener = listener;
848 | else
849 | unsecureListener = listener;
850 | listener.Start();
851 | while (!stopRequested)
852 | {
853 | int innerErrorCount = 0;
854 | DateTime innerLastError = DateTime.Now;
855 | try
856 | {
857 | TcpClient s = listener.AcceptTcpClient();
858 | int workerThreads, completionPortThreads;
859 | ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
860 | // Here is where we could enforce a minimum number of free pool threads,
861 | // if we wanted to ensure better performance.
862 | if (workerThreads > 0)
863 | {
864 | HttpProcessor processor = new HttpProcessor(s, this, isSecureListener ? ssl_certificate : null);
865 | ThreadPool.QueueUserWorkItem(processor.process);
866 | }
867 | else
868 | {
869 | try
870 | {
871 | StreamWriter outputStream = new StreamWriter(s.GetStream());
872 | outputStream.WriteLine("HTTP/1.1 503 Service Unavailable");
873 | outputStream.WriteLine("Connection: close");
874 | outputStream.WriteLine("");
875 | outputStream.WriteLine("Server too busy");
876 | }
877 | catch (ThreadAbortException ex)
878 | {
879 | throw ex;
880 | }
881 | }
882 | }
883 | catch (ThreadAbortException ex)
884 | {
885 | throw ex;
886 | }
887 | catch (Exception ex)
888 | {
889 | if (DateTime.Now.Hour != innerLastError.Hour || DateTime.Now.DayOfYear != innerLastError.DayOfYear)
890 | {
891 | innerLastError = DateTime.Now;
892 | innerErrorCount = 0;
893 | }
894 | if (++innerErrorCount > 10)
895 | throw ex;
896 | SimpleHttpLogger.Log(ex, "Inner Error count this hour: " + innerErrorCount);
897 | Thread.Sleep(1);
898 | }
899 | }
900 | }
901 | catch (ThreadAbortException) { stopRequested = true; }
902 | catch (Exception ex)
903 | {
904 | if (DateTime.Now.DayOfYear != lastError.DayOfYear || DateTime.Now.Year != lastError.Year)
905 | {
906 | lastError = DateTime.Now;
907 | errorCount = 0;
908 | }
909 | if (++errorCount > 100)
910 | throw ex;
911 | SimpleHttpLogger.Log(ex, "Restarting listener. Error count today: " + errorCount);
912 | threwExceptionOuter = true;
913 | }
914 | finally
915 | {
916 | try
917 | {
918 | if (listener != null)
919 | {
920 | listener.Stop();
921 | if (threwExceptionOuter)
922 | Thread.Sleep(1000);
923 | }
924 | }
925 | catch (ThreadAbortException) { stopRequested = true; }
926 | catch (Exception) { }
927 | }
928 | }
929 | }
930 |
931 | ///
932 | /// Starts listening for connections.
933 | ///
934 | public void Start()
935 | {
936 | if (thrHttp != null)
937 | thrHttp.Start(false);
938 | if (thrHttps != null)
939 | thrHttps.Start(true);
940 | }
941 |
942 | ///
943 | /// Stops listening for connections.
944 | ///
945 | public void Stop()
946 | {
947 | if (stopRequested)
948 | return;
949 | stopRequested = true;
950 | if (unsecureListener != null)
951 | try
952 | {
953 | unsecureListener.Stop();
954 | }
955 | catch (Exception ex)
956 | {
957 | SimpleHttpLogger.Log(ex);
958 | }
959 | if (secureListener != null)
960 | try
961 | {
962 | secureListener.Stop();
963 | }
964 | catch (Exception ex)
965 | {
966 | SimpleHttpLogger.Log(ex);
967 | }
968 | if (thrHttp != null)
969 | try
970 | {
971 | thrHttp.Abort();
972 | }
973 | catch (Exception ex)
974 | {
975 | SimpleHttpLogger.Log(ex);
976 | }
977 | if (thrHttps != null)
978 | try
979 | {
980 | thrHttps.Abort();
981 | }
982 | catch (Exception ex)
983 | {
984 | SimpleHttpLogger.Log(ex);
985 | }
986 | try
987 | {
988 | stopServer();
989 | }
990 | catch (Exception ex)
991 | {
992 | SimpleHttpLogger.Log(ex);
993 | }
994 | }
995 |
996 | ///
997 | /// Blocks the calling thread until the http listening threads finish or the timeout expires. Call this after calling Stop() if you need to wait for the listener to clean up, such as if you intend to start another instance of the server using the same port(s).
998 | ///
999 | /// Maximum number of milliseconds to wait for the HttpServer Threads to stop.
1000 | public void Join(int timeout_milliseconds = 2000)
1001 | {
1002 | System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
1003 | int timeToWait = timeout_milliseconds;
1004 | stopwatch.Start();
1005 | if (timeToWait > 0)
1006 | {
1007 | try
1008 | {
1009 | if (thrHttp != null && thrHttp.IsAlive)
1010 | thrHttp.Join(timeToWait);
1011 | }
1012 | catch (Exception ex)
1013 | {
1014 | SimpleHttpLogger.Log(ex);
1015 | }
1016 | }
1017 | stopwatch.Stop();
1018 | timeToWait = timeout_milliseconds - (int)stopwatch.ElapsedMilliseconds;
1019 | if (timeToWait > 0)
1020 | {
1021 | try
1022 | {
1023 | if (thrHttps != null && thrHttps.IsAlive)
1024 | thrHttps.Join(timeToWait);
1025 | }
1026 | catch (Exception ex)
1027 | {
1028 | SimpleHttpLogger.Log(ex);
1029 | }
1030 | }
1031 | }
1032 |
1033 | ///
1034 | /// Handles an Http GET request.
1035 | ///
1036 | /// The HttpProcessor handling the request.
1037 | public abstract void handleGETRequest(HttpProcessor p);
1038 | ///
1039 | /// Handles an Http POST request.
1040 | ///
1041 | /// The HttpProcessor handling the request.
1042 | /// The input stream. If the request's MIME type was "application/x-www-form-urlencoded", the StreamReader will be null and you can obtain the parameter values using p.PostParams, p.GetPostParam(), p.GetPostIntParam(), etc.
1043 | public abstract void handlePOSTRequest(HttpProcessor p, StreamReader inputData);
1044 | ///
1045 | /// This is called when the server is stopping. Perform any cleanup work here.
1046 | ///
1047 | public abstract void stopServer();
1048 | }
1049 | #region Helper Classes
1050 | public class Cookie
1051 | {
1052 | public string name;
1053 | public string value;
1054 | public TimeSpan expire;
1055 |
1056 | public Cookie(string name, string value, TimeSpan expire)
1057 | {
1058 | this.name = name;
1059 | this.value = value;
1060 | this.expire = expire;
1061 | }
1062 | }
1063 | public class Cookies
1064 | {
1065 | SortedList cookieCollection = new SortedList();
1066 | ///
1067 | /// Adds a cookie with the specified name and value. The cookie is set to expire immediately at the end of the browsing session.
1068 | ///
1069 | /// The cookie's name.
1070 | /// The cookie's value.
1071 | public void Add(string name, string value)
1072 | {
1073 | Add(name, value, TimeSpan.Zero);
1074 | }
1075 | ///
1076 | /// Adds a cookie with the specified name, value, and lifespan.
1077 | ///
1078 | /// The cookie's name.
1079 | /// The cookie's value.
1080 | /// The amount of time before the cookie should expire.
1081 | public void Add(string name, string value, TimeSpan expireTime)
1082 | {
1083 | if (name == null)
1084 | return;
1085 | name = name.ToLower();
1086 | cookieCollection[name] = new Cookie(name, value, expireTime);
1087 | }
1088 | ///
1089 | /// Gets the cookie with the specified name. If the cookie is not found, null is returned;
1090 | ///
1091 | /// The name of the cookie.
1092 | ///
1093 | public Cookie Get(string name)
1094 | {
1095 | Cookie cookie;
1096 | if (!cookieCollection.TryGetValue(name, out cookie))
1097 | cookie = null;
1098 | return cookie;
1099 | }
1100 | ///
1101 | /// Gets the value of the cookie with the specified name. If the cookie is not found, an empty string is returned;
1102 | ///
1103 | /// The name of the cookie.
1104 | ///
1105 | public string GetValue(string name)
1106 | {
1107 | Cookie cookie = Get(name);
1108 | if (cookie == null)
1109 | return "";
1110 | return cookie.value;
1111 | }
1112 | ///
1113 | /// Returns a string of "Set-Cookie: ..." headers (one for each cookie in the collection) separated by "\r\n". There is no leading or trailing "\r\n".
1114 | ///
1115 | /// A string of "Set-Cookie: ..." headers (one for each cookie in the collection) separated by "\r\n". There is no leading or trailing "\r\n".
1116 | public override string ToString()
1117 | {
1118 | List cookiesStr = new List();
1119 | foreach (Cookie cookie in cookieCollection.Values)
1120 | cookiesStr.Add("Set-Cookie: " + cookie.name + "=" + cookie.value + (cookie.expire == TimeSpan.Zero ? "" : "; Max-Age=" + (long)cookie.expire.TotalSeconds) + "; Path=/");
1121 | return string.Join("\r\n", cookiesStr);
1122 | }
1123 | ///
1124 | /// Returns a Cookies instance populated by parsing the specified string. The string should be the value of the "Cookie" header that was received from the remote client. If the string is null or empty, an empty cookies collection is returned.
1125 | ///
1126 | /// The value of the "Cookie" header sent by the remote client.
1127 | ///
1128 | public static Cookies FromString(string str)
1129 | {
1130 | Cookies cookies = new Cookies();
1131 | if (str == null)
1132 | return cookies;
1133 | str = Uri.UnescapeDataString(str);
1134 | string[] parts = str.Split(';');
1135 | for (int i = 0; i < parts.Length; i++)
1136 | {
1137 | int idxEquals = parts[i].IndexOf('=');
1138 | if (idxEquals < 1)
1139 | continue;
1140 | string name = parts[i].Substring(0, idxEquals).Trim();
1141 | string value = parts[i].Substring(idxEquals + 1).Trim();
1142 | cookies.Add(name, value);
1143 | }
1144 | return cookies;
1145 | }
1146 | }
1147 | public static class Extensions
1148 | {
1149 | ///
1150 | /// Returns the date and time formatted for insertion as the expiration date in a "Set-Cookie" header.
1151 | ///
1152 | ///
1153 | ///
1154 | public static string ToCookieTime(this DateTime time)
1155 | {
1156 | return time.ToString("dd MMM yyyy hh:mm:ss GMT");
1157 | }
1158 | }
1159 | ///
1160 | /// A class which handles error logging by the http server. It allows you to (optionally) register an ILogger instance to use for logging.
1161 | ///
1162 | public static class SimpleHttpLogger
1163 | {
1164 | private static ILogger logger = null;
1165 | private static bool logVerbose = false;
1166 | ///
1167 | /// (OPTIONAL) Keeps a static reference to the specified ILogger and uses it for http server error logging. Only one logger can be registered at a time; attempting to register a second logger simply replaces the first one.
1168 | ///
1169 | /// The logger that should be used when an error message needs logged. If null, logging will be disabled.
1170 | /// If true, additional error reporting will be enabled. These errors include things that can occur frequently during normal operation, so it may be spammy.
1171 | public static void RegisterLogger(ILogger loggerToRegister, bool logVerboseMessages = false)
1172 | {
1173 | logger = loggerToRegister;
1174 | logVerbose = logVerboseMessages;
1175 | }
1176 | ///
1177 | /// Unregisters the currently registered logger (if any) by calling RegisterLogger(null);
1178 | ///
1179 | public static void UnregisterLogger()
1180 | {
1181 | RegisterLogger(null);
1182 | }
1183 | internal static void Log(Exception ex, string additionalInformation = "")
1184 | {
1185 | try
1186 | {
1187 | if (logger != null)
1188 | logger.Log(ex, additionalInformation);
1189 | }
1190 | catch (Exception) { }
1191 | }
1192 | internal static void Log(string str)
1193 | {
1194 | try
1195 | {
1196 | if (logger != null)
1197 | {
1198 | logger.Log(str);
1199 | }
1200 | }
1201 | catch (Exception) { }
1202 | }
1203 |
1204 | internal static void LogVerbose(Exception ex, string additionalInformation = "")
1205 | {
1206 | if (logVerbose)
1207 | Log(ex, additionalInformation);
1208 | }
1209 |
1210 | internal static void LogVerbose(string str)
1211 | {
1212 | if (logVerbose)
1213 | Log(str);
1214 | }
1215 | }
1216 | ///
1217 | /// An interface which handles logging of exceptions and strings.
1218 | ///
1219 | public interface ILogger
1220 | {
1221 | ///
1222 | /// Log an exception, possibly with additional information provided to assist with debugging.
1223 | ///
1224 | /// An exception that was caught.
1225 | /// Additional information about the exception.
1226 | void Log(Exception ex, string additionalInformation = "");
1227 | ///
1228 | /// Log a string.
1229 | ///
1230 | /// A string to log.
1231 | void Log(string str);
1232 | }
1233 | #endregion
1234 | }
--------------------------------------------------------------------------------