1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
// Licensed under the MIT License.
3
4
// Copied from https://github.com/aspnet/AspNetCore/tree/master/src/Http/WebUtilities/src
5
6
using System;
7
using System.Collections.Generic;
8
using System.Diagnostics;
9
using System.IO;
10
using System.Threading;
11
using System.Threading.Tasks;
12
13
#pragma warning disable CA1001 // disposable fields
14
#pragma warning disable IDE0008 // Use explicit type
15
16
namespace Azure.Core.Http.Multipart
17
{
18
// https://www.ietf.org/rfc/rfc2046.txt
19
internal class MultipartReader
20
{
21
public const int DefaultHeadersCountLimit = 16;
22
public const int DefaultHeadersLengthLimit = 1024 * 16;
23
private const int DefaultBufferSize = 1024 * 4;
24
25
private readonly BufferedReadStream _stream;
26
private readonly MultipartBoundary _boundary;
27
private MultipartReaderStream _currentStream;
28
29
public MultipartReader(string boundary, Stream stream)
30
: this(boundary, stream, DefaultBufferSize)
31
{
32
}
33
34
public MultipartReader(string boundary, Stream stream, int bufferSize)
35
{
36
if (boundary == null)
37
{
38
throw new ArgumentNullException(nameof(boundary));
39
}
40
41
if (stream == null)
42
{
43
throw new ArgumentNullException(nameof(stream));
44
}
45
46
if (bufferSize < boundary.Length + 8) // Size of the boundary + leading and trailing CRLF + leading and trai
47
{
48
throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, "Insufficient buffer space, the bu
49
}
50
_stream = new BufferedReadStream(stream, bufferSize);
51
_boundary = new MultipartBoundary(boundary, false);
52
// This stream will drain any preamble data and remove the first boundary marker.
53
// TODO: HeadersLengthLimit can't be modified until after the constructor.
54
_currentStream = new MultipartReaderStream(_stream, _boundary) { LengthLimit = HeadersLengthLimit };
55
}
56
57
/// <summary>
58
/// The limit for the number of headers to read.
59
/// </summary>
60
public int HeadersCountLimit { get; set; } = DefaultHeadersCountLimit;
61
62
/// <summary>
63
/// The combined size limit for headers per multipart section.
64
/// </summary>
65
public int HeadersLengthLimit { get; set; } = DefaultHeadersLengthLimit;
66
67
/// <summary>
68
/// The optional limit for the total response body length.
69
/// </summary>
70
public long? BodyLengthLimit { get; set; }
71
72
public async Task<MultipartSection> ReadNextSectionAsync(CancellationToken cancellationToken = new CancellationT
73
{
74
// Drain the prior section.
75
await _currentStream.DrainAsync(cancellationToken).ConfigureAwait(false);
76
// If we're at the end return null
77
if (_currentStream.FinalBoundaryFound)
78
{
79
// There may be trailer data after the last boundary.
80
await _stream.DrainAsync(HeadersLengthLimit, cancellationToken).ConfigureAwait(false);
81
return null;
82
}
83
var headers = await ReadHeadersAsync(cancellationToken).ConfigureAwait(false);
84
_boundary.ExpectLeadingCrlf = true;
85
_currentStream = new MultipartReaderStream(_stream, _boundary) { LengthLimit = BodyLengthLimit };
86
long? baseStreamOffset = _stream.CanSeek ? (long?)_stream.Position : null;
87
return new MultipartSection() { Headers = headers, Body = _currentStream, BaseStreamOffset = baseStreamOffse
88
}
89
90
private async Task<Dictionary<string, StringValues>> ReadHeadersAsync(CancellationToken cancellationToken)
91
{
92
int totalSize = 0;
93
var accumulator = new KeyValueAccumulator();
94
var line = await _stream.ReadLineAsync(HeadersLengthLimit - totalSize, cancellationToken).ConfigureAwait(fal
95
while (!string.IsNullOrEmpty(line))
96
{
97
if (HeadersLengthLimit - totalSize < line.Length)
98
{
99
throw new InvalidDataException($"Multipart headers length limit {HeadersLengthLimit} exceeded.");
100
}
101
totalSize += line.Length;
102
int splitIndex = line.IndexOf(':');
103
if (splitIndex <= 0)
104
{
105
throw new InvalidDataException($"Invalid header line: {line}");
106
}
107
108
var name = line.Substring(0, splitIndex);
109
var value = line.Substring(splitIndex + 1, line.Length - splitIndex - 1).Trim();
110
accumulator.Append(name, value);
111
if (accumulator.KeyCount > HeadersCountLimit)
112
{
113
throw new InvalidDataException($"Multipart headers count limit {HeadersCountLimit} exceeded.");
114
}
115
116
line = await _stream.ReadLineAsync(HeadersLengthLimit - totalSize, cancellationToken).ConfigureAwait(fal
117
}
118
119
return accumulator.GetResults();
120
}
121
}
122
}
ncG1vNJzZmiZqqq%2Fpr%2FDpJirrJmbrqTA0meZpaeSY7CwvsRnrqKmlKTEtHrNnqtomaqqv6Z50p2iZp6fp3qvsdNoeqiclVp%2FcY%2FOr5yrmZeafILG1KucZ4ukpL%2Bis8RneaWnkqh7g63TnJ%2BYhaWhwaq8wKuri52RmbKzesetpKU%3D