/src/suricata7/src/app-layer-htp-range.c
Line | Count | Source |
1 | | /* Copyright (C) 2021 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | /** |
19 | | * \file |
20 | | * |
21 | | * \author Philippe Antoine <p.antoine@catenacyber.fr> |
22 | | */ |
23 | | |
24 | | #include "suricata-common.h" |
25 | | #include "app-layer-htp-range.h" |
26 | | #include "util-misc.h" //ParseSizeStringU64 |
27 | | #include "util-thash.h" //HashTable |
28 | | #include "util-memcmp.h" //SCBufferCmp |
29 | | #include "util-hash-lookup3.h" //hashlittle_safe |
30 | | #include "util-validate.h" //DEBUG_VALIDATE_BUG_ON |
31 | | #include "util-byte.h" //StringParseUint32 |
32 | | |
33 | | typedef struct ContainerTHashTable { |
34 | | THashTableContext *ht; |
35 | | uint32_t timeout; |
36 | | } ContainerTHashTable; |
37 | | |
38 | | // globals |
39 | | ContainerTHashTable ContainerUrlRangeList; |
40 | | |
41 | | static void HttpRangeBlockDerefContainer(HttpRangeContainerBlock *b); |
42 | | |
43 | 74 | #define CONTAINER_URLRANGE_HASH_SIZE 256 |
44 | | |
45 | | int HttpRangeContainerBufferCompare(HttpRangeContainerBuffer *a, HttpRangeContainerBuffer *b) |
46 | 41.0k | { |
47 | | // lexical order : start, buflen, offset |
48 | 41.0k | if (a->start > b->start) |
49 | 5.03k | return 1; |
50 | 35.9k | if (a->start < b->start) |
51 | 2.56k | return -1; |
52 | 33.4k | if (a->buflen > b->buflen) |
53 | 2.23k | return 1; |
54 | 31.1k | if (a->buflen < b->buflen) |
55 | 2.85k | return -1; |
56 | 28.3k | if (a->offset > b->offset) |
57 | 4.78k | return 1; |
58 | 23.5k | if (a->offset < b->offset) |
59 | 7.18k | return -1; |
60 | 16.3k | return 0; |
61 | 23.5k | } |
62 | | |
63 | 55.7k | RB_GENERATE(HTTP_RANGES, HttpRangeContainerBuffer, rb, HttpRangeContainerBufferCompare); HTTP_RANGES_RB_INSERT_COLOR Line | Count | Source | 63 | | RB_GENERATE(HTTP_RANGES, HttpRangeContainerBuffer, rb, HttpRangeContainerBufferCompare); |
HTTP_RANGES_RB_REMOVE_COLOR Line | Count | Source | 63 | | RB_GENERATE(HTTP_RANGES, HttpRangeContainerBuffer, rb, HttpRangeContainerBufferCompare); |
Line | Count | Source | 63 | | RB_GENERATE(HTTP_RANGES, HttpRangeContainerBuffer, rb, HttpRangeContainerBufferCompare); |
Line | Count | Source | 63 | | RB_GENERATE(HTTP_RANGES, HttpRangeContainerBuffer, rb, HttpRangeContainerBufferCompare); |
Unexecuted instantiation: HTTP_RANGES_RB_FIND Unexecuted instantiation: HTTP_RANGES_RB_NFIND Line | Count | Source | 63 | | RB_GENERATE(HTTP_RANGES, HttpRangeContainerBuffer, rb, HttpRangeContainerBufferCompare); |
|
64 | 55.7k | |
65 | 55.7k | static int ContainerUrlRangeSet(void *dst, void *src) |
66 | 55.7k | { |
67 | 2.96k | HttpRangeContainerFile *src_s = src; |
68 | 2.96k | HttpRangeContainerFile *dst_s = dst; |
69 | 2.96k | dst_s->len = src_s->len; |
70 | 2.96k | dst_s->key = SCMalloc(dst_s->len); |
71 | 2.96k | if (dst_s->key == NULL) |
72 | 0 | return -1; |
73 | 2.96k | memcpy(dst_s->key, src_s->key, dst_s->len); |
74 | 2.96k | dst_s->files = FileContainerAlloc(); |
75 | 2.96k | if (dst_s->files == NULL) { |
76 | 0 | SCFree(dst_s->key); |
77 | 0 | return -1; |
78 | 0 | } |
79 | 2.96k | RB_INIT(&dst_s->fragment_tree); |
80 | 2.96k | dst_s->flags = 0; |
81 | 2.96k | dst_s->lastsize = 0; |
82 | 2.96k | dst_s->totalsize = 0; |
83 | 2.96k | dst_s->hdata = NULL; |
84 | 2.96k | dst_s->error = false; |
85 | 2.96k | return 0; |
86 | 2.96k | } |
87 | | |
88 | | static bool ContainerUrlRangeCompare(void *a, void *b) |
89 | 20.0k | { |
90 | 20.0k | const HttpRangeContainerFile *as = a; |
91 | 20.0k | const HttpRangeContainerFile *bs = b; |
92 | | |
93 | | /* ranges in the error state should not be found so they can |
94 | | * be evicted */ |
95 | 20.0k | if (as->error || bs->error) { |
96 | 565 | return false; |
97 | 565 | } |
98 | | |
99 | 19.4k | if (SCBufferCmp(as->key, as->len, bs->key, bs->len) == 0) { |
100 | 15.3k | return true; |
101 | 15.3k | } |
102 | 4.07k | return false; |
103 | 19.4k | } |
104 | | |
105 | | static uint32_t ContainerUrlRangeHash(uint32_t hash_seed, void *s) |
106 | 72.1k | { |
107 | 72.1k | HttpRangeContainerFile *cur = s; |
108 | 72.1k | uint32_t h = hashlittle_safe(cur->key, cur->len, hash_seed); |
109 | 72.1k | return h; |
110 | 72.1k | } |
111 | | |
112 | | // base data stays in hash |
113 | | static void ContainerUrlRangeFree(void *s) |
114 | 0 | { |
115 | 0 | HttpRangeContainerBuffer *range = NULL, *tmp = NULL; |
116 | |
|
117 | 0 | HttpRangeContainerFile *cu = s; |
118 | 0 | SCFree(cu->key); |
119 | 0 | cu->key = NULL; |
120 | 0 | FileContainerFree(cu->files, cu->sbcfg); |
121 | 0 | cu->files = NULL; |
122 | 0 | RB_FOREACH_SAFE (range, HTTP_RANGES, &cu->fragment_tree, tmp) { |
123 | 0 | RB_REMOVE(HTTP_RANGES, &cu->fragment_tree, range); |
124 | 0 | SCFree(range->buffer); |
125 | 0 | (void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, range->buflen); |
126 | 0 | SCFree(range); |
127 | 0 | } |
128 | 0 | } |
129 | | |
130 | | static inline bool ContainerValueRangeTimeout(HttpRangeContainerFile *cu, const SCTime_t ts) |
131 | 0 | { |
132 | | // we only timeout if we have no flow referencing us |
133 | 0 | if ((uint32_t)SCTIME_SECS(ts) > cu->expire || cu->error) { |
134 | 0 | if (SC_ATOMIC_GET(cu->hdata->use_cnt) == 0) { |
135 | 0 | DEBUG_VALIDATE_BUG_ON(cu->files == NULL); |
136 | 0 | return true; |
137 | 0 | } |
138 | 0 | } |
139 | 0 | return false; |
140 | 0 | } |
141 | | |
142 | | static void ContainerUrlRangeUpdate(HttpRangeContainerFile *cu, uint32_t expire) |
143 | 72.1k | { |
144 | 72.1k | cu->expire = expire; |
145 | 72.1k | } |
146 | | |
147 | 74 | #define HTTP_RANGE_DEFAULT_TIMEOUT 60 |
148 | 74 | #define HTTP_RANGE_DEFAULT_MEMCAP 100 * 1024 * 1024 |
149 | | |
150 | | void HttpRangeContainersInit(void) |
151 | 74 | { |
152 | 74 | SCLogDebug("containers start"); |
153 | 74 | const char *str = NULL; |
154 | 74 | uint64_t memcap = HTTP_RANGE_DEFAULT_MEMCAP; |
155 | 74 | uint32_t timeout = HTTP_RANGE_DEFAULT_TIMEOUT; |
156 | 74 | if (ConfGet("app-layer.protocols.http.byterange.memcap", &str) == 1) { |
157 | 0 | if (ParseSizeStringU64(str, &memcap) < 0) { |
158 | 0 | SCLogWarning("memcap value cannot be deduced: %s," |
159 | 0 | " resetting to default", |
160 | 0 | str); |
161 | 0 | memcap = 0; |
162 | 0 | } |
163 | 0 | } |
164 | 74 | if (ConfGet("app-layer.protocols.http.byterange.timeout", &str) == 1) { |
165 | 0 | size_t slen = strlen(str); |
166 | 0 | if (slen > UINT16_MAX || StringParseUint32(&timeout, 10, (uint16_t)slen, str) <= 0) { |
167 | 0 | SCLogWarning("timeout value cannot be deduced: %s," |
168 | 0 | " resetting to default", |
169 | 0 | str); |
170 | 0 | timeout = 0; |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | 74 | ContainerUrlRangeList.ht = |
175 | 74 | THashInit("app-layer.protocols.http.byterange", sizeof(HttpRangeContainerFile), |
176 | 74 | ContainerUrlRangeSet, ContainerUrlRangeFree, ContainerUrlRangeHash, |
177 | 74 | ContainerUrlRangeCompare, false, memcap, CONTAINER_URLRANGE_HASH_SIZE); |
178 | 74 | ContainerUrlRangeList.timeout = timeout; |
179 | | |
180 | 74 | SCLogDebug("containers started"); |
181 | 74 | } |
182 | | |
183 | | void HttpRangeContainersDestroy(void) |
184 | 0 | { |
185 | 0 | THashShutdown(ContainerUrlRangeList.ht); |
186 | 0 | } |
187 | | |
188 | | uint32_t HttpRangeContainersTimeoutHash(const SCTime_t ts) |
189 | 0 | { |
190 | 0 | SCLogDebug("timeout: starting"); |
191 | 0 | uint32_t cnt = 0; |
192 | |
|
193 | 0 | for (uint32_t i = 0; i < ContainerUrlRangeList.ht->config.hash_size; i++) { |
194 | 0 | THashHashRow *hb = &ContainerUrlRangeList.ht->array[i]; |
195 | |
|
196 | 0 | if (HRLOCK_TRYLOCK(hb) != 0) |
197 | 0 | continue; |
198 | | /* hash bucket is now locked */ |
199 | 0 | THashData *h = hb->head; |
200 | 0 | while (h) { |
201 | 0 | DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(h->use_cnt) > (uint32_t)INT_MAX); |
202 | 0 | THashData *n = h->next; |
203 | 0 | THashDataLock(h); |
204 | 0 | if (ContainerValueRangeTimeout(h->data, ts)) { |
205 | | /* remove from the hash */ |
206 | 0 | if (h->prev != NULL) |
207 | 0 | h->prev->next = h->next; |
208 | 0 | if (h->next != NULL) |
209 | 0 | h->next->prev = h->prev; |
210 | 0 | if (hb->head == h) |
211 | 0 | hb->head = h->next; |
212 | 0 | if (hb->tail == h) |
213 | 0 | hb->tail = h->prev; |
214 | 0 | h->next = NULL; |
215 | 0 | h->prev = NULL; |
216 | | // we should log the timed out file somehow... |
217 | | // but it does not belong to any flow... |
218 | 0 | SCLogDebug("timeout: removing range %p", h); |
219 | 0 | ContainerUrlRangeFree(h->data); // TODO do we need a "RECYCLE" func? |
220 | 0 | DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(h->use_cnt) > (uint32_t)INT_MAX); |
221 | 0 | THashDataUnlock(h); |
222 | 0 | THashDataMoveToSpare(ContainerUrlRangeList.ht, h); |
223 | 0 | } else { |
224 | 0 | THashDataUnlock(h); |
225 | 0 | } |
226 | 0 | h = n; |
227 | 0 | } |
228 | 0 | HRLOCK_UNLOCK(hb); |
229 | 0 | } |
230 | | |
231 | 0 | SCLogDebug("timeout: ending"); |
232 | 0 | return cnt; |
233 | 0 | } |
234 | | |
235 | | /** |
236 | | * \returns locked data |
237 | | */ |
238 | | static void *HttpRangeContainerUrlGet(const uint8_t *key, uint32_t keylen, const Flow *f) |
239 | 72.1k | { |
240 | 72.1k | const SCTime_t ts = f->lastts; |
241 | 72.1k | HttpRangeContainerFile lookup; |
242 | 72.1k | memset(&lookup, 0, sizeof(lookup)); |
243 | | // cast so as not to have const in the structure |
244 | 72.1k | lookup.key = (uint8_t *)key; |
245 | 72.1k | lookup.len = keylen; |
246 | 72.1k | struct THashDataGetResult res = THashGetFromHash(ContainerUrlRangeList.ht, &lookup); |
247 | 72.1k | if (res.data) { |
248 | | // nothing more to do if (res.is_new) |
249 | 72.1k | ContainerUrlRangeUpdate(res.data->data, SCTIME_SECS(ts) + ContainerUrlRangeList.timeout); |
250 | 72.1k | HttpRangeContainerFile *c = res.data->data; |
251 | 72.1k | c->hdata = res.data; |
252 | 72.1k | SCLogDebug("c %p", c); |
253 | 72.1k | return res.data->data; |
254 | 72.1k | } |
255 | 0 | return NULL; |
256 | 72.1k | } |
257 | | |
258 | | static HttpRangeContainerBlock *HttpRangeOpenFileAux(HttpRangeContainerFile *c, uint64_t start, |
259 | | uint64_t end, uint64_t total, const StreamingBufferConfig *sbcfg, const uint8_t *name, |
260 | | uint16_t name_len, uint16_t flags) |
261 | 72.1k | { |
262 | 72.1k | if (c->files != NULL && c->files->tail == NULL) { |
263 | | /* this is the first request, we open a single file in the file container |
264 | | * this could be part of ContainerUrlRangeSet if we could have |
265 | | * all the arguments there |
266 | | */ |
267 | 3.08k | if (FileOpenFileWithId(c->files, sbcfg, 0, name, name_len, NULL, 0, flags) != 0) { |
268 | 0 | SCLogDebug("open file for range failed"); |
269 | 0 | return NULL; |
270 | 0 | } |
271 | 3.08k | } |
272 | 72.1k | HttpRangeContainerBlock *curf = SCCalloc(1, sizeof(HttpRangeContainerBlock)); |
273 | 72.1k | if (curf == NULL) { |
274 | 0 | c->error = true; |
275 | 0 | return NULL; |
276 | 0 | } |
277 | 72.1k | curf->files = NULL; |
278 | 72.1k | if (total > c->totalsize) { |
279 | | // we grow to the maximum size indicated by different range requests |
280 | | // we could add some warning/app-layer event in this case where |
281 | | // different range requests indicate different total sizes |
282 | 3.38k | c->totalsize = total; |
283 | 3.38k | } |
284 | 72.1k | const uint64_t buflen = end - start + 1; |
285 | | |
286 | | /* The big part of this function is now to decide which kind of HttpRangeContainerBlock |
287 | | * we will return : |
288 | | * - skipping already processed data |
289 | | * - storing out of order data for later use |
290 | | * - directly appending to the file if we are at the right offset |
291 | | */ |
292 | 72.1k | if (start == c->lastsize && c->files != NULL) { |
293 | | // easy case : append to current file |
294 | 4.30k | curf->container = c; |
295 | | // If we see 2 duplicate range requests with the same range, |
296 | | // the first one takes the ownership of the files container |
297 | | // protected by the lock from caller HTPFileOpenWithRange |
298 | 4.30k | curf->files = c->files; |
299 | 4.30k | c->files = NULL; |
300 | 4.30k | return curf; |
301 | 67.8k | } else if (start < c->lastsize && c->lastsize - start >= buflen) { |
302 | | // only overlap |
303 | | // redundant to be explicit that this block is independent |
304 | 28.4k | curf->toskip = buflen; |
305 | 28.4k | return curf; |
306 | 39.4k | } else if (start < c->lastsize && c->lastsize - start < buflen && c->files != NULL) { |
307 | | // some overlap, then some data to append to the file |
308 | 15.7k | curf->toskip = c->lastsize - start; |
309 | 15.7k | curf->files = c->files; |
310 | 15.7k | c->files = NULL; |
311 | 15.7k | curf->container = c; |
312 | 15.7k | return curf; |
313 | 15.7k | } |
314 | | // Because we are not in the previous cases, we will store the data for later use |
315 | | |
316 | | // block/range to be inserted in ordered linked list |
317 | 23.6k | if (!(THASH_CHECK_MEMCAP(ContainerUrlRangeList.ht, buflen))) { |
318 | | // skips this range |
319 | 3.68k | curf->toskip = buflen; |
320 | 3.68k | return curf; |
321 | 3.68k | } |
322 | 19.9k | curf->container = c; |
323 | | |
324 | 19.9k | HttpRangeContainerBuffer *range = SCCalloc(1, sizeof(HttpRangeContainerBuffer)); |
325 | 19.9k | if (range == NULL) { |
326 | 0 | c->error = true; |
327 | 0 | SCFree(curf); |
328 | 0 | return NULL; |
329 | 0 | } |
330 | | |
331 | 19.9k | (void)SC_ATOMIC_ADD(ContainerUrlRangeList.ht->memuse, buflen); |
332 | 19.9k | range->buffer = SCMalloc(buflen); |
333 | 19.9k | if (range->buffer == NULL) { |
334 | 0 | c->error = true; |
335 | 0 | SCFree(curf); |
336 | 0 | SCFree(range); |
337 | 0 | (void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, buflen); |
338 | 0 | return NULL; |
339 | 0 | } |
340 | 19.9k | range->buflen = buflen; |
341 | 19.9k | range->start = start; |
342 | 19.9k | range->offset = 0; |
343 | 19.9k | range->gap = 0; |
344 | 19.9k | curf->current = range; |
345 | 19.9k | return curf; |
346 | 19.9k | } |
347 | | |
348 | | static HttpRangeContainerBlock *HttpRangeOpenFile(HttpRangeContainerFile *c, uint64_t start, |
349 | | uint64_t end, uint64_t total, const StreamingBufferConfig *sbcfg, const uint8_t *name, |
350 | | uint16_t name_len, uint16_t flags, const uint8_t *data, uint32_t len) |
351 | 72.1k | { |
352 | 72.1k | HttpRangeContainerBlock *r = |
353 | 72.1k | HttpRangeOpenFileAux(c, start, end, total, sbcfg, name, name_len, flags); |
354 | 72.1k | if (r) { |
355 | 72.1k | if (HttpRangeAppendData(sbcfg, r, data, len) < 0) { |
356 | 0 | SCLogDebug("Failed to append data while opening"); |
357 | 0 | } |
358 | 72.1k | } |
359 | 72.1k | return r; |
360 | 72.1k | } |
361 | | |
362 | | HttpRangeContainerBlock *HttpRangeContainerOpenFile(const uint8_t *key, uint32_t keylen, |
363 | | const Flow *f, const HTTPContentRange *crparsed, const StreamingBufferConfig *sbcfg, |
364 | | const uint8_t *name, uint16_t name_len, uint16_t flags, const uint8_t *data, |
365 | | uint32_t data_len) |
366 | 16.3k | { |
367 | 16.3k | HttpRangeContainerFile *file_range_container = HttpRangeContainerUrlGet(key, keylen, f); |
368 | 16.3k | if (file_range_container == NULL) { |
369 | | // probably reached memcap |
370 | 0 | return NULL; |
371 | 0 | } |
372 | 16.3k | file_range_container->sbcfg = sbcfg; |
373 | | |
374 | 16.3k | HttpRangeContainerBlock *r = HttpRangeOpenFile(file_range_container, crparsed->start, |
375 | 16.3k | crparsed->end, crparsed->size, sbcfg, name, name_len, flags, data, data_len); |
376 | 16.3k | SCLogDebug("s->file_range == %p", r); |
377 | 16.3k | if (r == NULL) { |
378 | 0 | THashDecrUsecnt(file_range_container->hdata); |
379 | 0 | DEBUG_VALIDATE_BUG_ON( |
380 | 0 | SC_ATOMIC_GET(file_range_container->hdata->use_cnt) > (uint32_t)INT_MAX); |
381 | 0 | THashDataUnlock(file_range_container->hdata); |
382 | | |
383 | | // probably reached memcap |
384 | 0 | return NULL; |
385 | | /* in some cases we don't take a reference, so decr use cnt */ |
386 | 16.3k | } else if (r->container == NULL) { |
387 | 6.85k | THashDecrUsecnt(file_range_container->hdata); |
388 | 9.48k | } else { |
389 | 9.48k | SCLogDebug("container %p use_cnt %u", r, SC_ATOMIC_GET(r->container->hdata->use_cnt)); |
390 | 9.48k | DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(r->container->hdata->use_cnt) > (uint32_t)INT_MAX); |
391 | 9.48k | } |
392 | | |
393 | | /* we're done, so unlock. But since we have a reference in s->file_range keep use_cnt. */ |
394 | 16.3k | THashDataUnlock(file_range_container->hdata); |
395 | 16.3k | return r; |
396 | 16.3k | } |
397 | | |
398 | | int HttpRangeAppendData(const StreamingBufferConfig *sbcfg, HttpRangeContainerBlock *c, |
399 | | const uint8_t *data, uint32_t len) |
400 | 41.3k | { |
401 | 41.3k | if (len == 0) { |
402 | 19.7k | return 0; |
403 | 19.7k | } |
404 | | // first check if we need to skip all bytes |
405 | 21.6k | if (c->toskip >= len) { |
406 | 13.6k | c->toskip -= len; |
407 | 13.6k | return 0; |
408 | 13.6k | } |
409 | | // then if we need to skip only some bytes |
410 | 7.96k | if (c->toskip > 0) { |
411 | 2.03k | int r = 0; |
412 | 2.03k | if (c->files) { |
413 | 42 | if (data == NULL) { |
414 | | // gap overlapping already known data |
415 | 0 | r = FileAppendData(c->files, sbcfg, NULL, len - c->toskip); |
416 | 42 | } else { |
417 | 42 | r = FileAppendData(c->files, sbcfg, data + c->toskip, len - c->toskip); |
418 | 42 | } |
419 | 42 | } |
420 | 2.03k | c->toskip = 0; |
421 | 2.03k | return r; |
422 | 2.03k | } |
423 | | // If we are owning the file to append to it, let's do it |
424 | 5.92k | if (c->files) { |
425 | 1.36k | SCLogDebug("update files (FileAppendData)"); |
426 | 1.36k | return FileAppendData(c->files, sbcfg, data, len); |
427 | 1.36k | } |
428 | | // Maybe we were in the skipping case, |
429 | | // but we get more data than expected and had set c->toskip = 0 |
430 | | // so we need to check for last case with something to do |
431 | 4.56k | if (c->current) { |
432 | | // So we have a current allocated buffer to copy to |
433 | | // in the case of an unordered range being handled |
434 | 3.96k | SCLogDebug("update current: adding %u bytes to block %p", len, c); |
435 | | // GAP "data" |
436 | 3.96k | if (data == NULL) { |
437 | | // just save the length of the gap |
438 | 16 | c->current->gap += len; |
439 | | // data, but we're not yet complete |
440 | 3.94k | } else if (c->current->offset + len < c->current->buflen) { |
441 | 2.38k | memcpy(c->current->buffer + c->current->offset, data, len); |
442 | 2.38k | c->current->offset += len; |
443 | | // data, we're complete |
444 | 2.38k | } else if (c->current->offset + len == c->current->buflen) { |
445 | 762 | memcpy(c->current->buffer + c->current->offset, data, len); |
446 | 762 | c->current->offset += len; |
447 | | // data, more than expected |
448 | 797 | } else { |
449 | 797 | memcpy(c->current->buffer + c->current->offset, data, |
450 | 797 | c->current->buflen - c->current->offset); |
451 | 797 | c->current->offset = c->current->buflen; |
452 | 797 | } |
453 | 3.96k | } |
454 | 4.56k | return 0; |
455 | 5.92k | } |
456 | | |
457 | | static void HttpRangeFileClose( |
458 | | const StreamingBufferConfig *sbcfg, HttpRangeContainerFile *c, uint16_t flags) |
459 | 322 | { |
460 | 322 | SCLogDebug("closing range %p flags %04x", c, flags); |
461 | 322 | DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(c->hdata->use_cnt) == 0); |
462 | | // move ownership of file c->files->head to caller |
463 | 322 | FileCloseFile(c->files, sbcfg, NULL, 0, c->flags | flags); |
464 | 322 | c->files->head = NULL; |
465 | 322 | c->files->tail = NULL; |
466 | 322 | } |
467 | | |
468 | | /** |
469 | | * \note if `f` is non-NULL, the ownership of the file is transferred to the caller. |
470 | | */ |
471 | | File *HttpRangeClose(const StreamingBufferConfig *sbcfg, HttpRangeContainerBlock *c, uint16_t flags) |
472 | 9.48k | { |
473 | 9.48k | SCLogDebug("c %p c->container %p c->current %p", c, c->container, c->current); |
474 | | |
475 | 9.48k | DEBUG_VALIDATE_BUG_ON(c->container == NULL); |
476 | | |
477 | | /* we're processing an OOO chunk, won't be able to get us a full file just yet */ |
478 | 9.48k | if (c->current) { |
479 | 3.31k | SCLogDebug("processing ooo chunk as c->current is set %p", c->current); |
480 | | // some out-or-order range is finished |
481 | 3.31k | if (c->container->lastsize >= c->current->start + c->current->offset) { |
482 | | // if the range has become obsolete because we received the data already |
483 | | // we just free it |
484 | 15 | (void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, c->current->buflen); |
485 | 15 | SCFree(c->current->buffer); |
486 | 15 | SCFree(c->current); |
487 | 15 | c->current = NULL; |
488 | 15 | SCLogDebug("c->current was obsolete"); |
489 | 15 | return NULL; |
490 | 3.30k | } else { |
491 | | /* otherwise insert in red and black tree. If res != NULL, the insert |
492 | | failed because its a dup. */ |
493 | 3.30k | HttpRangeContainerBuffer *res = |
494 | 3.30k | HTTP_RANGES_RB_INSERT(&c->container->fragment_tree, c->current); |
495 | 3.30k | if (res) { |
496 | 2.85k | SCLogDebug("duplicate range fragment"); |
497 | 2.85k | (void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, c->current->buflen); |
498 | 2.85k | SCFree(c->current->buffer); |
499 | 2.85k | SCFree(c->current); |
500 | 2.85k | c->current = NULL; |
501 | 2.85k | return NULL; |
502 | 2.85k | } |
503 | 449 | SCLogDebug("inserted range fragment"); |
504 | 449 | c->current = NULL; |
505 | 449 | if (c->container->files == NULL) { |
506 | | // we have to wait for the flow owning the file |
507 | 3 | return NULL; |
508 | 3 | } |
509 | 446 | if (c->container->files->tail == NULL) { |
510 | | // file has already been closed meanwhile |
511 | 0 | return NULL; |
512 | 0 | } |
513 | | // keep on going, maybe this out of order chunk |
514 | | // became the missing part between open and close |
515 | 446 | } |
516 | 446 | SCLogDebug("c->current was set, file incomplete so return NULL"); |
517 | 6.16k | } else if (c->toskip > 0) { |
518 | | // was only an overlapping range, truncated before new bytes |
519 | 1.07k | SCLogDebug("c->toskip %" PRIu64, c->toskip); |
520 | 1.07k | if (c->files) { |
521 | | // if we expected new bytes after overlap |
522 | 1.07k | c->container->files = c->files; |
523 | 1.07k | c->files = NULL; |
524 | 1.07k | } |
525 | 1.07k | return NULL; |
526 | 5.09k | } else { |
527 | | // we just finished an in-order block |
528 | 5.09k | DEBUG_VALIDATE_BUG_ON(c->files == NULL); |
529 | | // move back the ownership of the file container to HttpRangeContainerFile |
530 | 5.09k | c->container->files = c->files; |
531 | 5.09k | c->files = NULL; |
532 | 5.09k | DEBUG_VALIDATE_BUG_ON(c->container->files->tail == NULL); |
533 | 5.09k | } |
534 | | |
535 | 5.54k | File *f = c->container->files->tail; |
536 | | |
537 | | /* See if we can use our stored fragments to (partly) reconstruct the file */ |
538 | 5.54k | HttpRangeContainerBuffer *range, *safe = NULL; |
539 | 5.54k | RB_FOREACH_SAFE (range, HTTP_RANGES, &c->container->fragment_tree, safe) { |
540 | 948 | if (f->size < range->start) { |
541 | | // this next range is not reached yet |
542 | 842 | break; |
543 | 842 | } |
544 | 106 | if (f->size == range->start) { |
545 | | // a new range just begins where we ended, append it |
546 | 59 | if (range->gap > 0) { |
547 | | // if the range had a gap, begin by it |
548 | 0 | if (FileAppendData(c->container->files, sbcfg, NULL, range->gap) != 0) { |
549 | 0 | c->container->lastsize = f->size; |
550 | 0 | HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED); |
551 | 0 | c->container->error = true; |
552 | 0 | return f; |
553 | 0 | } |
554 | 0 | } |
555 | 59 | if (FileAppendData(c->container->files, sbcfg, range->buffer, range->offset) != 0) { |
556 | 34 | c->container->lastsize = f->size; |
557 | 34 | HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED); |
558 | 34 | c->container->error = true; |
559 | 34 | return f; |
560 | 34 | } |
561 | 59 | } else { |
562 | | // the range starts before where we ended |
563 | 47 | uint64_t overlap = f->size - range->start; |
564 | 47 | if (overlap < range->offset) { |
565 | 16 | if (range->gap > 0) { |
566 | | // if the range had a gap, begin by it |
567 | 0 | if (FileAppendData(c->container->files, sbcfg, NULL, range->gap) != 0) { |
568 | 0 | c->container->lastsize = f->size; |
569 | 0 | HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED); |
570 | 0 | c->container->error = true; |
571 | 0 | return f; |
572 | 0 | } |
573 | 0 | } |
574 | | // And the range ends beyond where we ended |
575 | | // in this case of overlap, only add the extra data |
576 | 16 | if (FileAppendData(c->container->files, sbcfg, range->buffer + overlap, |
577 | 16 | range->offset - overlap) != 0) { |
578 | 0 | c->container->lastsize = f->size; |
579 | 0 | HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED); |
580 | 0 | c->container->error = true; |
581 | 0 | return f; |
582 | 0 | } |
583 | 16 | } |
584 | 47 | } |
585 | | /* Remove this range from the tree */ |
586 | 72 | HTTP_RANGES_RB_REMOVE(&c->container->fragment_tree, range); |
587 | 72 | (void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, range->buflen); |
588 | 72 | SCFree(range->buffer); |
589 | 72 | SCFree(range); |
590 | 72 | } |
591 | | // wait until we merged all the buffers to update known size |
592 | 5.50k | c->container->lastsize = f->size; |
593 | | |
594 | 5.50k | if (f->size >= c->container->totalsize) { |
595 | | // we finished the whole file |
596 | 72 | HttpRangeFileClose(sbcfg, c->container, flags); |
597 | 5.43k | } else { |
598 | | // we are expecting more ranges |
599 | 5.43k | f = NULL; |
600 | 5.43k | SCLogDebug("expecting more use_cnt %u", SC_ATOMIC_GET(c->container->hdata->use_cnt)); |
601 | 5.43k | } |
602 | 5.50k | SCLogDebug("returning f %p (c:%p container:%p)", f, c, c->container); |
603 | 5.50k | return f; |
604 | 5.54k | } |
605 | | |
606 | | static void HttpRangeBlockDerefContainer(HttpRangeContainerBlock *b) |
607 | 72.1k | { |
608 | 72.1k | if (b && b->container) { |
609 | 40.0k | DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(b->container->hdata->use_cnt) == 0); |
610 | 40.0k | THashDecrUsecnt(b->container->hdata); |
611 | 40.0k | b->container = NULL; |
612 | 40.0k | } |
613 | 72.1k | } |
614 | | |
615 | | void HttpRangeFreeBlock(HttpRangeContainerBlock *b) |
616 | 16.3k | { |
617 | 16.3k | if (b) { |
618 | 16.3k | BUG_ON(b->container == NULL && b->files != NULL); |
619 | 16.3k | const StreamingBufferConfig *sbcfg = b->container ? b->container->sbcfg : NULL; |
620 | | |
621 | 16.3k | HttpRangeBlockDerefContainer(b); |
622 | | |
623 | 16.3k | if (b->current) { |
624 | 0 | (void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, b->current->buflen); |
625 | 0 | SCFree(b->current->buffer); |
626 | 0 | SCFree(b->current); |
627 | 0 | } |
628 | | // we did not move ownership of the file container back to HttpRangeContainerFile |
629 | 16.3k | DEBUG_VALIDATE_BUG_ON(b->files != NULL); |
630 | 16.3k | if (b->files != NULL) { |
631 | 0 | FileContainerFree(b->files, sbcfg); |
632 | 0 | b->files = NULL; |
633 | 0 | } |
634 | 16.3k | SCFree(b); |
635 | 16.3k | } |
636 | 16.3k | } |