mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
629 lines
22 KiB
C
629 lines
22 KiB
C
/* Copyright (C) 2024 Open Information Security Foundation
|
|
*
|
|
* You can copy, redistribute or modify this Program under the terms of
|
|
* the GNU General Public License version 2 as published by the Free
|
|
* Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* version 2 along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* \author Philippe Antoine <p.antoine@catenacyber.fr>
|
|
*/
|
|
|
|
#include "suricata-common.h"
|
|
#include "app-layer-htp-range.h"
|
|
#include "util-misc.h" //ParseSizeStringU64
|
|
#include "util-thash.h" //HashTable
|
|
#include "util-memcmp.h" //SCBufferCmp
|
|
#include "util-hash-lookup3.h" //hashlittle_safe
|
|
#include "util-validate.h" //DEBUG_VALIDATE_BUG_ON
|
|
#include "util-byte.h" //StringParseUint32
|
|
|
|
typedef struct ContainerTHashTable {
|
|
THashTableContext *ht;
|
|
uint32_t timeout;
|
|
} ContainerTHashTable;
|
|
|
|
// globals
|
|
ContainerTHashTable ContainerUrlRangeList;
|
|
|
|
static void HttpRangeBlockDerefContainer(HttpRangeContainerBlock *b);
|
|
|
|
#define CONTAINER_URLRANGE_HASH_SIZE 256
|
|
|
|
int HTPByteRangeSetMemcap(uint64_t size)
|
|
{
|
|
if (size == 0 || (uint64_t)SC_ATOMIC_GET(ContainerUrlRangeList.ht->memuse) < size) {
|
|
SC_ATOMIC_SET(ContainerUrlRangeList.ht->config.memcap, size);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t HTPByteRangeMemcapGlobalCounter(void)
|
|
{
|
|
uint64_t tmpval = SC_ATOMIC_GET(ContainerUrlRangeList.ht->config.memcap);
|
|
return tmpval;
|
|
}
|
|
|
|
uint64_t HTPByteRangeMemuseGlobalCounter(void)
|
|
{
|
|
uint64_t tmpval = SC_ATOMIC_GET(ContainerUrlRangeList.ht->memuse);
|
|
return tmpval;
|
|
}
|
|
|
|
int HttpRangeContainerBufferCompare(HttpRangeContainerBuffer *a, HttpRangeContainerBuffer *b)
|
|
{
|
|
// lexical order : start, buflen, offset
|
|
if (a->start > b->start)
|
|
return 1;
|
|
if (a->start < b->start)
|
|
return -1;
|
|
if (a->buflen > b->buflen)
|
|
return 1;
|
|
if (a->buflen < b->buflen)
|
|
return -1;
|
|
if (a->offset > b->offset)
|
|
return 1;
|
|
if (a->offset < b->offset)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
RB_GENERATE(HTTP_RANGES, HttpRangeContainerBuffer, rb, HttpRangeContainerBufferCompare);
|
|
|
|
static int ContainerUrlRangeSet(void *dst, void *src)
|
|
{
|
|
HttpRangeContainerFile *src_s = src;
|
|
HttpRangeContainerFile *dst_s = dst;
|
|
dst_s->len = src_s->len;
|
|
dst_s->key = SCMalloc(dst_s->len);
|
|
if (dst_s->key == NULL)
|
|
return -1;
|
|
memcpy(dst_s->key, src_s->key, dst_s->len);
|
|
dst_s->files = FileContainerAlloc();
|
|
if (dst_s->files == NULL) {
|
|
SCFree(dst_s->key);
|
|
return -1;
|
|
}
|
|
RB_INIT(&dst_s->fragment_tree);
|
|
dst_s->flags = 0;
|
|
dst_s->lastsize = 0;
|
|
dst_s->totalsize = 0;
|
|
dst_s->hdata = NULL;
|
|
dst_s->error = false;
|
|
return 0;
|
|
}
|
|
|
|
static bool ContainerUrlRangeCompare(void *a, void *b)
|
|
{
|
|
const HttpRangeContainerFile *as = a;
|
|
const HttpRangeContainerFile *bs = b;
|
|
|
|
/* ranges in the error state should not be found so they can
|
|
* be evicted */
|
|
if (as->error || bs->error) {
|
|
return false;
|
|
}
|
|
|
|
if (SCBufferCmp(as->key, as->len, bs->key, bs->len) == 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint32_t ContainerUrlRangeHash(uint32_t hash_seed, void *s)
|
|
{
|
|
HttpRangeContainerFile *cur = s;
|
|
uint32_t h = hashlittle_safe(cur->key, cur->len, hash_seed);
|
|
return h;
|
|
}
|
|
|
|
// base data stays in hash
|
|
static void ContainerUrlRangeFree(void *s)
|
|
{
|
|
HttpRangeContainerBuffer *range = NULL, *tmp = NULL;
|
|
|
|
HttpRangeContainerFile *cu = s;
|
|
SCFree(cu->key);
|
|
cu->key = NULL;
|
|
FileContainerFree(cu->files, cu->sbcfg);
|
|
cu->files = NULL;
|
|
RB_FOREACH_SAFE (range, HTTP_RANGES, &cu->fragment_tree, tmp) {
|
|
RB_REMOVE(HTTP_RANGES, &cu->fragment_tree, range);
|
|
SCFree(range->buffer);
|
|
(void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, range->buflen);
|
|
SCFree(range);
|
|
}
|
|
}
|
|
|
|
static inline bool ContainerValueRangeTimeout(void *data, const SCTime_t ts)
|
|
{
|
|
HttpRangeContainerFile *cu = data;
|
|
// we only timeout if we have no flow referencing us
|
|
if (SCTIME_CMP_GTE(ts, cu->expire) || cu->error) {
|
|
DEBUG_VALIDATE_BUG_ON(cu->files == NULL);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void ContainerUrlRangeUpdate(HttpRangeContainerFile *cu, SCTime_t expire)
|
|
{
|
|
cu->expire = expire;
|
|
}
|
|
|
|
#define HTTP_RANGE_DEFAULT_TIMEOUT 60
|
|
#define HTTP_RANGE_DEFAULT_MEMCAP 100 * 1024 * 1024
|
|
|
|
void HttpRangeContainersInit(void)
|
|
{
|
|
SCLogDebug("containers start");
|
|
const char *str = NULL;
|
|
uint64_t memcap = HTTP_RANGE_DEFAULT_MEMCAP;
|
|
uint32_t timeout = HTTP_RANGE_DEFAULT_TIMEOUT;
|
|
if (SCConfGet("app-layer.protocols.http.byterange.memcap", &str) == 1) {
|
|
if (ParseSizeStringU64(str, &memcap) < 0) {
|
|
SCLogWarning("memcap value cannot be deduced: %s,"
|
|
" resetting to default",
|
|
str);
|
|
memcap = 0;
|
|
}
|
|
}
|
|
if (SCConfGet("app-layer.protocols.http.byterange.timeout", &str) == 1) {
|
|
size_t slen = strlen(str);
|
|
if (slen > UINT16_MAX || StringParseUint32(&timeout, 10, (uint16_t)slen, str) <= 0) {
|
|
SCLogWarning("timeout value cannot be deduced: %s,"
|
|
" resetting to default",
|
|
str);
|
|
timeout = 0;
|
|
}
|
|
}
|
|
|
|
ContainerUrlRangeList.ht = THashInit("app-layer.protocols.http.byterange",
|
|
sizeof(HttpRangeContainerFile), ContainerUrlRangeSet, ContainerUrlRangeFree,
|
|
ContainerUrlRangeHash, ContainerUrlRangeCompare, ContainerValueRangeTimeout, NULL,
|
|
false, memcap, CONTAINER_URLRANGE_HASH_SIZE);
|
|
ContainerUrlRangeList.timeout = timeout;
|
|
|
|
SCLogDebug("containers started");
|
|
}
|
|
|
|
void HttpRangeContainersDestroy(void)
|
|
{
|
|
THashShutdown(ContainerUrlRangeList.ht);
|
|
}
|
|
|
|
uint32_t HttpRangeContainersTimeoutHash(const SCTime_t ts)
|
|
{
|
|
return THashExpire(ContainerUrlRangeList.ht, ts);
|
|
}
|
|
|
|
/**
|
|
* \returns locked data
|
|
*/
|
|
static void *HttpRangeContainerUrlGet(const uint8_t *key, uint32_t keylen, const Flow *f)
|
|
{
|
|
const SCTime_t ts = f->lastts;
|
|
HttpRangeContainerFile lookup;
|
|
memset(&lookup, 0, sizeof(lookup));
|
|
// cast so as not to have const in the structure
|
|
lookup.key = (uint8_t *)key;
|
|
lookup.len = keylen;
|
|
struct THashDataGetResult res = THashGetFromHash(ContainerUrlRangeList.ht, &lookup);
|
|
if (res.data) {
|
|
// nothing more to do if (res.is_new)
|
|
ContainerUrlRangeUpdate(res.data->data, SCTIME_ADD_SECS(ts, ContainerUrlRangeList.timeout));
|
|
HttpRangeContainerFile *c = res.data->data;
|
|
c->hdata = res.data;
|
|
SCLogDebug("c %p", c);
|
|
return res.data->data;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static HttpRangeContainerBlock *HttpRangeOpenFileAux(HttpRangeContainerFile *c, uint64_t start,
|
|
uint64_t end, uint64_t total, const StreamingBufferConfig *sbcfg, const uint8_t *name,
|
|
uint16_t name_len, uint16_t flags)
|
|
{
|
|
if (c->files != NULL && c->files->tail == NULL) {
|
|
/* this is the first request, we open a single file in the file container
|
|
* this could be part of ContainerUrlRangeSet if we could have
|
|
* all the arguments there
|
|
*/
|
|
if (FileOpenFileWithId(c->files, sbcfg, 0, name, name_len, NULL, 0, flags) != 0) {
|
|
SCLogDebug("open file for range failed");
|
|
return NULL;
|
|
}
|
|
}
|
|
HttpRangeContainerBlock *curf = SCCalloc(1, sizeof(HttpRangeContainerBlock));
|
|
if (curf == NULL) {
|
|
c->error = true;
|
|
return NULL;
|
|
}
|
|
curf->files = NULL;
|
|
if (total > c->totalsize) {
|
|
// we grow to the maximum size indicated by different range requests
|
|
// we could add some warning/app-layer event in this case where
|
|
// different range requests indicate different total sizes
|
|
c->totalsize = total;
|
|
}
|
|
const uint64_t buflen = end - start + 1;
|
|
|
|
/* The big part of this function is now to decide which kind of HttpRangeContainerBlock
|
|
* we will return :
|
|
* - skipping already processed data
|
|
* - storing out of order data for later use
|
|
* - directly appending to the file if we are at the right offset
|
|
*/
|
|
if (start == c->lastsize && c->files != NULL) {
|
|
// easy case : append to current file
|
|
curf->container = c;
|
|
// If we see 2 duplicate range requests with the same range,
|
|
// the first one takes the ownership of the files container
|
|
// protected by the lock from caller HTPFileOpenWithRange
|
|
curf->files = c->files;
|
|
c->files = NULL;
|
|
return curf;
|
|
} else if (start < c->lastsize && c->lastsize - start >= buflen) {
|
|
// only overlap
|
|
// redundant to be explicit that this block is independent
|
|
curf->toskip = buflen;
|
|
return curf;
|
|
} else if (start < c->lastsize && c->lastsize - start < buflen && c->files != NULL) {
|
|
// some overlap, then some data to append to the file
|
|
curf->toskip = c->lastsize - start;
|
|
curf->files = c->files;
|
|
c->files = NULL;
|
|
curf->container = c;
|
|
return curf;
|
|
}
|
|
// Because we are not in the previous cases, we will store the data for later use
|
|
|
|
// block/range to be inserted in ordered linked list
|
|
if (!(THASH_CHECK_MEMCAP(ContainerUrlRangeList.ht, buflen))) {
|
|
// skips this range
|
|
curf->toskip = buflen;
|
|
return curf;
|
|
}
|
|
curf->container = c;
|
|
|
|
HttpRangeContainerBuffer *range = SCCalloc(1, sizeof(HttpRangeContainerBuffer));
|
|
if (range == NULL) {
|
|
c->error = true;
|
|
SCFree(curf);
|
|
return NULL;
|
|
}
|
|
|
|
(void)SC_ATOMIC_ADD(ContainerUrlRangeList.ht->memuse, buflen);
|
|
range->buffer = SCMalloc(buflen);
|
|
if (range->buffer == NULL) {
|
|
c->error = true;
|
|
SCFree(curf);
|
|
SCFree(range);
|
|
(void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, buflen);
|
|
return NULL;
|
|
}
|
|
range->buflen = buflen;
|
|
range->start = start;
|
|
range->offset = 0;
|
|
range->gap = 0;
|
|
curf->current = range;
|
|
return curf;
|
|
}
|
|
|
|
static HttpRangeContainerBlock *HttpRangeOpenFile(HttpRangeContainerFile *c, uint64_t start,
|
|
uint64_t end, uint64_t total, const StreamingBufferConfig *sbcfg, const uint8_t *name,
|
|
uint16_t name_len, uint16_t flags, const uint8_t *data, uint32_t len)
|
|
{
|
|
HttpRangeContainerBlock *r =
|
|
HttpRangeOpenFileAux(c, start, end, total, sbcfg, name, name_len, flags);
|
|
if (r) {
|
|
if (HttpRangeAppendData(sbcfg, r, data, len) < 0) {
|
|
SCLogDebug("Failed to append data while opening");
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
HttpRangeContainerBlock *HttpRangeContainerOpenFile(const uint8_t *key, uint32_t keylen,
|
|
const Flow *f, const HTTPContentRange *crparsed, const StreamingBufferConfig *sbcfg,
|
|
const uint8_t *name, uint16_t name_len, uint16_t flags, const uint8_t *data,
|
|
uint32_t data_len)
|
|
{
|
|
HttpRangeContainerFile *file_range_container = HttpRangeContainerUrlGet(key, keylen, f);
|
|
if (file_range_container == NULL) {
|
|
// probably reached memcap
|
|
return NULL;
|
|
}
|
|
file_range_container->sbcfg = sbcfg;
|
|
|
|
HttpRangeContainerBlock *r = HttpRangeOpenFile(file_range_container, crparsed->start,
|
|
crparsed->end, crparsed->size, sbcfg, name, name_len, flags, data, data_len);
|
|
SCLogDebug("s->file_range == %p", r);
|
|
if (r == NULL) {
|
|
THashDecrUsecnt(file_range_container->hdata);
|
|
DEBUG_VALIDATE_BUG_ON(
|
|
SC_ATOMIC_GET(file_range_container->hdata->use_cnt) > (uint32_t)INT_MAX);
|
|
THashDataUnlock(file_range_container->hdata);
|
|
|
|
// probably reached memcap
|
|
return NULL;
|
|
/* in some cases we don't take a reference, so decr use cnt */
|
|
} else if (r->container == NULL) {
|
|
THashDecrUsecnt(file_range_container->hdata);
|
|
} else {
|
|
SCLogDebug("container %p use_cnt %u", r, SC_ATOMIC_GET(r->container->hdata->use_cnt));
|
|
DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(r->container->hdata->use_cnt) > (uint32_t)INT_MAX);
|
|
}
|
|
|
|
/* we're done, so unlock. But since we have a reference in s->file_range keep use_cnt. */
|
|
THashDataUnlock(file_range_container->hdata);
|
|
return r;
|
|
}
|
|
|
|
int HttpRangeAppendData(const StreamingBufferConfig *sbcfg, HttpRangeContainerBlock *c,
|
|
const uint8_t *data, uint32_t len)
|
|
{
|
|
if (len == 0) {
|
|
return 0;
|
|
}
|
|
// first check if we need to skip all bytes
|
|
if (c->toskip >= len) {
|
|
c->toskip -= len;
|
|
return 0;
|
|
}
|
|
// then if we need to skip only some bytes
|
|
if (c->toskip > 0) {
|
|
int r = 0;
|
|
if (c->files) {
|
|
if (data == NULL) {
|
|
// gap overlapping already known data
|
|
r = FileAppendData(c->files, sbcfg, NULL, (uint32_t)(len - c->toskip));
|
|
} else {
|
|
r = FileAppendData(c->files, sbcfg, data + c->toskip, (uint32_t)(len - c->toskip));
|
|
}
|
|
}
|
|
c->toskip = 0;
|
|
return r;
|
|
}
|
|
// If we are owning the file to append to it, let's do it
|
|
if (c->files) {
|
|
SCLogDebug("update files (FileAppendData)");
|
|
return FileAppendData(c->files, sbcfg, data, len);
|
|
}
|
|
// Maybe we were in the skipping case,
|
|
// but we get more data than expected and had set c->toskip = 0
|
|
// so we need to check for last case with something to do
|
|
if (c->current) {
|
|
// So we have a current allocated buffer to copy to
|
|
// in the case of an unordered range being handled
|
|
SCLogDebug("update current: adding %u bytes to block %p", len, c);
|
|
// GAP "data"
|
|
if (data == NULL) {
|
|
// just save the length of the gap
|
|
c->current->gap += len;
|
|
// data, but we're not yet complete
|
|
} else if (c->current->offset + len < c->current->buflen) {
|
|
memcpy(c->current->buffer + c->current->offset, data, len);
|
|
c->current->offset += len;
|
|
// data, we're complete
|
|
} else if (c->current->offset + len == c->current->buflen) {
|
|
memcpy(c->current->buffer + c->current->offset, data, len);
|
|
c->current->offset += len;
|
|
// data, more than expected
|
|
} else {
|
|
memcpy(c->current->buffer + c->current->offset, data,
|
|
c->current->buflen - c->current->offset);
|
|
c->current->offset = c->current->buflen;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void HttpRangeFileClose(
|
|
const StreamingBufferConfig *sbcfg, HttpRangeContainerFile *c, uint16_t flags)
|
|
{
|
|
SCLogDebug("closing range %p flags %04x", c, flags);
|
|
DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(c->hdata->use_cnt) == 0);
|
|
// move ownership of file c->files->head to caller
|
|
FileCloseFile(c->files, sbcfg, NULL, 0, c->flags | flags);
|
|
c->files->head = NULL;
|
|
c->files->tail = NULL;
|
|
}
|
|
|
|
/**
|
|
* \note if `f` is non-NULL, the ownership of the file is transferred to the caller.
|
|
*/
|
|
File *HttpRangeClose(const StreamingBufferConfig *sbcfg, HttpRangeContainerBlock *c, uint16_t flags)
|
|
{
|
|
SCLogDebug("c %p c->container %p c->current %p", c, c->container, c->current);
|
|
|
|
DEBUG_VALIDATE_BUG_ON(c->container == NULL);
|
|
|
|
/* we're processing an OOO chunk, won't be able to get us a full file just yet */
|
|
if (c->current) {
|
|
SCLogDebug("processing ooo chunk as c->current is set %p", c->current);
|
|
// some out-or-order range is finished
|
|
if (c->container->lastsize >= c->current->start + c->current->offset) {
|
|
// if the range has become obsolete because we received the data already
|
|
// we just free it
|
|
(void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, c->current->buflen);
|
|
SCFree(c->current->buffer);
|
|
SCFree(c->current);
|
|
c->current = NULL;
|
|
SCLogDebug("c->current was obsolete");
|
|
return NULL;
|
|
} else {
|
|
/* otherwise insert in red and black tree. If res != NULL, the insert
|
|
failed because its a dup. */
|
|
HttpRangeContainerBuffer *res =
|
|
HTTP_RANGES_RB_INSERT(&c->container->fragment_tree, c->current);
|
|
if (res) {
|
|
SCLogDebug("duplicate range fragment");
|
|
(void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, c->current->buflen);
|
|
SCFree(c->current->buffer);
|
|
SCFree(c->current);
|
|
c->current = NULL;
|
|
return NULL;
|
|
}
|
|
SCLogDebug("inserted range fragment");
|
|
c->current = NULL;
|
|
if (c->container->files == NULL) {
|
|
// we have to wait for the flow owning the file
|
|
return NULL;
|
|
}
|
|
if (c->container->files->tail == NULL) {
|
|
// file has already been closed meanwhile
|
|
return NULL;
|
|
}
|
|
// keep on going, maybe this out of order chunk
|
|
// became the missing part between open and close
|
|
}
|
|
SCLogDebug("c->current was set, file incomplete so return NULL");
|
|
} else if (c->toskip > 0) {
|
|
// was only an overlapping range, truncated before new bytes
|
|
SCLogDebug("c->toskip %" PRIu64, c->toskip);
|
|
if (c->files) {
|
|
// if we expected new bytes after overlap
|
|
c->container->files = c->files;
|
|
c->files = NULL;
|
|
}
|
|
return NULL;
|
|
} else {
|
|
// we just finished an in-order block
|
|
DEBUG_VALIDATE_BUG_ON(c->files == NULL);
|
|
// move back the ownership of the file container to HttpRangeContainerFile
|
|
c->container->files = c->files;
|
|
c->files = NULL;
|
|
DEBUG_VALIDATE_BUG_ON(c->container->files->tail == NULL);
|
|
}
|
|
|
|
File *f = c->container->files->tail;
|
|
|
|
/* See if we can use our stored fragments to (partly) reconstruct the file */
|
|
HttpRangeContainerBuffer *range, *safe = NULL;
|
|
RB_FOREACH_SAFE (range, HTTP_RANGES, &c->container->fragment_tree, safe) {
|
|
if (f->size < range->start) {
|
|
// this next range is not reached yet
|
|
break;
|
|
}
|
|
if (f->size == range->start) {
|
|
// a new range just begins where we ended, append it
|
|
if (range->gap > 0) {
|
|
// if the range had a gap, begin by it
|
|
uint32_t gap = range->gap <= UINT32_MAX ? (uint32_t)range->gap : UINT32_MAX;
|
|
if (FileAppendData(c->container->files, sbcfg, NULL, gap) != 0) {
|
|
c->container->lastsize = f->size;
|
|
HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED);
|
|
c->container->error = true;
|
|
return f;
|
|
}
|
|
}
|
|
if (range->offset > UINT32_MAX) {
|
|
c->container->lastsize = f->size;
|
|
HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED);
|
|
c->container->error = true;
|
|
return f;
|
|
} else if (FileAppendData(c->container->files, sbcfg, range->buffer,
|
|
(uint32_t)range->offset) != 0) {
|
|
c->container->lastsize = f->size;
|
|
HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED);
|
|
c->container->error = true;
|
|
return f;
|
|
}
|
|
} else {
|
|
// the range starts before where we ended
|
|
uint64_t overlap = f->size - range->start;
|
|
if (overlap < range->offset) {
|
|
if (range->gap > 0) {
|
|
// if the range had a gap, begin by it
|
|
uint32_t gap = range->gap <= UINT32_MAX ? (uint32_t)range->gap : UINT32_MAX;
|
|
if (FileAppendData(c->container->files, sbcfg, NULL, gap) != 0) {
|
|
c->container->lastsize = f->size;
|
|
HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED);
|
|
c->container->error = true;
|
|
return f;
|
|
}
|
|
}
|
|
// And the range ends beyond where we ended
|
|
// in this case of overlap, only add the extra data
|
|
if (range->offset - overlap > UINT32_MAX) {
|
|
c->container->lastsize = f->size;
|
|
HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED);
|
|
c->container->error = true;
|
|
return f;
|
|
} else if (FileAppendData(c->container->files, sbcfg, range->buffer + overlap,
|
|
(uint32_t)(range->offset - overlap)) != 0) {
|
|
c->container->lastsize = f->size;
|
|
HttpRangeFileClose(sbcfg, c->container, flags | FILE_TRUNCATED);
|
|
c->container->error = true;
|
|
return f;
|
|
}
|
|
}
|
|
}
|
|
/* Remove this range from the tree */
|
|
HTTP_RANGES_RB_REMOVE(&c->container->fragment_tree, range);
|
|
(void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, range->buflen);
|
|
SCFree(range->buffer);
|
|
SCFree(range);
|
|
}
|
|
// wait until we merged all the buffers to update known size
|
|
c->container->lastsize = f->size;
|
|
|
|
if (f->size >= c->container->totalsize) {
|
|
// we finished the whole file
|
|
HttpRangeFileClose(sbcfg, c->container, flags);
|
|
} else {
|
|
// we are expecting more ranges
|
|
f = NULL;
|
|
SCLogDebug("expecting more use_cnt %u", SC_ATOMIC_GET(c->container->hdata->use_cnt));
|
|
}
|
|
SCLogDebug("returning f %p (c:%p container:%p)", f, c, c->container);
|
|
return f;
|
|
}
|
|
|
|
static void HttpRangeBlockDerefContainer(HttpRangeContainerBlock *b)
|
|
{
|
|
if (b && b->container) {
|
|
DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(b->container->hdata->use_cnt) == 0);
|
|
THashDecrUsecnt(b->container->hdata);
|
|
b->container = NULL;
|
|
}
|
|
}
|
|
|
|
void SCHttpRangeFreeBlock(HttpRangeContainerBlock *b)
|
|
{
|
|
if (b) {
|
|
DEBUG_VALIDATE_BUG_ON(b->container == NULL && b->files != NULL);
|
|
const StreamingBufferConfig *sbcfg = b->container ? b->container->sbcfg : NULL;
|
|
|
|
HttpRangeBlockDerefContainer(b);
|
|
|
|
if (b->current) {
|
|
(void)SC_ATOMIC_SUB(ContainerUrlRangeList.ht->memuse, b->current->buflen);
|
|
SCFree(b->current->buffer);
|
|
SCFree(b->current);
|
|
}
|
|
// we did not move ownership of the file container back to HttpRangeContainerFile
|
|
DEBUG_VALIDATE_BUG_ON(b->files != NULL);
|
|
if (b->files != NULL) {
|
|
FileContainerFree(b->files, sbcfg);
|
|
b->files = NULL;
|
|
}
|
|
SCFree(b);
|
|
}
|
|
}
|