Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit f8813a6

Browse files
author
Cesar Blum Silveira
committed
Handle response content length mismatches (#175).
1 parent 8c103f0 commit f8813a6

File tree

13 files changed

+482
-98
lines changed

13 files changed

+482
-98
lines changed

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7+
using System.Globalization;
78
using System.IO;
89
using System.Linq;
910
using System.Net;
@@ -75,7 +76,7 @@ public abstract partial class Frame : IFrameControl
7576
protected readonly long _keepAliveMilliseconds;
7677
private readonly long _requestHeadersTimeoutMilliseconds;
7778

78-
private int _responseBytesWritten;
79+
protected long _responseBytesWritten;
7980

8081
public Frame(ConnectionContext context)
8182
{
@@ -516,8 +517,8 @@ public async Task FlushAsync(CancellationToken cancellationToken)
516517

517518
public void Write(ArraySegment<byte> data)
518519
{
520+
VerifyAndUpdateWrite(data.Count);
519521
ProduceStartAndFireOnStarting().GetAwaiter().GetResult();
520-
_responseBytesWritten += data.Count;
521522

522523
if (_canHaveBody)
523524
{
@@ -547,7 +548,7 @@ public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationTo
547548
return WriteAsyncAwaited(data, cancellationToken);
548549
}
549550

550-
_responseBytesWritten += data.Count;
551+
VerifyAndUpdateWrite(data.Count);
551552

552553
if (_canHaveBody)
553554
{
@@ -573,8 +574,9 @@ public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationTo
573574

574575
public async Task WriteAsyncAwaited(ArraySegment<byte> data, CancellationToken cancellationToken)
575576
{
577+
VerifyAndUpdateWrite(data.Count);
578+
576579
await ProduceStartAndFireOnStarting();
577-
_responseBytesWritten += data.Count;
578580

579581
if (_canHaveBody)
580582
{
@@ -598,6 +600,23 @@ public async Task WriteAsyncAwaited(ArraySegment<byte> data, CancellationToken c
598600
}
599601
}
600602

603+
private void VerifyAndUpdateWrite(int count)
604+
{
605+
var responseHeaders = FrameResponseHeaders;
606+
607+
if (responseHeaders != null &&
608+
!responseHeaders.HasTransferEncoding &&
609+
responseHeaders.HasContentLength &&
610+
_responseBytesWritten + count > responseHeaders.HeaderContentLengthValue.Value)
611+
{
612+
_keepAlive = false;
613+
throw new InvalidOperationException(
614+
$"Response Content-Length mismatch: too many bytes written ({_responseBytesWritten + count} of {responseHeaders.HeaderContentLengthValue.Value}).");
615+
}
616+
617+
_responseBytesWritten += count;
618+
}
619+
601620
private void WriteChunked(ArraySegment<byte> data)
602621
{
603622
SocketOutput.Write(data, chunk: true);

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameHeaders.Generated.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3697,6 +3697,7 @@ protected override void ClearFast()
36973697
{
36983698
_bits = 0;
36993699
_headers = default(HeaderReferences);
3700+
37003701
MaybeUnknown?.Clear();
37013702
}
37023703

@@ -5670,6 +5671,7 @@ public StringValues HeaderContentLength
56705671
}
56715672
set
56725673
{
5674+
_contentLength = ParseContentLength(value);
56735675
_bits |= 2048L;
56745676
_headers._ContentLength = value;
56755677
_headers._rawContentLength = null;
@@ -7384,6 +7386,7 @@ protected override void SetValueFast(string key, StringValues value)
73847386
{
73857387
if ("Content-Length".Equals(key, StringComparison.OrdinalIgnoreCase))
73867388
{
7389+
_contentLength = ParseContentLength(value);
73877390
_bits |= 2048L;
73887391
_headers._ContentLength = value;
73897392
_headers._rawContentLength = null;
@@ -7809,6 +7812,7 @@ protected override void AddValueFast(string key, StringValues value)
78097812
{
78107813
ThrowDuplicateKeyException();
78117814
}
7815+
_contentLength = ParseContentLength(value);
78127816
_bits |= 2048L;
78137817
_headers._ContentLength = value;
78147818
_headers._rawContentLength = null;
@@ -8350,6 +8354,7 @@ protected override bool RemoveFast(string key)
83508354
{
83518355
if (((_bits & 2048L) != 0))
83528356
{
8357+
_contentLength = null;
83538358
_bits &= ~2048L;
83548359
_headers._ContentLength = StringValues.Empty;
83558360
_headers._rawContentLength = null;
@@ -8601,6 +8606,7 @@ protected override void ClearFast()
86018606
{
86028607
_bits = 0;
86038608
_headers = default(HeaderReferences);
8609+
_contentLength = null;
86048610
MaybeUnknown?.Clear();
86058611
}
86068612

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameHeaders.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections;
66
using System.Collections.Generic;
7+
using System.Globalization;
78
using System.Linq;
89
using Microsoft.AspNetCore.Http;
910
using Microsoft.Extensions.Primitives;
@@ -232,6 +233,18 @@ public static void ValidateHeaderCharacters(string headerCharacters)
232233
}
233234
}
234235

236+
public static long ParseContentLength(StringValues value)
237+
{
238+
try
239+
{
240+
return long.Parse(value, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture);
241+
}
242+
catch (FormatException ex)
243+
{
244+
throw new InvalidOperationException("Content-Length value must be an integral number.", ex);
245+
}
246+
}
247+
235248
private static void ThrowInvalidHeaderCharacter(char ch)
236249
{
237250
throw new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch));

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ public override async Task RequestProcessingAsync()
9292
try
9393
{
9494
await _application.ProcessRequestAsync(context).ConfigureAwait(false);
95+
96+
var responseHeaders = FrameResponseHeaders;
97+
if (!responseHeaders.HasTransferEncoding &&
98+
responseHeaders.HasContentLength &&
99+
_responseBytesWritten < responseHeaders.HeaderContentLengthValue.Value)
100+
{
101+
_keepAlive = false;
102+
ReportApplicationError(new InvalidOperationException(
103+
$"Response Content-Length mismatch: too few bytes written ({_responseBytesWritten} of {responseHeaders.HeaderContentLengthValue.Value})."));
104+
}
95105
}
96106
catch (Exception ex)
97107
{

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameResponseHeaders.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public partial class FrameResponseHeaders : FrameHeaders
1313
private static readonly byte[] _CrLf = new[] { (byte)'\r', (byte)'\n' };
1414
private static readonly byte[] _colonSpace = new[] { (byte)':', (byte)' ' };
1515

16+
private long? _contentLength;
17+
1618
public bool HasConnection => HeaderConnection.Count != 0;
1719

1820
public bool HasTransferEncoding => HeaderTransferEncoding.Count != 0;
@@ -23,6 +25,8 @@ public partial class FrameResponseHeaders : FrameHeaders
2325

2426
public bool HasDate => HeaderDate.Count != 0;
2527

28+
public long? HeaderContentLengthValue => _contentLength;
29+
2630
public Enumerator GetEnumerator()
2731
{
2832
return new Enumerator(this);

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/IKestrelTrace.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public interface IKestrelTrace : ILogger
3333

3434
void ConnectionDisconnectedWrite(string connectionId, int count, Exception ex);
3535

36-
void ConnectionHeadResponseBodyWrite(string connectionId, int count);
36+
void ConnectionHeadResponseBodyWrite(string connectionId, long count);
3737

3838
void ConnectionBadRequest(string connectionId, BadHttpRequestException ex);
3939

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/KestrelTrace.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class KestrelTrace : IKestrelTrace
2424
private static readonly Action<ILogger, string, Exception> _applicationError;
2525
private static readonly Action<ILogger, string, Exception> _connectionError;
2626
private static readonly Action<ILogger, string, int, Exception> _connectionDisconnectedWrite;
27-
private static readonly Action<ILogger, string, int, Exception> _connectionHeadResponseBodyWrite;
27+
private static readonly Action<ILogger, string, long, Exception> _connectionHeadResponseBodyWrite;
2828
private static readonly Action<ILogger, Exception> _notAllConnectionsClosedGracefully;
2929
private static readonly Action<ILogger, string, string, Exception> _connectionBadRequest;
3030

@@ -49,7 +49,7 @@ static KestrelTrace()
4949
_connectionDisconnectedWrite = LoggerMessage.Define<string, int>(LogLevel.Debug, 15, @"Connection id ""{ConnectionId}"" write of ""{count}"" bytes to disconnected client.");
5050
_notAllConnectionsClosedGracefully = LoggerMessage.Define(LogLevel.Debug, 16, "Some connections failed to close gracefully during server shutdown.");
5151
_connectionBadRequest = LoggerMessage.Define<string, string>(LogLevel.Information, 17, @"Connection id ""{ConnectionId}"" bad request data: ""{message}""");
52-
_connectionHeadResponseBodyWrite = LoggerMessage.Define<string, int>(LogLevel.Debug, 18, @"Connection id ""{ConnectionId}"" write of ""{count}"" body bytes to non-body HEAD response.");
52+
_connectionHeadResponseBodyWrite = LoggerMessage.Define<string, long>(LogLevel.Debug, 18, @"Connection id ""{ConnectionId}"" write of ""{count}"" body bytes to non-body HEAD response.");
5353
}
5454

5555
public KestrelTrace(ILogger logger)
@@ -135,7 +135,7 @@ public virtual void ConnectionDisconnectedWrite(string connectionId, int count,
135135
_connectionDisconnectedWrite(_logger, connectionId, count, ex);
136136
}
137137

138-
public virtual void ConnectionHeadResponseBodyWrite(string connectionId, int count)
138+
public virtual void ConnectionHeadResponseBodyWrite(string connectionId, long count)
139139
{
140140
_connectionHeadResponseBodyWrite(_logger, connectionId, count, null);
141141
}

0 commit comments

Comments
 (0)