Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content-Length header not always returned when enumerating HttpContentHeaders #102416

Merged
merged 6 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading