This commit is contained in:
Johnny Z 2018-04-12 02:31:39 +10:00 коммит произвёл Max Gortman
Родитель 997e60f19b
Коммит f227f95966
262 изменённых файлов: 42432 добавлений и 240 удалений

3
.gitattributes поставляемый
Просмотреть файл

@ -42,9 +42,10 @@
*.fs text=auto
*.fsx text=auto
*.hs text=auto
*.txt eol=crlf
*.csproj text=auto
*.vbproj text=auto
*.fsproj text=auto
*.dbproj text=auto
*.sln text=auto eol=crlf
*.sln text=auto eol=crlf

Просмотреть файл

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2010
VisualStudioVersion = 15.0.27130.2024
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{F5B1CA65-5852-41C6-9D6F-184A3889237B}"
ProjectSection(SolutionItems) = preProject
@ -95,7 +95,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetty.Microbench", "test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetty.Transport.Libuv", "src\DotNetty.Transport.Libuv\DotNetty.Transport.Libuv.csproj", "{9FE6A783-C20D-4097-9988-4178E2C4CE75}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Transport.Libuv.Tests", "test\DotNetty.Transport.Libuv.Tests\DotNetty.Transport.Libuv.Tests.csproj", "{1012C962-7F6D-4EC5-A0EC-0741A95BAD6B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetty.Transport.Libuv.Tests", "test\DotNetty.Transport.Libuv.Tests\DotNetty.Transport.Libuv.Tests.csproj", "{1012C962-7F6D-4EC5-A0EC-0741A95BAD6B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpServer", "examples\HttpServer\HttpServer.csproj", "{A7CACAE7-66E7-43DA-948B-28EB0DDDB582}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetty.Codecs.Http", "src\DotNetty.Codecs.Http\DotNetty.Codecs.Http.csproj", "{5F68A5B1-7907-4B16-8AFE-326E9DD7D65B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetty.Codecs.Http.Tests", "test\DotNetty.Codecs.Http.Tests\DotNetty.Codecs.Http.Tests.csproj", "{16C89E7C-1575-4685-8DFA-8E7E2C6101BF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -255,6 +261,18 @@ Global
{1012C962-7F6D-4EC5-A0EC-0741A95BAD6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1012C962-7F6D-4EC5-A0EC-0741A95BAD6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1012C962-7F6D-4EC5-A0EC-0741A95BAD6B}.Release|Any CPU.Build.0 = Release|Any CPU
{A7CACAE7-66E7-43DA-948B-28EB0DDDB582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7CACAE7-66E7-43DA-948B-28EB0DDDB582}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7CACAE7-66E7-43DA-948B-28EB0DDDB582}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7CACAE7-66E7-43DA-948B-28EB0DDDB582}.Release|Any CPU.Build.0 = Release|Any CPU
{5F68A5B1-7907-4B16-8AFE-326E9DD7D65B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F68A5B1-7907-4B16-8AFE-326E9DD7D65B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F68A5B1-7907-4B16-8AFE-326E9DD7D65B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F68A5B1-7907-4B16-8AFE-326E9DD7D65B}.Release|Any CPU.Build.0 = Release|Any CPU
{16C89E7C-1575-4685-8DFA-8E7E2C6101BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16C89E7C-1575-4685-8DFA-8E7E2C6101BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16C89E7C-1575-4685-8DFA-8E7E2C6101BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16C89E7C-1575-4685-8DFA-8E7E2C6101BF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -298,9 +316,12 @@ Global
{7155D1E6-00CE-4081-B922-E6C5524EE600} = {541093F6-616E-43D9-B671-FCD1F9C0A181}
{9FE6A783-C20D-4097-9988-4178E2C4CE75} = {126EA539-4B28-4B07-8B5D-D1D7F794D189}
{1012C962-7F6D-4EC5-A0EC-0741A95BAD6B} = {541093F6-616E-43D9-B671-FCD1F9C0A181}
{A7CACAE7-66E7-43DA-948B-28EB0DDDB582} = {F716F1EF-81EF-4020-914A-5422A13A9E13}
{5F68A5B1-7907-4B16-8AFE-326E9DD7D65B} = {126EA539-4B28-4B07-8B5D-D1D7F794D189}
{16C89E7C-1575-4685-8DFA-8E7E2C6101BF} = {541093F6-616E-43D9-B671-FCD1F9C0A181}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1FBD8DF1-D90A-4F21-8EB6-DA17B9431FE3}
{9FE6A783-C20D-4097-9988-4178E2C4CE75} = {126EA539-4B28-4B07-8B5D-D1D7F794D189}
SolutionGuid = {1FBD8DF1-D90A-4F21-8EB6-DA17B9431FE3}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,104 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace HttpServer
{
using System.Text;
using DotNetty.Buffers;
using DotNetty.Codecs.Http;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using System;
using DotNetty.Common;
sealed class HelloServerHandler : ChannelHandlerAdapter
{
static readonly ThreadLocalCache Cache = new ThreadLocalCache();
sealed class ThreadLocalCache : FastThreadLocal<AsciiString>
{
protected override AsciiString GetInitialValue()
{
DateTime dateTime = DateTime.UtcNow;
return AsciiString.Cached($"{dateTime.DayOfWeek}, {dateTime:dd MMM yyyy HH:mm:ss z}");
}
}
static readonly byte[] StaticPlaintext = Encoding.UTF8.GetBytes("Hello, World!");
static readonly int StaticPlaintextLen = StaticPlaintext.Length;
static readonly IByteBuffer PlaintextContentBuffer = Unpooled.UnreleasableBuffer(Unpooled.DirectBuffer().WriteBytes(StaticPlaintext));
static readonly AsciiString PlaintextClheaderValue = AsciiString.Cached($"{StaticPlaintextLen}");
static readonly AsciiString JsonClheaderValue = AsciiString.Cached($"{JsonLen()}");
static readonly AsciiString TypePlain = AsciiString.Cached("text/plain");
static readonly AsciiString TypeJson = AsciiString.Cached("application/json");
static readonly AsciiString ServerName = AsciiString.Cached("Netty");
static readonly AsciiString ContentTypeEntity = HttpHeaderNames.ContentType;
static readonly AsciiString DateEntity = HttpHeaderNames.Date;
static readonly AsciiString ContentLengthEntity = HttpHeaderNames.ContentLength;
static readonly AsciiString ServerEntity = HttpHeaderNames.Server;
volatile ICharSequence date = Cache.Value;
static int JsonLen() => Encoding.UTF8.GetBytes(NewMessage().ToJsonFormat()).Length;
static MessageBody NewMessage() => new MessageBody("Hello, World!");
public override void ChannelRead(IChannelHandlerContext ctx, object message)
{
if (message is IHttpRequest request)
{
try
{
this.Process(ctx, request);
}
finally
{
ReferenceCountUtil.Release(message);
}
}
else
{
ctx.FireChannelRead(message);
}
}
void Process(IChannelHandlerContext ctx, IHttpRequest request)
{
string uri = request.Uri;
switch (uri)
{
case "/plaintext":
this.WriteResponse(ctx, PlaintextContentBuffer.Duplicate(), TypePlain, PlaintextClheaderValue);
break;
case "/json":
byte[] json = Encoding.UTF8.GetBytes(NewMessage().ToJsonFormat());
this.WriteResponse(ctx, Unpooled.WrappedBuffer(json), TypeJson, JsonClheaderValue);
break;
default:
var response = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.NotFound, Unpooled.Empty, false);
ctx.WriteAndFlushAsync(response);
ctx.CloseAsync();
break;
}
}
void WriteResponse(IChannelHandlerContext ctx, IByteBuffer buf, ICharSequence contentType, ICharSequence contentLength)
{
// Build the response object.
var response = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.OK, buf, false);
HttpHeaders headers = response.Headers;
headers.Set(ContentTypeEntity, contentType);
headers.Set(ServerEntity, ServerName);
headers.Set(DateEntity, this.date);
headers.Set(ContentLengthEntity, contentLength);
// Close the non-keep-alive connection after the write operation is done.
ctx.WriteAsync(response);
}
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) => context.CloseAsync();
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
}
}

Просмотреть файл

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp1.1;net451</TargetFrameworks>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netcoreapp1.1' ">1.6.1</NetStandardImplicitPackageVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net451' ">
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\shared\dotnetty.com.pfx" Link="dotnetty.com.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotNetty.Buffers\DotNetty.Buffers.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Codecs.Http\DotNetty.Codecs.Http.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Codecs\DotNetty.Codecs.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Common\DotNetty.Common.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Handlers\DotNetty.Handlers.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Transport.Libuv\DotNetty.Transport.Libuv.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Transport\DotNetty.Transport.csproj" />
<ProjectReference Include="..\Examples.Common\Examples.Common.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace HttpServer
{
sealed class MessageBody
{
public MessageBody(string message)
{
this.Message = message;
}
public string Message { get; }
public string ToJsonFormat() => "{" + $"\"{nameof(MessageBody)}\" :" + "{" + $"\"{nameof(this.Message)}\"" + " :\"" + this.Message + "\"}" +"}";
}
}

Просмотреть файл

@ -0,0 +1,116 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace HttpServer
{
using System;
using System.IO;
using System.Net;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using DotNetty.Codecs.Http;
using DotNetty.Common;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using DotNetty.Transport.Libuv;
using Examples.Common;
class Program
{
static Program()
{
ResourceLeakDetector.Level = ResourceLeakDetector.DetectionLevel.Disabled;
}
static async Task RunServerAsync()
{
Console.WriteLine(
$"\n{RuntimeInformation.OSArchitecture} {RuntimeInformation.OSDescription}"
+ $"\n{RuntimeInformation.ProcessArchitecture} {RuntimeInformation.FrameworkDescription}"
+ $"\nProcessor Count : {Environment.ProcessorCount}\n");
bool useLibuv = ServerSettings.UseLibuv;
Console.WriteLine("Transport type : " + (useLibuv ? "Libuv" : "Socket"));
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
}
Console.WriteLine($"Server garbage collection: {GCSettings.IsServerGC}");
Console.WriteLine($"Current latency mode for garbage collection: {GCSettings.LatencyMode}");
IEventLoopGroup group;
IEventLoopGroup workGroup;
if (useLibuv)
{
var dispatcher = new DispatcherEventLoopGroup();
group = dispatcher;
workGroup = new WorkerEventLoopGroup(dispatcher);
}
else
{
group = new MultithreadEventLoopGroup(1);
workGroup = new MultithreadEventLoopGroup();
}
X509Certificate2 tlsCertificate = null;
if (ServerSettings.IsSsl)
{
tlsCertificate = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");
}
try
{
var bootstrap = new ServerBootstrap();
bootstrap.Group(group, workGroup);
if (useLibuv)
{
bootstrap.Channel<TcpServerChannel>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|| RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
bootstrap
.Option(ChannelOption.SoReuseport, true)
.ChildOption(ChannelOption.SoReuseaddr, true);
}
}
else
{
bootstrap.Channel<TcpServerSocketChannel>();
}
bootstrap
.Option(ChannelOption.SoBacklog, 8192)
.ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
if (tlsCertificate != null)
{
pipeline.AddLast(TlsHandler.Server(tlsCertificate));
}
pipeline.AddLast("encoder", new HttpResponseEncoder());
pipeline.AddLast("decoder", new HttpRequestDecoder(4096, 8192, 8192, false));
pipeline.AddLast("handler", new HelloServerHandler());
}));
IChannel bootstrapChannel = await bootstrap.BindAsync(IPAddress.IPv6Any, ServerSettings.Port);
Console.WriteLine($"Httpd started. Listening on {bootstrapChannel.LocalAddress}");
Console.ReadLine();
await bootstrapChannel.CloseAsync();
}
finally
{
group.ShutdownGracefullyAsync().Wait();
}
}
static void Main() => RunServerAsync().Wait();
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("HttpWebServer")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d4efc310-c3a7-42a2-bc9c-aa9cce3d1c63")]

Просмотреть файл

@ -0,0 +1,4 @@
{
"port": "7686",
"libuv": "true"
}

Просмотреть файл

@ -491,6 +491,42 @@ namespace DotNetty.Buffers
return value;
}
public virtual unsafe ICharSequence GetCharSequence(int index, int length, Encoding encoding)
{
this.CheckIndex0(index, length);
if (length == 0)
{
return StringCharSequence.Empty;
}
if (this.HasMemoryAddress)
{
IntPtr ptr = this.AddressOfPinnedMemory();
if (ptr != IntPtr.Zero)
{
return new StringCharSequence(UnsafeByteBufferUtil.GetString((byte*)(ptr + index), length, encoding));
}
else
{
fixed (byte* p = &this.GetPinnableMemoryAddress())
return new StringCharSequence(UnsafeByteBufferUtil.GetString(p + index, length, encoding));
}
}
if (this.HasArray)
{
return new StringCharSequence(encoding.GetString(this.Array, this.ArrayOffset + index, length));
}
return new StringCharSequence(this.ToString(index, length, encoding));
}
public virtual ICharSequence ReadCharSequence(int length, Encoding encoding)
{
ICharSequence sequence = this.GetCharSequence(this.readerIndex, length, encoding);
this.readerIndex += length;
return sequence;
}
public virtual IByteBuffer SetByte(int index, int value)
{
this.CheckIndex(index);
@ -633,6 +669,7 @@ namespace DotNetty.Buffers
this.SetBytes(index, src, 0, src.Length);
return this;
}
public abstract IByteBuffer SetBytes(int index, byte[] src, int srcIndex, int length);
public virtual IByteBuffer SetBytes(int index, IByteBuffer src)
@ -744,6 +781,48 @@ namespace DotNetty.Buffers
return bytes.Length;
}
public virtual int SetCharSequence(int index, ICharSequence sequence, Encoding encoding) => this.SetCharSequence0(index, sequence, encoding, false);
int SetCharSequence0(int index, ICharSequence sequence, Encoding encoding, bool expand)
{
if (ReferenceEquals(encoding, Encoding.UTF8))
{
int length = ByteBufferUtil.Utf8MaxBytes(sequence);
if (expand)
{
this.EnsureWritable0(length);
this.CheckIndex0(index, length);
}
else
{
this.CheckIndex(index, length);
}
return ByteBufferUtil.WriteUtf8(this, index, sequence, sequence.Count);
}
if (ReferenceEquals(encoding, Encoding.ASCII))
{
int length = sequence.Count;
if (expand)
{
this.EnsureWritable0(length);
this.CheckIndex0(index, length);
}
else
{
this.CheckIndex(index, length);
}
return ByteBufferUtil.WriteAscii(this, index, sequence, length);
}
byte[] bytes = encoding.GetBytes(sequence.ToString());
if (expand)
{
this.EnsureWritable0(bytes.Length);
// setBytes(...) will take care of checking the indices.
}
this.SetBytes(index, bytes);
return bytes.Length;
}
public virtual byte ReadByte()
{
this.CheckReadableBytes0(1);
@ -1184,6 +1263,13 @@ namespace DotNetty.Buffers
return this;
}
public virtual int WriteCharSequence(ICharSequence sequence, Encoding encoding)
{
int written = this.SetCharSequence0(this.writerIndex, sequence, encoding, true);
this.writerIndex += written;
return written;
}
public virtual int WriteString(string value, Encoding encoding)
{
int written = this.SetString0(this.writerIndex, value, encoding, true);

Просмотреть файл

@ -3,6 +3,7 @@
namespace DotNetty.Buffers
{
using System;
using System.Diagnostics;
using DotNetty.Common;

Просмотреть файл

@ -5,6 +5,7 @@ namespace DotNetty.Buffers
{
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Text;
using DotNetty.Common.Internal;
using DotNetty.Common.Internal.Logging;
@ -68,8 +69,8 @@ namespace DotNetty.Buffers
/// </summary>
public static string HexDump(byte[] array, int fromIndex, int length) => HexUtil.DoHexDump(array, fromIndex, length);
public static bool EnsureWritableSuccess(int ensureWritableResult) => ensureWritableResult == 0 || ensureWritableResult == 2;
public static bool EnsureWritableSuccess(int ensureWritableResult) => ensureWritableResult == 0 || ensureWritableResult == 2;
/// <summary>
/// Calculates the hash code of the specified buffer. This method is
/// useful when implementing a new buffer type.
@ -298,6 +299,104 @@ namespace DotNetty.Buffers
return buffer.ForEachByteDesc(toIndex, fromIndex - toIndex, new IndexOfProcessor(value));
}
public static IByteBuffer WriteUtf8(IByteBufferAllocator alloc, ICharSequence seq)
{
// UTF-8 uses max. 3 bytes per char, so calculate the worst case.
IByteBuffer buf = alloc.Buffer(Utf8MaxBytes(seq));
WriteUtf8(buf, seq);
return buf;
}
public static int WriteUtf8(IByteBuffer buf, ICharSequence seq) => ReserveAndWriteUtf8(buf, seq, Utf8MaxBytes(seq));
public static int ReserveAndWriteUtf8(IByteBuffer buf, ICharSequence seq, int reserveBytes)
{
for (;;)
{
if (buf is AbstractByteBuffer byteBuf)
{
byteBuf.EnsureWritable0(reserveBytes);
int written = WriteUtf8(byteBuf, byteBuf.WriterIndex, seq, seq.Count);
byteBuf.SetWriterIndex(byteBuf.WriterIndex + written);
return written;
}
else if (buf is WrappedByteBuffer)
{
// Unwrap as the wrapped buffer may be an AbstractByteBuf and so we can use fast-path.
buf = buf.Unwrap();
}
else
{
byte[] bytes = Encoding.UTF8.GetBytes(seq.ToString());
buf.WriteBytes(bytes);
return bytes.Length;
}
}
}
// Fast-Path implementation
internal static int WriteUtf8(AbstractByteBuffer buffer, int writerIndex, ICharSequence value, int len)
{
int oldWriterIndex = writerIndex;
// We can use the _set methods as these not need to do any index checks and reference checks.
// This is possible as we called ensureWritable(...) before.
for (int i = 0; i < len; i++)
{
char c = value[i];
if (c < 0x80)
{
buffer._SetByte(writerIndex++, (byte)c);
}
else if (c < 0x800)
{
buffer._SetByte(writerIndex++, (byte)(0xc0 | (c >> 6)));
buffer._SetByte(writerIndex++, (byte)(0x80 | (c & 0x3f)));
}
else if (char.IsSurrogate(c))
{
if (!char.IsHighSurrogate(c))
{
buffer._SetByte(writerIndex++, WriteUtfUnknown);
continue;
}
char c2;
try
{
// Surrogate Pair consumes 2 characters. Optimistically try to get the next character to avoid
// duplicate bounds checking with charAt. If an IndexOutOfBoundsException is thrown we will
// re-throw a more informative exception describing the problem.
c2 = value[++i];
}
catch (IndexOutOfRangeException)
{
buffer._SetByte(writerIndex++, WriteUtfUnknown);
break;
}
if (!char.IsLowSurrogate(c2))
{
buffer._SetByte(writerIndex++, WriteUtfUnknown);
buffer._SetByte(writerIndex++, char.IsHighSurrogate(c2) ? WriteUtfUnknown : c2);
continue;
}
int codePoint = CharUtil.ToCodePoint(c, c2);
// See http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630.
buffer._SetByte(writerIndex++, (byte)(0xf0 | (codePoint >> 18)));
buffer._SetByte(writerIndex++, (byte)(0x80 | ((codePoint >> 12) & 0x3f)));
buffer._SetByte(writerIndex++, (byte)(0x80 | ((codePoint >> 6) & 0x3f)));
buffer._SetByte(writerIndex++, (byte)(0x80 | (codePoint & 0x3f)));
}
else
{
buffer._SetByte(writerIndex++, (byte)(0xe0 | (c >> 12)));
buffer._SetByte(writerIndex++, (byte)(0x80 | ((c >> 6) & 0x3f)));
buffer._SetByte(writerIndex++, (byte)(0x80 | (c & 0x3f)));
}
}
return writerIndex - oldWriterIndex;
}
public static IByteBuffer WriteUtf8(IByteBufferAllocator alloc, string value)
{
// UTF-8 uses max. 3 bytes per char, so calculate the worst case.
@ -403,6 +502,8 @@ namespace DotNetty.Buffers
return writerIndex - oldWriterIndex;
}
internal static int Utf8MaxBytes(ICharSequence seq) => Utf8MaxBytes(seq.Count);
internal static int Utf8MaxBytes(string seq) => Utf8MaxBytes(seq.Length);
internal static int Utf8MaxBytes(int seqLength) => seqLength * MaxBytesPerCharUtf8;
@ -470,6 +571,61 @@ namespace DotNetty.Buffers
return encodedLength;
}
public static IByteBuffer WriteAscii(IByteBufferAllocator alloc, ICharSequence seq)
{
// ASCII uses 1 byte per char
IByteBuffer buf = alloc.Buffer(seq.Count);
WriteAscii(buf, seq);
return buf;
}
public static int WriteAscii(IByteBuffer buf, ICharSequence seq)
{
// ASCII uses 1 byte per char
int len = seq.Count;
if (seq is AsciiString asciiString)
{
buf.WriteBytes(asciiString.Array, asciiString.Offset, len);
}
else
{
for (;;)
{
if (buf is AbstractByteBuffer byteBuf)
{
byteBuf.EnsureWritable0(len);
int written = WriteAscii(byteBuf, byteBuf.WriterIndex, seq, len);
byteBuf.SetWriterIndex(byteBuf.WriterIndex + written);
return written;
}
else if (buf is WrappedByteBuffer)
{
// Unwrap as the wrapped buffer may be an AbstractByteBuf and so we can use fast-path.
buf = buf.Unwrap();
}
else
{
byte[] bytes = Encoding.ASCII.GetBytes(seq.ToString());
buf.WriteBytes(bytes);
return bytes.Length;
}
}
}
return len;
}
// Fast-Path implementation
internal static int WriteAscii(AbstractByteBuffer buffer, int writerIndex, ICharSequence seq, int len)
{
// We can use the _set methods as these not need to do any index checks and reference checks.
// This is possible as we called ensureWritable(...) before.
for (int i = 0; i < len; i++)
{
buffer._SetByte(writerIndex++, AsciiString.CharToByte(seq[i]));
}
return len;
}
public static IByteBuffer WriteAscii(IByteBufferAllocator alloc, string value)
{
// ASCII uses 1 byte per char
@ -592,6 +748,38 @@ namespace DotNetty.Buffers
}
}
public static void Copy(AsciiString src, IByteBuffer dst) => Copy(src, 0, dst, src.Count);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy(AsciiString src, int srcIdx, IByteBuffer dst, int dstIdx, int length)
{
if (MathUtil.IsOutOfBounds(srcIdx, length, src.Count))
{
ThrowHelper.ThrowIndexOutOfRangeException_Src(srcIdx, length, src.Count);
}
if (dst == null)
{
ThrowHelper.ThrowArgumentNullException_Dst();
}
// ReSharper disable once PossibleNullReferenceException
dst.SetBytes(dstIdx, src.Array, srcIdx + src.Offset, length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy(AsciiString src, int srcIdx, IByteBuffer dst, int length)
{
if (MathUtil.IsOutOfBounds(srcIdx, length, src.Count))
{
ThrowHelper.ThrowIndexOutOfRangeException_Src(srcIdx, length, src.Count);
}
if (dst == null)
{
ThrowHelper.ThrowArgumentNullException_Dst();
}
// ReSharper disable once PossibleNullReferenceException
dst.WriteBytes(src.Array, srcIdx + src.Offset, length);
}
/// <summary>
/// Returns a multi-line hexadecimal dump of the specified {@link ByteBuf} that is easy to read by humans.
/// </summary>

Просмотреть файл

@ -156,6 +156,12 @@ namespace DotNetty.Buffers
public IByteBuffer GetBytes(int index, Stream destination, int length) => this.CheckIndex(index, length);
public ICharSequence GetCharSequence(int index, int length, Encoding encoding)
{
this.CheckIndex(index, length);
return null;
}
public string GetString(int index, int length, Encoding encoding)
{
this.CheckIndex(index, length);
@ -218,6 +224,8 @@ namespace DotNetty.Buffers
public IByteBuffer SetZero(int index, int length) => this.CheckIndex(index, length);
public int SetCharSequence(int index, ICharSequence sequence, Encoding encoding) => throw new IndexOutOfRangeException();
public int SetString(int index, string value, Encoding encoding) => throw new IndexOutOfRangeException();
public bool ReadBoolean() => throw new IndexOutOfRangeException();
@ -276,6 +284,12 @@ namespace DotNetty.Buffers
public IByteBuffer ReadBytes(Stream destination, int length) => this.CheckLength(length);
public ICharSequence ReadCharSequence(int length, Encoding encoding)
{
this.CheckLength(length);
return null;
}
public string ReadString(int length, Encoding encoding)
{
this.CheckLength(length);
@ -338,6 +352,8 @@ namespace DotNetty.Buffers
public IByteBuffer WriteZero(int length) => this.CheckLength(length);
public int WriteCharSequence(ICharSequence sequence, Encoding encoding) => throw new IndexOutOfRangeException();
public int WriteString(string value, Encoding encoding) => throw new IndexOutOfRangeException();
public int IndexOf(int fromIndex, int toIndex, byte value)

Просмотреть файл

@ -483,6 +483,9 @@ namespace DotNetty.Buffers
/// </exception>
IByteBuffer GetBytes(int index, Stream destination, int length);
ICharSequence GetCharSequence(int index, int length, Encoding encoding);
/// <summary>
/// Gets a string with the given length at the given index.
/// </summary>
@ -781,6 +784,8 @@ namespace DotNetty.Buffers
/// </exception>
IByteBuffer SetZero(int index, int length);
int SetCharSequence(int index, ICharSequence sequence, Encoding encoding);
/// <summary>
/// Writes the specified string at the current writer index and increases
/// the writer index by the written bytes.
@ -973,6 +978,8 @@ namespace DotNetty.Buffers
IByteBuffer ReadBytes(Stream destination, int length);
ICharSequence ReadCharSequence(int length, Encoding encoding);
/// <summary>
/// Gets a string with the given length at the current reader index
/// and increases the reader index by the given length.
@ -1186,6 +1193,8 @@ namespace DotNetty.Buffers
IByteBuffer WriteZero(int length);
int WriteCharSequence(ICharSequence sequence, Encoding encoding);
int WriteString(string value, Encoding encoding);
int IndexOf(int fromIndex, int toIndex, byte value);

Просмотреть файл

@ -4,7 +4,7 @@
namespace DotNetty.Buffers
{
/// <summary>
/// Thread-safe interface for allocating <see cref="IByteBuffer" /> instances for use inside Helios reactive I/O
/// Thread-safe interface for allocating <see cref="IByteBuffer" />/.
/// </summary>
public interface IByteBufferAllocator
{

Просмотреть файл

@ -648,7 +648,7 @@ namespace DotNetty.Buffers
.Append(i)
.Append(": ");
PoolSubpage<T> s = head.Next;
for (; ;)
for (; ; )
{
buf.Append(s);
s = s.Next;
@ -706,7 +706,7 @@ namespace DotNetty.Buffers
// Rely on GC.
}
protected override PooledByteBuffer<byte[]> NewByteBuf(int maxCapacity) =>
protected override PooledByteBuffer<byte[]> NewByteBuf(int maxCapacity) =>
PooledHeapByteBuffer.NewInstance(maxCapacity);
protected override void MemoryCopy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length)
@ -751,7 +751,7 @@ namespace DotNetty.Buffers
return chunk;
}
protected override PooledByteBuffer<byte[]> NewByteBuf(int maxCapacity) =>
protected override PooledByteBuffer<byte[]> NewByteBuf(int maxCapacity) =>
PooledUnsafeDirectByteBuffer.NewInstance(maxCapacity);
protected override unsafe void MemoryCopy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) =>

Просмотреть файл

@ -59,7 +59,11 @@ namespace DotNetty.Buffers
this.DiscardMarks();
}
public override int Capacity => this.Length;
public override int Capacity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.Length;
}
public sealed override IByteBuffer AdjustCapacity(int newCapacity)
{

Просмотреть файл

@ -36,7 +36,7 @@ namespace DotNetty.Buffers
return this;
}
public override int ArrayOffset => this.Unwrap().ArrayOffset;
public override int ArrayOffset => this.Unwrap().ArrayOffset;
public override ref byte GetPinnableMemoryAddress() => ref this.Unwrap().GetPinnableMemoryAddress();
@ -103,4 +103,4 @@ namespace DotNetty.Buffers
protected internal override void _SetLongLE(int index, long value) => this.UnwrapCore()._SetLongLE(index, value);
}
}
}

Просмотреть файл

@ -41,7 +41,7 @@ namespace DotNetty.Buffers
public override int Capacity => this.MaxCapacity;
public override IByteBuffer AdjustCapacity(int newCapacity) =>throw new NotSupportedException("sliced buffer");
public override IByteBuffer AdjustCapacity(int newCapacity) => throw new NotSupportedException("sliced buffer");
public override int ArrayOffset => this.Idx(this.Unwrap().ArrayOffset);
@ -308,4 +308,4 @@ namespace DotNetty.Buffers
int Idx(int index) => index + this.adjustment;
}
}
}

Просмотреть файл

@ -143,6 +143,17 @@ namespace DotNetty.Buffers
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void ThrowIndexOutOfRangeException_Src(int srcIndex, int length, int count)
{
throw GetIndexOutOfRangeException();
IndexOutOfRangeException GetIndexOutOfRangeException()
{
return new IndexOutOfRangeException(string.Format("expected: 0 <= srcIdx({0}) <= srcIdx + length({1}) <= srcLen({2})", srcIndex, length, count));
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void ThrowIllegalReferenceCountException(int count)
{
@ -230,5 +241,16 @@ namespace DotNetty.Buffers
return new ArgumentOutOfRangeException("newCapacity", string.Format($"newCapacity: {0} (expected: 0-{1})", newCapacity, maxCapacity));
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void ThrowArgumentNullException_Dst()
{
throw GetArgumentOutOfRangeException();
ArgumentNullException GetArgumentOutOfRangeException()
{
return new ArgumentNullException("dst");
}
}
}
}

Просмотреть файл

@ -356,4 +356,4 @@ namespace DotNetty.Buffers
return this;
}
}
}
}

Просмотреть файл

@ -24,28 +24,28 @@ namespace DotNetty.Buffers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetUnsignedMedium(byte* bytes) =>
*bytes << 16 |
*(bytes + 1) << 8 |
*bytes << 16 |
*(bytes + 1) << 8 |
*(bytes + 2);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetUnsignedMediumLE(byte* bytes) =>
*bytes |
*(bytes + 1) << 8 |
*bytes |
*(bytes + 1) << 8 |
*(bytes + 2) << 16;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetInt(byte* bytes) =>
(*bytes << 24) |
(*(bytes + 1) << 16) |
(*(bytes + 2) << 8) |
(*bytes << 24) |
(*(bytes + 1) << 16) |
(*(bytes + 2) << 8) |
(*(bytes + 3));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetIntLE(byte* bytes) =>
*bytes |
*bytes |
(*(bytes + 1) << 8) |
(*(bytes + 2) << 16) |
(*(bytes + 2) << 16) |
(*(bytes + 3) << 24);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -365,4 +365,4 @@ namespace DotNetty.Buffers
internal static UnpooledUnsafeDirectByteBuffer NewUnsafeDirectByteBuffer(IByteBufferAllocator alloc, int initialCapacity, int maxCapacity) =>
new UnpooledUnsafeDirectByteBuffer(alloc, initialCapacity, maxCapacity);
}
}
}

Просмотреть файл

@ -210,6 +210,8 @@ namespace DotNetty.Buffers
return this;
}
public ICharSequence GetCharSequence(int index, int length, Encoding encoding) => this.Buf.GetCharSequence(index, length, encoding);
public string GetString(int index, int length, Encoding encoding) => this.Buf.GetString(index, length, encoding);
public virtual IByteBuffer SetBoolean(int index, bool value)
@ -350,6 +352,8 @@ namespace DotNetty.Buffers
return this;
}
public int SetCharSequence(int index, ICharSequence sequence, Encoding encoding) => this.Buf.SetCharSequence(index, sequence, encoding);
public virtual bool ReadBoolean() => this.Buf.ReadBoolean();
public virtual byte ReadByte() => this.Buf.ReadByte();
@ -436,6 +440,8 @@ namespace DotNetty.Buffers
return this;
}
public ICharSequence ReadCharSequence(int length, Encoding encoding) => this.Buf.ReadCharSequence(length, encoding);
public string ReadString(int length, Encoding encoding) => this.Buf.ReadString(length, encoding);
public virtual IByteBuffer SkipBytes(int length)
@ -576,6 +582,8 @@ namespace DotNetty.Buffers
return this;
}
public int WriteCharSequence(ICharSequence sequence, Encoding encoding) => this.Buf.WriteCharSequence(sequence, encoding);
public int WriteString(string value, Encoding encoding) => this.Buf.WriteString(value, encoding);
public virtual int IndexOf(int fromIndex, int toIndex, byte value) => this.Buf.IndexOf(fromIndex, toIndex, value);

Просмотреть файл

@ -522,6 +522,12 @@ namespace DotNetty.Buffers
return this;
}
public override ICharSequence GetCharSequence(int index, int length, Encoding encoding) => this.wrapped.GetCharSequence(index, length, encoding);
public override ICharSequence ReadCharSequence(int length, Encoding encoding) => this.wrapped.ReadCharSequence(length, encoding);
public override int SetCharSequence(int index, ICharSequence sequence, Encoding encoding) => this.wrapped.SetCharSequence(index, sequence, encoding);
public override string GetString(int index, int length, Encoding encoding) => this.wrapped.GetString(index, length, encoding);
public override string ReadString(int length, Encoding encoding) => this.wrapped.ReadString(length, encoding);
@ -530,6 +536,8 @@ namespace DotNetty.Buffers
public override IByteBuffer ReadBytes(Stream destination, int length) => this.wrapped.ReadBytes(destination, length);
public override int WriteCharSequence(ICharSequence sequence, Encoding encoding) => this.wrapped.WriteCharSequence(sequence, encoding);
public override int WriteString(string value, Encoding encoding) => this.wrapped.WriteString(value, encoding);
public override IByteBuffer SkipBytes(int length)

Просмотреть файл

@ -0,0 +1,221 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DotNetty.Common.Utilities;
using static Common.Utilities.StringUtil;
public sealed class CombinedHttpHeaders : DefaultHttpHeaders
{
public CombinedHttpHeaders(bool validate)
: base(new CombinedHttpHeadersImpl(AsciiString.CaseSensitiveHasher, ValueConverter(validate), NameValidator(validate)))
{
}
public override bool ContainsValue(AsciiString name, ICharSequence value, bool ignoreCase) =>
base.ContainsValue(name, TrimOws(value), ignoreCase);
sealed class CombinedHttpHeadersImpl : DefaultHeaders<AsciiString, ICharSequence>
{
// An estimate of the size of a header value.
const int ValueLengthEstimate = 10;
public CombinedHttpHeadersImpl(IHashingStrategy<AsciiString> nameHashingStrategy,
IValueConverter<ICharSequence> valueConverter, INameValidator<ICharSequence> nameValidator)
: base(nameHashingStrategy, valueConverter, nameValidator)
{
}
public override IEnumerable<ICharSequence> ValueIterator(AsciiString name)
{
ICharSequence value = null;
foreach (ICharSequence v in base.ValueIterator(name))
{
if (value != null)
{
throw new InvalidOperationException($"{nameof(CombinedHttpHeaders)} should only have one value");
}
value = v;
}
return value != null ? UnescapeCsvFields(value) : Enumerable.Empty<ICharSequence>();
}
public override IList<ICharSequence> GetAll(AsciiString name)
{
IList<ICharSequence> values = base.GetAll(name);
if (values.Count == 0)
{
return values;
}
if (values.Count != 1)
{
throw new InvalidOperationException($"{nameof(CombinedHttpHeaders)} should only have one value");
}
return UnescapeCsvFields(values[0]);
}
public override IHeaders<AsciiString, ICharSequence> Add(IHeaders<AsciiString, ICharSequence> headers)
{
// Override the fast-copy mechanism used by DefaultHeaders
if (ReferenceEquals(headers, this))
{
throw new ArgumentException("can't add to itself.");
}
if (headers is CombinedHttpHeadersImpl)
{
if (this.IsEmpty)
{
// Can use the fast underlying copy
this.AddImpl(headers);
}
else
{
// Values are already escaped so don't escape again
foreach (HeaderEntry<AsciiString, ICharSequence> header in headers)
{
this.AddEscapedValue(header.Key, header.Value);
}
}
}
else
{
foreach (HeaderEntry<AsciiString, ICharSequence> header in headers)
{
this.Add(header.Key, header.Value);
}
}
return this;
}
public override IHeaders<AsciiString, ICharSequence> Set(IHeaders<AsciiString, ICharSequence> headers)
{
if (ReferenceEquals(headers, this))
{
return this;
}
this.Clear();
return this.Add(headers);
}
public override IHeaders<AsciiString, ICharSequence> SetAll(IHeaders<AsciiString, ICharSequence> headers)
{
if (ReferenceEquals(headers, this))
{
return this;
}
foreach (AsciiString key in headers.Names())
{
this.Remove(key);
}
return this.Add(headers);
}
public override IHeaders<AsciiString, ICharSequence> Add(AsciiString name, ICharSequence value) =>
this.AddEscapedValue(name, EscapeCsv(value));
public override IHeaders<AsciiString, ICharSequence> Add(AsciiString name, IEnumerable<ICharSequence> values) =>
this.AddEscapedValue(name, CommaSeparate(values));
public override IHeaders<AsciiString, ICharSequence> AddObject(AsciiString name, object value) =>
this.AddEscapedValue(name, EscapeCsv(this.ValueConverter.ConvertObject(value)));
public override IHeaders<AsciiString, ICharSequence> AddObject(AsciiString name, IEnumerable<object> values) =>
this.AddEscapedValue(name, this.CommaSeparate(values));
public override IHeaders<AsciiString, ICharSequence> AddObject(AsciiString name, params object[] values) =>
this.AddEscapedValue(name, this.CommaSeparate(values));
public override IHeaders<AsciiString, ICharSequence> Set(AsciiString name, IEnumerable<ICharSequence> values)
{
base.Set(name, CommaSeparate(values));
return this;
}
public override IHeaders<AsciiString, ICharSequence> SetObject(AsciiString name, object value)
{
ICharSequence charSequence = EscapeCsv(this.ValueConverter.ConvertObject(value));
base.Set(name, charSequence);
return this;
}
public override IHeaders<AsciiString, ICharSequence> SetObject(AsciiString name, IEnumerable<object> values)
{
base.Set(name, this.CommaSeparate(values));
return this;
}
CombinedHttpHeadersImpl AddEscapedValue(AsciiString name, ICharSequence escapedValue)
{
if (!this.TryGet(name, out ICharSequence currentValue))
{
base.Add(name, escapedValue);
}
else
{
base.Set(name, CommaSeparateEscapedValues(currentValue, escapedValue));
}
return this;
}
ICharSequence CommaSeparate(IEnumerable<object> values)
{
StringBuilderCharSequence sb = values is ICollection collection
? new StringBuilderCharSequence(collection.Count * ValueLengthEstimate)
: new StringBuilderCharSequence();
foreach (object value in values)
{
if (sb.Count > 0)
{
sb.Append(Comma);
}
sb.Append(EscapeCsv(this.ValueConverter.ConvertObject(value)));
}
return sb;
}
static ICharSequence CommaSeparate(IEnumerable<ICharSequence> values)
{
StringBuilderCharSequence sb = values is ICollection collection
? new StringBuilderCharSequence(collection.Count * ValueLengthEstimate)
: new StringBuilderCharSequence();
foreach (ICharSequence value in values)
{
if (sb.Count > 0)
{
sb.Append(Comma);
}
sb.Append(EscapeCsv(value));
}
return sb;
}
static ICharSequence CommaSeparateEscapedValues(ICharSequence currentValue, ICharSequence value)
{
var builder = new StringBuilderCharSequence(currentValue.Count + 1 + value.Count);
builder.Append(currentValue);
builder.Append(Comma);
builder.Append(value);
return builder;
}
static ICharSequence EscapeCsv(ICharSequence value) => StringUtil.EscapeCsv(value, true);
}
}
}

Просмотреть файл

@ -0,0 +1,63 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using DotNetty.Buffers;
using DotNetty.Common;
public sealed class ComposedLastHttpContent : ILastHttpContent
{
readonly HttpHeaders trailingHeaders;
DecoderResult result;
internal ComposedLastHttpContent(HttpHeaders trailingHeaders)
{
this.trailingHeaders = trailingHeaders;
}
public HttpHeaders TrailingHeaders => this.trailingHeaders;
public IByteBufferHolder Copy()
{
var content = new DefaultLastHttpContent(Unpooled.Empty);
content.TrailingHeaders.Set(this.trailingHeaders);
return content;
}
public IByteBufferHolder Duplicate() => this.Copy();
public IByteBufferHolder RetainedDuplicate() => this.Copy();
public IByteBufferHolder Replace(IByteBuffer content)
{
var dup = new DefaultLastHttpContent(content);
dup.TrailingHeaders.SetAll(this.trailingHeaders);
return dup;
}
public IReferenceCounted Retain() => this;
public IReferenceCounted Retain(int increment) => this;
public IReferenceCounted Touch() => this;
public IReferenceCounted Touch(object hint) => this;
public IByteBuffer Content => Unpooled.Empty;
public DecoderResult Result
{
get => this.result;
set => this.result = value;
}
public int ReferenceCount => 1;
public bool Release() => false;
public bool Release(int decrement) => false;
}
}

Просмотреть файл

@ -0,0 +1,266 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cookies
{
using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using DotNetty.Common.Utilities;
public sealed class ClientCookieDecoder : CookieDecoder
{
// Strict encoder that validates that name and value chars are in the valid scope
// defined in RFC6265
public static readonly ClientCookieDecoder StrictDecoder = new ClientCookieDecoder(true);
// Lax instance that doesn't validate name and value
public static readonly ClientCookieDecoder LaxDecoder = new ClientCookieDecoder(false);
ClientCookieDecoder(bool strict) : base(strict)
{
}
public ICookie Decode(string header)
{
Contract.Requires(header != null);
int headerLen = header.Length;
if (headerLen == 0)
{
return null;
}
CookieBuilder cookieBuilder = null;
//loop:
for (int i = 0;;)
{
// Skip spaces and separators.
for (;;)
{
if (i == headerLen)
{
goto loop;
}
char c = header[i];
if (c == ',')
{
// Having multiple cookies in a single Set-Cookie header is
// deprecated, modern browsers only parse the first one
goto loop;
}
else if (c == '\t' || c == '\n' || c == 0x0b || c == '\f'
|| c == '\r' || c == ' ' || c == ';')
{
i++;
continue;
}
break;
}
int nameBegin = i;
int nameEnd;
int valueBegin;
int valueEnd;
for (;;)
{
char curChar = header[i];
if (curChar == ';')
{
// NAME; (no value till ';')
nameEnd = i;
valueBegin = valueEnd = -1;
break;
}
else if (curChar == '=')
{
// NAME=VALUE
nameEnd = i;
i++;
if (i == headerLen)
{
// NAME= (empty value, i.e. nothing after '=')
valueBegin = valueEnd = 0;
break;
}
valueBegin = i;
// NAME=VALUE;
int semiPos = header.IndexOf(';', i);
valueEnd = i = semiPos > 0 ? semiPos : headerLen;
break;
}
else
{
i++;
}
if (i == headerLen)
{
// NAME (no value till the end of string)
nameEnd = headerLen;
valueBegin = valueEnd = -1;
break;
}
}
if (valueEnd > 0 && header[valueEnd - 1] == ',')
{
// old multiple cookies separator, skipping it
valueEnd--;
}
if (cookieBuilder == null)
{
// cookie name-value pair
DefaultCookie cookie = this.InitCookie(header, nameBegin, nameEnd, valueBegin, valueEnd);
if (cookie == null)
{
return null;
}
cookieBuilder = new CookieBuilder(cookie, header);
}
else
{
// cookie attribute
cookieBuilder.AppendAttribute(nameBegin, nameEnd, valueBegin, valueEnd);
}
}
loop:
Debug.Assert(cookieBuilder != null);
return cookieBuilder.Cookie();
}
sealed class CookieBuilder
{
readonly string header;
readonly DefaultCookie cookie;
string domain;
string path;
long maxAge = long.MinValue;
int expiresStart;
int expiresEnd;
bool secure;
bool httpOnly;
internal CookieBuilder(DefaultCookie cookie, string header)
{
this.cookie = cookie;
this.header = header;
}
long MergeMaxAgeAndExpires()
{
// max age has precedence over expires
if (this.maxAge != long.MinValue)
{
return this.maxAge;
}
else if (IsValueDefined(this.expiresStart, this.expiresEnd))
{
DateTime? expiresDate = DateFormatter.ParseHttpDate(this.header, this.expiresStart, this.expiresEnd);
if (expiresDate != null)
{
return (expiresDate.Value.Ticks - DateTime.UtcNow.Ticks) / TimeSpan.TicksPerSecond;
}
}
return long.MinValue;
}
internal ICookie Cookie()
{
this.cookie.Domain = this.domain;
this.cookie.Path = this.path;
this.cookie.MaxAge = this.MergeMaxAgeAndExpires();
this.cookie.IsSecure = this.secure;
this.cookie.IsHttpOnly = this.httpOnly;
return this.cookie;
}
public void AppendAttribute(int keyStart, int keyEnd, int valueStart, int valueEnd)
{
int length = keyEnd - keyStart;
if (length == 4)
{
this.Parse4(keyStart, valueStart, valueEnd);
}
else if (length == 6)
{
this.Parse6(keyStart, valueStart, valueEnd);
}
else if (length == 7)
{
this.Parse7(keyStart, valueStart, valueEnd);
}
else if (length == 8)
{
this.Parse8(keyStart);
}
}
void Parse4(int nameStart, int valueStart, int valueEnd)
{
if (CharUtil.RegionMatchesIgnoreCase(this.header, nameStart, CookieHeaderNames.Path, 0, 4))
{
this.path = this.ComputeValue(valueStart, valueEnd);
}
}
void Parse6(int nameStart, int valueStart, int valueEnd)
{
if (CharUtil.RegionMatchesIgnoreCase(this.header, nameStart, CookieHeaderNames.Domain, 0, 5))
{
this.domain = this.ComputeValue(valueStart, valueEnd);
}
else if (CharUtil.RegionMatchesIgnoreCase(this.header, nameStart, CookieHeaderNames.Secure, 0, 5))
{
this.secure = true;
}
}
void SetMaxAge(string value)
{
if (long.TryParse(value, out long v))
{
this.maxAge = Math.Max(v, 0);
}
}
void Parse7(int nameStart, int valueStart, int valueEnd)
{
if (CharUtil.RegionMatchesIgnoreCase(this.header, nameStart, CookieHeaderNames.Expires, 0, 7))
{
this.expiresStart = valueStart;
this.expiresEnd = valueEnd;
}
else if (CharUtil.RegionMatchesIgnoreCase(this.header, nameStart, CookieHeaderNames.MaxAge, 0, 7))
{
this.SetMaxAge(this.ComputeValue(valueStart, valueEnd));
}
}
void Parse8(int nameStart)
{
if (CharUtil.RegionMatchesIgnoreCase(this.header, nameStart, CookieHeaderNames.HttpOnly, 0, 8))
{
this.httpOnly = true;
}
}
static bool IsValueDefined(int valueStart, int valueEnd) => valueStart != -1 && valueStart != valueEnd;
string ComputeValue(int valueStart, int valueEnd) => IsValueDefined(valueStart, valueEnd)
? this.header.Substring(valueStart, valueEnd - valueStart)
: null;
}
}
}

Просмотреть файл

@ -0,0 +1,147 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cookies
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Text;
using static CookieUtil;
public sealed class ClientCookieEncoder : CookieEncoder
{
// Strict encoder that validates that name and value chars are in the valid scope and (for methods that accept
// multiple cookies) sorts cookies into order of decreasing path length, as specified in RFC6265.
public static readonly ClientCookieEncoder StrictEncoder = new ClientCookieEncoder(true);
// Lax instance that doesn't validate name and value, and (for methods that accept multiple cookies) keeps
// cookies in the order in which they were given.
public static readonly ClientCookieEncoder LaxEncoder = new ClientCookieEncoder(false);
static readonly CookieComparer Comparer = new CookieComparer();
ClientCookieEncoder(bool strict) : base(strict)
{
}
public string Encode(string name, string value) => this.Encode(new DefaultCookie(name, value));
public string Encode(ICookie cookie)
{
Contract.Requires(cookie != null);
StringBuilder buf = StringBuilder();
this.Encode(buf, cookie);
return StripTrailingSeparator(buf);
}
sealed class CookieComparer : IComparer<ICookie>
{
public int Compare(ICookie c1, ICookie c2)
{
Debug.Assert(c1 != null && c2 != null);
string path1 = c1.Path;
string path2 = c2.Path;
// Cookies with unspecified path default to the path of the request. We don't
// know the request path here, but we assume that the length of an unspecified
// path is longer than any specified path (i.e. pathless cookies come first),
// because setting cookies with a path longer than the request path is of
// limited use.
int len1 = path1?.Length ?? int.MaxValue;
int len2 = path2?.Length ?? int.MaxValue;
int diff = len2 - len1;
if (diff != 0)
{
return diff;
}
// Rely on Java's sort stability to retain creation order in cases where
// cookies have same path length.
return -1;
}
}
public string Encode(params ICookie[] cookies)
{
if (cookies == null || cookies.Length == 0)
{
return null;
}
StringBuilder buf = StringBuilder();
if (this.Strict)
{
if (cookies.Length == 1)
{
this.Encode(buf, cookies[0]);
}
else
{
var cookiesSorted = new ICookie[cookies.Length];
Array.Copy(cookies, cookiesSorted, cookies.Length);
Array.Sort(cookiesSorted, Comparer);
foreach(ICookie c in cookiesSorted)
{
this.Encode(buf, c);
}
}
}
else
{
foreach (ICookie c in cookies)
{
this.Encode(buf, c);
}
}
return StripTrailingSeparatorOrNull(buf);
}
public string Encode(IEnumerable<ICookie> cookies)
{
Contract.Requires(cookies != null);
StringBuilder buf = StringBuilder();
if (this.Strict)
{
var cookiesList = new List<ICookie>();
foreach (ICookie cookie in cookies)
{
cookiesList.Add(cookie);
}
cookiesList.Sort(Comparer);
foreach (ICookie c in cookiesList)
{
this.Encode(buf, c);
}
}
else
{
foreach (ICookie cookie in cookies)
{
this.Encode(buf, cookie);
}
}
return StripTrailingSeparatorOrNull(buf);
}
void Encode(StringBuilder buf, ICookie c)
{
string name = c.Name;
string value = c.Value?? "";
this.ValidateCookie(name, value);
if (c.Wrap)
{
AddQuoted(buf, name, value);
}
else
{
Add(buf, name, value);
}
}
}
}

Просмотреть файл

@ -0,0 +1,74 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cookies
{
using DotNetty.Common.Internal.Logging;
using DotNetty.Common.Utilities;
using static CookieUtil;
public abstract class CookieDecoder
{
static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<CookieDecoder>();
protected readonly bool Strict;
protected CookieDecoder(bool strict)
{
this.Strict = strict;
}
protected DefaultCookie InitCookie(string header, int nameBegin, int nameEnd, int valueBegin, int valueEnd)
{
if (nameBegin == -1 || nameBegin == nameEnd)
{
Logger.Debug("Skipping cookie with null name");
return null;
}
if (valueBegin == -1)
{
Logger.Debug("Skipping cookie with null value");
return null;
}
var sequence = new StringCharSequence(header, valueBegin, valueEnd - valueBegin);
ICharSequence unwrappedValue = UnwrapValue(sequence);
if (unwrappedValue == null)
{
Logger.Debug("Skipping cookie because starting quotes are not properly balanced in '{}'", sequence);
return null;
}
string name = header.Substring(nameBegin, nameEnd - nameBegin);
int invalidOctetPos;
if (this.Strict && (invalidOctetPos = FirstInvalidCookieNameOctet(name)) >= 0)
{
if (Logger.DebugEnabled)
{
Logger.Debug("Skipping cookie because name '{}' contains invalid char '{}'",
name, name[invalidOctetPos]);
}
return null;
}
bool wrap = unwrappedValue.Count != valueEnd - valueBegin;
if (this.Strict && (invalidOctetPos = FirstInvalidCookieValueOctet(unwrappedValue)) >= 0)
{
if (Logger.DebugEnabled)
{
Logger.Debug("Skipping cookie because value '{}' contains invalid char '{}'",
unwrappedValue, unwrappedValue[invalidOctetPos]);
}
return null;
}
var cookie = new DefaultCookie(name, unwrappedValue.ToString());
cookie.Wrap = wrap;
return cookie;
}
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cookies
{
using System;
using DotNetty.Common.Utilities;
using static CookieUtil;
public abstract class CookieEncoder
{
protected readonly bool Strict;
protected CookieEncoder(bool strict)
{
this.Strict = strict;
}
protected void ValidateCookie(string name, string value)
{
if (!this.Strict)
{
return;
}
int pos;
if ((pos = FirstInvalidCookieNameOctet(name)) >= 0)
{
throw new ArgumentException($"Cookie name contains an invalid char: {name[pos]}");
}
var sequnce = new StringCharSequence(value);
ICharSequence unwrappedValue = UnwrapValue(sequnce);
if (unwrappedValue == null)
{
throw new ArgumentException($"Cookie value wrapping quotes are not balanced: {value}");
}
if ((pos = FirstInvalidCookieValueOctet(unwrappedValue)) >= 0)
{
throw new ArgumentException($"Cookie value contains an invalid char: {value[pos]}");
}
}
}
}

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cookies
{
using DotNetty.Common.Utilities;
public static class CookieHeaderNames
{
public static readonly AsciiString Path = AsciiString.Cached("Path");
public static readonly AsciiString Expires = AsciiString.Cached("Expires");
public static readonly AsciiString MaxAge = AsciiString.Cached("Max-Age");
public static readonly AsciiString Domain = AsciiString.Cached("Domain");
public static readonly AsciiString Secure = AsciiString.Cached("Secure");
public static readonly AsciiString HttpOnly = AsciiString.Cached("HTTPOnly");
}
}

Просмотреть файл

@ -0,0 +1,203 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cookies
{
using System;
using System.Collections;
using System.Text;
using DotNetty.Common;
using DotNetty.Common.Utilities;
static class CookieUtil
{
static readonly BitArray ValidCookieNameOctets = GetValidCookieNameOctets();
static readonly BitArray ValidCookieValueOctects = GetValidCookieValueOctets();
static readonly BitArray ValidCookieAttributeOctets = GetValidCookieAttributeValueOctets();
// token = 1*<any CHAR except CTLs or separators>
// separators =
//"(" | ")" | "<" | ">" | "@"
// | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "="
// | "{" | "}" | SP | HT
static BitArray GetValidCookieNameOctets()
{
var bitArray = new BitArray(128, false);
for (int i = 32; i < 127; i++)
{
bitArray[i] = true;
}
var separators = new int[]
{ '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t' };
foreach (int separator in separators)
{
bitArray[separator] = false;
}
return bitArray;
}
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
// US-ASCII characters excluding CTLs, whitespace, DQUOTE, comma, semicolon, and backslash
static BitArray GetValidCookieValueOctets()
{
var bitArray = new BitArray(128, false);
bitArray[0x21] = true;
for (int i = 0x23; i <= 0x2B; i++)
{
bitArray[i] = true;
}
for (int i = 0x2D; i <= 0x3A; i++)
{
bitArray[i] = true;
}
for (int i = 0x3C; i <= 0x5B; i++)
{
bitArray[i] = true;
}
for (int i = 0x5D; i <= 0x7E; i++)
{
bitArray[i] = true;
}
return bitArray;
}
// path-value = <any CHAR except CTLs or ";">
static BitArray GetValidCookieAttributeValueOctets()
{
var bitArray = new BitArray(128, false);
for (int i = 32; i < 127; i++)
{
bitArray[i] = true;
}
bitArray[';'] = false;
return bitArray;
}
internal static StringBuilder StringBuilder() => InternalThreadLocalMap.Get().StringBuilder;
internal static string StripTrailingSeparatorOrNull(StringBuilder buf) =>
buf.Length == 0 ? null : StripTrailingSeparator(buf);
internal static string StripTrailingSeparator(StringBuilder buf)
{
if (buf.Length > 0)
{
buf.Length = buf.Length - 2;
}
return buf.ToString();
}
internal static void Add(StringBuilder sb, string name, long val)
{
sb.Append(name);
sb.Append((char)HttpConstants.EqualsSign);
sb.Append(val);
sb.Append((char)HttpConstants.Semicolon);
sb.Append((char)HttpConstants.HorizontalSpace);
}
internal static void Add(StringBuilder sb, string name, string val)
{
sb.Append(name);
sb.Append((char)HttpConstants.EqualsSign);
sb.Append(val);
sb.Append((char)HttpConstants.Semicolon);
sb.Append((char)HttpConstants.HorizontalSpace);
}
internal static void Add(StringBuilder sb, string name)
{
sb.Append(name);
sb.Append((char)HttpConstants.Semicolon);
sb.Append((char)HttpConstants.HorizontalSpace);
}
internal static void AddQuoted(StringBuilder sb, string name, string val)
{
if (val == null)
{
val = "";
}
sb.Append(name);
sb.Append((char)HttpConstants.EqualsSign);
sb.Append((char)HttpConstants.DoubleQuote);
sb.Append(val);
sb.Append((char)HttpConstants.DoubleQuote);
sb.Append((char)HttpConstants.Semicolon);
sb.Append((char)HttpConstants.HorizontalSpace);
}
internal static int FirstInvalidCookieNameOctet(string cs) => FirstInvalidOctet(cs, ValidCookieNameOctets);
internal static int FirstInvalidCookieValueOctet(ICharSequence cs) => FirstInvalidOctet(cs, ValidCookieValueOctects);
static int FirstInvalidOctet(string cs, BitArray bits)
{
for (int i = 0; i < cs.Length; i++)
{
char c = cs[i];
if (!bits[c])
{
return i;
}
}
return -1;
}
static int FirstInvalidOctet(ICharSequence cs, BitArray bits)
{
for (int i = 0; i < cs.Count; i++)
{
char c = cs[i];
if (!bits[c])
{
return i;
}
}
return -1;
}
internal static ICharSequence UnwrapValue(ICharSequence cs)
{
int len = cs.Count;
if (len > 0 && cs[0] == '"')
{
if (len >= 2 && cs[len - 1] == '"')
{
// properly balanced
return len == 2 ? StringCharSequence.Empty : cs.SubSequence(1, len - 1);
}
else
{
return null;
}
}
return cs;
}
internal static string ValidateAttributeValue(string name, string value)
{
value = value?.Trim();
if (string.IsNullOrEmpty(value))
{
return null;
}
int i = FirstInvalidOctet(value, ValidCookieAttributeOctets);
if (i != -1)
{
throw new ArgumentException($"{name} contains the prohibited characters: ${value[i]}");
}
return value;
}
}
}

Просмотреть файл

@ -0,0 +1,227 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
// ReSharper disable ConvertToAutoPropertyWhenPossible
namespace DotNetty.Codecs.Http.Cookies
{
using System;
using System.Diagnostics.Contracts;
using System.Text;
using static CookieUtil;
public sealed class DefaultCookie : ICookie
{
// Constant for undefined MaxAge attribute value.
const long UndefinedMaxAge = long.MinValue;
readonly string name;
string value;
bool wrap;
string domain;
string path;
long maxAge = UndefinedMaxAge;
bool secure;
bool httpOnly;
public DefaultCookie(string name, string value)
{
Contract.Requires(!string.IsNullOrEmpty(name?.Trim()));
Contract.Requires(value != null);
this.name = name;
this.value = value;
}
public string Name => this.name;
public string Value
{
get => this.value;
set
{
Contract.Requires(value != null);
this.value = value;
}
}
public bool Wrap
{
get => this.wrap;
set => this.wrap = value;
}
public string Domain
{
get => this.domain;
set => this.domain = ValidateAttributeValue(nameof(this.domain), value);
}
public string Path
{
get => this.path;
set => this.path = ValidateAttributeValue(nameof(this.path), value);
}
public long MaxAge
{
get => this.maxAge;
set => this.maxAge = value;
}
public bool IsSecure
{
get => this.secure;
set => this.secure = value;
}
public bool IsHttpOnly
{
get => this.httpOnly;
set => this.httpOnly = value;
}
public override int GetHashCode() => this.name.GetHashCode();
public override bool Equals(object obj) => obj is DefaultCookie cookie && this.Equals(cookie);
public bool Equals(ICookie other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (!this.name.Equals(other.Name))
{
return false;
}
if (this.path == null)
{
if (other.Path != null)
{
return false;
}
}
else if (other.Path == null)
{
return false;
}
else if (!this.path.Equals(other.Path))
{
return false;
}
if (this.domain == null)
{
if (other.Domain != null)
{
return false;
}
}
else
{
return this.domain.Equals(other.Domain, StringComparison.OrdinalIgnoreCase);
}
return true;
}
public int CompareTo(ICookie other)
{
int v = string.Compare(this.name, other.Name, StringComparison.Ordinal);
if (v != 0)
{
return v;
}
if (this.path == null)
{
if (other.Path != null)
{
return -1;
}
}
else if (other.Path == null)
{
return 1;
}
else
{
v = string.Compare(this.path, other.Path, StringComparison.Ordinal);
if (v != 0)
{
return v;
}
}
if (this.domain == null)
{
if (other.Domain != null)
{
return -1;
}
}
else if (other.Domain == null)
{
return 1;
}
else
{
v = string.Compare(this.domain, other.Domain, StringComparison.OrdinalIgnoreCase);
return v;
}
return 0;
}
public int CompareTo(object obj)
{
if (obj == null)
{
return 1;
}
if (!(obj is ICookie cookie))
{
throw new ArgumentException($"{nameof(obj)} must be of {nameof(ICookie)} type");
}
return this.CompareTo(cookie);
}
public override string ToString()
{
StringBuilder buf = StringBuilder();
buf.Append($"{this.name}={this.Value}");
if (this.domain != null)
{
buf.Append($", domain={this.domain}");
}
if (this.path != null)
{
buf.Append($", path={this.path}");
}
if (this.maxAge >= 0)
{
buf.Append($", maxAge={this.maxAge}s");
}
if (this.secure)
{
buf.Append(", secure");
}
if (this.httpOnly)
{
buf.Append(", HTTPOnly");
}
return buf.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cookies
{
using System;
// http://en.wikipedia.org/wiki/HTTP_cookie
public interface ICookie : IEquatable<ICookie>, IComparable<ICookie>, IComparable
{
string Name { get; }
string Value { get; set; }
/// <summary>
/// Returns true if the raw value of this {@link Cookie},
/// was wrapped with double quotes in original Set-Cookie header.
/// </summary>
bool Wrap { get; set; }
string Domain { get; set; }
string Path { get; set; }
long MaxAge { get; set; }
bool IsSecure { get; set; }
///<summary>
/// Checks to see if this Cookie can only be accessed via HTTP.
/// If this returns true, the Cookie cannot be accessed through
/// client side script - But only if the browser supports it.
/// For more information, please look "http://www.owasp.org/index.php/HTTPOnly".
///</summary>
bool IsHttpOnly { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,146 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cookies
{
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using DotNetty.Common.Utilities;
// http://tools.ietf.org/html/rfc6265
// compliant cookie decoder to be used server side.
//
// http://tools.ietf.org/html/rfc2965
// cookies are still supported,old fields will simply be ignored.
public sealed class ServerCookieDecoder : CookieDecoder
{
static readonly AsciiString RFC2965Version = new AsciiString("$Version");
static readonly AsciiString RFC2965Path = new AsciiString($"${CookieHeaderNames.Path}");
static readonly AsciiString RFC2965Domain = new AsciiString($"${CookieHeaderNames.Domain}");
static readonly AsciiString RFC2965Port = new AsciiString("$Port");
//
// Strict encoder that validates that name and value chars are in the valid scope
// defined in RFC6265
//
public static readonly ServerCookieDecoder StrictDecoder = new ServerCookieDecoder(true);
//
// Lax instance that doesn't validate name and value
//
public static readonly ServerCookieDecoder LaxDecoder = new ServerCookieDecoder(false);
ServerCookieDecoder(bool strict) : base(strict)
{
}
public ISet<ICookie> Decode(string header)
{
Contract.Requires(header != null);
int headerLen = header.Length;
if (headerLen == 0)
{
return ImmutableHashSet<ICookie>.Empty;
}
var cookies = new SortedSet<ICookie>();
int i = 0;
bool rfc2965Style = false;
if (CharUtil.RegionMatchesIgnoreCase(header, 0, RFC2965Version, 0, RFC2965Version.Count))
{
// RFC 2965 style cookie, move to after version value
i = header.IndexOf(';') + 1;
rfc2965Style = true;
}
// loop
for (;;)
{
// Skip spaces and separators.
for (;;)
{
if (i == headerLen)
{
goto loop;
}
char c = header[i];
if (c == '\t' || c == '\n' || c == 0x0b || c == '\f'
|| c == '\r' || c == ' ' || c == ',' || c == ';')
{
i++;
continue;
}
break;
}
int nameBegin = i;
int nameEnd;
int valueBegin;
int valueEnd;
for (;;)
{
char curChar = header[i];
if (curChar == ';')
{
// NAME; (no value till ';')
nameEnd = i;
valueBegin = valueEnd = -1;
break;
}
else if (curChar == '=')
{
// NAME=VALUE
nameEnd = i;
i++;
if (i == headerLen)
{
// NAME= (empty value, i.e. nothing after '=')
valueBegin = valueEnd = 0;
break;
}
valueBegin = i;
// NAME=VALUE;
int semiPos = header.IndexOf(';', i);
valueEnd = i = semiPos > 0 ? semiPos : headerLen;
break;
}
else
{
i++;
}
if (i == headerLen)
{
// NAME (no value till the end of string)
nameEnd = headerLen;
valueBegin = valueEnd = -1;
break;
}
}
if (rfc2965Style && (CharUtil.RegionMatches(header, nameBegin, RFC2965Path, 0, RFC2965Path.Count)
|| CharUtil.RegionMatches(header, nameBegin, RFC2965Domain, 0, RFC2965Domain.Count)
|| CharUtil.RegionMatches(header, nameBegin, RFC2965Port, 0, RFC2965Port.Count)))
{
// skip obsolete RFC2965 fields
continue;
}
DefaultCookie cookie = this.InitCookie(header, nameBegin, nameEnd, valueBegin, valueEnd);
if (cookie != null)
{
cookies.Add(cookie);
}
}
loop:
return cookies;
}
}
}

Просмотреть файл

@ -0,0 +1,173 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cookies
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Text;
using static CookieUtil;
// http://tools.ietf.org/html/rfc6265 compliant cookie encoder to be used server side,
// so some fields are sent (Version is typically ignored).
//
// As Netty's Cookie merges Expires and MaxAge into one single field, only Max-Age field is sent.
// Note that multiple cookies must be sent as separate "Set-Cookie" headers.
public sealed class ServerCookieEncoder : CookieEncoder
{
//
// Strict encoder that validates that name and value chars are in the valid scope
// defined in RFC6265, and(for methods that accept multiple cookies) that only
// one cookie is encoded with any given name. (If multiple cookies have the same
// name, the last one is the one that is encoded.)
//
public static readonly ServerCookieEncoder StrictEncoder = new ServerCookieEncoder(true);
//
// Lax instance that doesn't validate name and value, and that allows multiple
// cookies with the same name.
//
public static readonly ServerCookieEncoder LaxEncoder = new ServerCookieEncoder(false);
ServerCookieEncoder(bool strict) : base(strict)
{
}
public string Encode(string name, string value) => this.Encode(new DefaultCookie(name, value));
public string Encode(ICookie cookie)
{
Contract.Requires(cookie != null);
string name = cookie.Name ?? nameof(cookie);
string value = cookie.Value ?? "";
this.ValidateCookie(name, value);
StringBuilder buf = StringBuilder();
if (cookie.Wrap)
{
AddQuoted(buf, name, value);
}
else
{
Add(buf, name, value);
}
if (cookie.MaxAge != long.MinValue)
{
Add(buf, (string)CookieHeaderNames.MaxAge, cookie.MaxAge);
DateTime expires = DateTime.UtcNow.AddMilliseconds(cookie.MaxAge * 1000);
buf.Append(CookieHeaderNames.Expires);
buf.Append((char)HttpConstants.EqualsSign);
DateFormatter.Append(expires, buf);
buf.Append((char)HttpConstants.Semicolon);
buf.Append((char)HttpConstants.HorizontalSpace);
}
if (cookie.Path != null)
{
Add(buf, (string)CookieHeaderNames.Path, cookie.Path);
}
if (cookie.Domain != null)
{
Add(buf, (string)CookieHeaderNames.Domain, cookie.Domain);
}
if (cookie.IsSecure)
{
Add(buf, (string)CookieHeaderNames.Secure);
}
if (cookie.IsHttpOnly)
{
Add(buf, (string)CookieHeaderNames.HttpOnly);
}
return StripTrailingSeparator(buf);
}
static List<string> Dedup(IReadOnlyList<string> encoded, IDictionary<string, int> nameToLastIndex)
{
var isLastInstance = new bool[encoded.Count];
foreach (int idx in nameToLastIndex.Values)
{
isLastInstance[idx] = true;
}
var dedupd = new List<string>(nameToLastIndex.Count);
for (int i = 0, n = encoded.Count; i < n; i++)
{
if (isLastInstance[i])
{
dedupd.Add(encoded[i]);
}
}
return dedupd;
}
public IList<string> Encode(params ICookie[] cookies)
{
if (cookies == null || cookies.Length == 0)
{
return ImmutableList<string>.Empty;
}
var encoded = new List<string>(cookies.Length);
Dictionary<string, int> nameToIndex = this.Strict && cookies.Length > 1 ? new Dictionary<string, int>() : null;
bool hasDupdName = false;
for (int i = 0; i < cookies.Length; i++)
{
ICookie c = cookies[i];
encoded.Add(this.Encode(c));
if (nameToIndex != null)
{
if (nameToIndex.ContainsKey(c.Name))
{
nameToIndex[c.Name] = i;
hasDupdName = true;
}
else
{
nameToIndex.Add(c.Name, i);
}
}
}
return hasDupdName ? Dedup(encoded, nameToIndex) : encoded;
}
public IList<string> Encode(ICollection<ICookie> cookies)
{
Contract.Requires(cookies != null);
if (cookies.Count == 0)
{
return ImmutableList<string>.Empty;
}
var encoded = new List<string>();
var nameToIndex = new Dictionary<string, int>();
bool hasDupdName = false;
int i = 0;
foreach (ICookie c in cookies)
{
encoded.Add(this.Encode(c));
if (nameToIndex.ContainsKey(c.Name))
{
nameToIndex[c.Name] = i;
hasDupdName = true;
}
else
{
nameToIndex.Add(c.Name, i);
}
i++;
}
return hasDupdName ? Dedup(encoded, nameToIndex) : encoded;
}
}
}

Просмотреть файл

@ -0,0 +1,190 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
// ReSharper disable ConvertToAutoPropertyWhenPossible
namespace DotNetty.Codecs.Http.Cors
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using DotNetty.Common.Concurrency;
using DotNetty.Common.Utilities;
// Configuration for Cross-Origin Resource Sharing (CORS).
public sealed class CorsConfig
{
readonly ISet<ICharSequence> origins;
readonly bool anyOrigin;
readonly bool enabled;
readonly ISet<ICharSequence> exposeHeaders;
readonly bool allowCredentials;
readonly long maxAge;
readonly ISet<HttpMethod> allowedRequestMethods;
readonly ISet<AsciiString> allowedRequestHeaders;
readonly bool allowNullOrigin;
readonly IDictionary<AsciiString, ICallable<object>> preflightHeaders;
readonly bool shortCircuit;
internal CorsConfig(CorsConfigBuilder builder)
{
this.origins = new HashSet<ICharSequence>(builder.origins, AsciiString.CaseSensitiveHasher);
this.anyOrigin = builder.anyOrigin;
this.enabled = builder.enabled;
this.exposeHeaders = builder.exposeHeaders;
this.allowCredentials = builder.allowCredentials;
this.maxAge = builder.maxAge;
this.allowedRequestMethods = builder.requestMethods;
this.allowedRequestHeaders = builder.requestHeaders;
this.allowNullOrigin = builder.allowNullOrigin;
this.preflightHeaders = builder.preflightHeaders;
this.shortCircuit = builder.shortCircuit;
}
public bool IsCorsSupportEnabled => this.enabled;
public bool IsAnyOriginSupported => this.anyOrigin;
public ICharSequence Origin => this.origins.Count == 0 ? CorsHandler.AnyOrigin : this.origins.First();
public ISet<ICharSequence> Origins => this.origins;
public bool IsNullOriginAllowed => this.allowNullOrigin;
public ISet<ICharSequence> ExposedHeaders() => this.exposeHeaders.ToImmutableHashSet();
public bool IsCredentialsAllowed => this.allowCredentials;
public long MaxAge => this.maxAge;
public ISet<HttpMethod> AllowedRequestMethods() => this.allowedRequestMethods.ToImmutableHashSet();
public ISet<AsciiString> AllowedRequestHeaders() => this.allowedRequestHeaders.ToImmutableHashSet();
public HttpHeaders PreflightResponseHeaders()
{
if (this.preflightHeaders.Count == 0)
{
return EmptyHttpHeaders.Default;
}
HttpHeaders headers = new DefaultHttpHeaders();
foreach (KeyValuePair<AsciiString, ICallable<object>> entry in this.preflightHeaders)
{
object value = GetValue(entry.Value);
if (value is IEnumerable<object> values)
{
headers.Add(entry.Key, values);
}
else
{
headers.Add(entry.Key, value);
}
}
return headers;
}
public bool IsShortCircuit => this.shortCircuit;
static object GetValue(ICallable<object> callable)
{
try
{
return callable.Call();
}
catch (Exception exception)
{
throw new InvalidOperationException($"Could not generate value for callable [{callable}]", exception);
}
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append($"{StringUtil.SimpleClassName(this)}")
.Append($"[enabled = {this.enabled}");
builder.Append(", origins=");
if (this.Origins.Count == 0)
{
builder.Append("*");
}
else
{
builder.Append("(");
foreach (ICharSequence value in this.Origins)
{
builder.Append($"'{value}'");
}
builder.Append(")");
}
builder.Append(", exposedHeaders=");
if (this.exposeHeaders.Count == 0)
{
builder.Append("*");
}
else
{
builder.Append("(");
foreach (ICharSequence value in this.exposeHeaders)
{
builder.Append($"'{value}'");
}
builder.Append(")");
}
builder.Append($", isCredentialsAllowed={this.allowCredentials}");
builder.Append($", maxAge={this.maxAge}");
builder.Append(", allowedRequestMethods=");
if (this.allowedRequestMethods.Count == 0)
{
builder.Append("*");
}
else
{
builder.Append("(");
foreach (HttpMethod value in this.allowedRequestMethods)
{
builder.Append($"'{value}'");
}
builder.Append(")");
}
builder.Append(", allowedRequestHeaders=");
if (this.allowedRequestHeaders.Count == 0)
{
builder.Append("*");
}
else
{
builder.Append("(");
foreach(AsciiString value in this.allowedRequestHeaders)
{
builder.Append($"'{value}'");
}
builder.Append(")");
}
builder.Append(", preflightHeaders=");
if (this.preflightHeaders.Count == 0)
{
builder.Append("*");
}
else
{
builder.Append("(");
foreach (AsciiString value in this.preflightHeaders.Keys)
{
builder.Append($"'{value}'");
}
builder.Append(")");
}
builder.Append("]");
return builder.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,191 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable InconsistentNaming
namespace DotNetty.Codecs.Http.Cors
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using DotNetty.Common.Concurrency;
using DotNetty.Common.Utilities;
public sealed class CorsConfigBuilder
{
public static CorsConfigBuilder ForAnyOrigin() => new CorsConfigBuilder();
public static CorsConfigBuilder ForOrigin(ICharSequence origin)
{
return CorsHandler.AnyOrigin.ContentEquals(origin) // * AnyOrigin
? new CorsConfigBuilder()
: new CorsConfigBuilder(origin);
}
public static CorsConfigBuilder ForOrigins(params ICharSequence[] origins) => new CorsConfigBuilder(origins);
internal readonly ISet<ICharSequence> origins;
internal readonly bool anyOrigin;
internal bool allowNullOrigin;
internal bool enabled = true;
internal bool allowCredentials;
internal readonly HashSet<ICharSequence> exposeHeaders = new HashSet<ICharSequence>(AsciiString.CaseSensitiveHasher);
internal long maxAge;
internal readonly ISet<HttpMethod> requestMethods = new HashSet<HttpMethod>();
internal readonly ISet<AsciiString> requestHeaders = new HashSet<AsciiString>();
internal readonly Dictionary<AsciiString, ICallable<object>> preflightHeaders = new Dictionary<AsciiString, ICallable<object>>();
internal bool noPreflightHeaders;
internal bool shortCircuit;
CorsConfigBuilder(params ICharSequence[] origins)
{
this.origins = new HashSet<ICharSequence>(origins);
this.anyOrigin = false;
}
CorsConfigBuilder()
{
this.anyOrigin = true;
this.origins = ImmutableHashSet<ICharSequence>.Empty;
}
public CorsConfigBuilder AllowNullOrigin()
{
this.allowNullOrigin = true;
return this;
}
public CorsConfigBuilder Disable()
{
this.enabled = false;
return this;
}
public CorsConfigBuilder ExposeHeaders(params ICharSequence[] headers)
{
foreach (ICharSequence header in headers)
{
this.exposeHeaders.Add(header);
}
return this;
}
public CorsConfigBuilder ExposeHeaders(params string[] headers)
{
foreach (string header in headers)
{
this.exposeHeaders.Add(new StringCharSequence(header));
}
return this;
}
public CorsConfigBuilder AllowCredentials()
{
this.allowCredentials = true;
return this;
}
public CorsConfigBuilder MaxAge(long max)
{
this.maxAge = max;
return this;
}
public CorsConfigBuilder AllowedRequestMethods(params HttpMethod[] methods)
{
this.requestMethods.UnionWith(methods);
return this;
}
public CorsConfigBuilder AllowedRequestHeaders(params AsciiString[] headers)
{
this.requestHeaders.UnionWith(headers);
return this;
}
public CorsConfigBuilder AllowedRequestHeaders(params ICharSequence[] headers)
{
foreach (ICharSequence header in headers)
{
this.requestHeaders.Add(new AsciiString(header));
}
return this;
}
public CorsConfigBuilder PreflightResponseHeader(AsciiString name, params object[] values)
{
Contract.Requires(values != null);
if (values.Length == 1)
{
this.preflightHeaders.Add(name, new ConstantValueGenerator(values[0]));
}
else
{
this.PreflightResponseHeader(name, new List<object>(values));
}
return this;
}
public CorsConfigBuilder PreflightResponseHeader(AsciiString name, ICollection<object> value)
{
this.preflightHeaders.Add(name, new ConstantValueGenerator(value));
return this;
}
public CorsConfigBuilder PreflightResponseHeader(AsciiString name, ICallable<object> valueGenerator)
{
this.preflightHeaders.Add(name, valueGenerator);
return this;
}
public CorsConfigBuilder NoPreflightResponseHeaders()
{
this.noPreflightHeaders = true;
return this;
}
public CorsConfigBuilder ShortCircuit()
{
this.shortCircuit = true;
return this;
}
public CorsConfig Build()
{
if (this.preflightHeaders.Count == 0 && !this.noPreflightHeaders)
{
this.preflightHeaders.Add(HttpHeaderNames.Date, DateValueGenerator.Default);
this.preflightHeaders.Add(HttpHeaderNames.ContentLength, new ConstantValueGenerator(new AsciiString("0")));
}
return new CorsConfig(this);
}
// This class is used for preflight HTTP response values that do not need to be
// generated, but instead the value is "static" in that the same value will be returned
// for each call.
sealed class ConstantValueGenerator : ICallable<object>
{
readonly object value;
internal ConstantValueGenerator(object value)
{
Contract.Requires(value != null);
this.value = value;
}
public object Call() => this.value;
}
// This callable is used for the DATE preflight HTTP response HTTP header.
// It's value must be generated when the response is generated, hence will be
// different for every call.
sealed class DateValueGenerator : ICallable<object>
{
internal static readonly DateValueGenerator Default = new DateValueGenerator();
public object Call() => new DateTime();
}
}
}

Просмотреть файл

@ -0,0 +1,211 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Cors
{
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using DotNetty.Common.Internal.Logging;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using static Common.Utilities.ReferenceCountUtil;
public class CorsHandler : ChannelDuplexHandler
{
static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<CorsHandler>();
internal static readonly AsciiString AnyOrigin = new AsciiString("*");
internal static readonly AsciiString NullOrigin = new AsciiString("null");
readonly CorsConfig config;
IHttpRequest request;
public CorsHandler(CorsConfig config)
{
Contract.Requires(config != null);
this.config = config;
}
public override void ChannelRead(IChannelHandlerContext context, object message)
{
if (this.config.IsCorsSupportEnabled && message is IHttpRequest)
{
this.request = (IHttpRequest)message;
if (IsPreflightRequest(this.request))
{
this.HandlePreflight(context, this.request);
return;
}
if (this.config.IsShortCircuit && !this.ValidateOrigin())
{
Forbidden(context, this.request);
return;
}
}
context.FireChannelRead(message);
}
void HandlePreflight(IChannelHandlerContext ctx, IHttpRequest req)
{
var response = new DefaultFullHttpResponse(req.ProtocolVersion, HttpResponseStatus.OK, true, true);
if (this.SetOrigin(response))
{
this.SetAllowMethods(response);
this.SetAllowHeaders(response);
this.SetAllowCredentials(response);
this.SetMaxAge(response);
this.SetPreflightHeaders(response);
}
if (!response.Headers.Contains(HttpHeaderNames.ContentLength))
{
response.Headers.Set(HttpHeaderNames.ContentLength, HttpHeaderValues.Zero);
}
Release(req);
Respond(ctx, req, response);
}
void SetPreflightHeaders(IHttpResponse response) => response.Headers.Add(this.config.PreflightResponseHeaders());
bool SetOrigin(IHttpResponse response)
{
if (!this.request.Headers.TryGet(HttpHeaderNames.Origin, out ICharSequence origin))
{
return false;
}
if (NullOrigin.ContentEquals(origin) && this.config.IsNullOriginAllowed)
{
SetNullOrigin(response);
return true;
}
if (this.config.IsAnyOriginSupported)
{
if (this.config.IsCredentialsAllowed)
{
this.EchoRequestOrigin(response);
SetVaryHeader(response);
}
else
{
SetAnyOrigin(response);
}
return true;
}
if (this.config.Origins.Contains(origin))
{
SetOrigin(response, origin);
SetVaryHeader(response);
return true;
}
Logger.Debug("Request origin [{}]] was not among the configured origins [{}]", origin, this.config.Origins);
return false;
}
bool ValidateOrigin()
{
if (this.config.IsAnyOriginSupported)
{
return true;
}
if (!this.request.Headers.TryGet(HttpHeaderNames.Origin, out ICharSequence origin))
{
// Not a CORS request so we cannot validate it. It may be a non CORS request.
return true;
}
if (NullOrigin.ContentEquals(origin) && this.config.IsNullOriginAllowed)
{
return true;
}
return this.config.Origins.Contains(origin);
}
void EchoRequestOrigin(IHttpResponse response) => SetOrigin(response, this.request.Headers.Get(HttpHeaderNames.Origin, null));
static void SetVaryHeader(IHttpResponse response) => response.Headers.Set(HttpHeaderNames.Vary, HttpHeaderNames.Origin);
static void SetAnyOrigin(IHttpResponse response) => SetOrigin(response, AnyOrigin);
static void SetNullOrigin(IHttpResponse response) => SetOrigin(response, NullOrigin);
static void SetOrigin(IHttpResponse response, ICharSequence origin) => response.Headers.Set(HttpHeaderNames.AccessControlAllowOrigin, origin);
void SetAllowCredentials(IHttpResponse response)
{
if (this.config.IsCredentialsAllowed
&& !AsciiString.ContentEquals(response.Headers.Get(HttpHeaderNames.AccessControlAllowOrigin, null), AnyOrigin))
{
response.Headers.Set(HttpHeaderNames.AccessControlAllowCredentials, new AsciiString("true"));
}
}
static bool IsPreflightRequest(IHttpRequest request)
{
HttpHeaders headers = request.Headers;
return request.Method.Equals(HttpMethod.Options)
&& headers.Contains(HttpHeaderNames.Origin)
&& headers.Contains(HttpHeaderNames.AccessControlRequestMethod);
}
void SetExposeHeaders(IHttpResponse response)
{
ISet<ICharSequence> headers = this.config.ExposedHeaders();
if (headers.Count > 0)
{
response.Headers.Set(HttpHeaderNames.AccessControlExposeHeaders, headers);
}
}
void SetAllowMethods(IHttpResponse response) => response.Headers.Set(HttpHeaderNames.AccessControlAllowMethods, this.config.AllowedRequestMethods());
void SetAllowHeaders(IHttpResponse response) => response.Headers.Set(HttpHeaderNames.AccessControlAllowHeaders, this.config.AllowedRequestHeaders());
void SetMaxAge(IHttpResponse response) => response.Headers.Set(HttpHeaderNames.AccessControlMaxAge, this.config.MaxAge);
public override Task WriteAsync(IChannelHandlerContext context, object message)
{
if (this.config.IsCorsSupportEnabled && message is IHttpResponse response)
{
if (this.SetOrigin(response))
{
this.SetAllowCredentials(response);
this.SetExposeHeaders(response);
}
}
return context.WriteAndFlushAsync(message);
}
static void Forbidden(IChannelHandlerContext ctx, IHttpRequest request)
{
var response = new DefaultFullHttpResponse(request.ProtocolVersion, HttpResponseStatus.Forbidden);
response.Headers.Set(HttpHeaderNames.ContentLength, HttpHeaderValues.Zero);
Release(request);
Respond(ctx, request, response);
}
static void Respond(IChannelHandlerContext ctx, IHttpRequest request, IHttpResponse response)
{
bool keepAlive = HttpUtil.IsKeepAlive(request);
HttpUtil.SetKeepAlive(response, keepAlive);
Task task = ctx.WriteAndFlushAsync(response);
if (!keepAlive)
{
task.ContinueWith(CloseOnComplete, ctx,
TaskContinuationOptions.ExecuteSynchronously);
}
}
static void CloseOnComplete(Task task, object state)
{
var ctx = (IChannelHandlerContext)state;
ctx.CloseAsync();
}
}
}

Просмотреть файл

@ -0,0 +1,143 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using System.Diagnostics.Contracts;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common;
public class DefaultFullHttpRequest : DefaultHttpRequest, IFullHttpRequest
{
readonly IByteBuffer content;
readonly HttpHeaders trailingHeader;
// Used to cache the value of the hash code and avoid {@link IllegalReferenceCountException}.
int hash;
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, string uri)
: this(httpVersion, method, uri, Unpooled.Buffer(0))
{
}
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, string uri, IByteBuffer content)
: this(httpVersion, method, uri, content, true)
{
}
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, string uri, bool validateHeaders)
: this(httpVersion, method, uri, Unpooled.Buffer(0), validateHeaders)
{
}
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, string uri,
IByteBuffer content, bool validateHeaders)
: base(httpVersion, method, uri, validateHeaders)
{
Contract.Requires(content != null);
this.content = content;
this.trailingHeader = new DefaultHttpHeaders(validateHeaders);
}
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, string uri,
IByteBuffer content, HttpHeaders headers, HttpHeaders trailingHeader)
: base(httpVersion, method, uri, headers)
{
Contract.Requires(content != null);
Contract.Requires(trailingHeader != null);
this.content = content;
this.trailingHeader = trailingHeader;
}
public HttpHeaders TrailingHeaders => this.trailingHeader;
public IByteBuffer Content => this.content;
public int ReferenceCount => this.content.ReferenceCount;
public IReferenceCounted Retain()
{
this.content.Retain();
return this;
}
public IReferenceCounted Retain(int increment)
{
this.content.Retain(increment);
return this;
}
public IReferenceCounted Touch()
{
this.content.Touch();
return this;
}
public IReferenceCounted Touch(object hint)
{
this.content.Touch(hint);
return this;
}
public bool Release() => this.content.Release();
public bool Release(int decrement) => this.content.Release(decrement);
public IByteBufferHolder Copy() => this.Replace(this.content.Copy());
public IByteBufferHolder Duplicate() => this.Replace(this.content.Duplicate());
public IByteBufferHolder RetainedDuplicate() => this.Replace(this.content.RetainedDuplicate());
public IByteBufferHolder Replace(IByteBuffer newContent) =>
new DefaultFullHttpRequest(this.ProtocolVersion, this.Method, this.Uri, newContent, this.Headers, this.trailingHeader);
public override int GetHashCode()
{
// ReSharper disable NonReadonlyMemberInGetHashCode
int hashCode = this.hash;
if (hashCode == 0)
{
if (this.content.ReferenceCount != 0)
{
try
{
hashCode = 31 + this.content.GetHashCode();
}
catch (IllegalReferenceCountException)
{
// Handle race condition between checking refCnt() == 0 and using the object.
hashCode = 31;
}
}
else
{
hashCode = 31;
}
hashCode = 31 * hashCode + this.trailingHeader.GetHashCode();
hashCode = 31 * hashCode + base.GetHashCode();
this.hash = hashCode;
}
// ReSharper restore NonReadonlyMemberInGetHashCode
return hashCode;
}
public override bool Equals(object obj)
{
if (!(obj is DefaultFullHttpRequest other))
{
return false;
}
return base.Equals(other)
&& this.content.Equals(other.content)
&& this.trailingHeader.Equals(other.trailingHeader);
}
public override string ToString() => HttpMessageUtil.AppendFullRequest(new StringBuilder(256), this).ToString();
}
}

Просмотреть файл

@ -0,0 +1,156 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using System.Diagnostics.Contracts;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common;
public class DefaultFullHttpResponse : DefaultHttpResponse, IFullHttpResponse
{
readonly IByteBuffer content;
readonly HttpHeaders trailingHeaders;
// Used to cache the value of the hash code and avoid {@link IllegalReferenceCountException}.
int hash;
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status)
: this(version, status, Unpooled.Buffer(0))
{
}
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, IByteBuffer content)
: this(version, status, content, true)
{
}
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, bool validateHeaders)
: this(version, status, Unpooled.Buffer(0), validateHeaders, false)
{
}
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, bool validateHeaders,
bool singleFieldHeaders)
: this(version, status, Unpooled.Buffer(0), validateHeaders, singleFieldHeaders)
{
}
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status,
IByteBuffer content, bool validateHeaders)
: this(version, status, content, validateHeaders, false)
{
}
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status,
IByteBuffer content, bool validateHeaders, bool singleFieldHeaders)
: base(version, status, validateHeaders, singleFieldHeaders)
{
Contract.Requires(content != null);
this.content = content;
this.trailingHeaders = singleFieldHeaders
? new CombinedHttpHeaders(validateHeaders)
: new DefaultHttpHeaders(validateHeaders);
}
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, IByteBuffer content, HttpHeaders headers, HttpHeaders trailingHeaders)
: base(version, status, headers)
{
Contract.Requires(content != null);
Contract.Requires(trailingHeaders != null);
this.content = content;
this.trailingHeaders = trailingHeaders;
}
public HttpHeaders TrailingHeaders => this.trailingHeaders;
public IByteBuffer Content => this.content;
public int ReferenceCount => this.content.ReferenceCount;
public IReferenceCounted Retain()
{
this.content.Retain();
return this;
}
public IReferenceCounted Retain(int increment)
{
this.content.Retain(increment);
return this;
}
public IReferenceCounted Touch()
{
this.content.Touch();
return this;
}
public IReferenceCounted Touch(object hint)
{
this.content.Touch(hint);
return this;
}
public bool Release() => this.content.Release();
public bool Release(int decrement) => this.content.Release(decrement);
public IByteBufferHolder Copy() => this.Replace(this.content.Copy());
public IByteBufferHolder Duplicate() => this.Replace(this.content.Duplicate());
public IByteBufferHolder RetainedDuplicate() => this.Replace(this.content.RetainedDuplicate());
public IByteBufferHolder Replace(IByteBuffer newContent) =>
new DefaultFullHttpResponse(this.ProtocolVersion, this.Status, newContent, this.Headers, this.trailingHeaders);
public override int GetHashCode()
{
// ReSharper disable NonReadonlyMemberInGetHashCode
int hashCode = this.hash;
if (hashCode == 0)
{
if (this.content.ReferenceCount != 0)
{
try
{
hashCode = 31 + this.content.GetHashCode();
}
catch (IllegalReferenceCountException)
{
// Handle race condition between checking refCnt() == 0 and using the object.
hashCode = 31;
}
}
else
{
hashCode = 31;
}
hashCode = 31 * hashCode + this.trailingHeaders.GetHashCode();
hashCode = 31 * hashCode + base.GetHashCode();
this.hash = hashCode;
}
// ReSharper restore NonReadonlyMemberInGetHashCode
return hashCode;
}
public override bool Equals(object obj)
{
if (!(obj is DefaultFullHttpResponse other))
{
return false;
}
return base.Equals(other)
&& this.content.Equals(other.content)
&& this.trailingHeaders.Equals(other.trailingHeaders);
}
public override string ToString() => HttpMessageUtil.AppendFullResponse(new StringBuilder(256), this).ToString();
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using System.Diagnostics.Contracts;
using DotNetty.Buffers;
using DotNetty.Common;
using DotNetty.Common.Utilities;
public class DefaultHttpContent : DefaultHttpObject, IHttpContent
{
readonly IByteBuffer content;
public DefaultHttpContent(IByteBuffer content)
{
Contract.Requires(content != null);
this.content = content;
}
public IByteBuffer Content => this.content;
public IByteBufferHolder Copy() => this.Replace(this.content.Copy());
public IByteBufferHolder Duplicate() => this.Replace(this.content.Duplicate());
public IByteBufferHolder RetainedDuplicate() => this.Replace(this.content.RetainedDuplicate());
public virtual IByteBufferHolder Replace(IByteBuffer buffer) => new DefaultHttpContent(buffer);
public int ReferenceCount => this.content.ReferenceCount;
public IReferenceCounted Retain()
{
this.content.Retain();
return this;
}
public IReferenceCounted Retain(int increment)
{
this.content.Retain(increment);
return this;
}
public IReferenceCounted Touch()
{
this.content.Touch();
return this;
}
public IReferenceCounted Touch(object hint)
{
this.content.Touch(hint);
return this;
}
public bool Release() => this.content.Release();
public bool Release(int decrement) => this.content.Release(decrement);
public override string ToString() => $"{StringUtil.SimpleClassName(this)} (data: {this.content}, decoderResult: {this.Result})";
}
}

Просмотреть файл

@ -0,0 +1,372 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using DotNetty.Codecs;
using DotNetty.Common.Utilities;
public class DefaultHttpHeaders : HttpHeaders
{
const int HighestInvalidValueCharMask = ~15;
internal static readonly INameValidator<ICharSequence> HttpNameValidator = new HeaderNameValidator();
internal static readonly INameValidator<ICharSequence> NotNullValidator = new NullNameValidator<ICharSequence>();
sealed class NameProcessor : IByteProcessor
{
public bool Process(byte value)
{
ValidateHeaderNameElement(value);
return true;
}
}
sealed class HeaderNameValidator : INameValidator<ICharSequence>
{
static readonly NameProcessor ByteProcessor = new NameProcessor();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ValidateName(ICharSequence name)
{
if (name == null || name.Count == 0)
{
ThrowHelper.ThrowArgumentException_HeaderName();
}
if (name is AsciiString asciiString)
{
asciiString.ForEachByte(ByteProcessor);
}
else
{
// Go through each character in the name
Debug.Assert(name != null);
// ReSharper disable once ForCanBeConvertedToForeach
// Avoid new enumerator instance
for (int index = 0; index < name.Count; ++index)
{
ValidateHeaderNameElement(name[index]);
}
}
}
}
readonly DefaultHeaders<AsciiString, ICharSequence> headers;
public DefaultHttpHeaders() : this(true)
{
}
public DefaultHttpHeaders(bool validate) : this(validate, NameValidator(validate))
{
}
protected DefaultHttpHeaders(bool validate, INameValidator<ICharSequence> nameValidator)
: this(new DefaultHeaders<AsciiString, ICharSequence>(AsciiString.CaseInsensitiveHasher,
ValueConverter(validate), nameValidator))
{
}
protected DefaultHttpHeaders(DefaultHeaders<AsciiString, ICharSequence> headers)
{
this.headers = headers;
}
public override HttpHeaders Add(HttpHeaders httpHeaders)
{
if (httpHeaders is DefaultHttpHeaders defaultHttpHeaders)
{
this.headers.Add(defaultHttpHeaders.headers);
return this;
}
return base.Add(httpHeaders);
}
public override HttpHeaders Set(HttpHeaders httpHeaders)
{
if (httpHeaders is DefaultHttpHeaders defaultHttpHeaders)
{
this.headers.Set(defaultHttpHeaders.headers);
return this;
}
return base.Set(httpHeaders);
}
public override HttpHeaders Add(AsciiString name, object value)
{
this.headers.AddObject(name, value);
return this;
}
public override HttpHeaders AddInt(AsciiString name, int value)
{
this.headers.AddInt(name, value);
return this;
}
public override HttpHeaders AddShort(AsciiString name, short value)
{
this.headers.AddShort(name, value);
return this;
}
public override HttpHeaders Remove(AsciiString name)
{
this.headers.Remove(name);
return this;
}
public override HttpHeaders Set(AsciiString name, object value)
{
this.headers.SetObject(name, value);
return this;
}
public override HttpHeaders Set(AsciiString name, IEnumerable<object> values)
{
this.headers.SetObject(name, values);
return this;
}
public override HttpHeaders SetInt(AsciiString name, int value)
{
this.headers.SetInt(name, value);
return this;
}
public override HttpHeaders SetShort(AsciiString name, short value)
{
this.headers.SetShort(name, value);
return this;
}
public override HttpHeaders Clear()
{
this.headers.Clear();
return this;
}
public override bool TryGet(AsciiString name, out ICharSequence value) => this.headers.TryGet(name, out value);
public override bool TryGetInt(AsciiString name, out int value) => this.headers.TryGetInt(name, out value);
public override int GetInt(AsciiString name, int defaultValue) => this.headers.GetInt(name, defaultValue);
public override bool TryGetShort(AsciiString name, out short value) => this.headers.TryGetShort(name, out value);
public override short GetShort(AsciiString name, short defaultValue) => this.headers.GetShort(name, defaultValue);
public override bool TryGetTimeMillis(AsciiString name, out long value) => this.headers.TryGetTimeMillis(name, out value);
public override long GetTimeMillis(AsciiString name, long defaultValue) => this.headers.GetTimeMillis(name, defaultValue);
public override IList<ICharSequence> GetAll(AsciiString name) => this.headers.GetAll(name);
public override IEnumerable<ICharSequence> ValueCharSequenceIterator(AsciiString name) => this.headers.ValueIterator(name);
public override IList<HeaderEntry<AsciiString, ICharSequence>> Entries()
{
if (this.IsEmpty)
{
return ImmutableList<HeaderEntry<AsciiString, ICharSequence>>.Empty;
}
var entriesConverted = new List<HeaderEntry<AsciiString, ICharSequence>>(this.headers.Size);
foreach(HeaderEntry<AsciiString, ICharSequence> entry in this)
{
entriesConverted.Add(entry);
}
return entriesConverted;
}
public override IEnumerator<HeaderEntry<AsciiString, ICharSequence>> GetEnumerator() => this.headers.GetEnumerator();
public override bool Contains(AsciiString name) => this.headers.Contains(name);
public override bool IsEmpty => this.headers.IsEmpty;
public override int Size => this.headers.Size;
public override bool Contains(AsciiString name, ICharSequence value, bool ignoreCase) =>
this.headers.Contains(name, value,
ignoreCase ? AsciiString.CaseInsensitiveHasher : AsciiString.CaseSensitiveHasher);
public override ISet<AsciiString> Names() => this.headers.Names();
public override bool Equals(object obj) => obj is DefaultHttpHeaders other
&& this.headers.Equals(other.headers, AsciiString.CaseSensitiveHasher);
public override int GetHashCode() => this.headers.HashCode(AsciiString.CaseSensitiveHasher);
public override HttpHeaders Copy() => new DefaultHttpHeaders(this.headers.Copy());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void ValidateHeaderNameElement(byte value)
{
switch (value)
{
case 0x00:
case 0x09: //'\t':
case 0x0a: //'\n':
case 0x0b:
case 0x0c: //'\f':
case 0x0d: //'\r':
case 0x20: //' ':
case 0x2c: //',':
case 0x3a: //':':
case 0x3b: //';':
case 0x3d: //'=':
ThrowHelper.ThrowArgumentException_HeaderValue(value);
break;
default:
// Check to see if the character is not an ASCII character, or invalid
if (value > 127)
{
ThrowHelper.ThrowArgumentException_HeaderValueNonAscii(value);
}
break;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void ValidateHeaderNameElement(char value)
{
switch (value)
{
case '\x00':
case '\t':
case '\n':
case '\x0b':
case '\f':
case '\r':
case ' ':
case ',':
case ':':
case ';':
case '=':
ThrowHelper.ThrowArgumentException_HeaderValue(value);
break;
default:
// Check to see if the character is not an ASCII character, or invalid
if (value > 127)
{
ThrowHelper.ThrowArgumentException_HeaderValueNonAscii(value);
}
break;
}
}
protected static IValueConverter<ICharSequence> ValueConverter(bool validate) =>
validate ? DefaultHeaderValueConverterAndValidator : DefaultHeaderValueConverter;
protected static INameValidator<ICharSequence> NameValidator(bool validate) =>
validate ? HttpNameValidator : NotNullValidator;
static readonly HeaderValueConverter DefaultHeaderValueConverter = new HeaderValueConverter();
class HeaderValueConverter : CharSequenceValueConverter
{
public override ICharSequence ConvertObject(object value)
{
if (value is ICharSequence seq)
{
return seq;
}
if (value is DateTime time)
{
return new StringCharSequence(DateFormatter.Format(time));
}
return new StringCharSequence(value.ToString());
}
}
static readonly HeaderValueConverterAndValidator DefaultHeaderValueConverterAndValidator = new HeaderValueConverterAndValidator();
sealed class HeaderValueConverterAndValidator : HeaderValueConverter
{
public override ICharSequence ConvertObject(object value)
{
ICharSequence seq = base.ConvertObject(value);
int state = 0;
// Start looping through each of the character
// ReSharper disable once ForCanBeConvertedToForeach
// Avoid enumerator allocation
for (int index = 0; index < seq.Count; index++)
{
state = ValidateValueChar(state, seq[index]);
}
if (state != 0)
{
ThrowHelper.ThrowArgumentException_HeaderValueEnd(seq);
}
return seq;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int ValidateValueChar(int state, char character)
{
// State:
// 0: Previous character was neither CR nor LF
// 1: The previous character was CR
// 2: The previous character was LF
if ((character & HighestInvalidValueCharMask) == 0)
{
// Check the absolutely prohibited characters.
switch (character)
{
case '\x00': // NULL
ThrowHelper.ThrowArgumentException_HeaderValueNullChar();
break;
case '\x0b': // Vertical tab
ThrowHelper.ThrowArgumentException_HeaderValueVerticalTabChar();
break;
case '\f':
ThrowHelper.ThrowArgumentException_HeaderValueFormFeed();
break;
}
}
// Check the CRLF (HT | SP) pattern
switch (state)
{
case 0:
switch (character)
{
case '\r':
return 1;
case '\n':
return 2;
}
break;
case 1:
switch (character)
{
case '\n':
return 2;
default:
ThrowHelper.ThrowArgumentException_NewLineAfterLineFeed();
break;
}
break;
case 2:
switch (character)
{
case '\t':
case ' ':
return 0;
default:
ThrowHelper.ThrowArgumentException_TabAndSpaceAfterLineFeed();
break;
}
break;
}
return state;
}
}
}
}

Просмотреть файл

@ -0,0 +1,73 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
// ReSharper disable ConvertToAutoPropertyWithPrivateSetter
namespace DotNetty.Codecs.Http
{
using System.Diagnostics.Contracts;
public abstract class DefaultHttpMessage : DefaultHttpObject, IHttpMessage
{
const int HashCodePrime = 31;
HttpVersion version;
readonly HttpHeaders headers;
protected DefaultHttpMessage(HttpVersion version) : this(version, true, false)
{
}
protected DefaultHttpMessage(HttpVersion version, bool validateHeaders, bool singleFieldHeaders)
{
Contract.Requires(version != null);
this.version = version;
this.headers = singleFieldHeaders
? new CombinedHttpHeaders(validateHeaders)
: new DefaultHttpHeaders(validateHeaders);
}
protected DefaultHttpMessage(HttpVersion version, HttpHeaders headers)
{
Contract.Requires(version != null);
Contract.Requires(headers != null);
this.version = version;
this.headers = headers;
}
public HttpHeaders Headers => this.headers;
public HttpVersion ProtocolVersion => this.version;
public override int GetHashCode()
{
int result = 1;
result = HashCodePrime * result + this.headers.GetHashCode();
// ReSharper disable once NonReadonlyMemberInGetHashCode
result = HashCodePrime * result + this.version.GetHashCode();
result = HashCodePrime * result + base.GetHashCode();
return result;
}
public override bool Equals(object obj)
{
if (!(obj is DefaultHttpMessage other))
{
return false;
}
return this.headers.Equals(other.headers)
&& this.version.Equals(other.version)
&& base.Equals(obj);
}
public IHttpMessage SetProtocolVersion(HttpVersion value)
{
Contract.Requires(value != null);
this.version = value;
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System.Diagnostics.Contracts;
public class DefaultHttpObject : IHttpObject
{
const int HashCodePrime = 31;
DecoderResult decoderResult = DecoderResult.Success;
protected DefaultHttpObject()
{
}
public DecoderResult Result
{
get => this.decoderResult;
set
{
Contract.Requires(value != null);
this.decoderResult = value;
}
}
public override int GetHashCode()
{
int result = 1;
// ReSharper disable once NonReadonlyMemberInGetHashCode
result = HashCodePrime * result + this.decoderResult.GetHashCode();
return result;
}
public override bool Equals(object obj)
{
if (!(obj is DefaultHttpObject other))
{
return false;
}
return this.decoderResult.Equals(other.decoderResult);
}
}
}

Просмотреть файл

@ -0,0 +1,88 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoPropertyWithPrivateSetter
namespace DotNetty.Codecs.Http
{
using System;
using System.Diagnostics.Contracts;
using System.Text;
public class DefaultHttpRequest : DefaultHttpMessage, IHttpRequest
{
const int HashCodePrime = 31;
HttpMethod method;
string uri;
public DefaultHttpRequest(HttpVersion httpVersion, HttpMethod method, string uri)
: this(httpVersion, method, uri, true)
{
}
public DefaultHttpRequest(HttpVersion version, HttpMethod method, string uri, bool validateHeaders)
: base(version, validateHeaders, false)
{
Contract.Requires(method != null);
Contract.Requires(uri != null);
this.method = method;
this.uri = uri;
}
public DefaultHttpRequest(HttpVersion version, HttpMethod method, string uri, HttpHeaders headers)
: base(version, headers)
{
Contract.Requires(method != null);
Contract.Requires(uri != null);
this.method = method;
this.uri = uri;
}
public HttpMethod Method => this.method;
public string Uri => this.uri;
public IHttpRequest SetMethod(HttpMethod value)
{
Contract.Requires(value != null);
this.method = value;
return this;
}
public IHttpRequest SetUri(string value)
{
Contract.Requires(value != null);
this.uri = value;
return this;
}
// ReSharper disable NonReadonlyMemberInGetHashCode
public override int GetHashCode()
{
int result = 1;
result = HashCodePrime * result + this.method.GetHashCode();
result = HashCodePrime * result + this.uri.GetHashCode();
result = HashCodePrime * result + base.GetHashCode();
return result;
}
// ReSharper restore NonReadonlyMemberInGetHashCode
public override bool Equals(object obj)
{
if (!(obj is DefaultHttpRequest other))
{
return false;
}
return this.method.Equals(other.method)
&& this.uri.Equals(other.uri, StringComparison.OrdinalIgnoreCase)
&& base.Equals(obj);
}
public override string ToString() => HttpMessageUtil.AppendRequest(new StringBuilder(256), this).ToString();
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWithPrivateSetter
namespace DotNetty.Codecs.Http
{
using System.Diagnostics.Contracts;
using System.Text;
public class DefaultHttpResponse : DefaultHttpMessage, IHttpResponse
{
HttpResponseStatus status;
public DefaultHttpResponse(HttpVersion version, HttpResponseStatus status, bool validateHeaders = true, bool singleFieldHeaders = false)
: base(version, validateHeaders, singleFieldHeaders)
{
Contract.Requires(status != null);
this.status = status;
}
public DefaultHttpResponse(HttpVersion version, HttpResponseStatus status, HttpHeaders headers)
: base(version, headers)
{
Contract.Requires(status != null);
this.status = status;
}
public HttpResponseStatus Status => this.status;
public IHttpResponse SetStatus(HttpResponseStatus value)
{
Contract.Requires(value != null);
this.status = value;
return this;
}
public override string ToString() => HttpMessageUtil.AppendResponse(new StringBuilder(256), this).ToString();
}
}

Просмотреть файл

@ -0,0 +1,84 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
public class DefaultLastHttpContent : DefaultHttpContent, ILastHttpContent
{
readonly HttpHeaders trailingHeaders;
readonly bool validateHeaders;
public DefaultLastHttpContent() : this(Unpooled.Buffer(0), true)
{
}
public DefaultLastHttpContent(IByteBuffer content) : this(content, true)
{
}
public DefaultLastHttpContent(IByteBuffer content, bool validateHeaders)
: base(content)
{
this.trailingHeaders = new TrailingHttpHeaders(validateHeaders);
this.validateHeaders = validateHeaders;
}
public HttpHeaders TrailingHeaders => this.trailingHeaders;
public override IByteBufferHolder Replace(IByteBuffer buffer)
{
var dup = new DefaultLastHttpContent(this.Content, this.validateHeaders);
dup.TrailingHeaders.Set(this.trailingHeaders);
return dup;
}
public override string ToString()
{
var buf = new StringBuilder(base.ToString());
buf.Append(StringUtil.Newline);
this.AppendHeaders(buf);
// Remove the last newline.
buf.Length = buf.Length - StringUtil.Newline.Length;
return buf.ToString();
}
void AppendHeaders(StringBuilder buf)
{
foreach (HeaderEntry<AsciiString, ICharSequence> e in this.trailingHeaders)
{
buf.Append($"{e.Key}: {e.Value}{StringUtil.Newline}");
}
}
sealed class TrailerNameValidator : INameValidator<ICharSequence>
{
public void ValidateName(ICharSequence name)
{
DefaultHttpHeaders.HttpNameValidator.ValidateName(name);
if (HttpHeaderNames.ContentLength.ContentEqualsIgnoreCase(name)
|| HttpHeaderNames.TransferEncoding.ContentEqualsIgnoreCase(name)
|| HttpHeaderNames.Trailer.ContentEqualsIgnoreCase(name))
{
ThrowHelper.ThrowArgumentException_TrailingHeaderName(name);
}
}
}
sealed class TrailingHttpHeaders : DefaultHttpHeaders
{
static readonly TrailerNameValidator TrailerNameValidator = new TrailerNameValidator();
public TrailingHttpHeaders(bool validate)
: base(validate, validate ? TrailerNameValidator : NotNullValidator)
{
}
}
}
}

Просмотреть файл

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="NuGet">
<TargetFrameworks>netstandard1.3;net45</TargetFrameworks>
<IsPackable>true</IsPackable>
<PackageId>DotNetty.Codecs.Http</PackageId>
<Description>Http codec for DotNetty</Description>
<Copyright>Copyright © Microsoft Corporation</Copyright>
<AssemblyTitle>DotNetty: Http codec</AssemblyTitle>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>0.4.7</VersionPrefix>
<Authors>Microsoft Azure</Authors>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>DotNetty.Codecs.Http</AssemblyName>
<AssemblyOriginatorKeyFile>../../DotNetty.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageTags>socket;tcp;protocol;netty;dotnetty;network;http</PackageTags>
<PackageProjectUrl>https://github.com/Azure/DotNetty/</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/Azure/DotNetty/blob/master/LICENSE.txt</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/Azure/DotNetty/</RepositoryUrl>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.3' ">1.6.1</NetStandardImplicitPackageVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\shared\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotNetty.Common\DotNetty.Common.csproj" />
<ProjectReference Include="..\DotNetty.Buffers\DotNetty.Buffers.csproj" />
<ProjectReference Include="..\DotNetty.Codecs\DotNetty.Codecs.csproj" />
<ProjectReference Include="..\DotNetty.Handlers\DotNetty.Handlers.csproj" />
<ProjectReference Include="..\DotNetty.Transport\DotNetty.Transport.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.Diagnostics.Contracts" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,85 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using DotNetty.Common.Utilities;
public class EmptyHttpHeaders : HttpHeaders
{
static readonly IEnumerator<HeaderEntry<AsciiString, ICharSequence>> EmptryEnumerator =
Enumerable.Empty<HeaderEntry<AsciiString, ICharSequence>>().GetEnumerator();
public static readonly EmptyHttpHeaders Default = new EmptyHttpHeaders();
protected EmptyHttpHeaders()
{
}
public override bool TryGet(AsciiString name, out ICharSequence value)
{
value = default(ICharSequence);
return false;
}
public override bool TryGetInt(AsciiString name, out int value)
{
value = default(int);
return false;
}
public override int GetInt(AsciiString name, int defaultValue) => defaultValue;
public override bool TryGetShort(AsciiString name, out short value)
{
value = default(short);
return false;
}
public override short GetShort(AsciiString name, short defaultValue) => defaultValue;
public override bool TryGetTimeMillis(AsciiString name, out long value)
{
value = default(long);
return false;
}
public override long GetTimeMillis(AsciiString name, long defaultValue) => defaultValue;
public override IList<ICharSequence> GetAll(AsciiString name) => ImmutableList<ICharSequence>.Empty;
public override IList<HeaderEntry<AsciiString, ICharSequence>> Entries() => ImmutableList<HeaderEntry<AsciiString, ICharSequence>>.Empty;
public override bool Contains(AsciiString name) => false;
public override bool IsEmpty => true;
public override int Size => 0;
public override ISet<AsciiString> Names() => ImmutableHashSet<AsciiString>.Empty;
public override HttpHeaders AddInt(AsciiString name, int value) => throw new NotSupportedException("read only");
public override HttpHeaders AddShort(AsciiString name, short value) => throw new NotSupportedException("read only");
public override HttpHeaders Set(AsciiString name, object value) => throw new NotSupportedException("read only");
public override HttpHeaders Set(AsciiString name, IEnumerable<object> values) => throw new NotSupportedException("read only");
public override HttpHeaders SetInt(AsciiString name, int value) => throw new NotSupportedException("read only");
public override HttpHeaders SetShort(AsciiString name, short value) => throw new NotSupportedException("read only");
public override HttpHeaders Remove(AsciiString name) => throw new NotSupportedException("read only");
public override HttpHeaders Clear() => throw new NotSupportedException("read only");
public override HttpHeaders Add(AsciiString name, object value) => throw new NotSupportedException("read only");
public override IEnumerator<HeaderEntry<AsciiString, ICharSequence>> GetEnumerator() => EmptryEnumerator;
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using DotNetty.Buffers;
using DotNetty.Common;
public sealed class EmptyLastHttpContent : ILastHttpContent
{
public static readonly EmptyLastHttpContent Default = new EmptyLastHttpContent();
EmptyLastHttpContent()
{
this.Content = Unpooled.Empty;
}
public DecoderResult Result
{
get => DecoderResult.Success;
set => throw new NotSupportedException("read only");
}
public int ReferenceCount => 1;
public IReferenceCounted Retain() => this;
public IReferenceCounted Retain(int increment) => this;
public IReferenceCounted Touch() => this;
public IReferenceCounted Touch(object hint) => this;
public bool Release() => false;
public bool Release(int decrement) => false;
public IByteBuffer Content { get; }
public IByteBufferHolder Copy() => this;
public IByteBufferHolder Duplicate() => this;
public IByteBufferHolder RetainedDuplicate() => this;
public IByteBufferHolder Replace(IByteBuffer content) => new DefaultLastHttpContent(content);
public HttpHeaders TrailingHeaders => EmptyHttpHeaders.Default;
public override string ToString() => nameof(EmptyLastHttpContent);
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using DotNetty.Buffers;
using DotNetty.Handlers.Streams;
public class HttpChunkedInput : IChunkedInput<IHttpContent>
{
readonly IChunkedInput<IByteBuffer> input;
readonly ILastHttpContent lastHttpContent;
bool sentLastChunk;
public HttpChunkedInput(IChunkedInput<IByteBuffer> input)
{
this.input = input;
this.lastHttpContent = EmptyLastHttpContent.Default;
}
public HttpChunkedInput(IChunkedInput<IByteBuffer> input, ILastHttpContent lastHttpContent)
{
this.input = input;
this.lastHttpContent = lastHttpContent;
}
public bool IsEndOfInput => this.input.IsEndOfInput && this.sentLastChunk;
public void Close() => this.input.Close();
public IHttpContent ReadChunk(IByteBufferAllocator allocator)
{
if (this.input.IsEndOfInput)
{
if (this.sentLastChunk)
{
return null;
}
// Send last chunk for this input
this.sentLastChunk = true;
return this.lastHttpContent;
}
else
{
IByteBuffer buf = this.input.ReadChunk(allocator);
return buf == null ? null : new DefaultHttpContent(buf);
}
}
public long Length => this.input.Length;
public long Progress => this.input.Progress;
}
}

Просмотреть файл

@ -0,0 +1,259 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System.Collections.Generic;
using System.Threading;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class HttpClientCodec : CombinedChannelDuplexHandler<HttpResponseDecoder, HttpRequestEncoder>,
HttpClientUpgradeHandler.ISourceCodec
{
// A queue that is used for correlating a request and a response.
readonly Queue<HttpMethod> queue = new Queue<HttpMethod>();
readonly bool parseHttpAfterConnectRequest;
// If true, decoding stops (i.e. pass-through)
bool done;
long requestResponseCounter;
readonly bool failOnMissingResponse;
public HttpClientCodec() : this(4096, 8192, 8192, false)
{
}
public HttpClientCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize)
: this(maxInitialLineLength, maxHeaderSize, maxChunkSize, false)
{
}
public HttpClientCodec(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool failOnMissingResponse)
: this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, true)
{
}
public HttpClientCodec(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool failOnMissingResponse,
bool validateHeaders)
: this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, validateHeaders, false)
{
}
public HttpClientCodec(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool failOnMissingResponse,
bool validateHeaders, bool parseHttpAfterConnectRequest)
{
this.Init(new Decoder(this, maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders), new Encoder(this));
this.failOnMissingResponse = failOnMissingResponse;
this.parseHttpAfterConnectRequest = parseHttpAfterConnectRequest;
}
public HttpClientCodec(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool failOnMissingResponse,
bool validateHeaders, int initialBufferSize)
: this(maxInitialLineLength, maxHeaderSize, maxChunkSize, failOnMissingResponse, validateHeaders, initialBufferSize, false)
{
}
public HttpClientCodec(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool failOnMissingResponse,
bool validateHeaders, int initialBufferSize, bool parseHttpAfterConnectRequest)
{
this.Init(new Decoder(this, maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize), new Encoder(this));
this.parseHttpAfterConnectRequest = parseHttpAfterConnectRequest;
this.failOnMissingResponse = failOnMissingResponse;
}
public void PrepareUpgradeFrom(IChannelHandlerContext ctx) => ((Encoder)this.OutboundHandler).Upgraded = true;
public void UpgradeFrom(IChannelHandlerContext ctx)
{
IChannelPipeline p = ctx.Channel.Pipeline;
p.Remove(this);
}
public bool SingleDecode
{
get => this.InboundHandler.SingleDecode;
set => this.InboundHandler.SingleDecode = value;
}
sealed class Encoder : HttpRequestEncoder
{
readonly HttpClientCodec clientCodec;
internal bool Upgraded;
public Encoder(HttpClientCodec clientCodec)
{
this.clientCodec = clientCodec;
}
protected override void Encode(IChannelHandlerContext context, object message, List<object> output)
{
if (this.Upgraded)
{
output.Add(ReferenceCountUtil.Retain(message));
return;
}
if (message is IHttpRequest request && !this.clientCodec.done)
{
this.clientCodec.queue.Enqueue(request.Method);
}
base.Encode(context, message, output);
if (this.clientCodec.failOnMissingResponse && !this.clientCodec.done)
{
// check if the request is chunked if so do not increment
if (message is ILastHttpContent)
{
// increment as its the last chunk
Interlocked.Increment(ref this.clientCodec.requestResponseCounter);
}
}
}
}
sealed class Decoder : HttpResponseDecoder
{
readonly HttpClientCodec clientCodec;
internal Decoder(HttpClientCodec clientCodec, int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders)
{
this.clientCodec = clientCodec;
}
internal Decoder(HttpClientCodec clientCodec, int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders, int initialBufferSize)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize)
{
this.clientCodec = clientCodec;
}
protected override void Decode(IChannelHandlerContext context, IByteBuffer buffer, List<object> output)
{
if (this.clientCodec.done)
{
int readable = this.ActualReadableBytes;
if (readable == 0)
{
// if non is readable just return null
// https://github.com/netty/netty/issues/1159
return;
}
output.Add(buffer.ReadBytes(readable));
}
else
{
int oldSize = output.Count;
base.Decode(context, buffer, output);
if (this.clientCodec.failOnMissingResponse)
{
int size = output.Count;
for (int i = oldSize; i < size; i++)
{
this.Decrement(output[i]);
}
}
}
}
void Decrement(object msg)
{
if (ReferenceEquals(null, msg))
{
return;
}
// check if it's an Header and its transfer encoding is not chunked.
if (msg is ILastHttpContent)
{
Interlocked.Decrement(ref this.clientCodec.requestResponseCounter);
}
}
protected override bool IsContentAlwaysEmpty(IHttpMessage msg)
{
int statusCode = ((IHttpResponse)msg).Status.Code;
if (statusCode == 100 || statusCode == 101)
{
// 100-continue and 101 switching protocols response should be excluded from paired comparison.
// Just delegate to super method which has all the needed handling.
return base.IsContentAlwaysEmpty(msg);
}
// Get the getMethod of the HTTP request that corresponds to the
// current response.
HttpMethod method = this.clientCodec.queue.Dequeue();
char firstChar = method.AsciiName[0];
switch (firstChar)
{
case 'H':
// According to 4.3, RFC2616:
// All responses to the HEAD request getMethod MUST NOT include a
// message-body, even though the presence of entity-header fields
// might lead one to believe they do.
if (HttpMethod.Head.Equals(method))
{
return true;
// The following code was inserted to work around the servers
// that behave incorrectly. It has been commented out
// because it does not work with well behaving servers.
// Please note, even if the 'Transfer-Encoding: chunked'
// header exists in the HEAD response, the response should
// have absolutely no content.
//
// Interesting edge case:
// Some poorly implemented servers will send a zero-byte
// chunk if Transfer-Encoding of the response is 'chunked'.
//
// return !msg.isChunked();
}
break;
case 'C':
// Successful CONNECT request results in a response with empty body.
if (statusCode == 200)
{
if (HttpMethod.Connect.Equals(method))
{
// Proxy connection established - Parse HTTP only if configured by parseHttpAfterConnectRequest,
// else pass through.
if (!this.clientCodec.parseHttpAfterConnectRequest)
{
this.clientCodec.done = true;
this.clientCodec.queue.Clear();
}
return true;
}
}
break;
}
return base.IsContentAlwaysEmpty(msg);
}
public override void ChannelInactive(IChannelHandlerContext ctx)
{
base.ChannelInactive(ctx);
if (this.clientCodec.failOnMissingResponse)
{
long missingResponses = Interlocked.Read(ref this.clientCodec.requestResponseCounter);
if (missingResponses > 0)
{
ctx.FireExceptionCaught(new PrematureChannelClosureException(
$"channel gone inactive with {missingResponses} missing response(s)"));
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,197 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
// Note HttpObjectAggregator already implements IChannelHandler
public class HttpClientUpgradeHandler : HttpObjectAggregator
{
// User events that are fired to notify about upgrade status.
public enum UpgradeEvent
{
// The Upgrade request was sent to the server.
UpgradeIssued,
// The Upgrade to the new protocol was successful.
UpgradeSuccessful,
// The Upgrade was unsuccessful due to the server not issuing
// with a 101 Switching Protocols response.
UpgradeRejected
}
public interface ISourceCodec
{
// Removes or disables the encoder of this codec so that the {@link UpgradeCodec} can send an initial greeting
// (if any).
void PrepareUpgradeFrom(IChannelHandlerContext ctx);
// Removes this codec (i.e. all associated handlers) from the pipeline.
void UpgradeFrom(IChannelHandlerContext ctx);
}
public interface IUpgradeCodec
{
// Returns the name of the protocol supported by this codec, as indicated by the {@code 'UPGRADE'} header.
ICharSequence Protocol { get; }
// Sets any protocol-specific headers required to the upgrade request. Returns the names of
// all headers that were added. These headers will be used to populate the CONNECTION header.
ICollection<ICharSequence> SetUpgradeHeaders(IChannelHandlerContext ctx, IHttpRequest upgradeRequest);
///
// Performs an HTTP protocol upgrade from the source codec. This method is responsible for
// adding all handlers required for the new protocol.
//
// ctx the context for the current handler.
// upgradeResponse the 101 Switching Protocols response that indicates that the server
// has switched to this protocol.
void UpgradeTo(IChannelHandlerContext ctx, IFullHttpResponse upgradeResponse);
}
readonly ISourceCodec sourceCodec;
readonly IUpgradeCodec upgradeCodec;
bool upgradeRequested;
public HttpClientUpgradeHandler(ISourceCodec sourceCodec, IUpgradeCodec upgradeCodec, int maxContentLength)
: base(maxContentLength)
{
Contract.Requires(sourceCodec != null);
Contract.Requires(upgradeCodec != null);
this.sourceCodec = sourceCodec;
this.upgradeCodec = upgradeCodec;
}
public override Task WriteAsync(IChannelHandlerContext context, object message)
{
if (!(message is IHttpRequest))
{
return context.WriteAsync(message);
}
if (this.upgradeRequested)
{
return TaskEx.FromException(new InvalidOperationException("Attempting to write HTTP request with upgrade in progress"));
}
this.upgradeRequested = true;
this.SetUpgradeRequestHeaders(context, (IHttpRequest)message);
// Continue writing the request.
Task task = context.WriteAsync(message);
// Notify that the upgrade request was issued.
context.FireUserEventTriggered(UpgradeEvent.UpgradeIssued);
// Now we wait for the next HTTP response to see if we switch protocols.
return task;
}
protected override void Decode(IChannelHandlerContext context, IHttpObject message, List<object> output)
{
IFullHttpResponse response = null;
try
{
if (!this.upgradeRequested)
{
throw new InvalidOperationException("Read HTTP response without requesting protocol switch");
}
if (message is IHttpResponse rep)
{
if (!HttpResponseStatus.SwitchingProtocols.Equals(rep.Status))
{
// The server does not support the requested protocol, just remove this handler
// and continue processing HTTP.
// NOTE: not releasing the response since we're letting it propagate to the
// next handler.
context.FireUserEventTriggered(UpgradeEvent.UpgradeRejected);
RemoveThisHandler(context);
context.FireChannelRead(rep);
return;
}
}
if (message is IFullHttpResponse fullRep)
{
response = fullRep;
// Need to retain since the base class will release after returning from this method.
response.Retain();
output.Add(response);
}
else
{
// Call the base class to handle the aggregation of the full request.
base.Decode(context, message, output);
if (output.Count == 0)
{
// The full request hasn't been created yet, still awaiting more data.
return;
}
Debug.Assert(output.Count == 1);
response = (IFullHttpResponse)output[0];
}
if (response.Headers.TryGet(HttpHeaderNames.Upgrade, out ICharSequence upgradeHeader) && !AsciiString.ContentEqualsIgnoreCase(this.upgradeCodec.Protocol, upgradeHeader))
{
throw new InvalidOperationException($"Switching Protocols response with unexpected UPGRADE protocol: {upgradeHeader}");
}
// Upgrade to the new protocol.
this.sourceCodec.PrepareUpgradeFrom(context);
this.upgradeCodec.UpgradeTo(context, response);
// Notify that the upgrade to the new protocol completed successfully.
context.FireUserEventTriggered(UpgradeEvent.UpgradeSuccessful);
// We guarantee UPGRADE_SUCCESSFUL event will be arrived at the next handler
// before http2 setting frame and http response.
this.sourceCodec.UpgradeFrom(context);
// We switched protocols, so we're done with the upgrade response.
// Release it and clear it from the output.
response.Release();
output.Clear();
RemoveThisHandler(context);
}
catch (Exception exception)
{
ReferenceCountUtil.Release(response);
context.FireExceptionCaught(exception);
RemoveThisHandler(context);
}
}
static void RemoveThisHandler(IChannelHandlerContext ctx) => ctx.Channel.Pipeline.Remove(ctx.Name);
void SetUpgradeRequestHeaders(IChannelHandlerContext ctx, IHttpRequest request)
{
// Set the UPGRADE header on the request.
request.Headers.Set(HttpHeaderNames.Upgrade, this.upgradeCodec.Protocol);
// Add all protocol-specific headers to the request.
var connectionParts = new List<ICharSequence>(2);
connectionParts.AddRange(this.upgradeCodec.SetUpgradeHeaders(ctx, request));
// Set the CONNECTION header from the set of all protocol-specific headers that were added.
var builder = new StringBuilder();
foreach (ICharSequence part in connectionParts)
{
builder.Append(part);
builder.Append(',');
}
builder.Append(HttpHeaderValues.Upgrade);
request.Headers.Set(HttpHeaderNames.Connection, new StringCharSequence(builder.ToString()));
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System.Text;
using DotNetty.Buffers;
public static class HttpConstants
{
// Horizontal space
public const byte HorizontalSpace = 32;
// Horizontal tab
public const byte HorizontalTab = 9;
// Carriage return
public const byte CarriageReturn = 13;
// Equals '='
public const byte EqualsSign = 61;
// Line feed character
public const byte LineFeed = 10;
// Colon ':'
public const byte Colon = 58;
// Semicolon ';'
public const byte Semicolon = 59;
// Comma ','
public const byte Comma = 44;
// Double quote '"'
public const byte DoubleQuote = (byte)'"';
// Default character set (UTF-8)
public static readonly Encoding DefaultEncoding = Encoding.UTF8;
// Horizontal space in char
public static readonly char HorizontalSpaceChar = (char)HorizontalSpace;
// For HttpObjectEncoder
internal static readonly int CrlfShort = (CarriageReturn << 8) | LineFeed;
internal static readonly int ZeroCrlfMedium = ('0' << 16) | CrlfShort;
internal static readonly byte[] ZeroCrlfCrlf = { (byte)'0', CarriageReturn, LineFeed, CarriageReturn, LineFeed };
internal static readonly IByteBuffer CrlfBuf = Unpooled.UnreleasableBuffer(Unpooled.WrappedBuffer(new[] { CarriageReturn, LineFeed }));
internal static readonly IByteBuffer ZeroCrlfCrlfBuf = Unpooled.UnreleasableBuffer(Unpooled.WrappedBuffer(ZeroCrlfCrlf));
}
}

Просмотреть файл

@ -0,0 +1,138 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Diagnostics.Contracts;
using DotNetty.Codecs.Compression;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Embedded;
public class HttpContentCompressor : HttpContentEncoder
{
static readonly AsciiString GZipString = AsciiString.Cached("gzip");
static readonly AsciiString DeflateString = AsciiString.Cached("deflate");
readonly int compressionLevel;
readonly int windowBits;
readonly int memLevel;
IChannelHandlerContext handlerContext;
public HttpContentCompressor() : this(6)
{
}
public HttpContentCompressor(int compressionLevel) : this(compressionLevel, 15, 8)
{
}
public HttpContentCompressor(int compressionLevel, int windowBits, int memLevel)
{
Contract.Requires(compressionLevel >= 0 && compressionLevel <= 9);
Contract.Requires(windowBits >= 9 && windowBits <= 15);
Contract.Requires(memLevel >= 1 && memLevel <= 9);
this.compressionLevel = compressionLevel;
this.windowBits = windowBits;
this.memLevel = memLevel;
}
public override void HandlerAdded(IChannelHandlerContext context) => this.handlerContext = context;
protected override Result BeginEncode(IHttpResponse headers, ICharSequence acceptEncoding)
{
if (headers.Headers.Contains(HttpHeaderNames.ContentEncoding))
{
// Content-Encoding was set, either as something specific or as the IDENTITY encoding
// Therefore, we should NOT encode here
return null;
}
ZlibWrapper? wrapper = this.DetermineWrapper(acceptEncoding);
if (wrapper == null)
{
return null;
}
ICharSequence targetContentEncoding;
switch (wrapper.Value)
{
case ZlibWrapper.Gzip:
targetContentEncoding = GZipString;
break;
case ZlibWrapper.Zlib:
targetContentEncoding = DeflateString;
break;
default:
throw new CodecException($"{wrapper.Value} not supported, only Gzip and Zlib are allowed.");
}
return new Result(targetContentEncoding,
new EmbeddedChannel(
this.handlerContext.Channel.Id,
this.handlerContext.Channel.Metadata.HasDisconnect,
this.handlerContext.Channel.Configuration,
ZlibCodecFactory.NewZlibEncoder(
wrapper.Value, this.compressionLevel, this.windowBits, this.memLevel)));
}
protected internal ZlibWrapper? DetermineWrapper(ICharSequence acceptEncoding)
{
float starQ = -1.0f;
float gzipQ = -1.0f;
float deflateQ = -1.0f;
ICharSequence[] parts = CharUtil.Split(acceptEncoding, ',');
foreach (ICharSequence encoding in parts)
{
float q = 1.0f;
int equalsPos = encoding.IndexOf('=');
if (equalsPos != -1)
{
try
{
q = float.Parse(encoding.ToString(equalsPos + 1));
}
catch (FormatException)
{
// Ignore encoding
q = 0.0f;
}
}
if (CharUtil.Contains(encoding, '*'))
{
starQ = q;
}
else if (AsciiString.Contains(encoding, GZipString) && q > gzipQ)
{
gzipQ = q;
}
else if (AsciiString.Contains(encoding, DeflateString) && q > deflateQ)
{
deflateQ = q;
}
}
if (gzipQ > 0.0f || deflateQ > 0.0f)
{
return gzipQ >= deflateQ ? ZlibWrapper.Gzip : ZlibWrapper.Zlib;
}
if (starQ > 0.0f)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
if (gzipQ == -1.0f)
{
return ZlibWrapper.Gzip;
}
if (deflateQ == -1.0f)
{
return ZlibWrapper.Zlib;
}
// ReSharper restore CompareOfFloatsByEqualityOperator
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,243 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections.Generic;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Embedded;
public abstract class HttpContentDecoder : MessageToMessageDecoder<IHttpObject>
{
internal static readonly AsciiString Identity = HttpHeaderValues.Identity;
protected IChannelHandlerContext HandlerContext;
EmbeddedChannel decoder;
bool continueResponse;
protected override void Decode(IChannelHandlerContext context, IHttpObject message, List<object> output)
{
if (message is IHttpResponse response && response.Status.Code == 100)
{
if (!(response is ILastHttpContent))
{
this.continueResponse = true;
}
// 100-continue response must be passed through.
output.Add(ReferenceCountUtil.Retain(message));
return;
}
if (this.continueResponse)
{
if (message is ILastHttpContent)
{
this.continueResponse = false;
}
// 100-continue response must be passed through.
output.Add(ReferenceCountUtil.Retain(message));
return;
}
if (message is IHttpMessage httpMessage)
{
this.Cleanup();
HttpHeaders headers = httpMessage.Headers;
// Determine the content encoding.
if (headers.TryGet(HttpHeaderNames.ContentEncoding, out ICharSequence contentEncoding))
{
contentEncoding = AsciiString.Trim(contentEncoding);
}
else
{
contentEncoding = Identity;
}
this.decoder = this.NewContentDecoder(contentEncoding);
if (this.decoder == null)
{
if (httpMessage is IHttpContent httpContent)
{
httpContent.Retain();
}
output.Add(httpMessage);
return;
}
// Remove content-length header:
// the correct value can be set only after all chunks are processed/decoded.
// If buffering is not an issue, add HttpObjectAggregator down the chain, it will set the header.
// Otherwise, rely on LastHttpContent message.
if (headers.Contains(HttpHeaderNames.ContentLength))
{
headers.Remove(HttpHeaderNames.ContentLength);
headers.Set(HttpHeaderNames.TransferEncoding, HttpHeaderValues.Chunked);
}
// Either it is already chunked or EOF terminated.
// See https://github.com/netty/netty/issues/5892
// set new content encoding,
ICharSequence targetContentEncoding = this.GetTargetContentEncoding(contentEncoding);
if (HttpHeaderValues.Identity.ContentEquals(targetContentEncoding))
{
// Do NOT set the 'Content-Encoding' header if the target encoding is 'identity'
// as per: http://tools.ietf.org/html/rfc2616#section-14.11
headers.Remove(HttpHeaderNames.ContentEncoding);
}
else
{
headers.Set(HttpHeaderNames.ContentEncoding, targetContentEncoding);
}
if (httpMessage is IHttpContent)
{
// If message is a full request or response object (headers + data), don't copy data part into out.
// Output headers only; data part will be decoded below.
// Note: "copy" object must not be an instance of LastHttpContent class,
// as this would (erroneously) indicate the end of the HttpMessage to other handlers.
IHttpMessage copy;
if (httpMessage is IHttpRequest req)
{
// HttpRequest or FullHttpRequest
copy = new DefaultHttpRequest(req.ProtocolVersion, req.Method, req.Uri);
}
else if (httpMessage is IHttpResponse res)
{
// HttpResponse or FullHttpResponse
copy = new DefaultHttpResponse(res.ProtocolVersion, res.Status);
}
else
{
throw new CodecException($"Object of class {StringUtil.SimpleClassName(httpMessage.GetType())} is not a HttpRequest or HttpResponse");
}
copy.Headers.Set(httpMessage.Headers);
copy.Result = httpMessage.Result;
output.Add(copy);
}
else
{
output.Add(httpMessage);
}
}
if (message is IHttpContent c)
{
if (this.decoder == null)
{
output.Add(c.Retain());
}
else
{
this.DecodeContent(c, output);
}
}
}
void DecodeContent(IHttpContent c, IList<object> output)
{
IByteBuffer content = c.Content;
this.Decode(content, output);
if (c is ILastHttpContent last)
{
this.FinishDecode(output);
// Generate an additional chunk if the decoder produced
// the last product on closure,
HttpHeaders headers = last.TrailingHeaders;
if (headers.IsEmpty)
{
output.Add(EmptyLastHttpContent.Default);
}
else
{
output.Add(new ComposedLastHttpContent(headers));
}
}
}
protected abstract EmbeddedChannel NewContentDecoder(ICharSequence contentEncoding);
protected ICharSequence GetTargetContentEncoding(ICharSequence contentEncoding) => Identity;
public override void HandlerRemoved(IChannelHandlerContext context)
{
this.CleanupSafely(context);
base.HandlerRemoved(context);
}
public override void ChannelInactive(IChannelHandlerContext context)
{
this.CleanupSafely(context);
base.ChannelInactive(context);
}
public override void HandlerAdded(IChannelHandlerContext context)
{
this.HandlerContext = context;
base.HandlerAdded(context);
}
void Cleanup()
{
if (this.decoder != null)
{
this.decoder.FinishAndReleaseAll();
this.decoder = null;
}
}
void CleanupSafely(IChannelHandlerContext context)
{
try
{
this.Cleanup();
}
catch (Exception cause)
{
// If cleanup throws any error we need to propagate it through the pipeline
// so we don't fail to propagate pipeline events.
context.FireExceptionCaught(cause);
}
}
void Decode(IByteBuffer buf, IList<object> output)
{
// call retain here as it will call release after its written to the channel
this.decoder.WriteInbound(buf.Retain());
this.FetchDecoderOutput(output);
}
void FinishDecode(ICollection<object> output)
{
if (this.decoder.Finish())
{
this.FetchDecoderOutput(output);
}
this.decoder = null;
}
void FetchDecoderOutput(ICollection<object> output)
{
for (;;)
{
var buf = this.decoder.ReadInbound<IByteBuffer>();
if (buf == null)
{
break;
}
if (!buf.IsReadable())
{
buf.Release();
continue;
}
output.Add(new DefaultHttpContent(buf));
}
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using DotNetty.Codecs.Compression;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels.Embedded;
public class HttpContentDecompressor : HttpContentDecoder
{
readonly bool strict;
public HttpContentDecompressor() : this(false)
{
}
public HttpContentDecompressor(bool strict)
{
this.strict = strict;
}
protected override EmbeddedChannel NewContentDecoder(ICharSequence contentEncoding)
{
if (HttpHeaderValues.Gzip.ContentEqualsIgnoreCase(contentEncoding)
|| HttpHeaderValues.XGzip.ContentEqualsIgnoreCase(contentEncoding))
{
return new EmbeddedChannel(
this.HandlerContext.Channel.Id,
this.HandlerContext.Channel.Metadata.HasDisconnect,
this.HandlerContext.Channel.Configuration,
ZlibCodecFactory.NewZlibDecoder(ZlibWrapper.Gzip));
}
if (HttpHeaderValues.Deflate.ContentEqualsIgnoreCase(contentEncoding)
|| HttpHeaderValues.XDeflate.ContentEqualsIgnoreCase(contentEncoding))
{
ZlibWrapper wrapper = this.strict ? ZlibWrapper.Zlib : ZlibWrapper.ZlibOrNone;
return new EmbeddedChannel(
this.HandlerContext.Channel.Id,
this.HandlerContext.Channel.Metadata.HasDisconnect,
this.HandlerContext.Channel.Configuration,
ZlibCodecFactory.NewZlibDecoder(wrapper));
}
// 'identity' or unsupported
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,357 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Embedded;
public abstract class HttpContentEncoder : MessageToMessageCodec<IHttpRequest, IHttpObject>
{
enum State
{
PassThrough,
AwaitHeaders,
AwaitContent
}
static readonly AsciiString ZeroLengthHead = AsciiString.Cached("HEAD");
static readonly AsciiString ZeroLengthConnect = AsciiString.Cached("CONNECT");
static readonly int ContinueCode = HttpResponseStatus.Continue.Code;
readonly Queue<ICharSequence> acceptEncodingQueue = new Queue<ICharSequence>();
EmbeddedChannel encoder;
State state = State.AwaitHeaders;
public override bool AcceptOutboundMessage(object msg) => msg is IHttpContent || msg is IHttpResponse;
protected override void Decode(IChannelHandlerContext ctx, IHttpRequest msg, List<object> output)
{
ICharSequence acceptedEncoding = msg.Headers.Get(HttpHeaderNames.AcceptEncoding, HttpContentDecoder.Identity);
HttpMethod meth = msg.Method;
if (ReferenceEquals(meth, HttpMethod.Head))
{
acceptedEncoding = ZeroLengthHead;
}
else if (ReferenceEquals(meth, HttpMethod.Connect))
{
acceptedEncoding = ZeroLengthConnect;
}
this.acceptEncodingQueue.Enqueue(acceptedEncoding);
output.Add(ReferenceCountUtil.Retain(msg));
}
protected override void Encode(IChannelHandlerContext ctx, IHttpObject msg, List<object> output)
{
bool isFull = msg is IHttpResponse && msg is ILastHttpContent;
switch (this.state)
{
case State.AwaitHeaders:
{
EnsureHeaders(msg);
Debug.Assert(this.encoder == null);
var res = (IHttpResponse)msg;
int code = res.Status.Code;
ICharSequence acceptEncoding;
if (code == ContinueCode)
{
// We need to not poll the encoding when response with CONTINUE as another response will follow
// for the issued request. See https://github.com/netty/netty/issues/4079
acceptEncoding = null;
}
else
{
// Get the list of encodings accepted by the peer.
acceptEncoding = this.acceptEncodingQueue.Count > 0 ? this.acceptEncodingQueue.Dequeue() : null;
if (acceptEncoding == null)
{
throw new InvalidOperationException("cannot send more responses than requests");
}
}
//
// per rfc2616 4.3 Message Body
// All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST NOT include a
// message-body. All other responses do include a message-body, although it MAY be of zero length.
//
// 9.4 HEAD
// The HEAD method is identical to GET except that the server MUST NOT return a message-body
// in the response.
//
// Also we should pass through HTTP/1.0 as transfer-encoding: chunked is not supported.
//
// See https://github.com/netty/netty/issues/5382
//
if (IsPassthru(res.ProtocolVersion, code, acceptEncoding))
{
if (isFull)
{
output.Add(ReferenceCountUtil.Retain(res));
}
else
{
output.Add(res);
// Pass through all following contents.
this.state = State.PassThrough;
}
break;
}
if (isFull)
{
// Pass through the full response with empty content and continue waiting for the the next resp.
if (!((IByteBufferHolder)res).Content.IsReadable())
{
output.Add(ReferenceCountUtil.Retain(res));
break;
}
}
// Prepare to encode the content.
Result result = this.BeginEncode(res, acceptEncoding);
// If unable to encode, pass through.
if (result == null)
{
if (isFull)
{
output.Add(ReferenceCountUtil.Retain(res));
}
else
{
output.Add(res);
// Pass through all following contents.
this.state = State.PassThrough;
}
break;
}
this.encoder = result.ContentEncoder;
// Encode the content and remove or replace the existing headers
// so that the message looks like a decoded message.
res.Headers.Set(HttpHeaderNames.ContentEncoding, result.TargetContentEncoding);
// Output the rewritten response.
if (isFull)
{
// Convert full message into unfull one.
var newRes = new DefaultHttpResponse(res.ProtocolVersion, res.Status);
newRes.Headers.Set(res.Headers);
output.Add(newRes);
EnsureContent(res);
this.EncodeFullResponse(newRes, (IHttpContent)res, output);
break;
}
else
{
// Make the response chunked to simplify content transformation.
res.Headers.Remove(HttpHeaderNames.ContentLength);
res.Headers.Set(HttpHeaderNames.TransferEncoding, HttpHeaderValues.Chunked);
output.Add(res);
this.state = State.AwaitContent;
if (!(msg is IHttpContent))
{
// only break out the switch statement if we have not content to process
// See https://github.com/netty/netty/issues/2006
break;
}
// Fall through to encode the content
goto case State.AwaitContent;
}
}
case State.AwaitContent:
{
EnsureContent(msg);
if (this.EncodeContent((IHttpContent)msg, output))
{
this.state = State.AwaitHeaders;
}
break;
}
case State.PassThrough:
{
EnsureContent(msg);
output.Add(ReferenceCountUtil.Retain(msg));
// Passed through all following contents of the current response.
if (msg is ILastHttpContent)
{
this.state = State.AwaitHeaders;
}
break;
}
}
}
void EncodeFullResponse(IHttpResponse newRes, IHttpContent content, IList<object> output)
{
int existingMessages = output.Count;
this.EncodeContent(content, output);
if (HttpUtil.IsContentLengthSet(newRes))
{
// adjust the content-length header
int messageSize = 0;
for (int i = existingMessages; i < output.Count; i++)
{
if (output[i] is IHttpContent httpContent)
{
messageSize += httpContent.Content.ReadableBytes;
}
}
HttpUtil.SetContentLength(newRes, messageSize);
}
else
{
newRes.Headers.Set(HttpHeaderNames.TransferEncoding, HttpHeaderValues.Chunked);
}
}
static bool IsPassthru(HttpVersion version, int code, ICharSequence httpMethod) =>
code < 200 || code == 204 || code == 304
|| (ReferenceEquals(httpMethod, ZeroLengthHead) || ReferenceEquals(httpMethod, ZeroLengthConnect) && code == 200)
|| ReferenceEquals(version, HttpVersion.Http10);
static void EnsureHeaders(IHttpObject msg)
{
if (!(msg is IHttpResponse))
{
throw new CodecException($"unexpected message type: {msg.GetType().Name} (expected: {StringUtil.SimpleClassName<IHttpResponse>()})");
}
}
static void EnsureContent(IHttpObject msg)
{
if (!(msg is IHttpContent))
{
throw new CodecException($"unexpected message type: {msg.GetType().Name} (expected: {StringUtil.SimpleClassName<IHttpContent>()})");
}
}
bool EncodeContent(IHttpContent c, IList<object> output)
{
IByteBuffer content = c.Content;
this.Encode(content, output);
if (c is ILastHttpContent last)
{
this.FinishEncode(output);
// Generate an additional chunk if the decoder produced
// the last product on closure,
HttpHeaders headers = last.TrailingHeaders;
if (headers.IsEmpty)
{
output.Add(EmptyLastHttpContent.Default);
}
else
{
output.Add(new ComposedLastHttpContent(headers));
}
return true;
}
return false;
}
protected abstract Result BeginEncode(IHttpResponse headers, ICharSequence acceptEncoding);
public override void HandlerRemoved(IChannelHandlerContext context)
{
this.CleanupSafely(context);
base.HandlerRemoved(context);
}
public override void ChannelInactive(IChannelHandlerContext context)
{
this.CleanupSafely(context);
base.ChannelInactive(context);
}
void Cleanup()
{
if (this.encoder != null)
{
// Clean-up the previous encoder if not cleaned up correctly.
this.encoder.FinishAndReleaseAll();
this.encoder = null;
}
}
void CleanupSafely(IChannelHandlerContext ctx)
{
try
{
this.Cleanup();
}
catch (Exception cause)
{
// If cleanup throws any error we need to propagate it through the pipeline
// so we don't fail to propagate pipeline events.
ctx.FireExceptionCaught(cause);
}
}
void Encode(IByteBuffer buf, IList<object> output)
{
// call retain here as it will call release after its written to the channel
this.encoder.WriteOutbound(buf.Retain());
this.FetchEncoderOutput(output);
}
void FinishEncode(IList<object> output)
{
if (this.encoder.Finish())
{
this.FetchEncoderOutput(output);
}
this.encoder = null;
}
void FetchEncoderOutput(ICollection<object> output)
{
for (;;)
{
var buf = this.encoder.ReadOutbound<IByteBuffer>();
if (buf == null)
{
break;
}
if (!buf.IsReadable())
{
buf.Release();
continue;
}
output.Add(new DefaultHttpContent(buf));
}
}
public sealed class Result
{
public Result(ICharSequence targetContentEncoding, EmbeddedChannel contentEncoder)
{
Contract.Requires(targetContentEncoding != null);
Contract.Requires(contentEncoder != null);
this.TargetContentEncoding = targetContentEncoding;
this.ContentEncoder = contentEncoder;
}
public ICharSequence TargetContentEncoding { get; }
public EmbeddedChannel ContentEncoder { get; }
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
// A user event designed to communicate that a expectation has failed and there should be no expectation that a
// body will follow.
public sealed class HttpExpectationFailedEvent
{
public static readonly HttpExpectationFailedEvent Default = new HttpExpectationFailedEvent();
HttpExpectationFailedEvent()
{
}
}
}

Просмотреть файл

@ -0,0 +1,170 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using DotNetty.Common.Utilities;
///
/// Standard HTTP header names.
///
/// These are all defined as lowercase to support HTTP/2 requirements while also not
/// violating HTTP/1.x requirements.New header names should always be lowercase.
///
public static class HttpHeaderNames
{
public static readonly AsciiString Accept = AsciiString.Cached("accept");
public static readonly AsciiString AcceptCharset = AsciiString.Cached("accept-charset");
public static readonly AsciiString AcceptEncoding = AsciiString.Cached("accept-encoding");
public static readonly AsciiString AcceptLanguage = AsciiString.Cached("accept-language");
public static readonly AsciiString AcceptRanges = AsciiString.Cached("accept-ranges");
public static readonly AsciiString AcceptPatch = AsciiString.Cached("accept-patch");
public static readonly AsciiString AccessControlAllowCredentials = AsciiString.Cached("access-control-allow-credentials");
public static readonly AsciiString AccessControlAllowHeaders = AsciiString.Cached("access-control-allow-headers");
public static readonly AsciiString AccessControlAllowMethods = AsciiString.Cached("access-control-allow-methods");
public static readonly AsciiString AccessControlAllowOrigin = AsciiString.Cached("access-control-allow-origin");
public static readonly AsciiString AccessControlExposeHeaders = AsciiString.Cached("access-control-expose-headers");
public static readonly AsciiString AccessControlMaxAge = AsciiString.Cached("access-control-max-age");
public static readonly AsciiString AccessControlRequestHeaders = AsciiString.Cached("access-control-request-headers");
public static readonly AsciiString AccessControlRequestMethod = AsciiString.Cached("access-control-request-method");
public static readonly AsciiString Age = AsciiString.Cached("age");
public static readonly AsciiString Allow = AsciiString.Cached("allow");
public static readonly AsciiString Authorization = AsciiString.Cached("authorization");
public static readonly AsciiString CacheControl = AsciiString.Cached("cache-control");
public static readonly AsciiString Connection = AsciiString.Cached("connection");
public static readonly AsciiString ContentBase = AsciiString.Cached("content-base");
public static readonly AsciiString ContentEncoding = AsciiString.Cached("content-encoding");
public static readonly AsciiString ContentLanguage = AsciiString.Cached("content-language");
public static readonly AsciiString ContentLength = AsciiString.Cached("content-length");
public static readonly AsciiString ContentLocation = AsciiString.Cached("content-location");
public static readonly AsciiString ContentTransferEncoding = AsciiString.Cached("content-transfer-encoding");
public static readonly AsciiString ContentDisposition = AsciiString.Cached("content-disposition");
public static readonly AsciiString ContentMD5 = AsciiString.Cached("content-md5");
public static readonly AsciiString ContentRange = AsciiString.Cached("content-range");
public static readonly AsciiString ContentSecurityPolicy = AsciiString.Cached("content-security-policy");
public static readonly AsciiString ContentType = AsciiString.Cached("content-type");
public static readonly AsciiString Cookie = AsciiString.Cached("cookie");
public static readonly AsciiString Date = AsciiString.Cached("date");
public static readonly AsciiString Etag = AsciiString.Cached("etag");
public static readonly AsciiString Expect = AsciiString.Cached("expect");
public static readonly AsciiString Expires = AsciiString.Cached("expires");
public static readonly AsciiString From = AsciiString.Cached("from");
public static readonly AsciiString Host = AsciiString.Cached("host");
public static readonly AsciiString IfMatch = AsciiString.Cached("if-match");
public static readonly AsciiString IfModifiedSince = AsciiString.Cached("if-modified-since");
public static readonly AsciiString IfNoneMatch = AsciiString.Cached("if-none-match");
public static readonly AsciiString IfRange = AsciiString.Cached("if-range");
public static readonly AsciiString IfUnmodifiedSince = AsciiString.Cached("if-unmodified-since");
public static readonly AsciiString LastModified = AsciiString.Cached("last-modified");
public static readonly AsciiString Location = AsciiString.Cached("location");
public static readonly AsciiString MaxForwards = AsciiString.Cached("max-forwards");
public static readonly AsciiString Origin = AsciiString.Cached("origin");
public static readonly AsciiString Pragma = AsciiString.Cached("pragma");
public static readonly AsciiString ProxyAuthenticate = AsciiString.Cached("proxy-authenticate");
public static readonly AsciiString ProxyAuthorization = AsciiString.Cached("proxy-authorization");
public static readonly AsciiString Range = AsciiString.Cached("range");
public static readonly AsciiString Referer = AsciiString.Cached("referer");
public static readonly AsciiString RetryAfter = AsciiString.Cached("retry-after");
public static readonly AsciiString SecWebsocketKey1 = AsciiString.Cached("sec-websocket-key1");
public static readonly AsciiString SecWebsocketKey2 = AsciiString.Cached("sec-websocket-key2");
public static readonly AsciiString SecWebsocketLocation = AsciiString.Cached("sec-websocket-location");
public static readonly AsciiString SecWebsocketOrigin = AsciiString.Cached("sec-websocket-origin");
public static readonly AsciiString SecWebsocketProtocol = AsciiString.Cached("sec-websocket-protocol");
public static readonly AsciiString SecWebsocketVersion = AsciiString.Cached("sec-websocket-version");
public static readonly AsciiString SecWebsocketKey = AsciiString.Cached("sec-websocket-key");
public static readonly AsciiString SecWebsocketAccept = AsciiString.Cached("sec-websocket-accept");
public static readonly AsciiString SecWebsocketExtensions = AsciiString.Cached("sec-websocket-extensions");
public static readonly AsciiString Server = AsciiString.Cached("server");
public static readonly AsciiString SetCookie = AsciiString.Cached("set-cookie");
public static readonly AsciiString SetCookie2 = AsciiString.Cached("set-cookie2");
public static readonly AsciiString Te = AsciiString.Cached("te");
public static readonly AsciiString Trailer = AsciiString.Cached("trailer");
public static readonly AsciiString TransferEncoding = AsciiString.Cached("transfer-encoding");
public static readonly AsciiString Upgrade = AsciiString.Cached("upgrade");
public static readonly AsciiString UserAgent = AsciiString.Cached("user-agent");
public static readonly AsciiString Vary = AsciiString.Cached("vary");
public static readonly AsciiString Via = AsciiString.Cached("via");
public static readonly AsciiString Warning = AsciiString.Cached("warning");
public static readonly AsciiString WebsocketLocation = AsciiString.Cached("websocket-location");
public static readonly AsciiString WebsocketOrigin = AsciiString.Cached("websocket-origin");
public static readonly AsciiString WebsocketProtocol = AsciiString.Cached("websocket-protocol");
public static readonly AsciiString WwwAuthenticate = AsciiString.Cached("www-authenticate");
public static readonly AsciiString XFrameOptions = AsciiString.Cached("x-frame-options");
}
}

Просмотреть файл

@ -0,0 +1,100 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using DotNetty.Common.Utilities;
public static class HttpHeaderValues
{
public static readonly AsciiString ApplicationJson = AsciiString.Cached("application/json");
public static readonly AsciiString ApplicationXWwwFormUrlencoded = AsciiString.Cached("application/x-www-form-urlencoded");
public static readonly AsciiString ApplicationOctetStream = AsciiString.Cached("application/octet-stream");
public static readonly AsciiString Attachment = AsciiString.Cached("attachment");
public static readonly AsciiString Base64 = AsciiString.Cached("base64");
public static readonly AsciiString Binary = AsciiString.Cached("binary");
public static readonly AsciiString Boundary = AsciiString.Cached("boundary");
public static readonly AsciiString Bytes = AsciiString.Cached("bytes");
public static readonly AsciiString Charset = AsciiString.Cached("charset");
public static readonly AsciiString Chunked = AsciiString.Cached("chunked");
public static readonly AsciiString Close = AsciiString.Cached("close");
public static readonly AsciiString Compress = AsciiString.Cached("compress");
public static readonly AsciiString Continue = AsciiString.Cached("100-continue");
public static readonly AsciiString Deflate = AsciiString.Cached("deflate");
public static readonly AsciiString XDeflate = AsciiString.Cached("x-deflate");
public static readonly AsciiString File = AsciiString.Cached("file");
public static readonly AsciiString FileName = AsciiString.Cached("filename");
public static readonly AsciiString FormData = AsciiString.Cached("form-data");
public static readonly AsciiString Gzip = AsciiString.Cached("gzip");
public static readonly AsciiString GzipDeflate = AsciiString.Cached("gzip,deflate");
public static readonly AsciiString XGzip = AsciiString.Cached("x-gzip");
public static readonly AsciiString Identity = AsciiString.Cached("identity");
public static readonly AsciiString KeepAlive = AsciiString.Cached("keep-alive");
public static readonly AsciiString MaxAge = AsciiString.Cached("max-age");
public static readonly AsciiString MaxStale = AsciiString.Cached("max-stale");
public static readonly AsciiString MinFresh = AsciiString.Cached("min-fresh");
public static readonly AsciiString MultipartFormData = AsciiString.Cached("multipart/form-data");
public static readonly AsciiString MultipartMixed = AsciiString.Cached("multipart/mixed");
public static readonly AsciiString MustRevalidate = AsciiString.Cached("must-revalidate");
public static readonly AsciiString Name = AsciiString.Cached("name");
public static readonly AsciiString NoCache = AsciiString.Cached("no-cache");
public static readonly AsciiString NoStore = AsciiString.Cached("no-store");
public static readonly AsciiString NoTransform = AsciiString.Cached("no-transform");
public static readonly AsciiString None = AsciiString.Cached("none");
public static readonly AsciiString Zero = AsciiString.Cached("0");
public static readonly AsciiString OnlyIfCached = AsciiString.Cached("only-if-cached");
public static readonly AsciiString Private = AsciiString.Cached("private");
public static readonly AsciiString ProxyRevalidate = AsciiString.Cached("proxy-revalidate");
public static readonly AsciiString Public = AsciiString.Cached("public");
public static readonly AsciiString QuotedPrintable = AsciiString.Cached("quoted-printable");
public static readonly AsciiString SMaxage = AsciiString.Cached("s-maxage");
public static readonly AsciiString TextPlain = AsciiString.Cached("text/plain");
public static readonly AsciiString Trailers = AsciiString.Cached("trailers");
public static readonly AsciiString Upgrade = AsciiString.Cached("upgrade");
public static readonly AsciiString Websocket = AsciiString.Cached("websocket");
}
}

Просмотреть файл

@ -0,0 +1,267 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ForCanBeConvertedToForeach
namespace DotNetty.Codecs.Http
{
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using DotNetty.Common.Utilities;
using static Common.Utilities.AsciiString;
public abstract class HttpHeaders : IEnumerable<HeaderEntry<AsciiString, ICharSequence>>
{
public abstract bool TryGet(AsciiString name, out ICharSequence value);
public ICharSequence Get(AsciiString name, ICharSequence defaultValue) => this.TryGet(name, out ICharSequence value) ? value : defaultValue;
public abstract bool TryGetInt(AsciiString name, out int value);
public abstract int GetInt(AsciiString name, int defaultValue);
public abstract bool TryGetShort(AsciiString name, out short value);
public abstract short GetShort(AsciiString name, short defaultValue);
public abstract bool TryGetTimeMillis(AsciiString name, out long value);
public abstract long GetTimeMillis(AsciiString name, long defaultValue);
public abstract IList<ICharSequence> GetAll(AsciiString name);
public abstract IList<HeaderEntry<AsciiString, ICharSequence>> Entries();
public virtual IEnumerable<ICharSequence> ValueCharSequenceIterator(AsciiString name) => this.GetAll(name);
public abstract bool Contains(AsciiString name);
public abstract bool IsEmpty { get; }
public abstract int Size { get; }
public abstract ISet<AsciiString> Names();
public abstract HttpHeaders Add(AsciiString name, object value);
public HttpHeaders Add(AsciiString name, IEnumerable<object> values)
{
foreach (object value in values)
{
this.Add(name, value);
}
return this;
}
public virtual HttpHeaders Add(HttpHeaders headers)
{
Contract.Requires(headers != null);
foreach (HeaderEntry<AsciiString, ICharSequence> pair in headers)
{
this.Add(pair.Key, pair.Value);
}
return this;
}
public abstract HttpHeaders AddInt(AsciiString name, int value);
public abstract HttpHeaders AddShort(AsciiString name, short value);
public abstract HttpHeaders Set(AsciiString name, object value);
public abstract HttpHeaders Set(AsciiString name, IEnumerable<object> values);
public virtual HttpHeaders Set(HttpHeaders headers)
{
Contract.Requires(headers != null);
this.Clear();
if (headers.IsEmpty)
{
return this;
}
foreach(HeaderEntry<AsciiString, ICharSequence> pair in headers)
{
this.Add(pair.Key, pair.Value);
}
return this;
}
public HttpHeaders SetAll(HttpHeaders headers)
{
Contract.Requires(headers != null);
if (headers.IsEmpty)
{
return this;
}
foreach (HeaderEntry<AsciiString, ICharSequence> pair in headers)
{
this.Add(pair.Key, pair.Value);
}
return this;
}
public abstract HttpHeaders SetInt(AsciiString name, int value);
public abstract HttpHeaders SetShort(AsciiString name, short value);
public abstract HttpHeaders Remove(AsciiString name);
public abstract HttpHeaders Clear();
public virtual bool Contains(AsciiString name, ICharSequence value, bool ignoreCase)
{
IEnumerable<ICharSequence> values = this.ValueCharSequenceIterator(name);
if (ignoreCase)
{
foreach (ICharSequence v in values)
{
if (v.ContentEqualsIgnoreCase(value))
{
return true;
}
}
}
else
{
foreach (ICharSequence v in this.ValueCharSequenceIterator(name))
{
if (v.ContentEquals(value))
{
return true;
}
}
}
return false;
}
public virtual bool ContainsValue(AsciiString name, ICharSequence value, bool ignoreCase)
{
foreach (ICharSequence v in this.ValueCharSequenceIterator(name))
{
if (ContainsCommaSeparatedTrimmed(v, value, ignoreCase))
{
return true;
}
}
return false;
}
static bool ContainsCommaSeparatedTrimmed(ICharSequence rawNext, ICharSequence expected, bool ignoreCase)
{
int begin = 0;
int end;
if (ignoreCase)
{
if ((end = IndexOf(rawNext, ',', begin)) == -1)
{
if (ContentEqualsIgnoreCase(Trim(rawNext), expected))
{
return true;
}
}
else
{
do
{
if (ContentEqualsIgnoreCase(Trim(rawNext.SubSequence(begin, end)), expected))
{
return true;
}
begin = end + 1;
}
while ((end = IndexOf(rawNext, ',', begin)) != -1);
if (begin < rawNext.Count)
{
if (ContentEqualsIgnoreCase(Trim(rawNext.SubSequence(begin, rawNext.Count)), expected))
{
return true;
}
}
}
}
else
{
if ((end = IndexOf(rawNext, ',', begin)) == -1)
{
if (ContentEquals(Trim(rawNext), expected))
{
return true;
}
}
else
{
do
{
if (ContentEquals(Trim(rawNext.SubSequence(begin, end)), expected))
{
return true;
}
begin = end + 1;
}
while ((end = IndexOf(rawNext, ',', begin)) != -1);
if (begin < rawNext.Count)
{
if (ContentEquals(Trim(rawNext.SubSequence(begin, rawNext.Count)), expected))
{
return true;
}
}
}
}
return false;
}
public bool TryGetAsString(AsciiString name, out string value)
{
if (this.TryGet(name, out ICharSequence v))
{
value = v.ToString();
return true;
}
else
{
value = default(string);
return false;
}
}
public IList<string> GetAllAsString(AsciiString name)
{
var values = new List<string>();
IList<ICharSequence> list = this.GetAll(name);
foreach (ICharSequence value in list)
{
values.Add(value.ToString());
}
return values;
}
public abstract IEnumerator<HeaderEntry<AsciiString, ICharSequence>> GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
public override string ToString() => HeadersUtils.ToString(this, this.Size);
/// <summary>
/// Deep copy of the headers.
/// </summary>
/// <returns>A deap copy of this.</returns>
public virtual HttpHeaders Copy()
{
var copy = new DefaultHttpHeaders();
copy.Set(this);
return copy;
}
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using static HttpConstants;
static class HttpHeadersEncoder
{
const int ColonAndSpaceShort = (Colon << 8) | HorizontalSpace;
public static void EncoderHeader(AsciiString name, ICharSequence value, IByteBuffer buf)
{
int nameLen = name.Count;
int valueLen = value.Count;
int entryLen = nameLen + valueLen + 4;
buf.EnsureWritable(entryLen);
int offset = buf.WriterIndex;
WriteAscii(buf, offset, name);
offset += nameLen;
buf.SetShort(offset, ColonAndSpaceShort);
offset += 2;
WriteAscii(buf, offset, value);
offset += valueLen;
buf.SetShort(offset, CrlfShort);
offset += 2;
buf.SetWriterIndex(offset);
}
static void WriteAscii(IByteBuffer buf, int offset, ICharSequence value)
{
if (value is AsciiString asciiString)
{
ByteBufferUtil.Copy(asciiString, 0, buf, offset, value.Count);
}
else
{
buf.SetCharSequence(offset, value, Encoding.ASCII);
}
}
}
}

Просмотреть файл

@ -0,0 +1,88 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System.Text;
using DotNetty.Common.Utilities;
static class HttpMessageUtil
{
internal static StringBuilder AppendRequest(StringBuilder buf, IHttpRequest req)
{
AppendCommon(buf, req);
AppendInitialLine(buf, req);
AppendHeaders(buf, req.Headers);
RemoveLastNewLine(buf);
return buf;
}
internal static StringBuilder AppendResponse(StringBuilder buf, IHttpResponse res)
{
AppendCommon(buf, res);
AppendInitialLine(buf, res);
AppendHeaders(buf, res.Headers);
RemoveLastNewLine(buf);
return buf;
}
static void AppendCommon(StringBuilder buf, IHttpMessage msg)
{
buf.Append($"{StringUtil.SimpleClassName(msg)}");
buf.Append("(decodeResult: ");
buf.Append(msg.Result);
buf.Append(", version: ");
buf.Append(msg.ProtocolVersion);
buf.Append($"){StringUtil.Newline}");
}
internal static StringBuilder AppendFullRequest(StringBuilder buf, IFullHttpRequest req)
{
AppendFullCommon(buf, req);
AppendInitialLine(buf, req);
AppendHeaders(buf, req.Headers);
AppendHeaders(buf, req.TrailingHeaders);
RemoveLastNewLine(buf);
return buf;
}
internal static StringBuilder AppendFullResponse(StringBuilder buf, IFullHttpResponse res)
{
AppendFullCommon(buf, res);
AppendInitialLine(buf, res);
AppendHeaders(buf, res.Headers);
AppendHeaders(buf, res.TrailingHeaders);
RemoveLastNewLine(buf);
return buf;
}
static void AppendFullCommon(StringBuilder buf, IFullHttpMessage msg)
{
buf.Append(StringUtil.SimpleClassName(msg));
buf.Append("(decodeResult: ");
buf.Append(msg.Result);
buf.Append(", version: ");
buf.Append(msg.ProtocolVersion);
buf.Append(", content: ");
buf.Append(msg.Content);
buf.Append(')');
buf.Append(StringUtil.Newline);
}
static void AppendInitialLine(StringBuilder buf, IHttpRequest req) =>
buf.Append($"{req.Method} {req.Uri} {req.ProtocolVersion}{StringUtil.Newline}");
static void AppendInitialLine(StringBuilder buf, IHttpResponse res) =>
buf.Append($"{res.ProtocolVersion} {res.Status}{StringUtil.Newline}");
static void AppendHeaders(StringBuilder buf, HttpHeaders headers)
{
foreach(HeaderEntry<AsciiString, ICharSequence> e in headers)
{
buf.Append($"{e.Key}:{e.Value}{StringUtil.Newline}");
}
}
static void RemoveLastNewLine(StringBuilder buf) => buf.Length = buf.Length - StringUtil.Newline.Length;
}
}

Просмотреть файл

@ -0,0 +1,234 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ForCanBeConvertedToForeach
// ReSharper disable ConvertToAutoPropertyWhenPossible
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using DotNetty.Common.Utilities;
public sealed class HttpMethod : IComparable<HttpMethod>, IComparable
{
/**
* The OPTIONS method represents a request for information about the communication options
* available on the request/response chain identified by the Request-URI. This method allows
* the client to determine the options and/or requirements associated with a resource, or the
* capabilities of a server, without implying a resource action or initiating a resource
* retrieval.
*/
public static readonly HttpMethod Options = new HttpMethod("OPTIONS");
/**
* The GET method means retrieve whatever information (in the form of an entity) is identified
* by the Request-URI. If the Request-URI refers to a data-producing process, it is the
* produced data which shall be returned as the entity in the response and not the source text
* of the process, unless that text happens to be the output of the process.
*/
public static readonly HttpMethod Get = new HttpMethod("GET");
/**
* The HEAD method is identical to GET except that the server MUST NOT return a message-body
* in the response.
*/
public static readonly HttpMethod Head = new HttpMethod("HEAD");
/**
* The POST method is used to request that the origin server accept the entity enclosed in the
* request as a new subordinate of the resource identified by the Request-URI in the
* Request-Line.
*/
public static readonly HttpMethod Post = new HttpMethod("POST");
/**
* The PUT method requests that the enclosed entity be stored under the supplied Request-URI.
*/
public static readonly HttpMethod Put = new HttpMethod("PUT");
/**
* The PATCH method requests that a set of changes described in the
* request entity be applied to the resource identified by the Request-URI.
*/
public static readonly HttpMethod Patch = new HttpMethod("PATCH");
/**
* The DELETE method requests that the origin server delete the resource identified by the
* Request-URI.
*/
public static readonly HttpMethod Delete = new HttpMethod("DELETE");
/**
* The TRACE method is used to invoke a remote, application-layer loop- back of the request
* message.
*/
public static readonly HttpMethod Trace = new HttpMethod("TRACE");
/**
* This specification reserves the method name CONNECT for use with a proxy that can dynamically
* switch to being a tunnel
*/
public static readonly HttpMethod Connect = new HttpMethod("CONNECT");
// HashMap
static readonly Dictionary<string, HttpMethod> MethodMap;
static HttpMethod()
{
MethodMap = new Dictionary<string, HttpMethod>
{
{ Options.ToString(), Options },
{ Get.ToString(), Get },
{ Head.ToString(), Head },
{ Post.ToString(), Post },
{ Put.ToString(), Put },
{ Patch.ToString(), Patch },
{ Delete.ToString(), Delete },
{ Trace.ToString(), Trace },
{ Connect.ToString(), Connect },
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static HttpMethod ValueOf(AsciiString name)
{
if (name != null)
{
HttpMethod result = ValueOfInline(name.Array);
if (result != null)
{
return result;
}
// Fall back to slow path
if (MethodMap.TryGetValue(name.ToString(), out result))
{
return result;
}
}
// Really slow path and error handling
return new HttpMethod(name?.ToString());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static HttpMethod ValueOfInline(byte[] bytes)
{
if (bytes.Length <= 2)
{
return null;
}
HttpMethod match = null;
int i = 0;
switch (bytes[i++])
{
case (byte)'C':
match = Connect;
break;
case (byte)'D':
match = Delete;
break;
case (byte)'G':
match = Get;
break;
case (byte)'H':
match = Head;
break;
case (byte)'O':
match = Options;
break;
case (byte)'P':
switch (bytes[i++])
{
case (byte)'O':
match = Post;
break;
case (byte)'U':
match = Put;
break;
case (byte)'A':
match = Patch;
break;
}
break;
case (byte)'T':
match = Trace;
break;
}
if (match != null)
{
byte[] array = match.name.Array;
if (bytes.Length == array.Length)
{
for (; i < bytes.Length; i++)
{
if (bytes[i] != array[i])
{
match = null;
break;
}
}
}
else
{
match = null;
}
}
return match;
}
readonly AsciiString name;
// Creates a new HTTP method with the specified name. You will not need to
// create a new method unless you are implementing a protocol derived from
// HTTP, such as
// http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol and
// http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol
//
public HttpMethod(string name)
{
Contract.Requires(name != null);
name = name.Trim();
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(nameof(name));
}
for (int i=0; i<name.Length;i++)
{
char c = name[i];
if (CharUtil.IsISOControl(c) || char.IsWhiteSpace(c))
{
throw new ArgumentException($"Invalid character '{c}' in {nameof(name)}");
}
}
this.name = AsciiString.Cached(name);
}
public string Name => this.name.ToString();
public AsciiString AsciiName => this.name;
public override int GetHashCode() => this.name.GetHashCode();
public override bool Equals(object obj)
{
if (!(obj is HttpMethod method))
{
return false;
}
return this.name.Equals(method.name);
}
public override string ToString() => this.name.ToString();
public int CompareTo(object obj) => this.CompareTo(obj as HttpMethod);
public int CompareTo(HttpMethod other) => this.name.CompareTo(other.name);
}
}

Просмотреть файл

@ -0,0 +1,354 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Common;
using DotNetty.Common.Internal.Logging;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class HttpObjectAggregator : MessageAggregator<IHttpObject, IHttpMessage, IHttpContent, IFullHttpMessage>
{
static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<HttpObjectAggregator>();
static readonly IFullHttpResponse Continue = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.Continue, Unpooled.Empty);
static readonly IFullHttpResponse ExpectationFailed = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.ExpectationFailed, Unpooled.Empty);
static readonly IFullHttpResponse TooLargeClose = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.RequestEntityTooLarge, Unpooled.Empty);
static readonly IFullHttpResponse TooLarge = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.RequestEntityTooLarge, Unpooled.Empty);
static HttpObjectAggregator()
{
ExpectationFailed.Headers.Set(HttpHeaderNames.ContentLength, HttpHeaderValues.Zero);
TooLarge.Headers.Set(HttpHeaderNames.ContentLength, HttpHeaderValues.Zero);
TooLargeClose.Headers.Set(HttpHeaderNames.ContentLength, HttpHeaderValues.Zero);
TooLargeClose.Headers.Set(HttpHeaderNames.Connection, HttpHeaderValues.Close);
}
readonly bool closeOnExpectationFailed;
public HttpObjectAggregator(int maxContentLength)
: this(maxContentLength, false)
{
}
public HttpObjectAggregator(int maxContentLength, bool closeOnExpectationFailed)
: base(maxContentLength)
{
this.closeOnExpectationFailed = closeOnExpectationFailed;
}
protected override bool IsStartMessage(IHttpObject msg) => msg is IHttpMessage;
protected override bool IsContentMessage(IHttpObject msg) => msg is IHttpContent;
protected override bool IsLastContentMessage(IHttpContent msg) => msg is ILastHttpContent;
protected override bool IsAggregated(IHttpObject msg) => msg is IFullHttpMessage;
protected override bool IsContentLengthInvalid(IHttpMessage start, int maxContentLength)
{
try
{
return HttpUtil.GetContentLength(start, -1) > maxContentLength;
}
catch (FormatException)
{
return false;
}
}
static object ContinueResponse(IHttpMessage start, int maxContentLength, IChannelPipeline pipeline)
{
if (HttpUtil.IsUnsupportedExpectation(start))
{
// if the request contains an unsupported expectation, we return 417
pipeline.FireUserEventTriggered(HttpExpectationFailedEvent.Default);
return ExpectationFailed.RetainedDuplicate();
}
else if (HttpUtil.Is100ContinueExpected(start))
{
// if the request contains 100-continue but the content-length is too large, we return 413
if (HttpUtil.GetContentLength(start, -1L) <= maxContentLength)
{
return Continue.RetainedDuplicate();
}
pipeline.FireUserEventTriggered(HttpExpectationFailedEvent.Default);
return TooLarge.RetainedDuplicate();
}
return null;
}
protected override object NewContinueResponse(IHttpMessage start, int maxContentLength, IChannelPipeline pipeline)
{
object response = ContinueResponse(start, maxContentLength, pipeline);
// we're going to respond based on the request expectation so there's no
// need to propagate the expectation further.
if (response != null)
{
start.Headers.Remove(HttpHeaderNames.Expect);
}
return response;
}
protected override bool CloseAfterContinueResponse(object msg) =>
this.closeOnExpectationFailed && this.IgnoreContentAfterContinueResponse(msg);
protected override bool IgnoreContentAfterContinueResponse(object msg) =>
msg is IHttpResponse response && response.Status.CodeClass.Equals(HttpStatusClass.ClientError);
protected override IFullHttpMessage BeginAggregation(IHttpMessage start, IByteBuffer content)
{
Debug.Assert(!(start is IFullHttpMessage));
HttpUtil.SetTransferEncodingChunked(start, false);
if (start is IHttpRequest request)
{
return new AggregatedFullHttpRequest(request, content, null);
}
else if (start is IHttpResponse response)
{
return new AggregatedFullHttpResponse(response, content, null);
}
throw new CodecException($"Invalid type {StringUtil.SimpleClassName(start)} expecting {nameof(IHttpRequest)} or {nameof(IHttpResponse)}");
}
protected override void Aggregate(IFullHttpMessage aggregated, IHttpContent content)
{
if (content is ILastHttpContent httpContent)
{
// Merge trailing headers into the message.
((AggregatedFullHttpMessage)aggregated).TrailingHeaders = httpContent.TrailingHeaders;
}
}
protected override void FinishAggregation(IFullHttpMessage aggregated)
{
// Set the 'Content-Length' header. If one isn't already set.
// This is important as HEAD responses will use a 'Content-Length' header which
// does not match the actual body, but the number of bytes that would be
// transmitted if a GET would have been used.
//
// See rfc2616 14.13 Content-Length
if (!HttpUtil.IsContentLengthSet(aggregated))
{
aggregated.Headers.Set(
HttpHeaderNames.ContentLength,
new AsciiString(aggregated.Content.ReadableBytes.ToString()));
}
}
protected override void HandleOversizedMessage(IChannelHandlerContext ctx, IHttpMessage oversized)
{
if (oversized is IHttpRequest)
{
// send back a 413 and close the connection
// If the client started to send data already, close because it's impossible to recover.
// If keep-alive is off and 'Expect: 100-continue' is missing, no need to leave the connection open.
if (oversized is IFullHttpMessage ||
!HttpUtil.Is100ContinueExpected(oversized) && !HttpUtil.IsKeepAlive(oversized))
{
ctx.WriteAndFlushAsync(TooLargeClose.RetainedDuplicate()).ContinueWith((t, s) =>
{
if (t.IsFaulted)
{
Logger.Debug("Failed to send a 413 Request Entity Too Large.", t.Exception);
}
((IChannelHandlerContext)s).CloseAsync();
},
ctx,
TaskContinuationOptions.ExecuteSynchronously);
}
else
{
ctx.WriteAndFlushAsync(TooLarge.RetainedDuplicate()).ContinueWith((t, s) =>
{
if (t.IsFaulted)
{
Logger.Debug("Failed to send a 413 Request Entity Too Large.", t.Exception);
((IChannelHandlerContext)s).CloseAsync();
}
},
ctx,
TaskContinuationOptions.ExecuteSynchronously);
}
// If an oversized request was handled properly and the connection is still alive
// (i.e. rejected 100-continue). the decoder should prepare to handle a new message.
var decoder = ctx.Channel.Pipeline.Get<HttpObjectDecoder>();
decoder?.Reset();
}
else if (oversized is IHttpResponse)
{
ctx.CloseAsync();
throw new TooLongFrameException($"Response entity too large: {oversized}");
}
else
{
throw new InvalidOperationException($"Invalid type {StringUtil.SimpleClassName(oversized)}, expecting {nameof(IHttpRequest)} or {nameof(IHttpResponse)}");
}
}
abstract class AggregatedFullHttpMessage : IFullHttpMessage
{
protected readonly IHttpMessage Message;
readonly IByteBuffer content;
HttpHeaders trailingHeaders;
protected AggregatedFullHttpMessage(IHttpMessage message, IByteBuffer content, HttpHeaders trailingHeaders)
{
this.Message = message;
this.content = content;
this.trailingHeaders = trailingHeaders;
}
public HttpHeaders TrailingHeaders
{
get
{
HttpHeaders headers = this.trailingHeaders;
return headers ?? EmptyHttpHeaders.Default;
}
internal set => this.trailingHeaders = value;
}
public HttpVersion ProtocolVersion => this.Message.ProtocolVersion;
public IHttpMessage SetProtocolVersion(HttpVersion version)
{
this.Message.SetProtocolVersion(version);
return this;
}
public HttpHeaders Headers => this.Message.Headers;
public DecoderResult Result
{
get => this.Message.Result;
set => this.Message.Result = value;
}
public IByteBuffer Content => this.content;
public int ReferenceCount => this.content.ReferenceCount;
public IReferenceCounted Retain()
{
this.content.Retain();
return this;
}
public IReferenceCounted Retain(int increment)
{
this.content.Retain(increment);
return this;
}
public IReferenceCounted Touch()
{
this.content.Touch();
return this;
}
public IReferenceCounted Touch(object hint)
{
this.content.Touch(hint);
return this;
}
public bool Release() => this.content.Release();
public bool Release(int decrement) => this.content.Release(decrement);
public abstract IByteBufferHolder Copy();
public abstract IByteBufferHolder Duplicate();
public abstract IByteBufferHolder RetainedDuplicate();
public abstract IByteBufferHolder Replace(IByteBuffer content);
}
sealed class AggregatedFullHttpRequest : AggregatedFullHttpMessage, IFullHttpRequest
{
internal AggregatedFullHttpRequest(IHttpRequest message, IByteBuffer content, HttpHeaders trailingHeaders)
: base(message, content, trailingHeaders)
{
}
public override IByteBufferHolder Copy() => this.Replace(this.Content.Copy());
public override IByteBufferHolder Duplicate() => this.Replace(this.Content.Duplicate());
public override IByteBufferHolder RetainedDuplicate() => this.Replace(this.Content.RetainedDuplicate());
public override IByteBufferHolder Replace(IByteBuffer content)
{
var dup = new DefaultFullHttpRequest(this.ProtocolVersion, this.Method, this.Uri, content,
this.Headers.Copy(), this.TrailingHeaders.Copy());
dup.Result = this.Result;
return dup;
}
public HttpMethod Method => ((IHttpRequest)this.Message).Method;
public IHttpRequest SetMethod(HttpMethod method)
{
((IHttpRequest)this.Message).SetMethod(method);
return this;
}
public string Uri => ((IHttpRequest)this.Message).Uri;
public IHttpRequest SetUri(string uri)
{
((IHttpRequest)this.Message).SetUri(uri);
return this;
}
public override string ToString() => HttpMessageUtil.AppendFullRequest(new StringBuilder(256), this).ToString();
}
sealed class AggregatedFullHttpResponse : AggregatedFullHttpMessage, IFullHttpResponse
{
public AggregatedFullHttpResponse(IHttpResponse message, IByteBuffer content, HttpHeaders trailingHeaders)
: base(message, content, trailingHeaders)
{
}
public override IByteBufferHolder Copy() => this.Replace(this.Content.Copy());
public override IByteBufferHolder Duplicate() => this.Replace(this.Content.Duplicate());
public override IByteBufferHolder RetainedDuplicate() => this.Replace(this.Content.RetainedDuplicate());
public override IByteBufferHolder Replace(IByteBuffer content)
{
var dup = new DefaultFullHttpResponse(this.ProtocolVersion, this.Status, content,
this.Headers.Copy(), this.TrailingHeaders.Copy());
dup.Result = this.Result;
return dup;
}
public HttpResponseStatus Status => ((IHttpResponse)this.Message).Status;
public IHttpResponse SetStatus(HttpResponseStatus status)
{
((IHttpResponse)this.Message).SetStatus(status);
return this;
}
public override string ToString() => HttpMessageUtil.AppendFullResponse(new StringBuilder(256), this).ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,898 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using DotNetty.Buffers;
using DotNetty.Common.Internal;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public abstract class HttpObjectDecoder : ByteToMessageDecoder
{
readonly int maxChunkSize;
readonly bool chunkedSupported;
protected readonly bool ValidateHeaders;
readonly HeaderParser headerParser;
readonly LineParser lineParser;
IHttpMessage message;
long chunkSize;
long contentLength = long.MinValue;
volatile bool resetRequested;
// These will be updated by splitHeader(...)
AsciiString name;
AsciiString value;
ILastHttpContent trailer;
enum State
{
SkipControlChars,
ReadInitial,
ReadHeader,
ReadVariableLengthContent,
ReadFixedLengthContent,
ReadChunkSize,
ReadChunkedContent,
ReadChunkDelimiter,
ReadChunkFooter,
BadMessage,
Upgraded
}
State currentState = State.SkipControlChars;
protected HttpObjectDecoder() : this(4096, 8192, 8192, true)
{
}
protected HttpObjectDecoder(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool chunkedSupported)
: this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, true)
{
}
protected HttpObjectDecoder(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
bool chunkedSupported, bool validateHeaders)
: this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, 128)
{
}
protected HttpObjectDecoder(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
bool chunkedSupported, bool validateHeaders, int initialBufferSize)
{
Contract.Requires(maxInitialLineLength > 0);
Contract.Requires(maxHeaderSize > 0);
Contract.Requires(maxChunkSize > 0);
var seq = new AppendableCharSequence(initialBufferSize);
this.lineParser = new LineParser(seq, maxInitialLineLength);
this.headerParser = new HeaderParser(seq, maxHeaderSize);
this.maxChunkSize = maxChunkSize;
this.chunkedSupported = chunkedSupported;
this.ValidateHeaders = validateHeaders;
}
protected override void Decode(IChannelHandlerContext context, IByteBuffer buffer, List<object> output)
{
if (this.resetRequested)
{
this.ResetNow();
}
switch (this.currentState)
{
case State.SkipControlChars:
{
if (!SkipControlCharacters(buffer))
{
return;
}
this.currentState = State.ReadInitial;
goto case State.ReadInitial; // Fall through
}
case State.ReadInitial:
{
try
{
AppendableCharSequence line = this.lineParser.Parse(buffer);
if (line == null)
{
return;
}
AsciiString[] initialLine = SplitInitialLine(line);
if (initialLine.Length < 3)
{
// Invalid initial line - ignore.
this.currentState = State.SkipControlChars;
return;
}
this.message = this.CreateMessage(initialLine);
this.currentState = State.ReadHeader;
goto case State.ReadHeader; // Fall through
}
catch (Exception e)
{
output.Add(this.InvalidMessage(buffer, e));
return;
}
}
case State.ReadHeader:
{
try
{
State? nextState = this.ReadHeaders(buffer);
if (nextState == null)
{
return;
}
this.currentState = nextState.Value;
switch (nextState.Value)
{
case State.SkipControlChars:
{
// fast-path
// No content is expected.
output.Add(this.message);
output.Add(EmptyLastHttpContent.Default);
this.ResetNow();
return;
}
case State.ReadChunkSize:
{
if (!this.chunkedSupported)
{
throw new ArgumentException("Chunked messages not supported");
}
// Chunked encoding - generate HttpMessage first. HttpChunks will follow.
output.Add(this.message);
return;
}
default:
{
// <a href="https://tools.ietf.org/html/rfc7230#section-3.3.3">RFC 7230, 3.3.3</a> states that if a
// request does not have either a transfer-encoding or a content-length header then the message body
// length is 0. However for a response the body length is the number of octets received prior to the
// server closing the connection. So we treat this as variable length chunked encoding.
long length = this.ContentLength();
if (length == 0 || length == -1 && this.IsDecodingRequest())
{
output.Add(this.message);
output.Add(EmptyLastHttpContent.Default);
this.ResetNow();
return;
}
Debug.Assert(nextState.Value == State.ReadFixedLengthContent
|| nextState.Value == State.ReadVariableLengthContent);
output.Add(this.message);
if (nextState == State.ReadFixedLengthContent)
{
// chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT state reads data chunk by chunk.
this.chunkSize = length;
}
// We return here, this forces decode to be called again where we will decode the content
return;
}
}
}
catch (Exception exception)
{
output.Add(this.InvalidMessage(buffer, exception));
return;
}
}
case State.ReadVariableLengthContent:
{
// Keep reading data as a chunk until the end of connection is reached.
int toRead = Math.Min(buffer.ReadableBytes, this.maxChunkSize);
if (toRead > 0)
{
IByteBuffer content = buffer.ReadRetainedSlice(toRead);
output.Add(new DefaultHttpContent(content));
}
return;
}
case State.ReadFixedLengthContent:
{
int readLimit = buffer.ReadableBytes;
// Check if the buffer is readable first as we use the readable byte count
// to create the HttpChunk. This is needed as otherwise we may end up with
// create a HttpChunk instance that contains an empty buffer and so is
// handled like it is the last HttpChunk.
//
// See https://github.com/netty/netty/issues/433
if (readLimit == 0)
{
return;
}
int toRead = Math.Min(readLimit, this.maxChunkSize);
if (toRead > this.chunkSize)
{
toRead = (int)this.chunkSize;
}
IByteBuffer content = buffer.ReadRetainedSlice(toRead);
this.chunkSize -= toRead;
if (this.chunkSize == 0)
{
// Read all content.
output.Add(new DefaultLastHttpContent(content, this.ValidateHeaders));
this.ResetNow();
}
else
{
output.Add(new DefaultHttpContent(content));
}
return;
}
// everything else after this point takes care of reading chunked content. basically, read chunk size,
// read chunk, read and ignore the CRLF and repeat until 0
case State.ReadChunkSize:
{
try
{
AppendableCharSequence line = this.lineParser.Parse(buffer);
if (line == null)
{
return;
}
int size = GetChunkSize(line.ToAsciiString());
this.chunkSize = size;
if (size == 0)
{
this.currentState = State.ReadChunkFooter;
return;
}
this.currentState = State.ReadChunkedContent;
goto case State.ReadChunkedContent; // fall-through
}
catch (Exception e)
{
output.Add(this.InvalidChunk(buffer, e));
return;
}
}
case State.ReadChunkedContent:
{
Debug.Assert(this.chunkSize <= int.MaxValue);
int toRead = Math.Min((int)this.chunkSize, this.maxChunkSize);
toRead = Math.Min(toRead, buffer.ReadableBytes);
if (toRead == 0)
{
return;
}
IHttpContent chunk = new DefaultHttpContent(buffer.ReadRetainedSlice(toRead));
this.chunkSize -= toRead;
output.Add(chunk);
if (this.chunkSize != 0)
{
return;
}
this.currentState = State.ReadChunkDelimiter;
goto case State.ReadChunkDelimiter; // fall-through
}
case State.ReadChunkDelimiter:
{
int wIdx = buffer.WriterIndex;
int rIdx = buffer.ReaderIndex;
while (wIdx > rIdx)
{
byte next = buffer.GetByte(rIdx++);
if (next == HttpConstants.LineFeed)
{
this.currentState = State.ReadChunkSize;
break;
}
}
buffer.SetReaderIndex(rIdx);
return;
}
case State.ReadChunkFooter:
{
try
{
ILastHttpContent lastTrialer = this.ReadTrailingHeaders(buffer);
if (lastTrialer == null)
{
return;
}
output.Add(lastTrialer);
this.ResetNow();
return;
}
catch (Exception exception)
{
output.Add(this.InvalidChunk(buffer, exception));
return;
}
}
case State.BadMessage:
{
// Keep discarding until disconnection.
buffer.SkipBytes(buffer.ReadableBytes);
break;
}
case State.Upgraded:
{
int readableBytes = buffer.ReadableBytes;
if (readableBytes > 0)
{
// Keep on consuming as otherwise we may trigger an DecoderException,
// other handler will replace this codec with the upgraded protocol codec to
// take the traffic over at some point then.
// See https://github.com/netty/netty/issues/2173
output.Add(buffer.ReadBytes(readableBytes));
}
break;
}
}
}
protected override void DecodeLast(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
base.DecodeLast(context, input, output);
if (this.resetRequested)
{
// If a reset was requested by decodeLast() we need to do it now otherwise we may produce a
// LastHttpContent while there was already one.
this.ResetNow();
}
// Handle the last unfinished message.
if (this.message != null)
{
bool chunked = HttpUtil.IsTransferEncodingChunked(this.message);
if (this.currentState == State.ReadVariableLengthContent
&& !input.IsReadable() && !chunked)
{
// End of connection.
output.Add(EmptyLastHttpContent.Default);
this.ResetNow();
return;
}
if (this.currentState == State.ReadHeader)
{
// If we are still in the state of reading headers we need to create a new invalid message that
// signals that the connection was closed before we received the headers.
output.Add(this.InvalidMessage(Unpooled.Empty,
new PrematureChannelClosureException("Connection closed before received headers")));
this.ResetNow();
return;
}
// Check if the closure of the connection signifies the end of the content.
bool prematureClosure;
if (this.IsDecodingRequest() || chunked)
{
// The last request did not wait for a response.
prematureClosure = true;
}
else
{
// Compare the length of the received content and the 'Content-Length' header.
// If the 'Content-Length' header is absent, the length of the content is determined by the end of the
// connection, so it is perfectly fine.
prematureClosure = this.ContentLength() > 0;
}
if (!prematureClosure)
{
output.Add(EmptyLastHttpContent.Default);
}
this.ResetNow();
}
}
public override void UserEventTriggered(IChannelHandlerContext context, object evt)
{
if (evt is HttpExpectationFailedEvent)
{
switch (this.currentState)
{
case State.ReadFixedLengthContent:
case State.ReadVariableLengthContent:
case State.ReadChunkSize:
this.Reset();
break;
}
}
base.UserEventTriggered(context, evt);
}
protected virtual bool IsContentAlwaysEmpty(IHttpMessage msg)
{
if (msg is IHttpResponse res)
{
int code = res.Status.Code;
// Correctly handle return codes of 1xx.
//
// See:
// - http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html Section 4.4
// - https://github.com/netty/netty/issues/222
if (code >= 100 && code < 200)
{
// One exception: Hixie 76 websocket handshake response
return !(code == 101 && !res.Headers.Contains(HttpHeaderNames.SecWebsocketAccept)
&& res.Headers.Contains(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket, true));
}
switch (code)
{
case 204:
case 304:
return true;
}
}
return false;
}
protected bool IsSwitchingToNonHttp1Protocol(IHttpResponse msg)
{
if (msg.Status.Code != HttpResponseStatus.SwitchingProtocols.Code)
{
return false;
}
return !msg.Headers.TryGet(HttpHeaderNames.Upgrade, out ICharSequence newProtocol)
|| !AsciiString.Contains(newProtocol, HttpVersion.Http10String)
&& !AsciiString.Contains(newProtocol, HttpVersion.Http11String);
}
// Resets the state of the decoder so that it is ready to decode a new message.
// This method is useful for handling a rejected request with {@code Expect: 100-continue} header.
public void Reset() => this.resetRequested = true;
void ResetNow()
{
IHttpMessage msg = this.message;
this.message = null;
this.name = null;
this.value = null;
this.contentLength = long.MinValue;
this.lineParser.Reset();
this.headerParser.Reset();
this.trailer = null;
if (!this.IsDecodingRequest())
{
if (msg is IHttpResponse res && this.IsSwitchingToNonHttp1Protocol(res))
{
this.currentState = State.Upgraded;
return;
}
}
this.resetRequested = false;
this.currentState = State.SkipControlChars;
}
IHttpMessage InvalidMessage(IByteBuffer buf, Exception cause)
{
this.currentState = State.BadMessage;
// Advance the readerIndex so that ByteToMessageDecoder does not complain
// when we produced an invalid message without consuming anything.
buf.SkipBytes(buf.ReadableBytes);
if (this.message != null)
{
this.message.Result = DecoderResult.Failure(cause);
}
else
{
this.message = this.CreateInvalidMessage();
this.message.Result = DecoderResult.Failure(cause);
}
IHttpMessage ret = this.message;
this.message = null;
return ret;
}
IHttpContent InvalidChunk(IByteBuffer buf, Exception cause)
{
this.currentState = State.BadMessage;
// Advance the readerIndex so that ByteToMessageDecoder does not complain
// when we produced an invalid message without consuming anything.
buf.SkipBytes(buf.ReadableBytes);
IHttpContent chunk = new DefaultLastHttpContent(Unpooled.Empty);
chunk.Result = DecoderResult.Failure(cause);
this.message = null;
this.trailer = null;
return chunk;
}
static bool SkipControlCharacters(IByteBuffer buffer)
{
bool skiped = false;
int wIdx = buffer.WriterIndex;
int rIdx = buffer.ReaderIndex;
while (wIdx > rIdx)
{
byte c = buffer.GetByte(rIdx++);
if (!CharUtil.IsISOControl(c) && !IsWhiteSpace(c))
{
rIdx--;
skiped = true;
break;
}
}
buffer.SetReaderIndex(rIdx);
return skiped;
}
State? ReadHeaders(IByteBuffer buffer)
{
IHttpMessage httpMessage = this.message;
HttpHeaders headers = httpMessage.Headers;
AppendableCharSequence line = this.headerParser.Parse(buffer);
if (line == null)
{
return null;
}
// ReSharper disable once ConvertIfDoToWhile
if (line.Count > 0)
{
do
{
byte firstChar = line.Bytes[0];
if (this.name != null && (firstChar == ' ' || firstChar == '\t'))
{
ICharSequence trimmedLine = CharUtil.Trim(line);
this.value = new AsciiString($"{this.value} {trimmedLine}");
}
else
{
if (this.name != null)
{
headers.Add(this.name, this.value);
}
this.SplitHeader(line);
}
line = this.headerParser.Parse(buffer);
if (line == null)
{
return null;
}
} while (line.Count > 0);
}
// Add the last header.
if (this.name != null)
{
headers.Add(this.name, this.value);
}
// reset name and value fields
this.name = null;
this.value = null;
State nextState;
if (this.IsContentAlwaysEmpty(httpMessage))
{
HttpUtil.SetTransferEncodingChunked(httpMessage, false);
nextState = State.SkipControlChars;
}
else if (HttpUtil.IsTransferEncodingChunked(httpMessage))
{
nextState = State.ReadChunkSize;
}
else if (this.ContentLength() >= 0)
{
nextState = State.ReadFixedLengthContent;
}
else
{
nextState = State.ReadVariableLengthContent;
}
return nextState;
}
long ContentLength()
{
if (this.contentLength == long.MinValue)
{
this.contentLength = HttpUtil.GetContentLength(this.message, -1L);
}
return this.contentLength;
}
ILastHttpContent ReadTrailingHeaders(IByteBuffer buffer)
{
AppendableCharSequence line = this.headerParser.Parse(buffer);
if (line == null)
{
return null;
}
AsciiString lastHeader = null;
if (line.Count > 0)
{
ILastHttpContent trailingHeaders = this.trailer;
if (trailingHeaders == null)
{
trailingHeaders = new DefaultLastHttpContent(Unpooled.Empty, this.ValidateHeaders);
this.trailer = trailingHeaders;
}
do
{
byte firstChar = line.Bytes[0];
if (lastHeader != null && (firstChar == ' ' || firstChar == '\t'))
{
IList<ICharSequence> current = trailingHeaders.TrailingHeaders.GetAll(lastHeader);
if (current.Count > 0)
{
int lastPos = current.Count - 1;
ICharSequence lineTrimmed = CharUtil.Trim(line);
current[lastPos] = new AsciiString($"{current[lastPos]} {lineTrimmed}");
}
}
else
{
this.SplitHeader(line);
AsciiString headerName = this.name;
if (!HttpHeaderNames.ContentLength.ContentEqualsIgnoreCase(headerName)
&& !HttpHeaderNames.TransferEncoding.ContentEqualsIgnoreCase(headerName)
&& !HttpHeaderNames.Trailer.ContentEqualsIgnoreCase(headerName))
{
trailingHeaders.TrailingHeaders.Add(headerName, this.value);
}
lastHeader = this.name;
// reset name and value fields
this.name = null;
this.value = null;
}
line = this.headerParser.Parse(buffer);
if (line == null)
{
return null;
}
} while (line.Count > 0);
this.trailer = null;
return trailingHeaders;
}
return EmptyLastHttpContent.Default;
}
protected abstract bool IsDecodingRequest();
protected abstract IHttpMessage CreateMessage(AsciiString[] initialLine);
protected abstract IHttpMessage CreateInvalidMessage();
static int GetChunkSize(AsciiString hex)
{
hex = hex.Trim();
for (int i = hex.Offset; i < hex.Count; i++)
{
byte c = hex.Array[i];
if (c == ';' || IsWhiteSpace(c) || CharUtil.IsISOControl(c))
{
hex = (AsciiString)hex.SubSequence(0, i);
break;
}
}
return hex.ParseInt(16);
}
static AsciiString[] SplitInitialLine(AppendableCharSequence sb)
{
byte[] chars = sb.Bytes;
int length = sb.Count;
int aStart = FindNonWhitespace(chars, 0, length);
int aEnd = FindWhitespace(chars, aStart, length);
int bStart = FindNonWhitespace(chars, aEnd, length);
int bEnd = FindWhitespace(chars, bStart, length);
int cStart = FindNonWhitespace(chars, bEnd, length);
int cEnd = FindEndOfString(chars, length);
return new[]
{
sb.SubStringUnsafe(aStart, aEnd),
sb.SubStringUnsafe(bStart, bEnd),
cStart < cEnd ? sb.SubStringUnsafe(cStart, cEnd) : AsciiString.Empty
};
}
void SplitHeader(AppendableCharSequence sb)
{
byte[] chars = sb.Bytes;
int length = sb.Count;
int nameEnd;
int colonEnd;
int nameStart = FindNonWhitespace(chars, 0, length);
for (nameEnd = nameStart; nameEnd < length; nameEnd++)
{
byte ch = chars[nameEnd];
if (ch == ':' || IsWhiteSpace(ch))
{
break;
}
}
for (colonEnd = nameEnd; colonEnd < length; colonEnd++)
{
if (chars[colonEnd] == ':')
{
colonEnd++;
break;
}
}
this.name = sb.SubStringUnsafe(nameStart, nameEnd);
int valueStart = FindNonWhitespace(chars, colonEnd, length);
if (valueStart == length)
{
this.value = AsciiString.Empty;
}
else
{
int valueEnd = FindEndOfString(chars, length);
this.value = sb.SubStringUnsafe(valueStart, valueEnd);
}
}
static int FindNonWhitespace(byte[] sb, int offset, int length)
{
for (int result = offset; result < length; ++result)
{
if (!IsWhiteSpace(sb[result]))
{
return result;
}
}
return length;
}
static int FindWhitespace(byte[] sb, int offset, int length)
{
for (int result = offset; result < length; ++result)
{
if (IsWhiteSpace(sb[result]))
{
return result;
}
}
return length;
}
static int FindEndOfString(byte[] sb, int length)
{
for (int result = length - 1; result > 0; --result)
{
if (!IsWhiteSpace(sb[result]))
{
return result + 1;
}
}
return 0;
}
class HeaderParser : IByteProcessor
{
readonly AppendableCharSequence seq;
readonly int maxLength;
int size;
internal HeaderParser(AppendableCharSequence seq, int maxLength)
{
this.seq = seq;
this.maxLength = maxLength;
}
public virtual AppendableCharSequence Parse(IByteBuffer buffer)
{
int oldSize = this.size;
this.seq.Reset();
int i = buffer.ForEachByte(this);
if (i == -1)
{
this.size = oldSize;
return null;
}
buffer.SetReaderIndex(i + 1);
return this.seq;
}
public void Reset() => this.size = 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Process(byte value)
{
if (value == HttpConstants.CarriageReturn)
{
return true;
}
if (value == HttpConstants.LineFeed)
{
return false;
}
if (++this.size > this.maxLength)
{
// TODO: Respond with Bad Request and discard the traffic
// or close the connection.
// No need to notify the upstream handlers - just log.
// If decoding a response, just throw an exception.
ThrowTooLongFrameException(this, this.maxLength);
}
this.seq.Append(value);
return true;
}
static void ThrowTooLongFrameException(HeaderParser parser, int length)
{
throw GetTooLongFrameException();
TooLongFrameException GetTooLongFrameException()
{
return new TooLongFrameException(parser.NewExceptionMessage(length));
}
}
protected virtual string NewExceptionMessage(int length) => $"HTTP header is larger than {length} bytes.";
}
sealed class LineParser : HeaderParser
{
internal LineParser(AppendableCharSequence seq, int maxLength)
: base(seq, maxLength)
{
}
public override AppendableCharSequence Parse(IByteBuffer buffer)
{
this.Reset();
return base.Parse(buffer);
}
protected override string NewExceptionMessage(int maxLength) => $"An HTTP line is larger than {maxLength} bytes.";
}
// Similar to char.IsWhiteSpace for ascii
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool IsWhiteSpace(byte c)
{
switch (c)
{
case HttpConstants.HorizontalSpace:
case HttpConstants.HorizontalTab:
case HttpConstants.CarriageReturn:
return true;
}
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,252 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections.Generic;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public abstract class HttpObjectEncoder<T> : MessageToMessageEncoder<object> where T : IHttpMessage
{
const float HeadersWeightNew = 1 / 5f;
const float HeadersWeightHistorical = 1 - HeadersWeightNew;
const float TrailersWeightNew = HeadersWeightNew;
const float TrailersWeightHistorical = HeadersWeightHistorical;
const int StInit = 0;
const int StContentNonChunk = 1;
const int StContentChunk = 2;
const int StContentAlwaysEmpty = 3;
int state = StInit;
// Used to calculate an exponential moving average of the encoded size of the initial line and the headers for
// a guess for future buffer allocations.
float headersEncodedSizeAccumulator = 256;
// Used to calculate an exponential moving average of the encoded size of the trailers for
// a guess for future buffer allocations.
float trailersEncodedSizeAccumulator = 256;
protected override void Encode(IChannelHandlerContext context, object message, List<object> output)
{
IByteBuffer buf = null;
if (message is IHttpMessage)
{
if (this.state != StInit)
{
throw new InvalidOperationException($"unexpected message type: {StringUtil.SimpleClassName(message)}");
}
var m = (T)message;
buf = context.Allocator.Buffer((int)this.headersEncodedSizeAccumulator);
// Encode the message.
this.EncodeInitialLine(buf, m);
this.state = this.IsContentAlwaysEmpty(m) ? StContentAlwaysEmpty
: HttpUtil.IsTransferEncodingChunked(m) ? StContentChunk : StContentNonChunk;
this.SanitizeHeadersBeforeEncode(m, this.state == StContentAlwaysEmpty);
this.EncodeHeaders(m.Headers, buf);
buf.WriteShort(HttpConstants.CrlfShort);
this.headersEncodedSizeAccumulator = HeadersWeightNew * PadSizeForAccumulation(buf.ReadableBytes)
+ HeadersWeightHistorical * this.headersEncodedSizeAccumulator;
}
// Bypass the encoder in case of an empty buffer, so that the following idiom works:
//
// ch.write(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
//
// See https://github.com/netty/netty/issues/2983 for more information.
if (message is IByteBuffer potentialEmptyBuf)
{
if (!potentialEmptyBuf.IsReadable())
{
output.Add(potentialEmptyBuf.Retain());
return;
}
}
if (message is IHttpContent || message is IByteBuffer || message is IFileRegion)
{
switch (this.state)
{
case StInit:
throw new InvalidOperationException($"unexpected message type: {StringUtil.SimpleClassName(message)}");
case StContentNonChunk:
long contentLength = ContentLength(message);
if (contentLength > 0)
{
if (buf != null && buf.WritableBytes >= contentLength && message is IHttpContent)
{
// merge into other buffer for performance reasons
buf.WriteBytes(((IHttpContent)message).Content);
output.Add(buf);
}
else
{
if (buf != null)
{
output.Add(buf);
}
output.Add(EncodeAndRetain(message));
}
if (message is ILastHttpContent)
{
this.state = StInit;
}
break;
}
goto case StContentAlwaysEmpty; // fall-through!
case StContentAlwaysEmpty:
// ReSharper disable once ConvertIfStatementToNullCoalescingExpression
if (buf != null)
{
// We allocated a buffer so add it now.
output.Add(buf);
}
else
{
// Need to produce some output otherwise an
// IllegalStateException will be thrown as we did not write anything
// Its ok to just write an EMPTY_BUFFER as if there are reference count issues these will be
// propagated as the caller of the encode(...) method will release the original
// buffer.
// Writing an empty buffer will not actually write anything on the wire, so if there is a user
// error with msg it will not be visible externally
output.Add(Unpooled.Empty);
}
break;
case StContentChunk:
if (buf != null)
{
// We allocated a buffer so add it now.
output.Add(buf);
}
this.EncodeChunkedContent(context, message, ContentLength(message), output);
break;
default:
throw new EncoderException($"unexpected state {this.state}: {StringUtil.SimpleClassName(message)}");
}
if (message is ILastHttpContent)
{
this.state = StInit;
}
}
else if (buf != null)
{
output.Add(buf);
}
}
protected void EncodeHeaders(HttpHeaders headers, IByteBuffer buf)
{
foreach (HeaderEntry<AsciiString, ICharSequence> header in headers)
{
HttpHeadersEncoder.EncoderHeader(header.Key, header.Value, buf);
}
}
void EncodeChunkedContent(IChannelHandlerContext context, object message, long contentLength, ICollection<object> output)
{
if (contentLength > 0)
{
var lengthHex = new AsciiString(Convert.ToString(contentLength, 16), Encoding.ASCII);
IByteBuffer buf = context.Allocator.Buffer(lengthHex.Count + 2);
buf.WriteCharSequence(lengthHex, Encoding.ASCII);
buf.WriteShort(HttpConstants.CrlfShort);
output.Add(buf);
output.Add(EncodeAndRetain(message));
output.Add(HttpConstants.CrlfBuf.Duplicate());
}
if (message is ILastHttpContent content)
{
HttpHeaders headers = content.TrailingHeaders;
if (headers.IsEmpty)
{
output.Add(HttpConstants.ZeroCrlfCrlfBuf.Duplicate());
}
else
{
IByteBuffer buf = context.Allocator.Buffer((int)this.trailersEncodedSizeAccumulator);
buf.WriteMedium(HttpConstants.ZeroCrlfMedium);
this.EncodeHeaders(headers, buf);
buf.WriteShort(HttpConstants.CrlfShort);
this.trailersEncodedSizeAccumulator = TrailersWeightNew * PadSizeForAccumulation(buf.ReadableBytes)
+ TrailersWeightHistorical * this.trailersEncodedSizeAccumulator;
output.Add(buf);
}
}
else if (contentLength == 0)
{
// Need to produce some output otherwise an
// IllegalstateException will be thrown
output.Add(ReferenceCountUtil.Retain(message));
}
}
// Allows to sanitize headers of the message before encoding these.
protected virtual void SanitizeHeadersBeforeEncode(T msg, bool isAlwaysEmpty)
{
// noop
}
protected virtual bool IsContentAlwaysEmpty(T msg) => false;
public override bool AcceptOutboundMessage(object msg) => msg is IHttpObject || msg is IByteBuffer || msg is IFileRegion;
static object EncodeAndRetain(object message)
{
if (message is IByteBuffer buffer)
{
return buffer.Retain();
}
if (message is IHttpContent content)
{
return content.Content.Retain();
}
if (message is IFileRegion region)
{
return region.Retain();
}
throw new InvalidOperationException($"unexpected message type: {StringUtil.SimpleClassName(message)}");
}
static long ContentLength(object message)
{
if (message is IHttpContent content)
{
return content.Content.ReadableBytes;
}
if (message is IByteBuffer buffer)
{
return buffer.ReadableBytes;
}
if (message is IFileRegion region)
{
return region.Count;
}
throw new InvalidOperationException($"unexpected message type: {StringUtil.SimpleClassName(message)}");
}
// Add some additional overhead to the buffer. The rational is that it is better to slightly over allocate and waste
// some memory, rather than under allocate and require a resize/copy.
// @param readableBytes The readable bytes in the buffer.
// @return The {@code readableBytes} with some additional padding.
static int PadSizeForAccumulation(int readableBytes) => (readableBytes << 2) / 3;
protected internal abstract void EncodeInitialLine(IByteBuffer buf, T message);
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using DotNetty.Common.Utilities;
public class HttpRequestDecoder : HttpObjectDecoder
{
public HttpRequestDecoder()
{
}
public HttpRequestDecoder(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize, true)
{
}
public HttpRequestDecoder(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize, true, validateHeaders)
{
}
public HttpRequestDecoder(
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders,
int initialBufferSize)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize, true, validateHeaders, initialBufferSize)
{
}
protected sealed override IHttpMessage CreateMessage(AsciiString[] initialLine) =>
new DefaultHttpRequest(
HttpVersion.ValueOf(initialLine[2]),
HttpMethod.ValueOf(initialLine[0]), initialLine[1].ToString(), this.ValidateHeaders);
protected override IHttpMessage CreateInvalidMessage() => new DefaultFullHttpRequest(HttpVersion.Http10, HttpMethod.Get, "/bad-request", this.ValidateHeaders);
protected override bool IsDecodingRequest() => true;
}
}

Просмотреть файл

@ -0,0 +1,79 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using static HttpConstants;
public class HttpRequestEncoder : HttpObjectEncoder<IHttpRequest>
{
const char Slash = '/';
const char QuestionMark = '?';
const int SlashAndSpaceShort = (Slash << 8) | HorizontalSpace;
const int SpaceSlashAndSpaceMedium = (HorizontalSpace << 16) | SlashAndSpaceShort;
public override bool AcceptOutboundMessage(object msg) => base.AcceptOutboundMessage(msg) && !(msg is IHttpResponse);
protected internal override void EncodeInitialLine(IByteBuffer buf, IHttpRequest request)
{
ByteBufferUtil.Copy(request.Method.AsciiName, buf);
string uri = request.Uri;
if (string.IsNullOrEmpty(uri))
{
// Add / as absolute path if no is present.
// See http://tools.ietf.org/html/rfc2616#section-5.1.2
buf.WriteMedium(SpaceSlashAndSpaceMedium);
}
else
{
var uriCharSequence = new StringBuilderCharSequence();
uriCharSequence.Append(uri);
bool needSlash = false;
int start = uri.IndexOf("://", StringComparison.Ordinal);
if (start != -1 && uri[0] != Slash)
{
start += 3;
// Correctly handle query params.
// See https://github.com/netty/netty/issues/2732
int index = uri.IndexOf(QuestionMark, start);
if (index == -1)
{
if (uri.LastIndexOf(Slash) < start)
{
needSlash = true;
}
}
else
{
if (uri.LastIndexOf(Slash, index) < start)
{
uriCharSequence.Insert(index, Slash);
}
}
}
buf.WriteByte(HorizontalSpace).WriteCharSequence(uriCharSequence, Encoding.UTF8);
if (needSlash)
{
// write "/ " after uri
buf.WriteShort(SlashAndSpaceShort);
}
else
{
buf.WriteByte(HorizontalSpace);
}
}
request.ProtocolVersion.Encode(buf);
buf.WriteShort(CrlfShort);
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using DotNetty.Common.Utilities;
public class HttpResponseDecoder : HttpObjectDecoder
{
static readonly HttpResponseStatus UnknownStatus = new HttpResponseStatus(999, new AsciiString("Unknown"));
public HttpResponseDecoder()
{
}
public HttpResponseDecoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize, true)
{
}
public HttpResponseDecoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize, true, validateHeaders)
{
}
public HttpResponseDecoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders, int initialBufferSize)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize, true, validateHeaders, initialBufferSize)
{
}
protected sealed override IHttpMessage CreateMessage(AsciiString[] initialLine) =>
new DefaultHttpResponse(
HttpVersion.ValueOf(initialLine[0]),
HttpResponseStatus.ValueOf(initialLine[1].ParseInt() , initialLine[2]), this.ValidateHeaders);
protected override IHttpMessage CreateInvalidMessage() => new DefaultFullHttpResponse(HttpVersion.Http10, UnknownStatus, this.ValidateHeaders);
protected override bool IsDecodingRequest() => false;
}
}

Просмотреть файл

@ -0,0 +1,61 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using DotNetty.Buffers;
public class HttpResponseEncoder : HttpObjectEncoder<IHttpResponse>
{
public override bool AcceptOutboundMessage(object msg) => base.AcceptOutboundMessage(msg) && !(msg is IHttpRequest);
protected internal override void EncodeInitialLine(IByteBuffer buf, IHttpResponse response)
{
response.ProtocolVersion.Encode(buf);
buf.WriteByte(HttpConstants.HorizontalSpace);
response.Status.Encode(buf);
buf.WriteShort(HttpConstants.CrlfShort);
}
protected override void SanitizeHeadersBeforeEncode(IHttpResponse msg, bool isAlwaysEmpty)
{
if (isAlwaysEmpty)
{
HttpResponseStatus status = msg.Status;
if (status.CodeClass == HttpStatusClass.Informational
|| status.Code == HttpResponseStatus.NoContent.Code)
{
// Stripping Content-Length:
// See https://tools.ietf.org/html/rfc7230#section-3.3.2
msg.Headers.Remove(HttpHeaderNames.ContentLength);
// Stripping Transfer-Encoding:
// See https://tools.ietf.org/html/rfc7230#section-3.3.1
msg.Headers.Remove(HttpHeaderNames.TransferEncoding);
}
}
}
protected override bool IsContentAlwaysEmpty(IHttpResponse msg)
{
// Correctly handle special cases as stated in:
// https://tools.ietf.org/html/rfc7230#section-3.3.3
HttpResponseStatus status = msg.Status;
if (status.CodeClass == HttpStatusClass.Informational)
{
if (status.Code == HttpResponseStatus.SwitchingProtocols.Code)
{
// We need special handling for WebSockets version 00 as it will include an body.
// Fortunally this version should not really be used in the wild very often.
// See https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#section-1.2
return msg.Headers.Contains(HttpHeaderNames.SecWebsocketVersion);
}
return true;
}
return status.Code == HttpResponseStatus.NoContent.Code
|| status.Code == HttpResponseStatus.NotModified.Code;
}
}
}

Просмотреть файл

@ -0,0 +1,560 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using System;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
/**
* The response code and its description of HTTP or its derived protocols, such as
* <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
* <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
*/
public class HttpResponseStatus : IComparable<HttpResponseStatus>
{
/**
* 100 Continue
*/
public static readonly HttpResponseStatus Continue = NewStatus(100, "Continue");
/**
* 101 Switching Protocols
*/
public static readonly HttpResponseStatus SwitchingProtocols = NewStatus(101, "Switching Protocols");
/**
* 102 Processing (WebDAV, RFC2518)
*/
public static readonly HttpResponseStatus Processing = NewStatus(102, "Processing");
/**
* 200 OK
*/
public static readonly HttpResponseStatus OK = NewStatus(200, "OK");
/**
* 201 Created
*/
public static readonly HttpResponseStatus Created = NewStatus(201, "Created");
/**
* 202 Accepted
*/
public static readonly HttpResponseStatus Accepted = NewStatus(202, "Accepted");
/**
* 203 Non-Authoritative Information (since HTTP/1.1)
*/
public static readonly HttpResponseStatus NonAuthoritativeInformation = NewStatus(203, "Non-Authoritative Information");
/**
* 204 No Content
*/
public static readonly HttpResponseStatus NoContent = NewStatus(204, "No Content");
/**
* 205 Reset Content
*/
public static readonly HttpResponseStatus ResetContent = NewStatus(205, "Reset Content");
/**
* 206 Partial Content
*/
public static readonly HttpResponseStatus PartialContent = NewStatus(206, "Partial Content");
/**
* 207 Multi-Status (WebDAV, RFC2518)
*/
public static readonly HttpResponseStatus MultiStatus = NewStatus(207, "Multi-Status");
/**
* 300 Multiple Choices
*/
public static readonly HttpResponseStatus MultipleChoices = NewStatus(300, "Multiple Choices");
/**
* 301 Moved Permanently
*/
public static readonly HttpResponseStatus MovedPermanently = NewStatus(301, "Moved Permanently");
/**
* 302 Found
*/
public static readonly HttpResponseStatus Found = NewStatus(302, "Found");
/**
* 303 See Other (since HTTP/1.1)
*/
public static readonly HttpResponseStatus SeeOther = NewStatus(303, "See Other");
/**
* 304 Not Modified
*/
public static readonly HttpResponseStatus NotModified = NewStatus(304, "Not Modified");
/**
* 305 Use Proxy (since HTTP/1.1)
*/
public static readonly HttpResponseStatus UseProxy = NewStatus(305, "Use Proxy");
/**
* 307 Temporary Redirect (since HTTP/1.1)
*/
public static readonly HttpResponseStatus TemporaryRedirect = NewStatus(307, "Temporary Redirect");
/**
* 308 Permanent Redirect (RFC7538)
*/
public static readonly HttpResponseStatus PermanentRedirect = NewStatus(308, "Permanent Redirect");
/**
* 400 Bad Request
*/
public static readonly HttpResponseStatus BadRequest = NewStatus(400, "Bad Request");
/**
* 401 Unauthorized
*/
public static readonly HttpResponseStatus Unauthorized = NewStatus(401, "Unauthorized");
/**
* 402 Payment Required
*/
public static readonly HttpResponseStatus PaymentRequired = NewStatus(402, "Payment Required");
/**
* 403 Forbidden
*/
public static readonly HttpResponseStatus Forbidden = NewStatus(403, "Forbidden");
/**
* 404 Not Found
*/
public static readonly HttpResponseStatus NotFound = NewStatus(404, "Not Found");
/**
* 405 Method Not Allowed
*/
public static readonly HttpResponseStatus MethodNotAllowed = NewStatus(405, "Method Not Allowed");
/**
* 406 Not Acceptable
*/
public static readonly HttpResponseStatus NotAcceptable = NewStatus(406, "Not Acceptable");
/**
* 407 Proxy Authentication Required
*/
public static readonly HttpResponseStatus ProxyAuthenticationRequired = NewStatus(407, "Proxy Authentication Required");
/**
* 408 Request Timeout
*/
public static readonly HttpResponseStatus RequestTimeout = NewStatus(408, "Request Timeout");
/**
* 409 Conflict
*/
public static readonly HttpResponseStatus Conflict = NewStatus(409, "Conflict");
/**
* 410 Gone
*/
public static readonly HttpResponseStatus Gone = NewStatus(410, "Gone");
/**
* 411 Length Required
*/
public static readonly HttpResponseStatus LengthRequired = NewStatus(411, "Length Required");
/**
* 412 Precondition Failed
*/
public static readonly HttpResponseStatus PreconditionFailed = NewStatus(412, "Precondition Failed");
/**
* 413 Request Entity Too Large
*/
public static readonly HttpResponseStatus RequestEntityTooLarge = NewStatus(413, "Request Entity Too Large");
/**
* 414 Request-URI Too Long
*/
public static readonly HttpResponseStatus RequestUriTooLong = NewStatus(414, "Request-URI Too Long");
/**
* 415 Unsupported Media Type
*/
public static readonly HttpResponseStatus UnsupportedMediaType = NewStatus(415, "Unsupported Media Type");
/**
* 416 Requested Range Not Satisfiable
*/
public static readonly HttpResponseStatus RequestedRangeNotSatisfiable = NewStatus(416, "Requested Range Not Satisfiable");
/**
* 417 Expectation Failed
*/
public static readonly HttpResponseStatus ExpectationFailed = NewStatus(417, "Expectation Failed");
/**
* 421 Misdirected Request
*
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-15#section-9.1.2">421 Status Code</a>
*/
public static readonly HttpResponseStatus MisdirectedRequest = NewStatus(421, "Misdirected Request");
/**
* 422 Unprocessable Entity (WebDAV, RFC4918)
*/
public static readonly HttpResponseStatus UnprocessableEntity = NewStatus(422, "Unprocessable Entity");
/**
* 423 Locked (WebDAV, RFC4918)
*/
public static readonly HttpResponseStatus Locked = NewStatus(423, "Locked");
/**
* 424 Failed Dependency (WebDAV, RFC4918)
*/
public static readonly HttpResponseStatus FailedDependency = NewStatus(424, "Failed Dependency");
/**
* 425 Unordered Collection (WebDAV, RFC3648)
*/
public static readonly HttpResponseStatus UnorderedCollection = NewStatus(425, "Unordered Collection");
/**
* 426 Upgrade Required (RFC2817)
*/
public static readonly HttpResponseStatus UpgradeRequired = NewStatus(426, "Upgrade Required");
/**
* 428 Precondition Required (RFC6585)
*/
public static readonly HttpResponseStatus PreconditionRequired = NewStatus(428, "Precondition Required");
/**
* 429 Too Many Requests (RFC6585)
*/
public static readonly HttpResponseStatus TooManyRequests = NewStatus(429, "Too Many Requests");
/**
* 431 Request Header Fields Too Large (RFC6585)
*/
public static readonly HttpResponseStatus RequestHeaderFieldsTooLarge = NewStatus(431, "Request Header Fields Too Large");
/**
* 500 Internal Server Error
*/
public static readonly HttpResponseStatus InternalServerError = NewStatus(500, "Internal Server Error");
/**
* 501 Not Implemented
*/
public static readonly HttpResponseStatus NotImplemented = NewStatus(501, "Not Implemented");
/**
* 502 Bad Gateway
*/
public static readonly HttpResponseStatus BadGateway = NewStatus(502, "Bad Gateway");
/**
* 503 Service Unavailable
*/
public static readonly HttpResponseStatus ServiceUnavailable = NewStatus(503, "Service Unavailable");
/**
* 504 Gateway Timeout
*/
public static readonly HttpResponseStatus GatewayTimeout = NewStatus(504, "Gateway Timeout");
/**
* 505 HTTP Version Not Supported
*/
public static readonly HttpResponseStatus HttpVersionNotSupported = NewStatus(505, "HTTP Version Not Supported");
/**
* 506 Variant Also Negotiates (RFC2295)
*/
public static readonly HttpResponseStatus VariantAlsoNegotiates = NewStatus(506, "Variant Also Negotiates");
/**
* 507 Insufficient Storage (WebDAV, RFC4918)
*/
public static readonly HttpResponseStatus InsufficientStorage = NewStatus(507, "Insufficient Storage");
/**
* 510 Not Extended (RFC2774)
*/
public static readonly HttpResponseStatus NotExtended = NewStatus(510, "Not Extended");
/**
* 511 Network Authentication Required (RFC6585)
*/
public static readonly HttpResponseStatus NetworkAuthenticationRequired = NewStatus(511, "Network Authentication Required");
static HttpResponseStatus NewStatus(int statusCode, string reasonPhrase) => new HttpResponseStatus(statusCode, new AsciiString(reasonPhrase), true);
// Returns the {@link HttpResponseStatus} represented by the specified code.
// If the specified code is a standard HTTP getStatus code, a cached instance
// will be returned. Otherwise, a new instance will be returned.
public static HttpResponseStatus ValueOf(int code) => ValueOf0(code) ?? new HttpResponseStatus(code);
static HttpResponseStatus ValueOf0(int code)
{
switch (code)
{
case 100:
return Continue;
case 101:
return SwitchingProtocols;
case 102:
return Processing;
case 200:
return OK;
case 201:
return Created;
case 202:
return Accepted;
case 203:
return NonAuthoritativeInformation;
case 204:
return NoContent;
case 205:
return ResetContent;
case 206:
return PartialContent;
case 207:
return MultiStatus;
case 300:
return MultipleChoices;
case 301:
return MovedPermanently;
case 302:
return Found;
case 303:
return SeeOther;
case 304:
return NotModified;
case 305:
return UseProxy;
case 307:
return TemporaryRedirect;
case 308:
return PermanentRedirect;
case 400:
return BadRequest;
case 401:
return Unauthorized;
case 402:
return PaymentRequired;
case 403:
return Forbidden;
case 404:
return NotFound;
case 405:
return MethodNotAllowed;
case 406:
return NotAcceptable;
case 407:
return ProxyAuthenticationRequired;
case 408:
return RequestTimeout;
case 409:
return Conflict;
case 410:
return Gone;
case 411:
return LengthRequired;
case 412:
return PreconditionFailed;
case 413:
return RequestEntityTooLarge;
case 414:
return RequestUriTooLong;
case 415:
return UnsupportedMediaType;
case 416:
return RequestedRangeNotSatisfiable;
case 417:
return ExpectationFailed;
case 421:
return MisdirectedRequest;
case 422:
return UnprocessableEntity;
case 423:
return Locked;
case 424:
return FailedDependency;
case 425:
return UnorderedCollection;
case 426:
return UpgradeRequired;
case 428:
return PreconditionRequired;
case 429:
return TooManyRequests;
case 431:
return RequestHeaderFieldsTooLarge;
case 500:
return InternalServerError;
case 501:
return NotImplemented;
case 502:
return BadGateway;
case 503:
return ServiceUnavailable;
case 504:
return GatewayTimeout;
case 505:
return HttpVersionNotSupported;
case 506:
return VariantAlsoNegotiates;
case 507:
return InsufficientStorage;
case 510:
return NotExtended;
case 511:
return NetworkAuthenticationRequired;
}
return null;
}
public static HttpResponseStatus ValueOf(int code, AsciiString reasonPhrase)
{
HttpResponseStatus responseStatus = ValueOf0(code);
return responseStatus != null && responseStatus.ReasonPhrase.ContentEquals(reasonPhrase)
? responseStatus
: new HttpResponseStatus(code, reasonPhrase);
}
public static HttpResponseStatus ParseLine(ICharSequence line) => line is AsciiString asciiString ? ParseLine(asciiString) : ParseLine(line.ToString());
public static HttpResponseStatus ParseLine(string line)
{
try
{
int space = line.IndexOf(' ');
return space == -1
? ValueOf(int.Parse(line))
: ValueOf(int.Parse(line.Substring(0, space)), new AsciiString(line.Substring(space + 1)));
}
catch (Exception e)
{
throw new ArgumentException($"malformed status line: {line}", e);
}
}
public static HttpResponseStatus ParseLine(AsciiString line)
{
try
{
int space = line.ForEachByte(ByteProcessor.FindAsciiSpace);
return space == -1
? ValueOf(line.ParseInt())
: ValueOf(line.ParseInt(0, space), (AsciiString)line.SubSequence(space + 1));
}
catch (Exception e)
{
throw new ArgumentException($"malformed status line: {line}", e);
}
}
readonly int code;
readonly AsciiString codeAsText;
readonly HttpStatusClass codeClass;
readonly AsciiString reasonPhrase;
readonly byte[] bytes;
HttpResponseStatus(int code)
: this(code, new AsciiString($"{HttpStatusClass.ValueOf(code).DefaultReasonPhrase} ({code})"), false)
{
}
public HttpResponseStatus(int code, AsciiString reasonPhrase)
: this(code, reasonPhrase, false)
{
}
public HttpResponseStatus(int code, AsciiString reasonPhrase, bool bytes)
{
if (code < 0)
{
throw new ArgumentException($"code: {code} (expected: 0+)");
}
if (reasonPhrase == null)
{
throw new ArgumentException(nameof(reasonPhrase));
}
// ReSharper disable once ForCanBeConvertedToForeach
for (int i = 0; i < reasonPhrase.Count; i++)
{
char c = reasonPhrase[i];
// Check prohibited characters.
switch (c)
{
case '\n':
case '\r':
throw new ArgumentException($"reasonPhrase contains one of the following prohibited characters: \\r\\n: {reasonPhrase}");
}
}
this.code = code;
this.codeAsText = new AsciiString(Convert.ToString(code));
this.reasonPhrase = reasonPhrase;
this.bytes = bytes ? Encoding.ASCII.GetBytes($"{code} {reasonPhrase}") : null;
this.codeClass = HttpStatusClass.ValueOf(code);
}
public int Code => this.code;
public AsciiString CodeAsText => this.codeAsText;
public AsciiString ReasonPhrase => this.reasonPhrase;
public HttpStatusClass CodeClass => this.codeClass;
public override int GetHashCode() => this.code;
public override bool Equals(object obj)
{
if (!(obj is HttpResponseStatus other))
{
return false;
}
return this.code == other.code;
}
public int CompareTo(HttpResponseStatus other) => this.code - other.code;
public override string ToString() =>
new StringBuilder(this.ReasonPhrase.Count + 4)
.Append(this.Code)
.Append(' ')
.Append(this.ReasonPhrase)
.ToString();
internal void Encode(IByteBuffer buf)
{
if (this.bytes == null)
{
ByteBufferUtil.Copy(this.codeAsText, buf);
buf.WriteByte(HttpConstants.HorizontalSpace);
buf.WriteCharSequence(this.reasonPhrase, Encoding.ASCII);
}
else
{
buf.WriteBytes(this.bytes);
}
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using DotNetty.Common.Utilities;
public sealed class HttpScheme
{
readonly int port;
readonly AsciiString name;
HttpScheme(int port, string name)
{
this.port = port;
this.name = AsciiString.Cached(name);
}
public AsciiString Name => this.name;
public int Port => this.port;
public override bool Equals(object obj)
{
if (!(obj is HttpScheme other))
{
return false;
}
return other.port == this.port && other.name.Equals(this.name);
}
public override int GetHashCode() => this.port * 31 + this.name.GetHashCode();
public override string ToString() => this.name.ToString();
}
}

Просмотреть файл

@ -0,0 +1,109 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System.Collections.Generic;
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
public class HttpServerCodec : CombinedChannelDuplexHandler<HttpRequestDecoder, HttpResponseEncoder>,
HttpServerUpgradeHandler.ISourceCodec
{
/** A queue that is used for correlating a request and a response. */
readonly Queue<HttpMethod> queue = new Queue<HttpMethod>();
public HttpServerCodec() : this(4096, 8192, 8192)
{
}
public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize)
{
this.Init(new HttpServerRequestDecoder(this, maxInitialLineLength, maxHeaderSize, maxChunkSize),
new HttpServerResponseEncoder(this));
}
public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders)
{
this.Init(new HttpServerRequestDecoder(this, maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders),
new HttpServerResponseEncoder(this));
}
public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders, int initialBufferSize)
{
this.Init(new HttpServerRequestDecoder(this, maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize),
new HttpServerResponseEncoder(this));
}
public void UpgradeFrom(IChannelHandlerContext ctx) => ctx.Channel.Pipeline.Remove(this);
sealed class HttpServerRequestDecoder : HttpRequestDecoder
{
readonly HttpServerCodec serverCodec;
public HttpServerRequestDecoder(HttpServerCodec serverCodec, int maxInitialLineLength, int maxHeaderSize, int maxChunkSize)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize)
{
this.serverCodec = serverCodec;
}
public HttpServerRequestDecoder(HttpServerCodec serverCodec, int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders)
:base(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders)
{
this.serverCodec = serverCodec;
}
public HttpServerRequestDecoder(HttpServerCodec serverCodec,
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, bool validateHeaders, int initialBufferSize)
: base(maxInitialLineLength, maxHeaderSize, maxChunkSize, validateHeaders, initialBufferSize)
{
this.serverCodec = serverCodec;
}
protected override void Decode(IChannelHandlerContext context, IByteBuffer buffer, List<object> output)
{
int oldSize = output.Count;
base.Decode(context, buffer, output);
int size = output.Count;
for (int i = oldSize; i < size; i++)
{
if (output[i] is IHttpRequest request)
{
this.serverCodec.queue.Enqueue(request.Method);
}
}
}
}
sealed class HttpServerResponseEncoder : HttpResponseEncoder
{
readonly HttpServerCodec serverCodec;
HttpMethod method;
public HttpServerResponseEncoder(HttpServerCodec serverCodec)
{
this.serverCodec = serverCodec;
}
protected override void SanitizeHeadersBeforeEncode(IHttpResponse msg, bool isAlwaysEmpty)
{
if (!isAlwaysEmpty && ReferenceEquals(this.method, HttpMethod.Connect) && msg.Status.CodeClass == HttpStatusClass.Success)
{
// Stripping Transfer-Encoding:
// See https://tools.ietf.org/html/rfc7230#section-3.3.1
msg.Headers.Remove(HttpHeaderNames.TransferEncoding);
return;
}
base.SanitizeHeadersBeforeEncode(msg, isAlwaysEmpty);
}
protected override bool IsContentAlwaysEmpty(IHttpResponse msg)
{
this.method = this.serverCodec.queue.Count > 0 ? this.serverCodec.queue.Dequeue() : null;
return HttpMethod.Head.Equals(this.method) || base.IsContentAlwaysEmpty(msg);
}
}
}
}

Просмотреть файл

@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class HttpServerExpectContinueHandler : ChannelHandlerAdapter
{
static readonly IFullHttpResponse ExpectationFailed = new DefaultFullHttpResponse(
HttpVersion.Http11, HttpResponseStatus.ExpectationFailed, Unpooled.Empty);
static readonly IFullHttpResponse Accept = new DefaultFullHttpResponse(
HttpVersion.Http11, HttpResponseStatus.Continue, Unpooled.Empty);
static HttpServerExpectContinueHandler()
{
ExpectationFailed.Headers.Set(HttpHeaderNames.ContentLength, HttpHeaderValues.Zero);
Accept.Headers.Set(HttpHeaderNames.ContentLength, HttpHeaderValues.Zero);
}
protected virtual IHttpResponse AcceptMessage(IHttpRequest request) => (IHttpResponse)Accept.RetainedDuplicate();
protected virtual IHttpResponse RejectResponse(IHttpRequest request) => (IHttpResponse)ExpectationFailed.RetainedDuplicate();
public override void ChannelRead(IChannelHandlerContext context, object message)
{
if (message is IHttpRequest req)
{
if (HttpUtil.Is100ContinueExpected(req))
{
IHttpResponse accept = this.AcceptMessage(req);
if (accept == null)
{
// the expectation failed so we refuse the request.
IHttpResponse rejection = this.RejectResponse(req);
ReferenceCountUtil.Release(message);
context.WriteAndFlushAsync(rejection)
.ContinueWith(CloseOnFailure, context, TaskContinuationOptions.ExecuteSynchronously);
return;
}
context.WriteAndFlushAsync(accept)
.ContinueWith(CloseOnFailure, context, TaskContinuationOptions.ExecuteSynchronously);
req.Headers.Remove(HttpHeaderNames.Expect);
}
base.ChannelRead(context, message);
}
}
static Task CloseOnFailure(Task task, object state)
{
if (task.IsFaulted)
{
var context = (IChannelHandlerContext)state;
return context.CloseAsync();
}
return TaskEx.Completed;
}
}
}

Просмотреть файл

@ -0,0 +1,98 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System.Threading.Tasks;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using static HttpUtil;
public class HttpServerKeepAliveHandler : ChannelDuplexHandler
{
static readonly AsciiString MultipartPrefix = new AsciiString("multipart");
bool persistentConnection = true;
// Track pending responses to support client pipelining: https://tools.ietf.org/html/rfc7230#section-6.3.2
int pendingResponses;
public override void ChannelRead(IChannelHandlerContext context, object message)
{
// read message and track if it was keepAlive
if (message is IHttpRequest request)
{
if (this.persistentConnection)
{
this.pendingResponses += 1;
this.persistentConnection = IsKeepAlive(request);
}
}
base.ChannelRead(context, message);
}
public override Task WriteAsync(IChannelHandlerContext context, object message)
{
// modify message on way out to add headers if needed
if (message is IHttpResponse response)
{
this.TrackResponse(response);
// Assume the response writer knows if they can persist or not and sets isKeepAlive on the response
if (!IsKeepAlive(response) || !IsSelfDefinedMessageLength(response))
{
// No longer keep alive as the client can't tell when the message is done unless we close connection
this.pendingResponses = 0;
this.persistentConnection = false;
}
// Server might think it can keep connection alive, but we should fix response header if we know better
if (!this.ShouldKeepAlive())
{
SetKeepAlive(response, false);
}
}
if (message is ILastHttpContent && !this.ShouldKeepAlive())
{
return base.WriteAsync(context, message)
.ContinueWith(CloseOnComplete, context, TaskContinuationOptions.ExecuteSynchronously);
}
return base.WriteAsync(context, message);
}
static Task CloseOnComplete(Task task, object state)
{
var context = (IChannelHandlerContext)state;
return context.CloseAsync();
}
void TrackResponse(IHttpResponse response)
{
if (!IsInformational(response))
{
this.pendingResponses -= 1;
}
}
bool ShouldKeepAlive() => this.pendingResponses != 0 || this.persistentConnection;
/// <summary>
/// Keep-alive only works if the client can detect when the message has ended without relying on the connection being
/// closed.
/// https://tools.ietf.org/html/rfc7230#section-6.3
/// https://tools.ietf.org/html/rfc7230#section-3.3.2
/// https://tools.ietf.org/html/rfc7230#section-3.3.3
/// </summary>
/// <param name="response">The HttpResponse to check</param>
/// <returns>true if the response has a self defined message length.</returns>
static bool IsSelfDefinedMessageLength(IHttpResponse response) =>
IsContentLengthSet(response) || IsTransferEncodingChunked(response) || IsMultipart(response)
|| IsInformational(response) || response.Status.Code == HttpResponseStatus.NoContent.Code;
static bool IsInformational(IHttpResponse response) => response.Status.CodeClass == HttpStatusClass.Informational;
static bool IsMultipart(IHttpResponse response)
{
return response.Headers.TryGet(HttpHeaderNames.ContentType, out ICharSequence contentType)
&& contentType.RegionMatchesIgnoreCase(0, MultipartPrefix, 0, MultipartPrefix.Count);
}
}
}

Просмотреть файл

@ -0,0 +1,333 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Common;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class HttpServerUpgradeHandler : HttpObjectAggregator
{
/// <summary>
/// The source codec that is used in the pipeline initially.
/// </summary>
public interface ISourceCodec
{
/// <summary>
/// Removes this codec (i.e. all associated handlers) from the pipeline.
/// </summary>
void UpgradeFrom(IChannelHandlerContext ctx);
}
/// <summary>
/// A codec that the source can be upgraded to.
/// </summary>
public interface IUpgradeCodec
{
/// <summary>
/// Gets all protocol-specific headers required by this protocol for a successful upgrade.
/// Any supplied header will be required to appear in the {@link HttpHeaderNames#CONNECTION} header as well.
/// </summary>
ICollection<AsciiString> RequiredUpgradeHeaders { get; }
/// <summary>
/// Prepares the {@code upgradeHeaders} for a protocol update based upon the contents of {@code upgradeRequest}.
/// This method returns a boolean value to proceed or abort the upgrade in progress. If {@code false} is
/// returned, the upgrade is aborted and the {@code upgradeRequest} will be passed through the inbound pipeline
/// as if no upgrade was performed. If {@code true} is returned, the upgrade will proceed to the next
/// step which invokes {@link #upgradeTo}. When returning {@code true}, you can add headers to
/// the {@code upgradeHeaders} so that they are added to the 101 Switching protocols response.
/// </summary>
bool PrepareUpgradeResponse(IChannelHandlerContext ctx, IFullHttpRequest upgradeRequest, HttpHeaders upgradeHeaders);
/// <summary>
/// Performs an HTTP protocol upgrade from the source codec. This method is responsible for
/// adding all handlers required for the new protocol.
///
/// ctx the context for the current handler.
/// upgradeRequest the request that triggered the upgrade to this protocol.
/// </summary>
void UpgradeTo(IChannelHandlerContext ctx, IFullHttpRequest upgradeRequest);
}
/// <summary>
/// Creates a new UpgradeCodec for the requested protocol name.
/// </summary>
public interface IUpgradeCodecFactory
{
/// <summary>
/// Invoked by {@link HttpServerUpgradeHandler} for all the requested protocol names in the order of
/// the client preference.The first non-{@code null} {@link UpgradeCodec} returned by this method
/// will be selected.
/// </summary>
IUpgradeCodec NewUpgradeCodec(ICharSequence protocol);
}
public sealed class UpgradeEvent : IReferenceCounted
{
readonly ICharSequence protocol;
readonly IFullHttpRequest upgradeRequest;
internal UpgradeEvent(ICharSequence protocol, IFullHttpRequest upgradeRequest)
{
this.protocol = protocol;
this.upgradeRequest = upgradeRequest;
}
public ICharSequence Protocol => this.protocol;
public IFullHttpRequest UpgradeRequest => this.upgradeRequest;
public int ReferenceCount => this.upgradeRequest.ReferenceCount;
public IReferenceCounted Retain()
{
this.upgradeRequest.Retain();
return this;
}
public IReferenceCounted Retain(int increment)
{
this.upgradeRequest.Retain(increment);
return this;
}
public IReferenceCounted Touch()
{
this.upgradeRequest.Touch();
return this;
}
public IReferenceCounted Touch(object hint)
{
this.upgradeRequest.Touch(hint);
return this;
}
public bool Release() => this.upgradeRequest.Release();
public bool Release(int decrement) => this.upgradeRequest.Release(decrement);
public override string ToString() => $"UpgradeEvent [protocol={this.protocol}, upgradeRequest={this.upgradeRequest}]";
}
readonly ISourceCodec sourceCodec;
readonly IUpgradeCodecFactory upgradeCodecFactory;
bool handlingUpgrade;
public HttpServerUpgradeHandler(ISourceCodec sourceCodec, IUpgradeCodecFactory upgradeCodecFactory)
: this(sourceCodec, upgradeCodecFactory, 0)
{
}
public HttpServerUpgradeHandler(ISourceCodec sourceCodec, IUpgradeCodecFactory upgradeCodecFactory, int maxContentLength)
: base(maxContentLength)
{
Contract.Requires(sourceCodec != null);
Contract.Requires(upgradeCodecFactory != null);
this.sourceCodec = sourceCodec;
this.upgradeCodecFactory = upgradeCodecFactory;
}
protected override void Decode(IChannelHandlerContext context, IHttpObject message, List<object> output)
{
// Determine if we're already handling an upgrade request or just starting a new one.
this.handlingUpgrade |= IsUpgradeRequest(message);
if (!this.handlingUpgrade)
{
// Not handling an upgrade request, just pass it to the next handler.
ReferenceCountUtil.Retain(message);
output.Add(message);
return;
}
if (message is IFullHttpRequest fullRequest)
{
ReferenceCountUtil.Retain(fullRequest);
output.Add(fullRequest);
}
else
{
// Call the base class to handle the aggregation of the full request.
base.Decode(context, message, output);
if (output.Count == 0)
{
// The full request hasn't been created yet, still awaiting more data.
return;
}
// Finished aggregating the full request, get it from the output list.
Debug.Assert(output.Count == 1);
this.handlingUpgrade = false;
fullRequest = (IFullHttpRequest)output[0];
}
if (this.Upgrade(context, fullRequest))
{
// The upgrade was successful, remove the message from the output list
// so that it's not propagated to the next handler. This request will
// be propagated as a user event instead.
output.Clear();
}
// The upgrade did not succeed, just allow the full request to propagate to the
// next handler.
}
static bool IsUpgradeRequest(IHttpObject msg)
{
if (!(msg is IHttpRequest request))
{
return false;
}
return request.Headers.Contains(HttpHeaderNames.Upgrade);
}
bool Upgrade(IChannelHandlerContext ctx, IFullHttpRequest request)
{
// Select the best protocol based on those requested in the UPGRADE header.
IList<ICharSequence> requestedProtocols = SplitHeader(request.Headers.Get(HttpHeaderNames.Upgrade, null));
int numRequestedProtocols = requestedProtocols.Count;
IUpgradeCodec upgradeCodec = null;
ICharSequence upgradeProtocol = null;
for (int i = 0; i < numRequestedProtocols; i++)
{
ICharSequence p = requestedProtocols[i];
IUpgradeCodec c = this.upgradeCodecFactory.NewUpgradeCodec(p);
if (c != null)
{
upgradeProtocol = p;
upgradeCodec = c;
break;
}
}
if (upgradeCodec == null)
{
// None of the requested protocols are supported, don't upgrade.
return false;
}
// Make sure the CONNECTION header is present.
;
if (!request.Headers.TryGet(HttpHeaderNames.Connection, out ICharSequence connectionHeader))
{
return false;
}
// Make sure the CONNECTION header contains UPGRADE as well as all protocol-specific headers.
ICollection<AsciiString> requiredHeaders = upgradeCodec.RequiredUpgradeHeaders;
IList<ICharSequence> values = SplitHeader(connectionHeader);
if (!AsciiString.ContainsContentEqualsIgnoreCase(values, HttpHeaderNames.Upgrade)
|| !AsciiString.ContainsAllContentEqualsIgnoreCase(values, requiredHeaders))
{
return false;
}
// Ensure that all required protocol-specific headers are found in the request.
foreach (AsciiString requiredHeader in requiredHeaders)
{
if (!request.Headers.Contains(requiredHeader))
{
return false;
}
}
// Prepare and send the upgrade response. Wait for this write to complete before upgrading,
// since we need the old codec in-place to properly encode the response.
IFullHttpResponse upgradeResponse = CreateUpgradeResponse(upgradeProtocol);
if (!upgradeCodec.PrepareUpgradeResponse(ctx, request, upgradeResponse.Headers))
{
return false;
}
// Create the user event to be fired once the upgrade completes.
var upgradeEvent = new UpgradeEvent(upgradeProtocol, request);
IUpgradeCodec finalUpgradeCodec = upgradeCodec;
ctx.WriteAndFlushAsync(upgradeResponse).ContinueWith(t =>
{
try
{
if (t.Status == TaskStatus.RanToCompletion)
{
// Perform the upgrade to the new protocol.
this.sourceCodec.UpgradeFrom(ctx);
finalUpgradeCodec.UpgradeTo(ctx, request);
// Notify that the upgrade has occurred. Retain the event to offset
// the release() in the finally block.
ctx.FireUserEventTriggered(upgradeEvent.Retain());
// Remove this handler from the pipeline.
ctx.Channel.Pipeline.Remove(this);
}
else
{
ctx.Channel.CloseAsync();
}
}
finally
{
// Release the event if the upgrade event wasn't fired.
upgradeEvent.Release();
}
}, TaskContinuationOptions.ExecuteSynchronously);
return true;
}
static IFullHttpResponse CreateUpgradeResponse(ICharSequence upgradeProtocol)
{
var res = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.SwitchingProtocols,
Unpooled.Empty, false);
res.Headers.Add(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade);
res.Headers.Add(HttpHeaderNames.Upgrade, upgradeProtocol);
return res;
}
static IList<ICharSequence> SplitHeader(ICharSequence header)
{
var builder = new StringBuilder(header.Count);
var protocols = new List<ICharSequence>(4);
// ReSharper disable once ForCanBeConvertedToForeach
for (int i = 0; i < header.Count; ++i)
{
char c = header[i];
if (char.IsWhiteSpace(c))
{
// Don't include any whitespace.
continue;
}
if (c == ',')
{
// Add the string and reset the builder for the next protocol.
// Add the string and reset the builder for the next protocol.
protocols.Add(new AsciiString(builder.ToString()));
builder.Length = 0;
}
else
{
builder.Append(c);
}
}
// Add the last protocol
if (builder.Length > 0)
{
protocols.Add(new AsciiString(builder.ToString()));
}
return protocols;
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using System;
using DotNetty.Common.Utilities;
public struct HttpStatusClass : IEquatable<HttpStatusClass>
{
public static readonly HttpStatusClass Informational = new HttpStatusClass(100, 200, "Informational");
public static readonly HttpStatusClass Success = new HttpStatusClass(200, 300, "Success");
public static readonly HttpStatusClass Redirection = new HttpStatusClass(300, 400, "Redirection");
public static readonly HttpStatusClass ClientError = new HttpStatusClass(400, 500, "Client Error");
public static readonly HttpStatusClass ServerError = new HttpStatusClass(500, 600, "Server Error");
public static readonly HttpStatusClass Unknown = new HttpStatusClass(0, 0, "Unknown Status");
public static HttpStatusClass ValueOf(int code)
{
if (Contains(Informational, code))
{
return Informational;
}
if (Contains(Success, code))
{
return Success;
}
if (Contains(Redirection, code))
{
return Redirection;
}
if (Contains(ClientError, code))
{
return ClientError;
}
if (Contains(ServerError, code))
{
return ServerError;
}
return Unknown;
}
public static HttpStatusClass ValueOf(ICharSequence code)
{
if (code != null && code.Count == 3)
{
char c0 = code[0];
return IsDigit(c0) && IsDigit(code[1]) && IsDigit(code[2])
? ValueOf(Digit(c0) * 100)
: Unknown;
}
return Unknown;
}
static int Digit(char c) => c - '0';
static bool IsDigit(char c) => c >= '0' && c <= '9';
readonly int min;
readonly int max;
readonly AsciiString defaultReasonPhrase;
HttpStatusClass(int min, int max, string defaultReasonPhrase)
{
this.min = min;
this.max = max;
this.defaultReasonPhrase = AsciiString.Cached(defaultReasonPhrase);
}
public bool Contains(int code) => Contains(this, code);
public static bool Contains(HttpStatusClass httpStatusClass, int code)
{
if ((httpStatusClass.min & httpStatusClass.max) == 0)
{
return code < 100 || code >= 600;
}
return code >= httpStatusClass.min && code < httpStatusClass.max;
}
public AsciiString DefaultReasonPhrase => this.defaultReasonPhrase;
public bool Equals(HttpStatusClass other) => this.min == other.min && this.max == other.max;
public override bool Equals(object obj) => obj is HttpStatusClass && this.Equals((HttpStatusClass)obj);
public override int GetHashCode() => this.min.GetHashCode() ^ this.max.GetHashCode();
public static bool operator !=(HttpStatusClass left, HttpStatusClass right) => !(left == right);
public static bool operator ==(HttpStatusClass left, HttpStatusClass right) => left.Equals(right);
}
}

Просмотреть файл

@ -0,0 +1,287 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using System;
using System.Collections.Generic;
using System.Text;
using DotNetty.Common.Utilities;
public static class HttpUtil
{
static readonly AsciiString CharsetEquals = new AsciiString(HttpHeaderValues.Charset + "=");
static readonly AsciiString Semicolon = AsciiString.Cached(";");
public static bool IsKeepAlive(IHttpMessage message)
{
if (message.Headers.TryGet(HttpHeaderNames.Connection, out ICharSequence connection)
&& HttpHeaderValues.Close.ContentEqualsIgnoreCase(connection))
{
return false;
}
if (message.ProtocolVersion.IsKeepAliveDefault)
{
return !HttpHeaderValues.Close.ContentEqualsIgnoreCase(connection);
}
else
{
return HttpHeaderValues.KeepAlive.ContentEqualsIgnoreCase(connection);
}
}
public static void SetKeepAlive(IHttpMessage message, bool keepAlive) => SetKeepAlive(message.Headers, message.ProtocolVersion, keepAlive);
public static void SetKeepAlive(HttpHeaders headers, HttpVersion httpVersion, bool keepAlive)
{
if (httpVersion.IsKeepAliveDefault)
{
if (keepAlive)
{
headers.Remove(HttpHeaderNames.Connection);
}
else
{
headers.Set(HttpHeaderNames.Connection, HttpHeaderValues.Close);
}
}
else
{
if (keepAlive)
{
headers.Set(HttpHeaderNames.Connection, HttpHeaderValues.KeepAlive);
}
else
{
headers.Remove(HttpHeaderNames.Connection);
}
}
}
public static long GetContentLength(IHttpMessage message)
{
if (message.Headers.TryGet(HttpHeaderNames.ContentLength, out ICharSequence value))
{
return CharUtil.ParseLong(value);
}
// We know the content length if it's a Web Socket message even if
// Content-Length header is missing.
long webSocketContentLength = GetWebSocketContentLength(message);
if (webSocketContentLength >= 0)
{
return webSocketContentLength;
}
// Otherwise we don't.
throw new FormatException($"header not found: {HttpHeaderNames.ContentLength}");
}
public static long GetContentLength(IHttpMessage message, long defaultValue)
{
if (message.Headers.TryGet(HttpHeaderNames.ContentLength, out ICharSequence value))
{
return CharUtil.ParseLong(value);
}
// We know the content length if it's a Web Socket message even if
// Content-Length header is missing.
long webSocketContentLength = GetWebSocketContentLength(message);
if (webSocketContentLength >= 0)
{
return webSocketContentLength;
}
// Otherwise we don't.
return defaultValue;
}
public static int GetContentLength(IHttpMessage message, int defaultValue) =>
(int)Math.Min(int.MaxValue, GetContentLength(message, (long)defaultValue));
static int GetWebSocketContentLength(IHttpMessage message)
{
// WebSocket messages have constant content-lengths.
HttpHeaders h = message.Headers;
if (message is IHttpRequest req)
{
if (HttpMethod.Get.Equals(req.Method)
&& h.Contains(HttpHeaderNames.SecWebsocketKey1)
&& h.Contains(HttpHeaderNames.SecWebsocketKey2))
{
return 8;
}
}
else if (message is IHttpResponse res)
{
if (res.Status.Code == 101
&& h.Contains(HttpHeaderNames.SecWebsocketOrigin)
&& h.Contains(HttpHeaderNames.SecWebsocketLocation))
{
return 16;
}
}
// Not a web socket message
return -1;
}
public static void SetContentLength(IHttpMessage message, long length) => message.Headers.Set(HttpHeaderNames.ContentLength, length);
public static bool IsContentLengthSet(IHttpMessage message) => message.Headers.Contains(HttpHeaderNames.ContentLength);
public static bool Is100ContinueExpected(IHttpMessage message)
{
if (!IsExpectHeaderValid(message))
{
return false;
}
ICharSequence expectValue = message.Headers.Get(HttpHeaderNames.Expect, null);
// unquoted tokens in the expect header are case-insensitive, thus 100-continue is case insensitive
return HttpHeaderValues.Continue.ContentEqualsIgnoreCase(expectValue);
}
internal static bool IsUnsupportedExpectation(IHttpMessage message)
{
if (!IsExpectHeaderValid(message))
{
return false;
}
return message.Headers.TryGet(HttpHeaderNames.Expect, out ICharSequence expectValue)
&& !HttpHeaderValues.Continue.ContentEqualsIgnoreCase(expectValue);
}
// Expect: 100-continue is for requests only and it works only on HTTP/1.1 or later. Note further that RFC 7231
// section 5.1.1 says "A server that receives a 100-continue expectation in an HTTP/1.0 request MUST ignore
// that expectation."
static bool IsExpectHeaderValid(IHttpMessage message) => message is IHttpRequest
&& message.ProtocolVersion.CompareTo(HttpVersion.Http11) >= 0;
public static void Set100ContinueExpected(IHttpMessage message, bool expected)
{
if (expected)
{
message.Headers.Set(HttpHeaderNames.Expect, HttpHeaderValues.Continue);
}
else
{
message.Headers.Remove(HttpHeaderNames.Expect);
}
}
public static bool IsTransferEncodingChunked(IHttpMessage message) => message.Headers.Contains(HttpHeaderNames.TransferEncoding, HttpHeaderValues.Chunked, true);
public static void SetTransferEncodingChunked(IHttpMessage m, bool chunked)
{
if (chunked)
{
m.Headers.Set(HttpHeaderNames.TransferEncoding, HttpHeaderValues.Chunked);
m.Headers.Remove(HttpHeaderNames.ContentLength);
}
else
{
IList<ICharSequence> encodings = m.Headers.GetAll(HttpHeaderNames.TransferEncoding);
if (encodings.Count == 0)
{
return;
}
var values = new List<ICharSequence>(encodings);
foreach (ICharSequence value in encodings)
{
if (HttpHeaderValues.Chunked.ContentEqualsIgnoreCase(value))
{
values.Remove(value);
}
}
if (values.Count == 0)
{
m.Headers.Remove(HttpHeaderNames.TransferEncoding);
}
else
{
m.Headers.Set(HttpHeaderNames.TransferEncoding, values);
}
}
}
public static Encoding GetCharset(IHttpMessage message) => GetCharset(message, Encoding.UTF8);
public static Encoding GetCharset(ICharSequence contentTypeValue) => contentTypeValue != null ? GetCharset(contentTypeValue, Encoding.UTF8) : Encoding.UTF8;
public static Encoding GetCharset(IHttpMessage message, Encoding defaultCharset)
{
return message.Headers.TryGet(HttpHeaderNames.ContentType, out ICharSequence contentTypeValue)
? GetCharset(contentTypeValue, defaultCharset)
: defaultCharset;
}
public static Encoding GetCharset(ICharSequence contentTypeValue, Encoding defaultCharset)
{
if (contentTypeValue != null)
{
ICharSequence charsetCharSequence = GetCharsetAsSequence(contentTypeValue);
if (charsetCharSequence != null)
{
try
{
return Encoding.GetEncoding(charsetCharSequence.ToString());
}
catch (ArgumentException)
{
return defaultCharset;
}
}
else
{
return defaultCharset;
}
}
else
{
return defaultCharset;
}
}
public static ICharSequence GetCharsetAsSequence(IHttpMessage message)
=> message.Headers.TryGet(HttpHeaderNames.ContentType, out ICharSequence contentTypeValue) ? GetCharsetAsSequence(contentTypeValue) : null;
public static ICharSequence GetCharsetAsSequence(ICharSequence contentTypeValue)
{
if (contentTypeValue == null)
{
throw new ArgumentException(nameof(contentTypeValue));
}
int indexOfCharset = AsciiString.IndexOfIgnoreCaseAscii(contentTypeValue, CharsetEquals, 0);
if (indexOfCharset != AsciiString.IndexNotFound)
{
int indexOfEncoding = indexOfCharset + CharsetEquals.Count;
if (indexOfEncoding < contentTypeValue.Count)
{
return contentTypeValue.SubSequence(indexOfEncoding, contentTypeValue.Count);
}
}
return null;
}
public static ICharSequence GetMimeType(IHttpMessage message) =>
message.Headers.TryGet(HttpHeaderNames.ContentType, out ICharSequence contentTypeValue) ? GetMimeType(contentTypeValue) : null;
public static ICharSequence GetMimeType(ICharSequence contentTypeValue)
{
if (contentTypeValue == null)
{
throw new ArgumentException(nameof(contentTypeValue));
}
int indexOfSemicolon = AsciiString.IndexOfIgnoreCaseAscii(contentTypeValue, Semicolon, 0);
if (indexOfSemicolon != AsciiString.IndexNotFound)
{
return contentTypeValue.SubSequence(0, indexOfSemicolon);
}
return contentTypeValue.Count > 0 ? contentTypeValue : null;
}
}
}

Просмотреть файл

@ -0,0 +1,239 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http
{
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
public class HttpVersion : IComparable<HttpVersion>, IComparable
{
static readonly Regex VersionPattern = new Regex("^(\\S+)/(\\d+)\\.(\\d+)$", RegexOptions.Compiled);
internal static readonly AsciiString Http10String = new AsciiString("HTTP/1.0");
internal static readonly AsciiString Http11String = new AsciiString("HTTP/1.1");
public static readonly HttpVersion Http10 = new HttpVersion("HTTP", 1, 0, false, true);
public static readonly HttpVersion Http11 = new HttpVersion("HTTP", 1, 1, true, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static HttpVersion ValueOf(AsciiString text)
{
if (text == null)
{
ThrowHelper.ThrowArgumentException_NullText();
}
// ReSharper disable once PossibleNullReferenceException
HttpVersion version = ValueOfInline(text.Array);
if (version != null)
{
return version;
}
// Fall back to slow path
text = text.Trim();
if (text.Count == 0)
{
ThrowHelper.ThrowArgumentException_EmptyText();
}
// Try to match without convert to uppercase first as this is what 99% of all clients
// will send anyway. Also there is a change to the RFC to make it clear that it is
// expected to be case-sensitive
//
// See:
// * http://trac.tools.ietf.org/wg/httpbis/trac/ticket/1
// * http://trac.tools.ietf.org/wg/httpbis/trac/wiki
//
return Version0(text) ?? new HttpVersion(text.ToString(), true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static HttpVersion ValueOfInline(byte[] bytes)
{
if (bytes.Length != 8) return null;
if (bytes[0] != (byte)'H') return null;
if (bytes[1] != (byte)'T') return null;
if (bytes[2] != (byte)'T') return null;
if (bytes[3] != (byte)'P') return null;
if (bytes[4] != (byte)'/') return null;
if (bytes[5] != (byte)'1') return null;
if (bytes[6] != (byte)'.') return null;
switch (bytes[7])
{
case (byte)'1':
return Http11;
case (byte)'0':
return Http10;
default:
return null;
}
}
static HttpVersion Version0(AsciiString text)
{
if (Http11String.Equals(text))
{
return Http11;
}
if (Http10String.Equals(text))
{
return Http10;
}
return null;
}
readonly string protocolName;
readonly int majorVersion;
readonly int minorVersion;
readonly AsciiString text;
readonly bool keepAliveDefault;
readonly byte[] bytes;
public HttpVersion(string text, bool keepAliveDefault)
{
Contract.Requires(text != null);
text = text.Trim().ToUpper();
if (string.IsNullOrEmpty(text))
{
throw new ArgumentException("empty text");
}
Match match = VersionPattern.Match(text);
if (!match.Success)
{
throw new ArgumentException($"invalid version format: {text}");
}
this.protocolName = match.Groups[1].Value;
this.majorVersion = int.Parse(match.Groups[2].Value);
this.minorVersion = int.Parse(match.Groups[3].Value);
this.text = new AsciiString($"{this.ProtocolName}/{this.MajorVersion}.{this.MinorVersion}");
this.keepAliveDefault = keepAliveDefault;
this.bytes = null;
}
HttpVersion(string protocolName, int majorVersion, int minorVersion, bool keepAliveDefault, bool bytes)
{
if (protocolName == null)
{
throw new ArgumentException(nameof(protocolName));
}
protocolName = protocolName.Trim().ToUpper();
if (string.IsNullOrEmpty(protocolName))
{
throw new ArgumentException("empty protocolName");
}
// ReSharper disable once ForCanBeConvertedToForeach
for (int i = 0; i < protocolName.Length; i++)
{
char c = protocolName[i];
if (CharUtil.IsISOControl(c) || char.IsWhiteSpace(c))
{
throw new ArgumentException($"invalid character {c} in protocolName");
}
}
if (majorVersion < 0)
{
throw new ArgumentException("negative majorVersion");
}
if (minorVersion < 0)
{
throw new ArgumentException("negative minorVersion");
}
this.protocolName = protocolName;
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
this.text = new AsciiString(protocolName + '/' + majorVersion + '.' + minorVersion);
this.keepAliveDefault = keepAliveDefault;
this.bytes = bytes ? this.text.Array : null;
}
public string ProtocolName => this.protocolName;
public int MajorVersion => this.majorVersion;
public int MinorVersion => this.minorVersion;
public AsciiString Text => this.text;
public bool IsKeepAliveDefault => this.keepAliveDefault;
public override string ToString() => this.text.ToString();
public override int GetHashCode() => (this.protocolName.GetHashCode() * 31 + this.majorVersion) * 31 + this.minorVersion;
public override bool Equals(object obj)
{
if (!(obj is HttpVersion that))
{
return false;
}
return this.minorVersion == that.minorVersion
&& this.majorVersion == that.majorVersion
&& this.protocolName.Equals(that.protocolName);
}
public int CompareTo(HttpVersion other)
{
int v = string.CompareOrdinal(this.protocolName, other.protocolName);
if (v != 0)
{
return v;
}
v = this.majorVersion - other.majorVersion;
if (v != 0)
{
return v;
}
return this.minorVersion - other.minorVersion;
}
public int CompareTo(object obj)
{
if (ReferenceEquals(this, obj))
{
return 0;
}
if (!(obj is HttpVersion))
{
throw new ArgumentException($"{nameof(obj)} must be of {nameof(HttpVersion)} type");
}
return this.CompareTo((HttpVersion)obj);
}
internal void Encode(IByteBuffer buf)
{
if (this.bytes == null)
{
buf.WriteCharSequence(this.text, Encoding.ASCII);
}
else
{
buf.WriteBytes(this.bytes);
}
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
public interface IFullHttpMessage : IHttpMessage, ILastHttpContent
{
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
public interface IFullHttpRequest : IHttpRequest, IFullHttpMessage
{
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
public interface IFullHttpResponse : IHttpResponse, IFullHttpMessage
{
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
using DotNetty.Buffers;
public interface IHttpContent : IHttpObject, IByteBufferHolder
{
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
public interface IHttpMessage : IHttpObject
{
HttpVersion ProtocolVersion { get; }
IHttpMessage SetProtocolVersion(HttpVersion version);
HttpHeaders Headers { get; }
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
public interface IHttpObject : IDecoderResultProvider
{
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
public interface IHttpRequest : IHttpMessage
{
HttpMethod Method { get; }
IHttpRequest SetMethod(HttpMethod method);
string Uri { get; }
IHttpRequest SetUri(string uri);
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
public interface IHttpResponse : IHttpMessage
{
HttpResponseStatus Status { get; }
IHttpResponse SetStatus(HttpResponseStatus status);
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http
{
public interface ILastHttpContent : IHttpContent
{
HttpHeaders TrailingHeaders { get; }
}
}

Просмотреть файл

@ -0,0 +1,341 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Multipart
{
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common;
using DotNetty.Common.Internal.Logging;
using DotNetty.Common.Utilities;
public abstract class AbstractDiskHttpData : AbstractHttpData
{
static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<AbstractDiskHttpData>();
FileStream fileStream;
protected AbstractDiskHttpData(string name, Encoding charset, long size) : base(name, charset, size)
{
}
protected abstract string DiskFilename { get; }
protected abstract string Prefix { get; }
protected abstract string BaseDirectory { get; }
protected abstract string Postfix { get; }
protected abstract bool DeleteOnExit { get; }
FileStream TempFile()
{
string newpostfix;
string diskFilename = this.DiskFilename;
if (diskFilename != null)
{
newpostfix = '_' + diskFilename;
}
else
{
newpostfix = this.Postfix;
}
string directory = this.BaseDirectory == null
? Path.GetTempPath()
: Path.Combine(Path.GetTempPath(), this.BaseDirectory);
// File.createTempFile
string fileName = Path.Combine(directory, $"{this.Prefix}{Path.GetRandomFileName()}{newpostfix}");
FileStream tmpFile = File.Create(fileName, 4096, // DefaultBufferSize
this.DeleteOnExit ? FileOptions.DeleteOnClose : FileOptions.None);
return tmpFile;
}
public override void SetContent(IByteBuffer buffer)
{
Contract.Requires(buffer != null);
try
{
this.Size = buffer.ReadableBytes;
this.CheckSize(this.Size);
if (this.DefinedSize > 0 && this.DefinedSize < this.Size)
{
throw new IOException($"Out of size: {this.Size} > {this.DefinedSize}");
}
if (this.fileStream == null)
{
this.fileStream = this.TempFile();
}
if (buffer.ReadableBytes == 0)
{
// empty file
return;
}
buffer.GetBytes(buffer.ReaderIndex, this.fileStream, buffer.ReadableBytes);
buffer.SetReaderIndex(buffer.ReaderIndex + buffer.ReadableBytes);
this.fileStream.Flush();
this.SetCompleted();
}
finally
{
// Release the buffer as it was retained before and we not need a reference to it at all
// See https://github.com/netty/netty/issues/1516
buffer.Release();
}
}
public override void AddContent(IByteBuffer buffer, bool last)
{
if (buffer != null)
{
try
{
int localsize = buffer.ReadableBytes;
this.CheckSize(this.Size + localsize);
if (this.DefinedSize > 0 && this.DefinedSize < this.Size + localsize)
{
throw new IOException($"Out of size: {this.Size} > {this.DefinedSize}");
}
if (this.fileStream == null)
{
this.fileStream = this.TempFile();
}
buffer.GetBytes(buffer.ReaderIndex, this.fileStream, buffer.ReadableBytes);
buffer.SetReaderIndex(buffer.ReaderIndex + localsize);
this.fileStream.Flush();
this.Size += buffer.ReadableBytes;
}
finally
{
// Release the buffer as it was retained before and we not need a reference to it at all
// See https://github.com/netty/netty/issues/1516
buffer.Release();
}
}
if (last)
{
if (this.fileStream == null)
{
this.fileStream = this.TempFile();
}
this.SetCompleted();
}
else
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
}
}
public override void SetContent(Stream source)
{
Contract.Requires(source != null);
if (this.fileStream != null)
{
this.Delete();
}
this.fileStream = this.TempFile();
int written = 0;
var bytes = new byte[4096 * 4];
while (true)
{
int read = source.Read(bytes, 0, bytes.Length);
if (read <= 0)
{
break;
}
written += read;
this.CheckSize(written);
this.fileStream.Write(bytes, 0, read);
}
this.fileStream.Flush();
// Reset the position to start for reads
this.fileStream.Position -= written;
this.Size = written;
if (this.DefinedSize > 0 && this.DefinedSize < this.Size)
{
try
{
Delete(this.fileStream);
}
catch (Exception error)
{
Logger.Warn("Failed to delete: {} {}", this.fileStream, error);
}
this.fileStream = null;
throw new IOException($"Out of size: {this.Size} > {this.DefinedSize}");
}
//isRenamed = true;
this.SetCompleted();
}
public override void Delete()
{
if (this.fileStream != null)
{
try
{
Delete(this.fileStream);
}
catch (IOException error)
{
Logger.Warn("Failed to delete file.", error);
}
this.fileStream = null;
}
}
public override byte[] GetBytes() => this.fileStream == null
? ArrayExtensions.ZeroBytes : ReadFrom(this.fileStream);
public override IByteBuffer GetByteBuffer()
{
if (this.fileStream == null)
{
return Unpooled.Empty;
}
byte[] array = ReadFrom(this.fileStream);
return Unpooled.WrappedBuffer(array);
}
public override IByteBuffer GetChunk(int length)
{
if (this.fileStream == null || length == 0)
{
return Unpooled.Empty;
}
int read = 0;
var bytes = new byte[length];
while (read < length)
{
int readnow = this.fileStream.Read(bytes, read, length - read);
if (readnow <= 0)
{
break;
}
read += readnow;
}
if (read == 0)
{
return Unpooled.Empty;
}
IByteBuffer buffer = Unpooled.WrappedBuffer(bytes);
buffer.SetReaderIndex(0);
buffer.SetWriterIndex(read);
return buffer;
}
public override string GetString() => this.GetString(HttpConstants.DefaultEncoding);
public override string GetString(Encoding encoding)
{
if (this.fileStream == null)
{
return string.Empty;
}
byte[] array = ReadFrom(this.fileStream);
if (encoding == null)
{
encoding = HttpConstants.DefaultEncoding;
}
return encoding.GetString(array);
}
public override bool IsInMemory => false;
public override bool RenameTo(FileStream destination)
{
Contract.Requires(destination != null);
if (this.fileStream == null)
{
throw new InvalidOperationException("No file defined so cannot be renamed");
}
// must copy
long chunkSize = 8196;
int position = 0;
while (position < this.Size)
{
if (chunkSize < this.Size - position)
{
chunkSize = this.Size - position;
}
var buffer = new byte[chunkSize];
int read = this.fileStream.Read(buffer, 0, (int)chunkSize);
if (read <= 0)
{
break;
}
destination.Write(buffer, 0, read);
position += read;
}
if (position == this.Size)
{
try
{
Delete(this.fileStream);
}
catch (IOException exception)
{
Logger.Warn("Failed to delete file.", exception);
}
this.fileStream = destination;
return true;
}
else
{
try
{
Delete(destination);
}
catch (IOException exception)
{
Logger.Warn("Failed to delete file.", exception);
}
return false;
}
}
static void Delete(FileStream fileStream)
{
string fileName = fileStream.Name;
fileStream.Dispose();
File.Delete(fileName);
}
static byte[] ReadFrom(Stream fileStream)
{
long srcsize = fileStream.Length;
if (srcsize > int.MaxValue)
{
throw new ArgumentException("File too big to be loaded in memory");
}
var array = new byte[(int)srcsize];
fileStream.Read(array, 0, array.Length);
return array;
}
public override FileStream GetFile() => this.fileStream;
public override IReferenceCounted Touch(object hint) => this;
}
}

Просмотреть файл

@ -0,0 +1,137 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoProperty
// ReSharper disable ConvertToAutoPropertyWithPrivateSetter
namespace DotNetty.Codecs.Http.Multipart
{
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public abstract class AbstractHttpData : AbstractReferenceCounted, IHttpData
{
static readonly Regex StripPattern = new Regex("(?:^\\s+|\\s+$|\\n)", RegexOptions.Compiled);
static readonly Regex ReplacePattern = new Regex("[\\r\\t]", RegexOptions.Compiled);
readonly string name;
protected long DefinedSize;
protected long Size;
Encoding charset = HttpConstants.DefaultEncoding;
bool completed;
long maxSize = DefaultHttpDataFactory.MaxSize;
protected AbstractHttpData(string name, Encoding charset, long size)
{
Contract.Requires(name != null);
name = StripPattern.Replace(name, " ");
name = ReplacePattern.Replace(name, "");
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("empty name");
}
this.name = name;
if (charset != null)
{
this.charset = charset;
}
this.DefinedSize = size;
}
public long MaxSize
{
get => this.maxSize;
set => this.maxSize = value;
}
public void CheckSize(long newSize)
{
if (this.MaxSize >= 0 && newSize > this.MaxSize)
{
throw new IOException("Size exceed allowed maximum capacity");
}
}
public string Name => this.name;
public bool IsCompleted => this.completed;
protected void SetCompleted() => this.completed = true;
public Encoding Charset
{
get => this.charset;
set
{
Contract.Requires(value != null);
this.charset = value;
}
}
public long Length => this.Size;
public long DefinedLength => this.DefinedSize;
public IByteBuffer Content
{
get
{
try
{
return this.GetByteBuffer();
}
catch (IOException e)
{
throw new ChannelException(e);
}
}
}
protected override void Deallocate() => this.Delete();
public abstract int CompareTo(IInterfaceHttpData other);
public abstract HttpDataType DataType { get; }
public abstract IByteBufferHolder Copy();
public abstract IByteBufferHolder Duplicate();
public abstract IByteBufferHolder RetainedDuplicate();
public abstract void SetContent(IByteBuffer buffer);
public abstract void SetContent(Stream source);
public abstract void AddContent(IByteBuffer buffer, bool last);
public abstract void Delete();
public abstract byte[] GetBytes();
public abstract IByteBuffer GetByteBuffer();
public abstract IByteBuffer GetChunk(int length);
public virtual string GetString() => this.GetString(this.charset);
public abstract string GetString(Encoding encoding);
public abstract bool RenameTo(FileStream destination);
public abstract bool IsInMemory { get; }
public abstract FileStream GetFile();
public abstract IByteBufferHolder Replace(IByteBuffer content);
}
}

Просмотреть файл

@ -0,0 +1,207 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Multipart
{
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common;
public abstract class AbstractMemoryHttpData : AbstractHttpData
{
IByteBuffer byteBuf;
int chunkPosition;
protected AbstractMemoryHttpData(string name, Encoding charset, long size)
: base(name, charset, size)
{
}
public override void SetContent(IByteBuffer buffer)
{
Contract.Requires(buffer != null);
long localsize = buffer.ReadableBytes;
this.CheckSize(localsize);
if (this.DefinedSize > 0 && this.DefinedSize < localsize)
{
throw new IOException($"Out of size: {localsize} > {this.DefinedSize}");
}
this.byteBuf?.Release();
this.byteBuf = buffer;
this.Size = localsize;
this.SetCompleted();
}
public override void SetContent(Stream inputStream)
{
Contract.Requires(inputStream != null);
if (!inputStream.CanRead)
{
throw new ArgumentException($"{nameof(inputStream)} is not readable");
}
IByteBuffer buffer = Unpooled.Buffer();
var bytes = new byte[4096 * 4];
int written = 0;
while (true)
{
int read = inputStream.Read(bytes, 0, bytes.Length);
if (read <= 0)
{
break;
}
buffer.WriteBytes(bytes, 0, read);
written += read;
this.CheckSize(written);
}
this.Size = written;
if (this.DefinedSize > 0 && this.DefinedSize < this.Size)
{
throw new IOException($"Out of size: {this.Size} > {this.DefinedSize}");
}
this.byteBuf?.Release();
this.byteBuf = buffer;
this.SetCompleted();
}
public override void AddContent(IByteBuffer buffer, bool last)
{
if (buffer != null)
{
long localsize = buffer.ReadableBytes;
this.CheckSize(this.Size + localsize);
if (this.DefinedSize > 0 && this.DefinedSize < this.Size + localsize)
{
throw new IOException($"Out of size: {(this.Size + localsize)} > {this.DefinedSize}");
}
this.Size += localsize;
if (this.byteBuf == null)
{
this.byteBuf = buffer;
}
else if (this.byteBuf is CompositeByteBuffer buf)
{
buf.AddComponent(true, buffer);
buf.SetWriterIndex((int)this.Size);
}
else
{
CompositeByteBuffer compositeBuffer = Unpooled.CompositeBuffer(int.MaxValue);
compositeBuffer.AddComponents(true, this.byteBuf, buffer);
compositeBuffer.SetWriterIndex((int)this.Size);
this.byteBuf = compositeBuffer;
}
}
if (last)
{
this.SetCompleted();
}
else
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
}
}
public override void Delete()
{
if (this.byteBuf != null)
{
this.byteBuf.Release();
this.byteBuf = null;
}
}
public override byte[] GetBytes()
{
if (this.byteBuf == null)
{
return Unpooled.Empty.Array;
}
var array = new byte[this.byteBuf.ReadableBytes];
this.byteBuf.GetBytes(this.byteBuf.ReaderIndex, array);
return array;
}
public override string GetString() => this.GetString(HttpConstants.DefaultEncoding);
public override string GetString(Encoding encoding)
{
if (this.byteBuf == null)
{
return string.Empty;
}
if (encoding == null)
{
encoding = HttpConstants.DefaultEncoding;
}
return this.byteBuf.ToString(encoding);
}
public override IByteBuffer GetByteBuffer() => this.byteBuf;
public override IByteBuffer GetChunk(int length)
{
if (this.byteBuf == null || length == 0 || this.byteBuf.ReadableBytes == 0)
{
this.chunkPosition = 0;
return Unpooled.Empty;
}
int sizeLeft = this.byteBuf.ReadableBytes - this.chunkPosition;
if (sizeLeft == 0)
{
this.chunkPosition = 0;
return Unpooled.Empty;
}
int sliceLength = length;
if (sizeLeft < length)
{
sliceLength = sizeLeft;
}
IByteBuffer chunk = this.byteBuf.RetainedSlice(this.chunkPosition, sliceLength);
this.chunkPosition += sliceLength;
return chunk;
}
public override bool IsInMemory => true;
public override bool RenameTo(FileStream destination)
{
Contract.Requires(destination != null);
if (!destination.CanWrite)
{
throw new ArgumentException($"{nameof(destination)} is not writable");
}
if (this.byteBuf == null)
{
return true;
}
this.byteBuf.GetBytes(this.byteBuf.ReaderIndex, destination, this.byteBuf.ReadableBytes);
destination.Flush();
return true;
}
public override FileStream GetFile() => throw new IOException("Not represented by a stream");
public override IReferenceCounted Touch(object hint)
{
this.byteBuf?.Touch(hint);
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,104 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Multipart
{
using System;
using System.Collections.Generic;
using DotNetty.Common.Utilities;
sealed class CaseIgnoringComparator : IEqualityComparer<ICharSequence>, IComparer<ICharSequence>
{
public static readonly IEqualityComparer<ICharSequence> Default = new CaseIgnoringComparator();
CaseIgnoringComparator()
{
}
public int Compare(ICharSequence x, ICharSequence y)
{
if (ReferenceEquals(x, y))
{
return 0;
}
if (x == null)
{
return -1;
}
if (y == null)
{
return 1;
}
int o1Length = x.Count;
int o2Length = y.Count;
int min = Math.Min(o1Length, o2Length);
for (int i = 0; i < min; i++)
{
char c1 = x[i];
char c2 = y[i];
if (c1 != c2)
{
c1 = char.ToUpper(c1);
c2 = char.ToUpper(c2);
if (c1 != c2)
{
c1 = char.ToLower(c1);
c2 = char.ToLower(c2);
if (c1 != c2)
{
return c1 - c2;
}
}
}
}
return o1Length - o2Length;
}
public bool Equals(ICharSequence x, ICharSequence y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x == null || y == null)
{
return false;
}
int o1Length = x.Count;
int o2Length = y.Count;
if (o1Length != o2Length)
{
return false;
}
for (int i = 0; i < o1Length; i++)
{
char c1 = x[i];
char c2 = y[i];
if (c1 != c2)
{
c1 = char.ToUpper(c1);
c2 = char.ToUpper(c2);
if (c1 != c2)
{
c1 = char.ToLower(c1);
c2 = char.ToLower(c2);
if (c1 != c2)
{
return false;
}
}
}
}
return true;
}
public int GetHashCode(ICharSequence obj) => obj.HashCode(true);
}
}

Просмотреть файл

@ -0,0 +1,283 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Multipart
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
public class DefaultHttpDataFactory : IHttpDataFactory
{
// Proposed default MINSIZE as 16 KB.
public static readonly long MinSize = 0x4000;
// Proposed default MAXSIZE = -1 as UNLIMITED
public static readonly long MaxSize = -1;
readonly bool useDisk;
readonly bool checkSize;
readonly long minSize;
long maxSize = MaxSize;
readonly Encoding charset = HttpConstants.DefaultEncoding;
// Keep all HttpDatas until cleanAllHttpData() is called.
readonly ConcurrentDictionary<IHttpRequest, List<IHttpData>> requestFileDeleteMap =
new ConcurrentDictionary<IHttpRequest, List<IHttpData>>(IdentityComparer.Default);
// HttpData will be in memory if less than default size (16KB).
// The type will be Mixed.
public DefaultHttpDataFactory()
{
this.useDisk = false;
this.checkSize = true;
this.minSize = MinSize;
}
public DefaultHttpDataFactory(Encoding charset) : this()
{
this.charset = charset;
}
// HttpData will be always on Disk if useDisk is True, else always in Memory if False
public DefaultHttpDataFactory(bool useDisk)
{
this.useDisk = useDisk;
this.checkSize = false;
}
public DefaultHttpDataFactory(bool useDisk, Encoding charset) : this(useDisk)
{
this.charset = charset;
}
public DefaultHttpDataFactory(long minSize)
{
this.useDisk = false;
this.checkSize = true;
this.minSize = minSize;
}
public DefaultHttpDataFactory(long minSize, Encoding charset) : this(minSize)
{
this.charset = charset;
}
public void SetMaxLimit(long max) => this.maxSize = max;
List<IHttpData> GetList(IHttpRequest request)
{
List<IHttpData> list = this.requestFileDeleteMap.GetOrAdd(request, _ => new List<IHttpData>());
return list;
}
public IAttribute CreateAttribute(IHttpRequest request, string name)
{
if (this.useDisk)
{
var diskAttribute = new DiskAttribute(name, this.charset);
diskAttribute.MaxSize = this.maxSize;
List<IHttpData> list = this.GetList(request);
list.Add(diskAttribute);
return diskAttribute;
}
if (this.checkSize)
{
var mixedAttribute = new MixedAttribute(name, this.minSize, this.charset);
mixedAttribute.MaxSize = this.maxSize;
List<IHttpData> list = this.GetList(request);
list.Add(mixedAttribute);
return mixedAttribute;
}
var attribute = new MemoryAttribute(name);
attribute.MaxSize = this.maxSize;
return attribute;
}
public IAttribute CreateAttribute(IHttpRequest request, string name, long definedSize)
{
if (this.useDisk)
{
var diskAttribute = new DiskAttribute(name, definedSize, this.charset);
diskAttribute.MaxSize = this.maxSize;
List<IHttpData> list = this.GetList(request);
list.Add(diskAttribute);
return diskAttribute;
}
if (this.checkSize)
{
var mixedAttribute = new MixedAttribute(name, definedSize, this.minSize, this.charset);
mixedAttribute.MaxSize = this.maxSize;
List<IHttpData> list = this.GetList(request);
list.Add(mixedAttribute);
return mixedAttribute;
}
var attribute = new MemoryAttribute(name, definedSize);
attribute.MaxSize = this.maxSize;
return attribute;
}
static void CheckHttpDataSize(IHttpData data)
{
try
{
data.CheckSize(data.Length);
}
catch (IOException)
{
throw new ArgumentException("Attribute bigger than maxSize allowed");
}
}
public IAttribute CreateAttribute(IHttpRequest request, string name, string value)
{
if (this.useDisk)
{
IAttribute attribute;
try
{
attribute = new DiskAttribute(name, value, this.charset);
attribute.MaxSize = this.maxSize;
}
catch (IOException)
{
// revert to Mixed mode
attribute = new MixedAttribute(name, value, this.minSize, this.charset);
attribute.MaxSize = this.maxSize;
}
CheckHttpDataSize(attribute);
List<IHttpData> list = this.GetList(request);
list.Add(attribute);
return attribute;
}
if (this.checkSize)
{
var mixedAttribute = new MixedAttribute(name, value, this.minSize, this.charset);
mixedAttribute.MaxSize = this.maxSize;
CheckHttpDataSize(mixedAttribute);
List<IHttpData> list = this.GetList(request);
list.Add(mixedAttribute);
return mixedAttribute;
}
try
{
var attribute = new MemoryAttribute(name, value, this.charset);
attribute.MaxSize = this.maxSize;
CheckHttpDataSize(attribute);
return attribute;
}
catch (IOException e)
{
throw new ArgumentException($"({request}, {name}, {value})", e);
}
}
public IFileUpload CreateFileUpload(IHttpRequest request, string name, string fileName,
string contentType, string contentTransferEncoding, Encoding encoding,
long size)
{
if (this.useDisk)
{
var fileUpload = new DiskFileUpload(name, fileName, contentType,
contentTransferEncoding, encoding, size);
fileUpload.MaxSize = this.maxSize;
CheckHttpDataSize(fileUpload);
List<IHttpData> list = this.GetList(request);
list.Add(fileUpload);
return fileUpload;
}
if (this.checkSize)
{
var fileUpload = new MixedFileUpload(name, fileName, contentType,
contentTransferEncoding, encoding, size, this.minSize);
fileUpload.MaxSize = this.maxSize;
CheckHttpDataSize(fileUpload);
List<IHttpData> list = this.GetList(request);
list.Add(fileUpload);
return fileUpload;
}
var memoryFileUpload = new MemoryFileUpload(name, fileName, contentType,
contentTransferEncoding, encoding, size);
memoryFileUpload.MaxSize = this.maxSize;
CheckHttpDataSize(memoryFileUpload);
return memoryFileUpload;
}
public void RemoveHttpDataFromClean(IHttpRequest request, IInterfaceHttpData data)
{
if (!(data is IHttpData httpData))
{
return;
}
// Do not use getList because it adds empty list to requestFileDeleteMap
// if request is not found
if (!this.requestFileDeleteMap.TryGetValue(request, out List<IHttpData> list))
{
return;
}
// Can't simply call list.remove(data), because different data items may be equal.
// Need to check identity.
int index = -1;
for (int i = 0; i < list.Count; i++)
{
if (ReferenceEquals(list[i], httpData))
{
index = i;
break;
}
}
if (index != -1)
{
list.RemoveAt(index);
}
if (list.Count == 0)
{
this.requestFileDeleteMap.TryRemove(request, out _);
}
}
public void CleanRequestHttpData(IHttpRequest request)
{
if (this.requestFileDeleteMap.TryRemove(request, out List<IHttpData> list))
{
foreach (IHttpData data in list)
{
data.Release();
}
}
}
public void CleanAllHttpData()
{
while (!this.requestFileDeleteMap.IsEmpty)
{
IHttpRequest[] keys = this.requestFileDeleteMap.Keys.ToArray();
foreach (IHttpRequest key in keys)
{
if (this.requestFileDeleteMap.TryRemove(key, out List<IHttpData> list))
{
foreach (IHttpData data in list)
{
data.Release();
}
}
}
}
}
// Similar to IdentityHashMap in Java
sealed class IdentityComparer : IEqualityComparer<IHttpRequest>
{
internal static readonly IdentityComparer Default = new IdentityComparer();
public bool Equals(IHttpRequest x, IHttpRequest y) => ReferenceEquals(x, y);
public int GetHashCode(IHttpRequest obj) => obj.GetHashCode();
}
}
}

Просмотреть файл

@ -0,0 +1,180 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Multipart
{
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
public class DiskAttribute : AbstractDiskHttpData, IAttribute
{
public static string DiskBaseDirectory;
public static bool DeleteOnExitTemporaryFile = true;
public static readonly string FilePrefix = "Attr_";
public static readonly string FilePostfix = ".att";
public DiskAttribute(string name)
: this(name, HttpConstants.DefaultEncoding)
{
}
public DiskAttribute(string name, long definedSize)
: this(name, definedSize, HttpConstants.DefaultEncoding)
{
}
public DiskAttribute(string name, Encoding charset)
: base(name, charset, 0)
{
}
public DiskAttribute(string name, long definedSize, Encoding charset)
: base(name, charset, definedSize)
{
}
public DiskAttribute(string name, string value)
: this(name, value, HttpConstants.DefaultEncoding)
{
}
public DiskAttribute(string name, string value, Encoding charset)
: base(name, charset, 0) // Attribute have no default size
{
this.Value = value;
}
public override HttpDataType DataType => HttpDataType.Attribute;
public string Value
{
get
{
byte[] bytes = this.GetBytes();
return this.Charset.GetString(bytes);
}
set
{
Contract.Requires(value != null);
byte[] bytes = this.Charset.GetBytes(value);
this.CheckSize(bytes.Length);
IByteBuffer buffer = Unpooled.WrappedBuffer(bytes);
if (this.DefinedSize > 0)
{
this.DefinedSize = buffer.ReadableBytes;
}
this.SetContent(buffer);
}
}
public override void AddContent(IByteBuffer buffer, bool last)
{
long newDefinedSize = this.Size + buffer.ReadableBytes;
this.CheckSize(newDefinedSize);
if (this.DefinedSize > 0 && this.DefinedSize < newDefinedSize)
{
this.DefinedSize = newDefinedSize;
}
base.AddContent(buffer, last);
}
public override int GetHashCode() => this.Name.GetHashCode();
public override bool Equals(object obj)
{
if (obj is IAttribute attribute)
{
return this.Name.Equals(attribute.Name, StringComparison.OrdinalIgnoreCase);
}
return false;
}
public override int CompareTo(IInterfaceHttpData other)
{
if (!(other is IAttribute))
{
throw new ArgumentException($"Cannot compare {this.DataType} with {other.DataType}");
}
return this.CompareTo((IAttribute)other);
}
public int CompareTo(IAttribute attribute) => string.Compare(this.Name, attribute.Name, StringComparison.OrdinalIgnoreCase);
public override string ToString()
{
try
{
return $"{this.Name}={this.Value}";
}
catch (IOException e)
{
return $"{this.Name}={e}";
}
}
protected override bool DeleteOnExit => DeleteOnExitTemporaryFile;
protected override string BaseDirectory => DiskBaseDirectory;
protected override string DiskFilename => $"{this.Name}{this.Postfix}";
protected override string Postfix => FilePostfix;
protected override string Prefix => FilePrefix;
public override IByteBufferHolder Copy() => this.Replace(this.Content?.Copy());
public override IByteBufferHolder Duplicate() => this.Replace(this.Content?.Duplicate());
public override IByteBufferHolder RetainedDuplicate()
{
IByteBuffer content = this.Content;
if (content != null)
{
content = content.RetainedDuplicate();
bool success = false;
try
{
var duplicate = (IAttribute)this.Replace(content);
success = true;
return duplicate;
}
finally
{
if (!success)
{
content.Release();
}
}
}
else
{
return this.Replace(null);
}
}
public override IByteBufferHolder Replace(IByteBuffer content)
{
var attr = new DiskAttribute(this.Name);
attr.Charset = this.Charset;
if (content != null)
{
try
{
attr.SetContent(content);
}
catch (IOException e)
{
throw new ChannelException(e);
}
}
return attr;
}
}
}

Просмотреть файл

@ -0,0 +1,165 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http.Multipart
{
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
public class DiskFileUpload : AbstractDiskHttpData, IFileUpload
{
public static string FileBaseDirectory;
public static bool DeleteOnExitTemporaryFile = true;
public static string FilePrefix = "FUp_";
public static readonly string FilePostfix = ".tmp";
string filename;
string contentType;
string contentTransferEncoding;
public DiskFileUpload(string name, string filename, string contentType,
string contentTransferEncoding, Encoding charset, long size)
: base(name, charset, size)
{
Contract.Requires(filename != null);
Contract.Requires(contentType != null);
this.filename = filename;
this.contentType = contentType;
this.contentTransferEncoding = contentTransferEncoding;
}
public override HttpDataType DataType => HttpDataType.FileUpload;
public string FileName
{
get => this.filename;
set
{
Contract.Requires(value != null);
this.filename = value;
}
}
public override int GetHashCode() => FileUploadUtil.HashCode(this);
public override bool Equals(object obj) => obj is IFileUpload fileUpload && FileUploadUtil.Equals(this, fileUpload);
public override int CompareTo(IInterfaceHttpData other)
{
if (!(other is IFileUpload))
{
throw new ArgumentException($"Cannot compare {this.DataType} with {other.DataType}");
}
return this.CompareTo((IFileUpload)other);
}
public int CompareTo(IFileUpload other) => FileUploadUtil.CompareTo(this, other);
public string ContentType
{
get => this.contentType;
set
{
Contract.Requires(value != null);
this.contentType = value;
}
}
public string ContentTransferEncoding
{
get => this.contentTransferEncoding;
set => this.contentTransferEncoding = value;
}
public override string ToString()
{
FileStream fileStream = null;
try
{
fileStream = this.GetFile();
}
catch (IOException)
{
// Should not occur.
}
return HttpHeaderNames.ContentDisposition + ": " +
HttpHeaderValues.FormData + "; " + HttpHeaderValues.Name + "=\"" + this.Name +
"\"; " + HttpHeaderValues.FileName + "=\"" + this.filename + "\"\r\n" +
HttpHeaderNames.ContentType + ": " + this.contentType +
(this.Charset != null ? "; " + HttpHeaderValues.Charset + '=' + this.Charset.WebName + "\r\n" : "\r\n") +
HttpHeaderNames.ContentLength + ": " + this.Length + "\r\n" +
"Completed: " + this.IsCompleted +
"\r\nIsInMemory: " + this.IsInMemory + "\r\nRealFile: " +
(fileStream != null ? fileStream.Name : "null") + " DefaultDeleteAfter: " +
DeleteOnExitTemporaryFile;
}
protected override bool DeleteOnExit => DeleteOnExitTemporaryFile;
protected override string BaseDirectory => FileBaseDirectory;
protected override string DiskFilename => "upload";
protected override string Postfix => FilePostfix;
protected override string Prefix => FilePrefix;
public override IByteBufferHolder Copy() => this.Replace(this.Content?.Copy());
public override IByteBufferHolder Duplicate() => this.Replace(this.Content?.Duplicate());
public override IByteBufferHolder RetainedDuplicate()
{
IByteBuffer content = this.Content;
if (content != null)
{
content = content.RetainedDuplicate();
bool success = false;
try
{
var duplicate = (IFileUpload)this.Replace(content);
success = true;
return duplicate;
}
finally
{
if (!success)
{
content.Release();
}
}
}
else
{
return this.Replace(null);
}
}
public override IByteBufferHolder Replace(IByteBuffer content)
{
var upload = new DiskFileUpload(
this.Name, this.FileName, this.ContentType, this.ContentTransferEncoding, this.Charset, this.Size);
if (content != null)
{
try
{
upload.SetContent(content);
}
catch (IOException e)
{
throw new ChannelException(e);
}
}
return upload;
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Multipart
{
using System;
public class EndOfDataDecoderException : DecoderException
{
public EndOfDataDecoderException(string message)
: base(message)
{
}
public EndOfDataDecoderException(Exception innerException)
: base(innerException)
{
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Multipart
{
using System;
public class ErrorDataDecoderException : DecoderException
{
public ErrorDataDecoderException(string message)
: base(message)
{
}
public ErrorDataDecoderException(Exception innerException)
: base(innerException)
{
}
public ErrorDataDecoderException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Multipart
{
using System;
public class ErrorDataEncoderException : Exception
{
public ErrorDataEncoderException(string message)
: base(message)
{
}
public ErrorDataEncoderException(Exception innerException)
: base(null, innerException)
{
}
public ErrorDataEncoderException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше