Welcome to mirror list, hosted at ThFree Co, Russian Federation.

MultipartReader.cs « src « WebUtilities « Http « src - github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 4de7165dc52920df10f086794f673ff6ba8f1803 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.WebUtilities;

// https://www.ietf.org/rfc/rfc2046.txt
/// <summary>
/// Reads multipart form content from the specified <see cref="Stream"/>.
/// </summary>
public class MultipartReader
{
    /// <summary>
    /// Gets the default value for <see cref="HeadersCountLimit"/>.
    /// Defaults to 16‬.
    /// </summary>
    public const int DefaultHeadersCountLimit = 16;

    /// <summary>
    /// Gets the default value for <see cref="HeadersLengthLimit"/>.
    /// Defaults to 16,384‬ bytes‬, which is approximately 16KB.
    /// </summary>
    public const int DefaultHeadersLengthLimit = 1024 * 16;
    private const int DefaultBufferSize = 1024 * 4;

    private readonly BufferedReadStream _stream;
    private readonly MultipartBoundary _boundary;
    private MultipartReaderStream _currentStream;

    /// <summary>
    /// Initializes a new instance of <see cref="MultipartReader"/>.
    /// </summary>
    /// <param name="boundary">The multipart boundary.</param>
    /// <param name="stream">The <see cref="Stream"/> containing multipart data.</param>
    public MultipartReader(string boundary, Stream stream)
        : this(boundary, stream, DefaultBufferSize)
    {
    }

    /// <summary>
    /// Initializes a new instance of <see cref="MultipartReader"/>.
    /// </summary>
    /// <param name="boundary">The multipart boundary.</param>
    /// <param name="stream">The <see cref="Stream"/> containing multipart data.</param>
    /// <param name="bufferSize">The minimum buffer size to use.</param>
    public MultipartReader(string boundary, Stream stream, int bufferSize)
    {
        if (boundary == null)
        {
            throw new ArgumentNullException(nameof(boundary));
        }

        if (stream == null)
        {
            throw new ArgumentNullException(nameof(stream));
        }

        if (bufferSize < boundary.Length + 8) // Size of the boundary + leading and trailing CRLF + leading and trailing '--' markers.
        {
            throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, "Insufficient buffer space, the buffer must be larger than the boundary: " + boundary);
        }
        _stream = new BufferedReadStream(stream, bufferSize);
        _boundary = new MultipartBoundary(boundary, false);
        // This stream will drain any preamble data and remove the first boundary marker.
        // TODO: HeadersLengthLimit can't be modified until after the constructor.
        _currentStream = new MultipartReaderStream(_stream, _boundary) { LengthLimit = HeadersLengthLimit };
    }

    /// <summary>
    /// The limit for the number of headers to read.
    /// </summary>
    public int HeadersCountLimit { get; set; } = DefaultHeadersCountLimit;

    /// <summary>
    /// The combined size limit for headers per multipart section.
    /// </summary>
    public int HeadersLengthLimit { get; set; } = DefaultHeadersLengthLimit;

    /// <summary>
    /// The optional limit for the total response body length.
    /// </summary>
    public long? BodyLengthLimit { get; set; }

    /// <summary>
    /// Reads the next <see cref="MultipartSection"/>.
    /// </summary>
    /// <param name="cancellationToken">The token to monitor for cancellation requests.
    /// The default value is <see cref="CancellationToken.None"/>.</param>
    /// <returns></returns>
    public async Task<MultipartSection?> ReadNextSectionAsync(CancellationToken cancellationToken = new CancellationToken())
    {
        // Drain the prior section.
        await _currentStream.DrainAsync(cancellationToken);
        // If we're at the end return null
        if (_currentStream.FinalBoundaryFound)
        {
            // There may be trailer data after the last boundary.
            await _stream.DrainAsync(HeadersLengthLimit, cancellationToken);
            return null;
        }
        var headers = await ReadHeadersAsync(cancellationToken);
        _boundary.ExpectLeadingCrlf = true;
        _currentStream = new MultipartReaderStream(_stream, _boundary) { LengthLimit = BodyLengthLimit };
        long? baseStreamOffset = _stream.CanSeek ? (long?)_stream.Position : null;
        return new MultipartSection() { Headers = headers, Body = _currentStream, BaseStreamOffset = baseStreamOffset };
    }

    private async Task<Dictionary<string, StringValues>> ReadHeadersAsync(CancellationToken cancellationToken)
    {
        int totalSize = 0;
        var accumulator = new KeyValueAccumulator();
        var line = await _stream.ReadLineAsync(HeadersLengthLimit - totalSize, cancellationToken);
        while (!string.IsNullOrEmpty(line))
        {
            if (HeadersLengthLimit - totalSize < line.Length)
            {
                throw new InvalidDataException($"Multipart headers length limit {HeadersLengthLimit} exceeded.");
            }
            totalSize += line.Length;
            int splitIndex = line.IndexOf(':');
            if (splitIndex <= 0)
            {
                throw new InvalidDataException($"Invalid header line: {line}");
            }

            var name = line.Substring(0, splitIndex);
            var value = line.Substring(splitIndex + 1, line.Length - splitIndex - 1).Trim();
            accumulator.Append(name, value);
            if (accumulator.KeyCount > HeadersCountLimit)
            {
                throw new InvalidDataException($"Multipart headers count limit {HeadersCountLimit} exceeded.");
            }

            line = await _stream.ReadLineAsync(HeadersLengthLimit - totalSize, cancellationToken);
        }

        return accumulator.GetResults();
    }
}