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

saitoha / libsixel / 21662119494

04 Feb 2026 07:09AM UTC coverage: 77.849% (+0.3%) from 77.565%
21662119494

push

github

saitoha
tests: add small test files to EXTRA_DIST

24278 of 51573 branches covered (47.08%)

38740 of 49763 relevant lines covered (77.85%)

12704508.89 hits per line

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

86.75
/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-quicklook.h"
105
#include "loader-registry.h"
106
#include "loader-wic.h"
107
#include "compat_stub.h"
108
#include "frame.h"
109
#include "chunk.h"
110
#include "allocator.h"
111
#include "encoder.h"
112
#include "logger.h"
113
#include "sixel_atomic.h"
114

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

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

150

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

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

161
    if (text == NULL) {
189!
162
        return NULL;
163
    }
164

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

171
#if HAVE_STRCPY_S
172
    (void)strcpy_s(copy, (rsize_t)length, text);
48✔
173
#else
174
    memcpy(copy, text, length);
141✔
175
#endif
176

177
    return copy;
189✔
178
}
105✔
179

180

181

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

200
    logger = NULL;
16,933✔
201
    if (loader != NULL) {
16,933!
202
        logger = &loader->logger;
16,933✔
203
    }
8,699✔
204
    if (logger == NULL || logger->file == NULL || !logger->active) {
16,933!
205
        return;
16,933✔
206
    }
207

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

247
    sixel_logger_logf(logger,
×
248
                      "worker",
249
                      "loader",
250
                      event,
251
                      -1,
252
                      -1,
253
                      0,
254
                      0,
255
                      0,
256
                      0,
257
                      "%s",
258
                      message);
259
}
8,699!
260

261
static SIXELSTATUS
262
loader_callback_trampoline(sixel_frame_t *frame, void *data)
8,887✔
263
{
264
    sixel_loader_callback_state_t *state;
6,931✔
265
    SIXELSTATUS status;
6,931✔
266
    sixel_loader_t *loader;
6,931✔
267

268
    state = (sixel_loader_callback_state_t *)data;
8,887✔
269
    loader = NULL;
8,887✔
270
    if (state == NULL || state->fn == NULL) {
8,887!
271
        return SIXEL_BAD_ARGUMENT;
272
    }
273

274
    loader = state->loader;
8,887✔
275
    if (loader != NULL && loader->log_loader_finished == 0) {
8,887!
276
        loader_log_stage(loader,
12,708✔
277
                         "finish",
278
                         "path=%s loader=%s bytes=%zu",
279
                         loader->log_path,
8,395✔
280
                         loader->log_loader_name,
8,395✔
281
                         loader->log_input_bytes);
4,313✔
282
        loader->log_loader_finished = 1;
8,395✔
283
    }
4,313✔
284

285
    status = state->fn(frame, state->context);
9,503✔
286
    if (SIXEL_FAILED(status) && state->loader != NULL) {
9,503!
287
        state->loader->callback_failed = 1;
70✔
288
    }
35✔
289

290
    return status;
4,597✔
291
}
4,567✔
292

293
static int
294
loader_plan_contains(sixel_loader_entry_t const **plan,
19,779✔
295
                     size_t plan_length,
296
                     sixel_loader_entry_t const *entry)
297
{
298
    size_t index;
10,655✔
299

300
    for (index = 0; index < plan_length; ++index) {
65,590!
301
        if (plan[index] == entry) {
39,405!
302
            return 1;
303
        }
304
    }
37,067✔
305

306
    return 0;
19,779✔
307
}
19,707✔
308

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

318
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
1,812!
319
        left = (unsigned char)token[index];
1,623✔
320
        right = (unsigned char)name[index];
1,623✔
321
        if (tolower(left) != tolower(right)) {
1,623!
322
            return 0;
174✔
323
        }
324
    }
861✔
325

326
    if (index != token_length || name[index] != '\0') {
189!
327
        return 0;
×
328
    }
329

330
    return 1;
105✔
331
}
279✔
332

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

341
    for (index = 0; index < entry_count; ++index) {
363!
342
        if (loader_token_matches(token,
642✔
343
                                 token_length,
279✔
344
                                 entries[index].name)) {
363!
345
            return &entries[index];
105✔
346
        }
347
    }
174✔
348

349
    return NULL;
350
}
105✔
351

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

392
    plan_length = 0;
8,505✔
393
    index = 0;
8,505✔
394
    cursor = order;
8,505✔
395
    token_start = order;
8,505✔
396
    token_end = order;
8,505✔
397
    order_end = NULL;
8,505✔
398
    token_length = 0;
8,505✔
399
    entry = NULL;
8,505✔
400
    limit = plan_capacity;
8,505✔
401
    allow_fallback = 1;
8,505✔
402

403
    if (order != NULL) {
8,505✔
404
        order_end = order + strlen(order);
189✔
405
        while (order_end > order &&
189!
406
               isspace((unsigned char)order_end[-1])) {
189!
407
            --order_end;
×
408
        }
409
        if (order_end > order && order_end[-1] == '!') {
189!
410
            allow_fallback = 0;
189✔
411
            --order_end;
189✔
412
            while (order_end > order &&
189!
413
                   isspace((unsigned char)order_end[-1])) {
189!
414
                --order_end;
×
415
            }
416
        }
105✔
417
    }
105✔
418

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

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

477
    if (allow_fallback) {
8,486✔
478
        for (index = 0; index < entry_count && plan_length < limit; ++index) {
40,278✔
479
            entry = &entries[index];
31,962✔
480
            if (!entry->default_enabled) {
31,962✔
481
                continue;
5,966✔
482
            }
483
            if (!loader_plan_contains(plan, plan_length, entry)) {
25,996!
484
                plan[plan_length] = entry;
25,996✔
485
                ++plan_length;
25,996✔
486
            }
19,602✔
487
        }
19,602✔
488
    }
4,260✔
489

490
    return plan_length;
11,033✔
491
}
2,528✔
492

493
static void
494
loader_append_chunk(char *dest,
833✔
495
                    size_t capacity,
496
                    size_t *offset,
497
                    char const *chunk)
498
{
499
    size_t available;
624✔
500
    size_t length;
624✔
501

502
    if (dest == NULL || offset == NULL || chunk == NULL) {
833!
503
        return;
504
    }
505

506
    if (*offset >= capacity) {
833!
507
        return;
508
    }
509

510
    available = capacity - *offset;
833✔
511
    if (available == 0) {
833✔
512
        return;
513
    }
514

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

523
    if (length > 0) {
833!
524
        memcpy(dest + *offset, chunk, length);
833✔
525
        *offset += length;
833✔
526
    }
415✔
527

528
    if (*offset < capacity) {
833!
529
        dest[*offset] = '\0';
833✔
530
    } else {
415✔
531
        dest[capacity - 1u] = '\0';
×
532
    }
533
}
415!
534

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

545
    if (value == NULL || value[0] == '\0') {
393!
546
        return;
×
547
    }
548

549
    written = sixel_compat_snprintf(line,
600✔
550
                                    sizeof(line),
551
                                    "  %-10s: %s\n",
552
                                    label,
207✔
553
                                    value);
207✔
554
    if (written < 0) {
393!
555
        return;
556
    }
557

558
    if ((size_t)written >= sizeof(line)) {
393✔
559
        line[sizeof(line) - 1u] = '\0';
10✔
560
    }
8✔
561

562
    loader_append_chunk(dest, capacity, offset, line);
393✔
563
}
207!
564

565
static void
566
loader_extract_extension(char const *path, char *buffer, size_t capacity)
110✔
567
{
568
    char const *dot;
84✔
569
    size_t index;
84✔
570

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

575
    buffer[0] = '\0';
110✔
576

577
    if (path == NULL) {
110✔
578
        return;
7✔
579
    }
580

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

586
#if defined(_WIN32)
587
    {
588
        char const *slash;
26✔
589
        char const *backslash;
26✔
590

591
        slash = strrchr(path, '/');
34✔
592
        backslash = strrchr(path, '\\');
34✔
593
        if ((slash != NULL && dot < slash) ||
34✔
594
                (backslash != NULL && dot < backslash)) {
2✔
595
            return;
596
        }
597
    }
598
#else
599
    {
600
        char const *slash;
47✔
601

602
        slash = strrchr(path, '/');
62✔
603
        if (slash != NULL && dot < slash) {
62!
604
            return;
605
        }
606
    }
28!
607
#endif
608

609
    if (dot[1] == '\0') {
51✔
610
        return;
611
    }
612

613
    dot += 1;
231✔
614

615
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
384!
616
        buffer[index] = (char)tolower((unsigned char)dot[index]);
288✔
617
    }
135✔
618
    buffer[index] = '\0';
96✔
619
}
52!
620

621

622

623

624

625

626

627

628

629

630

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

639
    buffer[0] = '\0';
30✔
640
    if (source == NULL) {
30✔
641
        return;
642
    }
643

644
    if (!CFStringGetCString(source,
60!
645
                             buffer,
30✔
646
                             (CFIndex)capacity,
30✔
647
                             kCFStringEncodingUTF8)) {
648
        buffer[0] = '\0';
649
    }
650
}
30✔
651
#endif
652

653

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

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

698
    if (pchunk != NULL && pchunk->source_path != NULL) {
110!
699
        path = pchunk->source_path;
51✔
700
    } else if (filename != NULL) {
58!
701
        path = filename;
702
    }
703

704
    if (path != NULL && strcmp(path, "-") != 0) {
103!
705
        display_path = path;
96✔
706
    }
45✔
707

708
    if (path != NULL && strcmp(path, "-") != 0 &&
102!
709
            strstr(path, "://") == NULL) {
70!
710
        metadata_path = path;
73✔
711
    }
45✔
712

713
    loader_extract_extension(path, extension, sizeof(extension));
109✔
714

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

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

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

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

833
    if (description_string != NULL && description_string[0] != '\0') {
106!
834
        description_text = description_string;
43✔
835
    } else if (desc_buffer[0] != '\0') {
91!
836
        description_text = desc_buffer;
837
    } else {
838
        description_text = "unknown content";
48✔
839
    }
840

841
    sixel_compat_snprintf(type_value,
162✔
842
                          sizeof(type_value),
843
                          "%s",
844
                          description_text);
52✔
845

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

861
    if (mime_string != NULL && mime_string[0] != '\0') {
110!
862
        loader_append_key_value(message,
105✔
863
                                sizeof(message),
864
                                &offset,
865
                                "mime",
866
                                mime_string);
43✔
867
    }
43✔
868

869
    if (uttype[0] != '\0') {
109!
870
        loader_append_key_value(message,
30✔
871
                                sizeof(message),
872
                                &offset,
873
                                "uti",
874
                                uttype);
15✔
875
    }
15✔
876

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

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

894
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
895
    int quicklook_available;
896
    int quicklook_supported;
897

898
    quicklook_available = 0;
18✔
899
    quicklook_supported = 0;
18✔
900

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

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

937
    if (suggestions == 0) {
88!
938
        loader_append_chunk(message,
110✔
939
                            sizeof(message),
940
                            &offset,
941
                            "    (no thumbnail helper hints)\n");
942
    }
52✔
943

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

958
    sixel_helper_set_additional_message(message);
110✔
959

960
    free(mime_string);
110✔
961
    free(description_string);
110✔
962
}
110✔
963

964
SIXELAPI SIXELSTATUS
965
sixel_loader_new(
8,538✔
966
    sixel_loader_t   /* out */ **pploader,
967
    sixel_allocator_t/* in */  *allocator)
968
{
969
    SIXELSTATUS status = SIXEL_FALSE;
8,538✔
970
    sixel_loader_t *loader;
6,656✔
971
    sixel_allocator_t *local_allocator;
6,656✔
972

973
    loader = NULL;
8,538✔
974
    local_allocator = allocator;
8,538✔
975

976
    if (pploader == NULL) {
8,538!
977
        sixel_helper_set_additional_message(
×
978
            "sixel_loader_new: pploader is null.");
979
        status = SIXEL_BAD_ARGUMENT;
×
980
        goto end;
×
981
    }
982

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

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

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

1036
    *pploader = loader;
8,538✔
1037
    status = SIXEL_OK;
8,538✔
1038

1039
end:
4,152✔
1040
    return status;
11,078✔
1041
}
2,540✔
1042

1043
SIXELAPI void
1044
sixel_loader_ref(
72,864✔
1045
    sixel_loader_t /* in */ *loader)
1046
{
1047
    if (loader == NULL) {
72,864!
1048
        return;
1049
    }
1050

1051
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
72,864✔
1052
}
37,584✔
1053

1054
SIXELAPI void
1055
sixel_loader_unref(
81,402✔
1056
    sixel_loader_t /* in */ *loader)
1057
{
1058
    sixel_allocator_t *allocator;
63,508✔
1059
    unsigned int previous;
63,508✔
1060

1061
    if (loader == NULL) {
81,402!
1062
        return;
1063
    }
1064

1065
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
81,402✔
1066
    if (previous == 1U) {
81,402✔
1067
        allocator = loader->allocator;
8,538✔
1068
        sixel_logger_close(&loader->logger);
8,538✔
1069
        sixel_allocator_free(allocator, loader->loader_order);
8,538✔
1070
        sixel_allocator_free(allocator, loader);
8,538✔
1071
        sixel_allocator_unref(allocator);
8,538✔
1072
    }
4,386✔
1073
}
41,970!
1074

1075
SIXELAPI SIXELSTATUS
1076
sixel_loader_setopt(
64,326✔
1077
    sixel_loader_t /* in */ *loader,
1078
    int            /* in */ option,
1079
    void const     /* in */ *value)
1080
{
1081
    SIXELSTATUS status = SIXEL_FALSE;
64,326✔
1082
    int const *flag;
50,196✔
1083
    unsigned char const *color;
50,196✔
1084
    char const *order;
50,196✔
1085
    char *copy;
50,196✔
1086
    sixel_allocator_t *allocator;
50,196✔
1087

1088
    flag = NULL;
64,326✔
1089
    color = NULL;
64,326✔
1090
    order = NULL;
64,326✔
1091
    copy = NULL;
64,326✔
1092
    allocator = NULL;
64,326✔
1093

1094
    if (loader == NULL) {
64,326!
1095
        sixel_helper_set_additional_message(
×
1096
            "sixel_loader_setopt: loader is null.");
1097
        status = SIXEL_BAD_ARGUMENT;
×
1098
        goto end0;
×
1099
    }
1100

1101
    sixel_loader_ref(loader);
64,326✔
1102

1103
    switch (option) {
64,326!
1104
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
4,152✔
1105
        flag = (int const *)value;
8,538✔
1106
        loader->fstatic = flag != NULL ? *flag : 0;
8,538!
1107
        status = SIXEL_OK;
8,538✔
1108
        break;
8,538✔
1109
    case SIXEL_LOADER_OPTION_USE_PALETTE:
4,152✔
1110
        flag = (int const *)value;
8,538✔
1111
        loader->fuse_palette = flag != NULL ? *flag : 0;
8,538!
1112
        status = SIXEL_OK;
8,538✔
1113
        break;
8,538✔
1114
    case SIXEL_LOADER_OPTION_REQCOLORS:
4,152✔
1115
        flag = (int const *)value;
8,538✔
1116
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
8,538!
1117
        if (loader->reqcolors < 1) {
8,538!
1118
            sixel_helper_set_additional_message(
×
1119
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1120
            status = SIXEL_BAD_ARGUMENT;
×
1121
            goto end;
×
1122
        }
1123
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
8,538!
1124
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1125
        }
1126
        status = SIXEL_OK;
4,422✔
1127
        break;
4,422✔
1128
    case SIXEL_LOADER_OPTION_BGCOLOR:
2,072✔
1129
        if (value == NULL) {
4,366✔
1130
            loader->has_bgcolor = 0;
4,236✔
1131
        } else {
2,204✔
1132
            color = (unsigned char const *)value;
130✔
1133
            loader->bgcolor[0] = color[0];
130✔
1134
            loader->bgcolor[1] = color[1];
130✔
1135
            loader->bgcolor[2] = color[2];
130✔
1136
            loader->has_bgcolor = 1;
130✔
1137
        }
1138
        status = SIXEL_OK;
2,294✔
1139
        break;
2,294✔
1140
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
4,152✔
1141
        flag = (int const *)value;
8,538✔
1142
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
8,538!
1143
        status = SIXEL_OK;
8,538✔
1144
        break;
8,538✔
1145
    case SIXEL_LOADER_OPTION_INSECURE:
4,152✔
1146
        flag = (int const *)value;
8,538✔
1147
        loader->finsecure = flag != NULL ? *flag : 0;
8,538!
1148
        status = SIXEL_OK;
8,538✔
1149
        break;
8,538✔
1150
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
2,072✔
1151
        loader->cancel_flag = (int const *)value;
4,366✔
1152
        status = SIXEL_OK;
4,366✔
1153
        break;
4,366✔
1154
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
2,072✔
1155
        allocator = loader->allocator;
4,366✔
1156
        sixel_allocator_free(allocator, loader->loader_order);
4,366✔
1157
        loader->loader_order = NULL;
4,366✔
1158
        if (value != NULL) {
4,366✔
1159
            order = (char const *)value;
189✔
1160
            copy = loader_strdup(order, allocator);
189✔
1161
            if (copy == NULL) {
189!
1162
                sixel_helper_set_additional_message(
×
1163
                    "sixel_loader_setopt: loader_strdup() failed.");
1164
                status = SIXEL_BAD_ALLOCATION;
×
1165
                goto end;
×
1166
            }
1167
            loader->loader_order = copy;
189✔
1168
        }
105✔
1169
        status = SIXEL_OK;
2,294✔
1170
        break;
2,294✔
1171
    case SIXEL_LOADER_OPTION_CONTEXT:
4,152✔
1172
        loader->context = (void *)value;
8,538✔
1173
        status = SIXEL_OK;
8,538✔
1174
        break;
8,538✔
1175
    default:
1176
        sixel_helper_set_additional_message(
×
1177
            "sixel_loader_setopt: unknown option.");
1178
        status = SIXEL_BAD_ARGUMENT;
×
1179
        goto end;
×
1180
    }
33,198✔
1181

1182
end:
31,128✔
1183
    sixel_loader_unref(loader);
64,326✔
1184

1185
end0:
31,128✔
1186
    return status;
83,610✔
1187
}
19,284✔
1188

1189
SIXELAPI char const *
1190
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
7,934✔
1191
{
1192
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
7,934!
1193
        return NULL;
1194
    }
1195
    return loader->last_loader_name;
7,934✔
1196
}
4,184✔
1197

1198
SIXELAPI char const *
1199
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
7,720✔
1200
{
1201
    if (loader == NULL || loader->last_source_path[0] == '\0') {
7,720!
1202
        return NULL;
112✔
1203
    }
1204
    return loader->last_source_path;
7,506✔
1205
}
4,072✔
1206

1207
SIXELAPI size_t
1208
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
3,967✔
1209
{
1210
    if (loader == NULL) {
3,967!
1211
        return 0u;
1212
    }
1213
    return loader->last_input_bytes;
3,967✔
1214
}
2,092✔
1215

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

1235
    pchunk = NULL;
8,538✔
1236
    plan = NULL;
8,538✔
1237
    entries = NULL;
8,538✔
1238
    entry_count = 0;
8,538✔
1239
    plan_length = 0;
8,538✔
1240
    plan_index = 0;
8,538✔
1241
    bgcolor = NULL;
8,538✔
1242
    reqcolors = 0;
8,538✔
1243
    order_override = NULL;
8,538✔
1244
    env_order = NULL;
8,538✔
1245

1246
    if (loader == NULL) {
8,538!
1247
        sixel_helper_set_additional_message(
×
1248
            "sixel_loader_load_file: loader is null.");
1249
        status = SIXEL_BAD_ARGUMENT;
×
1250
        goto end0;
×
1251
    }
1252

1253
    sixel_loader_ref(loader);
8,538✔
1254

1255
    loader->log_loader_finished = 0;
8,538✔
1256
    loader->log_loader_name[0] = '\0';
8,538✔
1257
    loader->log_input_bytes = 0u;
8,538✔
1258
    loader->log_path[0] = '\0';
8,538✔
1259
    if (filename != NULL) {
8,538✔
1260
        (void)sixel_compat_snprintf(loader->log_path,
12,609✔
1261
                                    sizeof(loader->log_path),
1262
                                    "%s",
1263
                                    filename);
4,281✔
1264
    }
4,281✔
1265
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
9,174✔
1266

1267
    memset(&callback_state, 0, sizeof(callback_state));
9,174✔
1268
    callback_state.loader = loader;
9,174✔
1269
    callback_state.fn = fn_load;
9,174✔
1270
    callback_state.context = loader->context;
9,174✔
1271
    loader->callback_failed = 0;
9,174✔
1272

1273
    entry_count = loader_registry_get_entries(&entries);
9,174✔
1274

1275
    reqcolors = loader->reqcolors;
9,174✔
1276
    if (reqcolors > SIXEL_PALETTE_MAX) {
9,174!
1277
        reqcolors = SIXEL_PALETTE_MAX;
1278
    }
1279

1280
    status = sixel_chunk_new(&pchunk,
8,538✔
1281
                             filename,
4,386✔
1282
                             loader->finsecure,
4,386✔
1283
                             loader->cancel_flag,
4,386✔
1284
                             loader->allocator);
4,386✔
1285
    if (status != SIXEL_OK) {
8,538!
1286
        goto end;
33✔
1287
    }
1288

1289
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
8,505!
1290
        status = SIXEL_OK;
×
1291
        goto end;
×
1292
    }
1293

1294
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
8,488!
1295
        (void)sixel_compat_snprintf(loader->log_path,
12,523✔
1296
                                    sizeof(loader->log_path),
1297
                                    "%s",
1298
                                    pchunk->source_path);
4,282✔
1299
    }
4,246✔
1300

1301
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
8,505!
1302
        status = SIXEL_LOGIC_ERROR;
×
1303
        goto end;
×
1304
    }
1305

1306
    if (loader->has_bgcolor) {
8,505✔
1307
        bgcolor = loader->bgcolor;
130✔
1308
    }
90✔
1309

1310
    status = SIXEL_FALSE;
8,486✔
1311
    order_override = loader->loader_order;
8,486✔
1312
    /*
1313
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
1314
     * a loader order via -L/--loaders or sixel_loader_setopt().
1315
     */
1316
    if (order_override == NULL) {
8,486✔
1317
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
8,316✔
1318
        if (env_order != NULL && env_order[0] != '\0') {
8,316!
1319
            order_override = env_order;
4,104✔
1320
        }
1321
    }
4,260✔
1322

1323
    plan = sixel_allocator_malloc(loader->allocator,
15,220✔
1324
                                  entry_count * sizeof(*plan));
6,751✔
1325
    if (plan == NULL) {
8,505!
1326
        status = SIXEL_BAD_ALLOCATION;
×
1327
        goto end;
×
1328
    }
1329

1330
    plan_length = loader_build_plan(order_override,
12,870✔
1331
                                    entries,
4,365✔
1332
                                    entry_count,
4,365✔
1333
                                    plan,
4,365✔
1334
                                    entry_count);
4,365✔
1335

1336
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
16,293✔
1337
        if (plan[plan_index] == NULL) {
12,011!
1338
            continue;
×
1339
        }
1340
        if (plan[plan_index]->predicate != NULL &&
12,067!
1341
            plan[plan_index]->predicate(pchunk) == 0) {
4,669✔
1342
            continue;
3,339✔
1343
        }
1344
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
8,672!
1345
        if (plan[plan_index]->name != NULL) {
8,672!
1346
            (void)sixel_compat_snprintf(loader->log_loader_name,
13,172✔
1347
                                        sizeof(loader->log_loader_name),
1348
                                        "%s",
1349
                                        plan[plan_index]->name);
4,542✔
1350
        } else {
4,500✔
1351
            loader->log_loader_name[0] = '\0';
×
1352
        }
1353
        loader_trace_try(plan[plan_index]->name);
8,672✔
1354
        status = plan[plan_index]->backend(pchunk,
13,172✔
1355
                                           loader->fstatic,
4,500✔
1356
                                           loader->fuse_palette,
4,500✔
1357
                                           reqcolors,
4,500✔
1358
                                           bgcolor,
4,500✔
1359
                                           loader->loop_control,
4,500✔
1360
                                           loader_callback_trampoline,
1361
                                           &callback_state);
1362
        loader_trace_result(plan[plan_index]->name, status);
8,672✔
1363
        if (SIXEL_SUCCEEDED(status)) {
8,672✔
1364
            break;
4,310✔
1365
        }
1366
    }
220✔
1367

1368
    if (SIXEL_FAILED(status)) {
8,505✔
1369
        if (!loader->callback_failed &&
228!
1370
                plan_length > 0u &&
110!
1371
                plan_index >= plan_length &&
110!
1372
                pchunk != NULL) {
110!
1373
            status = SIXEL_LOADER_FAILED;
110✔
1374
            loader_publish_diagnostic(pchunk, filename);
110✔
1375
        }
52✔
1376
        goto end;
178✔
1377
    }
1378

1379
    if (plan_index < plan_length &&
12,597!
1380
            plan[plan_index] != NULL &&
8,327!
1381
            plan[plan_index]->name != NULL) {
8,327!
1382
        (void)sixel_compat_snprintf(loader->last_loader_name,
12,607✔
1383
                                    sizeof(loader->last_loader_name),
1384
                                    "%s",
1385
                                    plan[plan_index]->name);
4,310✔
1386
    } else {
4,280✔
1387
        loader->last_loader_name[0] = '\0';
×
1388
    }
1389
    loader->last_input_bytes = pchunk->size;
8,327✔
1390
    if (pchunk->source_path != NULL) {
12,607✔
1391
        size_t path_len;
6,328✔
1392

1393
        path_len = strlen(pchunk->source_path);
8,113✔
1394
        if (path_len >= sizeof(loader->last_source_path)) {
8,113!
1395
            path_len = sizeof(loader->last_source_path) - 1u;
1396
        }
1397
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
8,113✔
1398
        loader->last_source_path[path_len] = '\0';
8,113✔
1399
    } else {
4,168✔
1400
        loader->last_source_path[0] = '\0';
214✔
1401
    }
1402

1403
end:
4,140✔
1404
    if (plan != NULL) {
4,434✔
1405
        sixel_allocator_free(loader->allocator, plan);
8,505✔
1406
        plan = NULL;
8,505✔
1407
    }
4,365✔
1408
    sixel_chunk_destroy(pchunk);
8,538✔
1409
    sixel_loader_unref(loader);
8,538✔
1410

1411
end0:
4,152✔
1412
    return status;
11,078✔
1413
}
2,540✔
1414

1415
/* load image from file */
1416

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

1467
    loader = NULL;
×
1468

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

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

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

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

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

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

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

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

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

1530
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1531

1532
end:
1533
    sixel_loader_unref(loader);
×
1534

1535
    return status;
×
1536
}
1537

1538

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

1547
    entries = NULL;
112✔
1548
    entry_count = loader_registry_get_entries(&entries);
112✔
1549

1550
    if (names != NULL && max_names > 0) {
112!
1551
        limit = entry_count;
56✔
1552
        if (limit > max_names) {
56!
1553
            limit = max_names;
1554
        }
1555
        for (index = 0; index < limit; ++index) {
268✔
1556
            names[index] = entries[index].name;
212✔
1557
        }
156✔
1558
    }
28✔
1559

1560
    return entry_count;
144✔
1561
}
32✔
1562

1563

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