Coverage Report

Created: 2026-06-07 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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);
HTTP_RANGES_RB_INSERT
Line
Count
Source
63
RB_GENERATE(HTTP_RANGES, HttpRangeContainerBuffer, rb, HttpRangeContainerBufferCompare);
HTTP_RANGES_RB_REMOVE
Line
Count
Source
63
RB_GENERATE(HTTP_RANGES, HttpRangeContainerBuffer, rb, HttpRangeContainerBufferCompare);
Unexecuted instantiation: HTTP_RANGES_RB_FIND
Unexecuted instantiation: HTTP_RANGES_RB_NFIND
HTTP_RANGES_RB_MINMAX
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
}