Coverage Report

Created: 2026-01-25 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libass/libass/ass_utils.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
3
 *
4
 * This file is part of libass.
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include "config.h"
20
#include "ass_compat.h"
21
22
#include <stddef.h>
23
#include <stdlib.h>
24
#include <stdio.h>
25
#include <stdint.h>
26
#include <inttypes.h>
27
28
#include "ass_library.h"
29
#include "ass.h"
30
#include "ass_utils.h"
31
#include "ass_string.h"
32
33
// Fallbacks
34
#ifndef HAVE_STRDUP
35
char *ass_strdup_fallback(const char *str)
36
{
37
    size_t len    = strlen(str) + 1;
38
    char *new_str = malloc(len);
39
    if (new_str)
40
        memcpy(new_str, str, len);
41
    return new_str;
42
}
43
#endif
44
45
#ifndef HAVE_STRNDUP
46
char *ass_strndup_fallback(const char *s, size_t n)
47
{
48
    char *end = memchr(s, 0, n);
49
    size_t len = end ? end - s : n;
50
    char *new = len < SIZE_MAX ? malloc(len + 1) : NULL;
51
    if (new) {
52
        memcpy(new, s, len);
53
        new[len] = 0;
54
    }
55
    return new;
56
}
57
#endif
58
59
void *ass_aligned_alloc(size_t alignment, size_t size, bool zero)
60
36.3k
{
61
36.3k
    assert(!(alignment & (alignment - 1))); // alignment must be power of 2
62
36.3k
    if (size >= SIZE_MAX - alignment - sizeof(void *))
63
0
        return NULL;
64
36.3k
    char *allocation = zero ? calloc(1, size + sizeof(void *) + alignment - 1)
65
36.3k
                            : malloc(size + sizeof(void *) + alignment - 1);
66
36.3k
    if (!allocation)
67
0
        return NULL;
68
36.3k
    char *ptr = allocation + sizeof(void *);
69
36.3k
    unsigned int misalign = (uintptr_t)ptr & (alignment - 1);
70
36.3k
    if (misalign)
71
36.3k
        ptr += alignment - misalign;
72
36.3k
    *((void **)ptr - 1) = allocation;
73
36.3k
    return ptr;
74
36.3k
}
75
76
void ass_aligned_free(void *ptr)
77
264k
{
78
264k
    if (ptr)
79
36.3k
        free(*((void **)ptr - 1));
80
264k
}
81
82
/**
83
 * This works similar to realloc(ptr, nmemb * size), but checks for overflow.
84
 *
85
 * Unlike some implementations of realloc, this never acts as a call to free().
86
 * If the total size is 0, it is bumped up to 1. This means a NULL return always
87
 * means allocation failure, and the unportable realloc(0, 0) case is avoided.
88
 */
89
void *ass_realloc_array(void *ptr, size_t nmemb, size_t size)
90
65.5k
{
91
65.5k
    if (nmemb > (SIZE_MAX / size))
92
0
        return NULL;
93
65.5k
    size *= nmemb;
94
65.5k
    if (size < 1)
95
0
        size = 1;
96
97
65.5k
    return realloc(ptr, size);
98
65.5k
}
99
100
/**
101
 * Like ass_realloc_array(), but:
102
 * 1. on failure, return the original ptr value, instead of NULL
103
 * 2. set errno to indicate failure (errno!=0) or success (errno==0)
104
 */
105
void *ass_try_realloc_array(void *ptr, size_t nmemb, size_t size)
106
45.1k
{
107
45.1k
    void *new_ptr = ass_realloc_array(ptr, nmemb, size);
108
45.1k
    if (new_ptr) {
109
45.1k
        errno = 0;
110
45.1k
        return new_ptr;
111
45.1k
    } else {
112
0
        errno = ENOMEM;
113
0
        return ptr;
114
0
    }
115
45.1k
}
116
117
void ass_msg(ASS_Library *priv, int lvl, const char *fmt, ...)
118
341k
{
119
341k
    va_list va;
120
341k
    va_start(va, fmt);
121
341k
    priv->msg_callback(lvl, fmt, va, priv->msg_callback_data);
122
341k
    va_end(va);
123
341k
}
124
125
unsigned ass_utf8_get_char(char **str)
126
557k
{
127
557k
    uint8_t *strp = (uint8_t *) * str;
128
557k
    unsigned c = *strp++;
129
557k
    unsigned mask = 0x80;
130
557k
    int len = -1;
131
803k
    while (c & mask) {
132
246k
        mask >>= 1;
133
246k
        len++;
134
246k
    }
135
557k
    if (len <= 0 || len > 4)
136
535k
        goto no_utf8;
137
22.0k
    c &= mask - 1;
138
31.7k
    while ((*strp & 0xc0) == 0x80) {
139
10.9k
        if (len-- <= 0)
140
1.26k
            goto no_utf8;
141
9.72k
        c = (c << 6) | (*strp++ & 0x3f);
142
9.72k
    }
143
20.7k
    if (len)
144
18.9k
        goto no_utf8;
145
1.78k
    *str = (char *) strp;
146
1.78k
    return c;
147
148
555k
  no_utf8:
149
555k
    strp = (uint8_t *) * str;
150
555k
    c = *strp++;
151
555k
    *str = (char *) strp;
152
555k
    return c;
153
20.7k
}
154
155
/**
156
 * Original version from http://www.cprogramming.com/tutorial/utf8.c
157
 * \brief Converts a single UTF-32 code point to UTF-8
158
 * \param dest Buffer to write to. Writes a NULL terminator.
159
 * \param ch 32-bit character code to convert
160
 * \return number of bytes written
161
 * converts a single character and ASSUMES YOU HAVE ENOUGH SPACE
162
 */
163
unsigned ass_utf8_put_char(char *dest, uint32_t ch)
164
0
{
165
0
    char *orig_dest = dest;
166
167
0
    if (ch < 0x80) {
168
0
        *dest++ = (char)ch;
169
0
    } else if (ch < 0x800) {
170
0
        *dest++ = (ch >> 6) | 0xC0;
171
0
        *dest++ = (ch & 0x3F) | 0x80;
172
0
    } else if (ch < 0x10000) {
173
0
        *dest++ = (ch >> 12) | 0xE0;
174
0
        *dest++ = ((ch >> 6) & 0x3F) | 0x80;
175
0
        *dest++ = (ch & 0x3F) | 0x80;
176
0
    } else if (ch < 0x110000) {
177
0
        *dest++ = (ch >> 18) | 0xF0;
178
0
        *dest++ = ((ch >> 12) & 0x3F) | 0x80;
179
0
        *dest++ = ((ch >> 6) & 0x3F) | 0x80;
180
0
        *dest++ = (ch & 0x3F) | 0x80;
181
0
    }
182
183
0
    *dest = '\0';
184
0
    return dest - orig_dest;
185
0
}
186
187
/**
188
 * \brief Parse UTF-16 and return the code point of the sequence starting at src.
189
 * \param src pointer to a pointer to the start of the UTF-16 data
190
 *            (will be set to the start of the next code point)
191
 * \return the code point
192
 */
193
static uint32_t ass_read_utf16be(uint8_t **src, size_t bytes)
194
0
{
195
0
    if (bytes < 2)
196
0
        goto too_short;
197
198
0
    uint32_t cp = ((*src)[0] << 8) | (*src)[1];
199
0
    *src += 2;
200
0
    bytes -= 2;
201
202
0
    if (cp >= 0xD800 && cp <= 0xDBFF) {
203
0
        if (bytes < 2)
204
0
            goto too_short;
205
206
0
        uint32_t cp2 = ((*src)[0] << 8) | (*src)[1];
207
208
0
        if (cp2 < 0xDC00 || cp2 > 0xDFFF)
209
0
            return 0xFFFD;
210
211
0
        *src += 2;
212
213
0
        cp = 0x10000 + ((cp - 0xD800) << 10) + (cp2 - 0xDC00);
214
0
    }
215
216
0
    if (cp >= 0xDC00 && cp <= 0xDFFF)
217
0
        return 0xFFFD;
218
219
0
    return cp;
220
221
0
too_short:
222
0
    *src += bytes;
223
0
    return 0xFFFD;
224
0
}
225
226
void ass_utf16be_to_utf8(char *dst, size_t dst_size, uint8_t *src, size_t src_size)
227
0
{
228
0
    uint8_t *end = src + src_size;
229
230
0
    if (!dst_size)
231
0
        return;
232
233
0
    while (src < end) {
234
0
        uint32_t cp = ass_read_utf16be(&src, end - src);
235
0
        if (dst_size < 5)
236
0
            break;
237
0
        unsigned s = ass_utf8_put_char(dst, cp);
238
0
        dst += s;
239
0
        dst_size -= s;
240
0
    }
241
242
0
    *dst = '\0';
243
0
}
244
245
/**
246
 * \brief find style by name the common way (\r matches differently)
247
 * \param track track
248
 * \param name style name
249
 * \return index in track->styles
250
 * Returns 0 if no styles found => expects at least 1 style.
251
 * Parsing code always adds "Default" style in the beginning.
252
 */
253
int ass_lookup_style(ASS_Track *track, char *name)
254
17.6k
{
255
17.6k
    int i;
256
    // '*' seem to mean literally nothing;
257
    // VSFilter removes them as soon as it can
258
17.7k
    while (*name == '*')
259
195
        ++name;
260
    // VSFilter then normalizes the case of "Default"
261
    // (only in contexts where this function is called)
262
17.6k
    if (ass_strcasecmp(name, "Default") == 0)
263
194
        name = "Default";
264
39.0k
    for (i = track->n_styles - 1; i >= 0; --i) {
265
21.6k
        if (strcmp(track->styles[i].Name, name) == 0)
266
194
            return i;
267
21.6k
    }
268
17.4k
    i = track->default_style;
269
17.4k
    ass_msg(track->library, MSGL_WARN,
270
17.4k
            "[%p]: Warning: no style named '%s' found, using '%s'",
271
17.4k
            track, name, track->styles[i].Name);
272
17.4k
    return i;
273
17.6k
}