• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saitoha / libsixel / 22256663556

21 Feb 2026 12:16PM UTC coverage: 82.856% (-0.02%) from 82.877%
22256663556

push

github

saitoha
fix wic loader signature and start-frame override args

25673 of 50202 branches covered (51.14%)

45851 of 55338 relevant lines covered (82.86%)

4080301.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.37
/src/loader.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2021-2025 libsixel developers. See `AUTHORS`.
5
 * Copyright (c) 2014-2019 Hayaki Saito
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25

26
#if defined(HAVE_CONFIG_H)
27
#include "config.h"
28
#endif
29
#if !defined(_POSIX_C_SOURCE)
30
# define _POSIX_C_SOURCE 200809L
31
#endif
32

33
/* STDC_HEADERS */
34
#include <stdio.h>
35
#include <stdlib.h>
36

37
#if HAVE_STRING_H
38
# include <string.h>
39
#endif
40
#if HAVE_CTYPE_H
41
# include <ctype.h>
42
#endif
43
#if HAVE_STDARG_H
44
# include <stdarg.h>
45
#endif
46
#if HAVE_SYS_TYPES_H
47
# include <sys/types.h>
48
#endif
49
#if HAVE_UNISTD_H
50
# include <unistd.h>
51
#endif
52
#if !defined(PATH_MAX)
53
#define PATH_MAX 4096
54
#endif
55
#if HAVE_FCNTL_H
56
# include <fcntl.h>
57
#endif
58
#if HAVE_SYS_TIME_H
59
# include <sys/time.h>
60
#elif HAVE_TIME_H
61
# include <time.h>
62
#endif  /* HAVE_SYS_TIME_H HAVE_TIME_H */
63
#if defined(_WIN32)
64
# if !defined(UNICODE)
65
#  define UNICODE
66
# endif
67
# if !defined(_UNICODE)
68
#  define _UNICODE
69
# endif
70
# if !defined(WIN32_LEAN_AND_MEAN)
71
#  define WIN32_LEAN_AND_MEAN
72
# endif
73
# include <windows.h>
74
#endif
75
#if HAVE_ERRNO_H
76
# include <errno.h>
77
#endif
78
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
79
# include <CoreServices/CoreServices.h>
80
#endif
81
#if HAVE_SYS_WAIT_H
82
# include <sys/wait.h>
83
#endif
84
#if HAVE_SPAWN_H
85
# include <spawn.h>
86
#endif
87
#if HAVE_LIMITS_H
88
# include <limits.h>
89
#endif
90
#if HAVE_DIRENT_H
91
# include <dirent.h>
92
#endif
93

94
#include <sixel.h>
95
#include "loader.h"
96
#include "loader-builtin.h"
97
#include "loader-common.h"
98
#include "loader-coregraphics.h"
99
#include "loader-gd.h"
100
#include "loader-gdk-pixbuf2.h"
101
#include "loader-gnome-thumbnailer.h"
102
#include "loader-libjpeg.h"
103
#include "loader-libpng.h"
104
#include "loader-librsvg.h"
105
#include "loader-quicklook.h"
106
#include "loader-registry.h"
107
#include "loader-wic.h"
108
#include "compat_stub.h"
109
#include "frame.h"
110
#include "chunk.h"
111
#include "allocator.h"
112
#include "encoder.h"
113
#include "logger.h"
114
#include "options.h"
115
#include "sixel_atomic.h"
116

117
/*
118
 * Internal loader state carried across backends.  The fields mirror the
119
 * original `loader.c` layout to keep statistics, logging, and allocator
120
 * ownership centralized while implementations move into per-backend files.
121
 */
122
struct sixel_loader {
123
    sixel_atomic_u32_t ref;
124
    int fstatic;
125
    int fuse_palette;
126
    int reqcolors;
127
    unsigned char bgcolor[3];
128
    int has_bgcolor;
129
    int loop_control;
130
    int finsecure;
131
    int has_start_frame_no;
132
    int start_frame_no;
133
    int const *cancel_flag;
134
    void *context;
135
    sixel_logger_t logger;
136
    char *loader_order;
137
    sixel_allocator_t *allocator;
138
    char last_loader_name[64];
139
    char last_source_path[PATH_MAX];
140
    size_t last_input_bytes;
141
    int callback_failed;
142
    int log_loader_finished;
143
    char log_path[PATH_MAX];
144
    char log_loader_name[64];
145
    size_t log_input_bytes;
146
};
147

148
typedef struct sixel_loader_callback_state {
149
    sixel_loader_t *loader;
150
    sixel_load_image_function fn;
151
    void *context;
152
} sixel_loader_callback_state_t;
153

154
int
155
sixel_loader_callback_is_canceled(void *data)
34,703✔
156
{
157
    sixel_loader_callback_state_t *state;
20,496✔
158

159
    state = (sixel_loader_callback_state_t *)data;
34,703✔
160
    if (state == NULL || state->loader == NULL ||
34,703!
161
        state->loader->cancel_flag == NULL) {
34,608✔
162
        return 0;
9,008✔
163
    }
164

165
    return *state->loader->cancel_flag != 0;
20,531✔
166
}
17,212✔
167

168

169
#if HAVE_POSIX_SPAWNP
170
extern char **environ;
171
#endif
172

173
static char *
174
loader_strdup(char const *text, sixel_allocator_t *allocator)
1,289✔
175
{
176
    char *copy;
770✔
177
    size_t length;
770✔
178

179
    if (text == NULL) {
1,289!
180
        return NULL;
181
    }
182

183
    length = strlen(text) + 1;
1,289✔
184
    copy = (char *)sixel_allocator_malloc(allocator, length);
1,289✔
185
    if (copy == NULL) {
1,289!
186
        return NULL;
187
    }
188

189
    /* Copy the terminating NUL byte as part of length. */
190
    memcpy(copy, text, length);
1,289✔
191

192
    return copy;
1,289✔
193
}
788✔
194

195

196

197
/*
198
 * Emit loader stage markers.
199
 *
200
 * Loader callbacks run the downstream pipeline synchronously, so the finish
201
 * marker must be issued before invoking fn_load() to avoid inflating the
202
 * loader span. The helper keeps the formatting consistent with
203
 * sixel_encoder_log_stage() without depending on encoder internals.
204
 */
205
static void
206
loader_log_stage(sixel_loader_t *loader,
26,937✔
207
                 char const *event,
208
                 char const *fmt,
209
                 ...)
210
{
211
    sixel_logger_t *logger;
15,849✔
212
    char message[256];
15,849✔
213
    va_list args;
15,849✔
214

215
    logger = NULL;
26,937✔
216
    if (loader != NULL) {
26,937!
217
        logger = &loader->logger;
26,937✔
218
    }
13,193✔
219
    if (logger == NULL || logger->file == NULL || !logger->active) {
26,937!
220
        return;
26,937✔
221
    }
222

223
    message[0] = '\0';
×
224
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
225
# if defined(__clang__)
226
#  pragma clang diagnostic push
227
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
228
# elif defined(__GNUC__) && !defined(__PCC__)
229
#  pragma GCC diagnostic push
230
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
231
# endif
232
#endif
233
    va_start(args, fmt);
×
234
    if (fmt != NULL) {
×
235
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
236
# if defined(__clang__)
237
#  pragma clang diagnostic push
238
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
239
# elif defined(__GNUC__) && !defined(__PCC__)
240
#  pragma GCC diagnostic push
241
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
242
# endif
243
#endif
244
        (void)sixel_compat_vsnprintf(message, sizeof(message), fmt, args);
×
245
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
246
# if defined(__clang__)
247
#  pragma clang diagnostic pop
248
# elif defined(__GNUC__) && !defined(__PCC__)
249
#  pragma GCC diagnostic pop
250
# endif
251
#endif
252
    }
253
    va_end(args);
×
254
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
255
# if defined(__clang__)
256
#  pragma clang diagnostic pop
257
# elif defined(__GNUC__) && !defined(__PCC__)
258
#  pragma GCC diagnostic pop
259
# endif
260
#endif
261

262
    sixel_logger_logf(logger,
×
263
                      "worker",
264
                      "loader",
265
                      event,
266
                      -1,
267
                      -1,
268
                      0,
269
                      0,
270
                      0,
271
                      0,
272
                      "%s",
273
                      message);
274
}
13,193!
275

276
static SIXELSTATUS
277
loader_callback_trampoline(sixel_frame_t *frame, void *data)
14,038✔
278
{
279
    sixel_loader_callback_state_t *state;
8,265✔
280
    SIXELSTATUS status;
8,265✔
281
    sixel_loader_t *loader;
8,265✔
282

283
    state = (sixel_loader_callback_state_t *)data;
14,038✔
284
    loader = NULL;
14,038✔
285
    if (state == NULL || state->fn == NULL) {
14,038!
286
        return SIXEL_BAD_ARGUMENT;
287
    }
288

289
    loader = state->loader;
14,038✔
290
    if (loader != NULL && loader->log_loader_finished == 0) {
14,038!
291
        loader_log_stage(loader,
19,892✔
292
                         "finish",
293
                         "path=%s loader=%s bytes=%zu",
294
                         loader->log_path,
13,359✔
295
                         loader->log_loader_name,
13,359✔
296
                         loader->log_input_bytes);
6,533✔
297
        loader->log_loader_finished = 1;
13,359✔
298
    }
6,533✔
299

300
    status = state->fn(frame, state->context);
14,925✔
301
    if (SIXEL_FAILED(status) && state->loader != NULL) {
14,925!
302
        state->loader->callback_failed = 1;
×
303
    }
304

305
    return status;
9,364✔
306
}
6,955✔
307

308
static int
309
loader_plan_contains(sixel_loader_entry_t const **plan,
31,242✔
310
                     size_t plan_length,
311
                     sixel_loader_entry_t const *entry)
312
{
313
    size_t index;
14,048✔
314

315
    for (index = 0; index < plan_length; ++index) {
93,590!
316
        if (plan[index] == entry) {
54,912!
317
            return 1;
318
        }
319
    }
49,855✔
320

321
    return 0;
31,242✔
322
}
26,727✔
323

324

325
static int
326
loader_token_matches(char const *token,
327
                     size_t token_length,
328
                     char const *name);
329

330
#if HAVE_WIC
331
static sixel_suboption_key_t const g_subkeys_loader_wic_loader[] = {
332
    {
333
        "ico_minsize",
334
        NULL,
335
        "SIXEL_LODER_WIC_ICO_MINSIZE",
336
        SIXEL_SUBOPTION_VALUE_FREE,
337
        NULL,
338
        0u
339
    }
340
};
341
#endif
342

343
static sixel_option_value_schema_t const g_schema_loader_values_loader[] = {
344
#if HAVE_LIBPNG
345
    { "libpng", 0, NULL, 0u },
346
#endif
347
#if HAVE_JPEG
348
    { "libjpeg", 0, NULL, 0u },
349
#endif
350
#if HAVE_WEBP
351
    { "libwebp", 0, NULL, 0u },
352
#endif
353
#if HAVE_LIBTIFF
354
    { "libtiff", 0, NULL, 0u },
355
#endif
356
    { "builtin", 0, NULL, 0u },
357
#if HAVE_WIC
358
    {
359
        "wic",
360
        0,
361
        g_subkeys_loader_wic_loader,
362
        sizeof(g_subkeys_loader_wic_loader)
363
            / sizeof(g_subkeys_loader_wic_loader[0])
364
    },
365
#endif
366
#if HAVE_COREGRAPHICS
367
    { "coregraphics", 0, NULL, 0u },
368
#endif
369
#ifdef HAVE_GDK_PIXBUF2
370
    { "gdk-pixbuf2", 0, NULL, 0u },
371
#endif
372
#if HAVE_GD
373
    { "gd", 0, NULL, 0u },
374
#endif
375
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
376
    { "quicklook", 0, NULL, 0u },
377
#endif
378
#if HAVE_FREEDESKTOP_THUMBNAILING
379
    { "gnome-thumbnailer", 0, NULL, 0u },
380
#endif
381
};
382

383
static sixel_option_argument_schema_t const g_schema_loaders_loader = {
384
    SIXEL_OPTFLAG_LOADERS,
385
    "--loaders",
386
    g_schema_loader_values_loader,
387
    sizeof(g_schema_loader_values_loader)
388
        / sizeof(g_schema_loader_values_loader[0])
389
};
390

391
static size_t
392
loader_token_name_length(char const *token, size_t token_length)
972✔
393
{
394
    size_t index;
450✔
395

396
    index = 0;
972✔
397
    while (index < token_length) {
11,114!
398
        if (token[index] == ':') {
9,832!
399
            break;
8✔
400
        }
401
        ++index;
9,816✔
402
    }
403

404
    return index;
1,748✔
405
}
450✔
406

407
static int
408
loader_parse_positive_int(char const *text, size_t length, int *value_out)
16✔
409
{
410
    size_t index;
8✔
411
    int value;
8✔
412
    unsigned char digit;
8✔
413

414
    if (text == NULL || value_out == NULL || length == 0) {
16!
415
        return 0;
416
    }
417

418
    value = 0;
8✔
419
    index = 0;
8✔
420
    while (index < length) {
48!
421
        digit = (unsigned char)text[index];
32✔
422
        if (digit < (unsigned char)'0' || digit > (unsigned char)'9') {
32!
423
            return 0;
424
        }
425
        if (value > (INT_MAX - 9) / 10) {
32!
426
            return 0;
427
        }
428
        value = value * 10 + (digit - (unsigned char)'0');
32✔
429
        ++index;
32✔
430
    }
431

432
    if (value <= 0) {
16!
433
        return 0;
434
    }
435

436
    *value_out = value;
16✔
437
    return 1;
16✔
438
}
2✔
439

440
static void
441
loader_apply_loader_suboptions(char const *order)
13,522✔
442
{
443
    char const *cursor;
7,958✔
444
    char const *token_start;
7,958✔
445
    char const *token_end;
7,958✔
446
    char const *order_end;
7,958✔
447
    size_t token_length;
7,958✔
448
    char token_buffer[256];
7,958✔
449
    char match_detail[128];
7,958✔
450
    sixel_option_argument_resolution_t resolution;
7,958✔
451
    size_t assignment_index;
7,958✔
452
    int parsed_value;
7,958✔
453

454
    cursor = order;
13,522✔
455
    token_start = order;
13,522✔
456
    token_end = order;
13,522✔
457
    order_end = NULL;
13,522✔
458
    token_length = 0;
13,522✔
459
    token_buffer[0] = '\0';
13,522✔
460
    match_detail[0] = '\0';
13,522✔
461
    resolution.resolved_base_value = 0;
13,522✔
462
    resolution.base_def = NULL;
13,522✔
463
    resolution.assignments = NULL;
13,522✔
464
    resolution.assignment_count = 0u;
13,522✔
465
    assignment_index = 0u;
13,522✔
466
    parsed_value = 0;
13,522✔
467

468
    sixel_helper_set_wic_ico_minsize(0);
13,522✔
469

470
    if (order == NULL || order[0] == '\0') {
13,522!
471
        return;
12,224✔
472
    }
473

474
    order_end = order + strlen(order);
1,298✔
475
    while (order_end > order && isspace((unsigned char)order_end[-1])) {
1,298!
476
        --order_end;
×
477
    }
478
    if (order_end > order && order_end[-1] == '!') {
1,298!
479
        --order_end;
1,298✔
480
    }
795✔
481

482
    token_start = order;
1,298✔
483
    cursor = order;
1,298✔
484
    while (cursor <= order_end) {
12,652✔
485
        if (cursor == order_end || *cursor == ',') {
11,354!
486
            token_end = cursor;
1,298✔
487
            while (token_start < token_end &&
1,298!
488
                   isspace((unsigned char)*token_start)) {
1,298!
489
                ++token_start;
×
490
            }
491
            while (token_end > token_start &&
1,298!
492
                   isspace((unsigned char)token_end[-1])) {
1,298!
493
                --token_end;
×
494
            }
495
            token_length = (size_t)(token_end - token_start);
1,298✔
496
            if (token_length > 0 && token_length < sizeof(token_buffer)) {
1,298!
497
                memcpy(token_buffer, token_start, token_length);
1,298✔
498
                token_buffer[token_length] = '\0';
1,298✔
499
                if (SIXEL_SUCCEEDED(sixel_option_parse_argument_with_suboptions(
2,034!
500
                        token_buffer,
501
                        &g_schema_loaders_loader,
502
                        &resolution,
503
                        match_detail,
504
                        sizeof(match_detail))) &&
1,298!
505
                    resolution.base_def != NULL &&
1,298!
506
                    strcmp(resolution.base_def->name, "wic") == 0) {
1,298!
507
                    assignment_index = 0u;
136✔
508
                    while (assignment_index < resolution.assignment_count) {
288!
509
                        if (strcmp(resolution.assignments[assignment_index]
18!
510
                                   .resolved_key_name,
2✔
511
                                   "ico_minsize") == 0 &&
16!
512
                            loader_parse_positive_int(
16✔
513
                                resolution.assignments[assignment_index]
8✔
514
                                .resolved_value_text,
8✔
515
                                strlen(resolution.assignments[assignment_index]
10✔
516
                                       .resolved_value_text),
16✔
517
                                &parsed_value)) {
518
                            sixel_helper_set_wic_ico_minsize(parsed_value);
16✔
519
                        }
2✔
520
                        ++assignment_index;
16✔
521
                    }
522
                }
34✔
523
                sixel_option_free_argument_resolution(&resolution);
1,298✔
524
            }
795✔
525
            token_start = cursor + 1;
1,298✔
526
        }
795✔
527
        ++cursor;
11,354✔
528
    }
529
}
6,628!
530

531
static int
532
loader_token_matches(char const *token,
2,867✔
533
                     size_t token_length,
534
                     char const *name)
535
{
536
    size_t index;
1,701✔
537
    unsigned char left;
1,701✔
538
    unsigned char right;
1,701✔
539

540
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
12,970!
541
        left = (unsigned char)token[index];
11,672✔
542
        right = (unsigned char)name[index];
11,672✔
543
        if (tolower(left) != tolower(right)) {
11,672✔
544
            return 0;
1,393✔
545
        }
546
    }
7,134✔
547

548
    if (index != token_length || name[index] != '\0') {
1,298!
549
        return 0;
×
550
    }
551

552
    return 1;
972✔
553
}
2,086✔
554

555
static sixel_loader_entry_t const *
556
loader_lookup_token(char const *token,
1,298✔
557
                    size_t token_length,
558
                    sixel_loader_entry_t const *entries,
559
                    size_t entry_count)
560
{
561
    size_t index;
776✔
562

563
    for (index = 0; index < entry_count; ++index) {
2,867!
564
        if (loader_token_matches(token,
4,953✔
565
                                 token_length,
2,086✔
566
                                 entries[index].name)) {
2,867✔
567
            return &entries[index];
972✔
568
        }
569
    }
1,291✔
570

571
    return NULL;
572
}
795✔
573

574
/*
575
 * loader_build_plan
576
 *
577
 * Translate a comma separated list into an execution plan that reorders the
578
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
579
 * are ignored so that new builds remain forward compatible.
580
 *
581
 * When the input ends with "!", the default fallback list is suppressed so
582
 * only the explicit tokens are tried.
583
 *
584
 *    user input "gd,coregraphics"
585
 *                |
586
 *                v
587
 *        +-------------------+
588
 *        | prioritized list  |
589
 *        +-------------------+
590
 *                |
591
 *                v
592
 *        +-------------------+
593
 *        | default fallbacks |
594
 *        +-------------------+
595
 */
596
static size_t
597
loader_build_plan(char const *order,
13,522✔
598
                  sixel_loader_entry_t const *entries,
599
                  size_t entry_count,
600
                  sixel_loader_entry_t const **plan,
601
                  size_t plan_capacity)
602
{
603
    size_t plan_length;
7,958✔
604
    size_t index;
7,958✔
605
    char const *cursor;
7,958✔
606
    char const *token_start;
7,958✔
607
    char const *token_end;
7,958✔
608
    char const *order_end;
7,958✔
609
    size_t token_length;
7,958✔
610
    sixel_loader_entry_t const *entry;
7,958✔
611
    size_t limit;
7,958✔
612
    int allow_fallback;
7,958✔
613

614
    plan_length = 0;
13,522✔
615
    index = 0;
13,522✔
616
    cursor = order;
13,522✔
617
    token_start = order;
13,522✔
618
    token_end = order;
13,522✔
619
    order_end = NULL;
13,522✔
620
    token_length = 0;
13,522✔
621
    entry = NULL;
13,522✔
622
    limit = plan_capacity;
13,522✔
623
    allow_fallback = 1;
13,522✔
624

625
    if (order != NULL) {
13,522✔
626
        order_end = order + strlen(order);
1,298✔
627
        while (order_end > order &&
1,298!
628
               isspace((unsigned char)order_end[-1])) {
1,298!
629
            --order_end;
×
630
        }
631
        if (order_end > order && order_end[-1] == '!') {
1,298!
632
            allow_fallback = 0;
1,298✔
633
            --order_end;
1,298✔
634
            while (order_end > order &&
1,298!
635
                   isspace((unsigned char)order_end[-1])) {
1,298!
636
                --order_end;
×
637
            }
638
        }
795✔
639
    }
795✔
640

641
    if (order != NULL && plan != NULL && plan_capacity > 0) {
12,714!
642
        token_start = order;
972✔
643
        cursor = order;
972✔
644
        while (cursor < order_end) {
11,354✔
645
            if (*cursor == ',') {
10,056!
646
                token_end = cursor;
×
647
                while (token_start < token_end &&
×
648
                       isspace((unsigned char)*token_start)) {
×
649
                    ++token_start;
×
650
                }
651
                while (token_end > token_start &&
×
652
                       isspace((unsigned char)token_end[-1])) {
×
653
                    --token_end;
×
654
                }
655
                token_length = (size_t)(token_end - token_start);
×
656
                if (token_length > 0) {
×
657
                    entry = loader_lookup_token(token_start,
×
658
                                                loader_token_name_length(
659
                                                    token_start,
660
                                                    token_length),
661
                                                entries,
662
                                                entry_count);
663
                    if (entry != NULL &&
×
664
                        !loader_plan_contains(plan,
×
665
                                              plan_length,
666
                                              entry) &&
×
667
                        plan_length < limit) {
668
                        plan[plan_length] = entry;
×
669
                        ++plan_length;
×
670
                    }
671
                }
672
                token_start = cursor + 1;
×
673
            }
674
            ++cursor;
10,056✔
675
        }
676

677
        token_end = order_end;
1,298✔
678
        while (token_start < token_end &&
1,298!
679
               isspace((unsigned char)*token_start)) {
1,298!
680
            ++token_start;
×
681
        }
682
        while (token_end > token_start &&
1,298!
683
               isspace((unsigned char)token_end[-1])) {
1,298!
684
            --token_end;
×
685
        }
686
        token_length = (size_t)(token_end - token_start);
1,298✔
687
        if (token_length > 0) {
1,298!
688
            entry = loader_lookup_token(token_start,
2,093✔
689
                                        loader_token_name_length(
795✔
690
                                            token_start,
795✔
691
                                            token_length),
795✔
692
                                        entries,
795✔
693
                                        entry_count);
795✔
694
            if (entry != NULL &&
2,211!
695
                !loader_plan_contains(plan, plan_length, entry) &&
1,475!
696
                plan_length < limit) {
795✔
697
                plan[plan_length] = entry;
1,298✔
698
                ++plan_length;
1,298✔
699
            }
795✔
700
        }
795✔
701
    }
795✔
702

703
    if (allow_fallback) {
13,368✔
704
        for (index = 0; index < entry_count && plan_length < limit; ++index) {
56,048✔
705
            entry = &entries[index];
43,824✔
706
            if (!entry->default_enabled) {
43,824✔
707
                continue;
6,444✔
708
            }
709
            if (!loader_plan_contains(plan, plan_length, entry)) {
37,380!
710
                plan[plan_length] = entry;
37,380✔
711
                ++plan_length;
37,380✔
712
            }
25,932✔
713
        }
25,932✔
714
    }
5,833✔
715

716
    return plan_length;
16,932✔
717
}
3,410✔
718

719
static void
720
loader_append_chunk(char *dest,
206✔
721
                    size_t capacity,
722
                    size_t *offset,
723
                    char const *chunk)
724
{
725
    size_t available;
132✔
726
    size_t length;
132✔
727

728
    if (dest == NULL || offset == NULL || chunk == NULL) {
206!
729
        return;
730
    }
731

732
    if (*offset >= capacity) {
206!
733
        return;
734
    }
735

736
    available = capacity - *offset;
206✔
737
    if (available == 0) {
206✔
738
        return;
739
    }
740

741
    length = strlen(chunk);
206✔
742
    if (length >= available) {
206!
743
        if (available == 0) {
×
744
            return;
745
        }
746
        length = available - 1u;
×
747
    }
748

749
    if (length > 0) {
206!
750
        memcpy(dest + *offset, chunk, length);
206✔
751
        *offset += length;
206✔
752
    }
206✔
753

754
    if (*offset < capacity) {
206!
755
        dest[*offset] = '\0';
206✔
756
    } else {
206✔
757
        dest[capacity - 1u] = '\0';
×
758
    }
759
}
206!
760

761
static void
762
loader_append_key_value(char *dest,
98✔
763
                        size_t capacity,
764
                        size_t *offset,
765
                        char const *label,
766
                        char const *value)
767
{
768
    char line[128];
60✔
769
    int written;
60✔
770

771
    if (value == NULL || value[0] == '\0') {
98!
772
        return;
×
773
    }
774

775
    written = sixel_compat_snprintf(line,
196✔
776
                                    sizeof(line),
777
                                    "  %-10s: %s\n",
778
                                    label,
98✔
779
                                    value);
98✔
780
    if (written < 0) {
98!
781
        return;
782
    }
783

784
    if ((size_t)written >= sizeof(line)) {
98!
785
        line[sizeof(line) - 1u] = '\0';
×
786
    }
787

788
    loader_append_chunk(dest, capacity, offset, line);
98✔
789
}
98!
790

791
static void
792
loader_extract_extension(char const *path, char *buffer, size_t capacity)
27✔
793
{
794
    char const *dot;
18✔
795
    size_t index;
18✔
796

797
    if (buffer == NULL || capacity == 0) {
27!
798
        return;
799
    }
800

801
    buffer[0] = '\0';
27✔
802

803
    if (path == NULL) {
27!
804
        return;
805
    }
806

807
    dot = strrchr(path, '.');
27✔
808
    if (dot == NULL || dot[1] == '\0') {
27!
809
        return;
15✔
810
    }
811

812
#if defined(_WIN32)
813
    {
814
        char const *slash;
815
        char const *backslash;
816

817
        slash = strrchr(path, '/');
818
        backslash = strrchr(path, '\\');
819
        if ((slash != NULL && dot < slash) ||
820
                (backslash != NULL && dot < backslash)) {
821
            return;
822
        }
823
    }
824
#else
825
    {
826
        char const *slash;
8✔
827

828
        slash = strrchr(path, '/');
12✔
829
        if (slash != NULL && dot < slash) {
12!
830
            return;
831
        }
832
    }
8!
833
#endif
834

835
    if (dot[1] == '\0') {
12✔
836
        return;
837
    }
838

839
    dot += 1;
12✔
840

841
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
48!
842
        buffer[index] = (char)tolower((unsigned char)dot[index]);
36✔
843
    }
36✔
844
    buffer[index] = '\0';
12✔
845
}
27!
846

847

848

849

850

851

852

853

854

855

856

857
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
858
static void
859
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
16✔
860
{
861
    if (buffer == NULL || capacity == 0) {
16!
862
        return;
863
    }
864

865
    buffer[0] = '\0';
16✔
866
    if (source == NULL) {
16!
867
        return;
868
    }
869

870
    if (!CFStringGetCString(source,
32!
871
                             buffer,
16✔
872
                             (CFIndex)capacity,
16✔
873
                             kCFStringEncodingUTF8)) {
874
        buffer[0] = '\0';
875
    }
876
}
16✔
877
#endif
878

879

880
static void
881
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
27✔
882
                          char const *filename)
883
{
884
    enum { description_length = 128 };
885
    enum { uttype_length = 128 };
886
    enum { extension_length = 32 };
887
    enum { message_length = 768 };
888
    char message[message_length];
18✔
889
    char type_value[description_length];
18✔
890
    char extension_text[extension_length + 2];
18✔
891
    char uttype[uttype_length];
18✔
892
    char desc_buffer[description_length];
18✔
893
    char extension[extension_length];
18✔
894
    char const *path;
18✔
895
    char const *display_path;
18✔
896
    char const *metadata_path;
18✔
897
    char const *description_text;
18✔
898
    char *mime_string;
18✔
899
    char *description_string;
18✔
900
    size_t offset;
18✔
901
    int gnome_available;
18✔
902
    int gnome_has_dirs;
18✔
903
    int gnome_has_match;
18✔
904
    int suggestions;
18✔
905

906
    message[0] = '\0';
27✔
907
    type_value[0] = '\0';
27✔
908
    extension_text[0] = '\0';
27✔
909
    uttype[0] = '\0';
27✔
910
    desc_buffer[0] = '\0';
27✔
911
    extension[0] = '\0';
27✔
912
    path = NULL;
27✔
913
    display_path = "(stdin)";
27✔
914
    metadata_path = NULL;
27✔
915
    description_text = NULL;
27✔
916
    mime_string = NULL;
27✔
917
    description_string = NULL;
27✔
918
    offset = 0u;
27✔
919
    gnome_available = 0;
27✔
920
    gnome_has_dirs = 0;
27✔
921
    gnome_has_match = 0;
27✔
922
    suggestions = 0;
27✔
923

924
    if (pchunk != NULL && pchunk->source_path != NULL) {
27!
925
        path = pchunk->source_path;
24✔
926
    } else if (filename != NULL) {
27!
927
        path = filename;
3✔
928
    }
3✔
929

930
    if (path != NULL && strcmp(path, "-") != 0) {
27!
931
        display_path = path;
24✔
932
    }
24✔
933

934
    if (path != NULL && strcmp(path, "-") != 0 &&
27!
935
            strstr(path, "://") == NULL) {
24!
936
        metadata_path = path;
24✔
937
    }
24✔
938

939
    loader_extract_extension(path, extension, sizeof(extension));
26✔
940

941
#if HAVE_FREEDESKTOP_THUMBNAILING
942
    if (metadata_path != NULL) {
26!
943
        /*
944
         * Collect MIME metadata via file(1) when fork() and friends are
945
         * available.  Windows builds compiled with clang64 lack these
946
         * interfaces, so the thumbnail helpers remain disabled there.
947
         */
948
        mime_string = thumbnailer_guess_content_type(metadata_path);
24✔
949
        description_string = thumbnailer_run_file(metadata_path, NULL);
24✔
950
    }
24✔
951
#else
952
    (void)metadata_path;
953
#endif
954

955
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
956
#if defined(__clang__)
957
    /*
958
     * Allow use of legacy UTType C APIs when compiling with the
959
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
960
     * so we must intentionally silence the deprecation warnings here.
961
     */
962
#pragma clang diagnostic push
963
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
964
#endif
965
    {
966
        CFStringRef uti_ref;
967
        CFStringRef mime_ref;
968
        CFStringRef ext_ref;
969
        CFStringRef desc_ref;
970
        CFStringRef preferred_mime;
971
        char uti_local[uttype_length];
972
        char desc_local[description_length];
973
        char mime_local[64];
974

975
        uti_ref = NULL;
8✔
976
        mime_ref = NULL;
8✔
977
        ext_ref = NULL;
8✔
978
        desc_ref = NULL;
8✔
979
        preferred_mime = NULL;
8✔
980
        uti_local[0] = '\0';
8✔
981
        desc_local[0] = '\0';
8✔
982
        mime_local[0] = '\0';
8✔
983

984
        if (mime_string != NULL) {
8!
985
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
16✔
986
                                                 mime_string,
8✔
987
                                                 kCFStringEncodingUTF8);
988
        }
8✔
989
        if (mime_ref != NULL) {
8!
990
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
8✔
991
                kUTTagClassMIMEType,
8✔
992
                mime_ref,
8✔
993
                NULL);
994
        }
8✔
995
        if (uti_ref == NULL && extension[0] != '\0') {
15!
996
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
997
                                                extension,
998
                                                kCFStringEncodingUTF8);
999
            if (ext_ref != NULL) {
×
1000
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
1001
                    kUTTagClassFilenameExtension,
1002
                    ext_ref,
1003
                    NULL);
1004
            }
1005
        }
1006
        if (uti_ref != NULL) {
8!
1007
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
8✔
1008
            desc_ref = UTTypeCopyDescription(uti_ref);
8✔
1009
            if (desc_ref != NULL) {
8!
1010
                loader_copy_cfstring(desc_ref,
16✔
1011
                                     desc_local,
8✔
1012
                                     sizeof(desc_local));
1013
                CFRelease(desc_ref);
8✔
1014
                desc_ref = NULL;
8✔
1015
            }
8✔
1016
            if (mime_string == NULL) {
16!
1017
                preferred_mime = UTTypeCopyPreferredTagWithClass(
1018
                    uti_ref,
1019
                    kUTTagClassMIMEType);
1020
                if (preferred_mime != NULL) {
×
1021
                    loader_copy_cfstring(preferred_mime,
1022
                                         mime_local,
1023
                                         sizeof(mime_local));
1024
                    CFRelease(preferred_mime);
1025
                    preferred_mime = NULL;
1026
                }
1027
                if (mime_local[0] != '\0') {
×
1028
                    mime_string = thumbnailer_strdup(mime_local);
1029
                }
1030
            }
1031
        }
8✔
1032
        if (mime_ref != NULL) {
8!
1033
            CFRelease(mime_ref);
8✔
1034
        }
8✔
1035
        if (ext_ref != NULL) {
16!
1036
            CFRelease(ext_ref);
1037
        }
1038
        if (uti_ref != NULL) {
8!
1039
            CFRelease(uti_ref);
8✔
1040
        }
8✔
1041
        if (uti_local[0] != '\0') {
8!
1042
            sixel_compat_snprintf(uttype,
16✔
1043
                                  sizeof(uttype),
1044
                                  "%s",
1045
                                  uti_local);
8✔
1046
        }
8✔
1047
        if (desc_local[0] != '\0') {
8!
1048
            sixel_compat_snprintf(desc_buffer,
16✔
1049
                                  sizeof(desc_buffer),
1050
                                  "%s",
1051
                                  desc_local);
8✔
1052
        }
8✔
1053
    }
1054
#if defined(__clang__)
1055
#pragma clang diagnostic pop
1056
#endif
1057
#endif
1058

1059
    if (description_string != NULL && description_string[0] != '\0') {
26!
1060
        description_text = description_string;
24✔
1061
    } else if (desc_buffer[0] != '\0') {
27!
1062
        description_text = desc_buffer;
1063
    } else {
1064
        description_text = "unknown content";
3✔
1065
    }
1066

1067
    sixel_compat_snprintf(type_value,
54✔
1068
                          sizeof(type_value),
1069
                          "%s",
1070
                          description_text);
27✔
1071

1072
    loader_append_chunk(message,
27✔
1073
                        sizeof(message),
1074
                        &offset,
1075
                        "diagnostic:\n");
1076
    loader_append_key_value(message,
54✔
1077
                            sizeof(message),
1078
                            &offset,
1079
                            "file",
1080
                            display_path);
27✔
1081
    loader_append_key_value(message,
54✔
1082
                            sizeof(message),
1083
                            &offset,
1084
                            "type",
1085
                            type_value);
27✔
1086

1087
    if (mime_string != NULL && mime_string[0] != '\0') {
27!
1088
        loader_append_key_value(message,
48✔
1089
                                sizeof(message),
1090
                                &offset,
1091
                                "mime",
1092
                                mime_string);
24✔
1093
    }
24✔
1094

1095
    if (uttype[0] != '\0') {
26!
1096
        loader_append_key_value(message,
16✔
1097
                                sizeof(message),
1098
                                &offset,
1099
                                "uti",
1100
                                uttype);
8✔
1101
    }
8✔
1102

1103
    if (extension[0] != '\0') {
30!
1104
        sixel_compat_snprintf(extension_text,
24✔
1105
                              sizeof(extension_text),
1106
                              ".%s",
1107
                              extension);
12✔
1108
        loader_append_key_value(message,
24✔
1109
                                sizeof(message),
1110
                                &offset,
1111
                                "extension",
1112
                                extension_text);
12✔
1113
    }
12✔
1114

1115
    loader_append_chunk(message,
27✔
1116
                        sizeof(message),
1117
                        &offset,
1118
                        "  suggestions:\n");
1119

1120
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1121
    int quicklook_available;
1122
    int quicklook_supported;
1123

1124
    quicklook_available = 0;
9✔
1125
    quicklook_supported = 0;
9✔
1126

1127
    quicklook_available = loader_registry_entry_available("quicklook");
9✔
1128
    if (quicklook_available) {
9!
1129
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
9✔
1130
    }
9✔
1131
    if (quicklook_supported) {
18!
1132
        loader_append_chunk(message,
1133
                            sizeof(message),
1134
                            &offset,
1135
                            "    - QuickLook rendered a preview during "
1136
                            "the probe; try -L quicklook.\n");
1137
        suggestions += 1;
1138
    }
1139
#endif
1140

1141
#if HAVE_FREEDESKTOP_THUMBNAILING
1142
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
27✔
1143
    if (gnome_available) {
27!
1144
        loader_probe_gnome_thumbnailers(mime_string,
27✔
1145
                                        &gnome_has_dirs,
1146
                                        &gnome_has_match);
1147
        if (gnome_has_dirs && gnome_has_match) {
27!
1148
            loader_append_chunk(message,
1149
                                sizeof(message),
1150
                                &offset,
1151
                                "    - GNOME thumbnailer definitions match "
1152
                                "this MIME type; try -L gnome-thumbnailer.\n"
1153
                                );
1154
            suggestions += 1;
1155
        }
1156
    }
27✔
1157
#else
1158
    (void)gnome_available;
1159
    (void)gnome_has_dirs;
1160
    (void)gnome_has_match;
1161
#endif
1162

1163
    if (suggestions == 0) {
27!
1164
        loader_append_chunk(message,
27✔
1165
                            sizeof(message),
1166
                            &offset,
1167
                            "    (no thumbnail helper hints)\n");
1168
    }
27✔
1169

1170
    if (suggestions > 0) {
27!
1171
        loader_append_chunk(message,
1172
                            sizeof(message),
1173
                            &offset,
1174
                            "  hint       : Enable one of the suggested "
1175
                            "loaders with -L.\n");
1176
    } else {
1177
        loader_append_chunk(message,
27✔
1178
                            sizeof(message),
1179
                            &offset,
1180
                            "  hint       : Convert the file to PNG or "
1181
                            "enable optional loaders.\n");
1182
    }
1183

1184
    sixel_helper_set_additional_message(message);
27✔
1185

1186
    free(mime_string);
27✔
1187
    free(description_string);
27✔
1188
}
27✔
1189

1190
SIXELAPI SIXELSTATUS
1191
sixel_loader_new(
13,578✔
1192
    sixel_loader_t   /* out */ **pploader,
1193
    sixel_allocator_t/* in */  *allocator)
1194
{
1195
    SIXELSTATUS status = SIXEL_FALSE;
13,578✔
1196
    sixel_loader_t *loader;
7,986✔
1197
    sixel_allocator_t *local_allocator;
7,986✔
1198

1199
    loader = NULL;
13,578✔
1200
    local_allocator = allocator;
13,578✔
1201

1202
    if (pploader == NULL) {
13,578!
1203
        sixel_helper_set_additional_message(
×
1204
            "sixel_loader_new: pploader is null.");
1205
        status = SIXEL_BAD_ARGUMENT;
×
1206
        goto end;
×
1207
    }
1208

1209
    if (local_allocator == NULL) {
13,578!
1210
        status = sixel_allocator_new(&local_allocator,
×
1211
                                     NULL,
1212
                                     NULL,
1213
                                     NULL,
1214
                                     NULL);
1215
        if (SIXEL_FAILED(status)) {
×
1216
            goto end;
×
1217
        }
1218
    } else {
1219
        sixel_allocator_ref(local_allocator);
13,578✔
1220
    }
1221

1222
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
13,578✔
1223
                                                      sizeof(*loader));
1224
    if (loader == NULL) {
13,578!
1225
        sixel_helper_set_additional_message(
×
1226
            "sixel_loader_new: sixel_allocator_malloc() failed.");
1227
        status = SIXEL_BAD_ALLOCATION;
×
1228
        sixel_allocator_unref(local_allocator);
×
1229
        goto end;
×
1230
    }
1231

1232
    loader->ref = 1U;
13,578✔
1233
    loader->fstatic = 0;
13,578✔
1234
    loader->fuse_palette = 0;
13,578✔
1235
    loader->reqcolors = SIXEL_PALETTE_MAX;
13,578✔
1236
    loader->bgcolor[0] = 0;
13,578✔
1237
    loader->bgcolor[1] = 0;
13,578✔
1238
    loader->bgcolor[2] = 0;
13,578✔
1239
    loader->has_bgcolor = 0;
13,578✔
1240
    loader->loop_control = SIXEL_LOOP_AUTO;
13,578✔
1241
    loader->finsecure = 0;
13,578✔
1242
    loader->has_start_frame_no = 0;
13,578✔
1243
    loader->start_frame_no = INT_MIN;
13,578✔
1244
    loader->cancel_flag = NULL;
13,578✔
1245
    loader->context = NULL;
13,578✔
1246
    /*
1247
     * Initialize a private logger. The helper reuses an existing global
1248
     * logger sink when present so loader markers share the timeline with
1249
     * upstream stages without requiring sixel_loader_setopt().
1250
     */
1251
    sixel_logger_init(&loader->logger);
13,578✔
1252
    (void)sixel_logger_prepare_env(&loader->logger);
13,578✔
1253
    loader->loader_order = NULL;
13,578✔
1254
    loader->allocator = local_allocator;
13,578✔
1255
    loader->last_loader_name[0] = '\0';
13,578✔
1256
    loader->last_source_path[0] = '\0';
13,578✔
1257
    loader->last_input_bytes = 0u;
13,578✔
1258
    loader->callback_failed = 0;
13,578✔
1259
    loader->log_loader_finished = 0;
13,578✔
1260
    loader->log_path[0] = '\0';
13,578✔
1261
    loader->log_loader_name[0] = '\0';
13,578✔
1262
    loader->log_input_bytes = 0u;
13,578✔
1263

1264
    *pploader = loader;
13,578✔
1265
    status = SIXEL_OK;
13,578✔
1266

1267
end:
6,918✔
1268
    return status;
17,004✔
1269
}
3,426✔
1270

1271
SIXELAPI void
1272
sixel_loader_ref(
122,014✔
1273
    sixel_loader_t /* in */ *loader)
1274
{
1275
    if (loader == NULL) {
122,014!
1276
        return;
1277
    }
1278

1279
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
122,014✔
1280
}
60,100✔
1281

1282
SIXELAPI void
1283
sixel_loader_unref(
135,592✔
1284
    sixel_loader_t /* in */ *loader)
1285
{
1286
    sixel_allocator_t *allocator;
79,768✔
1287
    unsigned int previous;
79,768✔
1288

1289
    if (loader == NULL) {
135,592!
1290
        return;
1291
    }
1292

1293
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
135,592✔
1294
    if (previous == 1U) {
135,592✔
1295
        allocator = loader->allocator;
13,578✔
1296
        sixel_logger_close(&loader->logger);
13,578✔
1297
        sixel_allocator_free(allocator, loader->loader_order);
13,578✔
1298
        sixel_allocator_free(allocator, loader);
13,578✔
1299
        sixel_allocator_unref(allocator);
13,578✔
1300
    }
6,660✔
1301
}
66,760!
1302

1303
SIXELAPI SIXELSTATUS
1304
sixel_loader_setopt(
108,436✔
1305
    sixel_loader_t /* in */ *loader,
1306
    int            /* in */ option,
1307
    void const     /* in */ *value)
1308
{
1309
    SIXELSTATUS status = SIXEL_FALSE;
108,436✔
1310
    int const *flag;
63,796✔
1311
    unsigned char const *color;
63,796✔
1312
    char const *order;
63,796✔
1313
    char *copy;
63,796✔
1314
    sixel_allocator_t *allocator;
63,796✔
1315

1316
    flag = NULL;
108,436✔
1317
    color = NULL;
108,436✔
1318
    order = NULL;
108,436✔
1319
    copy = NULL;
108,436✔
1320
    allocator = NULL;
108,436✔
1321

1322
    if (loader == NULL) {
108,436!
1323
        sixel_helper_set_additional_message(
×
1324
            "sixel_loader_setopt: loader is null.");
1325
        status = SIXEL_BAD_ARGUMENT;
×
1326
        goto end0;
×
1327
    }
1328

1329
    sixel_loader_ref(loader);
108,436✔
1330

1331
    switch (option) {
108,436!
1332
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
6,918✔
1333
        flag = (int const *)value;
13,578✔
1334
        loader->fstatic = flag != NULL ? *flag : 0;
13,578!
1335
        status = SIXEL_OK;
13,578✔
1336
        break;
13,578✔
1337
    case SIXEL_LOADER_OPTION_USE_PALETTE:
6,918✔
1338
        flag = (int const *)value;
13,578✔
1339
        loader->fuse_palette = flag != NULL ? *flag : 0;
13,578!
1340
        status = SIXEL_OK;
13,578✔
1341
        break;
13,578✔
1342
    case SIXEL_LOADER_OPTION_REQCOLORS:
6,918✔
1343
        flag = (int const *)value;
13,578✔
1344
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
13,578!
1345
        if (loader->reqcolors < 1) {
13,578!
1346
            sixel_helper_set_additional_message(
×
1347
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1348
            status = SIXEL_BAD_ARGUMENT;
×
1349
            goto end;
×
1350
        }
1351
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
13,578!
1352
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1353
        }
1354
        status = SIXEL_OK;
9,018✔
1355
        break;
9,018✔
1356
    case SIXEL_LOADER_OPTION_BGCOLOR:
3,372✔
1357
        if (value == NULL) {
6,742✔
1358
            loader->has_bgcolor = 0;
6,606✔
1359
        } else {
3,279✔
1360
            color = (unsigned char const *)value;
136✔
1361
            loader->bgcolor[0] = color[0];
136✔
1362
            loader->bgcolor[1] = color[1];
136✔
1363
            loader->bgcolor[2] = color[2];
136✔
1364
            loader->has_bgcolor = 1;
136✔
1365
        }
1366
        status = SIXEL_OK;
4,510✔
1367
        break;
4,510✔
1368
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
6,918✔
1369
        flag = (int const *)value;
13,578✔
1370
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
13,578!
1371
        status = SIXEL_OK;
13,578✔
1372
        break;
13,578✔
1373
    case SIXEL_LOADER_OPTION_INSECURE:
6,918✔
1374
        flag = (int const *)value;
13,578✔
1375
        loader->finsecure = flag != NULL ? *flag : 0;
13,578!
1376
        status = SIXEL_OK;
13,578✔
1377
        break;
13,578✔
1378
    case SIXEL_LOADER_OPTION_START_FRAME_NO:
3,372✔
1379
        if (value == NULL) {
6,742!
1380
            loader->has_start_frame_no = 0;
6,621✔
1381
            loader->start_frame_no = INT_MIN;
6,621✔
1382
        } else {
3,284✔
1383
            flag = (int const *)value;
121✔
1384
            loader->start_frame_no = *flag;
121✔
1385
            loader->has_start_frame_no = 1;
121✔
1386
        }
1387
        status = SIXEL_OK;
4,510✔
1388
        break;
4,510✔
1389
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
3,372✔
1390
        loader->cancel_flag = (int const *)value;
6,742✔
1391
        status = SIXEL_OK;
6,742✔
1392
        break;
6,742✔
1393
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
3,372✔
1394
        allocator = loader->allocator;
6,742✔
1395
        sixel_allocator_free(allocator, loader->loader_order);
6,742✔
1396
        loader->loader_order = NULL;
6,742✔
1397
        if (value != NULL) {
6,742✔
1398
            order = (char const *)value;
1,289✔
1399
            copy = loader_strdup(order, allocator);
1,289✔
1400
            if (copy == NULL) {
1,289!
1401
                sixel_helper_set_additional_message(
×
1402
                    "sixel_loader_setopt: loader_strdup() failed.");
1403
                status = SIXEL_BAD_ALLOCATION;
×
1404
                goto end;
×
1405
            }
1406
            loader->loader_order = copy;
1,289✔
1407
        }
788✔
1408
        status = SIXEL_OK;
4,510✔
1409
        break;
4,510✔
1410
    case SIXEL_LOADER_OPTION_CONTEXT:
6,918✔
1411
        loader->context = (void *)value;
13,578✔
1412
        status = SIXEL_OK;
13,578✔
1413
        break;
13,578✔
1414
    default:
1415
        sixel_helper_set_additional_message(
×
1416
            "sixel_loader_setopt: unknown option.");
1417
        status = SIXEL_BAD_ARGUMENT;
×
1418
        goto end;
×
1419
    }
53,440✔
1420

1421
end:
54,996✔
1422
    sixel_loader_unref(loader);
108,436✔
1423

1424
end0:
54,996✔
1425
    return status;
135,944✔
1426
}
27,508✔
1427

1428
SIXELAPI char const *
1429
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
12,414✔
1430
{
1431
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
12,414!
1432
        return NULL;
1433
    }
1434
    return loader->last_loader_name;
12,414✔
1435
}
6,168✔
1436

1437
SIXELAPI char const *
1438
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
12,141✔
1439
{
1440
    if (loader == NULL || loader->last_source_path[0] == '\0') {
12,141!
1441
        return NULL;
183✔
1442
    }
1443
    return loader->last_source_path;
11,868✔
1444
}
6,033✔
1445

1446
SIXELAPI size_t
1447
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
6,207✔
1448
{
1449
    if (loader == NULL) {
6,207!
1450
        return 0u;
1451
    }
1452
    return loader->last_input_bytes;
6,207✔
1453
}
3,084✔
1454

1455
int
1456
sixel_loader_get_start_frame_no(sixel_loader_t const *loader,
×
1457
                                int *start_frame_no)
1458
{
1459
    if (start_frame_no != NULL) {
×
1460
        *start_frame_no = INT_MIN;
×
1461
    }
1462
    if (loader == NULL || start_frame_no == NULL ||
×
1463
        loader->has_start_frame_no == 0) {
×
1464
        return 0;
1465
    }
1466

1467
    *start_frame_no = loader->start_frame_no;
×
1468
    return 1;
×
1469
}
1470

1471
SIXELAPI SIXELSTATUS
1472
sixel_loader_load_file(
13,578✔
1473
    sixel_loader_t         /* in */ *loader,
1474
    char const             /* in */ *filename,
1475
    sixel_load_image_function /* in */ fn_load)
1476
{
1477
    SIXELSTATUS status = SIXEL_FALSE;
13,578✔
1478
    sixel_chunk_t *pchunk;
7,986✔
1479
    sixel_loader_entry_t const **plan;
7,986✔
1480
    sixel_loader_entry_t const *entries;
7,986✔
1481
    size_t entry_count;
7,986✔
1482
    size_t plan_length;
7,986✔
1483
    size_t plan_index;
7,986✔
1484
    unsigned char *bgcolor;
7,986✔
1485
    int reqcolors;
7,986✔
1486
    char const *order_override;
7,986✔
1487
    char const *env_order;
7,986✔
1488
    sixel_loader_callback_state_t callback_state;
7,986✔
1489

1490
    pchunk = NULL;
13,578✔
1491
    plan = NULL;
13,578✔
1492
    entries = NULL;
13,578✔
1493
    entry_count = 0;
13,578✔
1494
    plan_length = 0;
13,578✔
1495
    plan_index = 0;
13,578✔
1496
    bgcolor = NULL;
13,578✔
1497
    reqcolors = 0;
13,578✔
1498
    order_override = NULL;
13,578✔
1499
    env_order = NULL;
13,578✔
1500

1501
    if (loader == NULL) {
13,578!
1502
        sixel_helper_set_additional_message(
×
1503
            "sixel_loader_load_file: loader is null.");
1504
        status = SIXEL_BAD_ARGUMENT;
×
1505
        goto end0;
×
1506
    }
1507

1508
    sixel_loader_ref(loader);
13,578✔
1509

1510
    loader->log_loader_finished = 0;
13,578✔
1511
    loader->log_loader_name[0] = '\0';
13,578✔
1512
    loader->log_input_bytes = 0u;
13,578✔
1513
    loader->log_path[0] = '\0';
13,578✔
1514
    if (filename != NULL) {
13,578✔
1515
        (void)sixel_compat_snprintf(loader->log_path,
19,863✔
1516
                                    sizeof(loader->log_path),
1517
                                    "%s",
1518
                                    filename);
6,540✔
1519
    }
6,540✔
1520
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
14,514✔
1521

1522
    memset(&callback_state, 0, sizeof(callback_state));
14,514✔
1523
    callback_state.loader = loader;
14,514✔
1524
    callback_state.fn = fn_load;
14,514✔
1525
    callback_state.context = loader->context;
14,514✔
1526
    loader->callback_failed = 0;
14,514✔
1527

1528
    entry_count = loader_registry_get_entries(&entries);
14,514✔
1529

1530
    reqcolors = loader->reqcolors;
14,514✔
1531
    if (reqcolors > SIXEL_PALETTE_MAX) {
14,514!
1532
        reqcolors = SIXEL_PALETTE_MAX;
1533
    }
1534

1535
    status = sixel_chunk_new(&pchunk,
13,578✔
1536
                             filename,
6,660✔
1537
                             loader->finsecure,
6,660✔
1538
                             loader->cancel_flag,
6,660✔
1539
                             loader->allocator);
6,660✔
1540
    if (status != SIXEL_OK) {
13,578!
1541
        goto end;
56✔
1542
    }
1543

1544
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
13,522!
1545
        status = SIXEL_OK;
×
1546
        goto end;
×
1547
    }
1548

1549
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
13,504!
1550
        (void)sixel_compat_snprintf(loader->log_path,
19,717✔
1551
                                    sizeof(loader->log_path),
1552
                                    "%s",
1553
                                    pchunk->source_path);
8,780✔
1554
    }
6,485✔
1555

1556
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
13,522!
1557
        status = SIXEL_LOGIC_ERROR;
×
1558
        goto end;
×
1559
    }
1560

1561
    if (loader->has_bgcolor) {
13,522✔
1562
        bgcolor = loader->bgcolor;
136✔
1563
    }
91✔
1564

1565
    status = SIXEL_FALSE;
13,369✔
1566
    order_override = loader->loader_order;
13,369✔
1567
    /*
1568
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
1569
     * a loader order via -L/--loaders or sixel_loader_setopt().
1570
     */
1571
    if (order_override == NULL) {
13,369✔
1572
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
12,233✔
1573
        if (env_order != NULL && env_order[0] != '\0') {
12,233!
1574
            order_override = env_order;
4,555✔
1575
        }
7✔
1576
    }
5,840✔
1577

1578
    loader_apply_loader_suboptions(order_override);
13,522✔
1579

1580
    plan = sixel_allocator_malloc(loader->allocator,
23,274✔
1581
                                  entry_count * sizeof(*plan));
12,098✔
1582
    if (plan == NULL) {
13,522!
1583
        status = SIXEL_BAD_ALLOCATION;
×
1584
        goto end;
×
1585
    }
1586

1587
    plan_length = loader_build_plan(order_override,
20,150✔
1588
                                    entries,
6,628✔
1589
                                    entry_count,
6,628✔
1590
                                    plan,
6,628✔
1591
                                    entry_count);
6,628✔
1592
    if (plan_length == 0u) {
13,522!
1593
        if (order_override != NULL && order_override[0] != '\0') {
×
1594
            sixel_helper_set_additional_message(
×
1595
                "sixel_loader_load_file: no supported loader in loader "
1596
                "order.");
1597
            status = SIXEL_BAD_ARGUMENT;
×
1598
        } else {
1599
            sixel_helper_set_additional_message(
×
1600
                "sixel_loader_load_file: no available loader backend.");
1601
            status = SIXEL_LOADER_FAILED;
×
1602
        }
1603
        goto end;
×
1604
    }
1605

1606
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
19,433✔
1607
        if (plan[plan_index] == NULL) {
19,192!
1608
            continue;
×
1609
        }
1610
        if (plan[plan_index]->predicate != NULL &&
19,493!
1611
            plan[plan_index]->predicate(pchunk) == 0) {
7,389✔
1612
            continue;
5,499✔
1613
        }
1614
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
13,693!
1615
        if (plan[plan_index]->name != NULL) {
13,693!
1616
            (void)sixel_compat_snprintf(loader->log_loader_name,
20,429✔
1617
                                        sizeof(loader->log_loader_name),
1618
                                        "%s",
1619
                                        plan[plan_index]->name);
9,109✔
1620
        } else {
6,736✔
1621
            loader->log_loader_name[0] = '\0';
×
1622
        }
1623
        loader_trace_try(plan[plan_index]->name);
13,693✔
1624
        status = plan[plan_index]->backend(pchunk,
20,429✔
1625
                                           loader->fstatic,
6,736✔
1626
                                           loader->fuse_palette,
6,736✔
1627
                                           reqcolors,
6,736✔
1628
                                           bgcolor,
6,736✔
1629
                                           loader->loop_control,
6,736✔
1630
                                           loader->has_start_frame_no,
6,736✔
1631
                                           loader->start_frame_no,
6,736✔
1632
                                           loader_callback_trampoline,
1633
                                           &callback_state);
1634
        loader_trace_result(plan[plan_index]->name, status);
13,693✔
1635
        if (SIXEL_SUCCEEDED(status)) {
13,693✔
1636
            break;
8,799✔
1637
        }
1638
    }
250✔
1639

1640
    if (SIXEL_FAILED(status)) {
13,522✔
1641
        if (status == SIXEL_FALSE) {
241!
1642
            if (!loader->callback_failed &&
54!
1643
                    plan_index >= plan_length &&
27!
1644
                    pchunk != NULL) {
27!
1645
                status = SIXEL_LOADER_FAILED;
27✔
1646
                loader_publish_diagnostic(pchunk, filename);
27✔
1647
            } else {
27✔
1648
                sixel_helper_set_additional_message(
×
1649
                    "sixel_loader_load_file: loader returned "
1650
                    "unspecified failure.");
1651
                status = SIXEL_LOADER_FAILED;
×
1652
            }
1653
        }
27✔
1654
        goto end;
241✔
1655
    }
1656

1657
    if (plan_index < plan_length &&
18,996!
1658
            plan[plan_index] != NULL &&
13,281!
1659
            plan[plan_index]->name != NULL) {
13,281!
1660
        (void)sixel_compat_snprintf(loader->last_loader_name,
19,767✔
1661
                                    sizeof(loader->last_loader_name),
1662
                                    "%s",
1663
                                    plan[plan_index]->name);
8,799✔
1664
    } else {
6,486✔
1665
        loader->last_loader_name[0] = '\0';
×
1666
    }
1667
    loader->last_input_bytes = pchunk->size;
13,281✔
1668
    if (pchunk->source_path != NULL) {
19,767✔
1669
        size_t path_len;
7,657✔
1670

1671
        path_len = strlen(pchunk->source_path);
13,008✔
1672
        if (path_len >= sizeof(loader->last_source_path)) {
13,008!
1673
            path_len = sizeof(loader->last_source_path) - 1u;
1674
        }
1675
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
13,008✔
1676
        loader->last_source_path[path_len] = '\0';
13,008✔
1677
    } else {
6,351✔
1678
        loader->last_source_path[0] = '\0';
273✔
1679
    }
1680

1681
end:
6,906✔
1682
    if (plan != NULL) {
9,030✔
1683
        sixel_allocator_free(loader->allocator, plan);
13,522✔
1684
        plan = NULL;
13,522✔
1685
    }
6,628✔
1686
    sixel_chunk_destroy(pchunk);
13,578✔
1687
    sixel_loader_unref(loader);
13,578✔
1688

1689
end0:
6,918✔
1690
    return status;
17,004✔
1691
}
3,426✔
1692

1693
/* load image from file */
1694

1695
SIXELAPI SIXELSTATUS
1696
sixel_helper_load_image_file(
×
1697
    char const                /* in */     *filename,     /* source file name */
1698
    int                       /* in */     fstatic,       /* whether to */
1699
                                                             /* extract a */
1700
                                                             /* static image */
1701
                                                             /* from an */
1702
                                                             /* animated gif */
1703
    int                       /* in */     fuse_palette,  /* whether to */
1704
                                                             /* use a */
1705
                                                             /* paletted */
1706
                                                             /* image; set */
1707
                                                             /* non-zero to */
1708
                                                             /* request one */
1709
    int                       /* in */     reqcolors,     /* requested */
1710
                                                             /* number of */
1711
                                                             /* colors; */
1712
                                                             /* should be */
1713
                                                             /* equal to or */
1714
                                                             /* less than */
1715
                                                             /* SIXEL_ */
1716
                                                             /* PALETTE_ */
1717
                                                             /* MAX */
1718
    unsigned char             /* in */     *bgcolor,      /* background */
1719
                                                             /* color, may */
1720
                                                             /* be NULL */
1721
    int                       /* in */     loop_control,  /* one of enum */
1722
                                                             /* loopControl */
1723
    sixel_load_image_function /* in */     fn_load,       /* callback */
1724
    int                       /* in */     finsecure,     /* true if do */
1725
                                                             /* not verify */
1726
                                                             /* SSL */
1727
    int const                 /* in */     *cancel_flag,  /* cancel flag, */
1728
                                                             /* may be */
1729
                                                             /* NULL */
1730
    void                      /* in/out */ *context,      /* private data */
1731
                                                             /* passed to */
1732
                                                             /* callback */
1733
                                                             /* function, */
1734
                                                             /* may be */
1735
                                                             /* NULL */
1736
    sixel_allocator_t         /* in */     *allocator     /* allocator */
1737
                                                             /* object, */
1738
                                                             /* may be */
1739
                                                             /* NULL */
1740
)
1741
{
1742
    SIXELSTATUS status = SIXEL_FALSE;
×
1743
    sixel_loader_t *loader;
1744

1745
    loader = NULL;
×
1746

1747
    status = sixel_loader_new(&loader, allocator);
×
1748
    if (SIXEL_FAILED(status)) {
×
1749
        goto end;
×
1750
    }
1751

1752
    status = sixel_loader_setopt(loader,
×
1753
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1754
                                 &fstatic);
1755
    if (SIXEL_FAILED(status)) {
×
1756
        goto end;
×
1757
    }
1758

1759
    status = sixel_loader_setopt(loader,
×
1760
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1761
                                 &fuse_palette);
1762
    if (SIXEL_FAILED(status)) {
×
1763
        goto end;
×
1764
    }
1765

1766
    status = sixel_loader_setopt(loader,
×
1767
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1768
                                 &reqcolors);
1769
    if (SIXEL_FAILED(status)) {
×
1770
        goto end;
×
1771
    }
1772

1773
    status = sixel_loader_setopt(loader,
×
1774
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1775
                                 bgcolor);
1776
    if (SIXEL_FAILED(status)) {
×
1777
        goto end;
×
1778
    }
1779

1780
    status = sixel_loader_setopt(loader,
×
1781
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1782
                                 &loop_control);
1783
    if (SIXEL_FAILED(status)) {
×
1784
        goto end;
×
1785
    }
1786

1787
    status = sixel_loader_setopt(loader,
×
1788
                                 SIXEL_LOADER_OPTION_INSECURE,
1789
                                 &finsecure);
1790
    if (SIXEL_FAILED(status)) {
×
1791
        goto end;
×
1792
    }
1793

1794
    status = sixel_loader_setopt(loader,
×
1795
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1796
                                 cancel_flag);
1797
    if (SIXEL_FAILED(status)) {
×
1798
        goto end;
×
1799
    }
1800

1801
    status = sixel_loader_setopt(loader,
×
1802
                                 SIXEL_LOADER_OPTION_CONTEXT,
1803
                                 context);
1804
    if (SIXEL_FAILED(status)) {
×
1805
        goto end;
×
1806
    }
1807

1808
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1809

1810
end:
1811
    sixel_loader_unref(loader);
×
1812

1813
    return status;
×
1814
}
1815

1816

1817
SIXELAPI size_t
1818
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
136✔
1819
{
1820
    sixel_loader_entry_t const *entries;
80✔
1821
    size_t entry_count;
80✔
1822
    size_t limit;
80✔
1823
    size_t index;
80✔
1824

1825
    entries = NULL;
136✔
1826
    entry_count = loader_registry_get_entries(&entries);
136✔
1827

1828
    if (names != NULL && max_names > 0) {
136!
1829
        limit = entry_count;
68✔
1830
        if (limit > max_names) {
68!
1831
            limit = max_names;
1832
        }
1833
        for (index = 0; index < limit; ++index) {
308✔
1834
            names[index] = entries[index].name;
240✔
1835
        }
168✔
1836
    }
32✔
1837

1838
    return entry_count;
168✔
1839
}
32✔
1840

1841

1842
/* emacs Local Variables:      */
1843
/* emacs mode: c               */
1844
/* emacs tab-width: 4          */
1845
/* emacs indent-tabs-mode: nil */
1846
/* emacs c-basic-offset: 4     */
1847
/* emacs End:                  */
1848
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1849
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc