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

saitoha / libsixel / 21335896708

25 Jan 2026 04:33PM UTC coverage: 76.581% (-2.3%) from 78.904%
21335896708

push

github

saitoha
meson: set build type to plain

20012 of 44638 branches covered (44.83%)

36354 of 47471 relevant lines covered (76.58%)

13461842.27 hits per line

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

86.93
/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 "assessment.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
    sixel_assessment_t *assessment;
134
    char *loader_order;
135
    sixel_allocator_t *allocator;
136
    char last_loader_name[64];
137
    char last_source_path[PATH_MAX];
138
    size_t last_input_bytes;
139
    int callback_failed;
140
    int log_loader_finished;
141
    char log_path[PATH_MAX];
142
    char log_loader_name[64];
143
    size_t log_input_bytes;
144
};
145

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

152

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

157
static char *
158
loader_strdup(char const *text, sixel_allocator_t *allocator)
132✔
159
{
160
    char *copy;
77✔
161
    size_t length;
77✔
162

163
    if (text == NULL) {
132!
164
        return NULL;
165
    }
166

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

173
#if HAVE_STRCPY_S
174
    (void)strcpy_s(copy, (rsize_t)length, text);
88✔
175
#else
176
    memcpy(copy, text, length);
44✔
177
#endif
178

179
    return copy;
132✔
180
}
33✔
181

182

183

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

202
    logger = NULL;
8,000✔
203
    if (loader != NULL) {
8,000!
204
        logger = &loader->logger;
8,000✔
205
    }
2,200✔
206
    if (logger == NULL || logger->file == NULL || !logger->active) {
8,000!
207
        return;
8,000✔
208
    }
209

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

249
    sixel_logger_logf(logger,
×
250
                      "worker",
251
                      "loader",
252
                      event,
253
                      -1,
254
                      -1,
255
                      0,
256
                      0,
257
                      0,
258
                      0,
259
                      "%s",
260
                      message);
261
}
2,200!
262

263
static SIXELSTATUS
264
loader_callback_trampoline(sixel_frame_t *frame, void *data)
4,356✔
265
{
266
    sixel_loader_callback_state_t *state;
2,544✔
267
    SIXELSTATUS status;
2,544✔
268
    sixel_loader_t *loader;
2,544✔
269

270
    state = (sixel_loader_callback_state_t *)data;
4,356✔
271
    loader = NULL;
4,356✔
272
    if (state == NULL || state->fn == NULL) {
4,356!
273
        return SIXEL_BAD_ARGUMENT;
274
    }
275

276
    loader = state->loader;
4,356✔
277
    if (loader != NULL && loader->log_loader_finished == 0) {
4,356!
278
        loader_log_stage(loader,
5,049✔
279
                         "finish",
280
                         "path=%s loader=%s bytes=%zu",
281
                         loader->log_path,
3,960✔
282
                         loader->log_loader_name,
3,960✔
283
                         loader->log_input_bytes);
1,089✔
284
        loader->log_loader_finished = 1;
3,960✔
285
    }
1,089✔
286

287
    status = state->fn(frame, state->context);
4,708✔
288
    if (SIXEL_FAILED(status) && state->loader != NULL) {
4,708!
289
        state->loader->callback_failed = 1;
24✔
290
    }
6✔
291

292
    return status;
2,232✔
293
}
1,188✔
294

295
static int
296
loader_plan_contains(sixel_loader_entry_t const **plan,
6,138✔
297
                     size_t plan_length,
298
                     sixel_loader_entry_t const *entry)
299
{
300
    size_t index;
1,579✔
301

302
    for (index = 0; index < plan_length; ++index) {
18,306!
303
        if (plan[index] == entry) {
8,978!
304
            return 1;
66✔
305
        }
306
    }
6,590✔
307

308
    return 0;
6,072✔
309
}
4,197✔
310

311
static int
312
loader_token_matches(char const *token,
176✔
313
                     size_t token_length,
314
                     char const *name)
315
{
316
    size_t index;
99✔
317
    unsigned char left;
99✔
318
    unsigned char right;
99✔
319

320
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
1,100!
321
        left = (unsigned char)token[index];
968✔
322
        right = (unsigned char)name[index];
968✔
323
        if (tolower(left) != tolower(right)) {
968!
324
            return 0;
44✔
325
        }
326
    }
231✔
327

328
    if (index != token_length || name[index] != '\0') {
132!
329
        return 0;
×
330
    }
331

332
    return 1;
66✔
333
}
77✔
334

335
static sixel_loader_entry_t const *
336
loader_lookup_token(char const *token,
132✔
337
                    size_t token_length,
338
                    sixel_loader_entry_t const *entries,
339
                    size_t entry_count)
340
{
341
    size_t index;
77✔
342

343
    for (index = 0; index < entry_count; ++index) {
176!
344
        if (loader_token_matches(token,
253✔
345
                                 token_length,
77✔
346
                                 entries[index].name)) {
176!
347
            return &entries[index];
66✔
348
        }
349
    }
44✔
350

351
    return NULL;
352
}
33✔
353

354
/*
355
 * loader_build_plan
356
 *
357
 * Translate a comma separated list into an execution plan that reorders the
358
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
359
 * are ignored so that new builds remain forward compatible.
360
 *
361
 *    user input "gd,coregraphics"
362
 *                |
363
 *                v
364
 *        +-------------------+
365
 *        | prioritized list  |
366
 *        +-------------------+
367
 *                |
368
 *                v
369
 *        +-------------------+
370
 *        | default fallbacks |
371
 *        +-------------------+
372
 */
373
static size_t
374
loader_build_plan(char const *order,
4,010✔
375
                  sixel_loader_entry_t const *entries,
376
                  size_t entry_count,
377
                  sixel_loader_entry_t const **plan,
378
                  size_t plan_capacity)
379
{
380
    size_t plan_length;
2,346✔
381
    size_t index;
2,346✔
382
    char const *cursor;
2,346✔
383
    char const *token_start;
2,346✔
384
    char const *token_end;
2,346✔
385
    size_t token_length;
2,346✔
386
    sixel_loader_entry_t const *entry;
2,346✔
387
    size_t limit;
2,346✔
388

389
    plan_length = 0;
4,010✔
390
    index = 0;
4,010✔
391
    cursor = order;
4,010✔
392
    token_start = order;
4,010✔
393
    token_end = order;
4,010✔
394
    token_length = 0;
4,010✔
395
    entry = NULL;
4,010✔
396
    limit = plan_capacity;
4,010✔
397

398
    if (order != NULL && plan != NULL && plan_capacity > 0) {
4,010!
399
        token_start = order;
66✔
400
        cursor = order;
66✔
401
        while (*cursor != '\0') {
1,056✔
402
            if (*cursor == ',') {
924!
403
                token_end = cursor;
×
404
                while (token_start < token_end &&
×
405
                       isspace((unsigned char)*token_start)) {
×
406
                    ++token_start;
×
407
                }
408
                while (token_end > token_start &&
×
409
                       isspace((unsigned char)token_end[-1])) {
×
410
                    --token_end;
×
411
                }
412
                token_length = (size_t)(token_end - token_start);
×
413
                if (token_length > 0) {
×
414
                    entry = loader_lookup_token(token_start,
×
415
                                                token_length,
416
                                                entries,
417
                                                entry_count);
418
                    if (entry != NULL &&
×
419
                        !loader_plan_contains(plan,
×
420
                                              plan_length,
421
                                              entry) &&
×
422
                        plan_length < limit) {
423
                        plan[plan_length] = entry;
×
424
                        ++plan_length;
×
425
                    }
426
                }
427
                token_start = cursor + 1;
×
428
            }
429
            ++cursor;
924✔
430
        }
431

432
        token_end = cursor;
132✔
433
        while (token_start < token_end &&
132!
434
               isspace((unsigned char)*token_start)) {
132!
435
            ++token_start;
×
436
        }
437
        while (token_end > token_start &&
132!
438
               isspace((unsigned char)token_end[-1])) {
132!
439
            --token_end;
×
440
        }
441
        token_length = (size_t)(token_end - token_start);
132✔
442
        if (token_length > 0) {
132!
443
            entry = loader_lookup_token(token_start,
165✔
444
                                        token_length,
33✔
445
                                        entries,
33✔
446
                                        entry_count);
33✔
447
            if (entry != NULL &&
187!
448
                !loader_plan_contains(plan, plan_length, entry) &&
165!
449
                plan_length < limit) {
33✔
450
                plan[plan_length] = entry;
132✔
451
                ++plan_length;
132✔
452
            }
33✔
453
        }
33✔
454
    }
33✔
455

456
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
14,774✔
457
        entry = &entries[index];
10,764✔
458
        if (!entry->default_enabled) {
10,764✔
459
            continue;
1,436✔
460
        }
461
        if (!loader_plan_contains(plan, plan_length, entry)) {
9,328✔
462
            plan[plan_length] = entry;
9,196✔
463
            ++plan_length;
9,196✔
464
        }
4,131✔
465
    }
4,164✔
466

467
    return plan_length;
4,402✔
468
}
392✔
469

470
static void
471
loader_append_chunk(char *dest,
358✔
472
                    size_t capacity,
473
                    size_t *offset,
474
                    char const *chunk)
475
{
476
    size_t available;
236✔
477
    size_t length;
236✔
478

479
    if (dest == NULL || offset == NULL || chunk == NULL) {
358!
480
        return;
481
    }
482

483
    if (*offset >= capacity) {
358!
484
        return;
485
    }
486

487
    available = capacity - *offset;
358✔
488
    if (available == 0) {
358✔
489
        return;
490
    }
491

492
    length = strlen(chunk);
358✔
493
    if (length >= available) {
358!
494
        if (available == 0) {
×
495
            return;
496
        }
497
        length = available - 1u;
×
498
    }
499

500
    if (length > 0) {
358!
501
        memcpy(dest + *offset, chunk, length);
358✔
502
        *offset += length;
358✔
503
    }
100✔
504

505
    if (*offset < capacity) {
358!
506
        dest[*offset] = '\0';
358✔
507
    } else {
100✔
508
        dest[capacity - 1u] = '\0';
×
509
    }
510
}
100!
511

512
static void
513
loader_append_key_value(char *dest,
158✔
514
                        size_t capacity,
515
                        size_t *offset,
516
                        char const *label,
517
                        char const *value)
518
{
519
    char line[128];
104✔
520
    int written;
104✔
521

522
    if (value == NULL || value[0] == '\0') {
158!
523
        return;
×
524
    }
525

526
    written = sixel_compat_snprintf(line,
206✔
527
                                    sizeof(line),
528
                                    "  %-10s: %s\n",
529
                                    label,
48✔
530
                                    value);
48✔
531
    if (written < 0) {
158!
532
        return;
533
    }
534

535
    if ((size_t)written >= sizeof(line)) {
158✔
536
        line[sizeof(line) - 1u] = '\0';
6✔
537
    }
4✔
538

539
    loader_append_chunk(dest, capacity, offset, line);
158✔
540
}
48!
541

542
static void
543
loader_extract_extension(char const *path, char *buffer, size_t capacity)
50✔
544
{
545
    char const *dot;
33✔
546
    size_t index;
33✔
547

548
    if (buffer == NULL || capacity == 0) {
50!
549
        return;
550
    }
551

552
    buffer[0] = '\0';
50✔
553

554
    if (path == NULL) {
50✔
555
        return;
6✔
556
    }
557

558
    dot = strrchr(path, '.');
38✔
559
    if (dot == NULL || dot[1] == '\0') {
38!
560
        return;
561
    }
562

563
#if defined(_WIN32)
564
    {
565
        char const *slash;
14✔
566
        char const *backslash;
14✔
567

568
        slash = strrchr(path, '/');
22✔
569
        backslash = strrchr(path, '\\');
22✔
570
        if ((slash != NULL && dot < slash) ||
22✔
571
                (backslash != NULL && dot < backslash)) {
2✔
572
            return;
573
        }
574
    }
575
#else
576
    {
577
        char const *slash;
12✔
578

579
        slash = strrchr(path, '/');
16✔
580
        if (slash != NULL && dot < slash) {
16!
581
            return;
582
        }
583
    }
4!
584
#endif
585

586
    if (dot[1] == '\0') {
16✔
587
        return;
588
    }
589

590
    dot += 1;
104✔
591

592
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
152!
593
        buffer[index] = (char)tolower((unsigned char)dot[index]);
114✔
594
    }
30✔
595
    buffer[index] = '\0';
38✔
596
}
13!
597

598

599

600

601

602

603

604

605

606

607

608
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
609
static void
610
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
8✔
611
{
612
    if (buffer == NULL || capacity == 0) {
8✔
613
        return;
614
    }
615

616
    buffer[0] = '\0';
8✔
617
    if (source == NULL) {
8!
618
        return;
619
    }
620

621
    if (!CFStringGetCString(source,
16✔
622
                             buffer,
8✔
623
                             (CFIndex)capacity,
8✔
624
                             kCFStringEncodingUTF8)) {
625
        buffer[0] = '\0';
626
    }
627
}
8✔
628
#endif
629

630

631
static void
632
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
50✔
633
                          char const *filename)
634
{
635
    enum { description_length = 128 };
28✔
636
    enum { uttype_length = 128 };
28✔
637
    enum { extension_length = 32 };
28✔
638
    enum { message_length = 768 };
28✔
639
    char message[message_length];
33✔
640
    char type_value[description_length];
33✔
641
    char extension_text[extension_length + 2];
33✔
642
    char uttype[uttype_length];
33✔
643
    char desc_buffer[description_length];
33✔
644
    char extension[extension_length];
33✔
645
    char const *path;
33✔
646
    char const *display_path;
33✔
647
    char const *metadata_path;
33✔
648
    char const *description_text;
33✔
649
    char *mime_string;
33✔
650
    char *description_string;
33✔
651
    size_t offset;
33✔
652
    int gnome_available;
33✔
653
    int gnome_has_dirs;
33✔
654
    int gnome_has_match;
33✔
655
    int suggestions;
33✔
656

657
    message[0] = '\0';
50✔
658
    type_value[0] = '\0';
50✔
659
    extension_text[0] = '\0';
50✔
660
    uttype[0] = '\0';
50✔
661
    desc_buffer[0] = '\0';
50✔
662
    extension[0] = '\0';
50✔
663
    path = NULL;
50✔
664
    display_path = "(stdin)";
50✔
665
    metadata_path = NULL;
50✔
666
    description_text = NULL;
50✔
667
    mime_string = NULL;
50✔
668
    description_string = NULL;
50✔
669
    offset = 0u;
50✔
670
    gnome_available = 0;
50✔
671
    gnome_has_dirs = 0;
50✔
672
    gnome_has_match = 0;
50✔
673
    suggestions = 0;
50✔
674

675
    if (pchunk != NULL && pchunk->source_path != NULL) {
50!
676
        path = pchunk->source_path;
16✔
677
    } else if (filename != NULL) {
21!
678
        path = filename;
679
    }
680

681
    if (path != NULL && strcmp(path, "-") != 0) {
44!
682
        display_path = path;
38✔
683
    }
10✔
684

685
    if (path != NULL && strcmp(path, "-") != 0 &&
43!
686
            strstr(path, "://") == NULL) {
24!
687
        metadata_path = path;
26✔
688
    }
10✔
689

690
    loader_extract_extension(path, extension, sizeof(extension));
49✔
691

692
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
693
    if (metadata_path != NULL) {
19✔
694
        /*
695
         * Collect MIME metadata via file(1) when fork() and friends are
696
         * available.  Windows builds compiled with clang64 lack these
697
         * interfaces, so the thumbnail helpers remain disabled there.
698
         */
699
        mime_string = thumbnailer_guess_content_type(metadata_path);
16✔
700
        description_string = thumbnailer_run_file(metadata_path, NULL);
16✔
701
    }
8✔
702
#else
703
    (void)metadata_path;
21✔
704
#endif
705

706
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
707
#if defined(__clang__)
708
    /*
709
     * Allow use of legacy UTType C APIs when compiling with the
710
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
711
     * so we must intentionally silence the deprecation warnings here.
712
     */
713
#pragma clang diagnostic push
714
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
715
#endif
716
    {
717
        CFStringRef uti_ref;
718
        CFStringRef mime_ref;
719
        CFStringRef ext_ref;
720
        CFStringRef desc_ref;
721
        CFStringRef preferred_mime;
722
        char uti_local[uttype_length];
723
        char desc_local[description_length];
724
        char mime_local[64];
725

726
        uti_ref = NULL;
4✔
727
        mime_ref = NULL;
4✔
728
        ext_ref = NULL;
4✔
729
        desc_ref = NULL;
4✔
730
        preferred_mime = NULL;
4✔
731
        uti_local[0] = '\0';
4✔
732
        desc_local[0] = '\0';
4✔
733
        mime_local[0] = '\0';
4✔
734

735
        if (mime_string != NULL) {
4✔
736
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
8✔
737
                                                 mime_string,
4✔
738
                                                 kCFStringEncodingUTF8);
739
        }
4✔
740
        if (mime_ref != NULL) {
4✔
741
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
4✔
742
                kUTTagClassMIMEType,
4✔
743
                mime_ref,
4✔
744
                NULL);
745
        }
4✔
746
        if (uti_ref == NULL && extension[0] != '\0') {
7✔
747
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
748
                                                extension,
749
                                                kCFStringEncodingUTF8);
750
            if (ext_ref != NULL) {
751
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
752
                    kUTTagClassFilenameExtension,
753
                    ext_ref,
754
                    NULL);
755
            }
756
        }
757
        if (uti_ref != NULL) {
4✔
758
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
4✔
759
            desc_ref = UTTypeCopyDescription(uti_ref);
4✔
760
            if (desc_ref != NULL) {
4✔
761
                loader_copy_cfstring(desc_ref,
8✔
762
                                     desc_local,
4✔
763
                                     sizeof(desc_local));
764
                CFRelease(desc_ref);
4✔
765
                desc_ref = NULL;
4✔
766
            }
4✔
767
            if (mime_string == NULL) {
8✔
768
                preferred_mime = UTTypeCopyPreferredTagWithClass(
769
                    uti_ref,
770
                    kUTTagClassMIMEType);
771
                if (preferred_mime != NULL) {
772
                    loader_copy_cfstring(preferred_mime,
773
                                         mime_local,
774
                                         sizeof(mime_local));
775
                    CFRelease(preferred_mime);
776
                    preferred_mime = NULL;
777
                }
778
                if (mime_local[0] != '\0') {
779
                    mime_string = thumbnailer_strdup(mime_local);
780
                }
781
            }
782
        }
4✔
783
        if (mime_ref != NULL) {
4✔
784
            CFRelease(mime_ref);
4✔
785
        }
4✔
786
        if (ext_ref != NULL) {
8✔
787
            CFRelease(ext_ref);
788
        }
789
        if (uti_ref != NULL) {
4✔
790
            CFRelease(uti_ref);
4✔
791
        }
4✔
792
        if (uti_local[0] != '\0') {
4✔
793
            sixel_compat_snprintf(uttype,
8✔
794
                                  sizeof(uttype),
795
                                  "%s",
796
                                  uti_local);
4✔
797
        }
4✔
798
        if (desc_local[0] != '\0') {
4✔
799
            sixel_compat_snprintf(desc_buffer,
8✔
800
                                  sizeof(desc_buffer),
801
                                  "%s",
802
                                  desc_local);
4✔
803
        }
4✔
804
    }
805
#if defined(__clang__)
806
#pragma clang diagnostic pop
807
#endif
808
#endif
809

810
    if (description_string != NULL && description_string[0] != '\0') {
47!
811
        description_text = description_string;
8✔
812
    } else if (desc_buffer[0] != '\0') {
42!
813
        description_text = desc_buffer;
814
    } else {
815
        description_text = "unknown content";
34✔
816
    }
817

818
    sixel_compat_snprintf(type_value,
63✔
819
                          sizeof(type_value),
820
                          "%s",
821
                          description_text);
13✔
822

823
    loader_append_chunk(message,
50✔
824
                        sizeof(message),
825
                        &offset,
826
                        "diagnostic:\n");
827
    loader_append_key_value(message,
63✔
828
                            sizeof(message),
829
                            &offset,
830
                            "file",
831
                            display_path);
13✔
832
    loader_append_key_value(message,
63✔
833
                            sizeof(message),
834
                            &offset,
835
                            "type",
836
                            type_value);
13✔
837

838
    if (mime_string != NULL && mime_string[0] != '\0') {
50!
839
        loader_append_key_value(message,
24✔
840
                                sizeof(message),
841
                                &offset,
842
                                "mime",
843
                                mime_string);
8✔
844
    }
8✔
845

846
    if (uttype[0] != '\0') {
49!
847
        loader_append_key_value(message,
8✔
848
                                sizeof(message),
849
                                &offset,
850
                                "uti",
851
                                uttype);
4✔
852
    }
4✔
853

854
    if (extension[0] != '\0') {
49✔
855
        sixel_compat_snprintf(extension_text,
48✔
856
                              sizeof(extension_text),
857
                              ".%s",
858
                              extension);
10✔
859
        loader_append_key_value(message,
48✔
860
                                sizeof(message),
861
                                &offset,
862
                                "extension",
863
                                extension_text);
10✔
864
    }
10✔
865

866
    loader_append_chunk(message,
50✔
867
                        sizeof(message),
868
                        &offset,
869
                        "  suggestions:\n");
870

871
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
872
    int quicklook_available;
873
    int quicklook_supported;
874

875
    quicklook_available = 0;
5✔
876
    quicklook_supported = 0;
5✔
877

878
    quicklook_available = loader_registry_entry_available("quicklook");
5✔
879
    if (quicklook_available) {
5✔
880
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
5✔
881
    }
5✔
882
    if (quicklook_supported) {
10✔
883
        loader_append_chunk(message,
884
                            sizeof(message),
885
                            &offset,
886
                            "    - QuickLook rendered a preview during "
887
                            "the probe; try -j quicklook.\n");
888
        suggestions += 1;
889
    }
890
#endif
891

892
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
893
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
20✔
894
    if (gnome_available) {
20!
895
        loader_probe_gnome_thumbnailers(mime_string,
20✔
896
                                        &gnome_has_dirs,
897
                                        &gnome_has_match);
898
        if (gnome_has_dirs && gnome_has_match) {
20!
899
            loader_append_chunk(message,
900
                                sizeof(message),
901
                                &offset,
902
                                "    - GNOME thumbnailer definitions match "
903
                                "this MIME type; try -j gnome-thumbnailer.\n"
904
                                );
905
            suggestions += 1;
906
        }
907
    }
10✔
908
#else
909
    (void)gnome_available;
21✔
910
    (void)gnome_has_dirs;
21✔
911
    (void)gnome_has_match;
21✔
912
#endif
913

914
    if (suggestions == 0) {
40!
915
        loader_append_chunk(message,
50✔
916
                            sizeof(message),
917
                            &offset,
918
                            "    (no thumbnail helper hints)\n");
919
    }
13✔
920

921
    if (suggestions > 0) {
50!
922
        loader_append_chunk(message,
923
                            sizeof(message),
924
                            &offset,
925
                            "  hint       : Enable one of the suggested "
926
                            "loaders with -j.\n");
927
    } else {
928
        loader_append_chunk(message,
50✔
929
                            sizeof(message),
930
                            &offset,
931
                            "  hint       : Convert the file to PNG or "
932
                            "enable optional loaders.\n");
933
    }
934

935
    sixel_helper_set_additional_message(message);
50✔
936

937
    free(mime_string);
50✔
938
    free(description_string);
50✔
939
}
50✔
940

941
SIXELAPI SIXELSTATUS
942
sixel_loader_new(
4,040✔
943
    sixel_loader_t   /* out */ **pploader,
944
    sixel_allocator_t/* in */  *allocator)
945
{
946
    SIXELSTATUS status = SIXEL_FALSE;
4,040✔
947
    sixel_loader_t *loader;
2,361✔
948
    sixel_allocator_t *local_allocator;
2,361✔
949

950
    loader = NULL;
4,040✔
951
    local_allocator = allocator;
4,040✔
952

953
    if (pploader == NULL) {
4,040!
954
        sixel_helper_set_additional_message(
×
955
            "sixel_loader_new: pploader is null.");
956
        status = SIXEL_BAD_ARGUMENT;
×
957
        goto end;
×
958
    }
959

960
    if (local_allocator == NULL) {
4,040!
961
        status = sixel_allocator_new(&local_allocator,
×
962
                                     NULL,
963
                                     NULL,
964
                                     NULL,
965
                                     NULL);
966
        if (SIXEL_FAILED(status)) {
×
967
            goto end;
×
968
        }
969
    } else {
970
        sixel_allocator_ref(local_allocator);
4,040✔
971
    }
972

973
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
4,040✔
974
                                                      sizeof(*loader));
975
    if (loader == NULL) {
4,040!
976
        sixel_helper_set_additional_message(
×
977
            "sixel_loader_new: sixel_allocator_malloc() failed.");
978
        status = SIXEL_BAD_ALLOCATION;
×
979
        sixel_allocator_unref(local_allocator);
×
980
        goto end;
×
981
    }
982

983
    loader->ref = 1U;
4,040✔
984
    loader->fstatic = 0;
4,040✔
985
    loader->fuse_palette = 0;
4,040✔
986
    loader->reqcolors = SIXEL_PALETTE_MAX;
4,040✔
987
    loader->bgcolor[0] = 0;
4,040✔
988
    loader->bgcolor[1] = 0;
4,040✔
989
    loader->bgcolor[2] = 0;
4,040✔
990
    loader->has_bgcolor = 0;
4,040✔
991
    loader->loop_control = SIXEL_LOOP_AUTO;
4,040✔
992
    loader->finsecure = 0;
4,040✔
993
    loader->cancel_flag = NULL;
4,040✔
994
    loader->context = NULL;
4,040✔
995
    /*
996
     * Initialize a private logger. The helper reuses an existing global
997
     * logger sink when present so loader markers share the timeline with
998
     * upstream stages without requiring sixel_loader_setopt().
999
     */
1000
    sixel_logger_init(&loader->logger);
4,040✔
1001
    (void)sixel_logger_prepare_env(&loader->logger);
4,040✔
1002
    loader->assessment = NULL;
4,040✔
1003
    loader->loader_order = NULL;
4,040✔
1004
    loader->allocator = local_allocator;
4,040✔
1005
    loader->last_loader_name[0] = '\0';
4,040✔
1006
    loader->last_source_path[0] = '\0';
4,040✔
1007
    loader->last_input_bytes = 0u;
4,040✔
1008
    loader->callback_failed = 0;
4,040✔
1009
    loader->log_loader_finished = 0;
4,040✔
1010
    loader->log_path[0] = '\0';
4,040✔
1011
    loader->log_loader_name[0] = '\0';
4,040✔
1012
    loader->log_input_bytes = 0u;
4,040✔
1013

1014
    *pploader = loader;
4,040✔
1015
    status = SIXEL_OK;
4,040✔
1016

1017
end:
2,929✔
1018
    return status;
4,435✔
1019
}
395✔
1020

1021
SIXELAPI void
1022
sixel_loader_ref(
37,636✔
1023
    sixel_loader_t /* in */ *loader)
1024
{
1025
    if (loader == NULL) {
37,636!
1026
        return;
1027
    }
1028

1029
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
37,636✔
1030
}
10,520✔
1031

1032
SIXELAPI void
1033
sixel_loader_unref(
41,676✔
1034
    sixel_loader_t /* in */ *loader)
1035
{
1036
    sixel_allocator_t *allocator;
24,363✔
1037
    unsigned int previous;
24,363✔
1038

1039
    if (loader == NULL) {
41,676!
1040
        return;
1041
    }
1042

1043
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
41,676✔
1044
    if (previous == 1U) {
41,676✔
1045
        allocator = loader->allocator;
4,040✔
1046
        sixel_logger_close(&loader->logger);
4,040✔
1047
        sixel_allocator_free(allocator, loader->loader_order);
4,040✔
1048
        sixel_allocator_free(allocator, loader);
4,040✔
1049
        sixel_allocator_unref(allocator);
4,040✔
1050
    }
1,111✔
1051
}
11,631!
1052

1053
SIXELAPI SIXELSTATUS
1054
sixel_loader_setopt(
33,596✔
1055
    sixel_loader_t /* in */ *loader,
1056
    int            /* in */ option,
1057
    void const     /* in */ *value)
1058
{
1059
    SIXELSTATUS status = SIXEL_FALSE;
33,596✔
1060
    int const *flag;
19,641✔
1061
    unsigned char const *color;
19,641✔
1062
    char const *order;
19,641✔
1063
    char *copy;
19,641✔
1064
    sixel_allocator_t *allocator;
19,641✔
1065

1066
    flag = NULL;
33,596✔
1067
    color = NULL;
33,596✔
1068
    order = NULL;
33,596✔
1069
    copy = NULL;
33,596✔
1070
    allocator = NULL;
33,596✔
1071

1072
    if (loader == NULL) {
33,596!
1073
        sixel_helper_set_additional_message(
×
1074
            "sixel_loader_setopt: loader is null.");
1075
        status = SIXEL_BAD_ARGUMENT;
×
1076
        goto end0;
×
1077
    }
1078

1079
    sixel_loader_ref(loader);
33,596✔
1080

1081
    switch (option) {
33,596!
1082
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
2,929✔
1083
        flag = (int const *)value;
4,040✔
1084
        loader->fstatic = flag != NULL ? *flag : 0;
4,040!
1085
        status = SIXEL_OK;
4,040✔
1086
        break;
4,040✔
1087
    case SIXEL_LOADER_OPTION_USE_PALETTE:
2,929✔
1088
        flag = (int const *)value;
4,040✔
1089
        loader->fuse_palette = flag != NULL ? *flag : 0;
4,040!
1090
        status = SIXEL_OK;
4,040✔
1091
        break;
4,040✔
1092
    case SIXEL_LOADER_OPTION_REQCOLORS:
2,929✔
1093
        flag = (int const *)value;
4,040✔
1094
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
4,040!
1095
        if (loader->reqcolors < 1) {
4,040!
1096
            sixel_helper_set_additional_message(
×
1097
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1098
            status = SIXEL_BAD_ARGUMENT;
×
1099
            goto end;
×
1100
        }
1101
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
4,040!
1102
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1103
        }
1104
        status = SIXEL_OK;
2,074✔
1105
        break;
2,074✔
1106
    case SIXEL_LOADER_OPTION_BGCOLOR:
1,669✔
1107
        if (value == NULL) {
2,360✔
1108
            loader->has_bgcolor = 0;
2,249✔
1109
        } else {
639✔
1110
            color = (unsigned char const *)value;
111✔
1111
            loader->bgcolor[0] = color[0];
111✔
1112
            loader->bgcolor[1] = color[1];
111✔
1113
            loader->bgcolor[2] = color[2];
111✔
1114
            loader->has_bgcolor = 1;
111✔
1115
        }
1116
        status = SIXEL_OK;
1,234✔
1117
        break;
1,234✔
1118
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
2,929✔
1119
        flag = (int const *)value;
4,040✔
1120
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
4,040!
1121
        status = SIXEL_OK;
4,040✔
1122
        break;
4,040✔
1123
    case SIXEL_LOADER_OPTION_INSECURE:
2,929✔
1124
        flag = (int const *)value;
4,040✔
1125
        loader->finsecure = flag != NULL ? *flag : 0;
4,040!
1126
        status = SIXEL_OK;
4,040✔
1127
        break;
4,040✔
1128
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
1,669✔
1129
        loader->cancel_flag = (int const *)value;
2,360✔
1130
        status = SIXEL_OK;
2,360✔
1131
        break;
2,360✔
1132
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
1,669✔
1133
        allocator = loader->allocator;
2,360✔
1134
        sixel_allocator_free(allocator, loader->loader_order);
2,360✔
1135
        loader->loader_order = NULL;
2,360✔
1136
        if (value != NULL) {
2,360✔
1137
            order = (char const *)value;
132✔
1138
            copy = loader_strdup(order, allocator);
132✔
1139
            if (copy == NULL) {
132!
1140
                sixel_helper_set_additional_message(
×
1141
                    "sixel_loader_setopt: loader_strdup() failed.");
1142
                status = SIXEL_BAD_ALLOCATION;
×
1143
                goto end;
×
1144
            }
1145
            loader->loader_order = copy;
132✔
1146
        }
33✔
1147
        status = SIXEL_OK;
1,234✔
1148
        break;
1,234✔
1149
    case SIXEL_LOADER_OPTION_CONTEXT:
2,929✔
1150
        loader->context = (void *)value;
4,040✔
1151
        loader->assessment = NULL;
4,040✔
1152
        status = SIXEL_OK;
4,040✔
1153
        break;
4,040✔
1154
    case SIXEL_LOADER_OPTION_ASSESSMENT:
1,606✔
1155
        loader->assessment = (sixel_assessment_t *)value;
2,276✔
1156
        status = SIXEL_OK;
2,276✔
1157
        break;
2,276✔
1158
    default:
1159
        sixel_helper_set_additional_message(
×
1160
            "sixel_loader_setopt: unknown option.");
1161
        status = SIXEL_BAD_ARGUMENT;
×
1162
        goto end;
×
1163
    }
9,409✔
1164

1165
end:
24,187✔
1166
    sixel_loader_unref(loader);
33,596✔
1167

1168
end0:
24,187✔
1169
    return status;
36,979✔
1170
}
3,383✔
1171

1172
SIXELAPI char const *
1173
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
4,392✔
1174
{
1175
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
4,392!
1176
        return NULL;
1177
    }
1178
    return loader->last_loader_name;
4,392✔
1179
}
1,296✔
1180

1181
SIXELAPI char const *
1182
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
4,224✔
1183
{
1184
    if (loader == NULL || loader->last_source_path[0] == '\0') {
4,224!
1185
        return NULL;
86✔
1186
    }
1187
    return loader->last_source_path;
4,056✔
1188
}
1,252✔
1189

1190
SIXELAPI size_t
1191
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
2,196✔
1192
{
1193
    if (loader == NULL) {
2,196!
1194
        return 0u;
1195
    }
1196
    return loader->last_input_bytes;
2,196✔
1197
}
648✔
1198

1199
SIXELAPI SIXELSTATUS
1200
sixel_loader_load_file(
4,040✔
1201
    sixel_loader_t         /* in */ *loader,
1202
    char const             /* in */ *filename,
1203
    sixel_load_image_function /* in */ fn_load)
1204
{
1205
    SIXELSTATUS status = SIXEL_FALSE;
4,040✔
1206
    sixel_chunk_t *pchunk;
2,361✔
1207
    sixel_loader_entry_t const **plan;
2,361✔
1208
    sixel_loader_entry_t const *entries;
2,361✔
1209
    size_t entry_count;
2,361✔
1210
    size_t plan_length;
2,361✔
1211
    size_t plan_index;
2,361✔
1212
    unsigned char *bgcolor;
2,361✔
1213
    int reqcolors;
2,361✔
1214
    sixel_assessment_t *assessment;
2,361✔
1215
    char const *order_override;
2,361✔
1216
    char const *env_order;
2,361✔
1217
    sixel_loader_callback_state_t callback_state;
2,361✔
1218

1219
    pchunk = NULL;
4,040✔
1220
    plan = NULL;
4,040✔
1221
    entries = NULL;
4,040✔
1222
    entry_count = 0;
4,040✔
1223
    plan_length = 0;
4,040✔
1224
    plan_index = 0;
4,040✔
1225
    bgcolor = NULL;
4,040✔
1226
    reqcolors = 0;
4,040✔
1227
    assessment = NULL;
4,040✔
1228
    order_override = NULL;
4,040✔
1229
    env_order = NULL;
4,040✔
1230

1231
    if (loader == NULL) {
4,040!
1232
        sixel_helper_set_additional_message(
×
1233
            "sixel_loader_load_file: loader is null.");
1234
        status = SIXEL_BAD_ARGUMENT;
×
1235
        goto end0;
×
1236
    }
1237

1238
    sixel_loader_ref(loader);
4,040✔
1239

1240
    loader->log_loader_finished = 0;
4,040✔
1241
    loader->log_loader_name[0] = '\0';
4,040✔
1242
    loader->log_input_bytes = 0u;
4,040✔
1243
    loader->log_path[0] = '\0';
4,040✔
1244
    if (filename != NULL) {
4,040✔
1245
        (void)sixel_compat_snprintf(loader->log_path,
4,941✔
1246
                                    sizeof(loader->log_path),
1247
                                    "%s",
1248
                                    filename);
1,069✔
1249
    }
1,069✔
1250
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
4,407✔
1251

1252
    memset(&callback_state, 0, sizeof(callback_state));
4,407✔
1253
    callback_state.loader = loader;
4,407✔
1254
    callback_state.fn = fn_load;
4,407✔
1255
    callback_state.context = loader->context;
4,407✔
1256
    loader->callback_failed = 0;
4,407✔
1257

1258
    entry_count = loader_registry_get_entries(&entries);
4,407✔
1259

1260
    reqcolors = loader->reqcolors;
4,407✔
1261
    if (reqcolors > SIXEL_PALETTE_MAX) {
4,407!
1262
        reqcolors = SIXEL_PALETTE_MAX;
1263
    }
1264

1265
    assessment = loader->assessment;
4,040✔
1266

1267
    /*
1268
     *  Assessment pipeline sketch:
1269
     *
1270
     *      +-------------+        +--------------+
1271
     *      | chunk read  | -----> | image decode |
1272
     *      +-------------+        +--------------+
1273
     *
1274
     *  The loader owns the hand-off.  Chunk I/O ends before any decoder runs,
1275
     *  so we time the read span in the encoder and pivot to decode once the
1276
     *  chunk is populated.
1277
     */
1278
    status = sixel_chunk_new(&pchunk,
4,040✔
1279
                             filename,
1,111✔
1280
                             loader->finsecure,
1,111✔
1281
                             loader->cancel_flag,
1,111✔
1282
                             loader->allocator);
1,111✔
1283
    if (status != SIXEL_OK) {
4,040!
1284
        goto end;
30✔
1285
    }
1286

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

1292
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
3,994!
1293
        (void)sixel_compat_snprintf(loader->log_path,
4,885✔
1294
                                    sizeof(loader->log_path),
1295
                                    "%s",
1296
                                    pchunk->source_path);
1,964✔
1297
    }
1,055✔
1298

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

1304
    if (loader->has_bgcolor) {
4,010✔
1305
        bgcolor = loader->bgcolor;
111✔
1306
    }
52✔
1307

1308
    status = SIXEL_FALSE;
3,663✔
1309
    if (assessment != NULL) {
3,663✔
1310
        sixel_assessment_stage_transition(
12✔
1311
            assessment,
3✔
1312
            SIXEL_ASSESSMENT_STAGE_IMAGE_DECODE);
1313
    }
3✔
1314
    order_override = loader->loader_order;
3,999✔
1315
    /*
1316
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
1317
     * a loader order via -L/--loaders or sixel_loader_setopt().
1318
     */
1319
    if (order_override == NULL) {
3,999✔
1320
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
3,878✔
1321
        if (env_order != NULL && env_order[0] != '\0') {
3,878!
1322
            order_override = env_order;
1,954✔
1323
        }
1324
    }
1,069✔
1325

1326
    plan = sixel_allocator_malloc(loader->allocator,
6,414✔
1327
                                  entry_count * sizeof(*plan));
3,358✔
1328
    if (plan == NULL) {
4,010!
1329
        status = SIXEL_BAD_ALLOCATION;
×
1330
        goto end;
×
1331
    }
1332

1333
    plan_length = loader_build_plan(order_override,
5,112✔
1334
                                    entries,
1,102✔
1335
                                    entry_count,
1,102✔
1336
                                    plan,
1,102✔
1337
                                    entry_count);
1,102✔
1338

1339
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
6,491✔
1340
        if (plan[plan_index] == NULL) {
4,463!
1341
            continue;
×
1342
        }
1343
        if (plan[plan_index]->predicate != NULL &&
4,504!
1344
            plan[plan_index]->predicate(pchunk) == 0) {
1,033✔
1345
            continue;
409✔
1346
        }
1347
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
4,054!
1348
        if (plan[plan_index]->name != NULL) {
4,054!
1349
            (void)sixel_compat_snprintf(loader->log_loader_name,
5,173✔
1350
                                        sizeof(loader->log_loader_name),
1351
                                        "%s",
1352
                                        plan[plan_index]->name);
2,082✔
1353
        } else {
1,119✔
1354
            loader->log_loader_name[0] = '\0';
×
1355
        }
1356
        loader_trace_try(plan[plan_index]->name);
4,054✔
1357
        status = plan[plan_index]->backend(pchunk,
5,173✔
1358
                                           loader->fstatic,
1,119✔
1359
                                           loader->fuse_palette,
1,119✔
1360
                                           reqcolors,
1,119✔
1361
                                           bgcolor,
1,119✔
1362
                                           loader->loop_control,
1,119✔
1363
                                           loader_callback_trampoline,
1364
                                           &callback_state);
1365
        loader_trace_result(plan[plan_index]->name, status);
4,054✔
1366
        if (SIXEL_SUCCEEDED(status)) {
4,054✔
1367
            break;
2,022✔
1368
        }
1369
    }
36✔
1370

1371
    if (SIXEL_FAILED(status)) {
4,010✔
1372
        if (!loader->callback_failed &&
84!
1373
                plan_length > 0u &&
50!
1374
                plan_index >= plan_length &&
50!
1375
                pchunk != NULL) {
50!
1376
            status = SIXEL_LOADER_FAILED;
50✔
1377
            loader_publish_diagnostic(pchunk, filename);
50✔
1378
        }
13✔
1379
        goto end;
74✔
1380
    }
1381

1382
    if (plan_index < plan_length &&
4,706!
1383
            plan[plan_index] != NULL &&
3,936!
1384
            plan[plan_index]->name != NULL) {
3,936!
1385
        (void)sixel_compat_snprintf(loader->last_loader_name,
5,019✔
1386
                                    sizeof(loader->last_loader_name),
1387
                                    "%s",
1388
                                    plan[plan_index]->name);
2,022✔
1389
    } else {
1,083✔
1390
        loader->last_loader_name[0] = '\0';
×
1391
    }
1392
    loader->last_input_bytes = pchunk->size;
3,936✔
1393
    if (pchunk->source_path != NULL) {
5,019✔
1394
        size_t path_len;
2,202✔
1395

1396
        path_len = strlen(pchunk->source_path);
3,768✔
1397
        if (path_len >= sizeof(loader->last_source_path)) {
3,768!
1398
            path_len = sizeof(loader->last_source_path) - 1u;
1399
        }
1400
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
3,768✔
1401
        loader->last_source_path[path_len] = '\0';
3,768✔
1402
    } else {
1,039✔
1403
        loader->last_source_path[0] = '\0';
168✔
1404
    }
1405

1406
end:
2,917✔
1407
    if (plan != NULL) {
2,086✔
1408
        sixel_allocator_free(loader->allocator, plan);
4,010✔
1409
        plan = NULL;
4,010✔
1410
    }
1,102✔
1411
    sixel_chunk_destroy(pchunk);
4,040✔
1412
    sixel_loader_unref(loader);
4,040✔
1413

1414
end0:
2,929✔
1415
    return status;
4,435✔
1416
}
395✔
1417

1418
/* load image from file */
1419

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

1470
    loader = NULL;
×
1471

1472
    status = sixel_loader_new(&loader, allocator);
×
1473
    if (SIXEL_FAILED(status)) {
×
1474
        goto end;
×
1475
    }
1476

1477
    status = sixel_loader_setopt(loader,
×
1478
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1479
                                 &fstatic);
1480
    if (SIXEL_FAILED(status)) {
×
1481
        goto end;
×
1482
    }
1483

1484
    status = sixel_loader_setopt(loader,
×
1485
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1486
                                 &fuse_palette);
1487
    if (SIXEL_FAILED(status)) {
×
1488
        goto end;
×
1489
    }
1490

1491
    status = sixel_loader_setopt(loader,
×
1492
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1493
                                 &reqcolors);
1494
    if (SIXEL_FAILED(status)) {
×
1495
        goto end;
×
1496
    }
1497

1498
    status = sixel_loader_setopt(loader,
×
1499
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1500
                                 bgcolor);
1501
    if (SIXEL_FAILED(status)) {
×
1502
        goto end;
×
1503
    }
1504

1505
    status = sixel_loader_setopt(loader,
×
1506
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1507
                                 &loop_control);
1508
    if (SIXEL_FAILED(status)) {
×
1509
        goto end;
×
1510
    }
1511

1512
    status = sixel_loader_setopt(loader,
×
1513
                                 SIXEL_LOADER_OPTION_INSECURE,
1514
                                 &finsecure);
1515
    if (SIXEL_FAILED(status)) {
×
1516
        goto end;
×
1517
    }
1518

1519
    status = sixel_loader_setopt(loader,
×
1520
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1521
                                 cancel_flag);
1522
    if (SIXEL_FAILED(status)) {
×
1523
        goto end;
×
1524
    }
1525

1526
    status = sixel_loader_setopt(loader,
×
1527
                                 SIXEL_LOADER_OPTION_CONTEXT,
1528
                                 context);
1529
    if (SIXEL_FAILED(status)) {
×
1530
        goto end;
×
1531
    }
1532

1533
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1534

1535
end:
1536
    sixel_loader_unref(loader);
×
1537

1538
    return status;
×
1539
}
1540

1541

1542
SIXELAPI size_t
1543
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
24✔
1544
{
1545
    sixel_loader_entry_t const *entries;
14✔
1546
    size_t entry_count;
14✔
1547
    size_t limit;
14✔
1548
    size_t index;
14✔
1549

1550
    entries = NULL;
24✔
1551
    entry_count = loader_registry_get_entries(&entries);
24✔
1552

1553
    if (names != NULL && max_names > 0) {
24!
1554
        limit = entry_count;
12✔
1555
        if (limit > max_names) {
12!
1556
            limit = max_names;
1557
        }
1558
        for (index = 0; index < limit; ++index) {
43✔
1559
            names[index] = entries[index].name;
31✔
1560
        }
13✔
1561
    }
3✔
1562

1563
    return entry_count;
26✔
1564
}
2✔
1565

1566

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