Coverage Report

Created: 2026-01-25 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavutil/bprint.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2012 Nicolas George
3
 *
4
 * This file is part of FFmpeg.
5
 *
6
 * FFmpeg is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * FFmpeg is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with FFmpeg; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
 */
20
21
#include <limits.h>
22
#include <stdarg.h>
23
#include <stdio.h>
24
#include <string.h>
25
#include <time.h>
26
#include "avstring.h"
27
#include "bprint.h"
28
#include "compat/va_copy.h"
29
#include "error.h"
30
#include "macros.h"
31
#include "mem.h"
32
33
0
#define av_bprint_room(buf) ((buf)->size - FFMIN((buf)->len, (buf)->size))
34
0
#define av_bprint_is_allocated(buf) ((buf)->str != (buf)->reserved_internal_buffer)
35
36
static int av_bprint_alloc(AVBPrint *buf, unsigned room)
37
0
{
38
0
    char *old_str, *new_str;
39
0
    unsigned min_size, new_size;
40
41
0
    if (buf->size == buf->size_max)
42
0
        return AVERROR(EIO);
43
0
    if (!av_bprint_is_complete(buf))
44
0
        return AVERROR_INVALIDDATA; /* it is already truncated anyway */
45
0
    min_size = buf->len + 1 + FFMIN(UINT_MAX - buf->len - 1, room);
46
0
    new_size = buf->size > buf->size_max / 2 ? buf->size_max : buf->size * 2;
47
0
    if (new_size < min_size)
48
0
        new_size = FFMIN(buf->size_max, min_size);
49
0
    old_str = av_bprint_is_allocated(buf) ? buf->str : NULL;
50
0
    new_str = av_realloc(old_str, new_size);
51
0
    if (!new_str)
52
0
        return AVERROR(ENOMEM);
53
0
    if (!old_str)
54
0
        memcpy(new_str, buf->str, buf->len + 1);
55
0
    buf->str  = new_str;
56
0
    buf->size = new_size;
57
0
    return 0;
58
0
}
59
60
static void av_bprint_grow(AVBPrint *buf, unsigned extra_len)
61
0
{
62
    /* arbitrary margin to avoid small overflows */
63
0
    extra_len = FFMIN(extra_len, UINT_MAX - 5 - buf->len);
64
0
    buf->len += extra_len;
65
0
    if (buf->size)
66
0
        buf->str[FFMIN(buf->len, buf->size - 1)] = 0;
67
0
}
68
69
void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max)
70
0
{
71
0
    unsigned size_auto = (char *)buf + sizeof(*buf) -
72
0
                         buf->reserved_internal_buffer;
73
74
0
    if (size_max == AV_BPRINT_SIZE_AUTOMATIC)
75
0
        size_max = size_auto;
76
0
    buf->str      = buf->reserved_internal_buffer;
77
0
    buf->len      = 0;
78
0
    buf->size     = FFMIN(size_auto, size_max);
79
0
    buf->size_max = size_max;
80
0
    *buf->str = 0;
81
0
    if (size_init > buf->size)
82
0
        av_bprint_alloc(buf, size_init - 1);
83
0
}
84
85
void av_bprint_init_for_buffer(AVBPrint *buf, char *buffer, unsigned size)
86
0
{
87
0
    if (size == 0) {
88
0
        av_bprint_init(buf, 0, AV_BPRINT_SIZE_COUNT_ONLY);
89
0
        return;
90
0
    }
91
92
0
    buf->str      = buffer;
93
0
    buf->len      = 0;
94
0
    buf->size     = size;
95
0
    buf->size_max = size;
96
0
    *buf->str = 0;
97
0
}
98
99
void av_vbprintf(AVBPrint *buf, const char *fmt, va_list vl_arg)
100
0
{
101
0
    unsigned room;
102
0
    char *dst;
103
0
    int extra_len;
104
0
    va_list vl;
105
106
0
    while (1) {
107
0
        room = av_bprint_room(buf);
108
0
        dst = room ? buf->str + buf->len : NULL;
109
0
        va_copy(vl, vl_arg);
110
0
        extra_len = vsnprintf(dst, room, fmt, vl);
111
0
        va_end(vl);
112
0
        if (extra_len <= 0)
113
0
            return;
114
0
        if (extra_len < room)
115
0
            break;
116
0
        if (av_bprint_alloc(buf, extra_len))
117
0
            break;
118
0
    }
119
0
    av_bprint_grow(buf, extra_len);
120
0
}
121
122
void av_bprintf(AVBPrint *buf, const char *fmt, ...)
123
0
{
124
0
    va_list vl;
125
0
    va_start(vl, fmt);
126
0
    av_vbprintf(buf, fmt, vl);
127
0
    va_end(vl);
128
0
}
129
130
void av_bprint_chars(AVBPrint *buf, char c, unsigned n)
131
0
{
132
0
    unsigned room, real_n;
133
134
0
    while (1) {
135
0
        room = av_bprint_room(buf);
136
0
        if (n < room)
137
0
            break;
138
0
        if (av_bprint_alloc(buf, n))
139
0
            break;
140
0
    }
141
0
    if (room) {
142
0
        real_n = FFMIN(n, room - 1);
143
0
        memset(buf->str + buf->len, c, real_n);
144
0
    }
145
0
    av_bprint_grow(buf, n);
146
0
}
147
148
void av_bprint_append_data(AVBPrint *buf, const char *data, unsigned size)
149
0
{
150
0
    unsigned room, real_n;
151
152
0
    while (1) {
153
0
        room = av_bprint_room(buf);
154
0
        if (size < room)
155
0
            break;
156
0
        if (av_bprint_alloc(buf, size))
157
0
            break;
158
0
    }
159
0
    if (room) {
160
0
        real_n = FFMIN(size, room - 1);
161
0
        memcpy(buf->str + buf->len, data, real_n);
162
0
    }
163
0
    av_bprint_grow(buf, size);
164
0
}
165
166
void av_bprint_strftime(AVBPrint *buf, const char *fmt, const struct tm *tm)
167
0
{
168
0
    unsigned room;
169
0
    size_t l;
170
0
    size_t fmt_len = strlen(fmt);
171
172
0
    if (!*fmt)
173
0
        return;
174
0
    while (1) {
175
0
        room = av_bprint_room(buf);
176
0
        if (room && (l = strftime(buf->str + buf->len, room, fmt, tm)))
177
0
            break;
178
179
        /* Due to the limitations of strftime() it is not possible to know if
180
         * the output buffer is too small or the output is empty.
181
         * However, a 256x output space requirement compared to the format
182
         * string length is so unlikely we can safely assume empty output. This
183
         * allows supporting possibly empty format strings like "%p". */
184
0
        if (room >> 8 > fmt_len)
185
0
            break;
186
187
        /* strftime does not tell us how much room it would need: let us
188
           retry with twice as much until the buffer is large enough */
189
0
        room = !room ? fmt_len + 1 :
190
0
               room <= INT_MAX / 2 ? room * 2 : INT_MAX;
191
0
        if (av_bprint_alloc(buf, room)) {
192
            /* impossible to grow, try to manage something useful anyway */
193
0
            room = av_bprint_room(buf);
194
0
            if (room < 1024) {
195
                /* if strftime fails because the buffer has (almost) reached
196
                   its maximum size, let us try in a local buffer; 1k should
197
                   be enough to format any real date+time string */
198
0
                char buf2[1024];
199
0
                if ((l = strftime(buf2, sizeof(buf2), fmt, tm))) {
200
0
                    av_bprintf(buf, "%s", buf2);
201
0
                    return;
202
0
                }
203
0
            }
204
0
            if (room) {
205
                /* if anything else failed and the buffer is not already
206
                   truncated, let us add a stock string and force truncation */
207
0
                static const char txt[] = "[truncated strftime output]";
208
0
                memset(buf->str + buf->len, '!', room);
209
0
                memcpy(buf->str + buf->len, txt, FFMIN(sizeof(txt) - 1, room));
210
0
                av_bprint_grow(buf, room); /* force truncation */
211
0
            }
212
0
            return;
213
0
        }
214
0
    }
215
0
    av_bprint_grow(buf, l);
216
0
}
217
218
void av_bprint_get_buffer(AVBPrint *buf, unsigned size,
219
                          unsigned char **mem, unsigned *actual_size)
220
0
{
221
0
    if (size > av_bprint_room(buf))
222
0
        av_bprint_alloc(buf, size);
223
0
    *actual_size = av_bprint_room(buf);
224
0
    *mem = *actual_size ? buf->str + buf->len : NULL;
225
0
}
226
227
void av_bprint_clear(AVBPrint *buf)
228
0
{
229
0
    if (buf->len) {
230
0
        *buf->str = 0;
231
0
        buf->len  = 0;
232
0
    }
233
0
}
234
235
int av_bprint_finalize(AVBPrint *buf, char **ret_str)
236
0
{
237
0
    unsigned real_size = FFMIN(buf->len + 1, buf->size);
238
0
    char *str;
239
0
    int ret = 0;
240
241
0
    if (ret_str) {
242
0
        if (av_bprint_is_allocated(buf)) {
243
0
            str = av_realloc(buf->str, real_size);
244
0
            if (!str)
245
0
                str = buf->str;
246
0
            buf->str = NULL;
247
0
        } else {
248
0
            str = av_memdup(buf->str, real_size);
249
0
            if (!str)
250
0
                ret = AVERROR(ENOMEM);
251
0
        }
252
0
        *ret_str = str;
253
0
    } else {
254
0
        if (av_bprint_is_allocated(buf))
255
0
            av_freep(&buf->str);
256
0
    }
257
0
    buf->size = real_size;
258
0
    return ret;
259
0
}
260
261
0
#define WHITESPACES " \n\t\r"
262
263
void av_bprint_escape(AVBPrint *dstbuf, const char *src, const char *special_chars,
264
                      enum AVEscapeMode mode, int flags)
265
0
{
266
0
    const char *src0 = src;
267
268
0
    if (mode == AV_ESCAPE_MODE_AUTO)
269
0
        mode = AV_ESCAPE_MODE_BACKSLASH; /* TODO: implement a heuristic */
270
271
0
    switch (mode) {
272
0
    case AV_ESCAPE_MODE_QUOTE:
273
        /* enclose the string between '' */
274
0
        av_bprint_chars(dstbuf, '\'', 1);
275
0
        for (; *src; src++) {
276
0
            if (*src == '\'')
277
0
                av_bprintf(dstbuf, "'\\''");
278
0
            else
279
0
                av_bprint_chars(dstbuf, *src, 1);
280
0
        }
281
0
        av_bprint_chars(dstbuf, '\'', 1);
282
0
        break;
283
284
0
    case AV_ESCAPE_MODE_XML:
285
        /* escape XML non-markup character data as per 2.4 by default: */
286
        /*  [^<&]* - ([^<&]* ']]>' [^<&]*) */
287
288
        /* additionally, given one of the AV_ESCAPE_FLAG_XML_* flags, */
289
        /* escape those specific characters as required. */
290
0
        for (; *src; src++) {
291
0
            switch (*src) {
292
0
            case '&' : av_bprintf(dstbuf, "%s", "&amp;");  break;
293
0
            case '<' : av_bprintf(dstbuf, "%s", "&lt;");   break;
294
0
            case '>' : av_bprintf(dstbuf, "%s", "&gt;");   break;
295
0
            case '\'':
296
0
                if (!(flags & AV_ESCAPE_FLAG_XML_SINGLE_QUOTES))
297
0
                    goto XML_DEFAULT_HANDLING;
298
299
0
                av_bprintf(dstbuf, "%s", "&apos;");
300
0
                break;
301
0
            case '"' :
302
0
                if (!(flags & AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES))
303
0
                    goto XML_DEFAULT_HANDLING;
304
305
0
                av_bprintf(dstbuf, "%s", "&quot;");
306
0
                break;
307
0
XML_DEFAULT_HANDLING:
308
0
            default: av_bprint_chars(dstbuf, *src, 1);
309
0
            }
310
0
        }
311
0
        break;
312
313
    /* case AV_ESCAPE_MODE_BACKSLASH or unknown mode */
314
0
    default:
315
        /* \-escape characters */
316
0
        for (; *src; src++) {
317
0
            int is_first_last       = src == src0 || !*(src+1);
318
0
            int is_ws               = !!strchr(WHITESPACES, *src);
319
0
            int is_strictly_special = special_chars && strchr(special_chars, *src);
320
0
            int is_special          =
321
0
                is_strictly_special || strchr("'\\", *src) ||
322
0
                (is_ws && (flags & AV_ESCAPE_FLAG_WHITESPACE));
323
324
0
            if (is_strictly_special ||
325
0
                (!(flags & AV_ESCAPE_FLAG_STRICT) &&
326
0
                 (is_special || (is_ws && is_first_last))))
327
0
                av_bprint_chars(dstbuf, '\\', 1);
328
0
            av_bprint_chars(dstbuf, *src, 1);
329
0
        }
330
0
        break;
331
0
    }
332
0
}