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

saitoha / libsixel / 21315958622

24 Jan 2026 01:37PM UTC coverage: 79.14% (-0.6%) from 79.747%
21315958622

push

github

saitoha
tests: add missing common shells

21516 of 48249 branches covered (44.59%)

36057 of 45561 relevant lines covered (79.14%)

18481199.33 hits per line

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

88.89
/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
#if !defined(HAVE_MEMCPY)
95
# define memcpy(d, s, n) (bcopy ((s), (d), (n)))
96
#endif
97

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

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

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

156

157
#if HAVE_POSIX_SPAWNP
158
extern char **environ;
159
#endif
160

161
static char *
162
loader_strdup(char const *text, sixel_allocator_t *allocator)
143✔
163
{
164
    char *copy;
110✔
165
    size_t length;
110✔
166

167
    if (text == NULL) {
143!
168
        return NULL;
169
    }
170

171
    length = strlen(text) + 1;
143✔
172
    copy = (char *)sixel_allocator_malloc(allocator, length);
143✔
173
    if (copy == NULL) {
143!
174
        return NULL;
175
    }
176

177
#if HAVE_STRCPY_S
178
    (void)strcpy_s(copy, (rsize_t)(length - 1), text);
179
#else
180
    memcpy(copy, text, length);
143✔
181
#endif
182

183
    return copy;
143✔
184
}
33✔
185

186

187

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

206
    logger = NULL;
7,501✔
207
    if (loader != NULL) {
9,643!
208
        logger = &loader->logger;
9,643✔
209
    }
2,434✔
210
    if (logger == NULL || logger->file == NULL || !logger->active) {
9,643!
211
        return;
9,643✔
212
    }
213

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

253
    sixel_logger_logf(logger,
×
254
                      "worker",
255
                      "loader",
256
                      event,
257
                      -1,
258
                      -1,
259
                      0,
260
                      0,
261
                      0,
262
                      0,
263
                      "%s",
264
                      message);
265
}
3,294!
266

267
static SIXELSTATUS
268
loader_callback_trampoline(sixel_frame_t *frame, void *data)
5,209✔
269
{
270
    sixel_loader_callback_state_t *state;
4,048✔
271
    SIXELSTATUS status;
4,048✔
272
    sixel_loader_t *loader;
4,048✔
273

274
    state = (sixel_loader_callback_state_t *)data;
4,048✔
275
    loader = NULL;
4,048✔
276
    if (state == NULL || state->fn == NULL) {
5,209!
277
        return SIXEL_BAD_ARGUMENT;
278
    }
279

280
    loader = state->loader;
5,209✔
281
    if (loader != NULL && loader->log_loader_finished == 0) {
5,209!
282
        loader_log_stage(loader,
5,986✔
283
                         "finish",
284
                         "path=%s loader=%s bytes=%zu",
285
                         loader->log_path,
4,780✔
286
                         loader->log_loader_name,
4,780✔
287
                         loader->log_input_bytes);
1,206✔
288
        loader->log_loader_finished = 1;
4,780✔
289
    }
1,206✔
290

291
    status = state->fn(frame, state->context);
5,600✔
292
    if (SIXEL_FAILED(status) && state->loader != NULL) {
5,600!
293
        state->loader->callback_failed = 1;
26✔
294
    }
6✔
295

296
    return status;
1,305✔
297
}
1,305✔
298

299
static int
300
loader_plan_contains(sixel_loader_entry_t const **plan,
5,057✔
301
                     size_t plan_length,
302
                     sixel_loader_entry_t const *entry)
303
{
304
    size_t index;
5,057✔
305

306
    for (index = 0; index < plan_length; ++index) {
22,816!
307
        if (plan[index] == entry) {
11,649!
308
            return 1;
33✔
309
        }
310
    }
8,977✔
311

312
    return 0;
5,024✔
313
}
5,057✔
314

315
static int
316
loader_token_matches(char const *token,
187✔
317
                     size_t token_length,
318
                     char const *name)
319
{
320
    size_t index;
154✔
321
    unsigned char left;
154✔
322
    unsigned char right;
154✔
323

324
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
1,188!
325
        left = (unsigned char)token[index];
1,045✔
326
        right = (unsigned char)name[index];
1,045✔
327
        if (tolower(left) != tolower(right)) {
1,045!
328
            return 0;
44✔
329
        }
330
    }
231✔
331

332
    if (index != token_length || name[index] != '\0') {
143!
333
        return 0;
×
334
    }
335

336
    return 1;
33✔
337
}
77✔
338

339
static sixel_loader_entry_t const *
340
loader_lookup_token(char const *token,
110✔
341
                    size_t token_length,
342
                    sixel_loader_entry_t const *entries,
343
                    size_t entry_count)
344
{
345
    size_t index;
110✔
346

347
    for (index = 0; index < entry_count; ++index) {
187!
348
        if (loader_token_matches(token,
264✔
349
                                 token_length,
77✔
350
                                 entries[index].name)) {
187!
351
            return &entries[index];
33✔
352
        }
353
    }
44✔
354

355
    return NULL;
356
}
33✔
357

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

393
    plan_length = 0;
3,762✔
394
    index = 0;
3,762✔
395
    cursor = order;
3,762✔
396
    token_start = order;
3,762✔
397
    token_end = order;
3,762✔
398
    token_length = 0;
3,762✔
399
    entry = NULL;
3,762✔
400
    limit = plan_capacity;
3,762✔
401

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

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

460
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
17,947✔
461
        entry = &entries[index];
13,114✔
462
        if (!entry->default_enabled) {
13,114✔
463
            continue;
1,947✔
464
        }
465
        if (!loader_plan_contains(plan, plan_length, entry)) {
11,167✔
466
            plan[plan_length] = entry;
11,024✔
467
            ++plan_length;
11,024✔
468
        }
4,991✔
469
    }
5,024✔
470

471
    return plan_length;
6,052✔
472
}
1,219✔
473

474
static void
475
loader_append_chunk(char *dest,
384✔
476
                    size_t capacity,
477
                    size_t *offset,
478
                    char const *chunk)
479
{
480
    size_t available;
324✔
481
    size_t length;
324✔
482

483
    if (dest == NULL || offset == NULL || chunk == NULL) {
384!
484
        return;
485
    }
486

487
    if (*offset >= capacity) {
384!
488
        return;
489
    }
490

491
    available = capacity - *offset;
384✔
492
    if (available == 0) {
324✔
493
        return;
494
    }
495

496
    length = strlen(chunk);
384✔
497
    if (length >= available) {
384!
498
        if (available == 0) {
×
499
            return;
500
        }
501
        length = available - 1u;
×
502
    }
503

504
    if (length > 0) {
384!
505
        memcpy(dest + *offset, chunk, length);
384✔
506
        *offset += length;
384✔
507
    }
104✔
508

509
    if (*offset < capacity) {
384!
510
        dest[*offset] = '\0';
384✔
511
    } else {
104✔
512
        dest[capacity - 1u] = '\0';
×
513
    }
514
}
146!
515

516
static void
517
loader_append_key_value(char *dest,
172✔
518
                        size_t capacity,
519
                        size_t *offset,
520
                        char const *label,
521
                        char const *value)
522
{
523
    char line[128];
148✔
524
    int written;
148✔
525

526
    if (value == NULL || value[0] == '\0') {
172!
527
        return;
×
528
    }
529

530
    written = sixel_compat_snprintf(line,
224✔
531
                                    sizeof(line),
532
                                    "  %-10s: %s\n",
533
                                    label,
52✔
534
                                    value);
52✔
535
    if (written < 0) {
172!
536
        return;
537
    }
538

539
    if ((size_t)written >= sizeof(line)) {
172✔
540
        line[sizeof(line) - 1u] = '\0';
6✔
541
    }
4✔
542

543
    loader_append_chunk(dest, capacity, offset, line);
172✔
544
}
74!
545

546
static void
547
loader_extract_extension(char const *path, char *buffer, size_t capacity)
53✔
548
{
549
    char const *dot;
44✔
550
    size_t index;
44✔
551

552
    if (buffer == NULL || capacity == 0) {
53!
553
        return;
554
    }
555

556
    buffer[0] = '\0';
53✔
557

558
    if (path == NULL) {
53✔
559
        return;
3✔
560
    }
561

562
    dot = strrchr(path, '.');
40✔
563
    if (dot == NULL || dot[1] == '\0') {
40!
564
        return;
565
    }
566

567
#if defined(_WIN32)
568
    {
569
        char const *slash;
16✔
570
        char const *backslash;
16✔
571

572
        slash = strrchr(path, '/');
22✔
573
        backslash = strrchr(path, '\\');
22✔
574
        if ((slash != NULL && dot < slash) ||
22✔
575
                (backslash != NULL && dot < backslash)) {
2✔
576
            return;
577
        }
578
    }
2✔
579
#else
580
    {
581
        char const *slash;
18✔
582

583
        slash = strrchr(path, '/');
18✔
584
        if (slash != NULL && dot < slash) {
18!
585
            return;
586
        }
587
    }
8!
588
#endif
589

590
    if (dot[1] == '\0') {
10✔
591
        return;
592
    }
593

594
    dot += 1;
106✔
595

596
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
160!
597
        buffer[index] = (char)tolower((unsigned char)dot[index]);
120✔
598
    }
30✔
599
    buffer[index] = '\0';
40✔
600
}
23!
601

602

603

604

605

606

607

608

609

610

611

612
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
613
static void
614
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
16✔
615
{
616
    if (buffer == NULL || capacity == 0) {
16!
617
        return;
618
    }
619

620
    buffer[0] = '\0';
16✔
621
    if (source == NULL) {
16✔
622
        return;
623
    }
624

625
    if (!CFStringGetCString(source,
32!
626
                             buffer,
16✔
627
                             (CFIndex)capacity,
16✔
628
                             kCFStringEncodingUTF8)) {
629
        buffer[0] = '\0';
630
    }
631
}
16✔
632
#endif
633

634

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

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

679
    if (pchunk != NULL && pchunk->source_path != NULL) {
53!
680
        path = pchunk->source_path;
10✔
681
    } else if (filename != NULL) {
22!
682
        path = filename;
683
    }
684

685
    if (path != NULL && strcmp(path, "-") != 0) {
43!
686
        display_path = path;
34✔
687
    }
10✔
688

689
    if (path != NULL && strcmp(path, "-") != 0 &&
36!
690
            strstr(path, "://") == NULL) {
20!
691
        metadata_path = path;
23✔
692
    }
10✔
693

694
    loader_extract_extension(path, extension, sizeof(extension));
52✔
695

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

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

730
        uti_ref = NULL;
9✔
731
        mime_ref = NULL;
9✔
732
        ext_ref = NULL;
9✔
733
        desc_ref = NULL;
9✔
734
        preferred_mime = NULL;
9✔
735
        uti_local[0] = '\0';
9✔
736
        desc_local[0] = '\0';
9✔
737
        mime_local[0] = '\0';
9✔
738

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

814
    if (description_string != NULL && description_string[0] != '\0') {
40!
815
        description_text = description_string;
8✔
816
    } else if (desc_buffer[0] != '\0') {
34!
817
        description_text = desc_buffer;
818
    } else {
819
        description_text = "unknown content";
26✔
820
    }
821

822
    sixel_compat_snprintf(type_value,
66✔
823
                          sizeof(type_value),
824
                          "%s",
825
                          description_text);
13✔
826

827
    loader_append_chunk(message,
53✔
828
                        sizeof(message),
829
                        &offset,
830
                        "diagnostic:\n");
831
    loader_append_key_value(message,
66✔
832
                            sizeof(message),
833
                            &offset,
834
                            "file",
835
                            display_path);
13✔
836
    loader_append_key_value(message,
66✔
837
                            sizeof(message),
838
                            &offset,
839
                            "type",
840
                            type_value);
13✔
841

842
    if (mime_string != NULL && mime_string[0] != '\0') {
44!
843
        loader_append_key_value(message,
26✔
844
                                sizeof(message),
845
                                &offset,
846
                                "mime",
847
                                mime_string);
8✔
848
    }
8✔
849

850
    if (uttype[0] != '\0') {
52!
851
        loader_append_key_value(message,
16✔
852
                                sizeof(message),
853
                                &offset,
854
                                "uti",
855
                                uttype);
8✔
856
    }
8✔
857

858
    if (extension[0] != '\0') {
52✔
859
        sixel_compat_snprintf(extension_text,
50✔
860
                              sizeof(extension_text),
861
                              ".%s",
862
                              extension);
10✔
863
        loader_append_key_value(message,
50✔
864
                                sizeof(message),
865
                                &offset,
866
                                "extension",
867
                                extension_text);
10✔
868
    }
10✔
869

870
    loader_append_chunk(message,
53✔
871
                        sizeof(message),
872
                        &offset,
873
                        "  suggestions:\n");
874

875
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
876
    int quicklook_available;
10✔
877
    int quicklook_supported;
10✔
878

879
    quicklook_available = 0;
10✔
880
    quicklook_supported = 0;
10✔
881

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

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

918
    if (suggestions == 0) {
31!
919
        loader_append_chunk(message,
53✔
920
                            sizeof(message),
921
                            &offset,
922
                            "    (no thumbnail helper hints)\n");
923
    }
13✔
924

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

939
    sixel_helper_set_additional_message(message);
53✔
940

941
    free(mime_string);
44✔
942
    free(description_string);
44✔
943
}
53✔
944

945
SIXELAPI SIXELSTATUS
946
sixel_loader_new(
4,863✔
947
    sixel_loader_t   /* out */ **pploader,
948
    sixel_allocator_t/* in */  *allocator)
949
{
950
    SIXELSTATUS status = SIXEL_FALSE;
3,783✔
951
    sixel_loader_t *loader;
3,783✔
952
    sixel_allocator_t *local_allocator;
3,783✔
953

954
    loader = NULL;
3,783✔
955
    local_allocator = allocator;
4,863✔
956

957
    if (pploader == NULL) {
4,863!
958
        sixel_helper_set_additional_message(
×
959
            "sixel_loader_new: pploader is null.");
960
        status = SIXEL_BAD_ARGUMENT;
961
        goto end;
×
962
    }
963

964
    if (local_allocator == NULL) {
4,863!
965
        status = sixel_allocator_new(&local_allocator,
×
966
                                     NULL,
967
                                     NULL,
968
                                     NULL,
969
                                     NULL);
970
        if (SIXEL_FAILED(status)) {
×
971
            goto end;
×
972
        }
973
    } else {
974
        sixel_allocator_ref(local_allocator);
4,863✔
975
    }
976

977
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
4,863✔
978
                                                      sizeof(*loader));
979
    if (loader == NULL) {
4,863!
980
        sixel_helper_set_additional_message(
×
981
            "sixel_loader_new: sixel_allocator_malloc() failed.");
982
        status = SIXEL_BAD_ALLOCATION;
983
        sixel_allocator_unref(local_allocator);
×
984
        goto end;
×
985
    }
986

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

1018
    *pploader = loader;
4,863✔
1019
    status = SIXEL_OK;
3,783✔
1020

1021
end:
3,635✔
1022
    return status;
6,091✔
1023
}
1,228✔
1024

1025
SIXELAPI void
1026
sixel_loader_ref(
36,503✔
1027
    sixel_loader_t /* in */ *loader)
1028
{
1029
    if (loader == NULL) {
36,503!
1030
        return;
1031
    }
1032

1033
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
46,850✔
1034
}
11,975✔
1035

1036
SIXELAPI void
1037
sixel_loader_unref(
51,713✔
1038
    sixel_loader_t /* in */ *loader)
1039
{
1040
    sixel_allocator_t *allocator;
40,286✔
1041
    unsigned int previous;
40,286✔
1042

1043
    if (loader == NULL) {
51,713!
1044
        return;
1045
    }
1046

1047
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
51,713✔
1048
    if (previous == 1U) {
51,713✔
1049
        allocator = loader->allocator;
4,863✔
1050
        sixel_logger_close(&loader->logger);
4,863✔
1051
        sixel_allocator_free(allocator, loader->loader_order);
4,863✔
1052
        sixel_allocator_free(allocator, loader);
4,863✔
1053
        sixel_allocator_unref(allocator);
4,863✔
1054
    }
1,228✔
1055
}
17,900!
1056

1057
SIXELAPI SIXELSTATUS
1058
sixel_loader_setopt(
41,987✔
1059
    sixel_loader_t /* in */ *loader,
1060
    int            /* in */ option,
1061
    void const     /* in */ *value)
1062
{
1063
    SIXELSTATUS status = SIXEL_FALSE;
32,720✔
1064
    int const *flag;
32,720✔
1065
    unsigned char const *color;
32,720✔
1066
    char const *order;
32,720✔
1067
    char *copy;
32,720✔
1068
    sixel_allocator_t *allocator;
32,720✔
1069

1070
    flag = NULL;
32,720✔
1071
    color = NULL;
32,720✔
1072
    order = NULL;
32,720✔
1073
    copy = NULL;
32,720✔
1074
    allocator = NULL;
32,720✔
1075

1076
    if (loader == NULL) {
41,987!
1077
        sixel_helper_set_additional_message(
×
1078
            "sixel_loader_setopt: loader is null.");
1079
        status = SIXEL_BAD_ARGUMENT;
1080
        goto end0;
×
1081
    }
1082

1083
    sixel_loader_ref(loader);
32,720✔
1084

1085
    switch (option) {
41,987!
1086
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
3,635✔
1087
        flag = (int const *)value;
3,783✔
1088
        loader->fstatic = flag != NULL ? *flag : 0;
4,863!
1089
        status = SIXEL_OK;
3,783✔
1090
        break;
4,863✔
1091
    case SIXEL_LOADER_OPTION_USE_PALETTE:
3,635✔
1092
        flag = (int const *)value;
3,783✔
1093
        loader->fuse_palette = flag != NULL ? *flag : 0;
4,863!
1094
        status = SIXEL_OK;
3,783✔
1095
        break;
4,863✔
1096
    case SIXEL_LOADER_OPTION_REQCOLORS:
3,635✔
1097
        flag = (int const *)value;
3,783✔
1098
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
4,863!
1099
        if (loader->reqcolors < 1) {
4,863!
1100
            sixel_helper_set_additional_message(
×
1101
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1102
            status = SIXEL_BAD_ARGUMENT;
1103
            goto end;
×
1104
        }
1105
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
4,863!
1106
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1107
        }
1108
        status = SIXEL_OK;
1,228✔
1109
        break;
1,228✔
1110
    case SIXEL_LOADER_OPTION_BGCOLOR:
2,375✔
1111
        if (value == NULL) {
3,225✔
1112
            loader->has_bgcolor = 0;
3,108✔
1113
        } else {
798✔
1114
            color = (unsigned char const *)value;
99✔
1115
            loader->bgcolor[0] = color[0];
117✔
1116
            loader->bgcolor[1] = color[1];
117✔
1117
            loader->bgcolor[2] = color[2];
117✔
1118
            loader->has_bgcolor = 1;
117✔
1119
        }
1120
        status = SIXEL_OK;
850✔
1121
        break;
850✔
1122
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
3,635✔
1123
        flag = (int const *)value;
3,783✔
1124
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
4,863!
1125
        status = SIXEL_OK;
3,783✔
1126
        break;
4,863✔
1127
    case SIXEL_LOADER_OPTION_INSECURE:
3,635✔
1128
        flag = (int const *)value;
3,783✔
1129
        loader->finsecure = flag != NULL ? *flag : 0;
4,863!
1130
        status = SIXEL_OK;
3,783✔
1131
        break;
4,863✔
1132
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
2,375✔
1133
        loader->cancel_flag = (int const *)value;
3,225✔
1134
        status = SIXEL_OK;
2,523✔
1135
        break;
3,225✔
1136
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
2,375✔
1137
        allocator = loader->allocator;
3,225✔
1138
        sixel_allocator_free(allocator, loader->loader_order);
3,225✔
1139
        loader->loader_order = NULL;
3,225✔
1140
        if (value != NULL) {
3,225✔
1141
            order = (char const *)value;
110✔
1142
            copy = loader_strdup(order, allocator);
143✔
1143
            if (copy == NULL) {
143!
1144
                sixel_helper_set_additional_message(
×
1145
                    "sixel_loader_setopt: loader_strdup() failed.");
1146
                status = SIXEL_BAD_ALLOCATION;
1147
                goto end;
×
1148
            }
1149
            loader->loader_order = copy;
143✔
1150
        }
33✔
1151
        status = SIXEL_OK;
850✔
1152
        break;
850✔
1153
    case SIXEL_LOADER_OPTION_CONTEXT:
3,635✔
1154
        loader->context = (void *)value;
4,863✔
1155
        loader->assessment = NULL;
4,863✔
1156
        status = SIXEL_OK;
3,783✔
1157
        break;
4,863✔
1158
    case SIXEL_LOADER_OPTION_ASSESSMENT:
2,305✔
1159
        loader->assessment = (sixel_assessment_t *)value;
3,134✔
1160
        status = SIXEL_OK;
2,453✔
1161
        break;
3,134✔
1162
    default:
1163
        sixel_helper_set_additional_message(
×
1164
            "sixel_loader_setopt: unknown option.");
1165
        status = SIXEL_BAD_ARGUMENT;
1166
        goto end;
×
1167
    }
10,747✔
1168

1169
end:
31,240✔
1170
    sixel_loader_unref(loader);
41,987✔
1171

1172
end0:
31,240✔
1173
    return status;
52,734✔
1174
}
10,747✔
1175

1176
SIXELAPI char const *
1177
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
6,102✔
1178
{
1179
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
6,102!
1180
        return NULL;
1181
    }
1182
    return loader->last_loader_name;
6,102✔
1183
}
1,614✔
1184

1185
SIXELAPI char const *
1186
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
5,492✔
1187
{
1188
    if (loader == NULL || loader->last_source_path[0] == '\0') {
5,492!
1189
        return NULL;
143✔
1190
    }
1191
    return loader->last_source_path;
4,882✔
1192
}
1,471✔
1193

1194
SIXELAPI size_t
1195
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
3,051✔
1196
{
1197
    if (loader == NULL) {
3,051!
1198
        return 0u;
1199
    }
1200
    return loader->last_input_bytes;
3,051✔
1201
}
807✔
1202

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

1223
    pchunk = NULL;
4,863✔
1224
    plan = NULL;
3,783✔
1225
    entries = NULL;
4,863✔
1226
    entry_count = 0;
3,783✔
1227
    plan_length = 0;
3,783✔
1228
    plan_index = 0;
3,783✔
1229
    bgcolor = NULL;
3,783✔
1230
    reqcolors = 0;
3,783✔
1231
    assessment = NULL;
3,783✔
1232
    order_override = NULL;
3,783✔
1233
    env_order = NULL;
3,783✔
1234

1235
    if (loader == NULL) {
4,863!
1236
        sixel_helper_set_additional_message(
×
1237
            "sixel_loader_load_file: loader is null.");
1238
        status = SIXEL_BAD_ARGUMENT;
1239
        goto end0;
×
1240
    }
1241

1242
    sixel_loader_ref(loader);
3,783✔
1243

1244
    loader->log_loader_finished = 0;
4,863✔
1245
    loader->log_loader_name[0] = '\0';
4,863✔
1246
    loader->log_input_bytes = 0u;
4,863✔
1247
    loader->log_path[0] = '\0';
4,863✔
1248
    if (filename != NULL) {
4,863✔
1249
        (void)sixel_compat_snprintf(loader->log_path,
5,339✔
1250
                                    sizeof(loader->log_path),
1251
                                    "%s",
1252
                                    filename);
1,087✔
1253
    }
1,087✔
1254
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
5,203✔
1255

1256
    memset(&callback_state, 0, sizeof(callback_state));
4,123✔
1257
    callback_state.loader = loader;
5,203✔
1258
    callback_state.fn = fn_load;
5,203✔
1259
    callback_state.context = loader->context;
5,203✔
1260
    loader->callback_failed = 0;
5,203✔
1261

1262
    entry_count = loader_registry_get_entries(&entries);
5,203✔
1263

1264
    reqcolors = loader->reqcolors;
5,203✔
1265
    if (reqcolors > SIXEL_PALETTE_MAX) {
5,203!
1266
        reqcolors = SIXEL_PALETTE_MAX;
1267
    }
1268

1269
    assessment = loader->assessment;
4,863✔
1270

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

1291
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
4,833!
1292
        status = SIXEL_OK;
1293
        goto end;
×
1294
    }
1295

1296
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
4,784!
1297
        (void)sixel_compat_snprintf(loader->log_path,
5,283✔
1298
                                    sizeof(loader->log_path),
1299
                                    "%s",
1300
                                    pchunk->source_path);
1,073✔
1301
    }
1,073✔
1302

1303
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
4,833!
1304
        status = SIXEL_LOGIC_ERROR;
1305
        goto end;
×
1306
    }
1307

1308
    if (loader->has_bgcolor) {
4,833✔
1309
        bgcolor = loader->bgcolor;
117✔
1310
    }
52✔
1311

1312
    status = SIXEL_FALSE;
3,376✔
1313
    if (assessment != NULL) {
4,447✔
1314
        sixel_assessment_stage_transition(
13✔
1315
            assessment,
3✔
1316
            SIXEL_ASSESSMENT_STAGE_IMAGE_DECODE);
1317
    }
3✔
1318
    order_override = loader->loader_order;
4,822✔
1319
    /*
1320
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
1321
     * a loader order via -L/--loaders or sixel_loader_setopt().
1322
     */
1323
    if (order_override == NULL) {
4,822✔
1324
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
4,690✔
1325
        if (env_order != NULL && env_order[0] != '\0') {
4,690!
1326
            order_override = env_order;
2,543✔
1327
        }
1328
    }
1,186✔
1329

1330
    plan = sixel_allocator_malloc(loader->allocator,
7,510✔
1331
                                  entry_count * sizeof(*plan));
3,748✔
1332
    if (plan == NULL) {
4,833!
1333
        status = SIXEL_BAD_ALLOCATION;
1334
        goto end;
×
1335
    }
1336

1337
    plan_length = loader_build_plan(order_override,
6,052✔
1338
                                    entries,
1,219✔
1339
                                    entry_count,
1,219✔
1340
                                    plan,
1,219✔
1341
                                    entry_count);
1,219✔
1342

1343
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
8,023✔
1344
        if (plan[plan_index] == NULL) {
5,401!
1345
            continue;
×
1346
        }
1347
        if (plan[plan_index]->predicate != NULL &&
5,442!
1348
            plan[plan_index]->predicate(pchunk) == 0) {
1,172✔
1349
            continue;
524✔
1350
        }
1351
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
4,877!
1352
        if (plan[plan_index]->name != NULL) {
4,877!
1353
            (void)sixel_compat_snprintf(loader->log_loader_name,
6,113✔
1354
                                        sizeof(loader->log_loader_name),
1355
                                        "%s",
1356
                                        plan[plan_index]->name);
1,236✔
1357
        } else {
1,236✔
1358
            loader->log_loader_name[0] = '\0';
×
1359
        }
1360
        loader_trace_try(plan[plan_index]->name);
4,877✔
1361
        status = plan[plan_index]->backend(pchunk,
6,113✔
1362
                                           loader->fstatic,
1,236✔
1363
                                           loader->fuse_palette,
1,236✔
1364
                                           reqcolors,
1,236✔
1365
                                           bgcolor,
1,236✔
1366
                                           loader->loop_control,
1,236✔
1367
                                           loader_callback_trampoline,
1368
                                           &callback_state);
1369
        loader_trace_result(plan[plan_index]->name, status);
4,877✔
1370
        if (SIXEL_SUCCEEDED(status)) {
4,877✔
1371
            break;
1,200✔
1372
        }
1373
    }
36✔
1374

1375
    if (SIXEL_FAILED(status)) {
4,833✔
1376
        if (!loader->callback_failed &&
89!
1377
                plan_length > 0u &&
53!
1378
                plan_index >= plan_length &&
53!
1379
                pchunk != NULL) {
53!
1380
            status = SIXEL_LOADER_FAILED;
44✔
1381
            loader_publish_diagnostic(pchunk, filename);
53✔
1382
        }
13✔
1383
        goto end;
79✔
1384
    }
1385

1386
    if (plan_index < plan_length &&
5,602!
1387
            plan[plan_index] != NULL &&
4,754!
1388
            plan[plan_index]->name != NULL) {
4,754!
1389
        (void)sixel_compat_snprintf(loader->last_loader_name,
5,954✔
1390
                                    sizeof(loader->last_loader_name),
1391
                                    "%s",
1392
                                    plan[plan_index]->name);
1,200✔
1393
    } else {
1,200✔
1394
        loader->last_loader_name[0] = '\0';
×
1395
    }
1396
    loader->last_input_bytes = pchunk->size;
4,754✔
1397
    if (pchunk->source_path != NULL) {
5,954✔
1398
        size_t path_len;
3,229✔
1399

1400
        path_len = strlen(pchunk->source_path);
4,144✔
1401
        if (path_len >= sizeof(loader->last_source_path)) {
4,144!
1402
            path_len = sizeof(loader->last_source_path) - 1u;
1403
        }
1404
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
4,144✔
1405
        loader->last_source_path[path_len] = '\0';
4,144✔
1406
    } else {
1,057✔
1407
        loader->last_source_path[0] = '\0';
610✔
1408
    }
1409

1410
end:
3,614✔
1411
    if (plan != NULL) {
1,240✔
1412
        sixel_allocator_free(loader->allocator, plan);
4,833✔
1413
        plan = NULL;
3,762✔
1414
    }
1,219✔
1415
    sixel_chunk_destroy(pchunk);
4,863✔
1416
    sixel_loader_unref(loader);
4,863✔
1417

1418
end0:
3,635✔
1419
    return status;
6,091✔
1420
}
1,228✔
1421

1422
/* load image from file */
1423

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

1474
    loader = NULL;
×
1475

1476
    status = sixel_loader_new(&loader, allocator);
×
1477
    if (SIXEL_FAILED(status)) {
×
1478
        goto end;
×
1479
    }
1480

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

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

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

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

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

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

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

1530
    status = sixel_loader_setopt(loader,
×
1531
                                 SIXEL_LOADER_OPTION_CONTEXT,
1532
                                 context);
1533
    if (SIXEL_FAILED(status)) {
×
1534
        goto end;
×
1535
    }
1536

1537
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1538

1539
end:
1540
    sixel_loader_unref(loader);
×
1541

1542
    return status;
×
1543
}
1544

1545

1546
SIXELAPI size_t
1547
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
26✔
1548
{
1549
    sixel_loader_entry_t const *entries;
20✔
1550
    size_t entry_count;
20✔
1551
    size_t limit;
20✔
1552
    size_t index;
20✔
1553

1554
    entries = NULL;
26✔
1555
    entry_count = loader_registry_get_entries(&entries);
26✔
1556

1557
    if (names != NULL && max_names > 0) {
26!
1558
        limit = entry_count;
10✔
1559
        if (limit > max_names) {
13!
1560
            limit = max_names;
1561
        }
1562
        for (index = 0; index < limit; ++index) {
47✔
1563
            names[index] = entries[index].name;
34✔
1564
        }
14✔
1565
    }
3✔
1566

1567
    return entry_count;
32✔
1568
}
6✔
1569

1570

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