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

saitoha / libsixel / 20466639304

23 Dec 2025 04:53PM UTC coverage: 51.46% (-6.3%) from 57.773%
20466639304

push

github

saitoha
build: fix windows find path in images meson build

14511 of 44933 branches covered (32.29%)

21089 of 40981 relevant lines covered (51.46%)

3915123.44 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
#include "config.h"
27
#if !defined(_POSIX_C_SOURCE)
28
# define _POSIX_C_SOURCE 200809L
29
#endif
30

31
/* STDC_HEADERS */
32
#include <stdio.h>
33
#include <stdlib.h>
34

35
#if HAVE_STRING_H
36
# include <string.h>
37
#endif
38
#if HAVE_CTYPE_H
39
# include <ctype.h>
40
#endif
41
#if HAVE_STDARG_H
42
# include <stdarg.h>
43
#endif
44
#if HAVE_SYS_TYPES_H
45
# include <sys/types.h>
46
#endif
47
#if HAVE_UNISTD_H
48
# include <unistd.h>
49
#endif
50
#if !defined(PATH_MAX)
51
#define PATH_MAX 4096
52
#endif
53
#if HAVE_FCNTL_H
54
# include <fcntl.h>
55
#endif
56
#if HAVE_SYS_TIME_H
57
# include <sys/time.h>
58
#elif HAVE_TIME_H
59
# include <time.h>
60
#endif  /* HAVE_SYS_TIME_H HAVE_TIME_H */
61
#if defined(_WIN32)
62
# include <windows.h>
63
#endif
64
#if HAVE_ERRNO_H
65
# include <errno.h>
66
#endif
67
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
68
# include <CoreServices/CoreServices.h>
69
#endif
70
#if HAVE_SYS_WAIT_H
71
# include <sys/wait.h>
72
#endif
73
#if HAVE_SPAWN_H
74
# include <spawn.h>
75
#endif
76
#if HAVE_LIMITS_H
77
# include <limits.h>
78
#endif
79
#if HAVE_DIRENT_H
80
# include <dirent.h>
81
#endif
82

83
#if !defined(HAVE_MEMCPY)
84
# define memcpy(d, s, n) (bcopy ((s), (d), (n)))
85
#endif
86

87
#include <sixel.h>
88
#include "loader.h"
89
#include "loader-builtin.h"
90
#include "loader-common.h"
91
#include "loader-coregraphics.h"
92
#include "loader-gd.h"
93
#include "loader-gdk-pixbuf2.h"
94
#include "loader-gnome-thumbnailer.h"
95
#include "loader-libjpeg.h"
96
#include "loader-libpng.h"
97
#include "loader-quicklook.h"
98
#include "loader-registry.h"
99
#include "loader-wic.h"
100
#include "compat_stub.h"
101
#include "frame.h"
102
#include "chunk.h"
103
#include "allocator.h"
104
#include "assessment.h"
105
#include "encoder.h"
106
#include "logger.h"
107

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

138
typedef struct sixel_loader_callback_state {
139
    sixel_loader_t *loader;
140
    sixel_load_image_function fn;
141
    void *context;
142
} sixel_loader_callback_state_t;
143

144

145
#if HAVE_POSIX_SPAWNP
146
extern char **environ;
147
#endif
148

149
static char *
150
loader_strdup(char const *text, sixel_allocator_t *allocator)
×
151
{
152
    char *copy;
×
153
    size_t length;
×
154

155
    if (text == NULL) {
×
156
        return NULL;
157
    }
158

159
    length = strlen(text) + 1;
×
160
    copy = (char *)sixel_allocator_malloc(allocator, length);
×
161
    if (copy == NULL) {
×
162
        return NULL;
163
    }
164

165
#if HAVE_STRCPY_S
166
    (void)strcpy_s(copy, (rsize_t)(length - 1), text);
167
#else
168
    memcpy(copy, text, length);
×
169
#endif
170

171
    return copy;
×
172
}
173

174

175

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

194
    logger = NULL;
1,590✔
195
    if (loader != NULL) {
1,590!
196
        logger = &loader->logger;
1,590✔
197
    }
198
    if (logger == NULL || logger->file == NULL || !logger->active) {
1,590!
199
        return;
1,590✔
200
    }
201

202
    message[0] = '\0';
×
203
#if defined(__clang__)
204
#pragma clang diagnostic push
205
#pragma clang diagnostic ignored "-Wformat-nonliteral"
206
#elif defined(__GNUC__)
207
#pragma GCC diagnostic push
208
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
209
#endif
210
    va_start(args, fmt);
×
211
    if (fmt != NULL) {
×
212
#if defined(__clang__)
213
#pragma clang diagnostic push
214
#pragma clang diagnostic ignored "-Wformat-nonliteral"
215
#elif defined(__GNUC__)
216
#pragma GCC diagnostic push
217
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
218
#endif
219
        (void)sixel_compat_vsnprintf(message, sizeof(message), fmt, args);
×
220
#if defined(__clang__)
221
#pragma clang diagnostic pop
222
#elif defined(__GNUC__)
223
#pragma GCC diagnostic pop
224
#endif
225
    }
226
    va_end(args);
×
227
#if defined(__clang__)
228
#pragma clang diagnostic pop
229
#elif defined(__GNUC__)
230
#pragma GCC diagnostic pop
231
#endif
232

233
    sixel_logger_logf(logger,
×
234
                      "worker",
235
                      "loader",
236
                      event,
237
                      -1,
238
                      -1,
239
                      0,
240
                      0,
241
                      0,
242
                      0,
243
                      "%s",
244
                      message);
245
}
1!
246

247
static SIXELSTATUS
248
loader_callback_trampoline(sixel_frame_t *frame, void *data)
916✔
249
{
250
    sixel_loader_callback_state_t *state;
916✔
251
    SIXELSTATUS status;
916✔
252
    sixel_loader_t *loader;
916✔
253

254
    state = (sixel_loader_callback_state_t *)data;
916✔
255
    loader = NULL;
916✔
256
    if (state == NULL || state->fn == NULL) {
916!
257
        return SIXEL_BAD_ARGUMENT;
258
    }
259

260
    loader = state->loader;
916✔
261
    if (loader != NULL && loader->log_loader_finished == 0) {
916!
262
        loader_log_stage(loader,
784✔
263
                         "finish",
264
                         "path=%s loader=%s bytes=%zu",
265
                         loader->log_path,
784✔
266
                         loader->log_loader_name,
784✔
267
                         loader->log_input_bytes);
268
        loader->log_loader_finished = 1;
784✔
269
    }
270

271
    status = state->fn(frame, state->context);
916✔
272
    if (SIXEL_FAILED(status) && state->loader != NULL) {
916!
273
        state->loader->callback_failed = 1;
8✔
274
    }
275

276
    return status;
277
}
278

279
static int
280
loader_plan_contains(sixel_loader_entry_t const **plan,
281
                     size_t plan_length,
282
                     sixel_loader_entry_t const *entry)
283
{
284
    size_t index;
285

286
    for (index = 0; index < plan_length; ++index) {
806!
287
        if (plan[index] == entry) {
×
288
            return 1;
289
        }
290
    }
291

292
    return 0;
293
}
294

295
static int
296
loader_token_matches(char const *token,
×
297
                     size_t token_length,
298
                     char const *name)
299
{
300
    size_t index;
×
301
    unsigned char left;
×
302
    unsigned char right;
×
303

304
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
×
305
        left = (unsigned char)token[index];
×
306
        right = (unsigned char)name[index];
×
307
        if (tolower(left) != tolower(right)) {
×
308
            return 0;
309
        }
310
    }
311

312
    if (index != token_length || name[index] != '\0') {
×
313
        return 0;
×
314
    }
315

316
    return 1;
317
}
318

319
static sixel_loader_entry_t const *
320
loader_lookup_token(char const *token,
×
321
                    size_t token_length,
322
                    sixel_loader_entry_t const *entries,
323
                    size_t entry_count)
324
{
325
    size_t index;
×
326

327
    for (index = 0; index < entry_count; ++index) {
×
328
        if (loader_token_matches(token,
×
329
                                 token_length,
330
                                 entries[index].name)) {
×
331
            return &entries[index];
332
        }
333
    }
334

335
    return NULL;
336
}
337

338
/*
339
 * loader_build_plan
340
 *
341
 * Translate a comma separated list into an execution plan that reorders the
342
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
343
 * are ignored so that new builds remain forward compatible.
344
 *
345
 *    user input "gd,coregraphics"
346
 *                |
347
 *                v
348
 *        +-------------------+
349
 *        | prioritized list  |
350
 *        +-------------------+
351
 *                |
352
 *                v
353
 *        +-------------------+
354
 *        | default fallbacks |
355
 *        +-------------------+
356
 */
357
static size_t
358
loader_build_plan(char const *order,
806✔
359
                  sixel_loader_entry_t const *entries,
360
                  size_t entry_count,
361
                  sixel_loader_entry_t const **plan,
362
                  size_t plan_capacity)
363
{
364
    size_t plan_length;
806✔
365
    size_t index;
806✔
366
    char const *cursor;
806✔
367
    char const *token_start;
806✔
368
    char const *token_end;
806✔
369
    size_t token_length;
806✔
370
    sixel_loader_entry_t const *entry;
806✔
371
    size_t limit;
806✔
372

373
    plan_length = 0;
806✔
374
    index = 0;
806✔
375
    cursor = order;
806✔
376
    token_start = order;
806✔
377
    token_end = order;
806✔
378
    token_length = 0;
806✔
379
    entry = NULL;
806✔
380
    limit = plan_capacity;
806✔
381

382
    if (order != NULL && plan != NULL && plan_capacity > 0) {
806!
383
        token_start = order;
384
        cursor = order;
385
        while (*cursor != '\0') {
×
386
            if (*cursor == ',') {
×
387
                token_end = cursor;
×
388
                while (token_start < token_end &&
×
389
                       isspace((unsigned char)*token_start)) {
×
390
                    ++token_start;
×
391
                }
392
                while (token_end > token_start &&
×
393
                       isspace((unsigned char)token_end[-1])) {
×
394
                    --token_end;
×
395
                }
396
                token_length = (size_t)(token_end - token_start);
×
397
                if (token_length > 0) {
×
398
                    entry = loader_lookup_token(token_start,
×
399
                                                token_length,
400
                                                entries,
401
                                                entry_count);
402
                    if (entry != NULL &&
×
403
                        !loader_plan_contains(plan,
×
404
                                              plan_length,
405
                                              entry) &&
×
406
                        plan_length < limit) {
407
                        plan[plan_length] = entry;
×
408
                        ++plan_length;
×
409
                    }
410
                }
411
                token_start = cursor + 1;
×
412
            }
413
            ++cursor;
×
414
        }
415

416
        token_end = cursor;
×
417
        while (token_start < token_end &&
×
418
               isspace((unsigned char)*token_start)) {
×
419
            ++token_start;
×
420
        }
421
        while (token_end > token_start &&
×
422
               isspace((unsigned char)token_end[-1])) {
×
423
            --token_end;
×
424
        }
425
        token_length = (size_t)(token_end - token_start);
×
426
        if (token_length > 0) {
×
427
            entry = loader_lookup_token(token_start,
×
428
                                        token_length,
429
                                        entries,
430
                                        entry_count);
431
            if (entry != NULL &&
×
432
                !loader_plan_contains(plan, plan_length, entry) &&
×
433
                plan_length < limit) {
434
                plan[plan_length] = entry;
×
435
                ++plan_length;
×
436
            }
437
        }
438
    }
439

440
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
2,418✔
441
        entry = &entries[index];
1,612✔
442
        if (!entry->default_enabled) {
1,612✔
443
            continue;
806✔
444
        }
445
        if (!loader_plan_contains(plan, plan_length, entry)) {
806!
446
            plan[plan_length] = entry;
806✔
447
            ++plan_length;
806✔
448
        }
449
    }
450

451
    return plan_length;
806✔
452
}
453

454
static void
455
loader_append_chunk(char *dest,
168✔
456
                    size_t capacity,
457
                    size_t *offset,
458
                    char const *chunk)
459
{
460
    size_t available;
168✔
461
    size_t length;
168✔
462

463
    if (dest == NULL || offset == NULL || chunk == NULL) {
168!
464
        return;
465
    }
466

467
    if (*offset >= capacity) {
168!
468
        return;
469
    }
470

471
    available = capacity - *offset;
168✔
472
    if (available == 0) {
168!
473
        return;
474
    }
475

476
    length = strlen(chunk);
168✔
477
    if (length >= available) {
168!
478
        if (available == 0) {
×
479
            return;
480
        }
481
        length = available - 1u;
×
482
    }
483

484
    if (length > 0) {
168!
485
        memcpy(dest + *offset, chunk, length);
168✔
486
        *offset += length;
168✔
487
    }
488

489
    if (*offset < capacity) {
168!
490
        dest[*offset] = '\0';
168✔
491
    } else {
492
        dest[capacity - 1u] = '\0';
×
493
    }
494
}
1!
495

496
static void
497
loader_append_key_value(char *dest,
80✔
498
                        size_t capacity,
499
                        size_t *offset,
500
                        char const *label,
501
                        char const *value)
502
{
503
    char line[128];
80✔
504
    int written;
80✔
505

506
    if (value == NULL || value[0] == '\0') {
80!
507
        return;
×
508
    }
509

510
    written = sixel_compat_snprintf(line,
80✔
511
                                    sizeof(line),
512
                                    "  %-10s: %s\n",
513
                                    label,
514
                                    value);
515
    if (written < 0) {
80!
516
        return;
517
    }
518

519
    if ((size_t)written >= sizeof(line)) {
80!
520
        line[sizeof(line) - 1u] = '\0';
×
521
    }
522

523
    loader_append_chunk(dest, capacity, offset, line);
80✔
524
}
1!
525

526
static void
527
loader_extract_extension(char const *path, char *buffer, size_t capacity)
22✔
528
{
529
    char const *dot;
22✔
530
    size_t index;
22✔
531

532
    if (buffer == NULL || capacity == 0) {
22!
533
        return;
534
    }
535

536
    buffer[0] = '\0';
22✔
537

538
    if (path == NULL) {
22✔
539
        return;
540
    }
541

542
    dot = strrchr(path, '.');
18✔
543
    if (dot == NULL || dot[1] == '\0') {
18!
544
        return;
545
    }
546

547
#if defined(_WIN32)
548
    {
549
        char const *slash;
550
        char const *backslash;
551

552
        slash = strrchr(path, '/');
553
        backslash = strrchr(path, '\\');
554
        if ((slash != NULL && dot < slash) ||
555
                (backslash != NULL && dot < backslash)) {
556
            return;
557
        }
558
    }
559
#else
560
    {
561
        char const *slash;
18✔
562

563
        slash = strrchr(path, '/');
18✔
564
        if (slash != NULL && dot < slash) {
18!
565
            return;
566
        }
567
    }
1!
568
#endif
569

570
    if (dot[1] == '\0') {
1!
571
        return;
572
    }
573

574
    dot += 1;
72✔
575

576
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
72!
577
        buffer[index] = (char)tolower((unsigned char)dot[index]);
54✔
578
    }
579
    buffer[index] = '\0';
18✔
580
}
1!
581

582

583

584

585

586

587

588

589

590

591

592
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
593
static void
594
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
595
{
596
    if (buffer == NULL || capacity == 0) {
2!
597
        return;
598
    }
599

600
    buffer[0] = '\0';
601
    if (source == NULL) {
1!
602
        return;
603
    }
604

605
    if (!CFStringGetCString(source,
2!
606
                             buffer,
607
                             (CFIndex)capacity,
608
                             kCFStringEncodingUTF8)) {
609
        buffer[0] = '\0';
610
    }
611
}
612
#endif
613

614

615
static void
616
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
22✔
617
                          char const *filename)
618
{
619
    enum { description_length = 128 };
22✔
620
    enum { uttype_length = 128 };
22✔
621
    enum { extension_length = 32 };
22✔
622
    enum { message_length = 768 };
22✔
623
    char message[message_length];
22✔
624
    char type_value[description_length];
22✔
625
    char extension_text[extension_length + 2];
22✔
626
    char uttype[uttype_length];
22✔
627
    char desc_buffer[description_length];
22✔
628
    char extension[extension_length];
22✔
629
    char const *path;
22✔
630
    char const *display_path;
22✔
631
    char const *metadata_path;
22✔
632
    char const *description_text;
22✔
633
    char *mime_string;
22✔
634
    char *description_string;
22✔
635
    size_t offset;
22✔
636
    int gnome_available;
22✔
637
    int gnome_has_dirs;
22✔
638
    int gnome_has_match;
22✔
639
    int suggestions;
22✔
640

641
    message[0] = '\0';
22✔
642
    type_value[0] = '\0';
22✔
643
    extension_text[0] = '\0';
22✔
644
    uttype[0] = '\0';
22✔
645
    desc_buffer[0] = '\0';
22✔
646
    extension[0] = '\0';
22✔
647
    path = NULL;
22✔
648
    display_path = "(stdin)";
22✔
649
    metadata_path = NULL;
22✔
650
    description_text = NULL;
22✔
651
    mime_string = NULL;
22✔
652
    description_string = NULL;
22✔
653
    offset = 0u;
22✔
654
    gnome_available = 0;
22✔
655
    gnome_has_dirs = 0;
22✔
656
    gnome_has_match = 0;
22✔
657
    suggestions = 0;
22✔
658

659
    if (pchunk != NULL && pchunk->source_path != NULL) {
22!
660
        path = pchunk->source_path;
661
    } else if (filename != NULL) {
4!
662
        path = filename;
663
    }
664

665
    if (path != NULL && strcmp(path, "-") != 0) {
18!
666
        display_path = path;
18✔
667
    }
668

669
    if (path != NULL && strcmp(path, "-") != 0 &&
18!
670
            strstr(path, "://") == NULL) {
18!
671
        metadata_path = path;
22✔
672
    }
673

674
    loader_extract_extension(path, extension, sizeof(extension));
22✔
675

676
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
677
    if (metadata_path != NULL) {
22✔
678
        /*
679
         * Collect MIME metadata via file(1) when fork() and friends are
680
         * available.  Windows builds compiled with clang64 lack these
681
         * interfaces, so the thumbnail helpers remain disabled there.
682
         */
683
        mime_string = thumbnailer_guess_content_type(metadata_path);
18✔
684
        description_string = thumbnailer_run_file(metadata_path, NULL);
18✔
685
    }
686
#else
687
    (void)metadata_path;
688
#endif
689

690
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
691
#if defined(__clang__)
692
    /*
693
     * Allow use of legacy UTType C APIs when compiling with the
694
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
695
     * so we must intentionally silence the deprecation warnings here.
696
     */
697
#pragma clang diagnostic push
698
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
699
#endif
700
    {
701
        CFStringRef uti_ref;
702
        CFStringRef mime_ref;
703
        CFStringRef ext_ref;
704
        CFStringRef desc_ref;
705
        CFStringRef preferred_mime;
706
        char uti_local[uttype_length];
707
        char desc_local[description_length];
708
        char mime_local[64];
709

710
        uti_ref = NULL;
711
        mime_ref = NULL;
712
        ext_ref = NULL;
713
        desc_ref = NULL;
714
        preferred_mime = NULL;
715
        uti_local[0] = '\0';
716
        desc_local[0] = '\0';
717
        mime_local[0] = '\0';
718

719
        if (mime_string != NULL) {
2✔
720
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
721
                                                 mime_string,
722
                                                 kCFStringEncodingUTF8);
723
        }
724
        if (mime_ref != NULL) {
2✔
725
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
726
                kUTTagClassMIMEType,
727
                mime_ref,
728
                NULL);
729
        }
730
        if (uti_ref == NULL && extension[0] != '\0') {
3!
731
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
732
                                                extension,
733
                                                kCFStringEncodingUTF8);
734
            if (ext_ref != NULL) {
×
735
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
736
                    kUTTagClassFilenameExtension,
737
                    ext_ref,
738
                    NULL);
739
            }
740
        }
741
        if (uti_ref != NULL) {
2✔
742
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
743
            desc_ref = UTTypeCopyDescription(uti_ref);
744
            if (desc_ref != NULL) {
1!
745
                loader_copy_cfstring(desc_ref,
746
                                     desc_local,
747
                                     sizeof(desc_local));
748
                CFRelease(desc_ref);
749
                desc_ref = NULL;
750
            }
751
            if (mime_string == NULL) {
1!
752
                preferred_mime = UTTypeCopyPreferredTagWithClass(
753
                    uti_ref,
754
                    kUTTagClassMIMEType);
755
                if (preferred_mime != NULL) {
×
756
                    loader_copy_cfstring(preferred_mime,
757
                                         mime_local,
758
                                         sizeof(mime_local));
759
                    CFRelease(preferred_mime);
760
                    preferred_mime = NULL;
761
                }
762
                if (mime_local[0] != '\0') {
×
763
                    mime_string = thumbnailer_strdup(mime_local);
764
                }
765
            }
766
        }
767
        if (mime_ref != NULL) {
2✔
768
            CFRelease(mime_ref);
769
        }
770
        if (ext_ref != NULL) {
1!
771
            CFRelease(ext_ref);
772
        }
773
        if (uti_ref != NULL) {
2✔
774
            CFRelease(uti_ref);
775
        }
776
        if (uti_local[0] != '\0') {
2✔
777
            sixel_compat_snprintf(uttype,
778
                                  sizeof(uttype),
779
                                  "%s",
780
                                  uti_local);
781
        }
782
        if (desc_local[0] != '\0') {
2✔
783
            sixel_compat_snprintf(desc_buffer,
784
                                  sizeof(desc_buffer),
785
                                  "%s",
786
                                  desc_local);
787
        }
788
    }
789
#if defined(__clang__)
790
#pragma clang diagnostic pop
791
#endif
792
#endif
793

794
    if (description_string != NULL && description_string[0] != '\0') {
18!
795
        description_text = description_string;
796
    } else if (desc_buffer[0] != '\0') {
4!
797
        description_text = desc_buffer;
798
    } else {
799
        description_text = "unknown content";
4✔
800
    }
801

802
    sixel_compat_snprintf(type_value,
22✔
803
                          sizeof(type_value),
804
                          "%s",
805
                          description_text);
806

807
    loader_append_chunk(message,
22✔
808
                        sizeof(message),
809
                        &offset,
810
                        "diagnostic:\n");
811
    loader_append_key_value(message,
22✔
812
                            sizeof(message),
813
                            &offset,
814
                            "file",
815
                            display_path);
816
    loader_append_key_value(message,
22✔
817
                            sizeof(message),
818
                            &offset,
819
                            "type",
820
                            type_value);
821

822
    if (mime_string != NULL && mime_string[0] != '\0') {
22!
823
        loader_append_key_value(message,
18✔
824
                                sizeof(message),
825
                                &offset,
826
                                "mime",
827
                                mime_string);
828
    }
829

830
    if (uttype[0] != '\0') {
22!
831
        loader_append_key_value(message,
×
832
                                sizeof(message),
833
                                &offset,
834
                                "uti",
835
                                uttype);
836
    }
837

838
    if (extension[0] != '\0') {
22✔
839
        sixel_compat_snprintf(extension_text,
18✔
840
                              sizeof(extension_text),
841
                              ".%s",
842
                              extension);
843
        loader_append_key_value(message,
18✔
844
                                sizeof(message),
845
                                &offset,
846
                                "extension",
847
                                extension_text);
848
    }
849

850
    loader_append_chunk(message,
22✔
851
                        sizeof(message),
852
                        &offset,
853
                        "  suggestions:\n");
854

855
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
856
    int quicklook_available;
857
    int quicklook_supported;
858

859
    quicklook_available = 0;
860
    quicklook_supported = 0;
861

862
    quicklook_available = loader_registry_entry_available("quicklook");
863
    if (quicklook_available) {
1!
864
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
865
    }
866
    if (quicklook_supported) {
1!
867
        loader_append_chunk(message,
868
                            sizeof(message),
869
                            &offset,
870
                            "    - QuickLook rendered a preview during "
871
                            "the probe; try -j quicklook.\n");
872
        suggestions += 1;
873
    }
874
#endif
875

876
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
877
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
22✔
878
    if (gnome_available) {
22!
879
        loader_probe_gnome_thumbnailers(mime_string,
22✔
880
                                        &gnome_has_dirs,
881
                                        &gnome_has_match);
882
        if (gnome_has_dirs && gnome_has_match) {
22!
883
            loader_append_chunk(message,
×
884
                                sizeof(message),
885
                                &offset,
886
                                "    - GNOME thumbnailer definitions match "
887
                                "this MIME type; try -j gnome-thumbnailer.\n"
888
                                );
889
            suggestions += 1;
×
890
        }
891
    }
892
#else
893
    (void)gnome_available;
894
    (void)gnome_has_dirs;
895
    (void)gnome_has_match;
896
#endif
897

898
    if (suggestions == 0) {
×
899
        loader_append_chunk(message,
22✔
900
                            sizeof(message),
901
                            &offset,
902
                            "    (no thumbnail helper hints)\n");
903
    }
904

905
    if (suggestions > 0) {
22!
906
        loader_append_chunk(message,
×
907
                            sizeof(message),
908
                            &offset,
909
                            "  hint       : Enable one of the suggested "
910
                            "loaders with -j.\n");
911
    } else {
912
        loader_append_chunk(message,
22✔
913
                            sizeof(message),
914
                            &offset,
915
                            "  hint       : Convert the file to PNG or "
916
                            "enable optional loaders.\n");
917
    }
918

919
    sixel_helper_set_additional_message(message);
22✔
920

921
    free(mime_string);
22✔
922
    free(description_string);
22✔
923
}
22✔
924

925
SIXELAPI SIXELSTATUS
926
sixel_loader_new(
806✔
927
    sixel_loader_t   /* out */ **pploader,
928
    sixel_allocator_t/* in */  *allocator)
929
{
930
    SIXELSTATUS status = SIXEL_FALSE;
806✔
931
    sixel_loader_t *loader;
806✔
932
    sixel_allocator_t *local_allocator;
806✔
933

934
    loader = NULL;
806✔
935
    local_allocator = allocator;
806✔
936

937
    if (pploader == NULL) {
806!
938
        sixel_helper_set_additional_message(
×
939
            "sixel_loader_new: pploader is null.");
940
        status = SIXEL_BAD_ARGUMENT;
×
941
        goto end;
×
942
    }
943

944
    if (local_allocator == NULL) {
806!
945
        status = sixel_allocator_new(&local_allocator,
×
946
                                     NULL,
947
                                     NULL,
948
                                     NULL,
949
                                     NULL);
950
        if (SIXEL_FAILED(status)) {
×
951
            goto end;
×
952
        }
953
    } else {
954
        sixel_allocator_ref(local_allocator);
806✔
955
    }
956

957
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
806✔
958
                                                      sizeof(*loader));
959
    if (loader == NULL) {
806!
960
        sixel_helper_set_additional_message(
×
961
            "sixel_loader_new: sixel_allocator_malloc() failed.");
962
        status = SIXEL_BAD_ALLOCATION;
×
963
        sixel_allocator_unref(local_allocator);
×
964
        goto end;
×
965
    }
966

967
    loader->ref = 1;
806✔
968
    loader->fstatic = 0;
806✔
969
    loader->fuse_palette = 0;
806✔
970
    loader->reqcolors = SIXEL_PALETTE_MAX;
806✔
971
    loader->bgcolor[0] = 0;
806✔
972
    loader->bgcolor[1] = 0;
806✔
973
    loader->bgcolor[2] = 0;
806✔
974
    loader->has_bgcolor = 0;
806✔
975
    loader->loop_control = SIXEL_LOOP_AUTO;
806✔
976
    loader->finsecure = 0;
806✔
977
    loader->cancel_flag = NULL;
806✔
978
    loader->context = NULL;
806✔
979
    /*
980
     * Initialize a private logger. The helper reuses an existing global
981
     * logger sink when present so loader markers share the timeline with
982
     * upstream stages without requiring sixel_loader_setopt().
983
     */
984
    sixel_logger_init(&loader->logger);
806✔
985
    (void)sixel_logger_prepare_env(&loader->logger);
806✔
986
    loader->assessment = NULL;
806✔
987
    loader->loader_order = NULL;
806✔
988
    loader->allocator = local_allocator;
806✔
989
    loader->last_loader_name[0] = '\0';
806✔
990
    loader->last_source_path[0] = '\0';
806✔
991
    loader->last_input_bytes = 0u;
806✔
992
    loader->callback_failed = 0;
806✔
993
    loader->log_loader_finished = 0;
806✔
994
    loader->log_path[0] = '\0';
806✔
995
    loader->log_loader_name[0] = '\0';
806✔
996
    loader->log_input_bytes = 0u;
806✔
997

998
    *pploader = loader;
806✔
999
    status = SIXEL_OK;
806✔
1000

1001
end:
806✔
1002
    return status;
806✔
1003
}
1004

1005
SIXELAPI void
1006
sixel_loader_ref(
8,238✔
1007
    sixel_loader_t /* in */ *loader)
1008
{
1009
    if (loader == NULL) {
6,341!
1010
        return;
1011
    }
1012

1013
    ++loader->ref;
6,341✔
1014
}
1015

1016
SIXELAPI void
1017
sixel_loader_unref(
9,044✔
1018
    sixel_loader_t /* in */ *loader)
1019
{
1020
    sixel_allocator_t *allocator;
9,044✔
1021

1022
    if (loader == NULL) {
9,044!
1023
        return;
1024
    }
1025

1026
    if (--loader->ref == 0) {
9,044✔
1027
        allocator = loader->allocator;
806✔
1028
        sixel_logger_close(&loader->logger);
806✔
1029
        sixel_allocator_free(allocator, loader->loader_order);
806✔
1030
        sixel_allocator_free(allocator, loader);
806✔
1031
        sixel_allocator_unref(allocator);
806✔
1032
    }
1033
}
1!
1034

1035
SIXELAPI SIXELSTATUS
1036
sixel_loader_setopt(
7,432✔
1037
    sixel_loader_t /* in */ *loader,
1038
    int            /* in */ option,
1039
    void const     /* in */ *value)
1040
{
1041
    SIXELSTATUS status = SIXEL_FALSE;
7,432✔
1042
    int const *flag;
7,432✔
1043
    unsigned char const *color;
7,432✔
1044
    char const *order;
7,432✔
1045
    char *copy;
7,432✔
1046
    sixel_allocator_t *allocator;
7,432✔
1047

1048
    flag = NULL;
7,432✔
1049
    color = NULL;
7,432✔
1050
    order = NULL;
7,432✔
1051
    copy = NULL;
7,432✔
1052
    allocator = NULL;
7,432✔
1053

1054
    if (loader == NULL) {
7,432!
1055
        sixel_helper_set_additional_message(
×
1056
            "sixel_loader_setopt: loader is null.");
1057
        status = SIXEL_BAD_ARGUMENT;
×
1058
        goto end0;
×
1059
    }
1060

1061
    sixel_loader_ref(loader);
7,432✔
1062

1063
    switch (option) {
7,432!
1064
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
806✔
1065
        flag = (int const *)value;
806✔
1066
        loader->fstatic = flag != NULL ? *flag : 0;
806!
1067
        status = SIXEL_OK;
806✔
1068
        break;
806✔
1069
    case SIXEL_LOADER_OPTION_USE_PALETTE:
806✔
1070
        flag = (int const *)value;
806✔
1071
        loader->fuse_palette = flag != NULL ? *flag : 0;
806!
1072
        status = SIXEL_OK;
806✔
1073
        break;
806✔
1074
    case SIXEL_LOADER_OPTION_REQCOLORS:
806✔
1075
        flag = (int const *)value;
806✔
1076
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
806!
1077
        if (loader->reqcolors < 1) {
806!
1078
            sixel_helper_set_additional_message(
×
1079
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1080
            status = SIXEL_BAD_ARGUMENT;
×
1081
            goto end;
×
1082
        }
1083
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
806!
1084
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1085
        }
1086
        status = SIXEL_OK;
1087
        break;
1088
    case SIXEL_LOADER_OPTION_BGCOLOR:
656✔
1089
        if (value == NULL) {
656✔
1090
            loader->has_bgcolor = 0;
628✔
1091
        } else {
1092
            color = (unsigned char const *)value;
28✔
1093
            loader->bgcolor[0] = color[0];
28✔
1094
            loader->bgcolor[1] = color[1];
28✔
1095
            loader->bgcolor[2] = color[2];
28✔
1096
            loader->has_bgcolor = 1;
28✔
1097
        }
1098
        status = SIXEL_OK;
1099
        break;
1100
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
806✔
1101
        flag = (int const *)value;
806✔
1102
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
806!
1103
        status = SIXEL_OK;
806✔
1104
        break;
806✔
1105
    case SIXEL_LOADER_OPTION_INSECURE:
806✔
1106
        flag = (int const *)value;
806✔
1107
        loader->finsecure = flag != NULL ? *flag : 0;
806!
1108
        status = SIXEL_OK;
806✔
1109
        break;
806✔
1110
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
656✔
1111
        loader->cancel_flag = (int const *)value;
656✔
1112
        status = SIXEL_OK;
656✔
1113
        break;
656✔
1114
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
656✔
1115
        allocator = loader->allocator;
656✔
1116
        sixel_allocator_free(allocator, loader->loader_order);
656✔
1117
        loader->loader_order = NULL;
656✔
1118
        if (value != NULL) {
656!
1119
            order = (char const *)value;
×
1120
            copy = loader_strdup(order, allocator);
×
1121
            if (copy == NULL) {
×
1122
                sixel_helper_set_additional_message(
×
1123
                    "sixel_loader_setopt: loader_strdup() failed.");
1124
                status = SIXEL_BAD_ALLOCATION;
×
1125
                goto end;
×
1126
            }
1127
            loader->loader_order = copy;
×
1128
        }
1129
        status = SIXEL_OK;
1130
        break;
1131
    case SIXEL_LOADER_OPTION_CONTEXT:
806✔
1132
        loader->context = (void *)value;
806✔
1133
        loader->assessment = NULL;
806✔
1134
        status = SIXEL_OK;
806✔
1135
        break;
806✔
1136
    case SIXEL_LOADER_OPTION_ASSESSMENT:
628✔
1137
        loader->assessment = (sixel_assessment_t *)value;
628✔
1138
        status = SIXEL_OK;
628✔
1139
        break;
628✔
1140
    default:
×
1141
        sixel_helper_set_additional_message(
×
1142
            "sixel_loader_setopt: unknown option.");
1143
        status = SIXEL_BAD_ARGUMENT;
×
1144
        goto end;
×
1145
    }
1146

1147
end:
7,432✔
1148
    sixel_loader_unref(loader);
7,432✔
1149

1150
end0:
7,432✔
1151
    return status;
7,432✔
1152
}
1153

1154
SIXELAPI char const *
1155
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
1,216✔
1156
{
1157
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
1,216!
1158
        return NULL;
1159
    }
1160
    return loader->last_loader_name;
1,216✔
1161
}
1162

1163
SIXELAPI char const *
1164
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
1,032✔
1165
{
1166
    if (loader == NULL || loader->last_source_path[0] == '\0') {
1,032!
1167
        return NULL;
1168
    }
1169
    return loader->last_source_path;
848✔
1170
}
1171

1172
SIXELAPI size_t
1173
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
608✔
1174
{
1175
    if (loader == NULL) {
608!
1176
        return 0u;
1177
    }
1178
    return loader->last_input_bytes;
608✔
1179
}
1180

1181
SIXELAPI SIXELSTATUS
1182
sixel_loader_load_file(
806✔
1183
    sixel_loader_t         /* in */ *loader,
1184
    char const             /* in */ *filename,
1185
    sixel_load_image_function /* in */ fn_load)
1186
{
1187
    SIXELSTATUS status = SIXEL_FALSE;
806✔
1188
    sixel_chunk_t *pchunk;
806✔
1189
    sixel_loader_entry_t const **plan;
806✔
1190
    sixel_loader_entry_t const *entries;
806✔
1191
    size_t entry_count;
806✔
1192
    size_t plan_length;
806✔
1193
    size_t plan_index;
806✔
1194
    unsigned char *bgcolor;
806✔
1195
    int reqcolors;
806✔
1196
    sixel_assessment_t *assessment;
806✔
1197
    char const *order_override;
806✔
1198
    char const *env_order;
806✔
1199
    sixel_loader_callback_state_t callback_state;
806✔
1200

1201
    pchunk = NULL;
806✔
1202
    plan = NULL;
806✔
1203
    entries = NULL;
806✔
1204
    entry_count = 0;
806✔
1205
    plan_length = 0;
806✔
1206
    plan_index = 0;
806✔
1207
    bgcolor = NULL;
806✔
1208
    reqcolors = 0;
806✔
1209
    assessment = NULL;
806✔
1210
    order_override = NULL;
806✔
1211
    env_order = NULL;
806✔
1212

1213
    if (loader == NULL) {
806!
1214
        sixel_helper_set_additional_message(
×
1215
            "sixel_loader_load_file: loader is null.");
1216
        status = SIXEL_BAD_ARGUMENT;
×
1217
        goto end0;
×
1218
    }
1219

1220
    sixel_loader_ref(loader);
806✔
1221

1222
    loader->log_loader_finished = 0;
806✔
1223
    loader->log_loader_name[0] = '\0';
806✔
1224
    loader->log_input_bytes = 0u;
806✔
1225
    loader->log_path[0] = '\0';
806✔
1226
    if (filename != NULL) {
806✔
1227
        (void)sixel_compat_snprintf(loader->log_path,
618✔
1228
                                    sizeof(loader->log_path),
1229
                                    "%s",
1230
                                    filename);
1231
    }
1232
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
806✔
1233

1234
    memset(&callback_state, 0, sizeof(callback_state));
806✔
1235
    callback_state.loader = loader;
806✔
1236
    callback_state.fn = fn_load;
806✔
1237
    callback_state.context = loader->context;
806✔
1238
    loader->callback_failed = 0;
806✔
1239

1240
    entry_count = loader_registry_get_entries(&entries);
806✔
1241

1242
    reqcolors = loader->reqcolors;
806✔
1243
    if (reqcolors > SIXEL_PALETTE_MAX) {
806!
1244
        reqcolors = SIXEL_PALETTE_MAX;
1245
    }
1246

1247
    assessment = loader->assessment;
806✔
1248

1249
    /*
1250
     *  Assessment pipeline sketch:
1251
     *
1252
     *      +-------------+        +--------------+
1253
     *      | chunk read  | -----> | image decode |
1254
     *      +-------------+        +--------------+
1255
     *
1256
     *  The loader owns the hand-off.  Chunk I/O ends before any decoder runs,
1257
     *  so we time the read span in the encoder and pivot to decode once the
1258
     *  chunk is populated.
1259
     */
1260
    status = sixel_chunk_new(&pchunk,
806✔
1261
                             filename,
1262
                             loader->finsecure,
1263
                             loader->cancel_flag,
1264
                             loader->allocator);
1265
    if (status != SIXEL_OK) {
806!
1266
        goto end;
×
1267
    }
1268

1269
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
806!
1270
        status = SIXEL_OK;
×
1271
        goto end;
×
1272
    }
1273

1274
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
806!
1275
        (void)sixel_compat_snprintf(loader->log_path,
618✔
1276
                                    sizeof(loader->log_path),
1277
                                    "%s",
1278
                                    pchunk->source_path);
1279
    }
1280

1281
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
806!
1282
        status = SIXEL_LOGIC_ERROR;
×
1283
        goto end;
×
1284
    }
1285

1286
    if (loader->has_bgcolor) {
806✔
1287
        bgcolor = loader->bgcolor;
28✔
1288
    }
1289

1290
    status = SIXEL_FALSE;
806✔
1291
    if (assessment != NULL) {
806✔
1292
        sixel_assessment_stage_transition(
4✔
1293
            assessment,
1294
            SIXEL_ASSESSMENT_STAGE_IMAGE_DECODE);
1295
    }
1296
    order_override = loader->loader_order;
806✔
1297
    /*
1298
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
1299
     * a loader order via -L/--loaders or sixel_loader_setopt().
1300
     */
1301
    if (order_override == NULL) {
806!
1302
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
806✔
1303
        if (env_order != NULL && env_order[0] != '\0') {
806!
1304
            order_override = env_order;
806✔
1305
        }
1306
    }
1307

1308
    plan = sixel_allocator_malloc(loader->allocator,
806✔
1309
                                  entry_count * sizeof(*plan));
1310
    if (plan == NULL) {
806!
1311
        status = SIXEL_BAD_ALLOCATION;
×
1312
        goto end;
×
1313
    }
1314

1315
    plan_length = loader_build_plan(order_override,
806✔
1316
                                    entries,
1317
                                    entry_count,
1318
                                    plan,
1319
                                    entry_count);
1320

1321
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
1,642✔
1322
        if (plan[plan_index] == NULL) {
806!
1323
            continue;
×
1324
        }
1325
        if (plan[plan_index]->predicate != NULL &&
806!
1326
            plan[plan_index]->predicate(pchunk) == 0) {
×
1327
            continue;
×
1328
        }
1329
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
806!
1330
        if (plan[plan_index]->name != NULL) {
806!
1331
            (void)sixel_compat_snprintf(loader->log_loader_name,
806✔
1332
                                        sizeof(loader->log_loader_name),
1333
                                        "%s",
1334
                                        plan[plan_index]->name);
1335
        } else {
1336
            loader->log_loader_name[0] = '\0';
×
1337
        }
1338
        loader_trace_try(plan[plan_index]->name);
806✔
1339
        status = plan[plan_index]->backend(pchunk,
806✔
1340
                                           loader->fstatic,
1341
                                           loader->fuse_palette,
1342
                                           reqcolors,
1343
                                           bgcolor,
1344
                                           loader->loop_control,
1345
                                           loader_callback_trampoline,
1346
                                           &callback_state);
1347
        loader_trace_result(plan[plan_index]->name, status);
806✔
1348
        if (SIXEL_SUCCEEDED(status)) {
806✔
1349
            break;
1350
        }
1351
    }
1352

1353
    if (SIXEL_FAILED(status)) {
806✔
1354
        if (!loader->callback_failed &&
30!
1355
                plan_length > 0u &&
22!
1356
                plan_index >= plan_length &&
22!
1357
                pchunk != NULL) {
22!
1358
            status = SIXEL_LOADER_FAILED;
22✔
1359
            loader_publish_diagnostic(pchunk, filename);
22✔
1360
        }
1361
        goto end;
30✔
1362
    }
1363

1364
    if (plan_index < plan_length &&
776!
1365
            plan[plan_index] != NULL &&
776!
1366
            plan[plan_index]->name != NULL) {
776!
1367
        (void)sixel_compat_snprintf(loader->last_loader_name,
776✔
1368
                                    sizeof(loader->last_loader_name),
1369
                                    "%s",
1370
                                    plan[plan_index]->name);
1371
    } else {
1372
        loader->last_loader_name[0] = '\0';
×
1373
    }
1374
    loader->last_input_bytes = pchunk->size;
776✔
1375
    if (pchunk->source_path != NULL) {
776✔
1376
        size_t path_len;
592✔
1377

1378
        path_len = strlen(pchunk->source_path);
592✔
1379
        if (path_len >= sizeof(loader->last_source_path)) {
592!
1380
            path_len = sizeof(loader->last_source_path) - 1u;
1381
        }
1382
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
592✔
1383
        loader->last_source_path[path_len] = '\0';
592✔
1384
    } else {
1385
        loader->last_source_path[0] = '\0';
184✔
1386
    }
1387

1388
end:
806✔
1389
    if (plan != NULL) {
✔
1390
        sixel_allocator_free(loader->allocator, plan);
806✔
1391
        plan = NULL;
806✔
1392
    }
1393
    sixel_chunk_destroy(pchunk);
806✔
1394
    sixel_loader_unref(loader);
806✔
1395

1396
end0:
806✔
1397
    return status;
806✔
1398
}
1399

1400
/* load image from file */
1401

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

1452
    loader = NULL;
×
1453

1454
    status = sixel_loader_new(&loader, allocator);
×
1455
    if (SIXEL_FAILED(status)) {
×
1456
        goto end;
×
1457
    }
1458

1459
    status = sixel_loader_setopt(loader,
×
1460
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1461
                                 &fstatic);
1462
    if (SIXEL_FAILED(status)) {
×
1463
        goto end;
×
1464
    }
1465

1466
    status = sixel_loader_setopt(loader,
×
1467
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1468
                                 &fuse_palette);
1469
    if (SIXEL_FAILED(status)) {
×
1470
        goto end;
×
1471
    }
1472

1473
    status = sixel_loader_setopt(loader,
×
1474
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1475
                                 &reqcolors);
1476
    if (SIXEL_FAILED(status)) {
×
1477
        goto end;
×
1478
    }
1479

1480
    status = sixel_loader_setopt(loader,
×
1481
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1482
                                 bgcolor);
1483
    if (SIXEL_FAILED(status)) {
×
1484
        goto end;
×
1485
    }
1486

1487
    status = sixel_loader_setopt(loader,
×
1488
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1489
                                 &loop_control);
1490
    if (SIXEL_FAILED(status)) {
×
1491
        goto end;
×
1492
    }
1493

1494
    status = sixel_loader_setopt(loader,
×
1495
                                 SIXEL_LOADER_OPTION_INSECURE,
1496
                                 &finsecure);
1497
    if (SIXEL_FAILED(status)) {
×
1498
        goto end;
×
1499
    }
1500

1501
    status = sixel_loader_setopt(loader,
×
1502
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1503
                                 cancel_flag);
1504
    if (SIXEL_FAILED(status)) {
×
1505
        goto end;
×
1506
    }
1507

1508
    status = sixel_loader_setopt(loader,
×
1509
                                 SIXEL_LOADER_OPTION_CONTEXT,
1510
                                 context);
1511
    if (SIXEL_FAILED(status)) {
×
1512
        goto end;
×
1513
    }
1514

1515
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1516

1517
end:
×
1518
    sixel_loader_unref(loader);
×
1519

1520
    return status;
×
1521
}
1522

1523

1524
SIXELAPI size_t
1525
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
8✔
1526
{
1527
    sixel_loader_entry_t const *entries;
8✔
1528
    size_t entry_count;
8✔
1529
    size_t limit;
8✔
1530
    size_t index;
8✔
1531

1532
    entries = NULL;
8✔
1533
    entry_count = loader_registry_get_entries(&entries);
8✔
1534

1535
    if (names != NULL && max_names > 0) {
8!
1536
        limit = entry_count;
4✔
1537
        if (limit > max_names) {
4!
1538
            limit = max_names;
1539
        }
1540
        for (index = 0; index < limit; ++index) {
12✔
1541
            names[index] = entries[index].name;
8✔
1542
        }
1543
    }
1544

1545
    return entry_count;
8✔
1546
}
1547

1548
#if HAVE_TESTS
1549
/* Simple allocation smoke test to exercise loader test harness entry. */
1550
static int
1551
loader_test_allocation_smoke(void)
1552
{
1553
    int nret = EXIT_FAILURE;
1554
    unsigned char *ptr = malloc(16);
1555

1556
    nret = EXIT_SUCCESS;
1557
    goto error;
1558

1559
error:
1560
    free(ptr);
1561
    return nret;
1562
}
1563

1564

1565
SIXELAPI int
1566
sixel_loader_tests_main(void)
×
1567
{
1568
    int nret = EXIT_FAILURE;
×
1569
    size_t i;
×
1570
    typedef int (* testcase)(void);
×
1571

1572
    static testcase const testcases[] = {
×
1573
        loader_test_allocation_smoke,
1574
    };
1575

1576
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1577
        nret = testcases[i]();
×
1578
        if (nret != EXIT_SUCCESS) {
×
1579
            goto error;
1580
        }
1581
    }
1582

1583
    nret = EXIT_SUCCESS;
1584

1585
error:
×
1586
    return nret;
×
1587
}
1588
#endif  /* HAVE_TESTS */
1589

1590
/* emacs Local Variables:      */
1591
/* emacs mode: c               */
1592
/* emacs tab-width: 4          */
1593
/* emacs indent-tabs-mode: nil */
1594
/* emacs c-basic-offset: 4     */
1595
/* emacs End:                  */
1596
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1597
/* 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