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

saitoha / libsixel / 21799996667

08 Feb 2026 02:42PM UTC coverage: 79.594% (+0.1%) from 79.456%
21799996667

push

github

saitoha
tests: split pngsuite loader cases and add ms-ssim checks

24793 of 51651 branches covered (48.0%)

40042 of 50308 relevant lines covered (79.59%)

6265085.15 hits per line

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

86.58
/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 "sixel_atomic.h"
115

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

145
typedef struct sixel_loader_callback_state {
146
    sixel_loader_t *loader;
147
    sixel_load_image_function fn;
148
    void *context;
149
} sixel_loader_callback_state_t;
150

151

152
#if HAVE_POSIX_SPAWNP
153
extern char **environ;
154
#endif
155

156
static char *
157
loader_strdup(char const *text, sixel_allocator_t *allocator)
242✔
158
{
159
    char *copy;
146✔
160
    size_t length;
146✔
161

162
    if (text == NULL) {
242!
163
        return NULL;
164
    }
165

166
    length = strlen(text) + 1;
242✔
167
    copy = (char *)sixel_allocator_malloc(allocator, length);
242✔
168
    if (copy == NULL) {
242!
169
        return NULL;
170
    }
171

172
    /* Copy the terminating NUL byte as part of length. */
173
    memcpy(copy, text, length);
242✔
174

175
    return copy;
242✔
176
}
122✔
177

178

179

180
/*
181
 * Emit loader stage markers.
182
 *
183
 * Loader callbacks run the downstream pipeline synchronously, so the finish
184
 * marker must be issued before invoking fn_load() to avoid inflating the
185
 * loader span. The helper keeps the formatting consistent with
186
 * sixel_encoder_log_stage() without depending on encoder internals.
187
 */
188
static void
189
loader_log_stage(sixel_loader_t *loader,
22,831✔
190
                 char const *event,
191
                 char const *fmt,
192
                 ...)
193
{
194
    sixel_logger_t *logger;
14,008✔
195
    char message[256];
14,008✔
196
    va_list args;
14,008✔
197

198
    logger = NULL;
22,831✔
199
    if (loader != NULL) {
22,831!
200
        logger = &loader->logger;
22,831✔
201
    }
10,862✔
202
    if (logger == NULL || logger->file == NULL || !logger->active) {
22,831!
203
        return;
22,831✔
204
    }
205

206
    message[0] = '\0';
×
207
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
208
# if defined(__clang__)
209
#  pragma clang diagnostic push
210
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
211
# elif defined(__GNUC__) && !defined(__PCC__)
212
#  pragma GCC diagnostic push
213
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
214
# endif
215
#endif
216
    va_start(args, fmt);
×
217
    if (fmt != NULL) {
×
218
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
219
# if defined(__clang__)
220
#  pragma clang diagnostic push
221
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
222
# elif defined(__GNUC__) && !defined(__PCC__)
223
#  pragma GCC diagnostic push
224
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
225
# endif
226
#endif
227
        (void)sixel_compat_vsnprintf(message, sizeof(message), fmt, args);
×
228
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
229
# if defined(__clang__)
230
#  pragma clang diagnostic pop
231
# elif defined(__GNUC__) && !defined(__PCC__)
232
#  pragma GCC diagnostic pop
233
# endif
234
#endif
235
    }
236
    va_end(args);
×
237
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
238
# if defined(__clang__)
239
#  pragma clang diagnostic pop
240
# elif defined(__GNUC__) && !defined(__PCC__)
241
#  pragma GCC diagnostic pop
242
# endif
243
#endif
244

245
    sixel_logger_logf(logger,
×
246
                      "worker",
247
                      "loader",
248
                      event,
249
                      -1,
250
                      -1,
251
                      0,
252
                      0,
253
                      0,
254
                      0,
255
                      "%s",
256
                      message);
257
}
10,862!
258

259
static SIXELSTATUS
260
loader_callback_trampoline(sixel_frame_t *frame, void *data)
11,678✔
261
{
262
    sixel_loader_callback_state_t *state;
7,166✔
263
    SIXELSTATUS status;
7,166✔
264
    sixel_loader_t *loader;
7,166✔
265

266
    state = (sixel_loader_callback_state_t *)data;
11,678✔
267
    loader = NULL;
11,678✔
268
    if (state == NULL || state->fn == NULL) {
11,678!
269
        return SIXEL_BAD_ARGUMENT;
270
    }
271

272
    loader = state->loader;
11,678✔
273
    if (loader != NULL && loader->log_loader_finished == 0) {
11,678!
274
        loader_log_stage(loader,
16,732✔
275
                         "finish",
276
                         "path=%s loader=%s bytes=%zu",
277
                         loader->log_path,
11,338✔
278
                         loader->log_loader_name,
11,338✔
279
                         loader->log_input_bytes);
5,394✔
280
        loader->log_loader_finished = 1;
11,338✔
281
    }
5,394✔
282

283
    status = state->fn(frame, state->context);
12,457✔
284
    if (SIXEL_FAILED(status) && state->loader != NULL) {
12,457!
285
        state->loader->callback_failed = 1;
90✔
286
    }
40✔
287

288
    return status;
7,393✔
289
}
5,554✔
290

291
static int
292
loader_plan_contains(sixel_loader_entry_t const **plan,
26,966✔
293
                     size_t plan_length,
294
                     sixel_loader_entry_t const *entry)
295
{
296
    size_t index;
12,102✔
297

298
    for (index = 0; index < plan_length; ++index) {
80,670!
299
        if (plan[index] == entry) {
47,147!
300
            return 1;
301
        }
302
    }
43,024✔
303

304
    return 0;
26,966✔
305
}
23,396✔
306

307
static int
308
loader_token_matches(char const *token,
426✔
309
                     size_t token_length,
310
                     char const *name)
311
{
312
    size_t index;
258✔
313
    unsigned char left;
258✔
314
    unsigned char right;
258✔
315

316
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
2,266!
317
        left = (unsigned char)token[index];
2,024✔
318
        right = (unsigned char)name[index];
2,024✔
319
        if (tolower(left) != tolower(right)) {
2,024!
320
            return 0;
184✔
321
        }
322
    }
1,000✔
323

324
    if (index != token_length || name[index] != '\0') {
242!
325
        return 0;
×
326
    }
327

328
    return 1;
158✔
329
}
306✔
330

331
static sixel_loader_entry_t const *
332
loader_lookup_token(char const *token,
242✔
333
                    size_t token_length,
334
                    sixel_loader_entry_t const *entries,
335
                    size_t entry_count)
336
{
337
    size_t index;
146✔
338

339
    for (index = 0; index < entry_count; ++index) {
426!
340
        if (loader_token_matches(token,
732✔
341
                                 token_length,
306✔
342
                                 entries[index].name)) {
426!
343
            return &entries[index];
158✔
344
        }
345
    }
184✔
346

347
    return NULL;
348
}
122✔
349

350
/*
351
 * loader_build_plan
352
 *
353
 * Translate a comma separated list into an execution plan that reorders the
354
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
355
 * are ignored so that new builds remain forward compatible.
356
 *
357
 * When the input ends with "!", the default fallback list is suppressed so
358
 * only the explicit tokens are tried.
359
 *
360
 *    user input "gd,coregraphics"
361
 *                |
362
 *                v
363
 *        +-------------------+
364
 *        | prioritized list  |
365
 *        +-------------------+
366
 *                |
367
 *                v
368
 *        +-------------------+
369
 *        | default fallbacks |
370
 *        +-------------------+
371
 */
372
static size_t
373
loader_build_plan(char const *order,
11,448✔
374
                  sixel_loader_entry_t const *entries,
375
                  size_t entry_count,
376
                  sixel_loader_entry_t const **plan,
377
                  size_t plan_capacity)
378
{
379
    size_t plan_length;
7,025✔
380
    size_t index;
7,025✔
381
    char const *cursor;
7,025✔
382
    char const *token_start;
7,025✔
383
    char const *token_end;
7,025✔
384
    char const *order_end;
7,025✔
385
    size_t token_length;
7,025✔
386
    sixel_loader_entry_t const *entry;
7,025✔
387
    size_t limit;
7,025✔
388
    int allow_fallback;
7,025✔
389

390
    plan_length = 0;
11,448✔
391
    index = 0;
11,448✔
392
    cursor = order;
11,448✔
393
    token_start = order;
11,448✔
394
    token_end = order;
11,448✔
395
    order_end = NULL;
11,448✔
396
    token_length = 0;
11,448✔
397
    entry = NULL;
11,448✔
398
    limit = plan_capacity;
11,448✔
399
    allow_fallback = 1;
11,448✔
400

401
    if (order != NULL) {
11,448✔
402
        order_end = order + strlen(order);
242✔
403
        while (order_end > order &&
242!
404
               isspace((unsigned char)order_end[-1])) {
242!
405
            --order_end;
×
406
        }
407
        if (order_end > order && order_end[-1] == '!') {
242!
408
            allow_fallback = 0;
242✔
409
            --order_end;
242✔
410
            while (order_end > order &&
242!
411
                   isspace((unsigned char)order_end[-1])) {
242!
412
                --order_end;
×
413
            }
414
        }
122✔
415
    }
122✔
416

417
    if (order != NULL && plan != NULL && plan_capacity > 0) {
10,657!
418
        token_start = order;
158✔
419
        cursor = order;
158✔
420
        while (cursor < order_end) {
1,956✔
421
            if (*cursor == ',') {
1,714!
422
                token_end = cursor;
×
423
                while (token_start < token_end &&
×
424
                       isspace((unsigned char)*token_start)) {
×
425
                    ++token_start;
×
426
                }
427
                while (token_end > token_start &&
×
428
                       isspace((unsigned char)token_end[-1])) {
×
429
                    --token_end;
×
430
                }
431
                token_length = (size_t)(token_end - token_start);
×
432
                if (token_length > 0) {
×
433
                    entry = loader_lookup_token(token_start,
×
434
                                                token_length,
435
                                                entries,
436
                                                entry_count);
437
                    if (entry != NULL &&
×
438
                        !loader_plan_contains(plan,
×
439
                                              plan_length,
440
                                              entry) &&
×
441
                        plan_length < limit) {
442
                        plan[plan_length] = entry;
×
443
                        ++plan_length;
×
444
                    }
445
                }
446
                token_start = cursor + 1;
×
447
            }
448
            ++cursor;
1,714✔
449
        }
450

451
        token_end = order_end;
242✔
452
        while (token_start < token_end &&
242!
453
               isspace((unsigned char)*token_start)) {
242!
454
            ++token_start;
×
455
        }
456
        while (token_end > token_start &&
242!
457
               isspace((unsigned char)token_end[-1])) {
242!
458
            --token_end;
×
459
        }
460
        token_length = (size_t)(token_end - token_start);
242✔
461
        if (token_length > 0) {
242!
462
            entry = loader_lookup_token(token_start,
364✔
463
                                        token_length,
122✔
464
                                        entries,
122✔
465
                                        entry_count);
122✔
466
            if (entry != NULL &&
388!
467
                !loader_plan_contains(plan, plan_length, entry) &&
278!
468
                plan_length < limit) {
122✔
469
                plan[plan_length] = entry;
242✔
470
                ++plan_length;
242✔
471
            }
122✔
472
        }
122✔
473
    }
122✔
474

475
    if (allow_fallback) {
11,429✔
476
        for (index = 0; index < entry_count && plan_length < limit; ++index) {
50,981✔
477
            entry = &entries[index];
39,775✔
478
            if (!entry->default_enabled) {
39,775✔
479
                continue;
6,494✔
480
            }
481
            if (!loader_plan_contains(plan, plan_length, entry)) {
33,281!
482
                plan[plan_length] = entry;
33,281✔
483
                ++plan_length;
33,281✔
484
            }
23,274✔
485
        }
23,274✔
486
    }
5,322✔
487

488
    return plan_length;
14,272✔
489
}
2,824✔
490

491
static void
492
loader_append_chunk(char *dest,
821✔
493
                    size_t capacity,
494
                    size_t *offset,
495
                    char const *chunk)
496
{
497
    size_t available;
486✔
498
    size_t length;
486✔
499

500
    if (dest == NULL || offset == NULL || chunk == NULL) {
821!
501
        return;
502
    }
503

504
    if (*offset >= capacity) {
821!
505
        return;
506
    }
507

508
    available = capacity - *offset;
821✔
509
    if (available == 0) {
821✔
510
        return;
511
    }
512

513
    length = strlen(chunk);
821✔
514
    if (length >= available) {
821!
515
        if (available == 0) {
×
516
            return;
517
        }
518
        length = available - 1u;
×
519
    }
520

521
    if (length > 0) {
821!
522
        memcpy(dest + *offset, chunk, length);
821✔
523
        *offset += length;
821✔
524
    }
396✔
525

526
    if (*offset < capacity) {
821!
527
        dest[*offset] = '\0';
821✔
528
    } else {
396✔
529
        dest[capacity - 1u] = '\0';
×
530
    }
531
}
396!
532

533
static void
534
loader_append_key_value(char *dest,
381✔
535
                        size_t capacity,
536
                        size_t *offset,
537
                        char const *label,
538
                        char const *value)
539
{
540
    char line[128];
222✔
541
    int written;
222✔
542

543
    if (value == NULL || value[0] == '\0') {
381!
544
        return;
×
545
    }
546

547
    written = sixel_compat_snprintf(line,
577✔
548
                                    sizeof(line),
549
                                    "  %-10s: %s\n",
550
                                    label,
196✔
551
                                    value);
196✔
552
    if (written < 0) {
381!
553
        return;
554
    }
555

556
    if ((size_t)written >= sizeof(line)) {
381!
557
        line[sizeof(line) - 1u] = '\0';
×
558
    }
559

560
    loader_append_chunk(dest, capacity, offset, line);
381✔
561
}
196!
562

563
static void
564
loader_extract_extension(char const *path, char *buffer, size_t capacity)
110✔
565
{
566
    char const *dot;
66✔
567
    size_t index;
66✔
568

569
    if (buffer == NULL || capacity == 0) {
110!
570
        return;
571
    }
572

573
    buffer[0] = '\0';
110✔
574

575
    if (path == NULL) {
110✔
576
        return;
11✔
577
    }
578

579
    dot = strrchr(path, '.');
92✔
580
    if (dot == NULL || dot[1] == '\0') {
92!
581
        return;
582
    }
583

584
#if defined(_WIN32)
585
    {
586
        char const *slash;
20✔
587
        char const *backslash;
20✔
588

589
        slash = strrchr(path, '/');
40✔
590
        backslash = strrchr(path, '\\');
40✔
591
        if ((slash != NULL && dot < slash) ||
40✔
592
                (backslash != NULL && dot < backslash)) {
5✔
593
            return;
594
        }
595
    }
596
#else
597
    {
598
        char const *slash;
35✔
599

600
        slash = strrchr(path, '/');
52✔
601
        if (slash != NULL && dot < slash) {
52!
602
            return;
603
        }
604
    }
20!
605
#endif
606

607
    if (dot[1] == '\0') {
57✔
608
        return;
609
    }
610

611
    dot += 1;
197✔
612

613
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
368!
614
        buffer[index] = (char)tolower((unsigned char)dot[index]);
276✔
615
    }
126✔
616
    buffer[index] = '\0';
92✔
617
}
50!
618

619

620

621

622

623

624

625

626

627

628

629
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
630
static void
631
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
34✔
632
{
633
    if (buffer == NULL || capacity == 0) {
34!
634
        return;
635
    }
636

637
    buffer[0] = '\0';
34✔
638
    if (source == NULL) {
34✔
639
        return;
640
    }
641

642
    if (!CFStringGetCString(source,
68!
643
                             buffer,
34✔
644
                             (CFIndex)capacity,
34✔
645
                             kCFStringEncodingUTF8)) {
646
        buffer[0] = '\0';
647
    }
648
}
34✔
649
#endif
650

651

652
static void
653
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
110✔
654
                          char const *filename)
655
{
656
    enum { description_length = 128 };
42✔
657
    enum { uttype_length = 128 };
42✔
658
    enum { extension_length = 32 };
42✔
659
    enum { message_length = 768 };
42✔
660
    char message[message_length];
66✔
661
    char type_value[description_length];
66✔
662
    char extension_text[extension_length + 2];
66✔
663
    char uttype[uttype_length];
66✔
664
    char desc_buffer[description_length];
66✔
665
    char extension[extension_length];
66✔
666
    char const *path;
66✔
667
    char const *display_path;
66✔
668
    char const *metadata_path;
66✔
669
    char const *description_text;
66✔
670
    char *mime_string;
66✔
671
    char *description_string;
66✔
672
    size_t offset;
66✔
673
    int gnome_available;
66✔
674
    int gnome_has_dirs;
66✔
675
    int gnome_has_match;
66✔
676
    int suggestions;
66✔
677

678
    message[0] = '\0';
110✔
679
    type_value[0] = '\0';
110✔
680
    extension_text[0] = '\0';
110✔
681
    uttype[0] = '\0';
110✔
682
    desc_buffer[0] = '\0';
110✔
683
    extension[0] = '\0';
110✔
684
    path = NULL;
110✔
685
    display_path = "(stdin)";
110✔
686
    metadata_path = NULL;
110✔
687
    description_text = NULL;
110✔
688
    mime_string = NULL;
110✔
689
    description_string = NULL;
110✔
690
    offset = 0u;
110✔
691
    gnome_available = 0;
110✔
692
    gnome_has_dirs = 0;
110✔
693
    gnome_has_match = 0;
110✔
694
    suggestions = 0;
110✔
695

696
    if (pchunk != NULL && pchunk->source_path != NULL) {
110!
697
        path = pchunk->source_path;
57✔
698
    } else if (filename != NULL) {
59!
699
        path = filename;
700
    }
701

702
    if (path != NULL && strcmp(path, "-") != 0) {
103!
703
        display_path = path;
92✔
704
    }
42✔
705

706
    if (path != NULL && strcmp(path, "-") != 0 &&
102!
707
            strstr(path, "://") == NULL) {
72!
708
        metadata_path = path;
75✔
709
    }
42✔
710

711
    loader_extract_extension(path, extension, sizeof(extension));
109✔
712

713
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
714
    if (metadata_path != NULL) {
61✔
715
        /*
716
         * Collect MIME metadata via file(1) when fork() and friends are
717
         * available.  Windows builds compiled with clang64 lack these
718
         * interfaces, so the thumbnail helpers remain disabled there.
719
         */
720
        mime_string = thumbnailer_guess_content_type(metadata_path);
52✔
721
        description_string = thumbnailer_run_file(metadata_path, NULL);
52✔
722
    }
37✔
723
#else
724
    (void)metadata_path;
30✔
725
#endif
726

727
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
728
#if defined(__clang__)
729
    /*
730
     * Allow use of legacy UTType C APIs when compiling with the
731
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
732
     * so we must intentionally silence the deprecation warnings here.
733
     */
734
#pragma clang diagnostic push
735
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
736
#endif
737
    {
738
        CFStringRef uti_ref;
739
        CFStringRef mime_ref;
740
        CFStringRef ext_ref;
741
        CFStringRef desc_ref;
742
        CFStringRef preferred_mime;
743
        char uti_local[uttype_length];
744
        char desc_local[description_length];
745
        char mime_local[64];
746

747
        uti_ref = NULL;
19✔
748
        mime_ref = NULL;
19✔
749
        ext_ref = NULL;
19✔
750
        desc_ref = NULL;
19✔
751
        preferred_mime = NULL;
19✔
752
        uti_local[0] = '\0';
19✔
753
        desc_local[0] = '\0';
19✔
754
        mime_local[0] = '\0';
19✔
755

756
        if (mime_string != NULL) {
19✔
757
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
34✔
758
                                                 mime_string,
17✔
759
                                                 kCFStringEncodingUTF8);
760
        }
17✔
761
        if (mime_ref != NULL) {
19✔
762
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
17✔
763
                kUTTagClassMIMEType,
17✔
764
                mime_ref,
17✔
765
                NULL);
766
        }
17✔
767
        if (uti_ref == NULL && extension[0] != '\0') {
23!
768
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
769
                                                extension,
770
                                                kCFStringEncodingUTF8);
771
            if (ext_ref != NULL) {
×
772
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
773
                    kUTTagClassFilenameExtension,
774
                    ext_ref,
775
                    NULL);
776
            }
777
        }
778
        if (uti_ref != NULL) {
19✔
779
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
17✔
780
            desc_ref = UTTypeCopyDescription(uti_ref);
17✔
781
            if (desc_ref != NULL) {
17!
782
                loader_copy_cfstring(desc_ref,
34✔
783
                                     desc_local,
17✔
784
                                     sizeof(desc_local));
785
                CFRelease(desc_ref);
17✔
786
                desc_ref = NULL;
17✔
787
            }
17✔
788
            if (mime_string == NULL) {
22!
789
                preferred_mime = UTTypeCopyPreferredTagWithClass(
790
                    uti_ref,
791
                    kUTTagClassMIMEType);
792
                if (preferred_mime != NULL) {
×
793
                    loader_copy_cfstring(preferred_mime,
794
                                         mime_local,
795
                                         sizeof(mime_local));
796
                    CFRelease(preferred_mime);
797
                    preferred_mime = NULL;
798
                }
799
                if (mime_local[0] != '\0') {
×
800
                    mime_string = thumbnailer_strdup(mime_local);
801
                }
802
            }
803
        }
17✔
804
        if (mime_ref != NULL) {
19✔
805
            CFRelease(mime_ref);
17✔
806
        }
17✔
807
        if (ext_ref != NULL) {
24!
808
            CFRelease(ext_ref);
809
        }
810
        if (uti_ref != NULL) {
19✔
811
            CFRelease(uti_ref);
17✔
812
        }
17✔
813
        if (uti_local[0] != '\0') {
19✔
814
            sixel_compat_snprintf(uttype,
34✔
815
                                  sizeof(uttype),
816
                                  "%s",
817
                                  uti_local);
17✔
818
        }
17✔
819
        if (desc_local[0] != '\0') {
19✔
820
            sixel_compat_snprintf(desc_buffer,
34✔
821
                                  sizeof(desc_buffer),
822
                                  "%s",
823
                                  desc_local);
17✔
824
        }
17✔
825
    }
826
#if defined(__clang__)
827
#pragma clang diagnostic pop
828
#endif
829
#endif
830

831
    if (description_string != NULL && description_string[0] != '\0') {
106!
832
        description_text = description_string;
37✔
833
    } else if (desc_buffer[0] != '\0') {
95!
834
        description_text = desc_buffer;
835
    } else {
836
        description_text = "unknown content";
58✔
837
    }
838

839
    sixel_compat_snprintf(type_value,
160✔
840
                          sizeof(type_value),
841
                          "%s",
842
                          description_text);
50✔
843

844
    loader_append_chunk(message,
110✔
845
                        sizeof(message),
846
                        &offset,
847
                        "diagnostic:\n");
848
    loader_append_key_value(message,
160✔
849
                            sizeof(message),
850
                            &offset,
851
                            "file",
852
                            display_path);
50✔
853
    loader_append_key_value(message,
160✔
854
                            sizeof(message),
855
                            &offset,
856
                            "type",
857
                            type_value);
50✔
858

859
    if (mime_string != NULL && mime_string[0] != '\0') {
110!
860
        loader_append_key_value(message,
89✔
861
                                sizeof(message),
862
                                &offset,
863
                                "mime",
864
                                mime_string);
37✔
865
    }
37✔
866

867
    if (uttype[0] != '\0') {
109!
868
        loader_append_key_value(message,
34✔
869
                                sizeof(message),
870
                                &offset,
871
                                "uti",
872
                                uttype);
17✔
873
    }
17✔
874

875
    if (extension[0] != '\0') {
109✔
876
        sixel_compat_snprintf(extension_text,
134✔
877
                              sizeof(extension_text),
878
                              ".%s",
879
                              extension);
42✔
880
        loader_append_key_value(message,
134✔
881
                                sizeof(message),
882
                                &offset,
883
                                "extension",
884
                                extension_text);
42✔
885
    }
42✔
886

887
    loader_append_chunk(message,
110✔
888
                        sizeof(message),
889
                        &offset,
890
                        "  suggestions:\n");
891

892
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
893
    int quicklook_available;
894
    int quicklook_supported;
895

896
    quicklook_available = 0;
20✔
897
    quicklook_supported = 0;
20✔
898

899
    quicklook_available = loader_registry_entry_available("quicklook");
20✔
900
    if (quicklook_available) {
20!
901
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
20✔
902
    }
20✔
903
    if (quicklook_supported) {
26!
904
        loader_append_chunk(message,
905
                            sizeof(message),
906
                            &offset,
907
                            "    - QuickLook rendered a preview during "
908
                            "the probe; try -L quicklook.\n");
909
        suggestions += 1;
910
    }
911
#endif
912

913
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
914
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
62✔
915
    if (gnome_available) {
62!
916
        loader_probe_gnome_thumbnailers(mime_string,
62✔
917
                                        &gnome_has_dirs,
918
                                        &gnome_has_match);
919
        if (gnome_has_dirs && gnome_has_match) {
62!
920
            loader_append_chunk(message,
921
                                sizeof(message),
922
                                &offset,
923
                                "    - GNOME thumbnailer definitions match "
924
                                "this MIME type; try -L gnome-thumbnailer.\n"
925
                                );
926
            suggestions += 1;
927
        }
928
    }
44✔
929
#else
930
    (void)gnome_available;
30✔
931
    (void)gnome_has_dirs;
30✔
932
    (void)gnome_has_match;
30✔
933
#endif
934

935
    if (suggestions == 0) {
92!
936
        loader_append_chunk(message,
110✔
937
                            sizeof(message),
938
                            &offset,
939
                            "    (no thumbnail helper hints)\n");
940
    }
50✔
941

942
    if (suggestions > 0) {
110!
943
        loader_append_chunk(message,
944
                            sizeof(message),
945
                            &offset,
946
                            "  hint       : Enable one of the suggested "
947
                            "loaders with -L.\n");
948
    } else {
949
        loader_append_chunk(message,
110✔
950
                            sizeof(message),
951
                            &offset,
952
                            "  hint       : Convert the file to PNG or "
953
                            "enable optional loaders.\n");
954
    }
955

956
    sixel_helper_set_additional_message(message);
110✔
957

958
    free(mime_string);
110✔
959
    free(description_string);
110✔
960
}
110✔
961

962
SIXELAPI SIXELSTATUS
963
sixel_loader_new(
11,493✔
964
    sixel_loader_t   /* out */ **pploader,
965
    sixel_allocator_t/* in */  *allocator)
966
{
967
    SIXELSTATUS status = SIXEL_FALSE;
11,493✔
968
    sixel_loader_t *loader;
7,049✔
969
    sixel_allocator_t *local_allocator;
7,049✔
970

971
    loader = NULL;
11,493✔
972
    local_allocator = allocator;
11,493✔
973

974
    if (pploader == NULL) {
11,493!
975
        sixel_helper_set_additional_message(
×
976
            "sixel_loader_new: pploader is null.");
977
        status = SIXEL_BAD_ARGUMENT;
×
978
        goto end;
×
979
    }
980

981
    if (local_allocator == NULL) {
11,493!
982
        status = sixel_allocator_new(&local_allocator,
×
983
                                     NULL,
984
                                     NULL,
985
                                     NULL,
986
                                     NULL);
987
        if (SIXEL_FAILED(status)) {
×
988
            goto end;
×
989
        }
990
    } else {
991
        sixel_allocator_ref(local_allocator);
11,493✔
992
    }
993

994
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
11,493✔
995
                                                      sizeof(*loader));
996
    if (loader == NULL) {
11,493!
997
        sixel_helper_set_additional_message(
×
998
            "sixel_loader_new: sixel_allocator_malloc() failed.");
999
        status = SIXEL_BAD_ALLOCATION;
×
1000
        sixel_allocator_unref(local_allocator);
×
1001
        goto end;
×
1002
    }
1003

1004
    loader->ref = 1U;
11,493✔
1005
    loader->fstatic = 0;
11,493✔
1006
    loader->fuse_palette = 0;
11,493✔
1007
    loader->reqcolors = SIXEL_PALETTE_MAX;
11,493✔
1008
    loader->bgcolor[0] = 0;
11,493✔
1009
    loader->bgcolor[1] = 0;
11,493✔
1010
    loader->bgcolor[2] = 0;
11,493✔
1011
    loader->has_bgcolor = 0;
11,493✔
1012
    loader->loop_control = SIXEL_LOOP_AUTO;
11,493✔
1013
    loader->finsecure = 0;
11,493✔
1014
    loader->cancel_flag = NULL;
11,493✔
1015
    loader->context = NULL;
11,493✔
1016
    /*
1017
     * Initialize a private logger. The helper reuses an existing global
1018
     * logger sink when present so loader markers share the timeline with
1019
     * upstream stages without requiring sixel_loader_setopt().
1020
     */
1021
    sixel_logger_init(&loader->logger);
11,493✔
1022
    (void)sixel_logger_prepare_env(&loader->logger);
11,493✔
1023
    loader->loader_order = NULL;
11,493✔
1024
    loader->allocator = local_allocator;
11,493✔
1025
    loader->last_loader_name[0] = '\0';
11,493✔
1026
    loader->last_source_path[0] = '\0';
11,493✔
1027
    loader->last_input_bytes = 0u;
11,493✔
1028
    loader->callback_failed = 0;
11,493✔
1029
    loader->log_loader_finished = 0;
11,493✔
1030
    loader->log_path[0] = '\0';
11,493✔
1031
    loader->log_loader_name[0] = '\0';
11,493✔
1032
    loader->log_input_bytes = 0u;
11,493✔
1033

1034
    *pploader = loader;
11,493✔
1035
    status = SIXEL_OK;
11,493✔
1036

1037
end:
6,025✔
1038
    return status;
14,329✔
1039
}
2,836✔
1040

1041
SIXELAPI void
1042
sixel_loader_ref(
98,028✔
1043
    sixel_loader_t /* in */ *loader)
1044
{
1045
    if (loader == NULL) {
98,028!
1046
        return;
1047
    }
1048

1049
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
98,028✔
1050
}
46,478✔
1051

1052
SIXELAPI void
1053
sixel_loader_unref(
109,521✔
1054
    sixel_loader_t /* in */ *loader)
1055
{
1056
    sixel_allocator_t *allocator;
67,141✔
1057
    unsigned int previous;
67,141✔
1058

1059
    if (loader == NULL) {
109,521!
1060
        return;
1061
    }
1062

1063
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
109,521✔
1064
    if (previous == 1U) {
109,521✔
1065
        allocator = loader->allocator;
11,493✔
1066
        sixel_logger_close(&loader->logger);
11,493✔
1067
        sixel_allocator_free(allocator, loader->loader_order);
11,493✔
1068
        sixel_allocator_free(allocator, loader);
11,493✔
1069
        sixel_allocator_unref(allocator);
11,493✔
1070
    }
5,468✔
1071
}
51,946!
1072

1073
SIXELAPI SIXELSTATUS
1074
sixel_loader_setopt(
86,535✔
1075
    sixel_loader_t /* in */ *loader,
1076
    int            /* in */ option,
1077
    void const     /* in */ *value)
1078
{
1079
    SIXELSTATUS status = SIXEL_FALSE;
86,535✔
1080
    int const *flag;
53,043✔
1081
    unsigned char const *color;
53,043✔
1082
    char const *order;
53,043✔
1083
    char *copy;
53,043✔
1084
    sixel_allocator_t *allocator;
53,043✔
1085

1086
    flag = NULL;
86,535✔
1087
    color = NULL;
86,535✔
1088
    order = NULL;
86,535✔
1089
    copy = NULL;
86,535✔
1090
    allocator = NULL;
86,535✔
1091

1092
    if (loader == NULL) {
86,535!
1093
        sixel_helper_set_additional_message(
×
1094
            "sixel_loader_setopt: loader is null.");
1095
        status = SIXEL_BAD_ARGUMENT;
×
1096
        goto end0;
×
1097
    }
1098

1099
    sixel_loader_ref(loader);
86,535✔
1100

1101
    switch (option) {
86,535!
1102
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
6,025✔
1103
        flag = (int const *)value;
11,493✔
1104
        loader->fstatic = flag != NULL ? *flag : 0;
11,493!
1105
        status = SIXEL_OK;
11,493✔
1106
        break;
11,493✔
1107
    case SIXEL_LOADER_OPTION_USE_PALETTE:
6,025✔
1108
        flag = (int const *)value;
11,493✔
1109
        loader->fuse_palette = flag != NULL ? *flag : 0;
11,493!
1110
        status = SIXEL_OK;
11,493✔
1111
        break;
11,493✔
1112
    case SIXEL_LOADER_OPTION_REQCOLORS:
6,025✔
1113
        flag = (int const *)value;
11,493✔
1114
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
11,493!
1115
        if (loader->reqcolors < 1) {
11,493!
1116
            sixel_helper_set_additional_message(
×
1117
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1118
            status = SIXEL_BAD_ARGUMENT;
×
1119
            goto end;
×
1120
        }
1121
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
11,493!
1122
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1123
        }
1124
        status = SIXEL_OK;
7,280✔
1125
        break;
7,280✔
1126
    case SIXEL_LOADER_OPTION_BGCOLOR:
3,125✔
1127
        if (value == NULL) {
5,859✔
1128
            loader->has_bgcolor = 0;
5,718✔
1129
        } else {
2,643✔
1130
            color = (unsigned char const *)value;
141✔
1131
            loader->bgcolor[0] = color[0];
141✔
1132
            loader->bgcolor[1] = color[1];
141✔
1133
            loader->bgcolor[2] = color[2];
141✔
1134
            loader->has_bgcolor = 1;
141✔
1135
        }
1136
        status = SIXEL_OK;
3,676✔
1137
        break;
3,676✔
1138
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
6,025✔
1139
        flag = (int const *)value;
11,493✔
1140
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
11,493!
1141
        status = SIXEL_OK;
11,493✔
1142
        break;
11,493✔
1143
    case SIXEL_LOADER_OPTION_INSECURE:
6,025✔
1144
        flag = (int const *)value;
11,493✔
1145
        loader->finsecure = flag != NULL ? *flag : 0;
11,493!
1146
        status = SIXEL_OK;
11,493✔
1147
        break;
11,493✔
1148
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
3,125✔
1149
        loader->cancel_flag = (int const *)value;
5,859✔
1150
        status = SIXEL_OK;
5,859✔
1151
        break;
5,859✔
1152
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
3,125✔
1153
        allocator = loader->allocator;
5,859✔
1154
        sixel_allocator_free(allocator, loader->loader_order);
5,859✔
1155
        loader->loader_order = NULL;
5,859✔
1156
        if (value != NULL) {
5,859✔
1157
            order = (char const *)value;
242✔
1158
            copy = loader_strdup(order, allocator);
242✔
1159
            if (copy == NULL) {
242!
1160
                sixel_helper_set_additional_message(
×
1161
                    "sixel_loader_setopt: loader_strdup() failed.");
1162
                status = SIXEL_BAD_ALLOCATION;
×
1163
                goto end;
×
1164
            }
1165
            loader->loader_order = copy;
242✔
1166
        }
122✔
1167
        status = SIXEL_OK;
3,676✔
1168
        break;
3,676✔
1169
    case SIXEL_LOADER_OPTION_CONTEXT:
6,025✔
1170
        loader->context = (void *)value;
11,493✔
1171
        status = SIXEL_OK;
11,493✔
1172
        break;
11,493✔
1173
    default:
1174
        sixel_helper_set_additional_message(
×
1175
            "sixel_loader_setopt: unknown option.");
1176
        status = SIXEL_BAD_ARGUMENT;
×
1177
        goto end;
×
1178
    }
41,010✔
1179

1180
end:
45,525✔
1181
    sixel_loader_unref(loader);
86,535✔
1182

1183
end0:
45,525✔
1184
    return status;
107,751✔
1185
}
21,216✔
1186

1187
SIXELAPI char const *
1188
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
10,728✔
1189
{
1190
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
10,728!
1191
        return NULL;
1192
    }
1193
    return loader->last_loader_name;
10,728✔
1194
}
5,020✔
1195

1196
SIXELAPI char const *
1197
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
10,472✔
1198
{
1199
    if (loader == NULL || loader->last_source_path[0] == '\0') {
10,472!
1200
        return NULL;
161✔
1201
    }
1202
    return loader->last_source_path;
10,216✔
1203
}
4,901✔
1204

1205
SIXELAPI size_t
1206
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
5,364✔
1207
{
1208
    if (loader == NULL) {
5,364!
1209
        return 0u;
1210
    }
1211
    return loader->last_input_bytes;
5,364✔
1212
}
2,510✔
1213

1214
SIXELAPI SIXELSTATUS
1215
sixel_loader_load_file(
11,493✔
1216
    sixel_loader_t         /* in */ *loader,
1217
    char const             /* in */ *filename,
1218
    sixel_load_image_function /* in */ fn_load)
1219
{
1220
    SIXELSTATUS status = SIXEL_FALSE;
11,493✔
1221
    sixel_chunk_t *pchunk;
7,049✔
1222
    sixel_loader_entry_t const **plan;
7,049✔
1223
    sixel_loader_entry_t const *entries;
7,049✔
1224
    size_t entry_count;
7,049✔
1225
    size_t plan_length;
7,049✔
1226
    size_t plan_index;
7,049✔
1227
    unsigned char *bgcolor;
7,049✔
1228
    int reqcolors;
7,049✔
1229
    char const *order_override;
7,049✔
1230
    char const *env_order;
7,049✔
1231
    sixel_loader_callback_state_t callback_state;
7,049✔
1232

1233
    pchunk = NULL;
11,493✔
1234
    plan = NULL;
11,493✔
1235
    entries = NULL;
11,493✔
1236
    entry_count = 0;
11,493✔
1237
    plan_length = 0;
11,493✔
1238
    plan_index = 0;
11,493✔
1239
    bgcolor = NULL;
11,493✔
1240
    reqcolors = 0;
11,493✔
1241
    order_override = NULL;
11,493✔
1242
    env_order = NULL;
11,493✔
1243

1244
    if (loader == NULL) {
11,493!
1245
        sixel_helper_set_additional_message(
×
1246
            "sixel_loader_load_file: loader is null.");
1247
        status = SIXEL_BAD_ARGUMENT;
×
1248
        goto end0;
×
1249
    }
1250

1251
    sixel_loader_ref(loader);
11,493✔
1252

1253
    loader->log_loader_finished = 0;
11,493✔
1254
    loader->log_loader_name[0] = '\0';
11,493✔
1255
    loader->log_input_bytes = 0u;
11,493✔
1256
    loader->log_path[0] = '\0';
11,493✔
1257
    if (filename != NULL) {
11,493✔
1258
        (void)sixel_compat_snprintf(loader->log_path,
16,597✔
1259
                                    sizeof(loader->log_path),
1260
                                    "%s",
1261
                                    filename);
5,356✔
1262
    }
5,356✔
1263
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
12,278✔
1264

1265
    memset(&callback_state, 0, sizeof(callback_state));
12,278✔
1266
    callback_state.loader = loader;
12,278✔
1267
    callback_state.fn = fn_load;
12,278✔
1268
    callback_state.context = loader->context;
12,278✔
1269
    loader->callback_failed = 0;
12,278✔
1270

1271
    entry_count = loader_registry_get_entries(&entries);
12,278✔
1272

1273
    reqcolors = loader->reqcolors;
12,278✔
1274
    if (reqcolors > SIXEL_PALETTE_MAX) {
12,278!
1275
        reqcolors = SIXEL_PALETTE_MAX;
1276
    }
1277

1278
    status = sixel_chunk_new(&pchunk,
11,493✔
1279
                             filename,
5,468✔
1280
                             loader->finsecure,
5,468✔
1281
                             loader->cancel_flag,
5,468✔
1282
                             loader->allocator);
5,468✔
1283
    if (status != SIXEL_OK) {
11,493!
1284
        goto end;
45✔
1285
    }
1286

1287
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
11,448!
1288
        status = SIXEL_OK;
×
1289
        goto end;
×
1290
    }
1291

1292
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
11,369!
1293
        (void)sixel_compat_snprintf(loader->log_path,
16,113✔
1294
                                    sizeof(loader->log_path),
1295
                                    "%s",
1296
                                    pchunk->source_path);
6,886✔
1297
    }
5,128✔
1298

1299
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
11,448!
1300
        status = SIXEL_LOGIC_ERROR;
×
1301
        goto end;
×
1302
    }
1303

1304
    if (loader->has_bgcolor) {
11,448✔
1305
        bgcolor = loader->bgcolor;
141✔
1306
    }
91✔
1307

1308
    status = SIXEL_FALSE;
11,429✔
1309
    order_override = loader->loader_order;
11,429✔
1310
    /*
1311
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
1312
     * a loader order via -L/--loaders or sixel_loader_setopt().
1313
     */
1314
    if (order_override == NULL) {
11,429✔
1315
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
11,206✔
1316
        if (env_order != NULL && env_order[0] != '\0') {
11,206!
1317
            order_override = env_order;
4,201✔
1318
        }
1319
    }
5,322✔
1320

1321
    plan = sixel_allocator_malloc(loader->allocator,
19,296✔
1322
                                  entry_count * sizeof(*plan));
9,651✔
1323
    if (plan == NULL) {
11,448!
1324
        status = SIXEL_BAD_ALLOCATION;
×
1325
        goto end;
×
1326
    }
1327

1328
    plan_length = loader_build_plan(order_override,
16,892✔
1329
                                    entries,
5,444✔
1330
                                    entry_count,
5,444✔
1331
                                    plan,
5,444✔
1332
                                    entry_count);
5,444✔
1333

1334
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
20,463✔
1335
        if (plan[plan_index] == NULL) {
16,064!
1336
            continue;
×
1337
        }
1338
        if (plan[plan_index]->predicate != NULL &&
16,141!
1339
            plan[plan_index]->predicate(pchunk) == 0) {
5,845✔
1340
            continue;
4,455✔
1341
        }
1342
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
11,609!
1343
        if (plan[plan_index]->name != NULL) {
11,609!
1344
            (void)sixel_compat_snprintf(loader->log_loader_name,
17,179✔
1345
                                        sizeof(loader->log_loader_name),
1346
                                        "%s",
1347
                                        plan[plan_index]->name);
7,388✔
1348
        } else {
5,570✔
1349
            loader->log_loader_name[0] = '\0';
×
1350
        }
1351
        loader_trace_try(plan[plan_index]->name);
11,609✔
1352
        status = plan[plan_index]->backend(pchunk,
17,179✔
1353
                                           loader->fstatic,
5,570✔
1354
                                           loader->fuse_palette,
5,570✔
1355
                                           reqcolors,
5,570✔
1356
                                           bgcolor,
5,570✔
1357
                                           loader->loop_control,
5,570✔
1358
                                           loader_callback_trampoline,
1359
                                           &callback_state);
1360
        loader_trace_result(plan[plan_index]->name, status);
11,609✔
1361
        if (SIXEL_SUCCEEDED(status)) {
11,609✔
1362
            break;
7,126✔
1363
        }
1364
    }
214✔
1365

1366
    if (SIXEL_FAILED(status)) {
11,448✔
1367
        if (!loader->callback_failed &&
242!
1368
                plan_length > 0u &&
110!
1369
                plan_index >= plan_length &&
110!
1370
                pchunk != NULL) {
110!
1371
            status = SIXEL_LOADER_FAILED;
110✔
1372
            loader_publish_diagnostic(pchunk, filename);
110✔
1373
        }
50✔
1374
        goto end;
198✔
1375
    }
1376

1377
    if (plan_index < plan_length &&
16,016!
1378
            plan[plan_index] != NULL &&
11,250!
1379
            plan[plan_index]->name != NULL) {
11,250!
1380
        (void)sixel_compat_snprintf(loader->last_loader_name,
16,606✔
1381
                                    sizeof(loader->last_loader_name),
1382
                                    "%s",
1383
                                    plan[plan_index]->name);
7,126✔
1384
    } else {
5,356✔
1385
        loader->last_loader_name[0] = '\0';
×
1386
    }
1387
    loader->last_input_bytes = pchunk->size;
11,250✔
1388
    if (pchunk->source_path != NULL) {
16,606✔
1389
        size_t path_len;
6,624✔
1390

1391
        path_len = strlen(pchunk->source_path);
10,805✔
1392
        if (path_len >= sizeof(loader->last_source_path)) {
10,805!
1393
            path_len = sizeof(loader->last_source_path) - 1u;
1394
        }
1395
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
10,805✔
1396
        loader->last_source_path[path_len] = '\0';
10,805✔
1397
    } else {
5,048✔
1398
        loader->last_source_path[0] = '\0';
445✔
1399
    }
1400

1401
end:
6,013✔
1402
    if (plan != NULL) {
7,292✔
1403
        sixel_allocator_free(loader->allocator, plan);
11,448✔
1404
        plan = NULL;
11,448✔
1405
    }
5,444✔
1406
    sixel_chunk_destroy(pchunk);
11,493✔
1407
    sixel_loader_unref(loader);
11,493✔
1408

1409
end0:
6,025✔
1410
    return status;
14,329✔
1411
}
2,836✔
1412

1413
/* load image from file */
1414

1415
SIXELAPI SIXELSTATUS
1416
sixel_helper_load_image_file(
×
1417
    char const                /* in */     *filename,     /* source file name */
1418
    int                       /* in */     fstatic,       /* whether to */
1419
                                                             /* extract a */
1420
                                                             /* static image */
1421
                                                             /* from an */
1422
                                                             /* animated gif */
1423
    int                       /* in */     fuse_palette,  /* whether to */
1424
                                                             /* use a */
1425
                                                             /* paletted */
1426
                                                             /* image; set */
1427
                                                             /* non-zero to */
1428
                                                             /* request one */
1429
    int                       /* in */     reqcolors,     /* requested */
1430
                                                             /* number of */
1431
                                                             /* colors; */
1432
                                                             /* should be */
1433
                                                             /* equal to or */
1434
                                                             /* less than */
1435
                                                             /* SIXEL_ */
1436
                                                             /* PALETTE_ */
1437
                                                             /* MAX */
1438
    unsigned char             /* in */     *bgcolor,      /* background */
1439
                                                             /* color, may */
1440
                                                             /* be NULL */
1441
    int                       /* in */     loop_control,  /* one of enum */
1442
                                                             /* loopControl */
1443
    sixel_load_image_function /* in */     fn_load,       /* callback */
1444
    int                       /* in */     finsecure,     /* true if do */
1445
                                                             /* not verify */
1446
                                                             /* SSL */
1447
    int const                 /* in */     *cancel_flag,  /* cancel flag, */
1448
                                                             /* may be */
1449
                                                             /* NULL */
1450
    void                      /* in/out */ *context,      /* private data */
1451
                                                             /* passed to */
1452
                                                             /* callback */
1453
                                                             /* function, */
1454
                                                             /* may be */
1455
                                                             /* NULL */
1456
    sixel_allocator_t         /* in */     *allocator     /* allocator */
1457
                                                             /* object, */
1458
                                                             /* may be */
1459
                                                             /* NULL */
1460
)
1461
{
1462
    SIXELSTATUS status = SIXEL_FALSE;
×
1463
    sixel_loader_t *loader;
1464

1465
    loader = NULL;
×
1466

1467
    status = sixel_loader_new(&loader, allocator);
×
1468
    if (SIXEL_FAILED(status)) {
×
1469
        goto end;
×
1470
    }
1471

1472
    status = sixel_loader_setopt(loader,
×
1473
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1474
                                 &fstatic);
1475
    if (SIXEL_FAILED(status)) {
×
1476
        goto end;
×
1477
    }
1478

1479
    status = sixel_loader_setopt(loader,
×
1480
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1481
                                 &fuse_palette);
1482
    if (SIXEL_FAILED(status)) {
×
1483
        goto end;
×
1484
    }
1485

1486
    status = sixel_loader_setopt(loader,
×
1487
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1488
                                 &reqcolors);
1489
    if (SIXEL_FAILED(status)) {
×
1490
        goto end;
×
1491
    }
1492

1493
    status = sixel_loader_setopt(loader,
×
1494
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1495
                                 bgcolor);
1496
    if (SIXEL_FAILED(status)) {
×
1497
        goto end;
×
1498
    }
1499

1500
    status = sixel_loader_setopt(loader,
×
1501
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1502
                                 &loop_control);
1503
    if (SIXEL_FAILED(status)) {
×
1504
        goto end;
×
1505
    }
1506

1507
    status = sixel_loader_setopt(loader,
×
1508
                                 SIXEL_LOADER_OPTION_INSECURE,
1509
                                 &finsecure);
1510
    if (SIXEL_FAILED(status)) {
×
1511
        goto end;
×
1512
    }
1513

1514
    status = sixel_loader_setopt(loader,
×
1515
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1516
                                 cancel_flag);
1517
    if (SIXEL_FAILED(status)) {
×
1518
        goto end;
×
1519
    }
1520

1521
    status = sixel_loader_setopt(loader,
×
1522
                                 SIXEL_LOADER_OPTION_CONTEXT,
1523
                                 context);
1524
    if (SIXEL_FAILED(status)) {
×
1525
        goto end;
×
1526
    }
1527

1528
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1529

1530
end:
1531
    sixel_loader_unref(loader);
×
1532

1533
    return status;
×
1534
}
1535

1536

1537
SIXELAPI size_t
1538
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
144✔
1539
{
1540
    sixel_loader_entry_t const *entries;
88✔
1541
    size_t entry_count;
88✔
1542
    size_t limit;
88✔
1543
    size_t index;
88✔
1544

1545
    entries = NULL;
144✔
1546
    entry_count = loader_registry_get_entries(&entries);
144✔
1547

1548
    if (names != NULL && max_names > 0) {
144!
1549
        limit = entry_count;
72✔
1550
        if (limit > max_names) {
72!
1551
            limit = max_names;
1552
        }
1553
        for (index = 0; index < limit; ++index) {
316✔
1554
            names[index] = entries[index].name;
244✔
1555
        }
164✔
1556
    }
32✔
1557

1558
    return entry_count;
176✔
1559
}
32✔
1560

1561

1562
/* emacs Local Variables:      */
1563
/* emacs mode: c               */
1564
/* emacs tab-width: 4          */
1565
/* emacs indent-tabs-mode: nil */
1566
/* emacs c-basic-offset: 4     */
1567
/* emacs End:                  */
1568
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1569
/* 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