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

saitoha / libsixel / 20609368106

31 Dec 2025 12:57AM UTC coverage: 52.011% (-6.3%) from 58.281%
20609368106

push

github

saitoha
tests: split converter option tap suites

14741 of 45141 branches covered (32.66%)

21394 of 41134 relevant lines covered (52.01%)

3932390.77 hits per line

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

71.53
/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

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

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

155

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

160
static char *
161
loader_strdup(char const *text, sixel_allocator_t *allocator)
×
162
{
163
    char *copy;
×
164
    size_t length;
×
165

166
    if (text == NULL) {
×
167
        return NULL;
168
    }
169

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

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

182
    return copy;
×
183
}
184

185

186

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

205
    logger = NULL;
1,646✔
206
    if (loader != NULL) {
1,646!
207
        logger = &loader->logger;
1,646✔
208
    }
209
    if (logger == NULL || logger->file == NULL || !logger->active) {
1,646!
210
        return;
1,646✔
211
    }
212

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

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

266
static SIXELSTATUS
267
loader_callback_trampoline(sixel_frame_t *frame, void *data)
944✔
268
{
269
    sixel_loader_callback_state_t *state;
944✔
270
    SIXELSTATUS status;
944✔
271
    sixel_loader_t *loader;
944✔
272

273
    state = (sixel_loader_callback_state_t *)data;
944✔
274
    loader = NULL;
944✔
275
    if (state == NULL || state->fn == NULL) {
944!
276
        return SIXEL_BAD_ARGUMENT;
277
    }
278

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

290
    status = state->fn(frame, state->context);
944✔
291
    if (SIXEL_FAILED(status) && state->loader != NULL) {
944!
292
        state->loader->callback_failed = 1;
8✔
293
    }
294

295
    return status;
296
}
297

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

305
    for (index = 0; index < plan_length; ++index) {
834!
306
        if (plan[index] == entry) {
×
307
            return 1;
308
        }
309
    }
310

311
    return 0;
312
}
313

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

323
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
×
324
        left = (unsigned char)token[index];
×
325
        right = (unsigned char)name[index];
×
326
        if (tolower(left) != tolower(right)) {
×
327
            return 0;
328
        }
329
    }
330

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

335
    return 1;
336
}
337

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

346
    for (index = 0; index < entry_count; ++index) {
×
347
        if (loader_token_matches(token,
×
348
                                 token_length,
349
                                 entries[index].name)) {
×
350
            return &entries[index];
351
        }
352
    }
353

354
    return NULL;
355
}
356

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

392
    plan_length = 0;
834✔
393
    index = 0;
834✔
394
    cursor = order;
834✔
395
    token_start = order;
834✔
396
    token_end = order;
834✔
397
    token_length = 0;
834✔
398
    entry = NULL;
834✔
399
    limit = plan_capacity;
834✔
400

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

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

459
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
2,502✔
460
        entry = &entries[index];
1,668✔
461
        if (!entry->default_enabled) {
1,668✔
462
            continue;
834✔
463
        }
464
        if (!loader_plan_contains(plan, plan_length, entry)) {
834!
465
            plan[plan_length] = entry;
834✔
466
            ++plan_length;
834✔
467
        }
468
    }
469

470
    return plan_length;
834✔
471
}
472

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

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

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

490
    available = capacity - *offset;
168✔
491
    if (available == 0) {
168!
492
        return;
493
    }
494

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

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

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

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

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

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

538
    if ((size_t)written >= sizeof(line)) {
80!
539
        line[sizeof(line) - 1u] = '\0';
×
540
    }
541

542
    loader_append_chunk(dest, capacity, offset, line);
80✔
543
}
1!
544

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

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

555
    buffer[0] = '\0';
22✔
556

557
    if (path == NULL) {
22✔
558
        return;
559
    }
560

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

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

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

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

589
    if (dot[1] == '\0') {
1!
590
        return;
591
    }
592

593
    dot += 1;
72✔
594

595
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
72!
596
        buffer[index] = (char)tolower((unsigned char)dot[index]);
54✔
597
    }
598
    buffer[index] = '\0';
18✔
599
}
1!
600

601

602

603

604

605

606

607

608

609

610

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

619
    buffer[0] = '\0';
620
    if (source == NULL) {
1!
621
        return;
622
    }
623

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

633

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

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

678
    if (pchunk != NULL && pchunk->source_path != NULL) {
22!
679
        path = pchunk->source_path;
680
    } else if (filename != NULL) {
4!
681
        path = filename;
682
    }
683

684
    if (path != NULL && strcmp(path, "-") != 0) {
18!
685
        display_path = path;
18✔
686
    }
687

688
    if (path != NULL && strcmp(path, "-") != 0 &&
18!
689
            strstr(path, "://") == NULL) {
18!
690
        metadata_path = path;
22✔
691
    }
692

693
    loader_extract_extension(path, extension, sizeof(extension));
22✔
694

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

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

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

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

813
    if (description_string != NULL && description_string[0] != '\0') {
18!
814
        description_text = description_string;
815
    } else if (desc_buffer[0] != '\0') {
4!
816
        description_text = desc_buffer;
817
    } else {
818
        description_text = "unknown content";
4✔
819
    }
820

821
    sixel_compat_snprintf(type_value,
22✔
822
                          sizeof(type_value),
823
                          "%s",
824
                          description_text);
825

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

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

849
    if (uttype[0] != '\0') {
22!
850
        loader_append_key_value(message,
×
851
                                sizeof(message),
852
                                &offset,
853
                                "uti",
854
                                uttype);
855
    }
856

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

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

874
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
875
    int quicklook_available;
876
    int quicklook_supported;
877

878
    quicklook_available = 0;
879
    quicklook_supported = 0;
880

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

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

917
    if (suggestions == 0) {
×
918
        loader_append_chunk(message,
22✔
919
                            sizeof(message),
920
                            &offset,
921
                            "    (no thumbnail helper hints)\n");
922
    }
923

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

938
    sixel_helper_set_additional_message(message);
22✔
939

940
    free(mime_string);
22✔
941
    free(description_string);
22✔
942
}
22✔
943

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

953
    loader = NULL;
834✔
954
    local_allocator = allocator;
834✔
955

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

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

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

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

1017
    *pploader = loader;
834✔
1018
    status = SIXEL_OK;
834✔
1019

1020
end:
834✔
1021
    return status;
834✔
1022
}
1023

1024
SIXELAPI void
1025
sixel_loader_ref(
8,546✔
1026
    sixel_loader_t /* in */ *loader)
1027
{
1028
    if (loader == NULL) {
6,572!
1029
        return;
1030
    }
1031

1032
    ++loader->ref;
6,572✔
1033
}
1034

1035
SIXELAPI void
1036
sixel_loader_unref(
9,380✔
1037
    sixel_loader_t /* in */ *loader)
1038
{
1039
    sixel_allocator_t *allocator;
9,380✔
1040

1041
    if (loader == NULL) {
9,380!
1042
        return;
1043
    }
1044

1045
    if (--loader->ref == 0) {
9,380✔
1046
        allocator = loader->allocator;
834✔
1047
        sixel_logger_close(&loader->logger);
834✔
1048
        sixel_allocator_free(allocator, loader->loader_order);
834✔
1049
        sixel_allocator_free(allocator, loader);
834✔
1050
        sixel_allocator_unref(allocator);
834✔
1051
    }
1052
}
1!
1053

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

1067
    flag = NULL;
7,712✔
1068
    color = NULL;
7,712✔
1069
    order = NULL;
7,712✔
1070
    copy = NULL;
7,712✔
1071
    allocator = NULL;
7,712✔
1072

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

1080
    sixel_loader_ref(loader);
7,712✔
1081

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

1166
end:
7,712✔
1167
    sixel_loader_unref(loader);
7,712✔
1168

1169
end0:
7,712✔
1170
    return status;
7,712✔
1171
}
1172

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

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

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

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

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

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

1239
    sixel_loader_ref(loader);
834✔
1240

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

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

1259
    entry_count = loader_registry_get_entries(&entries);
834✔
1260

1261
    reqcolors = loader->reqcolors;
834✔
1262
    if (reqcolors > SIXEL_PALETTE_MAX) {
834!
1263
        reqcolors = SIXEL_PALETTE_MAX;
1264
    }
1265

1266
    assessment = loader->assessment;
834✔
1267

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

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

1293
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
834!
1294
        (void)sixel_compat_snprintf(loader->log_path,
646✔
1295
                                    sizeof(loader->log_path),
1296
                                    "%s",
1297
                                    pchunk->source_path);
1298
    }
1299

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

1305
    if (loader->has_bgcolor) {
834✔
1306
        bgcolor = loader->bgcolor;
28✔
1307
    }
1308

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

1327
    plan = sixel_allocator_malloc(loader->allocator,
834✔
1328
                                  entry_count * sizeof(*plan));
1329
    if (plan == NULL) {
834!
1330
        status = SIXEL_BAD_ALLOCATION;
×
1331
        goto end;
×
1332
    }
1333

1334
    plan_length = loader_build_plan(order_override,
834✔
1335
                                    entries,
1336
                                    entry_count,
1337
                                    plan,
1338
                                    entry_count);
1339

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

1372
    if (SIXEL_FAILED(status)) {
834✔
1373
        if (!loader->callback_failed &&
30!
1374
                plan_length > 0u &&
22!
1375
                plan_index >= plan_length &&
22!
1376
                pchunk != NULL) {
22!
1377
            status = SIXEL_LOADER_FAILED;
22✔
1378
            loader_publish_diagnostic(pchunk, filename);
22✔
1379
        }
1380
        goto end;
30✔
1381
    }
1382

1383
    if (plan_index < plan_length &&
804!
1384
            plan[plan_index] != NULL &&
804!
1385
            plan[plan_index]->name != NULL) {
804!
1386
        (void)sixel_compat_snprintf(loader->last_loader_name,
804✔
1387
                                    sizeof(loader->last_loader_name),
1388
                                    "%s",
1389
                                    plan[plan_index]->name);
1390
    } else {
1391
        loader->last_loader_name[0] = '\0';
×
1392
    }
1393
    loader->last_input_bytes = pchunk->size;
804✔
1394
    if (pchunk->source_path != NULL) {
804✔
1395
        size_t path_len;
620✔
1396

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

1407
end:
834✔
1408
    if (plan != NULL) {
✔
1409
        sixel_allocator_free(loader->allocator, plan);
834✔
1410
        plan = NULL;
834✔
1411
    }
1412
    sixel_chunk_destroy(pchunk);
834✔
1413
    sixel_loader_unref(loader);
834✔
1414

1415
end0:
834✔
1416
    return status;
834✔
1417
}
1418

1419
/* load image from file */
1420

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

1471
    loader = NULL;
×
1472

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

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

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

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

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

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

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

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

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

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

1536
end:
×
1537
    sixel_loader_unref(loader);
×
1538

1539
    return status;
×
1540
}
1541

1542

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

1551
    entries = NULL;
8✔
1552
    entry_count = loader_registry_get_entries(&entries);
8✔
1553

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

1564
    return entry_count;
8✔
1565
}
1566

1567
#if HAVE_TESTS
1568
/* Simple allocation smoke test to exercise loader test harness entry. */
1569
static int
1570
loader_test_allocation_smoke(void)
1571
{
1572
    int nret = EXIT_FAILURE;
1573
    unsigned char *ptr = malloc(16);
1574

1575
    nret = EXIT_SUCCESS;
1576
    goto error;
1577

1578
error:
1579
    free(ptr);
1580
    return nret;
1581
}
1582

1583

1584
SIXELAPI int
1585
sixel_loader_tests_main(void)
×
1586
{
1587
    int nret = EXIT_FAILURE;
×
1588
    size_t i;
×
1589
    typedef int (* testcase)(void);
×
1590

1591
    static testcase const testcases[] = {
×
1592
        loader_test_allocation_smoke,
1593
    };
1594

1595
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1596
        nret = testcases[i]();
×
1597
        if (nret != EXIT_SUCCESS) {
×
1598
            goto error;
1599
        }
1600
    }
1601

1602
    nret = EXIT_SUCCESS;
1603

1604
error:
×
1605
    return nret;
×
1606
}
1607
#endif  /* HAVE_TESTS */
1608

1609
/* emacs Local Variables:      */
1610
/* emacs mode: c               */
1611
/* emacs tab-width: 4          */
1612
/* emacs indent-tabs-mode: nil */
1613
/* emacs c-basic-offset: 4     */
1614
/* emacs End:                  */
1615
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1616
/* 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