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

saitoha / libsixel / 19815657505

01 Dec 2025 08:08AM UTC coverage: 41.258% (-3.8%) from 45.065%
19815657505

push

github

saitoha
python: update shared api.py

10038 of 36877 branches covered (27.22%)

13200 of 31994 relevant lines covered (41.26%)

1167057.32 hits per line

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

38.81
/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_SYS_WAIT_H
68
# include <sys/wait.h>
69
#endif
70
#if HAVE_SPAWN_H
71
# include <spawn.h>
72
#endif
73
#if HAVE_LIMITS_H
74
# include <limits.h>
75
#endif
76
#if HAVE_DIRENT_H
77
# include <dirent.h>
78
#endif
79

80
#ifdef HAVE_GDK_PIXBUF2
81
# if HAVE_DIAGNOSTIC_TYPEDEF_REDEFINITION
82
#  pragma GCC diagnostic push
83
#  pragma GCC diagnostic ignored "-Wtypedef-redefinition"
84
# endif
85
# include <gdk-pixbuf/gdk-pixbuf.h>
86
# include <gdk-pixbuf/gdk-pixbuf-simple-anim.h>
87
# if HAVE_DIAGNOSTIC_TYPEDEF_REDEFINITION
88
#  pragma GCC diagnostic pop
89
# endif
90
#endif
91
#if HAVE_GD
92
# include <gd.h>
93
#endif
94
#if HAVE_LIBPNG
95
# include <png.h>
96
#endif  /* HAVE_LIBPNG */
97
#if HAVE_JPEG
98
# include <jpeglib.h>
99
#endif  /* HAVE_JPEG */
100
#if HAVE_COREGRAPHICS
101
# include <ApplicationServices/ApplicationServices.h>
102
# include <ImageIO/ImageIO.h>
103
#endif  /* HAVE_COREGRAPHICS */
104
#if HAVE_QUICKLOOK
105
# include <CoreServices/CoreServices.h>
106
# include <QuickLook/QuickLook.h>
107
#endif  /* HAVE_QUICKLOOK */
108

109
#if HAVE_QUICKLOOK_THUMBNAILING
110
CGImageRef
111
sixel_quicklook_thumbnail_create(CFURLRef url, CGSize max_size);
112
#endif
113

114
#if !defined(HAVE_MEMCPY)
115
# define memcpy(d, s, n) (bcopy ((s), (d), (n)))
116
#endif
117

118
#include <sixel.h>
119
#include "loader.h"
120
#include "compat_stub.h"
121
#include "frame.h"
122
#include "chunk.h"
123
#include "frompnm.h"
124
#include "fromgif.h"
125
#include "allocator.h"
126
#include "assessment.h"
127
#include "encoder.h"
128
#include "logger.h"
129

130
#define SIXEL_THUMBNAILER_DEFAULT_SIZE 512
131

132
static int loader_trace_enabled;
133
static int thumbnailer_default_size_hint =
134
    SIXEL_THUMBNAILER_DEFAULT_SIZE;
135
static int thumbnailer_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
136
static int thumbnailer_size_hint_initialized;
137

138
#if HAVE_POSIX_SPAWNP
139
extern char **environ;
140
#endif
141

142
struct sixel_loader {
143
    int ref;
144
    int fstatic;
145
    int fuse_palette;
146
    int reqcolors;
147
    unsigned char bgcolor[3];
148
    int has_bgcolor;
149
    int loop_control;
150
    int finsecure;
151
    int const *cancel_flag;
152
    void *context;
153
    sixel_logger_t logger;
154
    /*
155
     * Pointer to the active assessment observer.
156
     *
157
     * Loader clients opt in by setting SIXEL_LOADER_OPTION_ASSESSMENT.
158
     * Context slots remain usable for arbitrary callback state without
159
     * tripping the observer wiring.
160
     */
161
    sixel_assessment_t *assessment;
162
    char *loader_order;
163
    sixel_allocator_t *allocator;
164
    char last_loader_name[64];
165
    char last_source_path[PATH_MAX];
166
    size_t last_input_bytes;
167
    int callback_failed;
168
    int log_loader_finished;
169
    char log_path[PATH_MAX];
170
    char log_loader_name[64];
171
    size_t log_input_bytes;
172
};
173

174
static char *
175
loader_strdup(char const *text, sixel_allocator_t *allocator)
×
176
{
177
    char *copy;
178
    size_t length;
179

180
    if (text == NULL) {
×
181
        return NULL;
×
182
    }
183

184
    length = strlen(text) + 1;
×
185
    copy = (char *)sixel_allocator_malloc(allocator, length);
×
186
    if (copy == NULL) {
×
187
        return NULL;
×
188
    }
189

190
#if HAVE_STRCPY_S
191
    (void)strcpy_s(copy, (rsize_t)(length - 1), text);
192
#else
193
    memcpy(copy, text, length);
×
194
#endif
195

196
    return copy;
×
197
}
198

199
/*
200
 * loader_thumbnailer_initialize_size_hint
201
 *
202
 * Establish the runtime default thumbnail size hint.  The helper inspects
203
 * $SIXEL_THUMBNAILER_HINT_SIZE once so administrators can override
204
 * SIXEL_THUMBNAILER_DEFAULT_SIZE without recompiling.  Subsequent calls
205
 * become no-ops to avoid clobbering adjustments made through
206
 * sixel_helper_set_thumbnail_size_hint().
207
 */
208
static void
209
loader_thumbnailer_initialize_size_hint(void)
762✔
210
{
211
    char const *env_value;
212
    char *endptr;
213
    long parsed;
214

215
    if (thumbnailer_size_hint_initialized) {
762✔
216
        return;
616✔
217
    }
218

219
    thumbnailer_size_hint_initialized = 1;
726✔
220
    thumbnailer_default_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
726✔
221
    thumbnailer_size_hint = thumbnailer_default_size_hint;
726✔
222

223
    env_value = getenv("SIXEL_THUMBNAILER_HINT_SIZE");
726✔
224
    if (env_value == NULL || env_value[0] == '\0') {
726!
225
        return;
726✔
226
    }
227

228
    errno = 0;
×
229
    parsed = strtol(env_value, &endptr, 10);
×
230
    if (errno != 0) {
×
231
        return;
×
232
    }
233
    if (endptr == env_value || *endptr != '\0') {
×
234
        return;
×
235
    }
236
    if (parsed <= 0) {
×
237
        return;
×
238
    }
239
    if (parsed > (long)INT_MAX) {
×
240
        parsed = (long)INT_MAX;
×
241
    }
242

243
    thumbnailer_default_size_hint = (int)parsed;
×
244
    thumbnailer_size_hint = thumbnailer_default_size_hint;
×
245
}
154✔
246

247
/*
248
 * sixel_helper_set_loader_trace
249
 *
250
 * Toggle verbose loader tracing so debugging output can be collected.
251
 *
252
 * Arguments:
253
 *     enable - non-zero enables tracing, zero disables it.
254
 */
255
void
256
sixel_helper_set_loader_trace(int enable)
776✔
257
{
258
    loader_trace_enabled = enable ? 1 : 0;
776✔
259
}
776✔
260

261
/*
262
 * sixel_helper_set_thumbnail_size_hint
263
 *
264
 * Record the caller's preferred maximum thumbnail dimension.
265
 *
266
 * Arguments:
267
 *     size - requested dimension in pixels; non-positive resets to default.
268
 */
269
void
270
sixel_helper_set_thumbnail_size_hint(int size)
761✔
271
{
272
    loader_thumbnailer_initialize_size_hint();
761✔
273

274
    if (size > 0) {
761✔
275
        thumbnailer_size_hint = size;
155✔
276
    } else {
31✔
277
        thumbnailer_size_hint = thumbnailer_default_size_hint;
606✔
278
    }
279
}
761✔
280

281
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
282
/*
283
 * loader_trace_message
284
 *
285
 * Emit a formatted trace message when verbose loader tracing is enabled.
286
 *
287
 * Arguments:
288
 *     format - printf-style message template.
289
 *     ...    - arguments consumed according to the format string.
290
 */
291
static void
292
loader_trace_message(char const *format, ...)
25✔
293
{
294
    va_list args;
295

296
    if (!loader_trace_enabled) {
25!
297
        return;
25✔
298
    }
299

300
    fprintf(stderr, "libsixel: ");
×
301

302
    va_start(args, format);
×
303
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
304
# if defined(__clang__)
305
#  pragma clang diagnostic push
306
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
307
# elif defined(__GNUC__)
308
#  pragma GCC diagnostic push
309
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
310
# endif
311
#endif
312
    vfprintf(stderr, format, args);
×
313
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
314
# if defined(__clang__)
315
#  pragma clang diagnostic pop
316
# elif defined(__GNUC__)
317
#  pragma GCC diagnostic pop
318
# endif
319
#endif
320
    va_end(args);
×
321

322
    fprintf(stderr, "\n");
×
323
}
5✔
324
#endif  /* HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK */
325

326
static void
327
loader_trace_try(char const *name)
754✔
328
{
329
    if (loader_trace_enabled) {
754✔
330
        fprintf(stderr, "libsixel: trying %s loader\n", name);
15✔
331
    }
3✔
332
}
754✔
333

334
static void
335
loader_trace_result(char const *name, SIXELSTATUS status)
754✔
336
{
337
    if (!loader_trace_enabled) {
754✔
338
        return;
739✔
339
    }
340
    if (SIXEL_SUCCEEDED(status)) {
15!
341
        fprintf(stderr, "libsixel: loader %s succeeded\n", name);
15✔
342
    } else {
3✔
343
        fprintf(stderr, "libsixel: loader %s failed (%s)\n",
×
344
                name, sixel_helper_format_error(status));
345
    }
346
}
154✔
347

348
/*
349
 * Emit loader stage markers.
350
 *
351
 * Loader callbacks run the downstream pipeline synchronously, so the finish
352
 * marker must be issued before invoking fn_load() to avoid inflating the
353
 * loader span. The helper keeps the formatting consistent with
354
 * sixel_encoder_log_stage() without depending on encoder internals.
355
 */
356
static void
357
loader_log_stage(sixel_loader_t *loader,
1,507✔
358
                 char const *event,
359
                 char const *fmt,
360
                 ...)
361
{
362
    sixel_logger_t *logger;
363
    char message[256];
364
    va_list args;
365

366
    logger = NULL;
1,507✔
367
    if (loader != NULL) {
1,507!
368
        logger = &loader->logger;
1,507✔
369
    }
303✔
370
    if (logger == NULL || logger->file == NULL || !logger->active) {
1,507!
371
        return;
1,507✔
372
    }
373

374
    message[0] = '\0';
×
375
#if defined(__clang__)
376
#pragma clang diagnostic push
377
#pragma clang diagnostic ignored "-Wformat-nonliteral"
378
#elif defined(__GNUC__)
379
#pragma GCC diagnostic push
380
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
381
#endif
382
    va_start(args, fmt);
×
383
    if (fmt != NULL) {
×
384
#if defined(__clang__)
385
#pragma clang diagnostic push
386
#pragma clang diagnostic ignored "-Wformat-nonliteral"
387
#elif defined(__GNUC__)
388
#pragma GCC diagnostic push
389
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
390
#endif
391
        (void)vsnprintf(message, sizeof(message), fmt, args);
×
392
#if defined(__clang__)
393
#pragma clang diagnostic pop
394
#elif defined(__GNUC__)
395
#pragma GCC diagnostic pop
396
#endif
397
    }
398
    va_end(args);
×
399
#if defined(__clang__)
400
#pragma clang diagnostic pop
401
#elif defined(__GNUC__)
402
#pragma GCC diagnostic pop
403
#endif
404

405
    sixel_logger_logf(logger,
×
406
                      "worker",
407
                      "loader",
408
                      event,
409
                      -1,
410
                      -1,
411
                      0,
412
                      0,
413
                      0,
414
                      0,
415
                      "%s",
416
                      message);
417
}
303✔
418

419
typedef SIXELSTATUS (*sixel_loader_backend)(
420
    sixel_chunk_t const       *pchunk,
421
    int                        fstatic,
422
    int                        fuse_palette,
423
    int                        reqcolors,
424
    unsigned char             *bgcolor,
425
    int                        loop_control,
426
    sixel_load_image_function  fn_load,
427
    void                      *context);
428

429
typedef int (*sixel_loader_predicate)(sixel_chunk_t const *pchunk);
430

431
typedef struct sixel_loader_entry {
432
    char const              *name;
433
    sixel_loader_backend     backend;
434
    sixel_loader_predicate   predicate;
435
    int                      default_enabled;
436
} sixel_loader_entry_t;
437

438
typedef struct sixel_loader_callback_state {
439
    sixel_loader_t *loader;
440
    sixel_load_image_function fn;
441
    void *context;
442
} sixel_loader_callback_state_t;
443

444
static SIXELSTATUS
445
loader_callback_trampoline(sixel_frame_t *frame, void *data)
911✔
446
{
447
    sixel_loader_callback_state_t *state;
448
    SIXELSTATUS status;
449
    sixel_loader_t *loader;
450

451
    state = (sixel_loader_callback_state_t *)data;
911✔
452
    loader = NULL;
911✔
453
    if (state == NULL || state->fn == NULL) {
911!
454
        return SIXEL_BAD_ARGUMENT;
×
455
    }
456

457
    loader = state->loader;
911✔
458
    if (loader != NULL && loader->log_loader_finished == 0) {
911!
459
        loader_log_stage(loader,
896✔
460
                         "finish",
461
                         "path=%s loader=%s bytes=%zu",
462
                         loader->log_path,
746✔
463
                         loader->log_loader_name,
746✔
464
                         loader->log_input_bytes);
150✔
465
        loader->log_loader_finished = 1;
746✔
466
    }
150✔
467

468
    status = state->fn(frame, state->context);
1,026✔
469
    if (SIXEL_FAILED(status) && state->loader != NULL) {
1,026!
470
        state->loader->callback_failed = 1;
10✔
471
    }
2✔
472

473
    return status;
911✔
474
}
183✔
475

476
static int
477
loader_plan_contains(sixel_loader_entry_t const **plan,
902✔
478
                     size_t plan_length,
479
                     sixel_loader_entry_t const *entry)
480
{
481
    size_t index;
482

483
    for (index = 0; index < plan_length; ++index) {
1,053!
484
        if (plan[index] == entry) {
151!
485
            return 1;
×
486
        }
487
    }
151✔
488

489
    return 0;
902✔
490
}
302✔
491

492
static int
493
loader_token_matches(char const *token,
×
494
                     size_t token_length,
495
                     char const *name)
496
{
497
    size_t index;
498
    unsigned char left;
499
    unsigned char right;
500

501
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
×
502
        left = (unsigned char)token[index];
×
503
        right = (unsigned char)name[index];
×
504
        if (tolower(left) != tolower(right)) {
×
505
            return 0;
×
506
        }
507
    }
508

509
    if (index != token_length || name[index] != '\0') {
×
510
        return 0;
×
511
    }
512

513
    return 1;
×
514
}
515

516
static sixel_loader_entry_t const *
517
loader_lookup_token(char const *token,
×
518
                    size_t token_length,
519
                    sixel_loader_entry_t const *entries,
520
                    size_t entry_count)
521
{
522
    size_t index;
523

524
    for (index = 0; index < entry_count; ++index) {
×
525
        if (loader_token_matches(token,
×
526
                                 token_length,
527
                                 entries[index].name)) {
×
528
            return &entries[index];
×
529
        }
530
    }
531

532
    return NULL;
×
533
}
534

535
/*
536
 * loader_build_plan
537
 *
538
 * Translate a comma separated list into an execution plan that reorders the
539
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
540
 * are ignored so that new builds remain forward compatible.
541
 *
542
 *    user input "gd,coregraphics"
543
 *                |
544
 *                v
545
 *        +-------------------+
546
 *        | prioritized list  |
547
 *        +-------------------+
548
 *                |
549
 *                v
550
 *        +-------------------+
551
 *        | default fallbacks |
552
 *        +-------------------+
553
 */
554
static size_t
555
loader_build_plan(char const *order,
751✔
556
                  sixel_loader_entry_t const *entries,
557
                  size_t entry_count,
558
                  sixel_loader_entry_t const **plan,
559
                  size_t plan_capacity)
560
{
561
    size_t plan_length;
562
    size_t index;
563
    char const *cursor;
564
    char const *token_start;
565
    char const *token_end;
566
    size_t token_length;
567
    sixel_loader_entry_t const *entry;
568
    size_t limit;
569

570
    plan_length = 0;
751✔
571
    index = 0;
751✔
572
    cursor = order;
751✔
573
    token_start = order;
751✔
574
    token_end = order;
751✔
575
    token_length = 0;
751✔
576
    entry = NULL;
751✔
577
    limit = plan_capacity;
751✔
578

579
    if (order != NULL && plan != NULL && plan_capacity > 0) {
751!
580
        token_start = order;
×
581
        cursor = order;
×
582
        while (*cursor != '\0') {
×
583
            if (*cursor == ',') {
×
584
                token_end = cursor;
×
585
                while (token_start < token_end &&
×
586
                       isspace((unsigned char)*token_start)) {
×
587
                    ++token_start;
×
588
                }
589
                while (token_end > token_start &&
×
590
                       isspace((unsigned char)token_end[-1])) {
×
591
                    --token_end;
×
592
                }
593
                token_length = (size_t)(token_end - token_start);
×
594
                if (token_length > 0) {
×
595
                    entry = loader_lookup_token(token_start,
×
596
                                                token_length,
597
                                                entries,
598
                                                entry_count);
599
                    if (entry != NULL &&
×
600
                        !loader_plan_contains(plan,
×
601
                                              plan_length,
602
                                              entry) &&
×
603
                        plan_length < limit) {
604
                        plan[plan_length] = entry;
×
605
                        ++plan_length;
×
606
                    }
607
                }
608
                token_start = cursor + 1;
×
609
            }
610
            ++cursor;
×
611
        }
612

613
        token_end = cursor;
×
614
        while (token_start < token_end &&
×
615
               isspace((unsigned char)*token_start)) {
×
616
            ++token_start;
×
617
        }
618
        while (token_end > token_start &&
×
619
               isspace((unsigned char)token_end[-1])) {
×
620
            --token_end;
×
621
        }
622
        token_length = (size_t)(token_end - token_start);
×
623
        if (token_length > 0) {
×
624
            entry = loader_lookup_token(token_start,
×
625
                                        token_length,
626
                                        entries,
627
                                        entry_count);
628
            if (entry != NULL &&
×
629
                !loader_plan_contains(plan, plan_length, entry) &&
×
630
                plan_length < limit) {
631
                plan[plan_length] = entry;
×
632
                ++plan_length;
×
633
            }
634
        }
635
    }
636

637
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
2,555!
638
        entry = &entries[index];
1,804✔
639
        if (!entry->default_enabled) {
1,804✔
640
            continue;
902✔
641
        }
642
        if (!loader_plan_contains(plan, plan_length, entry)) {
902!
643
            plan[plan_length] = entry;
902✔
644
            ++plan_length;
902✔
645
        }
302✔
646
    }
302✔
647

648
    return plan_length;
751✔
649
}
650

651
static void
652
loader_append_chunk(char *dest,
30✔
653
                    size_t capacity,
654
                    size_t *offset,
655
                    char const *chunk)
656
{
657
    size_t available;
658
    size_t length;
659

660
    if (dest == NULL || offset == NULL || chunk == NULL) {
30!
661
        return;
×
662
    }
663

664
    if (*offset >= capacity) {
30!
665
        return;
×
666
    }
667

668
    available = capacity - *offset;
30✔
669
    if (available == 0) {
30!
670
        return;
×
671
    }
672

673
    length = strlen(chunk);
30✔
674
    if (length >= available) {
30!
675
        if (available == 0) {
×
676
            return;
×
677
        }
678
        length = available - 1u;
×
679
    }
680

681
    if (length > 0) {
30!
682
        memcpy(dest + *offset, chunk, length);
30✔
683
        *offset += length;
30✔
684
    }
6✔
685

686
    if (*offset < capacity) {
30!
687
        dest[*offset] = '\0';
30✔
688
    } else {
6✔
689
        dest[capacity - 1u] = '\0';
×
690
    }
691
}
6✔
692

693
static void
694
loader_append_key_value(char *dest,
10✔
695
                        size_t capacity,
696
                        size_t *offset,
697
                        char const *label,
698
                        char const *value)
699
{
700
    char line[128];
701
    int written;
702

703
    if (value == NULL || value[0] == '\0') {
10!
704
        return;
×
705
    }
706

707
    written = sixel_compat_snprintf(line,
12✔
708
                                    sizeof(line),
709
                                    "  %-10s: %s\n",
710
                                    label,
2✔
711
                                    value);
2✔
712
    if (written < 0) {
10!
713
        return;
×
714
    }
715

716
    if ((size_t)written >= sizeof(line)) {
10!
717
        line[sizeof(line) - 1u] = '\0';
×
718
    }
719

720
    loader_append_chunk(dest, capacity, offset, line);
10✔
721
}
2✔
722

723
static void
724
loader_extract_extension(char const *path, char *buffer, size_t capacity)
5✔
725
{
726
    char const *dot;
727
    size_t index;
728

729
    if (buffer == NULL || capacity == 0) {
5!
730
        return;
×
731
    }
732

733
    buffer[0] = '\0';
5✔
734

735
    if (path == NULL) {
5!
736
        return;
5✔
737
    }
738

739
    dot = strrchr(path, '.');
×
740
    if (dot == NULL || dot[1] == '\0') {
×
741
        return;
×
742
    }
743

744
#if defined(_WIN32)
745
    {
746
        char const *slash;
747
        char const *backslash;
748

749
        slash = strrchr(path, '/');
750
        backslash = strrchr(path, '\\');
751
        if ((slash != NULL && dot < slash) ||
752
                (backslash != NULL && dot < backslash)) {
753
            return;
754
        }
755
    }
756
#else
757
    {
758
        char const *slash;
759

760
        slash = strrchr(path, '/');
×
761
        if (slash != NULL && dot < slash) {
×
762
            return;
×
763
        }
764
    }
765
#endif
766

767
    if (dot[1] == '\0') {
×
768
        return;
×
769
    }
770

771
    dot += 1;
×
772

773
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
×
774
        buffer[index] = (char)tolower((unsigned char)dot[index]);
×
775
    }
776
    buffer[index] = '\0';
×
777
}
1✔
778

779
sixel_allocator_t *stbi_allocator;
780

781
void *
782
stbi_malloc(size_t n)
2,014✔
783
{
784
    return sixel_allocator_malloc(stbi_allocator, n);
2,014✔
785
}
786

787
void *
788
stbi_realloc(void *p, size_t n)
531✔
789
{
790
    return sixel_allocator_realloc(stbi_allocator, p, n);
531✔
791
}
792

793
void
794
stbi_free(void *p)
2,657✔
795
{
796
    sixel_allocator_free(stbi_allocator, p);
2,657✔
797
}
2,657✔
798

799
#define STBI_MALLOC stbi_malloc
800
#define STBI_REALLOC stbi_realloc
801
#define STBI_FREE stbi_free
802

803
#define STBI_NO_STDIO 1
804
#define STB_IMAGE_IMPLEMENTATION 1
805
#define STBI_FAILURE_USERMSG 1
806
#if defined(_WIN32)
807
# define STBI_NO_THREAD_LOCALS 1  /* no tls */
808
#endif
809
#define STBI_NO_GIF
810
#define STBI_NO_PNM
811

812
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
813
# pragma GCC diagnostic push
814
# pragma GCC diagnostic ignored "-Wsign-conversion"
815
#endif
816
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
817
# pragma GCC diagnostic push
818
# pragma GCC diagnostic ignored "-Wstrict-overflow"
819
#endif
820
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
821
# pragma GCC diagnostic push
822
# pragma GCC diagnostic ignored "-Wswitch-default"
823
#endif
824
#if HAVE_DIAGNOSTIC_SHADOW
825
# pragma GCC diagnostic push
826
# pragma GCC diagnostic ignored "-Wshadow"
827
#endif
828
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
829
# pragma GCC diagnostic push
830
# pragma GCC diagnostic ignored "-Wdouble-promotion"
831
#endif
832
# if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
833
# pragma GCC diagnostic push
834
# pragma GCC diagnostic ignored "-Wunused-function"
835
#endif
836
# if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
837
# pragma GCC diagnostic push
838
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
839
#endif
840
#include "stb_image.h"
841
#if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
842
# pragma GCC diagnostic pop
843
#endif
844
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
845
# pragma GCC diagnostic pop
846
#endif
847
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
848
# pragma GCC diagnostic pop
849
#endif
850
#if HAVE_DIAGNOSTIC_SHADOW
851
# pragma GCC diagnostic pop
852
#endif
853
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
854
# pragma GCC diagnostic pop
855
#endif
856
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
857
# pragma GCC diagnostic pop
858
#endif
859
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
860
# pragma GCC diagnostic pop
861
#endif
862

863

864
# if HAVE_JPEG
865
/* import from @uobikiemukot's sdump loader.h */
866
static SIXELSTATUS
867
load_jpeg(unsigned char **result,
868
          unsigned char *data,
869
          size_t datasize,
870
          int *pwidth,
871
          int *pheight,
872
          int *ppixelformat,
873
          sixel_allocator_t *allocator)
874
{
875
    SIXELSTATUS status = SIXEL_JPEG_ERROR;
876
    JDIMENSION row_stride;
877
    size_t size;
878
    JSAMPARRAY buffer;
879
    struct jpeg_decompress_struct cinfo;
880
    struct jpeg_error_mgr pub;
881

882
    cinfo.err = jpeg_std_error(&pub);
883

884
    jpeg_create_decompress(&cinfo);
885
    jpeg_mem_src(&cinfo, data, datasize);
886
    jpeg_read_header(&cinfo, TRUE);
887

888
    /* disable colormap (indexed color), grayscale -> rgb */
889
    cinfo.quantize_colors = FALSE;
890
    cinfo.out_color_space = JCS_RGB;
891
    jpeg_start_decompress(&cinfo);
892

893
    if (cinfo.output_components != 3) {
894
        sixel_helper_set_additional_message(
895
            "load_jpeg: unknown pixel format.");
896
        status = SIXEL_BAD_INPUT;
897
        goto end;
898
    }
899

900
    *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
901

902
    if (cinfo.output_width > INT_MAX || cinfo.output_height > INT_MAX) {
903
        status = SIXEL_BAD_INTEGER_OVERFLOW;
904
        goto end;
905
    }
906
    *pwidth = (int)cinfo.output_width;
907
    *pheight = (int)cinfo.output_height;
908

909
    size = (size_t)(*pwidth * *pheight * cinfo.output_components);
910
    *result = (unsigned char *)
911
        sixel_allocator_malloc(allocator, size);
912
    if (*result == NULL) {
913
        sixel_helper_set_additional_message(
914
            "load_jpeg: sixel_allocator_malloc() failed.");
915
        status = SIXEL_BAD_ALLOCATION;
916
        goto end;
917
    }
918
    row_stride = cinfo.output_width * (unsigned int)cinfo.output_components;
919
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo,
920
                                        JPOOL_IMAGE,
921
                                        row_stride,
922
                                        1);
923

924
    while (cinfo.output_scanline < cinfo.output_height) {
925
        jpeg_read_scanlines(&cinfo, buffer, 1);
926
        if (cinfo.err->num_warnings > 0) {
927
            sixel_helper_set_additional_message(
928
                "jpeg_read_scanlines: error/warining occuered.");
929
            status = SIXEL_BAD_INPUT;
930
            goto end;
931
        }
932
        memcpy(*result + (cinfo.output_scanline - 1) * row_stride,
933
               buffer[0],
934
               row_stride);
935
    }
936

937
    status = SIXEL_OK;
938

939
end:
940
    jpeg_finish_decompress(&cinfo);
941
    jpeg_destroy_decompress(&cinfo);
942

943
    return status;
944
}
945
# endif  /* HAVE_JPEG */
946

947

948
# if HAVE_LIBPNG
949
static void
950
read_png(png_structp png_ptr,
951
         png_bytep data,
952
         png_size_t length)
953
{
954
    sixel_chunk_t *pchunk = (sixel_chunk_t *)png_get_io_ptr(png_ptr);
955
    if (length > pchunk->size) {
956
        length = pchunk->size;
957
    }
958
    if (length > 0) {
959
        memcpy(data, pchunk->buffer, length);
960
        pchunk->buffer += length;
961
        pchunk->size -= length;
962
    }
963
}
964

965

966
static void
967
read_palette(png_structp png_ptr,
968
             png_infop info_ptr,
969
             unsigned char *palette,
970
             int ncolors,
971
             png_color *png_palette,
972
             png_color_16 *pbackground,
973
             int *transparent)
974
{
975
    png_bytep trans = NULL;
976
    int num_trans = 0;
977
    int i;
978

979
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
980
        png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
981
    }
982
    if (num_trans > 0) {
983
        *transparent = trans[0];
984
    }
985
    for (i = 0; i < ncolors; ++i) {
986
        if (pbackground && i < num_trans) {
987
            palette[i * 3 + 0] = ((0xff - trans[i]) * pbackground->red
988
                                   + trans[i] * png_palette[i].red) >> 8;
989
            palette[i * 3 + 1] = ((0xff - trans[i]) * pbackground->green
990
                                   + trans[i] * png_palette[i].green) >> 8;
991
            palette[i * 3 + 2] = ((0xff - trans[i]) * pbackground->blue
992
                                   + trans[i] * png_palette[i].blue) >> 8;
993
        } else {
994
            palette[i * 3 + 0] = png_palette[i].red;
995
            palette[i * 3 + 1] = png_palette[i].green;
996
            palette[i * 3 + 2] = png_palette[i].blue;
997
        }
998
    }
999
}
1000

1001
jmp_buf jmpbuf;
1002

1003
/* libpng error handler */
1004
static void
1005
png_error_callback(png_structp png_ptr, png_const_charp error_message)
1006
{
1007
    (void) png_ptr;
1008

1009
    sixel_helper_set_additional_message(error_message);
1010
#if HAVE_SETJMP && HAVE_LONGJMP
1011
    longjmp(jmpbuf, 1);
1012
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
1013
}
1014

1015

1016
static SIXELSTATUS
1017
load_png(unsigned char      /* out */ **result,
1018
         unsigned char      /* in */  *buffer,
1019
         size_t             /* in */  size,
1020
         int                /* out */ *psx,
1021
         int                /* out */ *psy,
1022
         unsigned char      /* out */ **ppalette,
1023
         int                /* out */ *pncolors,
1024
         int                /* in */  reqcolors,
1025
         int                /* out */ *pixelformat,
1026
         unsigned char      /* out */ *bgcolor,
1027
         int                /* out */ *transparent,
1028
         sixel_allocator_t  /* in */  *allocator)
1029
{
1030
    SIXELSTATUS status;
1031
    sixel_chunk_t read_chunk;
1032
    png_uint_32 bitdepth;
1033
    png_uint_32 png_status;
1034
    png_structp png_ptr;
1035
    png_infop info_ptr;
1036
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
1037
# pragma GCC diagnostic push
1038
# pragma GCC diagnostic ignored "-Wclobbered"
1039
#endif
1040
    unsigned char **rows = NULL;
1041
    png_color *png_palette = NULL;
1042
    png_color_16 background;
1043
    png_color_16p default_background;
1044
    png_uint_32 width;
1045
    png_uint_32 height;
1046
    int i;
1047
    int depth;
1048

1049
#if HAVE_SETJMP && HAVE_LONGJMP
1050
    if (setjmp(jmpbuf) != 0) {
1051
        sixel_allocator_free(allocator, *result);
1052
        *result = NULL;
1053
        status = SIXEL_PNG_ERROR;
1054
        goto cleanup;
1055
    }
1056
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
1057

1058
    status = SIXEL_FALSE;
1059
    *result = NULL;
1060

1061
    png_ptr = png_create_read_struct(
1062
        PNG_LIBPNG_VER_STRING, NULL, &png_error_callback, NULL);
1063
    if (!png_ptr) {
1064
        sixel_helper_set_additional_message(
1065
            "png_create_read_struct() failed.");
1066
        status = SIXEL_PNG_ERROR;
1067
        goto cleanup;
1068
    }
1069

1070
    /*
1071
     * The minimum valid PNG is 67 bytes.
1072
     * https://garethrees.org/2007/11/14/pngcrush/
1073
     */
1074
    if (size < 67) {
1075
        sixel_helper_set_additional_message("PNG data too small to be valid!");
1076
        status = SIXEL_PNG_ERROR;
1077
        goto cleanup;
1078
    }
1079

1080
#if HAVE_SETJMP
1081
    if (setjmp(png_jmpbuf(png_ptr)) != 0) {
1082
        sixel_allocator_free(allocator, *result);
1083
        *result = NULL;
1084
        status = SIXEL_PNG_ERROR;
1085
        goto cleanup;
1086
    }
1087
#endif  /* HAVE_SETJMP */
1088

1089
    info_ptr = png_create_info_struct(png_ptr);
1090
    if (!info_ptr) {
1091
        sixel_helper_set_additional_message(
1092
            "png_create_info_struct() failed.");
1093
        status = SIXEL_PNG_ERROR;
1094
        png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
1095
        goto cleanup;
1096
    }
1097
    read_chunk.buffer = buffer;
1098
    read_chunk.size = size;
1099

1100
    png_set_read_fn(png_ptr,(png_voidp)&read_chunk, read_png);
1101
    png_read_info(png_ptr, info_ptr);
1102

1103
    width = png_get_image_width(png_ptr, info_ptr);
1104
    height = png_get_image_height(png_ptr, info_ptr);
1105

1106
    if (width > INT_MAX || height > INT_MAX) {
1107
        status = SIXEL_BAD_INTEGER_OVERFLOW;
1108
        goto cleanup;
1109
    }
1110

1111
    *psx = (int)width;
1112
    *psy = (int)height;
1113

1114
    bitdepth = png_get_bit_depth(png_ptr, info_ptr);
1115
    if (bitdepth == 16) {
1116
#  if HAVE_DEBUG
1117
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1118
        fprintf(stderr, "stripping to 8bit...\n");
1119
#  endif
1120
        png_set_strip_16(png_ptr);
1121
        bitdepth = 8;
1122
    }
1123

1124
    if (bgcolor) {
1125
#  if HAVE_DEBUG
1126
        fprintf(stderr,
1127
                "background color is specified [%02x, %02x, %02x]\n",
1128
                bgcolor[0], bgcolor[1], bgcolor[2]);
1129
#  endif
1130
        background.red = bgcolor[0];
1131
        background.green = bgcolor[1];
1132
        background.blue = bgcolor[2];
1133
        background.gray = (bgcolor[0] + bgcolor[1] + bgcolor[2]) / 3;
1134
    } else if (png_get_bKGD(png_ptr, info_ptr, &default_background)
1135
            == PNG_INFO_bKGD) {
1136
        memcpy(&background, default_background, sizeof(background));
1137
#  if HAVE_DEBUG
1138
        fprintf(stderr,
1139
                "background color is found [%02x, %02x, %02x]\n",
1140
                background.red, background.green, background.blue);
1141
#  endif
1142
    } else {
1143
        background.red = 0;
1144
        background.green = 0;
1145
        background.blue = 0;
1146
        background.gray = 0;
1147
    }
1148

1149
    switch (png_get_color_type(png_ptr, info_ptr)) {
1150
    case PNG_COLOR_TYPE_PALETTE:
1151
#  if HAVE_DEBUG
1152
        fprintf(stderr, "paletted PNG(PNG_COLOR_TYPE_PALETTE)\n");
1153
#  endif
1154
        png_status = png_get_PLTE(png_ptr, info_ptr,
1155
                                  &png_palette, pncolors);
1156
        if (png_status != PNG_INFO_PLTE || png_palette == NULL) {
1157
            sixel_helper_set_additional_message(
1158
                "PLTE chunk not found");
1159
            status = SIXEL_PNG_ERROR;
1160
            goto cleanup;
1161
        }
1162
#  if HAVE_DEBUG
1163
        fprintf(stderr, "palette colors: %d\n", *pncolors);
1164
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1165
#  endif
1166
        if (ppalette == NULL || *pncolors > reqcolors) {
1167
#  if HAVE_DEBUG
1168
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
1169
                    reqcolors);
1170
            fprintf(stderr, "expand to RGB format...\n");
1171
#  endif
1172
            png_set_background(png_ptr, &background,
1173
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1174
            png_set_palette_to_rgb(png_ptr);
1175
            png_set_strip_alpha(png_ptr);
1176
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1177
        } else {
1178
            switch (bitdepth) {
1179
            case 1:
1180
                *ppalette = (unsigned char *)
1181
                    sixel_allocator_malloc(allocator,
1182
                                           (size_t)*pncolors * 3);
1183
                if (*ppalette == NULL) {
1184
                    sixel_helper_set_additional_message(
1185
                        "load_png: sixel_allocator_malloc() failed.");
1186
                    status = SIXEL_BAD_ALLOCATION;
1187
                    goto cleanup;
1188
                }
1189
                read_palette(png_ptr, info_ptr, *ppalette,
1190
                             *pncolors,
1191
                             png_palette,
1192
                             &background,
1193
                             transparent);
1194
                *pixelformat = SIXEL_PIXELFORMAT_PAL1;
1195
                break;
1196
            case 2:
1197
                *ppalette = (unsigned char *)
1198
                    sixel_allocator_malloc(allocator,
1199
                                           (size_t)*pncolors * 3);
1200
                if (*ppalette == NULL) {
1201
                    sixel_helper_set_additional_message(
1202
                        "load_png: sixel_allocator_malloc() failed.");
1203
                    status = SIXEL_BAD_ALLOCATION;
1204
                    goto cleanup;
1205
                }
1206
                read_palette(png_ptr, info_ptr, *ppalette,
1207
                             *pncolors,
1208
                             png_palette,
1209
                             &background,
1210
                             transparent);
1211
                *pixelformat = SIXEL_PIXELFORMAT_PAL2;
1212
                break;
1213
            case 4:
1214
                *ppalette = (unsigned char *)
1215
                    sixel_allocator_malloc(allocator,
1216
                                           (size_t)*pncolors * 3);
1217
                if (*ppalette == NULL) {
1218
                    sixel_helper_set_additional_message(
1219
                        "load_png: sixel_allocator_malloc() failed.");
1220
                    status = SIXEL_BAD_ALLOCATION;
1221
                    goto cleanup;
1222
                }
1223
                read_palette(png_ptr, info_ptr, *ppalette,
1224
                             *pncolors,
1225
                             png_palette,
1226
                             &background,
1227
                             transparent);
1228
                *pixelformat = SIXEL_PIXELFORMAT_PAL4;
1229
                break;
1230
            case 8:
1231
                *ppalette = (unsigned char *)
1232
                    sixel_allocator_malloc(allocator,
1233
                                           (size_t)*pncolors * 3);
1234
                if (*ppalette == NULL) {
1235
                    sixel_helper_set_additional_message(
1236
                        "load_png: sixel_allocator_malloc() failed.");
1237
                    status = SIXEL_BAD_ALLOCATION;
1238
                    goto cleanup;
1239
                }
1240
                read_palette(png_ptr, info_ptr, *ppalette,
1241
                             *pncolors,
1242
                             png_palette,
1243
                             &background,
1244
                             transparent);
1245
                *pixelformat = SIXEL_PIXELFORMAT_PAL8;
1246
                break;
1247
            default:
1248
                png_set_background(png_ptr, &background,
1249
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1250
                png_set_palette_to_rgb(png_ptr);
1251
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1252
                break;
1253
            }
1254
        }
1255
        break;
1256
    case PNG_COLOR_TYPE_GRAY:
1257
#  if HAVE_DEBUG
1258
        fprintf(stderr, "grayscale PNG(PNG_COLOR_TYPE_GRAY)\n");
1259
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1260
#  endif
1261
        if (1 << bitdepth > reqcolors) {
1262
#  if HAVE_DEBUG
1263
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
1264
                    reqcolors);
1265
            fprintf(stderr, "expand into RGB format...\n");
1266
#  endif
1267
            png_set_background(png_ptr, &background,
1268
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1269
            png_set_gray_to_rgb(png_ptr);
1270
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1271
        } else {
1272
            switch (bitdepth) {
1273
            case 1:
1274
            case 2:
1275
            case 4:
1276
                if (ppalette) {
1277
#  if HAVE_DECL_PNG_SET_EXPAND_GRAY_1_2_4_TO_8
1278
#   if HAVE_DEBUG
1279
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
1280
                            (unsigned int)bitdepth);
1281
#   endif
1282
                    png_set_expand_gray_1_2_4_to_8(png_ptr);
1283
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
1284
#  elif HAVE_DECL_PNG_SET_GRAY_1_2_4_TO_8
1285
#   if HAVE_DEBUG
1286
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
1287
                            (unsigned int)bitdepth);
1288
#   endif
1289
                    png_set_gray_1_2_4_to_8(png_ptr);
1290
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
1291
#  else
1292
#   if HAVE_DEBUG
1293
                    fprintf(stderr, "expand into RGB format...\n");
1294
#   endif
1295
                    png_set_background(png_ptr, &background,
1296
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1297
                    png_set_gray_to_rgb(png_ptr);
1298
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1299
#  endif
1300
                } else {
1301
                    png_set_background(png_ptr, &background,
1302
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1303
                    png_set_gray_to_rgb(png_ptr);
1304
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1305
                }
1306
                break;
1307
            case 8:
1308
                if (ppalette) {
1309
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
1310
                } else {
1311
#  if HAVE_DEBUG
1312
                    fprintf(stderr, "expand into RGB format...\n");
1313
#  endif
1314
                    png_set_background(png_ptr, &background,
1315
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1316
                    png_set_gray_to_rgb(png_ptr);
1317
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1318
                }
1319
                break;
1320
            default:
1321
#  if HAVE_DEBUG
1322
                fprintf(stderr, "expand into RGB format...\n");
1323
#  endif
1324
                png_set_background(png_ptr, &background,
1325
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1326
                png_set_gray_to_rgb(png_ptr);
1327
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1328
                break;
1329
            }
1330
        }
1331
        break;
1332
    case PNG_COLOR_TYPE_GRAY_ALPHA:
1333
#  if HAVE_DEBUG
1334
        fprintf(stderr, "grayscale-alpha PNG(PNG_COLOR_TYPE_GRAY_ALPHA)\n");
1335
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1336
        fprintf(stderr, "expand to RGB format...\n");
1337
#  endif
1338
        png_set_background(png_ptr, &background,
1339
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1340
        png_set_gray_to_rgb(png_ptr);
1341
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1342
        break;
1343
    case PNG_COLOR_TYPE_RGB_ALPHA:
1344
#  if HAVE_DEBUG
1345
        fprintf(stderr, "RGBA PNG(PNG_COLOR_TYPE_RGB_ALPHA)\n");
1346
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1347
        fprintf(stderr, "expand to RGB format...\n");
1348
#  endif
1349
        png_set_background(png_ptr, &background,
1350
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1351
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1352
        break;
1353
    case PNG_COLOR_TYPE_RGB:
1354
#  if HAVE_DEBUG
1355
        fprintf(stderr, "RGB PNG(PNG_COLOR_TYPE_RGB)\n");
1356
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1357
#  endif
1358
        png_set_background(png_ptr, &background,
1359
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1360
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1361
        break;
1362
    default:
1363
        /* unknown format */
1364
        goto cleanup;
1365
    }
1366
    depth = sixel_helper_compute_depth(*pixelformat);
1367
    *result = (unsigned char *)
1368
        sixel_allocator_malloc(allocator,
1369
                               (size_t)(*psx * *psy * depth));
1370
    if (*result == NULL) {
1371
        sixel_helper_set_additional_message(
1372
            "load_png: sixel_allocator_malloc() failed.");
1373
        status = SIXEL_BAD_ALLOCATION;
1374
        goto cleanup;
1375
    }
1376
    rows = (unsigned char **)sixel_allocator_malloc(
1377
        allocator,
1378
        (size_t)*psy * sizeof(unsigned char *));
1379
    if (rows == NULL) {
1380
        sixel_helper_set_additional_message(
1381
            "load_png: sixel_allocator_malloc() failed.");
1382
        status = SIXEL_BAD_ALLOCATION;
1383
        goto cleanup;
1384
    }
1385
    switch (*pixelformat) {
1386
    case SIXEL_PIXELFORMAT_PAL1:
1387
    case SIXEL_PIXELFORMAT_PAL2:
1388
    case SIXEL_PIXELFORMAT_PAL4:
1389
        for (i = 0; i < *psy; ++i) {
1390
            rows[i] = *result + (depth * *psx * (int)bitdepth + 7) / 8 * i;
1391
        }
1392
        break;
1393
    default:
1394
        for (i = 0; i < *psy; ++i) {
1395
            rows[i] = *result + depth * *psx * i;
1396
        }
1397
        break;
1398
    }
1399

1400
    png_read_image(png_ptr, rows);
1401

1402
    status = SIXEL_OK;
1403

1404
cleanup:
1405
    png_destroy_read_struct(&png_ptr, &info_ptr,(png_infopp)0);
1406

1407
    if (rows != NULL) {
1408
        sixel_allocator_free(allocator, rows);
1409
    }
1410

1411
    return status;
1412
}
1413
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
1414
# pragma GCC diagnostic pop
1415
#endif
1416

1417
# endif  /* HAVE_LIBPNG */
1418

1419

1420
static SIXELSTATUS
1421
load_sixel(unsigned char        /* out */ **result,
260✔
1422
           unsigned char        /* in */  *buffer,
1423
           int                  /* in */  size,
1424
           int                  /* out */ *psx,
1425
           int                  /* out */ *psy,
1426
           unsigned char        /* out */ **ppalette,
1427
           int                  /* out */ *pncolors,
1428
           int                  /* in */  reqcolors,
1429
           int                  /* out */ *ppixelformat,
1430
           sixel_allocator_t    /* in */  *allocator)
1431
{
1432
    SIXELSTATUS status = SIXEL_FALSE;
260✔
1433
    unsigned char *p = NULL;
260✔
1434
    unsigned char *palette = NULL;
260✔
1435
    int colors;
1436
    int i;
1437

1438
    /* sixel */
1439
    status = sixel_decode_raw(buffer, size,
312✔
1440
                              &p, psx, psy,
52✔
1441
                              &palette, &colors, allocator);
52✔
1442
    if (SIXEL_FAILED(status)) {
260!
1443
        goto end;
×
1444
    }
1445
    if (ppalette == NULL || colors > reqcolors) {
312!
1446
        *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
60✔
1447
        *result = (unsigned char *)
60✔
1448
            sixel_allocator_malloc(allocator,
72✔
1449
                                   (size_t)(*psx * *psy * 3));
60✔
1450
        if (*result == NULL) {
60!
1451
            sixel_helper_set_additional_message(
×
1452
                "load_sixel: sixel_allocator_malloc() failed.");
1453
            status = SIXEL_BAD_ALLOCATION;
×
1454
            goto end;
×
1455
        }
1456
        for (i = 0; i < *psx * *psy; ++i) {
9,450,960✔
1457
            (*result)[i * 3 + 0] = palette[p[i] * 3 + 0];
9,450,900✔
1458
            (*result)[i * 3 + 1] = palette[p[i] * 3 + 1];
9,450,900✔
1459
            (*result)[i * 3 + 2] = palette[p[i] * 3 + 2];
9,450,900✔
1460
        }
1,890,180✔
1461
    } else {
12✔
1462
        *ppixelformat = SIXEL_PIXELFORMAT_PAL8;
200✔
1463
        *result = p;
200✔
1464
        *ppalette = palette;
200✔
1465
        *pncolors = colors;
200✔
1466
        p = NULL;
200✔
1467
        palette = NULL;
200✔
1468
    }
1469

1470
end:
208✔
1471
    /*
1472
     * Release the decoded index buffer when the caller requested an RGB
1473
     * conversion.  Palette-backed callers steal ownership by nulling `p`.
1474
     */
1475
    sixel_allocator_free(allocator, p);
260✔
1476
    sixel_allocator_free(allocator, palette);
260✔
1477

1478
    return status;
260✔
1479
}
1480

1481

1482
/* detect whether given chunk is sixel stream */
1483
static int
1484
chunk_is_sixel(sixel_chunk_t const *chunk)
751✔
1485
{
1486
    unsigned char *p;
1487
    unsigned char *end;
1488

1489
    p = chunk->buffer;
751✔
1490
    end = p + chunk->size;
751✔
1491

1492
    if (chunk->size < 3) {
751!
1493
        return 0;
4✔
1494
    }
1495

1496
    p++;
747✔
1497
    if (p >= end) {
747!
1498
        return 0;
×
1499
    }
1500
    if (*(p - 1) == 0x90 || (*(p - 1) == 0x1b && *p == 0x50)) {
747!
1501
        while (p++ < end) {
950!
1502
            if (*p == 0x71) {
950✔
1503
                return 1;
260✔
1504
            } else if (*p == 0x18 || *p == 0x1a) {
690!
1505
                return 0;
×
1506
            } else if (*p < 0x20) {
690✔
1507
                continue;
10✔
1508
            } else if (*p < 0x30) {
680!
1509
                return 0;
×
1510
            } else if (*p < 0x40) {
680✔
1511
                continue;
675✔
1512
            }
1513
        }
1514
    }
1515
    return 0;
487✔
1516
}
151✔
1517

1518

1519
/* detect whether given chunk is PNM stream */
1520
static int
1521
chunk_is_pnm(sixel_chunk_t const *chunk)
491✔
1522
{
1523
    if (chunk->size < 2) {
491!
1524
        return 0;
4✔
1525
    }
1526
    if (chunk->buffer[0] == 'P' &&
497✔
1527
        chunk->buffer[1] >= '1' &&
50!
1528
        chunk->buffer[1] <= '6') {
50!
1529
        return 1;
50✔
1530
    }
1531
    return 0;
437✔
1532
}
99✔
1533

1534

1535
#if HAVE_LIBPNG
1536
/* detect whether given chunk is PNG stream */
1537
static int
1538
chunk_is_png(sixel_chunk_t const *chunk)
1539
{
1540
    if (chunk->size < 8) {
1541
        return 0;
1542
    }
1543
    if (png_check_sig(chunk->buffer, 8)) {
1544
        return 1;
1545
    }
1546
    return 0;
1547
}
1548
#endif  /* HAVE_LIBPNG */
1549

1550

1551
/* detect whether given chunk is GIF stream */
1552
static int
1553
chunk_is_gif(sixel_chunk_t const *chunk)
441✔
1554
{
1555
    if (chunk->size < 6) {
441✔
1556
        return 0;
5✔
1557
    }
1558
    if (chunk->buffer[0] == 'G' &&
443✔
1559
        chunk->buffer[1] == 'I' &&
35!
1560
        chunk->buffer[2] == 'F' &&
35!
1561
        chunk->buffer[3] == '8' &&
35!
1562
        (chunk->buffer[4] == '7' || chunk->buffer[4] == '9') &&
35!
1563
        chunk->buffer[5] == 'a') {
35!
1564
        return 1;
35✔
1565
    }
1566
    return 0;
401✔
1567
}
89✔
1568

1569

1570
#if HAVE_JPEG
1571
/* detect whether given chunk is JPEG stream */
1572
static int
1573
chunk_is_jpeg(sixel_chunk_t const *chunk)
1574
{
1575
    if (chunk->size < 2) {
1576
        return 0;
1577
    }
1578
    if (memcmp("\xFF\xD8", chunk->buffer, 2) == 0) {
1579
        return 1;
1580
    }
1581
    return 0;
1582
}
1583
#endif  /* HAVE_JPEG */
1584

1585
typedef union _fn_pointer {
1586
    sixel_load_image_function fn;
1587
    void *                    p;
1588
} fn_pointer;
1589

1590
/* load images using builtin image loaders */
1591
static SIXELSTATUS
1592
load_with_builtin(
751✔
1593
    sixel_chunk_t const *pchunk,      /* image data */
1594
    int fstatic,                       /* static */
1595
    int fuse_palette,                  /* whether to use palette if possible */
1596
    int reqcolors,                     /* reqcolors */
1597
    unsigned char *bgcolor,            /* background color */
1598
    int loop_control,                  /* one of enum loop_control */
1599
    sixel_load_image_function fn_load, /* callback */
1600
    void *context                      /* private data for callback */
1601
)
1602
{
1603
    SIXELSTATUS status = SIXEL_FALSE;
751✔
1604
    sixel_frame_t *frame = NULL;
751✔
1605
    unsigned char *pixels;
1606
    fn_pointer fnp;
1607
    stbi__context stb_context;
1608
    int depth;
1609
    char message[256];
1610
    int nwrite;
1611

1612
    pixels = NULL;
751✔
1613
    if (chunk_is_sixel(pchunk)) {
751✔
1614
        status = sixel_frame_new(&frame, pchunk->allocator);
260✔
1615
        if (SIXEL_FAILED(status)) {
260!
1616
            goto end;
×
1617
        }
1618
        if (pchunk->size > INT_MAX) {
260!
1619
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1620
            goto end;
×
1621
        }
1622
        status = load_sixel(&pixels,
100✔
1623
                            pchunk->buffer,
260✔
1624
                            (int)pchunk->size,
260✔
1625
                            &frame->width,
260✔
1626
                            &frame->height,
260✔
1627
                            fuse_palette ? &frame->palette: NULL,
212✔
1628
                            &frame->ncolors,
260✔
1629
                            reqcolors,
52✔
1630
                            &frame->pixelformat,
260✔
1631
                            pchunk->allocator);
260✔
1632
        if (SIXEL_FAILED(status)) {
260!
1633
            goto end;
×
1634
        }
1635
        sixel_frame_set_pixels(frame, pixels);
260✔
1636
    } else if (chunk_is_pnm(pchunk)) {
543✔
1637
        status = sixel_frame_new(&frame, pchunk->allocator);
50✔
1638
        if (SIXEL_FAILED(status)) {
50!
1639
            goto end;
×
1640
        }
1641
        if (pchunk->size > INT_MAX) {
50!
1642
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1643
            goto end;
×
1644
        }
1645
        /* pnm */
1646
        status = load_pnm(pchunk->buffer,
60✔
1647
                          (int)pchunk->size,
50✔
1648
                          frame->allocator,
50✔
1649
                          &pixels,
1650
                          &frame->width,
50✔
1651
                          &frame->height,
50✔
1652
                          fuse_palette ? &frame->palette: NULL,
10!
1653
                          &frame->ncolors,
50✔
1654
                          &frame->pixelformat);
50!
1655
        if (SIXEL_FAILED(status)) {
50!
1656
            goto end;
×
1657
        }
1658
        sixel_frame_set_pixels(frame, pixels);
50✔
1659
    } else if (chunk_is_gif(pchunk)) {
451✔
1660
        fnp.fn = fn_load;
35✔
1661
        if (pchunk->size > INT_MAX) {
35!
1662
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1663
            goto end;
×
1664
        }
1665
        status = load_gif(pchunk->buffer,
42✔
1666
                          (int)pchunk->size,
35✔
1667
                          bgcolor,
7✔
1668
                          reqcolors,
7✔
1669
                          fuse_palette,
7✔
1670
                          fstatic,
7✔
1671
                          loop_control,
7✔
1672
                          fnp.p,
7✔
1673
                          context,
7✔
1674
                          pchunk->allocator);
35✔
1675
        if (SIXEL_FAILED(status)) {
35✔
1676
            goto end;
10✔
1677
        }
1678
        goto end;
25✔
1679
    } else {
1680
        /*
1681
         * Fallback to stb_image decoding when no specialized handler
1682
         * claimed the chunk.
1683
         *
1684
         *    +--------------+     +--------------------+
1685
         *    | raw chunk    | --> | stb_image decoding |
1686
         *    +--------------+     +--------------------+
1687
         *                        |
1688
         *                        v
1689
         *                +--------------------+
1690
         *                | sixel frame emit   |
1691
         *                +--------------------+
1692
         */
1693
        status = sixel_frame_new(&frame, pchunk->allocator);
406✔
1694
        if (SIXEL_FAILED(status)) {
406!
1695
            goto end;
×
1696
        }
1697
        if (pchunk->size > INT_MAX) {
406!
1698
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1699
            goto end;
×
1700
        }
1701
        stbi_allocator = pchunk->allocator;
406✔
1702
        stbi__start_mem(&stb_context,
406✔
1703
                        pchunk->buffer,
406✔
1704
                        (int)pchunk->size);
406✔
1705
        pixels = stbi__load_and_postprocess_8bit(&stb_context,
730✔
1706
                                                 &frame->width,
406✔
1707
                                                 &frame->height,
406✔
1708
                                                 &depth,
1709
                                                 3);
1710
        if (pixels == NULL) {
406✔
1711
            sixel_helper_set_additional_message(stbi_failure_reason());
5✔
1712
            status = SIXEL_STBI_ERROR;
5✔
1713
            goto end;
5✔
1714
        }
1715
        sixel_frame_set_pixels(frame, pixels);
401✔
1716
        frame->loop_count = 1;
401✔
1717
        switch (depth) {
401!
1718
        case 1:
320✔
1719
        case 3:
1720
        case 4:
1721
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
401✔
1722
            break;
401✔
1723
        default:
1724
            nwrite = snprintf(message,
×
1725
                              sizeof(message),
1726
                              "load_with_builtin() failed.\n"
1727
                              "reason: unknown pixel-format.(depth: %d)\n",
1728
                              depth);
1729
            if (nwrite > 0) {
×
1730
                sixel_helper_set_additional_message(message);
×
1731
            }
1732
            status = SIXEL_STBI_ERROR;
×
1733
            goto end;
×
1734
        }
1735
    }
1736

1737
    status = sixel_frame_strip_alpha(frame, bgcolor);
711✔
1738
    if (SIXEL_FAILED(status)) {
711!
1739
        goto end;
×
1740
    }
1741

1742
    status = fn_load(frame, context);
711✔
1743
    if (SIXEL_FAILED(status)) {
711!
1744
        goto end;
×
1745
    }
1746

1747
    status = SIXEL_OK;
711✔
1748

1749
end:
600✔
1750
    sixel_frame_unref(frame);
751✔
1751

1752
    return status;
751✔
1753
}
1754

1755

1756
#if HAVE_JPEG
1757
/*
1758
 * Dedicated libjpeg loader wiring minimal pipeline.
1759
 *
1760
 *    +------------+     +-------------------+     +--------------------+
1761
 *    | JPEG chunk | --> | libjpeg decode    | --> | sixel frame emit   |
1762
 *    +------------+     +-------------------+     +--------------------+
1763
 */
1764
static SIXELSTATUS
1765
load_with_libjpeg(
1766
    sixel_chunk_t const       /* in */     *pchunk,
1767
    int                       /* in */     fstatic,
1768
    int                       /* in */     fuse_palette,
1769
    int                       /* in */     reqcolors,
1770
    unsigned char             /* in */     *bgcolor,
1771
    int                       /* in */     loop_control,
1772
    sixel_load_image_function /* in */     fn_load,
1773
    void                      /* in/out */ *context)
1774
{
1775
    SIXELSTATUS status = SIXEL_FALSE;
1776
    sixel_frame_t *frame = NULL;
1777
    unsigned char *pixels;
1778

1779
    (void)fstatic;
1780
    (void)fuse_palette;
1781
    (void)reqcolors;
1782
    (void)loop_control;
1783

1784
    pixels = NULL;
1785
    status = sixel_frame_new(&frame, pchunk->allocator);
1786
    if (SIXEL_FAILED(status)) {
1787
        goto end;
1788
    }
1789

1790
    status = load_jpeg(&pixels,
1791
                       pchunk->buffer,
1792
                       pchunk->size,
1793
                       &frame->width,
1794
                       &frame->height,
1795
                       &frame->pixelformat,
1796
                       pchunk->allocator);
1797
    if (SIXEL_FAILED(status)) {
1798
        goto end;
1799
    }
1800

1801
    sixel_frame_set_pixels(frame, pixels);
1802

1803
    status = sixel_frame_strip_alpha(frame, bgcolor);
1804
    if (SIXEL_FAILED(status)) {
1805
        goto end;
1806
    }
1807

1808
    status = fn_load(frame, context);
1809
    if (SIXEL_FAILED(status)) {
1810
        goto end;
1811
    }
1812

1813
    status = SIXEL_OK;
1814

1815
end:
1816
    sixel_frame_unref(frame);
1817

1818
    return status;
1819
}
1820

1821
static int
1822
loader_can_try_libjpeg(sixel_chunk_t const *chunk)
1823
{
1824
    if (chunk == NULL) {
1825
        return 0;
1826
    }
1827

1828
    return chunk_is_jpeg(chunk);
1829
}
1830
#endif  /* HAVE_JPEG */
1831

1832
#if HAVE_LIBPNG
1833
/*
1834
 * Dedicated libpng loader for precise PNG decoding.
1835
 *
1836
 *    +-----------+     +------------------+     +--------------------+
1837
 *    | PNG chunk | --> | libpng decode    | --> | sixel frame emit   |
1838
 *    +-----------+     +------------------+     +--------------------+
1839
 */
1840
static SIXELSTATUS
1841
load_with_libpng(
1842
    sixel_chunk_t const       /* in */     *pchunk,
1843
    int                       /* in */     fstatic,
1844
    int                       /* in */     fuse_palette,
1845
    int                       /* in */     reqcolors,
1846
    unsigned char             /* in */     *bgcolor,
1847
    int                       /* in */     loop_control,
1848
    sixel_load_image_function /* in */     fn_load,
1849
    void                      /* in/out */ *context)
1850
{
1851
    SIXELSTATUS status = SIXEL_FALSE;
1852
    sixel_frame_t *frame = NULL;
1853
    unsigned char *pixels;
1854

1855
    (void)fstatic;
1856
    (void)loop_control;
1857

1858
    pixels = NULL;
1859
    status = sixel_frame_new(&frame, pchunk->allocator);
1860
    if (SIXEL_FAILED(status)) {
1861
        goto end;
1862
    }
1863

1864
    status = load_png(&pixels,
1865
                      pchunk->buffer,
1866
                      pchunk->size,
1867
                      &frame->width,
1868
                      &frame->height,
1869
                      fuse_palette ? &frame->palette : NULL,
1870
                      &frame->ncolors,
1871
                      reqcolors,
1872
                      &frame->pixelformat,
1873
                      bgcolor,
1874
                      &frame->transparent,
1875
                      pchunk->allocator);
1876
    if (SIXEL_FAILED(status)) {
1877
        goto end;
1878
    }
1879

1880
    sixel_frame_set_pixels(frame, pixels);
1881

1882
    status = sixel_frame_strip_alpha(frame, bgcolor);
1883
    if (SIXEL_FAILED(status)) {
1884
        goto end;
1885
    }
1886

1887
    status = fn_load(frame, context);
1888
    if (SIXEL_FAILED(status)) {
1889
        goto end;
1890
    }
1891

1892
    status = SIXEL_OK;
1893

1894
end:
1895
    sixel_frame_unref(frame);
1896

1897
    return status;
1898
}
1899

1900
static int
1901
loader_can_try_libpng(sixel_chunk_t const *chunk)
1902
{
1903
    if (chunk == NULL) {
1904
        return 0;
1905
    }
1906

1907
    return chunk_is_png(chunk);
1908
}
1909
#endif  /* HAVE_LIBPNG */
1910

1911
#ifdef HAVE_GDK_PIXBUF2
1912
/*
1913
 * Loader backed by gdk-pixbuf2. The entire animation is consumed via
1914
 * GdkPixbufLoader, each frame is copied into a temporary buffer and
1915
 * forwarded as a sixel_frame_t. Loop attributes provided by gdk-pixbuf
1916
 * are reconciled with libsixel's loop control settings.
1917
 */
1918
static SIXELSTATUS
1919
load_with_gdkpixbuf(
1920
    sixel_chunk_t const *pchunk,      /* image data */
1921
    int fstatic,                       /* static */
1922
    int fuse_palette,                  /* whether to use palette if possible */
1923
    int reqcolors,                     /* reqcolors */
1924
    unsigned char *bgcolor,            /* background color */
1925
    int loop_control,                  /* one of enum loop_control */
1926
    sixel_load_image_function fn_load, /* callback */
1927
    void *context                      /* private data for callback */
1928
)
1929
{
1930
    SIXELSTATUS status = SIXEL_FALSE;
1931
    GdkPixbuf *pixbuf;
1932
    GdkPixbufLoader *loader = NULL;
1933
    gboolean loader_closed = FALSE;  /* remember if loader was already closed */
1934
    GdkPixbufAnimation *animation;
1935
    GdkPixbufAnimationIter *it = NULL;
1936
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1937
# pragma GCC diagnostic push
1938
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1939
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1940
    GTimeVal time_val;
1941
    GTimeVal start_time;
1942
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1943
# pragma GCC diagnostic pop
1944
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1945
    sixel_frame_t *frame = NULL;
1946
    int stride;
1947
    unsigned char *p;
1948
    unsigned char *pixels;
1949
    int i;
1950
    int depth;
1951
    int anim_loop_count = (-1);  /* (-1): infinite, >=0: finite loop count */
1952
    int delay_ms;
1953
    gboolean use_animation = FALSE;
1954

1955
    (void) fuse_palette;
1956
    (void) reqcolors;
1957
    (void) bgcolor;
1958

1959
    status = sixel_frame_new(&frame, pchunk->allocator);
1960
    if (SIXEL_FAILED(status)) {
1961
        goto end;
1962
    }
1963

1964
#if (! GLIB_CHECK_VERSION(2, 36, 0))
1965
    g_type_init();
1966
#endif
1967
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1968
# pragma GCC diagnostic push
1969
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1970
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1971
    g_get_current_time(&time_val);
1972
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1973
# pragma GCC diagnostic pop
1974
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1975
    start_time = time_val;
1976
    loader = gdk_pixbuf_loader_new();
1977
    if (loader == NULL) {
1978
        status = SIXEL_GDK_ERROR;
1979
        goto end;
1980
    }
1981
    /*
1982
     * feed the whole blob and close so the animation metadata becomes
1983
     * available
1984
     */
1985
    if (! gdk_pixbuf_loader_write(loader, pchunk->buffer, pchunk->size, NULL)) {
1986
        status = SIXEL_GDK_ERROR;
1987
        goto end;
1988
    }
1989
    if (! gdk_pixbuf_loader_close(loader, NULL)) {
1990
        status = SIXEL_GDK_ERROR;
1991
        goto end;
1992
    }
1993
    loader_closed = TRUE;
1994
    pixbuf = NULL;
1995
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1996
# pragma GCC diagnostic push
1997
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1998
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1999
    animation = gdk_pixbuf_loader_get_animation(loader);
2000
    if (animation) {
2001
        /*
2002
         * +------------------------------------------------------+
2003
         * | GdkPixbuf 2.44 keeps the animation APIs available,   |
2004
         * | but marks them deprecated. We still need the         |
2005
         * | GTimeVal-driven timeline to preserve playback, so we |
2006
         * | mute the warning locally instead of abandoning       |
2007
         * | multi-frame decoding.                                |
2008
         * +------------------------------------------------------+
2009
         */
2010
        if (GDK_IS_PIXBUF_SIMPLE_ANIM(animation)) {
2011
            anim_loop_count = gdk_pixbuf_simple_anim_get_loop(
2012
                                 GDK_PIXBUF_SIMPLE_ANIM(animation))
2013
                             ? (-1)
2014
                             : 1;
2015
        } else {
2016
            GParamSpec *loop_pspec = g_object_class_find_property(
2017
                G_OBJECT_GET_CLASS(animation), "loop");
2018
            if (loop_pspec == NULL) {
2019
                loop_pspec = g_object_class_find_property(
2020
                    G_OBJECT_GET_CLASS(animation), "loop-count");
2021
            }
2022
            if (loop_pspec) {
2023
                GValue loop_value = G_VALUE_INIT;
2024
                g_value_init(&loop_value, loop_pspec->value_type);
2025
                g_object_get_property(G_OBJECT(animation),
2026
                                      g_param_spec_get_name(loop_pspec),
2027
                                      &loop_value);
2028
                if (G_VALUE_HOLDS_BOOLEAN(&loop_value)) {
2029
                    /* TRUE means "loop forever" for these properties */
2030
                    anim_loop_count = g_value_get_boolean(&loop_value)
2031
                                      ? (-1)
2032
                                      : 1;
2033
                } else if (G_VALUE_HOLDS_INT(&loop_value)) {
2034
                    int loop_int = g_value_get_int(&loop_value);
2035
                    /* GIF spec treats zero as infinite repetition */
2036
                    anim_loop_count = (loop_int <= 0) ? (-1) : loop_int;
2037
                } else if (G_VALUE_HOLDS_UINT(&loop_value)) {
2038
                    guint loop_uint = g_value_get_uint(&loop_value);
2039
                    if (loop_uint == 0U) {
2040
                        anim_loop_count = (-1);
2041
                    } else {
2042
                        anim_loop_count = loop_uint > (guint)INT_MAX
2043
                                            ? INT_MAX
2044
                                            : (int)loop_uint;
2045
                    }
2046
                }
2047
                g_value_unset(&loop_value);
2048
            }
2049
        }
2050
        if (!fstatic &&
2051
                !gdk_pixbuf_animation_is_static_image(animation)) {
2052
            use_animation = TRUE;
2053
        }
2054
    }
2055
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2056
# pragma GCC diagnostic pop
2057
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
2058

2059
    if (! use_animation) {
2060
        /* fall back to single frame decoding */
2061
        pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
2062
        if (pixbuf == NULL) {
2063
            goto end;
2064
        }
2065
        frame->frame_no = 0;
2066
        frame->width = gdk_pixbuf_get_width(pixbuf);
2067
        frame->height = gdk_pixbuf_get_height(pixbuf);
2068
        stride = gdk_pixbuf_get_rowstride(pixbuf);
2069
        sixel_frame_set_pixels(
2070
            frame,
2071
            sixel_allocator_malloc(pchunk->allocator,
2072
                                   (size_t)(frame->height * stride)));
2073
        pixels = sixel_frame_get_pixels(frame);
2074
        if (pixels == NULL) {
2075
            sixel_helper_set_additional_message(
2076
                "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
2077
            status = SIXEL_BAD_ALLOCATION;
2078
            goto end;
2079
        }
2080
        if (stride / frame->width == 4) {
2081
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
2082
            depth = 4;
2083
        } else {
2084
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
2085
            depth = 3;
2086
        }
2087
        p = gdk_pixbuf_get_pixels(pixbuf);
2088
        if (stride == frame->width * depth) {
2089
            memcpy(pixels, p, (size_t)(frame->height * stride));
2090
        } else {
2091
            for (i = 0; i < frame->height; ++i) {
2092
                memcpy(pixels + frame->width * depth * i,
2093
                       p + stride * i,
2094
                       (size_t)(frame->width * depth));
2095
            }
2096
        }
2097
        frame->delay = 0;
2098
        frame->multiframe = 0;
2099
        frame->loop_count = 0;
2100
        status = fn_load(frame, context);
2101
        if (status != SIXEL_OK) {
2102
            goto end;
2103
        }
2104
    } else {
2105
        gboolean finished;
2106

2107
        /* reset iterator to the beginning of the timeline */
2108
        time_val = start_time;
2109
        frame->frame_no = 0;
2110
        frame->loop_count = 0;
2111

2112
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2113
# pragma GCC diagnostic push
2114
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2115
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
2116
        it = gdk_pixbuf_animation_get_iter(animation, &time_val);
2117
        if (it == NULL) {
2118
            status = SIXEL_GDK_ERROR;
2119
            goto end;
2120
        }
2121

2122
        for (;;) {
2123
            /* handle one logical loop of the animation */
2124
            finished = FALSE;
2125
            while (!gdk_pixbuf_animation_iter_on_currently_loading_frame(it)) {
2126
                /* {{{ */
2127
                pixbuf = gdk_pixbuf_animation_iter_get_pixbuf(it);
2128
                if (pixbuf == NULL) {
2129
                    finished = TRUE;
2130
                    break;
2131
                }
2132
                /* allocate a scratch copy of the current frame */
2133
                frame->width = gdk_pixbuf_get_width(pixbuf);
2134
                frame->height = gdk_pixbuf_get_height(pixbuf);
2135
                stride = gdk_pixbuf_get_rowstride(pixbuf);
2136
                sixel_frame_set_pixels(
2137
                    frame,
2138
                    sixel_allocator_malloc(
2139
                        pchunk->allocator,
2140
                        (size_t)(frame->height * stride)));
2141
                pixels = sixel_frame_get_pixels(frame);
2142
                if (pixels == NULL) {
2143
                    sixel_helper_set_additional_message(
2144
                        "load_with_gdkpixbuf: "
2145
                        "sixel_allocator_malloc() failed.");
2146
                    status = SIXEL_BAD_ALLOCATION;
2147
                    goto end;
2148
                }
2149
                if (gdk_pixbuf_get_has_alpha(pixbuf)) {
2150
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
2151
                    depth = 4;
2152
                } else {
2153
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
2154
                    depth = 3;
2155
                }
2156
                p = gdk_pixbuf_get_pixels(pixbuf);
2157
                if (stride == frame->width * depth) {
2158
                    memcpy(pixels, p,
2159
                           (size_t)(frame->height * stride));
2160
                } else {
2161
                    for (i = 0; i < frame->height; ++i) {
2162
                        memcpy(pixels + frame->width * depth * i,
2163
                               p + stride * i,
2164
                               (size_t)(frame->width * depth));
2165
                    }
2166
                }
2167
                delay_ms = gdk_pixbuf_animation_iter_get_delay_time(it);
2168
                if (delay_ms < 0) {
2169
                    delay_ms = 0;
2170
                }
2171
                /*
2172
                 * advance the synthetic clock before asking gdk to move
2173
                 * forward
2174
                 */
2175
                g_time_val_add(&time_val, delay_ms * 1000);
2176
                frame->delay = delay_ms / 10;
2177
                frame->multiframe = 1;
2178

2179
                if (!gdk_pixbuf_animation_iter_advance(it, &time_val)) {
2180
                    finished = TRUE;
2181
                }
2182
                status = fn_load(frame, context);
2183
                if (status != SIXEL_OK) {
2184
                    goto end;
2185
                }
2186
                /* release scratch pixels before decoding the next frame */
2187
                sixel_allocator_free(pchunk->allocator, pixels);
2188
                sixel_frame_set_pixels(frame, NULL);
2189
                frame->frame_no++;
2190

2191
                if (finished) {
2192
                    break;
2193
                }
2194
                /* }}} */
2195
            }
2196

2197
            if (frame->frame_no == 0) {
2198
                break;
2199
            }
2200

2201
            /* finished processing one full loop */
2202
            ++frame->loop_count;
2203

2204
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
2205
                break;
2206
            }
2207
            if (loop_control == SIXEL_LOOP_AUTO) {
2208
                /* obey header-provided loop count when AUTO */
2209
                if (anim_loop_count >= 0 &&
2210
                    frame->loop_count >= anim_loop_count) {
2211
                    break;
2212
                }
2213
            } else if (loop_control != SIXEL_LOOP_FORCE &&
2214
                       anim_loop_count > 0 &&
2215
                       frame->loop_count >= anim_loop_count) {
2216
                break;
2217
            }
2218

2219
            /* restart iteration from the beginning for the next pass */
2220
            g_object_unref(it);
2221
            time_val = start_time;
2222
            it = gdk_pixbuf_animation_get_iter(animation, &time_val);
2223
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2224
# pragma GCC diagnostic pop
2225
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
2226
            if (it == NULL) {
2227
                status = SIXEL_GDK_ERROR;
2228
                goto end;
2229
            }
2230
            /* next pass starts counting frames from zero again */
2231
            frame->frame_no = 0;
2232
        }
2233
    }
2234

2235
    status = SIXEL_OK;
2236

2237
end:
2238
    if (frame) {
2239
        /* drop the reference we obtained from sixel_frame_new() */
2240
        sixel_frame_unref(frame);
2241
    }
2242
    if (it) {
2243
        g_object_unref(it);
2244
    }
2245
    if (loader) {
2246
        if (!loader_closed) {
2247
            /* ensure the incremental loader is finalized even on error paths */
2248
            gdk_pixbuf_loader_close(loader, NULL);
2249
        }
2250
        g_object_unref(loader);
2251
    }
2252

2253
    return status;
2254

2255
}
2256
#endif  /* HAVE_GDK_PIXBUF2 */
2257

2258
#if HAVE_COREGRAPHICS
2259
static SIXELSTATUS
2260
load_with_coregraphics(
3✔
2261
    sixel_chunk_t const       /* in */     *pchunk,
2262
    int                       /* in */     fstatic,
2263
    int                       /* in */     fuse_palette,
2264
    int                       /* in */     reqcolors,
2265
    unsigned char             /* in */     *bgcolor,
2266
    int                       /* in */     loop_control,
2267
    sixel_load_image_function /* in */     fn_load,
2268
    void                      /* in/out */ *context)
2269
{
2270
    SIXELSTATUS status = SIXEL_FALSE;
3✔
2271
    sixel_frame_t *frame = NULL;
3✔
2272
    CFDataRef data = NULL;
3✔
2273
    CGImageSourceRef source = NULL;
3✔
2274
    CGImageRef image = NULL;
3✔
2275
    CGColorSpaceRef color_space = NULL;
3✔
2276
    CGContextRef ctx = NULL;
3✔
2277
    size_t stride;
2278
    size_t frame_count;
2279
    int anim_loop_count = (-1);
3✔
2280
    CFDictionaryRef props = NULL;
3✔
2281
    CFDictionaryRef anim_dict;
2282
    CFNumberRef loop_num;
2283
    CFDictionaryRef frame_props;
2284
    CFDictionaryRef frame_anim_dict;
2285
    CFNumberRef delay_num;
2286
    double delay_sec;
2287
    size_t i;
2288
    unsigned char *pixels;
2289

2290
    (void) fuse_palette;
3✔
2291
    (void) reqcolors;
3✔
2292
    (void) bgcolor;
3✔
2293

2294
    status = sixel_frame_new(&frame, pchunk->allocator);
3✔
2295
    if (SIXEL_FAILED(status)) {
3!
2296
        goto end;
2297
    }
2298

2299
    data = CFDataCreate(kCFAllocatorDefault,
6✔
2300
                        pchunk->buffer,
3✔
2301
                        (CFIndex)pchunk->size);
3✔
2302
    if (! data) {
3!
2303
        status = SIXEL_FALSE;
2304
        goto end;
2305
    }
2306

2307
    source = CGImageSourceCreateWithData(data, NULL);
3✔
2308
    if (! source) {
3!
2309
        status = SIXEL_FALSE;
2310
        goto end;
2311
    }
2312

2313
    frame_count = CGImageSourceGetCount(source);
3✔
2314
    if (! frame_count) {
3!
2315
        status = SIXEL_FALSE;
3✔
2316
        goto end;
3✔
2317
    }
2318
    if (fstatic) {
2319
        frame_count = 1;
2320
    }
2321

2322
    props = CGImageSourceCopyProperties(source, NULL);
2323
    if (props) {
2324
        anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
2325
            props, kCGImagePropertyGIFDictionary);
2326
        if (anim_dict) {
2327
            loop_num = (CFNumberRef)CFDictionaryGetValue(
2328
                anim_dict, kCGImagePropertyGIFLoopCount);
2329
            if (loop_num) {
2330
                CFNumberGetValue(loop_num, kCFNumberIntType, &anim_loop_count);
2331
            }
2332
        }
2333
        CFRelease(props);
2334
    }
2335

2336
    color_space = CGColorSpaceCreateDeviceRGB();
2337
    if (! color_space) {
×
2338
        status = SIXEL_FALSE;
2339
        goto end;
2340
    }
2341

2342
    frame->loop_count = 0;
2343

2344
    for (;;) {
2345
        frame->frame_no = 0;
2346
        for (i = 0; i < frame_count; ++i) {
×
2347
            delay_sec = 0.0;
2348
            frame_props = CGImageSourceCopyPropertiesAtIndex(
2349
                source, (CFIndex)i, NULL);
2350
            if (frame_props) {
2351
                frame_anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
2352
                    frame_props, kCGImagePropertyGIFDictionary);
2353
                if (frame_anim_dict) {
2354
                    delay_num = (CFNumberRef)CFDictionaryGetValue(
2355
                        frame_anim_dict, kCGImagePropertyGIFUnclampedDelayTime);
2356
                    if (! delay_num) {
2357
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
2358
                            frame_anim_dict, kCGImagePropertyGIFDelayTime);
2359
                    }
2360
                    if (delay_num) {
2361
                        CFNumberGetValue(delay_num,
2362
                                         kCFNumberDoubleType,
2363
                                         &delay_sec);
2364
                    }
2365
                }
2366
#if defined(kCGImagePropertyPNGDictionary) && \
2367
    defined(kCGImagePropertyAPNGUnclampedDelayTime) && \
2368
    defined(kCGImagePropertyAPNGDelayTime)
2369
                if (delay_sec <= 0.0) {
2370
                    CFDictionaryRef png_frame;
2371

2372
                    png_frame = (CFDictionaryRef)CFDictionaryGetValue(
2373
                        frame_props, kCGImagePropertyPNGDictionary);
2374
                    if (png_frame) {
2375
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
2376
                            png_frame, kCGImagePropertyAPNGUnclampedDelayTime);
2377
                        if (! delay_num) {
2378
                            delay_num = (CFNumberRef)CFDictionaryGetValue(
2379
                                png_frame, kCGImagePropertyAPNGDelayTime);
2380
                        }
2381
                        if (delay_num) {
2382
                            CFNumberGetValue(delay_num,
2383
                                             kCFNumberDoubleType,
2384
                                             &delay_sec);
2385
                        }
2386
                    }
2387
                }
2388
#endif
2389
                CFRelease(frame_props);
2390
            }
2391
            if (delay_sec <= 0.0) {
2392
                delay_sec = 0.1;
2393
            }
2394
            frame->delay = (int)(delay_sec * 100.0 + 0.5);
2395
            if (frame->delay < 1) {
2396
                frame->delay = 1;
2397
            }
2398

2399
            image = CGImageSourceCreateImageAtIndex(source, (CFIndex)i, NULL);
2400
            if (! image) {
×
2401
                status = SIXEL_FALSE;
2402
                goto end;
2403
            }
2404

2405
            frame->width = (int)CGImageGetWidth(image);
2406
            frame->height = (int)CGImageGetHeight(image);
2407
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
2408
            stride = (size_t)frame->width * 4;
2409
            pixels = sixel_frame_get_pixels(frame);
2410
            if (pixels != NULL) {
2411
                sixel_allocator_free(pchunk->allocator, pixels);
2412
                sixel_frame_set_pixels(frame, NULL);
2413
            }
2414
            sixel_frame_set_pixels(frame,
2415
                                   sixel_allocator_malloc(
2416
                                       pchunk->allocator,
2417
                                       (size_t)(frame->height * stride)));
2418

2419
            pixels = sixel_frame_get_pixels(frame);
2420
            if (pixels == NULL) {
×
2421
                sixel_helper_set_additional_message(
2422
                    "load_with_coregraphics: sixel_allocator_malloc() failed.");
2423
                status = SIXEL_BAD_ALLOCATION;
2424
                CGImageRelease(image);
2425
                goto end;
2426
            }
2427

2428
            ctx = CGBitmapContextCreate(pixels,
2429
                                        frame->width,
2430
                                        frame->height,
2431
                                        8,
2432
                                        stride,
2433
                                        color_space,
2434
                                        kCGImageAlphaPremultipliedLast |
2435
                                            kCGBitmapByteOrder32Big);
2436
            if (!ctx) {
×
2437
                CGImageRelease(image);
2438
                goto end;
2439
            }
2440

2441
            CGContextDrawImage(ctx,
2442
                               CGRectMake(0, 0, frame->width, frame->height),
2443
                               image);
2444
            CGContextRelease(ctx);
2445
            ctx = NULL;
2446

2447
            frame->multiframe = (frame_count > 1);
2448
            status = fn_load(frame, context);
2449
            CGImageRelease(image);
2450
            image = NULL;
2451
            if (status != SIXEL_OK) {
×
2452
                goto end;
2453
            }
2454
            ++frame->frame_no;
2455
        }
2456

2457
        ++frame->loop_count;
2458

2459
        if (frame_count <= 1) {
×
2460
            break;
2461
        }
2462
        if (loop_control == SIXEL_LOOP_DISABLE) {
×
2463
            break;
2464
        }
2465
        if (loop_control == SIXEL_LOOP_AUTO) {
×
2466
            if (anim_loop_count < 0) {
×
2467
                break;
2468
            }
2469
            if (anim_loop_count > 0 && frame->loop_count >= anim_loop_count) {
2470
                break;
2471
            }
2472
            continue;
2473
        }
2474
    }
2475

2476
    status = SIXEL_OK;
2477

2478
end:
2479
    if (ctx) {
3✔
2480
        CGContextRelease(ctx);
2481
    }
2482
    if (color_space) {
2483
        CGColorSpaceRelease(color_space);
2484
    }
2485
    if (image) {
2486
        CGImageRelease(image);
2487
    }
2488
    if (source) {
3✔
2489
        CFRelease(source);
3✔
2490
    }
3✔
2491
    if (data) {
3✔
2492
        CFRelease(data);
3✔
2493
    }
3✔
2494
    if (frame) {
3✔
2495
        sixel_frame_unref(frame);
3✔
2496
    }
3✔
2497
    return status;
3✔
2498
}
2499
#endif  /* HAVE_COREGRAPHICS */
2500

2501
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
2502
static SIXELSTATUS
2503
load_with_quicklook(
2504
    sixel_chunk_t const       /* in */     *pchunk,
2505
    int                       /* in */     fstatic,
2506
    int                       /* in */     fuse_palette,
2507
    int                       /* in */     reqcolors,
2508
    unsigned char             /* in */     *bgcolor,
2509
    int                       /* in */     loop_control,
2510
    sixel_load_image_function /* in */     fn_load,
2511
    void                      /* in/out */ *context)
2512
{
2513
    SIXELSTATUS status = SIXEL_FALSE;
2514
    sixel_frame_t *frame = NULL;
2515
    CFStringRef path = NULL;
2516
    CFURLRef url = NULL;
2517
    CGImageRef image = NULL;
2518
    CGColorSpaceRef color_space = NULL;
2519
    CGContextRef ctx = NULL;
2520
    CGRect bounds;
2521
    size_t stride;
2522
    unsigned char fill_color[3];
2523
    CGFloat fill_r;
2524
    CGFloat fill_g;
2525
    CGFloat fill_b;
2526
    CGFloat max_dimension;
2527
    CGSize max_size;
2528
    unsigned char *pixels;
2529

2530
    (void)fstatic;
2531
    (void)fuse_palette;
2532
    (void)reqcolors;
2533
    (void)loop_control;
2534

2535
    if (pchunk == NULL || pchunk->source_path == NULL) {
2536
        goto end;
2537
    }
2538

2539
    loader_thumbnailer_initialize_size_hint();
2540

2541
    status = sixel_frame_new(&frame, pchunk->allocator);
2542
    if (SIXEL_FAILED(status)) {
×
2543
        goto end;
2544
    }
2545

2546
    path = CFStringCreateWithCString(kCFAllocatorDefault,
2547
                                     pchunk->source_path,
2548
                                     kCFStringEncodingUTF8);
2549
    if (path == NULL) {
×
2550
        status = SIXEL_RUNTIME_ERROR;
2551
        goto end;
2552
    }
2553

2554
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
2555
                                        path,
2556
                                        kCFURLPOSIXPathStyle,
2557
                                        false);
2558
    if (url == NULL) {
×
2559
        status = SIXEL_RUNTIME_ERROR;
2560
        goto end;
2561
    }
2562

2563
    if (thumbnailer_size_hint > 0) {
×
2564
        max_dimension = (CGFloat)thumbnailer_size_hint;
2565
    } else {
2566
        max_dimension = (CGFloat)SIXEL_THUMBNAILER_DEFAULT_SIZE;
2567
    }
2568
    max_size.width = max_dimension;
2569
    max_size.height = max_dimension;
2570
#if HAVE_QUICKLOOK_THUMBNAILING
2571
    image = sixel_quicklook_thumbnail_create(url, max_size);
2572
    if (image == NULL) {
2573
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2574
#  pragma clang diagnostic push
2575
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
2576
# endif
2577
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
2578
                                       url,
2579
                                       max_size,
2580
                                       NULL);
2581
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2582
#  pragma clang diagnostic pop
2583
# endif
2584
    }
2585
#else
2586
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2587
#  pragma clang diagnostic push
2588
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
2589
# endif
2590
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
2591
                                   url,
2592
                                   max_size,
2593
                                   NULL);
2594
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2595
#  pragma clang diagnostic pop
2596
# endif
2597
#endif
2598
    if (image == NULL) {
×
2599
        status = SIXEL_RUNTIME_ERROR;
2600
        sixel_helper_set_additional_message(
2601
            "load_with_quicklook: CQLThumbnailImageCreate() failed.");
2602
        goto end;
2603
    }
2604

2605
    color_space = CGColorSpaceCreateDeviceRGB();
2606
    if (color_space == NULL) {
×
2607
        status = SIXEL_RUNTIME_ERROR;
2608
        sixel_helper_set_additional_message(
2609
            "load_with_quicklook: CGColorSpaceCreateDeviceRGB() failed.");
2610
        goto end;
2611
    }
2612

2613
    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
2614
    frame->width = (int)CGImageGetWidth(image);
2615
    frame->height = (int)CGImageGetHeight(image);
2616
    if (frame->width <= 0 || frame->height <= 0) {
2617
        status = SIXEL_RUNTIME_ERROR;
2618
        sixel_helper_set_additional_message(
2619
            "load_with_quicklook: invalid image size detected.");
2620
        goto end;
2621
    }
2622

2623
    stride = (size_t)frame->width * 4;
2624
    sixel_frame_set_pixels(
2625
        frame,
2626
        sixel_allocator_malloc(pchunk->allocator,
2627
                               (size_t)frame->height * stride));
2628
    pixels = sixel_frame_get_pixels(frame);
2629
    if (pixels == NULL) {
×
2630
        sixel_helper_set_additional_message(
2631
            "load_with_quicklook: sixel_allocator_malloc() failed.");
2632
        status = SIXEL_BAD_ALLOCATION;
2633
        goto end;
2634
    }
2635

2636
    if (bgcolor != NULL) {
×
2637
        fill_color[0] = bgcolor[0];
2638
        fill_color[1] = bgcolor[1];
2639
        fill_color[2] = bgcolor[2];
2640
    } else {
2641
        fill_color[0] = 255;
2642
        fill_color[1] = 255;
2643
        fill_color[2] = 255;
2644
    }
2645

2646
    ctx = CGBitmapContextCreate(pixels,
2647
                                frame->width,
2648
                                frame->height,
2649
                                8,
2650
                                stride,
2651
                                color_space,
2652
                                kCGImageAlphaPremultipliedLast |
2653
                                    kCGBitmapByteOrder32Big);
2654
    if (ctx == NULL) {
×
2655
        status = SIXEL_RUNTIME_ERROR;
2656
        sixel_helper_set_additional_message(
2657
            "load_with_quicklook: CGBitmapContextCreate() failed.");
2658
        goto end;
2659
    }
2660

2661
    bounds = CGRectMake(0,
2662
                        0,
2663
                        (CGFloat)frame->width,
2664
                        (CGFloat)frame->height);
2665
    fill_r = (CGFloat)fill_color[0] / 255.0f;
2666
    fill_g = (CGFloat)fill_color[1] / 255.0f;
2667
    fill_b = (CGFloat)fill_color[2] / 255.0f;
2668
    CGContextSetRGBFillColor(ctx, fill_r, fill_g, fill_b, 1.0f);
2669
    CGContextFillRect(ctx, bounds);
2670
    CGContextDrawImage(ctx, bounds, image);
2671
    CGContextFlush(ctx);
2672

2673
    /* Abort when Quick Look produced no visible pixels so other loaders run. */
2674
    {
2675
        size_t pixel_count;
2676
        size_t index;
2677
        unsigned char *pixel;
2678
        int has_content;
2679

2680
        pixel_count = (size_t)frame->width * (size_t)frame->height;
2681
        pixel = pixels;
2682
        has_content = 0;
2683
        for (index = 0; index < pixel_count; ++index) {
2684
            if (pixel[0] != fill_color[0] ||
2685
                    pixel[1] != fill_color[1] ||
2686
                    pixel[2] != fill_color[2] ||
2687
                    pixel[3] != 0xff) {
2688
                has_content = 1;
2689
                break;
2690
            }
2691
            pixel += 4;
2692
        }
2693
        if (! has_content) {
×
2694
            sixel_helper_set_additional_message(
2695
                "load_with_quicklook: thumbnail contained no visible pixels.");
2696
            status = SIXEL_BAD_INPUT;
2697
            CGContextRelease(ctx);
2698
            ctx = NULL;
2699
            goto end;
2700
        }
2701
    }
2702

2703
    CGContextRelease(ctx);
2704
    ctx = NULL;
2705

2706
    frame->delay = 0;
2707
    frame->frame_no = 0;
2708
    frame->loop_count = 1;
2709
    frame->multiframe = 0;
2710
    frame->transparent = (-1);
2711

2712
    status = sixel_frame_strip_alpha(frame, fill_color);
2713
    if (SIXEL_FAILED(status)) {
×
2714
        goto end;
2715
    }
2716

2717
    status = fn_load(frame, context);
2718
    if (status != SIXEL_OK) {
×
2719
        goto end;
2720
    }
2721

2722
    status = SIXEL_OK;
2723

2724
end:
2725
    if (ctx != NULL) {
2726
        CGContextRelease(ctx);
2727
    }
2728
    if (color_space != NULL) {
2729
        CGColorSpaceRelease(color_space);
2730
    }
2731
    if (image != NULL) {
2732
        CGImageRelease(image);
2733
    }
2734
    if (url != NULL) {
2735
        CFRelease(url);
2736
    }
2737
    if (path != NULL) {
2738
        CFRelease(path);
2739
    }
2740
    if (frame != NULL) {
2741
        sixel_frame_unref(frame);
2742
    }
2743

2744
    return status;
2745
}
2746
#endif  /* HAVE_COREGRAPHICS && HAVE_QUICKLOOK */
2747

2748
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
2749

2750
# if defined(HAVE_NANOSLEEP)
2751
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
2752
# endif
2753
# if defined(HAVE_REALPATH)
2754
char * realpath(const char *restrict path, char *restrict resolved_path);
2755
# endif
2756
# if defined(HAVE_MKSTEMP)
2757
int mkstemp(char *);
2758
# endif
2759

2760
/*
2761
 * thumbnailer_message_finalize
2762
 *
2763
 * Clamp formatted messages so callers do not have to repeat truncation
2764
 * checks after calling sixel_compat_snprintf().
2765
 */
2766
static void
2767
thumbnailer_message_finalize(char *buffer, size_t capacity, int written)
×
2768
{
2769
    if (buffer == NULL || capacity == 0) {
×
2770
        return;
×
2771
    }
2772

2773
    if (written < 0) {
×
2774
        buffer[0] = '\0';
×
2775
        return;
×
2776
    }
2777

2778
    if ((size_t)written >= capacity) {
×
2779
        buffer[capacity - 1u] = '\0';
×
2780
    }
2781
}
2782

2783
/*
2784
 * thumbnailer_sleep_briefly
2785
 *
2786
 * Yield the CPU for a short duration so child polling loops avoid busy
2787
 * waiting.
2788
 *
2789
 */
2790
static void
2791
thumbnailer_sleep_briefly(void)
×
2792
{
2793
# if HAVE_NANOSLEEP
2794
    struct timespec ts;
2795
# endif
2796

2797
# if HAVE_NANOSLEEP
2798
    ts.tv_sec = 0;
×
2799
    ts.tv_nsec = 10000000L;
×
2800
    nanosleep(&ts, NULL);
×
2801
# elif defined(_WIN32)
2802
    Sleep(10);
2803
# else
2804
    (void)usleep(10000);
2805
# endif
2806
}
×
2807

2808
# if !defined(_WIN32) && defined(HAVE__REALPATH) && !defined(HAVE_REALPATH)
2809
static char *
2810
thumbnailer_resolve_without_realpath(char const *path)
2811
{
2812
    char *cwd;
2813
    char *resolved;
2814
    size_t cwd_length;
2815
    size_t path_length;
2816
    int need_separator;
2817

2818
    cwd = NULL;
2819
    resolved = NULL;
2820
    cwd_length = 0;
2821
    path_length = 0;
2822
    need_separator = 0;
2823

2824
    if (path == NULL) {
2825
        return NULL;
2826
    }
2827

2828
    if (path[0] == '/') {
2829
        path_length = strlen(path);
2830
        resolved = malloc(path_length + 1);
2831
        if (resolved == NULL) {
2832
            return NULL;
2833
        }
2834
        memcpy(resolved, path, path_length + 1);
2835

2836
        return resolved;
2837
    }
2838

2839
#  if defined(PATH_MAX)
2840
    cwd = malloc(PATH_MAX);
2841
    if (cwd != NULL) {
2842
        if (getcwd(cwd, PATH_MAX) != NULL) {
2843
            cwd_length = strlen(cwd);
2844
            path_length = strlen(path);
2845
            need_separator = 0;
2846
            if (cwd_length > 0 && cwd[cwd_length - 1] != '/') {
2847
                need_separator = 1;
2848
            }
2849
            resolved = malloc(cwd_length + need_separator + path_length + 1);
2850
            if (resolved != NULL) {
2851
                memcpy(resolved, cwd, cwd_length);
2852
                if (need_separator != 0) {
2853
                    resolved[cwd_length] = '/';
2854
                }
2855
                memcpy(resolved + cwd_length + need_separator,
2856
                       path,
2857
                       path_length + 1);
2858
            }
2859
            free(cwd);
2860
            if (resolved != NULL) {
2861
                return resolved;
2862
            }
2863
        } else {
2864
            free(cwd);
2865
        }
2866
    }
2867
#  endif  /* PATH_MAX */
2868

2869
    path_length = strlen(path);
2870
    resolved = malloc(path_length + 1);
2871
    if (resolved == NULL) {
2872
        return NULL;
2873
    }
2874
    memcpy(resolved, path, path_length + 1);
2875

2876
    return resolved;
2877
}
2878
# endif  /* _WIN32 off, HAVE__REALPATH on, HAVE_REALPATH off */
2879

2880
/*
2881
 * thumbnailer_resolve_path
2882
 *
2883
 * Resolve the supplied path to an absolute canonical path when possible.
2884
 *
2885
 * Arguments:
2886
 *     path - original filesystem path.
2887
 * Returns:
2888
 *     Newly allocated canonical path or NULL on failure.
2889
 */
2890
static char *
2891
thumbnailer_resolve_path(char const *path)
×
2892
{
2893
    char *resolved;
2894

2895
    resolved = NULL;
×
2896

2897
    if (path == NULL) {
×
2898
        return NULL;
×
2899
    }
2900

2901
# if defined(HAVE__FULLPATH)
2902
    resolved = _fullpath(NULL, path, 0);
2903
# elif defined(HAVE__REALPATH)
2904
    resolved = _realpath(path, NULL);
2905
# elif defined(HAVE_REALPATH)
2906
    resolved = realpath(path, NULL);
×
2907
# else
2908
    resolved = thumbnailer_resolve_without_realpath(path);
2909
# endif
2910

2911
    return resolved;
×
2912
}
2913

2914
struct thumbnailer_string_list {
2915
    char **items;
2916
    size_t length;
2917
    size_t capacity;
2918
};
2919

2920
struct thumbnailer_entry {
2921
    char *exec_line;
2922
    char *tryexec;
2923
    struct thumbnailer_string_list *mime_types;
2924
};
2925

2926
/*
2927
 * thumbnailer_strdup
2928
 *
2929
 * Duplicate a string with malloc so thumbnail helpers own their copies.
2930
 *
2931
 * Arguments:
2932
 *     src - zero-terminated string to copy; may be NULL.
2933
 * Returns:
2934
 *     Newly allocated duplicate or NULL on failure/NULL input.
2935
 */
2936
static char *
2937
thumbnailer_strdup(char const *src)
20✔
2938
{
2939
    char *copy;
2940
    size_t length;
2941

2942
    copy = NULL;
20✔
2943
    length = 0;
20✔
2944

2945
    if (src == NULL) {
20!
2946
        return NULL;
×
2947
    }
2948

2949
    length = strlen(src);
20✔
2950
    copy = malloc(length + 1);
20✔
2951
    if (copy == NULL) {
20!
2952
        return NULL;
×
2953
    }
2954
    memcpy(copy, src, length + 1);
20✔
2955

2956
    return copy;
20✔
2957
}
4✔
2958

2959
/*
2960
 * thumbnailer_string_list_new
2961
 *
2962
 * Allocate an empty expandable string list used throughout the loader.
2963
 *
2964
 * Arguments:
2965
 *     None.
2966
 * Returns:
2967
 *     Newly allocated list instance or NULL on failure.
2968
 */
2969
static struct thumbnailer_string_list *
2970
thumbnailer_string_list_new(void)
5✔
2971
{
2972
    struct thumbnailer_string_list *list;
2973

2974
    list = malloc(sizeof(*list));
5✔
2975
    if (list == NULL) {
5!
2976
        return NULL;
×
2977
    }
2978

2979
    list->items = NULL;
5✔
2980
    list->length = 0;
5✔
2981
    list->capacity = 0;
5✔
2982

2983
    return list;
5✔
2984
}
1✔
2985

2986
/*
2987
 * thumbnailer_string_list_free
2988
 *
2989
 * Release every string stored in the list and free the container itself.
2990
 *
2991
 * Arguments:
2992
 *     list - list instance produced by thumbnailer_string_list_new().
2993
 */
2994
static void
2995
thumbnailer_string_list_free(struct thumbnailer_string_list *list)
10✔
2996
{
2997
    size_t index;
2998

2999
    index = 0;
10✔
3000

3001
    if (list == NULL) {
10✔
3002
        return;
5✔
3003
    }
3004

3005
    if (list->items != NULL) {
5!
3006
        for (index = 0; index < list->length; ++index) {
20✔
3007
            free(list->items[index]);
15✔
3008
            list->items[index] = NULL;
15✔
3009
        }
3✔
3010
        free(list->items);
5✔
3011
        list->items = NULL;
5✔
3012
    }
1✔
3013

3014
    free(list);
5✔
3015
}
2✔
3016

3017
/*
3018
 * thumbnailer_string_list_append
3019
 *
3020
 * Append a copy of the supplied string to the dynamic list.
3021
 *
3022
 * Arguments:
3023
 *     list  - destination list.
3024
 *     value - string to duplicate and append.
3025
 * Returns:
3026
 *     1 on success, 0 on allocation failure or invalid input.
3027
 */
3028
static int
3029
thumbnailer_string_list_append(struct thumbnailer_string_list *list,
15✔
3030
                               char const *value)
3031
{
3032
    size_t new_capacity;
3033
    char **new_items;
3034
    char *copy;
3035

3036
    new_capacity = 0;
15✔
3037
    new_items = NULL;
15✔
3038
    copy = NULL;
15✔
3039

3040
    if (list == NULL || value == NULL) {
15!
3041
        return 0;
×
3042
    }
3043

3044
    copy = thumbnailer_strdup(value);
15✔
3045
    if (copy == NULL) {
15!
3046
        return 0;
×
3047
    }
3048

3049
    if (list->length == list->capacity) {
15✔
3050
        new_capacity = (list->capacity == 0) ? 4 : list->capacity * 2;
5!
3051
        new_items = realloc(list->items,
6✔
3052
                            new_capacity * sizeof(*list->items));
1✔
3053
        if (new_items == NULL) {
5!
3054
            free(copy);
×
3055
            return 0;
×
3056
        }
3057
        list->items = new_items;
5✔
3058
        list->capacity = new_capacity;
5✔
3059
    }
1✔
3060

3061
    list->items[list->length] = copy;
15✔
3062
    list->length += 1;
15✔
3063

3064
    return 1;
15✔
3065
}
3✔
3066

3067
/*
3068
 * thumbnailer_entry_init
3069
 *
3070
 * Prepare a thumbnailer_entry structure for population.
3071
 *
3072
 * Arguments:
3073
 *     entry - caller-provided structure to initialize.
3074
 */
3075
static void
3076
thumbnailer_entry_init(struct thumbnailer_entry *entry)
5✔
3077
{
3078
    if (entry == NULL) {
5!
3079
        return;
×
3080
    }
3081

3082
    entry->exec_line = NULL;
5✔
3083
    entry->tryexec = NULL;
5✔
3084
    entry->mime_types = NULL;
5✔
3085
}
1✔
3086

3087
/*
3088
 * thumbnailer_entry_clear
3089
 *
3090
 * Release every heap allocation associated with a thumbnailer_entry.
3091
 *
3092
 * Arguments:
3093
 *     entry - structure previously initialized with thumbnailer_entry_init().
3094
 */
3095
static void
3096
thumbnailer_entry_clear(struct thumbnailer_entry *entry)
5✔
3097
{
3098
    if (entry == NULL) {
5!
3099
        return;
×
3100
    }
3101

3102
    free(entry->exec_line);
5✔
3103
    entry->exec_line = NULL;
5✔
3104
    free(entry->tryexec);
5✔
3105
    entry->tryexec = NULL;
5✔
3106
    thumbnailer_string_list_free(entry->mime_types);
5✔
3107
    entry->mime_types = NULL;
5✔
3108
}
1✔
3109

3110
/*
3111
 * thumbnailer_join_paths
3112
 *
3113
 * Concatenate two path fragments inserting a slash when required.
3114
 *
3115
 * Arguments:
3116
 *     left  - directory prefix.
3117
 *     right - trailing component.
3118
 * Returns:
3119
 *     Newly allocated combined path or NULL on failure.
3120
 */
3121
static char *
3122
thumbnailer_join_paths(char const *left, char const *right)
20✔
3123
{
3124
    size_t left_length;
3125
    size_t right_length;
3126
    int need_separator;
3127
    char *combined;
3128

3129
    left_length = 0;
20✔
3130
    right_length = 0;
20✔
3131
    need_separator = 0;
20✔
3132
    combined = NULL;
20✔
3133

3134
    if (left == NULL || right == NULL) {
20!
3135
        return NULL;
×
3136
    }
3137

3138
    left_length = strlen(left);
20✔
3139
    right_length = strlen(right);
20✔
3140
    need_separator = 0;
20✔
3141

3142
    if (left_length > 0 && right_length > 0 &&
24!
3143
            left[left_length - 1] != '/' && right[0] != '/') {
20!
3144
        need_separator = 1;
20✔
3145
    }
4✔
3146

3147
    combined = malloc(left_length + right_length + need_separator + 1);
20✔
3148
    if (combined == NULL) {
20!
3149
        return NULL;
×
3150
    }
3151

3152
    memcpy(combined, left, left_length);
20✔
3153
    if (need_separator) {
20!
3154
        combined[left_length] = '/';
20✔
3155
        memcpy(combined + left_length + 1, right, right_length);
20✔
3156
        combined[left_length + right_length + 1] = '\0';
20✔
3157
    } else {
4✔
3158
        memcpy(combined + left_length, right, right_length);
×
3159
        combined[left_length + right_length] = '\0';
×
3160
    }
3161

3162
    return combined;
20✔
3163
}
4✔
3164

3165
/*
3166
 * thumbnailer_collect_directories
3167
 *
3168
 * Enumerate directories that may contain FreeDesktop thumbnailer
3169
 * definitions according to the XDG specification.
3170
 *
3171
 * GNOME thumbnailers follow the XDG data directory contract:
3172
 *
3173
 *     +------------------+      +---------------------------+
3174
 *     | HOME/.local/share| ---> | HOME/.local/share/        |
3175
 *     |                  |      |    thumbnailers/(*.thumbnailer)
3176
 *     +------------------+      +---------------------------+
3177
 *
3178
 *     +------------------+      +---------------------------+
3179
 *     | XDG_DATA_DIRS    | ---> | <dir>/thumbnailers/(*.thumbnailer)
3180
 *     +------------------+      +---------------------------+
3181
 *
3182
 * The helper below expands both sources so that the caller can iterate
3183
 * through every known definition in order of precedence.
3184
 *
3185
 * Arguments:
3186
 *     None.
3187
 * Returns:
3188
 *     Newly allocated list of directory paths or NULL on failure.
3189
 */
3190
static struct thumbnailer_string_list *
3191
thumbnailer_collect_directories(void)
5✔
3192
{
3193
    struct thumbnailer_string_list *dirs;
3194
    char const *xdg_data_dirs;
3195
    char const *home_dir;
3196
    char const *default_dirs;
3197
    char *candidate;
3198
    char *local_share;
3199
    char *dirs_copy;
3200
    char *token;
3201

3202
    dirs = NULL;
5✔
3203
    xdg_data_dirs = NULL;
5✔
3204
    home_dir = NULL;
5✔
3205
    default_dirs = NULL;
5✔
3206
    candidate = NULL;
5✔
3207
    local_share = NULL;
5✔
3208
    dirs_copy = NULL;
5✔
3209
    token = NULL;
5✔
3210

3211
    dirs = thumbnailer_string_list_new();
5✔
3212
    if (dirs == NULL) {
5!
3213
        return NULL;
×
3214
    }
3215

3216
    home_dir = getenv("HOME");
5✔
3217
    loader_trace_message(
9!
3218
        "thumbnailer_collect_directories: HOME=%s",
3219
        (home_dir != NULL && home_dir[0] != '\0') ? home_dir : "(unset)");
5!
3220
    if (home_dir != NULL && home_dir[0] != '\0') {
5!
3221
        local_share = thumbnailer_join_paths(home_dir,
5✔
3222
                                             ".local/share");
3223
        if (local_share != NULL) {
5!
3224
            candidate = thumbnailer_join_paths(local_share,
5✔
3225
                                               "thumbnailers");
3226
            if (candidate != NULL) {
5!
3227
                if (!thumbnailer_string_list_append(dirs, candidate)) {
5!
3228
                    free(candidate);
×
3229
                    free(local_share);
×
3230
                    thumbnailer_string_list_free(dirs);
×
3231
                    return NULL;
×
3232
                }
3233
                loader_trace_message(
5✔
3234
                    "thumbnailer_collect_directories: added %s",
3235
                    candidate);
1✔
3236
                free(candidate);
5✔
3237
                candidate = NULL;
5✔
3238
            }
1✔
3239
            free(local_share);
5✔
3240
            local_share = NULL;
5✔
3241
        }
1✔
3242
    }
1✔
3243

3244
    xdg_data_dirs = getenv("XDG_DATA_DIRS");
6✔
3245
    if (xdg_data_dirs == NULL || xdg_data_dirs[0] == '\0') {
6!
3246
        default_dirs = "/usr/local/share:/usr/share";
5✔
3247
        xdg_data_dirs = default_dirs;
5✔
3248
    }
1✔
3249
    loader_trace_message(
5✔
3250
        "thumbnailer_collect_directories: XDG_DATA_DIRS=%s",
3251
        xdg_data_dirs);
1✔
3252

3253
    dirs_copy = thumbnailer_strdup(xdg_data_dirs);
5✔
3254
    if (dirs_copy == NULL) {
5!
3255
        thumbnailer_string_list_free(dirs);
×
3256
        return NULL;
×
3257
    }
3258
    token = strtok(dirs_copy, ":");
5✔
3259
    while (token != NULL) {
15✔
3260
        candidate = thumbnailer_join_paths(token, "thumbnailers");
10✔
3261
        if (candidate != NULL) {
10!
3262
            if (!thumbnailer_string_list_append(dirs, candidate)) {
10!
3263
                free(candidate);
×
3264
                free(dirs_copy);
×
3265
                thumbnailer_string_list_free(dirs);
×
3266
                return NULL;
×
3267
            }
3268
            loader_trace_message(
10✔
3269
                "thumbnailer_collect_directories: added %s",
3270
                candidate);
2✔
3271
            free(candidate);
10✔
3272
            candidate = NULL;
10✔
3273
        }
2✔
3274
        token = strtok(NULL, ":");
10✔
3275
    }
3276
    free(dirs_copy);
5✔
3277
    dirs_copy = NULL;
5✔
3278

3279
    return dirs;
5✔
3280
}
1✔
3281

3282
/*
3283
 * thumbnailer_trim_right
3284
 *
3285
 * Remove trailing whitespace in place from a mutable string.
3286
 *
3287
 * Arguments:
3288
 *     text - string to trim; must be writable and zero-terminated.
3289
 */
3290
static void
3291
thumbnailer_trim_right(char *text)
×
3292
{
3293
    size_t length;
3294

3295
    length = 0;
×
3296

3297
    if (text == NULL) {
×
3298
        return;
×
3299
    }
3300

3301
    length = strlen(text);
×
3302
    while (length > 0 && isspace((unsigned char)text[length - 1]) != 0) {
×
3303
        text[length - 1] = '\0';
×
3304
        length -= 1;
×
3305
    }
3306
}
3307

3308
/*
3309
 * thumbnailer_trim_left
3310
 *
3311
 * Skip leading whitespace so parsers can focus on significant tokens.
3312
 *
3313
 * Arguments:
3314
 *     text - string to inspect; may be NULL.
3315
 * Returns:
3316
 *     Pointer to first non-space character or NULL when input is NULL.
3317
 */
3318
static char *
3319
thumbnailer_trim_left(char *text)
×
3320
{
3321
    if (text == NULL) {
×
3322
        return NULL;
×
3323
    }
3324

3325
    while (*text != '\0' && isspace((unsigned char)*text) != 0) {
×
3326
        text += 1;
×
3327
    }
3328

3329
    return text;
×
3330
}
3331

3332
/*
3333
 * thumbnailer_parse_file
3334
 *
3335
 * Populate a thumbnailer_entry by parsing a .thumbnailer ini file.
3336
 *
3337
 * Arguments:
3338
 *     path  - filesystem path to the ini file.
3339
 *     entry - output structure initialized with thumbnailer_entry_init().
3340
 * Returns:
3341
 *     1 on success, 0 on parse error or allocation failure.
3342
 */
3343
static int
3344
thumbnailer_parse_file(char const *path, struct thumbnailer_entry *entry)
×
3345
{
3346
    FILE *fp;
3347
    char line[1024];
3348
    int in_group;
3349
    char *trimmed;
3350
    char *key_end;
3351
    char *value;
3352
    char *token_start;
3353
    char *token_end;
3354
    struct thumbnailer_string_list *mime_types;
3355
    size_t index;
3356

3357
    fp = NULL;
×
3358
    in_group = 0;
×
3359
    trimmed = NULL;
×
3360
    key_end = NULL;
×
3361
    value = NULL;
×
3362
    token_start = NULL;
×
3363
    token_end = NULL;
×
3364
    mime_types = NULL;
×
3365
    index = 0;
×
3366

3367
    if (path == NULL || entry == NULL) {
×
3368
        return 0;
×
3369
    }
3370

3371
    fp = fopen(path, "r");
×
3372
    if (fp == NULL) {
×
3373
        return 0;
×
3374
    }
3375

3376
    mime_types = thumbnailer_string_list_new();
×
3377
    if (mime_types == NULL) {
×
3378
        fclose(fp);
×
3379
        fp = NULL;
×
3380
        return 0;
×
3381
    }
3382

3383
    while (fgets(line, sizeof(line), fp) != NULL) {
×
3384
        trimmed = thumbnailer_trim_left(line);
×
3385
        thumbnailer_trim_right(trimmed);
×
3386
        if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') {
×
3387
            continue;
×
3388
        }
3389
        if (trimmed[0] == '[') {
×
3390
            key_end = strchr(trimmed, ']');
×
3391
            if (key_end != NULL) {
×
3392
                *key_end = '\0';
×
3393
                if (strcmp(trimmed + 1, "Thumbnailer Entry") == 0) {
×
3394
                    in_group = 1;
×
3395
                } else {
3396
                    in_group = 0;
×
3397
                }
3398
            }
3399
            continue;
×
3400
        }
3401
        if (!in_group) {
×
3402
            continue;
×
3403
        }
3404
        key_end = strchr(trimmed, '=');
×
3405
        if (key_end == NULL) {
×
3406
            continue;
×
3407
        }
3408
        *key_end = '\0';
×
3409
        value = thumbnailer_trim_left(key_end + 1);
×
3410
        thumbnailer_trim_right(trimmed);
×
3411
        thumbnailer_trim_right(value);
×
3412
        if (strcmp(trimmed, "Exec") == 0) {
×
3413
            free(entry->exec_line);
×
3414
            entry->exec_line = thumbnailer_strdup(value);
×
3415
            if (entry->exec_line == NULL) {
×
3416
                fclose(fp);
×
3417
                fp = NULL;
×
3418
                thumbnailer_string_list_free(mime_types);
×
3419
                mime_types = NULL;
×
3420
                return 0;
×
3421
            }
3422
        } else if (strcmp(trimmed, "TryExec") == 0) {
×
3423
            free(entry->tryexec);
×
3424
            entry->tryexec = thumbnailer_strdup(value);
×
3425
            if (entry->tryexec == NULL) {
×
3426
                fclose(fp);
×
3427
                fp = NULL;
×
3428
                thumbnailer_string_list_free(mime_types);
×
3429
                mime_types = NULL;
×
3430
                return 0;
×
3431
            }
3432
        } else if (strcmp(trimmed, "MimeType") == 0) {
×
3433
            for (index = 0; index < mime_types->length; ++index) {
×
3434
                free(mime_types->items[index]);
×
3435
                mime_types->items[index] = NULL;
×
3436
            }
3437
            mime_types->length = 0;
×
3438
            token_start = value;
×
3439
            while (token_start != NULL && token_start[0] != '\0') {
×
3440
                token_end = strchr(token_start, ';');
×
3441
                if (token_end != NULL) {
×
3442
                    *token_end = '\0';
×
3443
                }
3444
                token_start = thumbnailer_trim_left(token_start);
×
3445
                thumbnailer_trim_right(token_start);
×
3446
                if (token_start[0] != '\0') {
×
3447
                    if (!thumbnailer_string_list_append(mime_types,
×
3448
                                                       token_start)) {
3449
                        fclose(fp);
×
3450
                        fp = NULL;
×
3451
                        thumbnailer_string_list_free(mime_types);
×
3452
                        mime_types = NULL;
×
3453
                        return 0;
×
3454
                    }
3455
                }
3456
                if (token_end == NULL) {
×
3457
                    break;
×
3458
                }
3459
                token_start = token_end + 1;
×
3460
            }
3461
        }
3462
    }
3463

3464
    fclose(fp);
×
3465
    fp = NULL;
×
3466

3467
    thumbnailer_string_list_free(entry->mime_types);
×
3468
    entry->mime_types = mime_types;
×
3469

3470
    return 1;
×
3471
}
3472

3473
/*
3474
 * thumbnailer_has_tryexec
3475
 *
3476
 * Confirm that the optional TryExec binary exists and is executable.
3477
 *
3478
 * Arguments:
3479
 *     tryexec - value from the .thumbnailer file; may be NULL.
3480
 * Returns:
3481
 *     1 when executable, 0 otherwise.
3482
 */
3483
static int
3484
thumbnailer_has_tryexec(char const *tryexec)
×
3485
{
3486
    char const *path_variable;
3487
    char const *start;
3488
    char const *end;
3489
    size_t length;
3490
    char *candidate;
3491
    int executable;
3492

3493
    path_variable = NULL;
×
3494
    start = NULL;
×
3495
    end = NULL;
×
3496
    length = 0;
×
3497
    candidate = NULL;
×
3498
    executable = 0;
×
3499

3500
    if (tryexec == NULL || tryexec[0] == '\0') {
×
3501
        return 1;
×
3502
    }
3503

3504
    if (strchr(tryexec, '/') != NULL) {
×
3505
        if (access(tryexec, X_OK) == 0) {
×
3506
            return 1;
×
3507
        }
3508
        return 0;
×
3509
    }
3510

3511
    path_variable = getenv("PATH");
×
3512
    if (path_variable == NULL) {
×
3513
        return 0;
×
3514
    }
3515

3516
    start = path_variable;
×
3517
    while (*start != '\0') {
×
3518
        end = strchr(start, ':');
×
3519
        if (end == NULL) {
×
3520
            end = start + strlen(start);
×
3521
        }
3522
        length = (size_t)(end - start);
×
3523
        candidate = malloc(length + strlen(tryexec) + 2);
×
3524
        if (candidate == NULL) {
×
3525
            return 0;
×
3526
        }
3527
        memcpy(candidate, start, length);
×
3528
        candidate[length] = '/';
×
3529
        strcpy(candidate + length + 1, tryexec);
×
3530
        if (access(candidate, X_OK) == 0) {
×
3531
            executable = 1;
×
3532
            free(candidate);
×
3533
            candidate = NULL;
×
3534
            break;
×
3535
        }
3536
        free(candidate);
×
3537
        candidate = NULL;
×
3538
        if (*end == '\0') {
×
3539
            break;
×
3540
        }
3541
        start = end + 1;
×
3542
    }
3543

3544
    return executable;
×
3545
}
3546

3547
/*
3548
 * thumbnailer_mime_matches
3549
 *
3550
 * Test whether a thumbnailer MIME pattern matches the probed MIME type.
3551
 *
3552
 * Arguments:
3553
 *     pattern   - literal MIME pattern or prefix ending with "slash-asterisk".
3554
 *     mime_type - MIME value obtained from file --mime-type.
3555
 * Returns:
3556
 *     1 when the pattern applies, 0 otherwise.
3557
 */
3558
static int
3559
thumbnailer_mime_matches(char const *pattern, char const *mime_type)
×
3560
{
3561
    size_t length;
3562

3563
    length = 0;
×
3564

3565
    if (pattern == NULL || mime_type == NULL) {
×
3566
        return 0;
×
3567
    }
3568

3569
    if (strcmp(pattern, mime_type) == 0) {
×
3570
        return 1;
×
3571
    }
3572

3573
    length = strlen(pattern);
×
3574
    if (length >= 2 && pattern[length - 1] == '*' &&
×
3575
            pattern[length - 2] == '/') {
×
3576
        return strncmp(pattern, mime_type, length - 1) == 0;
×
3577
    }
3578

3579
    return 0;
×
3580
}
3581

3582
/*
3583
 * thumbnailer_supports_mime
3584
 *
3585
 * Iterate over MIME patterns advertised by a thumbnailer entry.
3586
 *
3587
 * Arguments:
3588
 *     entry     - parsed thumbnailer entry with mime_types list.
3589
 *     mime_type - MIME type string to match.
3590
 * Returns:
3591
 *     1 when a match is found, 0 otherwise.
3592
 */
3593
static int
3594
thumbnailer_supports_mime(struct thumbnailer_entry *entry,
×
3595
                          char const *mime_type)
3596
{
3597
    size_t index;
3598

3599
    index = 0;
×
3600

3601
    if (entry == NULL || entry->mime_types == NULL) {
×
3602
        return 0;
×
3603
    }
3604

3605
    if (mime_type == NULL) {
×
3606
        return 0;
×
3607
    }
3608

3609
    for (index = 0; index < entry->mime_types->length; ++index) {
×
3610
        if (thumbnailer_mime_matches(entry->mime_types->items[index],
×
3611
                                     mime_type)) {
3612
            return 1;
×
3613
        }
3614
    }
3615

3616
    return 0;
×
3617
}
3618

3619
/*
3620
 * thumbnailer_shell_quote
3621
 *
3622
 * Produce a single-quoted variant of an argument for readable logging.
3623
 *
3624
 * Arguments:
3625
 *     text - unquoted argument.
3626
 * Returns:
3627
 *     Newly allocated quoted string or NULL on allocation failure.
3628
 */
3629
static char *
3630
thumbnailer_shell_quote(char const *text)
×
3631
{
3632
    size_t index;
3633
    size_t length;
3634
    size_t needed;
3635
    char *quoted;
3636
    size_t position;
3637

3638
    index = 0;
×
3639
    length = 0;
×
3640
    needed = 0;
×
3641
    quoted = NULL;
×
3642
    position = 0;
×
3643

3644
    if (text == NULL) {
×
3645
        return NULL;
×
3646
    }
3647

3648
    length = strlen(text);
×
3649
    needed = 2;
×
3650
    for (index = 0; index < length; ++index) {
×
3651
        if (text[index] == '\'') {
×
3652
            needed += 4;
×
3653
        } else {
3654
            needed += 1;
×
3655
        }
3656
    }
3657

3658
    quoted = malloc(needed + 1);
×
3659
    if (quoted == NULL) {
×
3660
        return NULL;
×
3661
    }
3662

3663
    quoted[position++] = '\'';
×
3664
    for (index = 0; index < length; ++index) {
×
3665
        if (text[index] == '\'') {
×
3666
            quoted[position++] = '\'';
×
3667
            quoted[position++] = '\\';
×
3668
            quoted[position++] = '\'';
×
3669
            quoted[position++] = '\'';
×
3670
        } else {
3671
            quoted[position++] = text[index];
×
3672
        }
3673
    }
3674
    quoted[position++] = '\'';
×
3675
    quoted[position] = '\0';
×
3676

3677
    return quoted;
×
3678
}
3679

3680
struct thumbnailer_builder {
3681
    char *buffer;
3682
    size_t length;
3683
    size_t capacity;
3684
};
3685

3686
/*
3687
 * thumbnailer_builder_reserve
3688
 *
3689
 * Grow the builder buffer so future appends fit without overflow.
3690
 *
3691
 * Arguments:
3692
 *     builder    - mutable builder instance.
3693
 *     additional - number of bytes that must fit excluding terminator.
3694
 * Returns:
3695
 *     1 on success, 0 on allocation failure.
3696
 */
3697
static int
3698
thumbnailer_builder_reserve(struct thumbnailer_builder *builder,
×
3699
                            size_t additional)
3700
{
3701
    size_t new_capacity;
3702
    char *new_buffer;
3703

3704
    new_capacity = 0;
×
3705
    new_buffer = NULL;
×
3706

3707
    if (builder->length + additional + 1 <= builder->capacity) {
×
3708
        return 1;
×
3709
    }
3710

3711
    new_capacity = (builder->capacity == 0) ? 64 : builder->capacity;
×
3712
    while (new_capacity < builder->length + additional + 1) {
×
3713
        new_capacity *= 2;
×
3714
    }
3715

3716
    new_buffer = realloc(builder->buffer, new_capacity);
×
3717
    if (new_buffer == NULL) {
×
3718
        return 0;
×
3719
    }
3720

3721
    builder->buffer = new_buffer;
×
3722
    builder->capacity = new_capacity;
×
3723

3724
    return 1;
×
3725
}
3726

3727
/*
3728
 * thumbnailer_builder_append_char
3729
 *
3730
 * Append a single character to the builder.
3731
 *
3732
 * Arguments:
3733
 *     builder - mutable builder instance.
3734
 *     ch      - character to append.
3735
 * Returns:
3736
 *     1 on success, 0 on allocation failure.
3737
 */
3738
static int
3739
thumbnailer_builder_append_char(struct thumbnailer_builder *builder,
×
3740
                                char ch)
3741
{
3742
    if (!thumbnailer_builder_reserve(builder, 1)) {
×
3743
        return 0;
×
3744
    }
3745

3746
    builder->buffer[builder->length] = ch;
×
3747
    builder->length += 1;
×
3748
    builder->buffer[builder->length] = '\0';
×
3749

3750
    return 1;
×
3751
}
3752

3753
/*
3754
 * thumbnailer_builder_append
3755
 *
3756
 * Append a string of known length to the builder buffer.
3757
 *
3758
 * Arguments:
3759
 *     builder - mutable builder instance.
3760
 *     text    - zero-terminated string to append.
3761
 * Returns:
3762
 *     1 on success, 0 on allocation failure or NULL input.
3763
 */
3764
static int
3765
thumbnailer_builder_append(struct thumbnailer_builder *builder,
×
3766
                           char const *text)
3767
{
3768
    size_t length;
3769

3770
    length = 0;
×
3771

3772
    if (text == NULL) {
×
3773
        return 1;
×
3774
    }
3775

3776
    length = strlen(text);
×
3777
    if (!thumbnailer_builder_reserve(builder, length)) {
×
3778
        return 0;
×
3779
    }
3780

3781
    memcpy(builder->buffer + builder->length, text, length);
×
3782
    builder->length += length;
×
3783
    builder->buffer[builder->length] = '\0';
×
3784

3785
    return 1;
×
3786
}
3787

3788
/*
3789
 * thumbnailer_builder_clear
3790
 *
3791
 * Reset builder length to zero while retaining allocated storage.
3792
 *
3793
 * Arguments:
3794
 *     builder - builder to reset.
3795
 */
3796
static void
3797
thumbnailer_builder_clear(struct thumbnailer_builder *builder)
×
3798
{
3799
    if (builder->buffer != NULL) {
×
3800
        builder->buffer[0] = '\0';
×
3801
    }
3802
    builder->length = 0;
×
3803
}
×
3804

3805
/*
3806
 * thumbnailer_command owns the argv array that will be passed to the
3807
 * thumbnailer helper.  The display field keeps a human readable command line
3808
 * for verbose logging without recomputing the shell quoted form.
3809
 */
3810
struct thumbnailer_command {
3811
    char **argv;
3812
    size_t argc;
3813
    char *display;
3814
};
3815

3816
/*
3817
 * thumbnailer_command_free
3818
 *
3819
 * Release argv entries, the array itself, and the formatted display copy.
3820
 *
3821
 * Arguments:
3822
 *     command - structure created by thumbnailer_build_command().
3823
 */
3824
static void
3825
thumbnailer_command_free(struct thumbnailer_command *command)
×
3826
{
3827
    size_t index;
3828

3829
    if (command == NULL) {
×
3830
        return;
×
3831
    }
3832

3833
    if (command->argv != NULL) {
×
3834
        for (index = 0; index < command->argc; ++index) {
×
3835
            free(command->argv[index]);
×
3836
            command->argv[index] = NULL;
×
3837
        }
3838
        free(command->argv);
×
3839
        command->argv = NULL;
×
3840
    }
3841

3842
    free(command->display);
×
3843
    command->display = NULL;
×
3844

3845
    free(command);
×
3846
}
3847

3848
/*
3849
 * thumbnailer_command_format
3850
 *
3851
 * Join argv entries into a human-readable command line for logging.
3852
 *
3853
 * Arguments:
3854
 *     argv - array of argument strings.
3855
 *     argc - number of entries stored in argv.
3856
 * Returns:
3857
 *     Newly allocated formatted string or NULL on allocation failure.
3858
 */
3859
static char *
3860
thumbnailer_command_format(char **argv, size_t argc)
×
3861
{
3862
    struct thumbnailer_builder builder;
3863
    char *quoted;
3864
    size_t index;
3865

3866
    builder.buffer = NULL;
×
3867
    builder.length = 0;
×
3868
    builder.capacity = 0;
×
3869
    quoted = NULL;
×
3870

3871
    for (index = 0; index < argc; ++index) {
×
3872
        if (index > 0) {
×
3873
            if (!thumbnailer_builder_append_char(&builder, ' ')) {
×
3874
                free(builder.buffer);
×
3875
                builder.buffer = NULL;
×
3876
                return NULL;
×
3877
            }
3878
        }
3879
        quoted = thumbnailer_shell_quote(argv[index]);
×
3880
        if (quoted == NULL) {
×
3881
            free(builder.buffer);
×
3882
            builder.buffer = NULL;
×
3883
            return NULL;
×
3884
        }
3885
        if (!thumbnailer_builder_append(&builder, quoted)) {
×
3886
            free(quoted);
×
3887
            quoted = NULL;
×
3888
            free(builder.buffer);
×
3889
            builder.buffer = NULL;
×
3890
            return NULL;
×
3891
        }
3892
        free(quoted);
×
3893
        quoted = NULL;
×
3894
    }
3895

3896
    return builder.buffer;
×
3897
}
3898

3899
/*
3900
 * thumbnailer_build_command
3901
 *
3902
 * Expand a .thumbnailer Exec template into an argv array that honours
3903
 * FreeDesktop substitution rules.
3904
 *
3905
 * Arguments:
3906
 *     template_command - Exec line containing % tokens.
3907
 *     input_path       - filesystem path to the source document.
3908
 *     input_uri        - URI representation for %u expansions.
3909
 *     output_path      - PNG destination path for %o expansions.
3910
 *     size             - numeric size hint passed to %s tokens.
3911
 *     mime_type        - MIME value for %m replacements.
3912
 * Returns:
3913
 *     Newly allocated command or NULL on parse/allocation failure.
3914
 */
3915
static struct thumbnailer_command *
3916
thumbnailer_build_command(char const *template_command,
×
3917
                          char const *input_path,
3918
                          char const *input_uri,
3919
                          char const *output_path,
3920
                          int size,
3921
                          char const *mime_type)
3922
{
3923
    struct thumbnailer_builder builder;
3924
    struct thumbnailer_string_list *tokens;
3925
    struct thumbnailer_command *command;
3926
    char const *ptr;
3927
    char size_text[16];
3928
    int in_single_quote;
3929
    int in_double_quote;
3930
    int escape_next;
3931
    char const *replacement;
3932
    size_t index;
3933
    int written;
3934

3935
    builder.buffer = NULL;
×
3936
    builder.length = 0;
×
3937
    builder.capacity = 0;
×
3938
    tokens = NULL;
×
3939
    command = NULL;
×
3940
    ptr = template_command;
×
3941
    size_text[0] = '\0';
×
3942
    in_single_quote = 0;
×
3943
    in_double_quote = 0;
×
3944
    escape_next = 0;
×
3945
    replacement = NULL;
×
3946
    index = 0;
×
3947

3948
    if (template_command == NULL) {
×
3949
        return NULL;
×
3950
    }
3951

3952
    tokens = thumbnailer_string_list_new();
×
3953
    if (tokens == NULL) {
×
3954
        return NULL;
×
3955
    }
3956

3957
    if (size > 0) {
×
3958
        written = sixel_compat_snprintf(size_text,
×
3959
                                        sizeof(size_text),
3960
                                        "%d",
3961
                                        size);
3962
        if (written < 0) {
×
3963
            goto error;
×
3964
        }
3965
        if ((size_t)written >= sizeof(size_text)) {
×
3966
            size_text[sizeof(size_text) - 1u] = '\0';
×
3967
        }
3968
    }
3969

3970
    while (ptr != NULL && ptr[0] != '\0') {
×
3971
        if (!in_single_quote && !in_double_quote && escape_next == 0 &&
×
3972
                (ptr[0] == ' ' || ptr[0] == '\t')) {
×
3973
            if (builder.length > 0) {
×
3974
                if (!thumbnailer_string_list_append(tokens,
×
3975
                                                    builder.buffer)) {
×
3976
                    goto error;
×
3977
                }
3978
                thumbnailer_builder_clear(&builder);
×
3979
            }
3980
            ptr += 1;
×
3981
            continue;
×
3982
        }
3983
        if (!in_single_quote && escape_next == 0 && ptr[0] == '\\') {
×
3984
            escape_next = 1;
×
3985
            ptr += 1;
×
3986
            continue;
×
3987
        }
3988
        if (!in_double_quote && escape_next == 0 && ptr[0] == '\'') {
×
3989
            in_single_quote = !in_single_quote;
×
3990
            ptr += 1;
×
3991
            continue;
×
3992
        }
3993
        if (!in_single_quote && escape_next == 0 && ptr[0] == '"') {
×
3994
            in_double_quote = !in_double_quote;
×
3995
            ptr += 1;
×
3996
            continue;
×
3997
        }
3998
        if (escape_next != 0) {
×
3999
            if (!thumbnailer_builder_append_char(&builder, ptr[0])) {
×
4000
                goto error;
×
4001
            }
4002
            escape_next = 0;
×
4003
            ptr += 1;
×
4004
            continue;
×
4005
        }
4006
        if (ptr[0] == '%' && ptr[1] != '\0') {
×
4007
            replacement = NULL;
×
4008
            ptr += 1;
×
4009
            switch (ptr[0]) {
×
4010
            case '%':
4011
                if (!thumbnailer_builder_append_char(&builder, '%')) {
×
4012
                    goto error;
×
4013
                }
4014
                break;
×
4015
            case 'i':
4016
            case 'I':
4017
                replacement = input_path;
×
4018
                break;
×
4019
            case 'u':
4020
            case 'U':
4021
                replacement = input_uri;
×
4022
                break;
×
4023
            case 'o':
4024
            case 'O':
4025
                replacement = output_path;
×
4026
                break;
×
4027
            case 's':
4028
            case 'S':
4029
                replacement = size_text;
×
4030
                break;
×
4031
            case 'm':
4032
            case 'M':
4033
                replacement = mime_type;
×
4034
                break;
×
4035
            default:
4036
                if (!thumbnailer_builder_append_char(&builder, '%') ||
×
4037
                        !thumbnailer_builder_append_char(&builder,
×
4038
                                                         ptr[0])) {
×
4039
                    goto error;
×
4040
                }
4041
                break;
×
4042
            }
4043
            if (replacement != NULL) {
×
4044
                if (!thumbnailer_builder_append(&builder, replacement)) {
×
4045
                    goto error;
×
4046
                }
4047
            }
4048
            ptr += 1;
×
4049
            continue;
×
4050
        }
4051
        if (!thumbnailer_builder_append_char(&builder, ptr[0])) {
×
4052
            goto error;
×
4053
        }
4054
        ptr += 1;
×
4055
    }
4056

4057
    if (builder.length > 0) {
×
4058
        if (!thumbnailer_string_list_append(tokens, builder.buffer)) {
×
4059
            goto error;
×
4060
        }
4061
    }
4062

4063
    command = malloc(sizeof(*command));
×
4064
    if (command == NULL) {
×
4065
        goto error;
×
4066
    }
4067

4068
    command->argc = tokens->length;
×
4069
    command->argv = NULL;
×
4070
    command->display = NULL;
×
4071

4072
    if (tokens->length == 0) {
×
4073
        goto error;
×
4074
    }
4075

4076
    command->argv = malloc(sizeof(char *) * (tokens->length + 1));
×
4077
    if (command->argv == NULL) {
×
4078
        goto error;
×
4079
    }
4080

4081
    for (index = 0; index < tokens->length; ++index) {
×
4082
        command->argv[index] = thumbnailer_strdup(tokens->items[index]);
×
4083
        if (command->argv[index] == NULL) {
×
4084
            goto error;
×
4085
        }
4086
    }
4087
    command->argv[tokens->length] = NULL;
×
4088

4089
    command->display = thumbnailer_command_format(command->argv,
×
4090
                                                  command->argc);
4091
    if (command->display == NULL) {
×
4092
        goto error;
×
4093
    }
4094

4095
    thumbnailer_string_list_free(tokens);
×
4096
    tokens = NULL;
×
4097
    if (builder.buffer != NULL) {
×
4098
        free(builder.buffer);
×
4099
        builder.buffer = NULL;
×
4100
    }
4101

4102
    return command;
×
4103

4104
error:
4105
    if (tokens != NULL) {
×
4106
        thumbnailer_string_list_free(tokens);
×
4107
        tokens = NULL;
×
4108
    }
4109
    if (builder.buffer != NULL) {
×
4110
        free(builder.buffer);
×
4111
        builder.buffer = NULL;
×
4112
    }
4113
    if (command != NULL) {
×
4114
        thumbnailer_command_free(command);
×
4115
        command = NULL;
×
4116
    }
4117

4118
    return NULL;
×
4119
}
4120

4121
/*
4122
 * thumbnailer_is_evince_thumbnailer
4123
 *
4124
 * Detect whether the selected thumbnailer maps to evince-thumbnailer so
4125
 * the stdout redirection workaround can be applied.
4126
 *
4127
 * Arguments:
4128
 *     exec_line - Exec string parsed from the .thumbnailer file.
4129
 *     tryexec   - optional TryExec value for additional matching.
4130
 * Returns:
4131
 *     1 when evince-thumbnailer is referenced, 0 otherwise.
4132
 */
4133
static int
4134
thumbnailer_is_evince_thumbnailer(char const *exec_line,
×
4135
                                  char const *tryexec)
4136
{
4137
    char const *needle;
4138
    char const *basename;
4139

4140
    needle = "evince-thumbnailer";
×
4141
    basename = NULL;
×
4142

4143
    if (exec_line != NULL && strstr(exec_line, needle) != NULL) {
×
4144
        return 1;
×
4145
    }
4146

4147
    if (tryexec != NULL) {
×
4148
        basename = strrchr(tryexec, '/');
×
4149
        if (basename != NULL) {
×
4150
            basename += 1;
×
4151
        } else {
4152
            basename = tryexec;
×
4153
        }
4154
        if (strcmp(basename, needle) == 0) {
×
4155
            return 1;
×
4156
        }
4157
        if (strstr(tryexec, needle) != NULL) {
×
4158
            return 1;
×
4159
        }
4160
    }
4161

4162
    return 0;
×
4163
}
4164

4165
/*
4166
 * thumbnailer_build_evince_command
4167
 *
4168
 * Construct an argv sequence that streams evince-thumbnailer output to
4169
 * stdout so downstream code can capture the PNG safely.
4170
 *
4171
 * Arguments:
4172
 *     input_path - source document path.
4173
 *     size       - numeric size hint forwarded to the -s option.
4174
 * Returns:
4175
 *     Newly allocated command or NULL on allocation failure.
4176
 */
4177
static struct thumbnailer_command *
4178
thumbnailer_build_evince_command(char const *input_path,
×
4179
                                 int size)
4180
{
4181
    struct thumbnailer_command *command;
4182
    char size_text[16];
4183
    size_t index;
4184
    int written;
4185

4186
    command = NULL;
×
4187
    index = 0;
×
4188

4189
    if (input_path == NULL) {
×
4190
        return NULL;
×
4191
    }
4192

4193
    command = malloc(sizeof(*command));
×
4194
    if (command == NULL) {
×
4195
        return NULL;
×
4196
    }
4197

4198
    command->argc = 5;
×
4199
    command->argv = malloc(sizeof(char *) * (command->argc + 1));
×
4200
    if (command->argv == NULL) {
×
4201
        thumbnailer_command_free(command);
×
4202
        return NULL;
×
4203
    }
4204

4205
    for (index = 0; index < command->argc + 1; ++index) {
×
4206
        command->argv[index] = NULL;
×
4207
    }
4208

4209
    written = sixel_compat_snprintf(size_text,
×
4210
                                    sizeof(size_text),
4211
                                    "%d",
4212
                                    size);
4213
    if (written < 0) {
×
4214
        size_text[0] = '\0';
×
4215
    } else if ((size_t)written >= sizeof(size_text)) {
×
4216
        size_text[sizeof(size_text) - 1u] = '\0';
×
4217
    }
4218

4219
    command->argv[0] = thumbnailer_strdup("evince-thumbnailer");
×
4220
    command->argv[1] = thumbnailer_strdup("-s");
×
4221
    command->argv[2] = thumbnailer_strdup(size_text);
×
4222
    command->argv[3] = thumbnailer_strdup(input_path);
×
4223
    command->argv[4] = thumbnailer_strdup("/dev/stdout");
×
4224
    command->argv[5] = NULL;
×
4225

4226
    for (index = 0; index < command->argc; ++index) {
×
4227
        if (command->argv[index] == NULL) {
×
4228
            thumbnailer_command_free(command);
×
4229
            return NULL;
×
4230
        }
4231
    }
4232

4233
    command->display = thumbnailer_command_format(command->argv,
×
4234
                                                  command->argc);
4235
    if (command->display == NULL) {
×
4236
        thumbnailer_command_free(command);
×
4237
        return NULL;
×
4238
    }
4239

4240
    return command;
×
4241
}
4242

4243
/*
4244
 * thumbnailer_build_file_uri
4245
 *
4246
 * Convert a filesystem path into a percent-encoded file:// URI.
4247
 *
4248
 * Arguments:
4249
 *     path - filesystem path; may be relative but will be resolved.
4250
 * Returns:
4251
 *     Newly allocated URI string or NULL on error.
4252
 */
4253
static char *
4254
thumbnailer_build_file_uri(char const *path)
×
4255
{
4256
    char *resolved;
4257
    size_t index;
4258
    size_t length;
4259
    size_t needed;
4260
    char *uri;
4261
    size_t position;
4262
    char const hex[] = "0123456789ABCDEF";
×
4263

4264
    resolved = NULL;
×
4265
    index = 0;
×
4266
    length = 0;
×
4267
    needed = 0;
×
4268
    uri = NULL;
×
4269
    position = 0;
×
4270

4271
    if (path == NULL) {
×
4272
        return NULL;
×
4273
    }
4274

4275
    resolved = thumbnailer_resolve_path(path);
×
4276
    if (resolved == NULL) {
×
4277
        return NULL;
×
4278
    }
4279

4280
    length = strlen(resolved);
×
4281
    needed = 7;
×
4282
    for (index = 0; index < length; ++index) {
×
4283
        unsigned char ch;
4284

4285
        ch = (unsigned char)resolved[index];
×
4286
        if (isalnum(ch) || ch == '-' || ch == '_' ||
×
4287
                ch == '.' || ch == '~' || ch == '/') {
×
4288
            needed += 1;
×
4289
        } else {
4290
            needed += 3;
×
4291
        }
4292
    }
4293

4294
    uri = malloc(needed + 1);
×
4295
    if (uri == NULL) {
×
4296
        free(resolved);
×
4297
        resolved = NULL;
×
4298
        return NULL;
×
4299
    }
4300

4301
    memcpy(uri, "file://", 7);
×
4302
    position = 7;
×
4303
    for (index = 0; index < length; ++index) {
×
4304
        unsigned char ch;
4305

4306
        ch = (unsigned char)resolved[index];
×
4307
        if (isalnum(ch) || ch == '-' || ch == '_' ||
×
4308
                ch == '.' || ch == '~' || ch == '/') {
×
4309
            uri[position++] = (char)ch;
×
4310
        } else {
4311
            uri[position++] = '%';
×
4312
            uri[position++] = hex[(ch >> 4) & 0xF];
×
4313
            uri[position++] = hex[ch & 0xF];
×
4314
        }
4315
    }
4316
    uri[position] = '\0';
×
4317

4318
    free(resolved);
×
4319
    resolved = NULL;
×
4320

4321
    return uri;
×
4322
}
4323

4324
/*
4325
 * thumbnailer_run_file
4326
 *
4327
 * Invoke the file(1) utility to collect metadata about the input path.
4328
 *
4329
 * Arguments:
4330
 *     path   - filesystem path forwarded to file(1).
4331
 *     option - optional argument appended after "-b".  Pass NULL to obtain
4332
 *              the human readable description and "--mime-type" for the
4333
 *              MIME identifier.
4334
 * Returns:
4335
 *     Newly allocated string trimmed of trailing whitespace or NULL on
4336
 *     failure.
4337
 */
4338
static char *
4339
thumbnailer_run_file(char const *path, char const *option)
×
4340
{
4341
    int pipefd[2];
4342
    pid_t pid;
4343
    ssize_t bytes_read;
4344
    char buffer[256];
4345
    size_t total;
4346
    int status;
4347
    char *result;
4348
    char *trimmed;
4349

4350
    pipefd[0] = -1;
×
4351
    pipefd[1] = -1;
×
4352
    pid = (-1);
×
4353
    bytes_read = 0;
×
4354
    total = 0;
×
4355
    status = 0;
×
4356
    result = NULL;
×
4357
    trimmed = NULL;
×
4358

4359
    if (path == NULL) {
×
4360
        return NULL;
×
4361
    }
4362

4363
    if (pipe(pipefd) < 0) {
×
4364
        return NULL;
×
4365
    }
4366

4367
    pid = fork();
×
4368
    if (pid < 0) {
×
4369
        close(pipefd[0]);
×
4370
        close(pipefd[1]);
×
4371
        return NULL;
×
4372
    }
4373

4374
    if (pid == 0) {
×
4375
        char const *argv[6];
4376
        size_t arg_index;
4377

4378
        close(pipefd[0]);
×
4379
        if (dup2(pipefd[1], STDOUT_FILENO) < 0) {
×
4380
            _exit(127);
×
4381
        }
4382
        close(pipefd[1]);
×
4383
        arg_index = 0u;
×
4384
        argv[arg_index++] = "file";
×
4385
        argv[arg_index++] = "-b";
×
4386
        if (option != NULL) {
×
4387
            argv[arg_index++] = option;
×
4388
        }
4389
        argv[arg_index++] = path;
×
4390
        argv[arg_index] = NULL;
×
4391
        execvp("file", (char * const *)argv);
×
4392
        _exit(127);
×
4393
    }
4394

4395
    close(pipefd[1]);
×
4396
    pipefd[1] = -1;
×
4397
    total = 0;
×
4398
    while ((bytes_read = read(pipefd[0], buffer + total,
×
4399
                              sizeof(buffer) - total - 1)) > 0) {
×
4400
        total += (size_t)bytes_read;
×
4401
        if (total >= sizeof(buffer) - 1) {
×
4402
            break;
×
4403
        }
4404
    }
4405
    buffer[total] = '\0';
×
4406
    close(pipefd[0]);
×
4407
    pipefd[0] = -1;
×
4408

4409
    while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {
×
4410
        continue;
×
4411
    }
4412

4413
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
×
4414
        return NULL;
×
4415
    }
4416

4417
    trimmed = thumbnailer_trim_left(buffer);
×
4418
    thumbnailer_trim_right(trimmed);
×
4419
    if (trimmed[0] == '\0') {
×
4420
        return NULL;
×
4421
    }
4422

4423
    result = thumbnailer_strdup(trimmed);
×
4424

4425
    return result;
×
4426
}
4427

4428
/*
4429
 * thumbnailer_guess_content_type
4430
 *
4431
 * Obtain the MIME identifier for the supplied path using file(1).
4432
 */
4433
static char *
4434
thumbnailer_guess_content_type(char const *path)
×
4435
{
4436
    return thumbnailer_run_file(path, "--mime-type");
×
4437
}
4438

4439
/*
4440
 * Thumbnailer supervision overview:
4441
 *
4442
 *     +-------------------+    pipe(stderr)    +-------------------+
4443
 *     | libsixel parent   | <----------------- | thumbnailer argv  |
4444
 *     |  - polls stdout   |                   |  - renders PNG     |
4445
 *     |  - polls stderr   | -----------------> |  - returns code   |
4446
 *     |  - waits pid      |    pipe(stdout)    |                   |
4447
 *     +-------------------+  posix_spawn/fork  +-------------------+
4448
 *
4449
 * Non-blocking pipes keep verbose thumbnailers from stalling the loop,
4450
 * and argv arrays mean Exec templates never pass through /bin/sh.
4451
 *
4452
 * thumbnailer_spawn is responsible for preparing pipes, launching the
4453
 * thumbnail helper, and streaming any emitted data back into libsixel.
4454
 *
4455
 *  - stderr is captured into stderr_output so verbose mode can replay the
4456
 *    diagnostics without leaking them to non-verbose invocations.
4457
 *  - stdout can optionally be redirected into a temporary file when
4458
 *    thumbnailers insist on writing image data to the standard output stream.
4459
 *  - All file descriptors are placed into non-blocking mode to avoid stalls
4460
 *    while the parent waits for the child process to complete.
4461
 * Arguments:
4462
 *     command          - prepared argv array to execute.
4463
 *     thumbnailer_name - friendly name used in log messages.
4464
 *     log_prefix       - identifier describing the current pipeline step.
4465
 *     capture_stdout   - non-zero to capture stdout into stdout_path.
4466
 *     stdout_path      - file receiving stdout when capture_stdout != 0.
4467
 * Returns:
4468
 *     SIXEL_OK on success or a libsixel error code on failure.
4469
 */
4470
static SIXELSTATUS
4471
thumbnailer_spawn(struct thumbnailer_command const *command,
×
4472
                  char const *thumbnailer_name,
4473
                  char const *log_prefix,
4474
                  int capture_stdout,
4475
                  char const *stdout_path)
4476
{
4477
    pid_t pid;
4478
    int status_code;
4479
    int wait_result;
4480
    SIXELSTATUS status;
4481
    char message[256];
4482
    int stderr_pipe[2];
4483
    int stdout_pipe[2];
4484
    int stderr_pipe_created;
4485
    int stdout_pipe_created;
4486
    int flags;
4487
    ssize_t read_result;
4488
    ssize_t stdout_read_result;
4489
    char stderr_buffer[256];
4490
    char stdout_buffer[4096];
4491
    char *stderr_output;
4492
    size_t stderr_length;
4493
    size_t stderr_capacity;
4494
    int child_exited;
4495
    int stderr_open;
4496
    int stdout_open;
4497
    int have_status;
4498
    int fatal_error;
4499
    int output_fd;
4500
    size_t write_offset;
4501
    ssize_t write_result;
4502
    size_t to_write;
4503
    char const *display_command;
4504
    int written;
4505
# if HAVE_POSIX_SPAWNP
4506
    posix_spawn_file_actions_t actions;
4507
    int spawn_result;
4508
# endif
4509

4510
    pid = (-1);
×
4511
    status_code = 0;
×
4512
    wait_result = 0;
×
4513
    status = SIXEL_RUNTIME_ERROR;
×
4514
    memset(message, 0, sizeof(message));
×
4515
    stderr_pipe[0] = -1;
×
4516
    stderr_pipe[1] = -1;
×
4517
    stdout_pipe[0] = -1;
×
4518
    stdout_pipe[1] = -1;
×
4519
    stderr_pipe_created = 0;
×
4520
    stdout_pipe_created = 0;
×
4521
    flags = 0;
×
4522
    read_result = 0;
×
4523
    stdout_read_result = 0;
×
4524
    stderr_output = NULL;
×
4525
    stderr_length = 0;
×
4526
    stderr_capacity = 0;
×
4527
    child_exited = 0;
×
4528
    stderr_open = 0;
×
4529
    stdout_open = 0;
×
4530
    have_status = 0;
×
4531
    fatal_error = 0;
×
4532
    output_fd = -1;
×
4533
    write_offset = 0;
×
4534
    write_result = 0;
×
4535
    to_write = 0;
×
4536
    display_command = NULL;
×
4537
# if HAVE_POSIX_SPAWNP
4538
    spawn_result = 0;
×
4539
# endif
4540

4541
    if (command == NULL || command->argv == NULL ||
×
4542
            command->argv[0] == NULL) {
×
4543
        return SIXEL_BAD_ARGUMENT;
×
4544
    }
4545

4546
    if (capture_stdout && stdout_path == NULL) {
×
4547
        return SIXEL_BAD_ARGUMENT;
×
4548
    }
4549

4550
    if (capture_stdout) {
×
4551
        output_fd = open(stdout_path,
×
4552
                         O_WRONLY | O_CREAT | O_TRUNC,
4553
                         0600);
4554
        if (output_fd < 0) {
×
4555
            written = sixel_compat_snprintf(message,
×
4556
                                            sizeof(message),
4557
                                            "%s: open(%s) failed (%s).",
4558
                                            log_prefix,
4559
                                            stdout_path,
4560
                                            strerror(errno));
×
4561
            thumbnailer_message_finalize(message,
×
4562
                                         sizeof(message),
4563
                                         written);
4564
            sixel_helper_set_additional_message(message);
×
4565
            goto cleanup;
×
4566
        }
4567
    }
4568

4569
    /* stderr is collected even for successful runs so verbose users can see
4570
     * the thumbnailer's own commentary.  Failing to set the pipe is not
4571
     * fatal; we continue without stderr capture in that case.
4572
     */
4573
    if (pipe(stderr_pipe) == 0) {
×
4574
        stderr_pipe_created = 1;
×
4575
        stderr_open = 1;
×
4576
    }
4577

4578
    if (capture_stdout) {
×
4579
        if (pipe(stdout_pipe) != 0) {
×
4580
            written = sixel_compat_snprintf(
×
4581
                message,
4582
                sizeof(message),
4583
                "%s: pipe() for stdout failed (%s).",
4584
                log_prefix,
4585
                strerror(errno));
×
4586
            thumbnailer_message_finalize(message,
×
4587
                                         sizeof(message),
4588
                                         written);
4589
            sixel_helper_set_additional_message(message);
×
4590
            goto cleanup;
×
4591
        }
4592
        stdout_pipe_created = 1;
×
4593
        stdout_open = 1;
×
4594
    }
4595

4596
    display_command = (command->display != NULL) ?
×
4597
            command->display : command->argv[0];
×
4598
    loader_trace_message("%s: executing %s",
×
4599
                         log_prefix,
4600
                         display_command);
4601

4602
# if HAVE_POSIX_SPAWNP
4603
    if (posix_spawn_file_actions_init(&actions) != 0) {
×
4604
        written = sixel_compat_snprintf(
×
4605
            message,
4606
            sizeof(message),
4607
            "%s: posix_spawn_file_actions_init() failed.",
4608
            log_prefix);
4609
        thumbnailer_message_finalize(message,
×
4610
                                     sizeof(message),
4611
                                     written);
4612
        sixel_helper_set_additional_message(message);
×
4613
        goto cleanup;
×
4614
    }
4615
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
×
4616
        (void)posix_spawn_file_actions_adddup2(&actions,
×
4617
                                               stderr_pipe[1],
4618
                                               STDERR_FILENO);
4619
        (void)posix_spawn_file_actions_addclose(&actions,
×
4620
                                                stderr_pipe[0]);
4621
        (void)posix_spawn_file_actions_addclose(&actions,
×
4622
                                                stderr_pipe[1]);
4623
    }
4624
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
×
4625
        (void)posix_spawn_file_actions_adddup2(&actions,
×
4626
                                               stdout_pipe[1],
4627
                                               STDOUT_FILENO);
4628
        (void)posix_spawn_file_actions_addclose(&actions,
×
4629
                                                stdout_pipe[0]);
4630
        (void)posix_spawn_file_actions_addclose(&actions,
×
4631
                                                stdout_pipe[1]);
4632
    }
4633
    if (output_fd >= 0) {
×
4634
        (void)posix_spawn_file_actions_addclose(&actions,
×
4635
                                                output_fd);
4636
    }
4637
    spawn_result = posix_spawnp(&pid,
×
4638
                                command->argv[0],
×
4639
                                &actions,
4640
                                NULL,
4641
                                (char * const *)command->argv,
×
4642
                                environ);
4643
    posix_spawn_file_actions_destroy(&actions);
×
4644
    if (spawn_result != 0) {
×
4645
        written = sixel_compat_snprintf(message,
×
4646
                                        sizeof(message),
4647
                                        "%s: posix_spawnp() failed (%s).",
4648
                                        log_prefix,
4649
                                        strerror(spawn_result));
4650
        thumbnailer_message_finalize(message,
×
4651
                                     sizeof(message),
4652
                                     written);
4653
        sixel_helper_set_additional_message(message);
×
4654
        goto cleanup;
×
4655
    }
4656
# else
4657
    pid = fork();
4658
    if (pid < 0) {
4659
        written = sixel_compat_snprintf(message,
4660
                                        sizeof(message),
4661
                                        "%s: fork() failed (%s).",
4662
                                        log_prefix,
4663
                                        strerror(errno));
4664
        thumbnailer_message_finalize(message,
4665
                                     sizeof(message),
4666
                                     written);
4667
        sixel_helper_set_additional_message(message);
4668
        goto cleanup;
4669
    }
4670
    if (pid == 0) {
4671
        if (stderr_pipe_created && stderr_pipe[1] >= 0) {
4672
            if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) {
4673
                _exit(127);
4674
            }
4675
        }
4676
        if (stdout_pipe_created && stdout_pipe[1] >= 0) {
4677
            if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) {
4678
                _exit(127);
4679
            }
4680
        }
4681
        if (stderr_pipe[0] >= 0) {
4682
            close(stderr_pipe[0]);
4683
        }
4684
        if (stderr_pipe[1] >= 0) {
4685
            close(stderr_pipe[1]);
4686
        }
4687
        if (stdout_pipe[0] >= 0) {
4688
            close(stdout_pipe[0]);
4689
        }
4690
        if (stdout_pipe[1] >= 0) {
4691
            close(stdout_pipe[1]);
4692
        }
4693
        if (output_fd >= 0) {
4694
            close(output_fd);
4695
        }
4696
        execvp(command->argv[0], (char * const *)command->argv);
4697
        _exit(127);
4698
    }
4699
# endif
4700

4701
    loader_trace_message("%s: forked child pid=%ld",
×
4702
                         log_prefix,
4703
                         (long)pid);
4704

4705
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
×
4706
        close(stderr_pipe[1]);
×
4707
        stderr_pipe[1] = -1;
×
4708
    }
4709
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
×
4710
        close(stdout_pipe[1]);
×
4711
        stdout_pipe[1] = -1;
×
4712
    }
4713

4714
    if (stderr_pipe_created && stderr_pipe[0] >= 0) {
×
4715
        flags = fcntl(stderr_pipe[0], F_GETFL, 0);
×
4716
        if (flags >= 0) {
×
4717
            (void)fcntl(stderr_pipe[0],
×
4718
                        F_SETFL,
4719
                        flags | O_NONBLOCK);
4720
        }
4721
    }
4722
    if (stdout_pipe_created && stdout_pipe[0] >= 0) {
×
4723
        flags = fcntl(stdout_pipe[0], F_GETFL, 0);
×
4724
        if (flags >= 0) {
×
4725
            (void)fcntl(stdout_pipe[0],
×
4726
                        F_SETFL,
4727
                        flags | O_NONBLOCK);
4728
        }
4729
    }
4730

4731
    /* The monitoring loop drains stderr/stdout as long as any descriptor is
4732
     * open.  Non-blocking reads ensure the parent does not deadlock if the
4733
     * child process stalls or writes data in bursts.
4734
     */
4735
    while (!child_exited || stderr_open || stdout_open) {
×
4736
        if (stderr_pipe_created && stderr_open) {
×
4737
            read_result = read(stderr_pipe[0],
×
4738
                               stderr_buffer,
4739
                               (ssize_t)sizeof(stderr_buffer));
4740
            if (read_result > 0) {
×
4741
                if (stderr_length + (size_t)read_result + 1 >
×
4742
                        stderr_capacity) {
4743
                    size_t new_capacity;
4744
                    char *new_output;
4745

4746
                    new_capacity = stderr_capacity;
×
4747
                    if (new_capacity == 0) {
×
4748
                        new_capacity = 256;
×
4749
                    }
4750
                    while (stderr_length + (size_t)read_result + 1 >
×
4751
                            new_capacity) {
4752
                        new_capacity *= 2U;
×
4753
                    }
4754
                    new_output = realloc(stderr_output, new_capacity);
×
4755
                    if (new_output == NULL) {
×
4756
                        free(stderr_output);
×
4757
                        stderr_output = NULL;
×
4758
                        stderr_capacity = 0;
×
4759
                        stderr_length = 0;
×
4760
                        stderr_open = 0;
×
4761
                        if (stderr_pipe[0] >= 0) {
×
4762
                            close(stderr_pipe[0]);
×
4763
                            stderr_pipe[0] = -1;
×
4764
                        }
4765
                        stderr_pipe_created = 0;
×
4766
                        written = sixel_compat_snprintf(message,
×
4767
                                                        sizeof(message),
4768
                                                        "%s: realloc() failed.",
4769
                                                        log_prefix);
4770
                        thumbnailer_message_finalize(message,
×
4771
                                                     sizeof(message),
4772
                                                     written);
4773
                        sixel_helper_set_additional_message(message);
×
4774
                        status = SIXEL_BAD_ALLOCATION;
×
4775
                        fatal_error = 1;
×
4776
                        break;
×
4777
                    }
4778
                    stderr_output = new_output;
×
4779
                    stderr_capacity = new_capacity;
×
4780
                }
4781
                memcpy(stderr_output + stderr_length,
×
4782
                       stderr_buffer,
4783
                       (size_t)read_result);
4784
                stderr_length += (size_t)read_result;
×
4785
                stderr_output[stderr_length] = '\0';
×
4786
            } else if (read_result == 0) {
×
4787
                stderr_open = 0;
×
4788
                if (stderr_pipe[0] >= 0) {
×
4789
                    close(stderr_pipe[0]);
×
4790
                    stderr_pipe[0] = -1;
×
4791
                }
4792
                stderr_pipe_created = 0;
×
4793
            } else if (errno == EINTR) {
×
4794
                /* retry */
4795
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4796
                /* no data */
4797
            } else {
4798
                written = sixel_compat_snprintf(message,
×
4799
                                                sizeof(message),
4800
                                                "%s: read() failed (%s).",
4801
                                                log_prefix,
4802
                                                strerror(errno));
×
4803
                thumbnailer_message_finalize(message,
×
4804
                                             sizeof(message),
4805
                                             written);
4806
                sixel_helper_set_additional_message(message);
×
4807
                stderr_open = 0;
×
4808
                if (stderr_pipe[0] >= 0) {
×
4809
                    close(stderr_pipe[0]);
×
4810
                    stderr_pipe[0] = -1;
×
4811
                }
4812
                stderr_pipe_created = 0;
×
4813
            }
4814
        }
4815

4816
        if (stdout_pipe_created && stdout_open) {
×
4817
            stdout_read_result = read(stdout_pipe[0],
×
4818
                                      stdout_buffer,
4819
                                      (ssize_t)sizeof(stdout_buffer));
4820
            if (stdout_read_result > 0) {
×
4821
                write_offset = 0;
×
4822
                while (write_offset < (size_t)stdout_read_result) {
×
4823
                    to_write = (size_t)stdout_read_result - write_offset;
×
4824
                    write_result = write(output_fd,
×
4825
                                          stdout_buffer + write_offset,
4826
                                          to_write);
4827
                    if (write_result < 0) {
×
4828
                        if (errno == EINTR) {
×
4829
                            continue;
×
4830
                        }
4831
                    written = sixel_compat_snprintf(message,
×
4832
                                                    sizeof(message),
4833
                                                    "%s: write() failed (%s).",
4834
                                                    log_prefix,
4835
                                                    strerror(errno));
×
4836
                    thumbnailer_message_finalize(message,
×
4837
                                                 sizeof(message),
4838
                                                 written);
4839
                    sixel_helper_set_additional_message(message);
×
4840
                    stdout_open = 0;
×
4841
                    fatal_error = 1;
×
4842
                    break;
×
4843
                }
4844
                    write_offset += (size_t)write_result;
×
4845
                }
4846
            } else if (stdout_read_result == 0) {
×
4847
                stdout_open = 0;
×
4848
                if (stdout_pipe[0] >= 0) {
×
4849
                    close(stdout_pipe[0]);
×
4850
                    stdout_pipe[0] = -1;
×
4851
                }
4852
                stdout_pipe_created = 0;
×
4853
            } else if (errno == EINTR) {
×
4854
                /* retry */
4855
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4856
                /* no data */
4857
            } else {
4858
                written = sixel_compat_snprintf(message,
×
4859
                                                sizeof(message),
4860
                                                "%s: read() failed (%s).",
4861
                                                log_prefix,
4862
                                                strerror(errno));
×
4863
                thumbnailer_message_finalize(message,
×
4864
                                             sizeof(message),
4865
                                             written);
4866
                sixel_helper_set_additional_message(message);
×
4867
                stdout_open = 0;
×
4868
                if (stdout_pipe[0] >= 0) {
×
4869
                    close(stdout_pipe[0]);
×
4870
                    stdout_pipe[0] = -1;
×
4871
                }
4872
                stdout_pipe_created = 0;
×
4873
            }
4874
        }
4875

4876
        if (!child_exited) {
×
4877
            wait_result = waitpid(pid, &status_code, WNOHANG);
×
4878
            if (wait_result > 0) {
×
4879
                child_exited = 1;
×
4880
                have_status = 1;
×
4881
            } else if (wait_result == 0) {
×
4882
                /* child running */
4883
            } else if (errno != EINTR) {
×
4884
            written = sixel_compat_snprintf(message,
×
4885
                                            sizeof(message),
4886
                                            "%s: waitpid() failed (%s).",
4887
                                            log_prefix,
4888
                                            strerror(errno));
×
4889
            thumbnailer_message_finalize(message,
×
4890
                                         sizeof(message),
4891
                                         written);
4892
            sixel_helper_set_additional_message(message);
×
4893
            status = SIXEL_RUNTIME_ERROR;
×
4894
            fatal_error = 1;
×
4895
            break;
×
4896
        }
4897
        }
4898

4899
        if (!child_exited || stderr_open || stdout_open) {
×
4900
            thumbnailer_sleep_briefly();
×
4901
        }
4902
    }
4903

4904
    if (!child_exited) {
×
4905
        do {
4906
            wait_result = waitpid(pid, &status_code, 0);
×
4907
        } while (wait_result < 0 && errno == EINTR);
×
4908
        if (wait_result < 0) {
×
4909
        written = sixel_compat_snprintf(message,
×
4910
                                        sizeof(message),
4911
                                        "%s: waitpid() failed (%s).",
4912
                                        log_prefix,
4913
                                        strerror(errno));
×
4914
        thumbnailer_message_finalize(message,
×
4915
                                     sizeof(message),
4916
                                     written);
4917
        sixel_helper_set_additional_message(message);
×
4918
        status = SIXEL_RUNTIME_ERROR;
×
4919
        goto cleanup;
×
4920
    }
4921
        have_status = 1;
×
4922
    }
4923

4924
    if (!have_status) {
×
4925
        written = sixel_compat_snprintf(message,
×
4926
                                        sizeof(message),
4927
                                        "%s: waitpid() failed (no status).",
4928
                                        log_prefix);
4929
        thumbnailer_message_finalize(message,
×
4930
                                     sizeof(message),
4931
                                     written);
4932
        sixel_helper_set_additional_message(message);
×
4933
        status = SIXEL_RUNTIME_ERROR;
×
4934
        goto cleanup;
×
4935
    }
4936

4937
    if (!fatal_error) {
×
4938
        if (WIFEXITED(status_code) && WEXITSTATUS(status_code) == 0) {
×
4939
            status = SIXEL_OK;
×
4940
            loader_trace_message("%s: child pid=%ld exited successfully",
×
4941
                                 log_prefix,
4942
                                 (long)pid);
4943
        } else if (WIFEXITED(status_code)) {
×
4944
            written = sixel_compat_snprintf(message,
×
4945
                                            sizeof(message),
4946
                                            "%s: %s exited with status %d.",
4947
                                            log_prefix,
4948
                                            (thumbnailer_name != NULL) ?
×
4949
                                            thumbnailer_name :
4950
                                            "thumbnailer",
4951
                                            WEXITSTATUS(status_code));
×
4952
            thumbnailer_message_finalize(message,
×
4953
                                         sizeof(message),
4954
                                         written);
4955
            sixel_helper_set_additional_message(message);
×
4956
            status = SIXEL_RUNTIME_ERROR;
×
4957
        } else if (WIFSIGNALED(status_code)) {
×
4958
            written = sixel_compat_snprintf(message,
×
4959
                                            sizeof(message),
4960
                                            "%s: %s terminated by signal %d.",
4961
                                            log_prefix,
4962
                                            (thumbnailer_name != NULL) ?
×
4963
                                            thumbnailer_name :
4964
                                            "thumbnailer",
4965
                                            WTERMSIG(status_code));
4966
            thumbnailer_message_finalize(message,
×
4967
                                         sizeof(message),
4968
                                         written);
4969
            sixel_helper_set_additional_message(message);
×
4970
            status = SIXEL_RUNTIME_ERROR;
×
4971
        } else {
4972
            written = sixel_compat_snprintf(message,
×
4973
                                            sizeof(message),
4974
                                            "%s: %s exited abnormally.",
4975
                                            log_prefix,
4976
                                            (thumbnailer_name != NULL) ?
×
4977
                                            thumbnailer_name :
4978
                                            "thumbnailer");
4979
            thumbnailer_message_finalize(message,
×
4980
                                         sizeof(message),
4981
                                         written);
4982
            sixel_helper_set_additional_message(message);
×
4983
            status = SIXEL_RUNTIME_ERROR;
×
4984
        }
4985
    }
4986

4987
cleanup:
4988
    if (stderr_output != NULL && loader_trace_enabled &&
×
4989
            stderr_length > 0) {
4990
        loader_trace_message("%s: stderr:\n%s",
×
4991
                             log_prefix,
4992
                             stderr_output);
4993
    }
4994

4995
    if (stderr_pipe[0] >= 0) {
×
4996
        close(stderr_pipe[0]);
×
4997
        stderr_pipe[0] = -1;
×
4998
    }
4999
    if (stderr_pipe[1] >= 0) {
×
5000
        close(stderr_pipe[1]);
×
5001
        stderr_pipe[1] = -1;
×
5002
    }
5003
    if (stdout_pipe[0] >= 0) {
×
5004
        close(stdout_pipe[0]);
×
5005
        stdout_pipe[0] = -1;
×
5006
    }
5007
    if (stdout_pipe[1] >= 0) {
×
5008
        close(stdout_pipe[1]);
×
5009
        stdout_pipe[1] = -1;
×
5010
    }
5011
    if (output_fd >= 0) {
×
5012
        close(output_fd);
×
5013
        output_fd = -1;
×
5014
    }
5015
    /* stderr_output accumulates all diagnostic text, so release it even when
5016
     * verbose tracing is disabled.
5017
     */
5018
    free(stderr_output);
×
5019

5020
    return status;
×
5021
}
5022

5023

5024

5025
/*
5026
 * load_with_gnome_thumbnailer
5027
 *
5028
 * Drive the FreeDesktop thumbnailer pipeline and then decode the PNG
5029
 * result using the built-in loader.
5030
 *
5031
 * GNOME thumbnail workflow overview:
5032
 *
5033
 *     +------------+    +-------------------+    +----------------+
5034
 *     | source URI | -> | .thumbnailer Exec | -> | PNG thumbnail  |
5035
 *     +------------+    +-------------------+    +----------------+
5036
 *             |                    |                        |
5037
 *             |                    v                        v
5038
 *             |           spawn via /bin/sh         load_with_builtin()
5039
 *             v
5040
 *     file --mime-type
5041
 *
5042
 * Each step logs verbose breadcrumbs so integrators can diagnose which
5043
 * thumbnailer matched, how the command was prepared, and why fallbacks
5044
 * were selected.
5045
 *
5046
 * Arguments:
5047
 *     pchunk        - source chunk representing the original document.
5048
 *     fstatic       - image static-ness flag.
5049
 *     fuse_palette  - palette usage flag.
5050
 *     reqcolors     - requested colour count.
5051
 *     bgcolor       - background colour override.
5052
 *     loop_control  - animation loop control flag.
5053
 *     fn_load       - downstream decoder callback.
5054
 *     context       - user context forwarded to fn_load.
5055
 * Returns:
5056
 *     SIXEL_OK on success or libsixel error code on failure.
5057
 */
5058
static SIXELSTATUS
5059
load_with_gnome_thumbnailer(
×
5060
    sixel_chunk_t const       /* in */     *pchunk,
5061
    int                       /* in */     fstatic,
5062
    int                       /* in */     fuse_palette,
5063
    int                       /* in */     reqcolors,
5064
    unsigned char             /* in */     *bgcolor,
5065
    int                       /* in */     loop_control,
5066
    sixel_load_image_function /* in */     fn_load,
5067
    void                      /* in/out */ *context)
5068
{
5069
    SIXELSTATUS status;
5070
    sixel_chunk_t *thumb_chunk;
5071
    char template_path[] = "/tmp/libsixel-thumb-XXXXXX";
×
5072
    char *png_path;
5073
    size_t path_length;
5074
    struct thumbnailer_string_list *directories;
5075
    size_t dir_index;
5076
    DIR *dir;
5077
    struct dirent *entry;
5078
    char *thumbnailer_path;
5079
    struct thumbnailer_entry info;
5080
    char *content_type;
5081
    char *input_uri;
5082
    struct thumbnailer_command *command;
5083
    struct thumbnailer_command *evince_command;
5084
    int executed;
5085
    int command_success;
5086
    int requested_size;
5087
    char const *log_prefix;
5088
    int fd;
5089
    int written;
5090

5091
    loader_thumbnailer_initialize_size_hint();
×
5092

5093
    status = SIXEL_FALSE;
×
5094
    thumb_chunk = NULL;
×
5095
    png_path = NULL;
×
5096
    path_length = 0;
×
5097
    fd = -1;
×
5098
    directories = NULL;
×
5099
    dir_index = 0;
×
5100
    dir = NULL;
×
5101
    entry = NULL;
×
5102
    thumbnailer_path = NULL;
×
5103
    content_type = NULL;
×
5104
    input_uri = NULL;
×
5105
    command = NULL;
×
5106
    evince_command = NULL;
×
5107
    executed = 0;
×
5108
    command_success = 0;
×
5109
    log_prefix = "load_with_gnome_thumbnailer";
×
5110
    requested_size = thumbnailer_size_hint;
×
5111
    if (requested_size <= 0) {
×
5112
        requested_size = SIXEL_THUMBNAILER_DEFAULT_SIZE;
×
5113
    }
5114

5115
    loader_trace_message("%s: thumbnail size hint=%d",
×
5116
                         log_prefix,
5117
                         requested_size);
5118

5119
    thumbnailer_entry_init(&info);
×
5120

5121
    if (pchunk->source_path == NULL) {
×
5122
        sixel_helper_set_additional_message(
×
5123
            "load_with_gnome_thumbnailer: source path is unavailable.");
5124
        status = SIXEL_BAD_ARGUMENT;
×
5125
        goto end;
×
5126
    }
5127

5128
#if defined(HAVE_MKSTEMP)
5129
    fd = mkstemp(template_path);
×
5130
#elif defined(HAVE__MKTEMP)
5131
    fd = _mktemp(template_path);
5132
#elif defined(HAVE_MKTEMP)
5133
    fd = mktemp(template_path);
5134
#endif
5135
    if (fd < 0) {
×
5136
        sixel_helper_set_additional_message(
×
5137
            "load_with_gnome_thumbnailer: mkstemp() failed.");
5138
        status = SIXEL_RUNTIME_ERROR;
×
5139
        goto end;
×
5140
    }
5141
    close(fd);
×
5142
    fd = -1;
×
5143

5144
    path_length = strlen(template_path) + 5;
×
5145
    png_path = malloc(path_length);
×
5146
    if (png_path == NULL) {
×
5147
        sixel_helper_set_additional_message(
×
5148
            "load_with_gnome_thumbnailer: malloc() failed.");
5149
        status = SIXEL_BAD_ALLOCATION;
×
5150
        unlink(template_path);
×
5151
        goto end;
×
5152
    }
5153
    written = sixel_compat_snprintf(png_path,
×
5154
                                    path_length,
5155
                                    "%s.png",
5156
                                    template_path);
5157
    thumbnailer_message_finalize(png_path,
×
5158
                                 path_length,
5159
                                 written);
5160
    if (rename(template_path, png_path) != 0) {
×
5161
        sixel_helper_set_additional_message(
×
5162
            "load_with_gnome_thumbnailer: rename() failed.");
5163
        status = SIXEL_RUNTIME_ERROR;
×
5164
        unlink(template_path);
×
5165
        goto end;
×
5166
    }
5167

5168
    content_type = thumbnailer_guess_content_type(pchunk->source_path);
×
5169
    input_uri = thumbnailer_build_file_uri(pchunk->source_path);
×
5170

5171
    loader_trace_message("%s: detected MIME type %s for %s",
×
5172
                         log_prefix,
5173
                         (content_type != NULL) ? content_type :
×
5174
                         "(unknown)",
5175
                         pchunk->source_path);
×
5176

5177
    directories = thumbnailer_collect_directories();
×
5178
    if (directories == NULL) {
×
5179
        status = SIXEL_RUNTIME_ERROR;
×
5180
        goto end;
×
5181
    }
5182

5183
    /* Iterate through every configured thumbnailer directory so we honour
5184
     * overrides in $HOME as well as desktop environment defaults discovered
5185
     * through XDG_DATA_DIRS.
5186
     */
5187
    for (dir_index = 0; dir_index < directories->length; ++dir_index) {
×
5188
        loader_trace_message("%s: checking thumbnailers in %s",
×
5189
                             log_prefix,
5190
                             directories->items[dir_index]);
×
5191

5192
        dir = opendir(directories->items[dir_index]);
×
5193
        if (dir == NULL) {
×
5194
            continue;
×
5195
        }
5196
        while ((entry = readdir(dir)) != NULL) {
×
5197
            thumbnailer_entry_clear(&info);
×
5198
            thumbnailer_entry_init(&info);
×
5199
            size_t name_length;
5200

5201
            name_length = strlen(entry->d_name);
×
5202
            if (name_length < 12 ||
×
5203
                    strcmp(entry->d_name + name_length - 12,
×
5204
                           ".thumbnailer") != 0) {
5205
                continue;
×
5206
            }
5207
            thumbnailer_path = thumbnailer_join_paths(
×
5208
                directories->items[dir_index],
×
5209
                entry->d_name);
×
5210
            if (thumbnailer_path == NULL) {
×
5211
                continue;
×
5212
            }
5213
            if (!thumbnailer_parse_file(thumbnailer_path, &info)) {
×
5214
                free(thumbnailer_path);
×
5215
                thumbnailer_path = NULL;
×
5216
                continue;
×
5217
            }
5218
            free(thumbnailer_path);
×
5219
            thumbnailer_path = NULL;
×
5220
            loader_trace_message(
×
5221
                "%s: parsed %s (TryExec=%s)",
5222
                log_prefix,
5223
                entry->d_name,
×
5224
                (info.tryexec != NULL) ? info.tryexec : "(none)");
×
5225
            if (content_type == NULL) {
×
5226
                continue;
×
5227
            }
5228
            if (!thumbnailer_has_tryexec(info.tryexec)) {
×
5229
                loader_trace_message("%s: skipping %s (TryExec missing)",
×
5230
                                     log_prefix,
5231
                                     entry->d_name);
×
5232
                continue;
×
5233
            }
5234
            if (!thumbnailer_supports_mime(&info, content_type)) {
×
5235
                loader_trace_message("%s: %s does not support %s",
×
5236
                                     log_prefix,
5237
                                     entry->d_name,
×
5238
                                     content_type);
5239
                continue;
×
5240
            }
5241
            if (info.exec_line == NULL) {
×
5242
                continue;
×
5243
            }
5244
            loader_trace_message("%s: %s supports %s with Exec=\"%s\"",
×
5245
                                 log_prefix,
5246
                                 entry->d_name,
×
5247
                                 content_type,
5248
                                 info.exec_line);
5249
            loader_trace_message("%s: preparing %s for %s",
×
5250
                                 log_prefix,
5251
                                 entry->d_name,
×
5252
                                 content_type);
5253
            command = thumbnailer_build_command(info.exec_line,
×
5254
                                                pchunk->source_path,
×
5255
                                                input_uri,
5256
                                                png_path,
5257
                                                requested_size,
5258
                                                content_type);
5259
            if (command == NULL) {
×
5260
                continue;
×
5261
            }
5262
            if (thumbnailer_is_evince_thumbnailer(info.exec_line,
×
5263
                                                  info.tryexec)) {
×
5264
                loader_trace_message(
×
5265
                    "%s: applying evince-thumbnailer stdout workaround",
5266
                    log_prefix);
5267
                /* evince-thumbnailer fails when passed an output path.
5268
                 * Redirect stdout and copy the stream instead.
5269
                 */
5270
                evince_command = thumbnailer_build_evince_command(
×
5271
                    pchunk->source_path,
×
5272
                    requested_size);
5273
                if (evince_command == NULL) {
×
5274
                    thumbnailer_command_free(command);
×
5275
                    command = NULL;
×
5276
                    continue;
×
5277
                }
5278
                thumbnailer_command_free(command);
×
5279
                command = evince_command;
×
5280
                evince_command = NULL;
×
5281
                unlink(png_path);
×
5282
                status = thumbnailer_spawn(command,
×
5283
                                           entry->d_name,
×
5284
                                           log_prefix,
5285
                                           1,
5286
                                           png_path);
5287
            } else {
5288
                unlink(png_path);
×
5289
                status = thumbnailer_spawn(command,
×
5290
                                           entry->d_name,
×
5291
                                           log_prefix,
5292
                                           0,
5293
                                           NULL);
5294
            }
5295
            thumbnailer_command_free(command);
×
5296
            command = NULL;
×
5297
            executed = 1;
×
5298
            if (SIXEL_SUCCEEDED(status)) {
×
5299
                command_success = 1;
×
5300
                loader_trace_message("%s: %s produced %s",
×
5301
                                     log_prefix,
5302
                                     entry->d_name,
×
5303
                                     png_path);
5304
                break;
×
5305
            }
5306
        }
5307
        closedir(dir);
×
5308
        dir = NULL;
×
5309
        if (command_success) {
×
5310
            break;
×
5311
        }
5312
    }
5313

5314
    if (!command_success) {
×
5315
        loader_trace_message("%s: falling back to gdk-pixbuf-thumbnailer",
×
5316
                             log_prefix);
5317
        unlink(png_path);
×
5318
        command = thumbnailer_build_command(
×
5319
            "gdk-pixbuf-thumbnailer --size=%s %i %o",
5320
            pchunk->source_path,
×
5321
            input_uri,
5322
            png_path,
5323
            requested_size,
5324
            content_type);
5325
        if (command != NULL) {
×
5326
            unlink(png_path);
×
5327
            status = thumbnailer_spawn(command,
×
5328
                                       "gdk-pixbuf-thumbnailer",
5329
                                       log_prefix,
5330
                                       0,
5331
                                       NULL);
5332
            thumbnailer_command_free(command);
×
5333
            command = NULL;
×
5334
            if (SIXEL_FAILED(status)) {
×
5335
                goto end;
×
5336
            }
5337
            executed = 1;
×
5338
            command_success = 1;
×
5339
            loader_trace_message("%s: gdk-pixbuf-thumbnailer produced %s",
×
5340
                                 log_prefix,
5341
                                 png_path);
5342
        }
5343
    }
5344

5345
    if (!executed) {
×
5346
        sixel_helper_set_additional_message(
×
5347
            "load_with_gnome_thumbnailer: no thumbnailer available.");
5348
        status = SIXEL_RUNTIME_ERROR;
×
5349
        goto end;
×
5350
    }
5351

5352
    status = sixel_chunk_new(&thumb_chunk,
×
5353
                             png_path,
5354
                             0,
5355
                             NULL,
5356
                             pchunk->allocator);
×
5357
    if (SIXEL_FAILED(status)) {
×
5358
        goto end;
×
5359
    }
5360
    status = load_with_builtin(thumb_chunk,
×
5361
                               fstatic,
5362
                               fuse_palette,
5363
                               reqcolors,
5364
                               bgcolor,
5365
                               loop_control,
5366
                               fn_load,
5367
                               context);
5368
    if (SIXEL_FAILED(status)) {
×
5369
        goto end;
×
5370
    }
5371

5372
    status = SIXEL_OK;
×
5373

5374
end:
5375
    if (command != NULL) {
×
5376
        thumbnailer_command_free(command);
×
5377
        command = NULL;
×
5378
    }
5379
    if (evince_command != NULL) {
×
5380
        thumbnailer_command_free(evince_command);
×
5381
        evince_command = NULL;
×
5382
    }
5383
    if (thumb_chunk != NULL) {
×
5384
        sixel_chunk_destroy(thumb_chunk);
×
5385
        thumb_chunk = NULL;
×
5386
    }
5387
    if (png_path != NULL) {
×
5388
        unlink(png_path);
×
5389
        free(png_path);
×
5390
        png_path = NULL;
×
5391
    }
5392
    if (fd >= 0) {
×
5393
        close(fd);
×
5394
        fd = -1;
×
5395
    }
5396
    if (directories != NULL) {
×
5397
        thumbnailer_string_list_free(directories);
×
5398
        directories = NULL;
×
5399
    }
5400
    if (dir != NULL) {
×
5401
        closedir(dir);
×
5402
        dir = NULL;
×
5403
    }
5404
    thumbnailer_entry_clear(&info);
×
5405
    free(content_type);
×
5406
    content_type = NULL;
×
5407
    free(input_uri);
×
5408
    input_uri = NULL;
×
5409

5410
    return status;
×
5411
}
5412

5413
#endif  /* HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK */
5414

5415

5416
#if HAVE_GD
5417
static int
5418
detect_file_format(int len, unsigned char *data)
5419
{
5420
    if (len > 18 && memcmp("TRUEVISION", data + len - 18, 10) == 0) {
5421
        return SIXEL_FORMAT_TGA;
5422
    }
5423

5424
    if (len > 3 && memcmp("GIF", data, 3) == 0) {
5425
        return SIXEL_FORMAT_GIF;
5426
    }
5427

5428
    if (len > 8 && memcmp("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", data, 8) == 0) {
5429
        return SIXEL_FORMAT_PNG;
5430
    }
5431

5432
    if (len > 2 && memcmp("BM", data, 2) == 0) {
5433
        return SIXEL_FORMAT_BMP;
5434
    }
5435

5436
    if (len > 2 && memcmp("\xFF\xD8", data, 2) == 0) {
5437
        return SIXEL_FORMAT_JPG;
5438
    }
5439

5440
    if (len > 2 && memcmp("\x00\x00", data, 2) == 0) {
5441
        return SIXEL_FORMAT_WBMP;
5442
    }
5443

5444
    if (len > 2 && memcmp("\x4D\x4D", data, 2) == 0) {
5445
        return SIXEL_FORMAT_TIFF;
5446
    }
5447

5448
    if (len > 2 && memcmp("\x49\x49", data, 2) == 0) {
5449
        return SIXEL_FORMAT_TIFF;
5450
    }
5451

5452
    if (len > 2 && memcmp("\033P", data, 2) == 0) {
5453
        return SIXEL_FORMAT_SIXEL;
5454
    }
5455

5456
    if (len > 2 && data[0] == 0x90
5457
        && (data[len - 1] == 0x9C || data[len - 2] == 0x9C)) {
5458
        return SIXEL_FORMAT_SIXEL;
5459
    }
5460

5461
    if (len > 1 && data[0] == 'P' && data[1] >= '1' && data[1] <= '6') {
5462
        return SIXEL_FORMAT_PNM;
5463
    }
5464

5465
    if (len > 3 && memcmp("gd2", data, 3) == 0) {
5466
        return SIXEL_FORMAT_GD2;
5467
    }
5468

5469
    if (len > 4 && memcmp("8BPS", data, 4) == 0) {
5470
        return SIXEL_FORMAT_PSD;
5471
    }
5472

5473
    if (len > 11 && memcmp("#?RADIANCE\n", data, 11) == 0) {
5474
        return SIXEL_FORMAT_HDR;
5475
    }
5476

5477
    return (-1);
5478
}
5479
#endif /* HAVE_GD */
5480

5481
#if HAVE_GD
5482

5483
static SIXELSTATUS
5484
load_with_gd(
5485
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
5486
    int                       /* in */     fstatic,      /* static */
5487
    int                       /* in */     fuse_palette, /* whether to use */
5488
                                                  /* palette if possible */
5489
    int                       /* in */     reqcolors,    /* reqcolors */
5490
    unsigned char             /* in */     *bgcolor,     /* background */
5491
                                                  /* color */
5492
    int                       /* in */     loop_control, /* one of enum */
5493
                                                  /* loop_control */
5494
    sixel_load_image_function /* in */     fn_load,      /* callback */
5495
    void                      /* in/out */ *context      /* private */
5496
                                                  /* data for callback */
5497
)
5498
{
5499
    SIXELSTATUS status = SIXEL_FALSE;
5500
    unsigned char *p;
5501
    gdImagePtr im = NULL;
5502
    int x, y;
5503
    int c;
5504
    sixel_frame_t *frame = NULL;
5505
    int format;
5506

5507
    (void) fstatic;
5508
    (void) fuse_palette;
5509
    (void) reqcolors;
5510
    (void) bgcolor;
5511
    (void) loop_control;
5512

5513
    format = detect_file_format(pchunk->size, pchunk->buffer);
5514

5515
    if (format == SIXEL_FORMAT_GIF) {
5516
#if HAVE_DECL_GDIMAGECREATEFROMGIFANIMPTR
5517
        gdImagePtr *ims = NULL;
5518
        int frames = 0;
5519
        int i;
5520
        int *delays = NULL;
5521

5522
        ims = gdImageCreateFromGifAnimPtr(pchunk->size, pchunk->buffer,
5523
                                          &frames, &delays);
5524
        if (ims == NULL) {
5525
            status = SIXEL_GD_ERROR;
5526
            goto end;
5527
        }
5528

5529
        for (i = 0; i < frames; i++) {
5530
            im = ims[i];
5531
            if (!gdImageTrueColor(im)) {
5532
# if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
5533
                if (!gdImagePaletteToTrueColor(im)) {
5534
                    status = SIXEL_GD_ERROR;
5535
                    goto gif_end;
5536
                }
5537
# else
5538
                status = SIXEL_GD_ERROR;
5539
                goto gif_end;
5540
# endif
5541
            }
5542

5543
            status = sixel_frame_new(&frame, pchunk->allocator);
5544
            if (SIXEL_FAILED(status)) {
5545
                frame = NULL;
5546
                goto gif_end;
5547
            }
5548

5549
            frame->width = gdImageSX(im);
5550
            frame->height = gdImageSY(im);
5551
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
5552
            sixel_frame_set_pixels(frame,
5553
                                   sixel_allocator_malloc(
5554
                                       pchunk->allocator,
5555
                                       (size_t)(frame->width
5556
                                                * frame->height * 3)));
5557
            p = sixel_frame_get_pixels(frame);
5558
            if (p == NULL) {
5559
                sixel_helper_set_additional_message(
5560
                    "load_with_gd: sixel_allocator_malloc() failed.");
5561
                status = SIXEL_BAD_ALLOCATION;
5562
                sixel_frame_unref(frame);
5563
                frame = NULL;
5564
                goto gif_end;
5565
            }
5566
            for (y = 0; y < frame->height; y++) {
5567
                for (x = 0; x < frame->width; x++) {
5568
                    c = gdImageTrueColorPixel(im, x, y);
5569
                    *p++ = gdTrueColorGetRed(c);
5570
                    *p++ = gdTrueColorGetGreen(c);
5571
                    *p++ = gdTrueColorGetBlue(c);
5572
                }
5573
            }
5574

5575
            if (delays) {
5576
                frame->delay.tv_sec = delays[i] / 100;
5577
                frame->delay.tv_nsec = (delays[i] % 100) * 10000000L;
5578
            }
5579

5580
            status = fn_load(frame, context);
5581
            sixel_frame_unref(frame);
5582
            frame = NULL;
5583
            gdImageDestroy(im);
5584
            ims[i] = NULL;
5585
            if (SIXEL_FAILED(status)) {
5586
                goto gif_end;
5587
            }
5588
        }
5589

5590
        status = SIXEL_OK;
5591

5592
gif_end:
5593
        if (delays) {
5594
            gdFree(delays);
5595
        }
5596
        if (ims) {
5597
            for (i = 0; i < frames; i++) {
5598
                if (ims[i]) {
5599
                    gdImageDestroy(ims[i]);
5600
                }
5601
            }
5602
            gdFree(ims);
5603
        }
5604
        goto end;
5605
#else
5606
        status = SIXEL_GD_ERROR;
5607
        goto end;
5608
#endif
5609
    }
5610

5611
    switch (format) {
5612
#if HAVE_DECL_GDIMAGECREATEFROMPNGPTR
5613
        case SIXEL_FORMAT_PNG:
5614
            im = gdImageCreateFromPngPtr(pchunk->size, pchunk->buffer);
5615
            break;
5616
#endif  /* HAVE_DECL_GDIMAGECREATEFROMPNGPTR */
5617
#if HAVE_DECL_GDIMAGECREATEFROMBMPPTR
5618
        case SIXEL_FORMAT_BMP:
5619
            im = gdImageCreateFromBmpPtr(pchunk->size, pchunk->buffer);
5620
            break;
5621
#endif  /* HAVE_DECL_GDIMAGECREATEFROMBMPPTR */
5622
        case SIXEL_FORMAT_JPG:
5623
#if HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX
5624
            im = gdImageCreateFromJpegPtrEx(pchunk->size, pchunk->buffer, 1);
5625
#elif HAVE_DECL_GDIMAGECREATEFROMJPEGPTR
5626
            im = gdImageCreateFromJpegPtr(pchunk->size, pchunk->buffer);
5627
#endif  /* HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX */
5628
            break;
5629
#if HAVE_DECL_GDIMAGECREATEFROMTGAPTR
5630
        case SIXEL_FORMAT_TGA:
5631
            im = gdImageCreateFromTgaPtr(pchunk->size, pchunk->buffer);
5632
            break;
5633
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTGAPTR */
5634
#if HAVE_DECL_GDIMAGECREATEFROMWBMPPTR
5635
        case SIXEL_FORMAT_WBMP:
5636
            im = gdImageCreateFromWBMPPtr(pchunk->size, pchunk->buffer);
5637
            break;
5638
#endif  /* HAVE_DECL_GDIMAGECREATEFROMWBMPPTR */
5639
#if HAVE_DECL_GDIMAGECREATEFROMTIFFPTR
5640
        case SIXEL_FORMAT_TIFF:
5641
            im = gdImageCreateFromTiffPtr(pchunk->size, pchunk->buffer);
5642
            break;
5643
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTIFFPTR */
5644
#if HAVE_DECL_GDIMAGECREATEFROMGD2PTR
5645
        case SIXEL_FORMAT_GD2:
5646
            im = gdImageCreateFromGd2Ptr(pchunk->size, pchunk->buffer);
5647
            break;
5648
#endif  /* HAVE_DECL_GDIMAGECREATEFROMGD2PTR */
5649
        default:
5650
            status = SIXEL_GD_ERROR;
5651
            sixel_helper_set_additional_message(
5652
                "unexpected image format detected.");
5653
            goto end;
5654
    }
5655

5656
    if (im == NULL) {
5657
        status = SIXEL_GD_ERROR;
5658
        /* TODO: retrieve error detail */
5659
        goto end;
5660
    }
5661

5662
    if (!gdImageTrueColor(im)) {
5663
#if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
5664
        if (!gdImagePaletteToTrueColor(im)) {
5665
            gdImageDestroy(im);
5666
            status = SIXEL_GD_ERROR;
5667
            /* TODO: retrieve error detail */
5668
            goto end;
5669
        }
5670
#else
5671
        status = SIXEL_GD_ERROR;
5672
        /* TODO: retrieve error detail */
5673
        goto end;
5674
#endif
5675
    }
5676

5677
    status = sixel_frame_new(&frame, pchunk->allocator);
5678
    if (SIXEL_FAILED(status)) {
5679
        goto end;
5680
    }
5681

5682
    frame->width = gdImageSX(im);
5683
    frame->height = gdImageSY(im);
5684
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
5685
    sixel_frame_set_pixels(frame,
5686
                           sixel_allocator_malloc(
5687
                               pchunk->allocator,
5688
                               (size_t)(frame->width * frame->height * 3)));
5689
    p = sixel_frame_get_pixels(frame);
5690
    if (p == NULL) {
5691
        sixel_helper_set_additional_message(
5692
            "load_with_gd: sixel_allocator_malloc() failed.");
5693
        status = SIXEL_BAD_ALLOCATION;
5694
        gdImageDestroy(im);
5695
        goto end;
5696
    }
5697
    for (y = 0; y < frame->height; y++) {
5698
        for (x = 0; x < frame->width; x++) {
5699
            c = gdImageTrueColorPixel(im, x, y);
5700
            *p++ = gdTrueColorGetRed(c);
5701
            *p++ = gdTrueColorGetGreen(c);
5702
            *p++ = gdTrueColorGetBlue(c);
5703
        }
5704
    }
5705
    gdImageDestroy(im);
5706

5707
    status = fn_load(frame, context);
5708
    if (SIXEL_FAILED(status)) {
5709
        goto end;
5710
    }
5711

5712
    sixel_frame_unref(frame);
5713

5714
    status = SIXEL_OK;
5715

5716
end:
5717
    return status;
5718
}
5719

5720
#endif  /* HAVE_GD */
5721

5722
#if HAVE_WIC
5723

5724
#include <windows.h>
5725
#include <wincodec.h>
5726

5727
SIXELSTATUS
5728
load_with_wic(
5729
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
5730
    int                       /* in */     fstatic,      /* static */
5731
    int                       /* in */     fuse_palette, /* whether to use */
5732
                                                  /* palette if possible */
5733
    int                       /* in */     reqcolors,    /* reqcolors */
5734
    unsigned char             /* in */     *bgcolor,     /* background */
5735
                                                  /* color */
5736
    int                       /* in */     loop_control, /* one of enum */
5737
                                                  /* loop_control */
5738
    sixel_load_image_function /* in */     fn_load,      /* callback */
5739
    void                      /* in/out */ *context      /* private */
5740
                                                  /* data for callback */
5741
)
5742
{
5743
    HRESULT                 hr         = E_FAIL;
5744
    SIXELSTATUS             status     = SIXEL_FALSE;
5745
    IWICImagingFactory     *factory    = NULL;
5746
    IWICStream             *stream     = NULL;
5747
    IWICBitmapDecoder      *decoder    = NULL;
5748
    IWICBitmapFrameDecode  *wicframe   = NULL;
5749
    IWICFormatConverter    *conv       = NULL;
5750
    IWICBitmapSource       *src        = NULL;
5751
    IWICPalette            *wicpalette = NULL;
5752
    WICColor               *wiccolors  = NULL;
5753
    IWICMetadataQueryReader *qdecoder  = NULL;
5754
    IWICMetadataQueryReader *qframe    = NULL;
5755
    UINT                    ncolors    = 0;
5756
    sixel_frame_t          *frame      = NULL;
5757
    int                     comp       = 4;
5758
    UINT                    actual     = 0;
5759
    UINT                    i;
5760
    UINT                    frame_count = 0;
5761
    int                     anim_loop_count = (-1);
5762
    int                     is_gif;
5763
    WICColor                c;
5764
    unsigned char          *pixels;
5765

5766
    (void) reqcolors;
5767
    (void) bgcolor;
5768

5769
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
5770
    if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
5771
        return status;
5772
    }
5773

5774
    status = sixel_frame_new(&frame, pchunk->allocator);
5775
    if (SIXEL_FAILED(status)) {
5776
        goto end;
5777
    }
5778

5779
    hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
5780
                          &IID_IWICImagingFactory, (void**)&factory);
5781
    if (FAILED(hr)) {
5782
        goto end;
5783
    }
5784

5785
    hr = factory->lpVtbl->CreateStream(factory, &stream);
5786
    if (FAILED(hr)) {
5787
        goto end;
5788
    }
5789

5790
    hr = stream->lpVtbl->InitializeFromMemory(stream,
5791
                                              (BYTE*)pchunk->buffer,
5792
                                              (DWORD)pchunk->size);
5793
    if (FAILED(hr)) {
5794
        goto end;
5795
    }
5796

5797
    hr = factory->lpVtbl->CreateDecoderFromStream(
5798
        factory,
5799
        (IStream*)stream,
5800
        NULL,
5801
        WICDecodeMetadataCacheOnDemand,
5802
        &decoder);
5803
    if (FAILED(hr)) {
5804
        goto end;
5805
    }
5806

5807
    is_gif = (memcmp("GIF", pchunk->buffer, 3) == 0);
5808

5809
    if (is_gif) {
5810
        hr = decoder->lpVtbl->GetFrameCount(decoder, &frame_count);
5811
        if (FAILED(hr)) {
5812
            goto end;
5813
        }
5814
        if (fstatic) {
5815
            frame_count = 1;
5816
        }
5817

5818
        hr = decoder->lpVtbl->GetMetadataQueryReader(decoder, &qdecoder);
5819
        if (SUCCEEDED(hr)) {
5820
            PROPVARIANT pv;
5821
            PropVariantInit(&pv);
5822
            hr = qdecoder->lpVtbl->GetMetadataByName(
5823
                qdecoder,
5824
                L"/appext/Application/NETSCAPE2.0/Loop",
5825
                &pv);
5826
            if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5827
                anim_loop_count = pv.uiVal;
5828
            }
5829
            PropVariantClear(&pv);
5830
            qdecoder->lpVtbl->Release(qdecoder);
5831
            qdecoder = NULL;
5832
        }
5833

5834
        frame->loop_count = 0;
5835
        for (;;) {
5836
            frame->frame_no = 0;
5837
            for (i = 0; i < frame_count; ++i) {
5838
                hr = decoder->lpVtbl->GetFrame(decoder, i, &wicframe);
5839
                if (FAILED(hr)) {
5840
                    goto end;
5841
                }
5842

5843
                hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5844
                if (FAILED(hr)) {
5845
                    goto end;
5846
                }
5847
                hr = conv->lpVtbl->Initialize(conv,
5848
                                              (IWICBitmapSource*)wicframe,
5849
                                              &GUID_WICPixelFormat32bppRGBA,
5850
                                              WICBitmapDitherTypeNone,
5851
                                              NULL,
5852
                                              0.0,
5853
                                              WICBitmapPaletteTypeCustom);
5854
                if (FAILED(hr)) {
5855
                    goto end;
5856
                }
5857

5858
                src = (IWICBitmapSource*)conv;
5859
                comp = 4;
5860
                frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5861

5862
                hr = src->lpVtbl->GetSize(
5863
                    src,
5864
                    (UINT *)&frame->width,
5865
                    (UINT *)&frame->height);
5866
                if (FAILED(hr)) {
5867
                    goto end;
5868
                }
5869

5870
                if (frame->width <= 0 || frame->height <= 0 ||
5871
                    frame->width > SIXEL_WIDTH_LIMIT ||
5872
                    frame->height > SIXEL_HEIGHT_LIMIT) {
5873
                    sixel_helper_set_additional_message(
5874
                        "load_with_wic: an invalid width or height parameter "
5875
                        "detected.");
5876
                    status = SIXEL_BAD_INPUT;
5877
                    goto end;
5878
                }
5879

5880
                sixel_frame_set_pixels(frame,
5881
                                       sixel_allocator_malloc(
5882
                                           pchunk->allocator,
5883
                                           (size_t)(frame->height
5884
                                                    * frame->width * comp)));
5885
                pixels = sixel_frame_get_pixels(frame);
5886
                if (pixels == NULL) {
5887
                    hr = E_OUTOFMEMORY;
5888
                    goto end;
5889
                }
5890

5891
                {
5892
                    WICRect rc = {
5893
                        0,
5894
                        0,
5895
                        (INT)frame->width,
5896
                        (INT)frame->height
5897
                    };
5898
                    hr = src->lpVtbl->CopyPixels(
5899
                        src,
5900
                        &rc,
5901
                        frame->width * comp,
5902
                        (UINT)frame->width * frame->height * comp,
5903
                        pixels);
5904
                    if (FAILED(hr)) {
5905
                        goto end;
5906
                    }
5907
                }
5908

5909
                frame->delay = 0;
5910
                hr = wicframe->lpVtbl->GetMetadataQueryReader(
5911
                    wicframe,
5912
                    &qframe);
5913
                if (SUCCEEDED(hr)) {
5914
                    PROPVARIANT pv;
5915
                    PropVariantInit(&pv);
5916
                    hr = qframe->lpVtbl->GetMetadataByName(
5917
                        qframe,
5918
                        L"/grctlext/Delay",
5919
                        &pv);
5920
                    if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5921
                        frame->delay = (int)(pv.uiVal) * 10;
5922
                    }
5923
                    PropVariantClear(&pv);
5924
                    qframe->lpVtbl->Release(qframe);
5925
                    qframe = NULL;
5926
                }
5927

5928
                frame->multiframe = 1;
5929
                status = fn_load(frame, context);
5930
                if (SIXEL_FAILED(status)) {
5931
                    goto end;
5932
                }
5933
                sixel_frame_set_pixels(frame, NULL);
5934
                frame->palette = NULL;
5935

5936
                if (conv) {
5937
                    conv->lpVtbl->Release(conv);
5938
                    conv = NULL;
5939
                }
5940
                if (wicframe) {
5941
                    wicframe->lpVtbl->Release(wicframe);
5942
                    wicframe = NULL;
5943
                }
5944

5945
                frame->frame_no++;
5946
            }
5947

5948
            ++frame->loop_count;
5949

5950
            if (anim_loop_count < 0) {
5951
                break;
5952
            }
5953
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
5954
                break;
5955
            }
5956
            if (loop_control == SIXEL_LOOP_AUTO &&
5957
                frame->loop_count == anim_loop_count) {
5958
                break;
5959
            }
5960
        }
5961

5962
        status = SIXEL_OK;
5963
        goto end;
5964
    }
5965

5966
    hr = decoder->lpVtbl->GetFrame(decoder, 0, &wicframe);
5967
    if (FAILED(hr)) {
5968
        goto end;
5969
    }
5970

5971
    if (fuse_palette) {
5972
        hr = factory->lpVtbl->CreatePalette(factory, &wicpalette);
5973
        if (SUCCEEDED(hr)) {
5974
            hr = wicframe->lpVtbl->CopyPalette(wicframe, wicpalette);
5975
        }
5976
        if (SUCCEEDED(hr)) {
5977
            hr = wicpalette->lpVtbl->GetColorCount(wicpalette, &ncolors);
5978
        }
5979
        if (SUCCEEDED(hr) && ncolors > 0 && ncolors <= 256) {
5980
            hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5981
            if (SUCCEEDED(hr)) {
5982
                hr = conv->lpVtbl->Initialize(conv,
5983
                                              (IWICBitmapSource*)wicframe,
5984
                                              &GUID_WICPixelFormat8bppIndexed,
5985
                                              WICBitmapDitherTypeNone,
5986
                                              wicpalette,
5987
                                              0.0,
5988
                                              WICBitmapPaletteTypeCustom);
5989
            }
5990
            if (SUCCEEDED(hr)) {
5991
                src = (IWICBitmapSource*)conv;
5992
                comp = 1;
5993
                frame->pixelformat = SIXEL_PIXELFORMAT_PAL8;
5994
                frame->palette = sixel_allocator_malloc(
5995
                    pchunk->allocator,
5996
                    (size_t)ncolors * 3);
5997
                if (frame->palette == NULL) {
5998
                    hr = E_OUTOFMEMORY;
5999
                } else {
6000
                    wiccolors = (WICColor *)sixel_allocator_malloc(
6001
                        pchunk->allocator,
6002
                        (size_t)ncolors * sizeof(WICColor));
6003
                    if (wiccolors == NULL) {
6004
                        hr = E_OUTOFMEMORY;
6005
                    } else {
6006
                        actual = 0;
6007
                        hr = wicpalette->lpVtbl->GetColors(
6008
                            wicpalette, ncolors, wiccolors, &actual);
6009
                        if (SUCCEEDED(hr) && actual == ncolors) {
6010
                            for (i = 0; i < ncolors; ++i) {
6011
                                c = wiccolors[i];
6012
                                frame->palette[i * 3 + 0] =
6013
                                    (unsigned char)((c >> 16) & 0xFF);
6014
                                frame->palette[i * 3 + 1] =
6015
                                    (unsigned char)((c >> 8) & 0xFF);
6016
                                frame->palette[i * 3 + 2] =
6017
                                    (unsigned char)(c & 0xFF);
6018
                            }
6019
                            frame->ncolors = (int)ncolors;
6020
                        } else {
6021
                            hr = E_FAIL;
6022
                        }
6023
                    }
6024
                }
6025
            }
6026
            if (FAILED(hr)) {
6027
                if (conv) {
6028
                    conv->lpVtbl->Release(conv);
6029
                    conv = NULL;
6030
                }
6031
                sixel_allocator_free(pchunk->allocator, frame->palette);
6032
                frame->palette = NULL;
6033
                sixel_allocator_free(pchunk->allocator, wiccolors);
6034
                wiccolors = NULL;
6035
                src = NULL;
6036
            }
6037
        }
6038
    }
6039

6040
    if (src == NULL) {
6041
        hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
6042
        if (FAILED(hr)) {
6043
            goto end;
6044
        }
6045

6046
        hr = conv->lpVtbl->Initialize(conv, (IWICBitmapSource*)wicframe,
6047
                                      &GUID_WICPixelFormat32bppRGBA,
6048
                                      WICBitmapDitherTypeNone, NULL, 0.0,
6049
                                      WICBitmapPaletteTypeCustom);
6050
        if (FAILED(hr)) {
6051
            goto end;
6052
        }
6053

6054
        src = (IWICBitmapSource*)conv;
6055
        comp = 4;
6056
        frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
6057
    }
6058

6059
    hr = src->lpVtbl->GetSize(
6060
        src, (UINT *)&frame->width, (UINT *)&frame->height);
6061
    if (FAILED(hr)) {
6062
        goto end;
6063
    }
6064

6065
    /* check size */
6066
    if (frame->width <= 0) {
6067
        sixel_helper_set_additional_message(
6068
            "load_with_wic: an invalid width parameter detected.");
6069
        status = SIXEL_BAD_INPUT;
6070
        goto end;
6071
    }
6072
    if (frame->height <= 0) {
6073
        sixel_helper_set_additional_message(
6074
            "load_with_wic: an invalid width parameter detected.");
6075
        status = SIXEL_BAD_INPUT;
6076
        goto end;
6077
    }
6078
    if (frame->width > SIXEL_WIDTH_LIMIT) {
6079
        sixel_helper_set_additional_message(
6080
            "load_with_wic: given width parameter is too huge.");
6081
        status = SIXEL_BAD_INPUT;
6082
        goto end;
6083
    }
6084
    if (frame->height > SIXEL_HEIGHT_LIMIT) {
6085
        sixel_helper_set_additional_message(
6086
            "load_with_wic: given height parameter is too huge.");
6087
        status = SIXEL_BAD_INPUT;
6088
        goto end;
6089
    }
6090

6091
    sixel_frame_set_pixels(frame,
6092
                           sixel_allocator_malloc(
6093
                               pchunk->allocator,
6094
                               (size_t)(frame->height * frame->width * comp)));
6095
    pixels = sixel_frame_get_pixels(frame);
6096

6097
    {
6098
        WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
6099
        hr = src->lpVtbl->CopyPixels(
6100
            src,
6101
            &rc,                                        /* prc */
6102
            frame->width * comp,                        /* cbStride */
6103
            (UINT)frame->width * frame->height * comp,  /* cbBufferSize */
6104
            pixels);                                    /* pbBuffer */
6105
        if (FAILED(hr)) {
6106
            goto end;
6107
        }
6108
    }
6109

6110
    status = fn_load(frame, context);
6111
    if (SIXEL_FAILED(status)) {
6112
        goto end;
6113
    }
6114

6115
end:
6116
    if (conv) {
6117
         conv->lpVtbl->Release(conv);
6118
    }
6119
    if (wicpalette) {
6120
         wicpalette->lpVtbl->Release(wicpalette);
6121
    }
6122
    if (wiccolors) {
6123
         sixel_allocator_free(pchunk->allocator, wiccolors);
6124
    }
6125
    if (wicframe) {
6126
         wicframe->lpVtbl->Release(wicframe);
6127
    }
6128
    if (qdecoder) {
6129
         qdecoder->lpVtbl->Release(qdecoder);
6130
    }
6131
    if (qframe) {
6132
         qframe->lpVtbl->Release(qframe);
6133
    }
6134
    if (stream) {
6135
         stream->lpVtbl->Release(stream);
6136
    }
6137
    if (factory) {
6138
         factory->lpVtbl->Release(factory);
6139
    }
6140
    sixel_frame_unref(frame);
6141

6142
    CoUninitialize();
6143

6144
    if (FAILED(hr)) {
6145
        return SIXEL_FALSE;
6146
    }
6147

6148
    return SIXEL_OK;
6149
}
6150

6151
#endif /* HAVE_WIC */
6152

6153
#if HAVE_WIC
6154
static int
6155
loader_can_try_wic(sixel_chunk_t const *chunk)
6156
{
6157
    if (chunk == NULL) {
6158
        return 0;
6159
    }
6160
    if (chunk_is_gif(chunk)) {
6161
        return 0;
6162
    }
6163
    return 1;
6164
}
6165
#endif
6166

6167
static sixel_loader_entry_t const sixel_loader_entries[] = {
6168
    /*
6169
     * Fast loaders take precedence so probing prefers native decoders.
6170
     *
6171
     * 1. libpng   2. libjpeg   3. builtin   4+. remaining generic loaders
6172
     */
6173
#if HAVE_LIBPNG
6174
    { "libpng", load_with_libpng, loader_can_try_libpng, 1 },
6175
#endif
6176
#if HAVE_JPEG
6177
    { "libjpeg", load_with_libjpeg, loader_can_try_libjpeg, 1 },
6178
#endif
6179
    { "builtin", load_with_builtin, NULL, 1 },
6180
#if HAVE_WIC
6181
    { "wic", load_with_wic, loader_can_try_wic, 1 },
6182
#endif
6183
#if HAVE_COREGRAPHICS
6184
    { "coregraphics", load_with_coregraphics, NULL, 1 },
6185
#endif
6186
#ifdef HAVE_GDK_PIXBUF2
6187
    { "gdk-pixbuf2", load_with_gdkpixbuf, NULL, 1 },
6188
#endif
6189
#if HAVE_GD
6190
    { "gd", load_with_gd, NULL, 1 },
6191
#endif
6192
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
6193
    { "quicklook", load_with_quicklook, NULL, 0 },
6194
#endif
6195
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6196
    { "gnome-thumbnailer", load_with_gnome_thumbnailer, NULL, 0 },
6197
#endif
6198
};
6199

6200
static int
6201
loader_entry_available(char const *name)
10✔
6202
{
6203
    size_t index;
6204
    size_t entry_count;
6205

6206
    if (name == NULL) {
10!
6207
        return 0;
×
6208
    }
6209

6210
    entry_count = sizeof(sixel_loader_entries) /
10✔
6211
                  sizeof(sixel_loader_entries[0]);
6212

6213
    for (index = 0; index < entry_count; ++index) {
27!
6214
        if (sixel_loader_entries[index].name != NULL &&
23!
6215
                strcmp(sixel_loader_entries[index].name, name) == 0) {
23✔
6216
            return 1;
6✔
6217
        }
6218
    }
5✔
6219

6220
    return 0;
4✔
6221
}
2✔
6222

6223
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
6224
static void
6225
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
6226
{
6227
    if (buffer == NULL || capacity == 0) {
6228
        return;
6229
    }
6230

6231
    buffer[0] = '\0';
6232
    if (source == NULL) {
×
6233
        return;
6234
    }
6235

6236
    if (!CFStringGetCString(source,
6237
                             buffer,
6238
                             (CFIndex)capacity,
6239
                             kCFStringEncodingUTF8)) {
6240
        buffer[0] = '\0';
6241
    }
6242
}
6243

6244
static int
6245
loader_quicklook_can_decode(sixel_chunk_t const *pchunk,
1✔
6246
                            char const *filename)
6247
{
6248
    char const *path;
6249
    CFStringRef path_ref;
6250
    CFURLRef url;
6251
    CGFloat max_dimension;
6252
    CGSize max_size;
6253
    CGImageRef image;
6254
    int result;
6255

6256
    path = NULL;
1✔
6257
    path_ref = NULL;
1✔
6258
    url = NULL;
1✔
6259
    image = NULL;
1✔
6260
    result = 0;
1✔
6261

6262
    loader_thumbnailer_initialize_size_hint();
1✔
6263

6264
    if (pchunk != NULL && pchunk->source_path != NULL) {
1✔
6265
        path = pchunk->source_path;
6266
    } else if (filename != NULL) {
6267
        path = filename;
6268
    }
6269

6270
    if (path == NULL || strcmp(path, "-") == 0 ||
1✔
6271
            strstr(path, "://") != NULL) {
6272
        return 0;
1✔
6273
    }
6274

6275
    path_ref = CFStringCreateWithCString(kCFAllocatorDefault,
6276
                                         path,
6277
                                         kCFStringEncodingUTF8);
6278
    if (path_ref == NULL) {
×
6279
        return 0;
6280
    }
6281

6282
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
6283
                                        path_ref,
6284
                                        kCFURLPOSIXPathStyle,
6285
                                        false);
6286
    CFRelease(path_ref);
6287
    path_ref = NULL;
6288
    if (url == NULL) {
×
6289
        return 0;
6290
    }
6291

6292
    if (thumbnailer_size_hint > 0) {
×
6293
        max_dimension = (CGFloat)thumbnailer_size_hint;
6294
    } else {
6295
        max_dimension = (CGFloat)SIXEL_THUMBNAILER_DEFAULT_SIZE;
6296
    }
6297
    max_size.width = max_dimension;
6298
    max_size.height = max_dimension;
6299

6300
#if HAVE_QUICKLOOK_THUMBNAILING
6301
    image = sixel_quicklook_thumbnail_create(url, max_size);
6302
    if (image == NULL) {
6303
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6304
#  pragma clang diagnostic push
6305
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
6306
# endif
6307
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
6308
                                       url,
6309
                                       max_size,
6310
                                       NULL);
6311
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6312
#  pragma clang diagnostic pop
6313
# endif
6314
    }
6315
#else
6316
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6317
#  pragma clang diagnostic push
6318
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
6319
# endif
6320
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
6321
                                   url,
6322
                                   max_size,
6323
                                   NULL);
6324
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6325
#  pragma clang diagnostic pop
6326
# endif
6327
#endif
6328

6329
    if (image != NULL) {
6330
        result = 1;
6331
        CGImageRelease(image);
6332
        image = NULL;
6333
    }
6334

6335
    CFRelease(url);
6336
    url = NULL;
6337

6338
    return result;
6339
}
1✔
6340
#else
6341
static int
6342
loader_quicklook_can_decode(sixel_chunk_t const *pchunk,
6343
                            char const *filename)
6344
{
6345
    (void)pchunk;
6346
    (void)filename;
6347
    return 0;
6348
}
6349
#endif
6350

6351
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6352
static void
6353
loader_probe_gnome_thumbnailers(char const *mime_type,
5✔
6354
                                int *has_directories,
6355
                                int *has_match)
6356
{
6357
    struct thumbnailer_string_list *directories;
6358
    struct thumbnailer_entry info;
6359
    size_t dir_index;
6360
    DIR *dir;
6361
    struct dirent *entry;
6362
    char *thumbnailer_path;
6363
    int match;
6364
    int directories_present;
6365
    size_t name_length;
6366

6367
    directories = NULL;
5✔
6368
    dir_index = 0;
5✔
6369
    dir = NULL;
5✔
6370
    entry = NULL;
5✔
6371
    thumbnailer_path = NULL;
5✔
6372
    match = 0;
5✔
6373
    directories_present = 0;
5✔
6374
    name_length = 0;
5✔
6375

6376
    if (has_directories != NULL) {
5!
6377
        *has_directories = 0;
5✔
6378
    }
1✔
6379
    if (has_match != NULL) {
5!
6380
        *has_match = 0;
5✔
6381
    }
1✔
6382

6383
    directories = thumbnailer_collect_directories();
5✔
6384
    if (directories == NULL) {
5!
6385
        return;
×
6386
    }
6387

6388
    if (directories->length > 0) {
5!
6389
        directories_present = 1;
5✔
6390
        if (has_directories != NULL) {
5!
6391
            *has_directories = 1;
5✔
6392
        }
1✔
6393
    }
1✔
6394

6395
    thumbnailer_entry_init(&info);
6✔
6396

6397
    if (mime_type != NULL && mime_type[0] != '\0') {
6!
6398
        for (dir_index = 0; dir_index < directories->length && match == 0;
×
6399
                ++dir_index) {
×
6400
            dir = opendir(directories->items[dir_index]);
×
6401
            if (dir == NULL) {
×
6402
                continue;
×
6403
            }
6404
            while (match == 0 && (entry = readdir(dir)) != NULL) {
×
6405
                thumbnailer_entry_clear(&info);
×
6406
                thumbnailer_entry_init(&info);
×
6407
                name_length = strlen(entry->d_name);
×
6408
                if (name_length < 12 ||
×
6409
                        strcmp(entry->d_name + name_length - 12,
×
6410
                               ".thumbnailer") != 0) {
6411
                    continue;
×
6412
                }
6413
                thumbnailer_path = thumbnailer_join_paths(
×
6414
                    directories->items[dir_index],
×
6415
                    entry->d_name);
×
6416
                if (thumbnailer_path == NULL) {
×
6417
                    continue;
×
6418
                }
6419
                if (!thumbnailer_parse_file(thumbnailer_path, &info)) {
×
6420
                    free(thumbnailer_path);
×
6421
                    thumbnailer_path = NULL;
×
6422
                    continue;
×
6423
                }
6424
                free(thumbnailer_path);
×
6425
                thumbnailer_path = NULL;
×
6426
                if (!thumbnailer_has_tryexec(info.tryexec)) {
×
6427
                    continue;
×
6428
                }
6429
                if (thumbnailer_supports_mime(&info, mime_type)) {
×
6430
                    match = 1;
×
6431
                }
6432
            }
6433
            closedir(dir);
×
6434
            dir = NULL;
×
6435
        }
6436
    }
6437

6438
    thumbnailer_entry_clear(&info);
5✔
6439
    thumbnailer_string_list_free(directories);
5✔
6440

6441
    if (directories_present && has_match != NULL) {
5!
6442
        *has_match = match;
5✔
6443
    }
1✔
6444
}
1✔
6445
#endif
6446

6447
static void
6448
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
5✔
6449
                          char const *filename)
6450
{
6451
    enum { description_length = 128 };
6452
    enum { uttype_length = 128 };
6453
    enum { extension_length = 32 };
6454
    enum { message_length = 768 };
6455
    char message[message_length];
6456
    char type_value[description_length];
6457
    char extension_text[extension_length + 2];
6458
    char uttype[uttype_length];
6459
    char desc_buffer[description_length];
6460
    char extension[extension_length];
6461
    char const *path;
6462
    char const *display_path;
6463
    char const *metadata_path;
6464
    char const *description_text;
6465
    char *mime_string;
6466
    char *description_string;
6467
    size_t offset;
6468
    int quicklook_available;
6469
    int quicklook_supported;
6470
    int gnome_available;
6471
    int gnome_has_dirs;
6472
    int gnome_has_match;
6473
    int suggestions;
6474

6475
    message[0] = '\0';
5✔
6476
    type_value[0] = '\0';
5✔
6477
    extension_text[0] = '\0';
5✔
6478
    uttype[0] = '\0';
5✔
6479
    desc_buffer[0] = '\0';
5✔
6480
    extension[0] = '\0';
5✔
6481
    path = NULL;
5✔
6482
    display_path = "(stdin)";
5✔
6483
    metadata_path = NULL;
5✔
6484
    description_text = NULL;
5✔
6485
    mime_string = NULL;
5✔
6486
    description_string = NULL;
5✔
6487
    offset = 0u;
5✔
6488
    quicklook_available = 0;
5✔
6489
    quicklook_supported = 0;
5✔
6490
    gnome_available = 0;
5✔
6491
    gnome_has_dirs = 0;
5✔
6492
    gnome_has_match = 0;
5✔
6493
    suggestions = 0;
5✔
6494

6495
    if (pchunk != NULL && pchunk->source_path != NULL) {
5!
6496
        path = pchunk->source_path;
×
6497
    } else if (filename != NULL) {
4!
6498
        path = filename;
×
6499
    }
6500

6501
    if (path != NULL && strcmp(path, "-") != 0) {
5!
6502
        display_path = path;
×
6503
    }
6504

6505
    if (path != NULL && strcmp(path, "-") != 0 &&
4!
6506
            strstr(path, "://") == NULL) {
×
6507
        metadata_path = path;
×
6508
    }
6509

6510
    loader_extract_extension(path, extension, sizeof(extension));
4✔
6511

6512
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6513
    if (metadata_path != NULL) {
4!
6514
        /*
6515
         * Collect MIME metadata via file(1) when fork() and friends are
6516
         * available.  Windows builds compiled with clang64 lack these
6517
         * interfaces, so the thumbnail helpers remain disabled there.
6518
         */
6519
        mime_string = thumbnailer_guess_content_type(metadata_path);
×
6520
        description_string = thumbnailer_run_file(metadata_path, NULL);
×
6521
    }
6522
#else
6523
    (void)metadata_path;
6524
#endif
6525

6526
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
6527
#if defined(__clang__)
6528
    /*
6529
     * Allow use of legacy UTType C APIs when compiling with the
6530
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
6531
     * so we must intentionally silence the deprecation warnings here.
6532
     */
6533
#pragma clang diagnostic push
6534
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
6535
#endif
6536
    {
6537
        CFStringRef uti_ref;
6538
        CFStringRef mime_ref;
6539
        CFStringRef ext_ref;
6540
        CFStringRef desc_ref;
6541
        CFStringRef preferred_mime;
6542
        char uti_local[uttype_length];
6543
        char desc_local[description_length];
6544
        char mime_local[64];
6545

6546
        uti_ref = NULL;
6547
        mime_ref = NULL;
6548
        ext_ref = NULL;
6549
        desc_ref = NULL;
6550
        preferred_mime = NULL;
6551
        uti_local[0] = '\0';
6552
        desc_local[0] = '\0';
6553
        mime_local[0] = '\0';
6554

6555
        if (mime_string != NULL) {
6556
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
6557
                                                 mime_string,
6558
                                                 kCFStringEncodingUTF8);
6559
        }
6560
        if (mime_ref != NULL) {
6561
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
6562
                kUTTagClassMIMEType,
6563
                mime_ref,
6564
                NULL);
6565
        }
6566
        if (uti_ref == NULL && extension[0] != '\0') {
1✔
6567
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
6568
                                                extension,
6569
                                                kCFStringEncodingUTF8);
6570
            if (ext_ref != NULL) {
6571
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
6572
                    kUTTagClassFilenameExtension,
6573
                    ext_ref,
6574
                    NULL);
6575
            }
6576
        }
6577
        if (uti_ref != NULL) {
6578
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
6579
            desc_ref = UTTypeCopyDescription(uti_ref);
6580
            if (desc_ref != NULL) {
6581
                loader_copy_cfstring(desc_ref,
6582
                                     desc_local,
6583
                                     sizeof(desc_local));
6584
                CFRelease(desc_ref);
6585
                desc_ref = NULL;
6586
            }
6587
            if (mime_string == NULL) {
6588
                preferred_mime = UTTypeCopyPreferredTagWithClass(
6589
                    uti_ref,
6590
                    kUTTagClassMIMEType);
6591
                if (preferred_mime != NULL) {
6592
                    loader_copy_cfstring(preferred_mime,
6593
                                         mime_local,
6594
                                         sizeof(mime_local));
6595
                    CFRelease(preferred_mime);
6596
                    preferred_mime = NULL;
6597
                }
6598
                if (mime_local[0] != '\0') {
6599
                    mime_string = thumbnailer_strdup(mime_local);
6600
                }
6601
            }
6602
        }
6603
        if (mime_ref != NULL) {
6604
            CFRelease(mime_ref);
6605
        }
6606
        if (ext_ref != NULL) {
6607
            CFRelease(ext_ref);
6608
        }
6609
        if (uti_ref != NULL) {
6610
            CFRelease(uti_ref);
6611
        }
6612
        if (uti_local[0] != '\0') {
6613
            sixel_compat_snprintf(uttype,
6614
                                  sizeof(uttype),
6615
                                  "%s",
6616
                                  uti_local);
6617
        }
6618
        if (desc_local[0] != '\0') {
6619
            sixel_compat_snprintf(desc_buffer,
6620
                                  sizeof(desc_buffer),
6621
                                  "%s",
6622
                                  desc_local);
6623
        }
6624
    }
6625
#if defined(__clang__)
6626
#pragma clang diagnostic pop
6627
#endif
6628
#endif
6629

6630
    if (description_string != NULL && description_string[0] != '\0') {
4!
6631
        description_text = description_string;
×
6632
    } else if (desc_buffer[0] != '\0') {
5!
6633
        description_text = desc_buffer;
×
6634
    } else {
6635
        description_text = "unknown content";
5✔
6636
    }
6637

6638
    sixel_compat_snprintf(type_value,
6✔
6639
                          sizeof(type_value),
6640
                          "%s",
6641
                          description_text);
1✔
6642

6643
    loader_append_chunk(message,
5✔
6644
                        sizeof(message),
6645
                        &offset,
6646
                        "diagnostic:\n");
6647
    loader_append_key_value(message,
6✔
6648
                            sizeof(message),
6649
                            &offset,
6650
                            "file",
6651
                            display_path);
1✔
6652
    loader_append_key_value(message,
6✔
6653
                            sizeof(message),
6654
                            &offset,
6655
                            "type",
6656
                            type_value);
1✔
6657

6658
    if (mime_string != NULL && mime_string[0] != '\0') {
5!
6659
        loader_append_key_value(message,
×
6660
                                sizeof(message),
6661
                                &offset,
6662
                                "mime",
6663
                                mime_string);
6664
    }
6665

6666
    if (uttype[0] != '\0') {
4!
6667
        loader_append_key_value(message,
×
6668
                                sizeof(message),
6669
                                &offset,
6670
                                "uti",
6671
                                uttype);
6672
    }
6673

6674
    if (extension[0] != '\0') {
4!
6675
        sixel_compat_snprintf(extension_text,
×
6676
                              sizeof(extension_text),
6677
                              ".%s",
6678
                              extension);
6679
        loader_append_key_value(message,
×
6680
                                sizeof(message),
6681
                                &offset,
6682
                                "extension",
6683
                                extension_text);
6684
    }
6685

6686
    loader_append_chunk(message,
5✔
6687
                        sizeof(message),
6688
                        &offset,
6689
                        "  suggestions:\n");
6690

6691
    quicklook_available = loader_entry_available("quicklook");
5✔
6692
    if (quicklook_available) {
5!
6693
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
1✔
6694
    }
1✔
6695
    if (quicklook_supported) {
6!
6696
        loader_append_chunk(message,
×
6697
                            sizeof(message),
6698
                            &offset,
6699
                            "    - QuickLook rendered a preview during "
6700
                            "the probe; try -j quicklook.\n");
6701
        suggestions += 1;
×
6702
    }
6703

6704
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6705
    gnome_available = loader_entry_available("gnome-thumbnailer");
5✔
6706
    if (gnome_available) {
5!
6707
        loader_probe_gnome_thumbnailers(mime_string,
5✔
6708
                                        &gnome_has_dirs,
6709
                                        &gnome_has_match);
6710
        if (gnome_has_dirs && gnome_has_match) {
5!
6711
            loader_append_chunk(message,
×
6712
                                sizeof(message),
6713
                                &offset,
6714
                                "    - GNOME thumbnailer definitions match "
6715
                                "this MIME type; try -j gnome-thumbnailer.\n"
6716
                                );
6717
            suggestions += 1;
×
6718
        }
6719
    }
1✔
6720
#else
6721
    (void)gnome_available;
6722
    (void)gnome_has_dirs;
6723
    (void)gnome_has_match;
6724
#endif
6725

6726
    if (suggestions == 0) {
5!
6727
        loader_append_chunk(message,
5✔
6728
                            sizeof(message),
6729
                            &offset,
6730
                            "    (no thumbnail helper hints)\n");
6731
    }
1✔
6732

6733
    if (suggestions > 0) {
5!
6734
        loader_append_chunk(message,
×
6735
                            sizeof(message),
6736
                            &offset,
6737
                            "  hint       : Enable one of the suggested "
6738
                            "loaders with -j.\n");
6739
    } else {
6740
        loader_append_chunk(message,
5✔
6741
                            sizeof(message),
6742
                            &offset,
6743
                            "  hint       : Convert the file to PNG or "
6744
                            "enable optional loaders.\n");
6745
    }
6746

6747
    sixel_helper_set_additional_message(message);
5✔
6748

6749
    free(mime_string);
5✔
6750
    free(description_string);
5✔
6751
}
5✔
6752

6753
SIXELAPI SIXELSTATUS
6754
sixel_loader_new(
761✔
6755
    sixel_loader_t   /* out */ **pploader,
6756
    sixel_allocator_t/* in */  *allocator)
6757
{
6758
    SIXELSTATUS status = SIXEL_FALSE;
761✔
6759
    sixel_loader_t *loader;
6760
    sixel_allocator_t *local_allocator;
6761

6762
    loader = NULL;
761✔
6763
    local_allocator = allocator;
761✔
6764

6765
    if (pploader == NULL) {
761!
6766
        sixel_helper_set_additional_message(
×
6767
            "sixel_loader_new: pploader is null.");
6768
        status = SIXEL_BAD_ARGUMENT;
×
6769
        goto end;
×
6770
    }
6771

6772
    if (local_allocator == NULL) {
761!
6773
        status = sixel_allocator_new(&local_allocator,
×
6774
                                     NULL,
6775
                                     NULL,
6776
                                     NULL,
6777
                                     NULL);
6778
        if (SIXEL_FAILED(status)) {
×
6779
            goto end;
×
6780
        }
6781
    } else {
6782
        sixel_allocator_ref(local_allocator);
761✔
6783
    }
6784

6785
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
761✔
6786
                                                      sizeof(*loader));
6787
    if (loader == NULL) {
761!
6788
        sixel_helper_set_additional_message(
×
6789
            "sixel_loader_new: sixel_allocator_malloc() failed.");
6790
        status = SIXEL_BAD_ALLOCATION;
×
6791
        sixel_allocator_unref(local_allocator);
×
6792
        goto end;
×
6793
    }
6794

6795
    loader->ref = 1;
761✔
6796
    loader->fstatic = 0;
761✔
6797
    loader->fuse_palette = 0;
761✔
6798
    loader->reqcolors = SIXEL_PALETTE_MAX;
761✔
6799
    loader->bgcolor[0] = 0;
761✔
6800
    loader->bgcolor[1] = 0;
761✔
6801
    loader->bgcolor[2] = 0;
761✔
6802
    loader->has_bgcolor = 0;
761✔
6803
    loader->loop_control = SIXEL_LOOP_AUTO;
761✔
6804
    loader->finsecure = 0;
761✔
6805
    loader->cancel_flag = NULL;
761✔
6806
    loader->context = NULL;
761✔
6807
    /*
6808
     * Initialize a private logger. The helper reuses an existing global
6809
     * logger sink when present so loader markers share the timeline with
6810
     * upstream stages without requiring sixel_loader_setopt().
6811
     */
6812
    sixel_logger_init(&loader->logger);
761✔
6813
    (void)sixel_logger_prepare_env(&loader->logger);
761✔
6814
    loader->assessment = NULL;
761✔
6815
    loader->loader_order = NULL;
761✔
6816
    loader->allocator = local_allocator;
761✔
6817
    loader->last_loader_name[0] = '\0';
761✔
6818
    loader->last_source_path[0] = '\0';
761✔
6819
    loader->last_input_bytes = 0u;
761✔
6820
    loader->callback_failed = 0;
761✔
6821
    loader->log_loader_finished = 0;
761✔
6822
    loader->log_path[0] = '\0';
761✔
6823
    loader->log_loader_name[0] = '\0';
761✔
6824
    loader->log_input_bytes = 0u;
761✔
6825

6826
    *pploader = loader;
761✔
6827
    status = SIXEL_OK;
761✔
6828

6829
end:
608✔
6830
    return status;
761✔
6831
}
6832

6833
SIXELAPI void
6834
sixel_loader_ref(
8,336✔
6835
    sixel_loader_t /* in */ *loader)
6836
{
6837
    if (loader == NULL) {
8,336!
6838
        return;
×
6839
    }
6840

6841
    ++loader->ref;
8,336✔
6842
}
1,676✔
6843

6844
SIXELAPI void
6845
sixel_loader_unref(
9,097✔
6846
    sixel_loader_t /* in */ *loader)
6847
{
6848
    sixel_allocator_t *allocator;
6849

6850
    if (loader == NULL) {
9,097!
6851
        return;
×
6852
    }
6853

6854
    if (--loader->ref == 0) {
9,097✔
6855
        allocator = loader->allocator;
761✔
6856
        sixel_logger_close(&loader->logger);
761✔
6857
        sixel_allocator_free(allocator, loader->loader_order);
761✔
6858
        sixel_allocator_free(allocator, loader);
761✔
6859
        sixel_allocator_unref(allocator);
761✔
6860
    }
153✔
6861
}
1,829✔
6862

6863
SIXELAPI SIXELSTATUS
6864
sixel_loader_setopt(
7,575✔
6865
    sixel_loader_t /* in */ *loader,
6866
    int            /* in */ option,
6867
    void const     /* in */ *value)
6868
{
6869
    SIXELSTATUS status = SIXEL_FALSE;
7,575✔
6870
    int const *flag;
6871
    unsigned char const *color;
6872
    char const *order;
6873
    char *copy;
6874
    sixel_allocator_t *allocator;
6875

6876
    flag = NULL;
7,575✔
6877
    color = NULL;
7,575✔
6878
    order = NULL;
7,575✔
6879
    copy = NULL;
7,575✔
6880
    allocator = NULL;
7,575✔
6881

6882
    if (loader == NULL) {
7,575!
6883
        sixel_helper_set_additional_message(
×
6884
            "sixel_loader_setopt: loader is null.");
6885
        status = SIXEL_BAD_ARGUMENT;
×
6886
        goto end0;
×
6887
    }
6888

6889
    sixel_loader_ref(loader);
7,575✔
6890

6891
    switch (option) {
7,575!
6892
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
608✔
6893
        flag = (int const *)value;
761✔
6894
        loader->fstatic = flag != NULL ? *flag : 0;
761!
6895
        status = SIXEL_OK;
761✔
6896
        break;
761✔
6897
    case SIXEL_LOADER_OPTION_USE_PALETTE:
608✔
6898
        flag = (int const *)value;
761✔
6899
        loader->fuse_palette = flag != NULL ? *flag : 0;
761!
6900
        status = SIXEL_OK;
761✔
6901
        break;
761✔
6902
    case SIXEL_LOADER_OPTION_REQCOLORS:
608✔
6903
        flag = (int const *)value;
761✔
6904
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
761!
6905
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
761!
6906
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
6907
        }
6908
        status = SIXEL_OK;
761✔
6909
        break;
761✔
6910
    case SIXEL_LOADER_OPTION_BGCOLOR:
608✔
6911
        if (value == NULL) {
761✔
6912
            loader->has_bgcolor = 0;
731✔
6913
        } else {
147✔
6914
            color = (unsigned char const *)value;
30✔
6915
            loader->bgcolor[0] = color[0];
30✔
6916
            loader->bgcolor[1] = color[1];
30✔
6917
            loader->bgcolor[2] = color[2];
30✔
6918
            loader->has_bgcolor = 1;
30✔
6919
        }
6920
        status = SIXEL_OK;
761✔
6921
        break;
761✔
6922
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
608✔
6923
        flag = (int const *)value;
761✔
6924
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
761!
6925
        status = SIXEL_OK;
761✔
6926
        break;
761✔
6927
    case SIXEL_LOADER_OPTION_INSECURE:
608✔
6928
        flag = (int const *)value;
761✔
6929
        loader->finsecure = flag != NULL ? *flag : 0;
761!
6930
        status = SIXEL_OK;
761✔
6931
        break;
761✔
6932
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
608✔
6933
        loader->cancel_flag = (int const *)value;
761✔
6934
        status = SIXEL_OK;
761✔
6935
        break;
761✔
6936
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
608✔
6937
        allocator = loader->allocator;
761✔
6938
        sixel_allocator_free(allocator, loader->loader_order);
761✔
6939
        loader->loader_order = NULL;
761✔
6940
        if (value != NULL) {
761!
6941
            order = (char const *)value;
×
6942
            copy = loader_strdup(order, allocator);
×
6943
            if (copy == NULL) {
×
6944
                sixel_helper_set_additional_message(
×
6945
                    "sixel_loader_setopt: loader_strdup() failed.");
6946
                status = SIXEL_BAD_ALLOCATION;
×
6947
                goto end;
×
6948
            }
6949
            loader->loader_order = copy;
×
6950
        }
6951
        status = SIXEL_OK;
761✔
6952
        break;
761✔
6953
    case SIXEL_LOADER_OPTION_CONTEXT:
608✔
6954
        loader->context = (void *)value;
761✔
6955
        loader->assessment = NULL;
761✔
6956
        status = SIXEL_OK;
761✔
6957
        break;
761✔
6958
    case SIXEL_LOADER_OPTION_ASSESSMENT:
580✔
6959
        loader->assessment = (sixel_assessment_t *)value;
726✔
6960
        status = SIXEL_OK;
726✔
6961
        break;
726✔
6962
    default:
6963
        sixel_helper_set_additional_message(
×
6964
            "sixel_loader_setopt: unknown option.");
6965
        status = SIXEL_BAD_ARGUMENT;
×
6966
        goto end;
×
6967
    }
1,523✔
6968

6969
end:
6,052✔
6970
    sixel_loader_unref(loader);
7,575✔
6971

6972
end0:
6,052✔
6973
    return status;
7,575✔
6974
}
6975

6976
SIXELAPI char const *
6977
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
1,402✔
6978
{
6979
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
1,402!
6980
        return NULL;
×
6981
    }
6982
    return loader->last_loader_name;
1,402✔
6983
}
282✔
6984

6985
SIXELAPI char const *
6986
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
1,172✔
6987
{
6988
    if (loader == NULL || loader->last_source_path[0] == '\0') {
1,172!
6989
        return NULL;
230✔
6990
    }
6991
    return loader->last_source_path;
942✔
6992
}
236✔
6993

6994
SIXELAPI size_t
6995
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
701✔
6996
{
6997
    if (loader == NULL) {
701!
6998
        return 0u;
×
6999
    }
7000
    return loader->last_input_bytes;
701✔
7001
}
141✔
7002

7003
SIXELAPI SIXELSTATUS
7004
sixel_loader_load_file(
761✔
7005
    sixel_loader_t         /* in */ *loader,
7006
    char const             /* in */ *filename,
7007
    sixel_load_image_function /* in */ fn_load)
7008
{
7009
    SIXELSTATUS status = SIXEL_FALSE;
761✔
7010
    sixel_chunk_t *pchunk;
7011
    sixel_loader_entry_t const *plan[
7012
        sizeof(sixel_loader_entries) / sizeof(sixel_loader_entries[0])];
7013
    size_t entry_count;
7014
    size_t plan_length;
7015
    size_t plan_index;
7016
    unsigned char *bgcolor;
7017
    int reqcolors;
7018
    sixel_assessment_t *assessment;
7019
    char const *order_override;
7020
    char const *env_order;
7021
    sixel_loader_callback_state_t callback_state;
7022

7023
    pchunk = NULL;
761✔
7024
    entry_count = 0;
761✔
7025
    plan_length = 0;
761✔
7026
    plan_index = 0;
761✔
7027
    bgcolor = NULL;
761✔
7028
    reqcolors = 0;
761✔
7029
    assessment = NULL;
761✔
7030
    order_override = NULL;
761✔
7031
    env_order = NULL;
761✔
7032

7033
    if (loader == NULL) {
761!
7034
        sixel_helper_set_additional_message(
×
7035
            "sixel_loader_load_file: loader is null.");
7036
        status = SIXEL_BAD_ARGUMENT;
×
7037
        goto end0;
×
7038
    }
7039

7040
    sixel_loader_ref(loader);
761✔
7041

7042
    loader->log_loader_finished = 0;
761✔
7043
    loader->log_loader_name[0] = '\0';
761✔
7044
    loader->log_input_bytes = 0u;
761✔
7045
    loader->log_path[0] = '\0';
761✔
7046
    if (filename != NULL) {
761✔
7047
        (void)snprintf(loader->log_path,
526✔
7048
                       sizeof(loader->log_path),
7049
                       "%s",
7050
                       filename);
7051
    }
106✔
7052
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
820✔
7053

7054
    memset(&callback_state, 0, sizeof(callback_state));
820✔
7055
    callback_state.loader = loader;
820✔
7056
    callback_state.fn = fn_load;
820✔
7057
    callback_state.context = loader->context;
820✔
7058
    loader->callback_failed = 0;
820✔
7059

7060
    entry_count = sizeof(sixel_loader_entries) /
820✔
7061
                  sizeof(sixel_loader_entries[0]);
7062

7063
    reqcolors = loader->reqcolors;
820✔
7064
    if (reqcolors > SIXEL_PALETTE_MAX) {
820!
7065
        reqcolors = SIXEL_PALETTE_MAX;
×
7066
    }
7067

7068
    assessment = loader->assessment;
761✔
7069

7070
    /*
7071
     *  Assessment pipeline sketch:
7072
     *
7073
     *      +-------------+        +--------------+
7074
     *      | chunk read  | -----> | image decode |
7075
     *      +-------------+        +--------------+
7076
     *
7077
     *  The loader owns the hand-off.  Chunk I/O ends before any decoder runs,
7078
     *  so we time the read span in the encoder and pivot to decode once the
7079
     *  chunk is populated.
7080
     */
7081
    status = sixel_chunk_new(&pchunk,
761✔
7082
                             filename,
153✔
7083
                             loader->finsecure,
153✔
7084
                             loader->cancel_flag,
153✔
7085
                             loader->allocator);
153✔
7086
    if (status != SIXEL_OK) {
761✔
7087
        goto end;
10✔
7088
    }
7089

7090
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
751!
7091
        status = SIXEL_OK;
×
7092
        goto end;
×
7093
    }
7094

7095
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
704!
7096
        (void)snprintf(loader->log_path,
516✔
7097
                       sizeof(loader->log_path),
7098
                       "%s",
7099
                       pchunk->source_path);
412✔
7100
    }
104✔
7101

7102
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
751!
7103
        status = SIXEL_LOGIC_ERROR;
×
7104
        goto end;
×
7105
    }
7106

7107
    if (loader->has_bgcolor) {
751✔
7108
        bgcolor = loader->bgcolor;
30✔
7109
    }
6✔
7110

7111
    status = SIXEL_FALSE;
611✔
7112
    if (assessment != NULL) {
611✔
7113
        sixel_assessment_stage_transition(
5✔
7114
            assessment,
1✔
7115
            SIXEL_ASSESSMENT_STAGE_IMAGE_DECODE);
7116
    }
1✔
7117
    order_override = loader->loader_order;
751✔
7118
    /*
7119
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
7120
     * a loader order via -j/--loaders or sixel_loader_setopt().
7121
     */
7122
    if (order_override == NULL) {
751!
7123
        env_order = getenv("SIXEL_LOADER_PRIORITY_LIST");
751✔
7124
        if (env_order != NULL && env_order[0] != '\0') {
751!
7125
            order_override = env_order;
×
7126
        }
7127
    }
151✔
7128

7129
    plan_length = loader_build_plan(order_override,
902✔
7130
                                    sixel_loader_entries,
7131
                                    entry_count,
151✔
7132
                                    plan,
151✔
7133
                                    entry_count);
151✔
7134

7135
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
769✔
7136
        if (plan[plan_index] == NULL) {
754!
7137
            continue;
×
7138
        }
7139
        if (plan[plan_index]->predicate != NULL &&
754!
7140
            plan[plan_index]->predicate(pchunk) == 0) {
×
7141
            continue;
×
7142
        }
7143
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
754!
7144
        if (plan[plan_index]->name != NULL) {
754!
7145
            (void)snprintf(loader->log_loader_name,
754✔
7146
                           sizeof(loader->log_loader_name),
7147
                           "%s",
7148
                           plan[plan_index]->name);
600✔
7149
        } else {
154✔
7150
            loader->log_loader_name[0] = '\0';
×
7151
        }
7152
        loader_trace_try(plan[plan_index]->name);
754✔
7153
        status = plan[plan_index]->backend(pchunk,
908✔
7154
                                           loader->fstatic,
154✔
7155
                                           loader->fuse_palette,
154✔
7156
                                           reqcolors,
154✔
7157
                                           bgcolor,
154✔
7158
                                           loader->loop_control,
154✔
7159
                                           loader_callback_trampoline,
7160
                                           &callback_state);
7161
        loader_trace_result(plan[plan_index]->name, status);
754✔
7162
        if (SIXEL_SUCCEEDED(status)) {
754✔
7163
            break;
736✔
7164
        }
7165
    }
6✔
7166

7167
    if (SIXEL_FAILED(status)) {
751✔
7168
        if (!loader->callback_failed &&
16!
7169
                plan_length > 0u &&
5!
7170
                plan_index >= plan_length &&
5✔
7171
                pchunk != NULL) {
5!
7172
            status = SIXEL_LOADER_FAILED;
5✔
7173
            loader_publish_diagnostic(pchunk, filename);
5✔
7174
        }
1✔
7175
        goto end;
15✔
7176
    }
7177

7178
    if (plan_index < plan_length &&
884!
7179
            plan[plan_index] != NULL &&
736!
7180
            plan[plan_index]->name != NULL) {
736!
7181
        (void)snprintf(loader->last_loader_name,
736✔
7182
                       sizeof(loader->last_loader_name),
7183
                       "%s",
7184
                       plan[plan_index]->name);
588✔
7185
    } else {
148✔
7186
        loader->last_loader_name[0] = '\0';
×
7187
    }
7188
    loader->last_input_bytes = pchunk->size;
736✔
7189
    if (pchunk->source_path != NULL) {
884✔
7190
        size_t path_len;
7191

7192
        path_len = strlen(pchunk->source_path);
506✔
7193
        if (path_len >= sizeof(loader->last_source_path)) {
506!
7194
            path_len = sizeof(loader->last_source_path) - 1u;
×
7195
        }
7196
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
506✔
7197
        loader->last_source_path[path_len] = '\0';
506✔
7198
    } else {
102✔
7199
        loader->last_source_path[0] = '\0';
230✔
7200
    }
7201

7202
end:
608✔
7203
    sixel_chunk_destroy(pchunk);
761✔
7204
    sixel_loader_unref(loader);
761✔
7205

7206
end0:
608✔
7207
    return status;
761✔
7208
}
7209

7210
/* load image from file */
7211

7212
SIXELAPI SIXELSTATUS
7213
sixel_helper_load_image_file(
×
7214
    char const                /* in */     *filename,     /* source file name */
7215
    int                       /* in */     fstatic,       /* whether to */
7216
                                                             /* extract a */
7217
                                                             /* static image */
7218
                                                             /* from an */
7219
                                                             /* animated gif */
7220
    int                       /* in */     fuse_palette,  /* whether to */
7221
                                                             /* use a */
7222
                                                             /* paletted */
7223
                                                             /* image; set */
7224
                                                             /* non-zero to */
7225
                                                             /* request one */
7226
    int                       /* in */     reqcolors,     /* requested */
7227
                                                             /* number of */
7228
                                                             /* colors; */
7229
                                                             /* should be */
7230
                                                             /* equal to or */
7231
                                                             /* less than */
7232
                                                             /* SIXEL_ */
7233
                                                             /* PALETTE_ */
7234
                                                             /* MAX */
7235
    unsigned char             /* in */     *bgcolor,      /* background */
7236
                                                             /* color, may */
7237
                                                             /* be NULL */
7238
    int                       /* in */     loop_control,  /* one of enum */
7239
                                                             /* loopControl */
7240
    sixel_load_image_function /* in */     fn_load,       /* callback */
7241
    int                       /* in */     finsecure,     /* true if do */
7242
                                                             /* not verify */
7243
                                                             /* SSL */
7244
    int const                 /* in */     *cancel_flag,  /* cancel flag, */
7245
                                                             /* may be */
7246
                                                             /* NULL */
7247
    void                      /* in/out */ *context,      /* private data */
7248
                                                             /* passed to */
7249
                                                             /* callback */
7250
                                                             /* function, */
7251
                                                             /* may be */
7252
                                                             /* NULL */
7253
    sixel_allocator_t         /* in */     *allocator     /* allocator */
7254
                                                             /* object, */
7255
                                                             /* may be */
7256
                                                             /* NULL */
7257
)
7258
{
7259
    SIXELSTATUS status = SIXEL_FALSE;
×
7260
    sixel_loader_t *loader;
7261

7262
    loader = NULL;
×
7263

7264
    status = sixel_loader_new(&loader, allocator);
×
7265
    if (SIXEL_FAILED(status)) {
×
7266
        goto end;
×
7267
    }
7268

7269
    status = sixel_loader_setopt(loader,
×
7270
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
7271
                                 &fstatic);
7272
    if (SIXEL_FAILED(status)) {
×
7273
        goto end;
×
7274
    }
7275

7276
    status = sixel_loader_setopt(loader,
×
7277
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
7278
                                 &fuse_palette);
7279
    if (SIXEL_FAILED(status)) {
×
7280
        goto end;
×
7281
    }
7282

7283
    status = sixel_loader_setopt(loader,
×
7284
                                 SIXEL_LOADER_OPTION_REQCOLORS,
7285
                                 &reqcolors);
7286
    if (SIXEL_FAILED(status)) {
×
7287
        goto end;
×
7288
    }
7289

7290
    status = sixel_loader_setopt(loader,
×
7291
                                 SIXEL_LOADER_OPTION_BGCOLOR,
7292
                                 bgcolor);
7293
    if (SIXEL_FAILED(status)) {
×
7294
        goto end;
×
7295
    }
7296

7297
    status = sixel_loader_setopt(loader,
×
7298
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
7299
                                 &loop_control);
7300
    if (SIXEL_FAILED(status)) {
×
7301
        goto end;
×
7302
    }
7303

7304
    status = sixel_loader_setopt(loader,
×
7305
                                 SIXEL_LOADER_OPTION_INSECURE,
7306
                                 &finsecure);
7307
    if (SIXEL_FAILED(status)) {
×
7308
        goto end;
×
7309
    }
7310

7311
    status = sixel_loader_setopt(loader,
×
7312
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
7313
                                 cancel_flag);
7314
    if (SIXEL_FAILED(status)) {
×
7315
        goto end;
×
7316
    }
7317

7318
    status = sixel_loader_setopt(loader,
×
7319
                                 SIXEL_LOADER_OPTION_CONTEXT,
7320
                                 context);
7321
    if (SIXEL_FAILED(status)) {
×
7322
        goto end;
×
7323
    }
7324

7325
    status = sixel_loader_load_file(loader, filename, fn_load);
×
7326

7327
end:
7328
    sixel_loader_unref(loader);
×
7329

7330
    return status;
×
7331
}
7332

7333

7334
SIXELAPI size_t
7335
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
10✔
7336
{
7337
    size_t entry_count;
7338
    size_t limit;
7339
    size_t index;
7340

7341
    entry_count = sizeof(sixel_loader_entries) /
10✔
7342
                  sizeof(sixel_loader_entries[0]);
7343

7344
    if (names != NULL && max_names > 0) {
10!
7345
        limit = entry_count;
5✔
7346
        if (limit > max_names) {
5!
7347
            limit = max_names;
×
7348
        }
7349
        for (index = 0; index < limit; ++index) {
17✔
7350
            names[index] = sixel_loader_entries[index].name;
12✔
7351
        }
4✔
7352
    }
1✔
7353

7354
    return entry_count;
10✔
7355
}
7356

7357
#if HAVE_TESTS
7358
static int
7359
test1(void)
×
7360
{
7361
    int nret = EXIT_FAILURE;
×
7362
    unsigned char *ptr = malloc(16);
×
7363

7364
    nret = EXIT_SUCCESS;
×
7365
    goto error;
×
7366

7367
    nret = EXIT_SUCCESS;
7368

7369
error:
7370
    free(ptr);
×
7371
    return nret;
×
7372
}
7373

7374

7375
SIXELAPI int
7376
sixel_loader_tests_main(void)
×
7377
{
7378
    int nret = EXIT_FAILURE;
×
7379
    size_t i;
7380
    typedef int (* testcase)(void);
7381

7382
    static testcase const testcases[] = {
7383
        test1,
7384
    };
7385

7386
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
7387
        nret = testcases[i]();
×
7388
        if (nret != EXIT_SUCCESS) {
×
7389
            goto error;
×
7390
        }
7391
    }
7392

7393
    nret = EXIT_SUCCESS;
×
7394

7395
error:
7396
    return nret;
×
7397
}
7398
#endif  /* HAVE_TESTS */
7399

7400
/* emacs Local Variables:      */
7401
/* emacs mode: c               */
7402
/* emacs tab-width: 4          */
7403
/* emacs indent-tabs-mode: nil */
7404
/* emacs c-basic-offset: 4     */
7405
/* emacs End:                  */
7406
/* vim: set expandtab ts=4 sts=4 sw=4 : */
7407
/* 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