Skip to content

Commit

Permalink
Content-Length header not always returned when enumerating HttpConten…
Browse files Browse the repository at this point in the history
…tHeaders (dotnet#102416)

* Content-Length header not always returned when enumerating HttpContentHeaders

* fix failing unit test

* make loading ContentLength part of HttpHeaders and HttpHeadersNonValidated

* rework

* initialize content length in ReadOnlyMemoryContent ctor
  • Loading branch information
pedrobsaila authored and Ruihan-Yin committed May 30, 2024
1 parent 6c625e1 commit a0b8f6f
Show file tree
Hide file tree
Showing 10 changed files with 58 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public ByteArrayContent(byte[] content)

_content = content;
_count = content.Length;
Headers.ContentLength = _count;
}

public ByteArrayContent(byte[] content, int offset, int count)
Expand All @@ -35,6 +36,7 @@ public ByteArrayContent(byte[] content, int offset, int count)
_content = content;
_offset = offset;
_count = count;
Headers.ContentLength = _count;
}

protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ public sealed class ReadOnlyMemoryContent : HttpContent
{
private readonly ReadOnlyMemory<byte> _content;

public ReadOnlyMemoryContent(ReadOnlyMemory<byte> content) =>
public ReadOnlyMemoryContent(ReadOnlyMemory<byte> content)
{
_content = content;
Headers.ContentLength = content.Length;
}

protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken)
{
Expand Down
10 changes: 10 additions & 0 deletions src/libraries/System.Net.Http/src/System/Net/Http/StreamContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public StreamContent(Stream content)

// Indicate that we should use default buffer size by setting size to 0.
InitializeContent(content, 0);

if (TryComputeLength(out long contentLength))
{
Headers.ContentLength = contentLength;
}
}

public StreamContent(Stream content, int bufferSize)
Expand All @@ -31,6 +36,11 @@ public StreamContent(Stream content, int bufferSize)
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize);

InitializeContent(content, bufferSize);

if (TryComputeLength(out long contentLength))
{
Headers.ContentLength = contentLength;
}
}

[MemberNotNull(nameof(_content))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ public void ContentLength_UseWholeSourceArray_LengthMatchesArrayLength()
var contentData = new byte[10];
var content = new ByteArrayContent(contentData);

Assert.Contains("Content-Length", content.Headers.ToString());
Assert.Contains(content.Headers, h => h.Key == "Content-Length");
Assert.Contains(content.Headers.NonValidated, h => h.Key == "Content-Length");
Assert.Equal(contentData.Length, content.Headers.ContentLength);
}

Expand All @@ -75,6 +78,9 @@ public void ContentLength_UsePartialSourceArray_LengthMatchesArrayLength()
var contentData = new byte[10];
var content = new ByteArrayContent(contentData, 5, 3);

Assert.Contains("Content-Length", content.Headers.ToString());
Assert.Contains(content.Headers, h => h.Key == "Content-Length");
Assert.Contains(content.Headers.NonValidated, h => h.Key == "Content-Length");
Assert.Equal(3, content.Headers.ContentLength);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ public void ToString_DefaultAndNonDefaultInstance_DumpAllFields()
Assert.Equal(
"Method: PUT, RequestUri: 'http://a.com/', Version: 1.0, Content: " + typeof(StringContent).ToString() + ", Headers:" + Environment.NewLine +
$"{{{Environment.NewLine}" +
" Content-Length: 7" + Environment.NewLine +
" Content-Type: text/plain; charset=utf-8" + Environment.NewLine +
"}", rm.ToString());

Expand All @@ -228,6 +229,7 @@ public void ToString_DefaultAndNonDefaultInstance_DumpAllFields()
" Accept: text/xml; q=0.1" + Environment.NewLine +
" Accept-Language: en-US,en;q=0.5" + Environment.NewLine +
" Custom-Request-Header: value1" + Environment.NewLine +
" Content-Length: 7" + Environment.NewLine +
" Content-Type: text/plain; charset=utf-8" + Environment.NewLine +
" Custom-Content-Header: value2" + Environment.NewLine +
"}", rm.ToString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ public void ToString_DefaultAndNonDefaultInstance_DumpAllFields()
Assert.Equal(
"StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.0, Content: " + typeof(StringContent).ToString() + ", Headers:" + Environment.NewLine +
"{" + Environment.NewLine +
" Content-Length: 7" + Environment.NewLine +
" Content-Type: text/plain; charset=utf-8" + Environment.NewLine +
"}", rm.ToString());

Expand All @@ -326,6 +327,7 @@ public void ToString_DefaultAndNonDefaultInstance_DumpAllFields()
" Accept-Ranges: bytes" + Environment.NewLine +
" Accept-Ranges: pages" + Environment.NewLine +
" Custom-Response-Header: value1" + Environment.NewLine +
" Content-Length: 7" + Environment.NewLine +
" Content-Type: text/plain; charset=utf-8" + Environment.NewLine +
" Custom-Content-Header: value2" + Environment.NewLine +
"}", rm.ToString());
Expand All @@ -339,6 +341,7 @@ public void ToString_DefaultAndNonDefaultInstance_DumpAllFields()
" Accept-Ranges: bytes" + Environment.NewLine +
" Accept-Ranges: pages" + Environment.NewLine +
" Custom-Response-Header: value1" + Environment.NewLine +
" Content-Length: 7" + Environment.NewLine +
" Content-Type: text/plain; charset=utf-8" + Environment.NewLine +
" Custom-Content-Header: value2" + Environment.NewLine +
"}, Trailing Headers:" + Environment.NewLine +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public async Task ReadAsStringAsync_OneSubContentWithHeaders_MatchesExpected(Mul

Assert.Equal(
"--theBoundary\r\n" +
"Content-Length: 26\r\n" +
"someHeaderName: andSomeHeaderValue\r\n" +
"someOtherHeaderName: withNotOne, ButTwoValues\r\n" +
"oneMoreHeader: withNotOne, AndNotTwo, butThreeValues\r\n" +
Expand All @@ -182,9 +183,11 @@ public async Task ReadAsStringAsync_TwoSubContents_MatchesExpected(MultipartCont

Assert.Equal(
"--theBoundary\r\n" +
"Content-Length: 26\r\n" +
"\r\n" +
"This is a ByteArrayContent\r\n" +
"--theBoundary\r\n" +
"Content-Length: 23\r\n" +
"Content-Type: text/plain; charset=utf-8\r\n" +
"\r\n" +
"This is a StringContent\r\n" +
Expand Down Expand Up @@ -477,28 +480,31 @@ public async Task ReadAsStreamAsync_CustomEncodingSelector_CustomEncodingIsUsed(

byte[] expected = Concat(
"--fooBoundary\r\n"u8.ToArray(),
"Content-Length: 4\r\n"u8.ToArray(),
"Content-Type: text/plain; charset=utf-8\r\n"u8.ToArray(),
"latin1: "u8.ToArray(),
Encoding.Latin1.GetBytes("\U0001F600"),
"\r\n\r\n"u8.ToArray(),
"bar1"u8.ToArray(),
"\r\n--fooBoundary\r\n"u8.ToArray(),
"Content-Length: 4\r\n"u8.ToArray(),
"utf8: "u8.ToArray(),
"\U0001F600"u8.ToArray(),
"\r\n\r\n"u8.ToArray(),
"bar2"u8.ToArray(),
"\r\n--fooBoundary\r\n"u8.ToArray(),
"Content-Length: 4\r\n"u8.ToArray(),
"ascii: "u8.ToArray(),
Encoding.ASCII.GetBytes("\U0001F600"),
"\r\n\r\n"u8.ToArray(),
"bar3"u8.ToArray(),
"\r\n--fooBoundary\r\n"u8.ToArray(),
"Content-Length: 4\r\n"u8.ToArray(),
"default: "u8.ToArray(),
Encoding.Latin1.GetBytes("\U0001F600"),
"\r\n\r\n"u8.ToArray(),
"bar4"u8.ToArray(),
"\r\n--fooBoundary--\r\n"u8.ToArray());

Assert.Equal(expected, ms.ToArray());

static byte[] Concat(params byte[][] arrays) => arrays.SelectMany(b => b).ToArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public async Task Serialize_StringContent_Success()
string result = new StreamReader(output).ReadToEnd();

Assert.Equal(
"--test_boundary\r\nContent-Type: text/plain; charset=utf-8\r\n"
"--test_boundary\r\nContent-Length: 11\r\nContent-Type: text/plain; charset=utf-8\r\n"
+ "Content-Disposition: form-data\r\n\r\nHello World\r\n--test_boundary--\r\n",
result);
}
Expand All @@ -110,7 +110,7 @@ public async Task Serialize_NamedStringContent_Success()
string result = new StreamReader(output).ReadToEnd();

Assert.Equal(
"--test_boundary\r\nContent-Type: text/plain; charset=utf-8\r\n"
"--test_boundary\r\nContent-Length: 11\r\nContent-Type: text/plain; charset=utf-8\r\n"
+ "Content-Disposition: form-data; name=test_name\r\n\r\nHello World\r\n--test_boundary--\r\n",
result);
}
Expand All @@ -128,7 +128,7 @@ public async Task Serialize_FileNameStringContent_Success()
string result = new StreamReader(output).ReadToEnd();

Assert.Equal(
"--test_boundary\r\nContent-Type: text/plain; charset=utf-8\r\n"
"--test_boundary\r\nContent-Length: 11\r\nContent-Type: text/plain; charset=utf-8\r\n"
+ "Content-Disposition: form-data; name=test_name; "
+ "filename=test_file_name; filename*=utf-8\'\'test_file_name\r\n\r\n"
+ "Hello World\r\n--test_boundary--\r\n",
Expand All @@ -148,7 +148,7 @@ public async Task Serialize_QuotedName_Success()
string result = new StreamReader(output).ReadToEnd();

Assert.Equal(
"--test_boundary\r\nContent-Type: text/plain; charset=utf-8\r\n"
"--test_boundary\r\nContent-Length: 11\r\nContent-Type: text/plain; charset=utf-8\r\n"
+ "Content-Disposition: form-data; name=\"test name\"\r\n\r\nHello World\r\n--test_boundary--\r\n",
result);
}
Expand All @@ -166,7 +166,7 @@ public async Task Serialize_InvalidName_Encoded()
string result = new StreamReader(output).ReadToEnd();

Assert.Equal(
"--test_boundary\r\nContent-Type: text/plain; charset=utf-8\r\n"
"--test_boundary\r\nContent-Length: 11\r\nContent-Type: text/plain; charset=utf-8\r\n"
+ "Content-Disposition: form-data; name=\"=?utf-8?B?dGVzdOOCrw0KIG5hbcOp?=\""
+ "\r\n\r\nHello World\r\n--test_boundary--\r\n",
result);
Expand All @@ -185,7 +185,7 @@ public async Task Serialize_InvalidQuotedName_Encoded()
string result = new StreamReader(output).ReadToEnd();

Assert.Equal(
"--test_boundary\r\nContent-Type: text/plain; charset=utf-8\r\n"
"--test_boundary\r\nContent-Length: 11\r\nContent-Type: text/plain; charset=utf-8\r\n"
+ "Content-Disposition: form-data; name=\"=?utf-8?B?dGVzdOOCrw0KIG5hbcOp?=\""
+ "\r\n\r\nHello World\r\n--test_boundary--\r\n",
result);
Expand All @@ -204,7 +204,7 @@ public async Task Serialize_InvalidNamedFileName_Encoded()
string result = new StreamReader(output).ReadToEnd();

Assert.Equal(
"--test_boundary\r\nContent-Type: text/plain; charset=utf-8\r\n"
"--test_boundary\r\nContent-Length: 11\r\nContent-Type: text/plain; charset=utf-8\r\n"
+ "Content-Disposition: form-data; name=\"=?utf-8?B?dGVzdOOCrw0KIG5hbcOp?=\";"
+ " filename=\"=?utf-8?B?ZmlsZeOCrw0KIG5hbcOp?=\"; filename*=utf-8\'\'file%E3%82%AF%0D%0A%20nam%C3%A9"
+ "\r\n\r\nHello World\r\n--test_boundary--\r\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ public void ContentLength_SetStreamSupportingSeeking_StreamLengthMatchesHeaderVa
var source = new MockStream(new byte[10], true, true); // Supports seeking.
var content = new StreamContent(source);

Assert.Contains("Content-Length", content.Headers.ToString());
Assert.Contains(content.Headers, h => h.Key == "Content-Length");
Assert.Contains(content.Headers.NonValidated, h => h.Key == "Content-Length");
Assert.Equal(source.Length, content.Headers.ContentLength);
}

[Fact]
public void ContentLength_SetStreamSupportingSeekingWithBuffering_StreamLengthMatchesHeaderValue()
{
var source = new MockStream(new byte[10], true, true); // Supports seeking.
var content = new StreamContent(source, 5);

Assert.Contains("Content-Length", content.Headers.ToString());
Assert.Contains(content.Headers, h => h.Key == "Content-Length");
Assert.Contains(content.Headers.NonValidated, h => h.Key == "Content-Length");
Assert.Equal(source.Length, content.Headers.ContentLength);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ public void ToString_NonDefaultInstanceWithNoCustomHeaders_DumpAllFields(string
Assert.Equal(
$"Method: PUT, RequestUri: '{uriData}', Version: 1.0, Content: " + typeof(StringContent).ToString() + ", Headers:" + Environment.NewLine +
$"{{{Environment.NewLine}" +
" Content-Length: 7" + Environment.NewLine +
" Content-Type: text/plain; charset=utf-8" + Environment.NewLine +
"}", rm.ToString());
}
Expand All @@ -361,6 +362,7 @@ public void ToString_NonDefaultInstanceWithCustomHeaders_DumpAllFields(string ur
" Accept: text/plain; q=0.2" + Environment.NewLine +
" Accept: text/xml; q=0.1" + Environment.NewLine +
" Custom-Request-Header: value1" + Environment.NewLine +
" Content-Length: 7" + Environment.NewLine +
" Content-Type: text/plain; charset=utf-8" + Environment.NewLine +
" Custom-Content-Header: value2" + Environment.NewLine +
"}", rm.ToString());
Expand Down

0 comments on commit a0b8f6f

Please sign in to comment.