Skip to content

Commit 501b4e1

Browse files
committed
Merge branch 'feature/boundary_ends_with_double_dash' into develop
Resolves #123
2 parents ddf2338 + 1c8cf7c commit 501b4e1

2 files changed

Lines changed: 124 additions & 6 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Xunit;
7+
8+
namespace HttpMultipartParser.UnitTests.ParserScenarios
9+
{
10+
public class BoundaryEndsWithDoubleDash
11+
{
12+
// The boundary in this scenario ends with '--'. This is an unusual scenario but perfectly legitimate.
13+
// For details, see: https://github.com/Http-Multipart-Data-Parser/Http-Multipart-Data-Parser/issues/123
14+
private static readonly string _testData = TestUtil.TrimAllLines(
15+
@"--boundary_text--
16+
Content-type: text/plain; charset=UTF-8
17+
Content-Disposition: form-data; name=""file1""; filename=""file1.txt""
18+
19+
file content here
20+
--boundary_text--
21+
Content-type: text/plain; charset=UTF-8
22+
Content-Disposition: form-data; name=""file2""; filename=""file2.txt""
23+
24+
file content here 2
25+
--boundary_text----"
26+
);
27+
28+
private static readonly TestData _testCase = new TestData(
29+
_testData,
30+
Enumerable.Empty<ParameterPart>().ToList(),
31+
new List<FilePart>() {
32+
new FilePart("file1", "file1.txt", TestUtil.StringToStreamNoBom("file content here"), (new[] { new KeyValuePair<string, string>("charset", "UTF-8") }).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)),
33+
new FilePart("file2", "file2.txt", TestUtil.StringToStreamNoBom("file content here 2"), (new[] { new KeyValuePair<string, string>("charset", "UTF-8") }).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)),
34+
}
35+
);
36+
37+
public BoundaryEndsWithDoubleDash()
38+
{
39+
foreach (var filePart in _testCase.ExpectedFileData)
40+
{
41+
filePart.Data.Position = 0;
42+
}
43+
}
44+
45+
/// <summary>
46+
/// Tests for correct detection of the boundary in the input stream.
47+
/// </summary>
48+
[Fact]
49+
public void CanAutoDetectBoundary()
50+
{
51+
using (Stream stream = TestUtil.StringToStream(_testCase.Request, Encoding.UTF8))
52+
{
53+
var parser = MultipartFormDataParser.Parse(stream);
54+
Assert.True(_testCase.Validate(parser));
55+
}
56+
}
57+
58+
/// <summary>
59+
/// Tests for correct detection of the boundary in the input stream.
60+
/// </summary>
61+
[Fact]
62+
public async Task CanAutoDetectBoundaryAsync()
63+
{
64+
using (Stream stream = TestUtil.StringToStream(_testCase.Request, Encoding.UTF8))
65+
{
66+
var parser = await MultipartFormDataParser.ParseAsync(stream, Encoding.UTF8);
67+
Assert.True(_testCase.Validate(parser));
68+
}
69+
}
70+
}
71+
}

Source/HttpMultipartParser/StreamingBinaryMultipartFormDataParser.cs

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,12 @@ private static string DetectBoundary(RebufferableBinaryReader reader)
277277
// Remove the two dashes
278278
string boundary = line.Substring(2);
279279

280-
// If the string ends with '--' it means that we found the "end" boundary and we
281-
// need to trim the two dashes to get the actual boundary
282-
if (boundary.EndsWith("--"))
280+
// If the string ends with '--' and it's not followed by content, we can safely assume that we
281+
// found the "end" boundary. In this scenario, we must trim the two dashes to get the actual boundary.
282+
// Otherwise, the boundary must be accepted as-is. The reason we check for "additional content" is to
283+
// resolve the problem explained in GH-123.
284+
var moreContentAvailable = MoreContentAvailable(reader);
285+
if (boundary.EndsWith("--") && !moreContentAvailable)
283286
{
284287
boundary = boundary.Substring(0, boundary.Length - 2);
285288
reader.Buffer($"--{boundary}--\n");
@@ -333,9 +336,12 @@ private static async Task<string> DetectBoundaryAsync(RebufferableBinaryReader r
333336
// Remove the two dashes
334337
string boundary = line.Substring(2);
335338

336-
// If the string ends with '--' it means that we found the "end" boundary and we
337-
// need to trim the two dashes to get the actual boundary.
338-
if (boundary.EndsWith("--"))
339+
// If the string ends with '--' and it's not followed by content, we can safely assume that we
340+
// found the "end" boundary. In this scenario, we must trim the two dashes to get the actual boundary.
341+
// Otherwise, the boundary must be accepted as-is. The reason we check for "additional content" is to
342+
// resolve the problem explained in GH-123.
343+
var moreContentAvailable = await MoreContentAvailableAsync(reader).ConfigureAwait(false);
344+
if (boundary.EndsWith("--") && !moreContentAvailable)
339345
{
340346
boundary = boundary.Substring(0, boundary.Length - 2);
341347
reader.Buffer($"--{boundary}--\n");
@@ -348,6 +354,47 @@ private static async Task<string> DetectBoundaryAsync(RebufferableBinaryReader r
348354
return boundary;
349355
}
350356

357+
/// <summary>
358+
/// Determine if there is more content.
359+
/// </summary>
360+
/// <param name="reader">
361+
/// The binary reader to parse.
362+
/// </param>
363+
/// <returns>
364+
/// A boolean indicating whether more content is available in the binary reader.
365+
/// </returns>
366+
private static bool MoreContentAvailable(RebufferableBinaryReader reader)
367+
{
368+
var line = reader.ReadLine();
369+
370+
if (line == null) return false;
371+
else reader.Buffer($"{line}\n");
372+
373+
return true;
374+
}
375+
376+
/// <summary>
377+
/// Determine if there is more content.
378+
/// </summary>
379+
/// <param name="reader">
380+
/// The binary reader to parse.
381+
/// </param>
382+
/// <param name="cancellationToken">
383+
/// The cancellation token.
384+
/// </param>
385+
/// <returns>
386+
/// A boolean indicating whether more content is available in the binary reader.
387+
/// </returns>
388+
private static async Task<bool> MoreContentAvailableAsync(RebufferableBinaryReader reader, CancellationToken cancellationToken = default)
389+
{
390+
var line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
391+
392+
if (line == null) return false;
393+
else reader.Buffer($"{line}\n");
394+
395+
return true;
396+
}
397+
351398
/// <summary>
352399
/// Use a few assumptions to determine if a section contains a file.
353400
/// </summary>

0 commit comments

Comments
 (0)