Coverage Report

Created: 2024-04-25 06:10

/src/uWebSockets/src/ChunkedEncoding.h
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Authored by Alex Hultman, 2018-2022.
3
 * Intellectual property of third-party.
4
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
#ifndef UWS_CHUNKEDENCODING_H
19
#define UWS_CHUNKEDENCODING_H
20
21
/* Independent chunked encoding parser, used by HttpParser. */
22
23
#include <string>
24
#include <cstring>
25
#include <algorithm>
26
#include <string_view>
27
#include "MoveOnlyFunction.h"
28
#include <optional>
29
30
namespace uWS {
31
32
    constexpr uint64_t STATE_HAS_SIZE = 1ull << (sizeof(uint64_t) * 8 - 1);//0x80000000;
33
    constexpr uint64_t STATE_IS_CHUNKED = 1ull << (sizeof(uint64_t) * 8 - 2);//0x40000000;
34
    constexpr uint64_t STATE_SIZE_MASK = ~(3ull << (sizeof(uint64_t) * 8 - 2));//0x3FFFFFFF;
35
    constexpr uint64_t STATE_IS_ERROR = ~0ull;//0xFFFFFFFF;
36
    constexpr uint64_t STATE_SIZE_OVERFLOW = 0x0Full << (sizeof(uint64_t) * 8 - 8);//0x0F000000;
37
38
129k
    inline uint64_t chunkSize(uint64_t state) {
39
129k
        return state & STATE_SIZE_MASK;
40
129k
    }
41
42
    /* Reads hex number until CR or out of data to consume. Updates state. Returns bytes consumed. */
43
5.59k
    inline void consumeHexNumber(std::string_view &data, uint64_t &state) {
44
        /* Consume everything higher than 32 */
45
30.4k
        while (data.length() && data.data()[0] > 32) {
46
47
24.9k
            unsigned char digit = (unsigned char)data.data()[0];
48
24.9k
            if (digit >= 'a') {
49
1.92k
                digit = (unsigned char) (digit - ('a' - ':'));
50
23.0k
            } else if (digit >= 'A') {
51
1.09k
                digit = (unsigned char) (digit - ('A' - ':'));
52
1.09k
            }
53
54
24.9k
            unsigned int number = ((unsigned int) digit - (unsigned int) '0');
55
56
24.9k
            if (number > 16 || (chunkSize(state) & STATE_SIZE_OVERFLOW)) {
57
70
                state = STATE_IS_ERROR;
58
70
                return;
59
70
            }
60
61
            // extract state bits
62
24.8k
            uint64_t bits = /*state &*/ STATE_IS_CHUNKED;
63
64
24.8k
            state = (state & STATE_SIZE_MASK) * 16ull + number;
65
66
24.8k
            state |= bits;
67
24.8k
            data.remove_prefix(1);
68
24.8k
        }
69
        /* Consume everything not /n */
70
61.6k
        while (data.length() && data.data()[0] != '\n') {
71
56.0k
            data.remove_prefix(1);
72
56.0k
        }
73
        /* Now we stand on \n so consume it and enable size */
74
5.52k
        if (data.length()) {
75
4.53k
            state += 2; // include the two last /r/n
76
4.53k
            state |= STATE_HAS_SIZE | STATE_IS_CHUNKED;
77
4.53k
            data.remove_prefix(1);
78
4.53k
        }
79
5.52k
    }
80
81
22.4k
    inline void decChunkSize(uint64_t &state, unsigned int by) {
82
83
        //unsigned int bits = state & STATE_IS_CHUNKED;
84
85
22.4k
        state = (state & ~STATE_SIZE_MASK) | (chunkSize(state) - by);
86
87
        //state |= bits;
88
22.4k
    }
89
90
35.2k
    inline bool hasChunkSize(uint64_t state) {
91
35.2k
        return state & STATE_HAS_SIZE;
92
35.2k
    }
93
94
    /* Are we in the middle of parsing chunked encoding? */
95
23.0k
    inline bool isParsingChunkedEncoding(uint64_t state) {
96
23.0k
        return state & ~STATE_SIZE_MASK;
97
23.0k
    }
98
99
28.5k
    inline bool isParsingInvalidChunkedEncoding(uint64_t state) {
100
28.5k
        return state == STATE_IS_ERROR;
101
28.5k
    }
102
103
    /* Returns next chunk (empty or not), or if all data was consumed, nullopt is returned. */
104
46.5k
    static std::optional<std::string_view> getNextChunk(std::string_view &data, uint64_t &state, bool trailer = false) {
105
106
51.2k
        while (data.length()) {
107
108
            // if in "drop trailer mode", just drop up to what we have as size
109
29.7k
            if (((state & STATE_IS_CHUNKED) == 0) && hasChunkSize(state) && chunkSize(state)) {
110
111
                //printf("Parsing trailer now\n");
112
113
2.93k
                while(data.length() && chunkSize(state)) {
114
2.73k
                    data.remove_prefix(1);
115
2.73k
                    decChunkSize(state, 1);
116
117
2.73k
                    if (chunkSize(state) == 0) {
118
119
                        /* This is an actual place where we need 0 as state */
120
1.36k
                        state = 0;
121
122
                        /* The parser MUST stop consuming here */
123
1.36k
                        return std::nullopt;
124
1.36k
                    }
125
2.73k
                }
126
200
                continue;
127
1.56k
            }
128
129
28.1k
            if (!hasChunkSize(state)) {
130
5.59k
                consumeHexNumber(data, state);
131
5.59k
                if (isParsingInvalidChunkedEncoding(state)) {
132
70
                    return std::nullopt;
133
70
                }
134
5.52k
                if (hasChunkSize(state) && chunkSize(state) == 2) {
135
136
                    //printf("Setting state to trailer-parsing and emitting empty chunk\n");
137
138
                    // set trailer state and increase size to 4
139
1.43k
                    if (trailer) {
140
0
                        state = 4 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE;
141
1.43k
                    } else {
142
1.43k
                        state = 2 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE;
143
1.43k
                    }
144
145
1.43k
                    return std::string_view(nullptr, 0);
146
1.43k
                }
147
4.09k
                continue;
148
5.52k
            }
149
150
            // do we have data to emit all?
151
22.5k
            if (data.length() >= chunkSize(state)) {
152
                // emit all but 2 bytes then reset state to 0 and goto beginning
153
                // not fin
154
2.86k
                std::string_view emitSoon;
155
2.86k
                bool shouldEmit = false;
156
2.86k
                if (chunkSize(state) > 2) {
157
2.47k
                    emitSoon = std::string_view(data.data(), chunkSize(state) - 2);
158
2.47k
                    shouldEmit = true;
159
2.47k
                }
160
2.86k
                data.remove_prefix(chunkSize(state));
161
2.86k
                state = STATE_IS_CHUNKED;
162
2.86k
                if (shouldEmit) {
163
2.47k
                    return emitSoon;
164
2.47k
                }
165
396
                continue;
166
19.7k
            } else {
167
                /* We will consume all our input data */
168
19.7k
                std::string_view emitSoon;
169
19.7k
                if (chunkSize(state) > 2) {
170
19.7k
                    uint64_t maximalAppEmit = chunkSize(state) - 2;
171
19.7k
                    if (data.length() > maximalAppEmit) {
172
371
                        emitSoon = data.substr(0, maximalAppEmit);
173
19.3k
                    } else {
174
                        //cb(data);
175
19.3k
                        emitSoon = data;
176
19.3k
                    }
177
19.7k
                }
178
19.7k
                decChunkSize(state, (unsigned int) data.length());
179
19.7k
                state |= STATE_IS_CHUNKED;
180
                // new: decrease data by its size (bug)
181
19.7k
                data.remove_prefix(data.length()); // ny bug fix för getNextChunk
182
19.7k
                if (emitSoon.length()) {
183
19.7k
                    return emitSoon;
184
19.7k
                } else {
185
1
                    return std::nullopt;
186
1
                }
187
19.7k
            }
188
22.5k
        }
189
190
21.5k
        return std::nullopt;
191
46.5k
    }
192
193
    /* This is really just a wrapper for convenience */
194
    struct ChunkIterator {
195
196
        std::string_view *data;
197
        std::optional<std::string_view> chunk;
198
        uint64_t *state;
199
        bool trailer;
200
201
22.9k
        ChunkIterator(std::string_view *data, uint64_t *state, bool trailer = false) : data(data), state(state), trailer(trailer) {
202
22.9k
            chunk = uWS::getNextChunk(*data, *state, trailer);
203
22.9k
        }
204
205
22.9k
        ChunkIterator() {
206
207
22.9k
        }
208
209
22.9k
        ChunkIterator begin() {
210
22.9k
            return *this;
211
22.9k
        }
212
213
22.9k
        ChunkIterator end() {
214
22.9k
            return ChunkIterator();
215
22.9k
        }
216
217
23.6k
        std::string_view operator*() {
218
23.6k
            if (!chunk.has_value()) {
219
0
                std::abort();
220
0
            }
221
23.6k
            return chunk.value();
222
23.6k
        }
223
224
46.5k
        bool operator!=(const ChunkIterator &other) const {
225
46.5k
            return other.chunk.has_value() != chunk.has_value();
226
46.5k
        }
227
228
23.6k
        ChunkIterator &operator++() {
229
23.6k
            chunk = uWS::getNextChunk(*data, *state, trailer);
230
23.6k
            return *this;
231
23.6k
        }
232
233
    };
234
}
235
236
#endif // UWS_CHUNKEDENCODING_H