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

saitoha / libsixel / 19390336138

15 Nov 2025 01:07PM UTC coverage: 43.379% (+0.09%) from 43.289%
19390336138

push

github

saitoha
tests: redirect invalid option checks through /dev/null

8475 of 27744 branches covered (30.55%)

11581 of 26697 relevant lines covered (43.38%)

973308.93 hits per line

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

43.16
/src/loader.c
1
/*
2
 * Copyright (c) 2021-2025 libsixel developers. See `AUTHORS`.
3
 * Copyright (c) 2014-2019 Hayaki Saito
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
6
 * this software and associated documentation files (the "Software"), to deal in
7
 * the Software without restriction, including without limitation the rights to
8
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
 * the Software, and to permit persons to whom the Software is furnished to do so,
10
 * subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included in all
13
 * copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
 */
22

23
#include "config.h"
24
#if !defined(_POSIX_C_SOURCE)
25
# define _POSIX_C_SOURCE 200809L
26
#endif
27

28
/* STDC_HEADERS */
29
#include <stdio.h>
30
#include <stdlib.h>
31

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

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

106
#if HAVE_QUICKLOOK_THUMBNAILING
107
CGImageRef
108
sixel_quicklook_thumbnail_create(CFURLRef url, CGSize max_size);
109
#endif
110

111
#if !defined(HAVE_MEMCPY)
112
# define memcpy(d, s, n) (bcopy ((s), (d), (n)))
113
#endif
114

115
#include <sixel.h>
116
#include "loader.h"
117
#include "compat_stub.h"
118
#include "frame.h"
119
#include "chunk.h"
120
#include "frompnm.h"
121
#include "fromgif.h"
122
#include "allocator.h"
123
#include "assessment.h"
124
#include "encoder.h"
125

126
#define SIXEL_THUMBNAILER_DEFAULT_SIZE 512
127

128
static int loader_trace_enabled;
129
static int thumbnailer_default_size_hint =
130
    SIXEL_THUMBNAILER_DEFAULT_SIZE;
131
static int thumbnailer_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
132
static int thumbnailer_size_hint_initialized;
133

134
#if HAVE_POSIX_SPAWNP
135
extern char **environ;
136
#endif
137

138
struct sixel_loader {
139
    int ref;
140
    int fstatic;
141
    int fuse_palette;
142
    int reqcolors;
143
    unsigned char bgcolor[3];
144
    int has_bgcolor;
145
    int loop_control;
146
    int finsecure;
147
    int const *cancel_flag;
148
    void *context;
149
    /*
150
     * Pointer to the active assessment observer.
151
     *
152
     * Loader clients opt in by setting SIXEL_LOADER_OPTION_ASSESSMENT.
153
     * Context slots remain usable for arbitrary callback state without
154
     * tripping the observer wiring.
155
     */
156
    sixel_assessment_t *assessment;
157
    char *loader_order;
158
    sixel_allocator_t *allocator;
159
    char last_loader_name[64];
160
    char last_source_path[PATH_MAX];
161
    size_t last_input_bytes;
162
};
163

164
static char *
165
loader_strdup(char const *text, sixel_allocator_t *allocator)
×
166
{
167
    char *copy;
168
    size_t length;
169

170
    if (text == NULL) {
×
171
        return NULL;
×
172
    }
173

174
    length = strlen(text) + 1;
×
175
    copy = (char *)sixel_allocator_malloc(allocator, length);
×
176
    if (copy == NULL) {
×
177
        return NULL;
×
178
    }
179

180
#if HAVE_STRCPY_S
181
    (void)strcpy_s(copy, (rsize_t)(length - 1), text);
182
#else
183
    memcpy(copy, text, length);
×
184
#endif
185

186
    return copy;
×
187
}
188

189
/*
190
 * loader_thumbnailer_initialize_size_hint
191
 *
192
 * Establish the runtime default thumbnail size hint.  The helper inspects
193
 * $SIXEL_THUMBNAILER_HINT_SIZE once so administrators can override
194
 * SIXEL_THUMBNAILER_DEFAULT_SIZE without recompiling.  Subsequent calls
195
 * become no-ops to avoid clobbering adjustments made through
196
 * sixel_helper_set_thumbnail_size_hint().
197
 */
198
static void
199
loader_thumbnailer_initialize_size_hint(void)
460✔
200
{
201
    char const *env_value;
202
    char *endptr;
203
    long parsed;
204

205
    if (thumbnailer_size_hint_initialized) {
460✔
206
        return;
314✔
207
    }
208

209
    thumbnailer_size_hint_initialized = 1;
436✔
210
    thumbnailer_default_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
436✔
211
    thumbnailer_size_hint = thumbnailer_default_size_hint;
436✔
212

213
    env_value = getenv("SIXEL_THUMBNAILER_HINT_SIZE");
436✔
214
    if (env_value == NULL || env_value[0] == '\0') {
436!
215
        return;
436✔
216
    }
217

218
    errno = 0;
×
219
    parsed = strtol(env_value, &endptr, 10);
×
220
    if (errno != 0) {
×
221
        return;
×
222
    }
223
    if (endptr == env_value || *endptr != '\0') {
×
224
        return;
×
225
    }
226
    if (parsed <= 0) {
×
227
        return;
×
228
    }
229
    if (parsed > (long)INT_MAX) {
×
230
        parsed = (long)INT_MAX;
×
231
    }
232

233
    thumbnailer_default_size_hint = (int)parsed;
×
234
    thumbnailer_size_hint = thumbnailer_default_size_hint;
×
235
}
156✔
236

237
/*
238
 * sixel_helper_set_loader_trace
239
 *
240
 * Toggle verbose loader tracing so debugging output can be collected.
241
 *
242
 * Arguments:
243
 *     enable - non-zero enables tracing, zero disables it.
244
 */
245
void
246
sixel_helper_set_loader_trace(int enable)
466✔
247
{
248
    loader_trace_enabled = enable ? 1 : 0;
466✔
249
}
466✔
250

251
/*
252
 * sixel_helper_set_thumbnail_size_hint
253
 *
254
 * Record the caller's preferred maximum thumbnail dimension.
255
 *
256
 * Arguments:
257
 *     size - requested dimension in pixels; non-positive resets to default.
258
 */
259
void
260
sixel_helper_set_thumbnail_size_hint(int size)
457✔
261
{
262
    loader_thumbnailer_initialize_size_hint();
457✔
263

264
    if (size > 0) {
457✔
265
        thumbnailer_size_hint = size;
93✔
266
    } else {
31✔
267
        thumbnailer_size_hint = thumbnailer_default_size_hint;
364✔
268
    }
269
}
457✔
270

271
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
272
/*
273
 * loader_trace_message
274
 *
275
 * Emit a formatted trace message when verbose loader tracing is enabled.
276
 *
277
 * Arguments:
278
 *     format - printf-style message template.
279
 *     ...    - arguments consumed according to the format string.
280
 */
281
static void
282
loader_trace_message(char const *format, ...)
45✔
283
{
284
    va_list args;
285

286
    if (!loader_trace_enabled) {
45!
287
        return;
45✔
288
    }
289

290
    fprintf(stderr, "libsixel: ");
×
291

292
    va_start(args, format);
×
293
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
294
# if defined(__clang__)
295
#  pragma clang diagnostic push
296
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
297
# elif defined(__GNUC__)
298
#  pragma GCC diagnostic push
299
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
300
# endif
301
#endif
302
    vfprintf(stderr, format, args);
×
303
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
304
# if defined(__clang__)
305
#  pragma clang diagnostic pop
306
# elif defined(__GNUC__)
307
#  pragma GCC diagnostic pop
308
# endif
309
#endif
310
    va_end(args);
×
311

312
    fprintf(stderr, "\n");
×
313
}
15✔
314
#endif  /* HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK */
315

316
static void
317
loader_trace_try(char const *name)
454✔
318
{
319
    if (loader_trace_enabled) {
454✔
320
        fprintf(stderr, "libsixel: trying %s loader\n", name);
9✔
321
    }
3✔
322
}
454✔
323

324
static void
325
loader_trace_result(char const *name, SIXELSTATUS status)
454✔
326
{
327
    if (!loader_trace_enabled) {
454✔
328
        return;
445✔
329
    }
330
    if (SIXEL_SUCCEEDED(status)) {
9!
331
        fprintf(stderr, "libsixel: loader %s succeeded\n", name);
9✔
332
    } else {
3✔
333
        fprintf(stderr, "libsixel: loader %s failed (%s)\n",
×
334
                name, sixel_helper_format_error(status));
335
    }
336
}
154✔
337

338
typedef SIXELSTATUS (*sixel_loader_backend)(
339
    sixel_chunk_t const       *pchunk,
340
    int                        fstatic,
341
    int                        fuse_palette,
342
    int                        reqcolors,
343
    unsigned char             *bgcolor,
344
    int                        loop_control,
345
    sixel_load_image_function  fn_load,
346
    void                      *context);
347

348
typedef int (*sixel_loader_predicate)(sixel_chunk_t const *pchunk);
349

350
typedef struct sixel_loader_entry {
351
    char const              *name;
352
    sixel_loader_backend     backend;
353
    sixel_loader_predicate   predicate;
354
    int                      default_enabled;
355
} sixel_loader_entry_t;
356

357
static int
358
loader_plan_contains(sixel_loader_entry_t const **plan,
602✔
359
                     size_t plan_length,
360
                     sixel_loader_entry_t const *entry)
361
{
362
    size_t index;
363

364
    for (index = 0; index < plan_length; ++index) {
753!
365
        if (plan[index] == entry) {
151!
366
            return 1;
×
367
        }
368
    }
151✔
369

370
    return 0;
602✔
371
}
302✔
372

373
static int
374
loader_token_matches(char const *token,
×
375
                     size_t token_length,
376
                     char const *name)
377
{
378
    size_t index;
379
    unsigned char left;
380
    unsigned char right;
381

382
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
×
383
        left = (unsigned char)token[index];
×
384
        right = (unsigned char)name[index];
×
385
        if (tolower(left) != tolower(right)) {
×
386
            return 0;
×
387
        }
388
    }
389

390
    if (index != token_length || name[index] != '\0') {
×
391
        return 0;
×
392
    }
393

394
    return 1;
×
395
}
396

397
static sixel_loader_entry_t const *
398
loader_lookup_token(char const *token,
×
399
                    size_t token_length,
400
                    sixel_loader_entry_t const *entries,
401
                    size_t entry_count)
402
{
403
    size_t index;
404

405
    for (index = 0; index < entry_count; ++index) {
×
406
        if (loader_token_matches(token,
×
407
                                 token_length,
408
                                 entries[index].name)) {
×
409
            return &entries[index];
×
410
        }
411
    }
412

413
    return NULL;
×
414
}
415

416
/*
417
 * loader_build_plan
418
 *
419
 * Translate a comma separated list into an execution plan that reorders the
420
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
421
 * are ignored so that new builds remain forward compatible.
422
 *
423
 *    user input "gd,coregraphics"
424
 *                |
425
 *                v
426
 *        +-------------------+
427
 *        | prioritized list  |
428
 *        +-------------------+
429
 *                |
430
 *                v
431
 *        +-------------------+
432
 *        | default fallbacks |
433
 *        +-------------------+
434
 */
435
static size_t
436
loader_build_plan(char const *order,
451✔
437
                  sixel_loader_entry_t const *entries,
438
                  size_t entry_count,
439
                  sixel_loader_entry_t const **plan,
440
                  size_t plan_capacity)
441
{
442
    size_t plan_length;
443
    size_t index;
444
    char const *cursor;
445
    char const *token_start;
446
    char const *token_end;
447
    size_t token_length;
448
    sixel_loader_entry_t const *entry;
449
    size_t limit;
450

451
    plan_length = 0;
451✔
452
    index = 0;
451✔
453
    cursor = order;
451✔
454
    token_start = order;
451✔
455
    token_end = order;
451✔
456
    token_length = 0;
451✔
457
    entry = NULL;
451✔
458
    limit = plan_capacity;
451✔
459

460
    if (order != NULL && plan != NULL && plan_capacity > 0) {
451!
461
        token_start = order;
×
462
        cursor = order;
×
463
        while (*cursor != '\0') {
×
464
            if (*cursor == ',') {
×
465
                token_end = cursor;
×
466
                while (token_start < token_end &&
×
467
                       isspace((unsigned char)*token_start)) {
×
468
                    ++token_start;
×
469
                }
470
                while (token_end > token_start &&
×
471
                       isspace((unsigned char)token_end[-1])) {
×
472
                    --token_end;
×
473
                }
474
                token_length = (size_t)(token_end - token_start);
×
475
                if (token_length > 0) {
×
476
                    entry = loader_lookup_token(token_start,
×
477
                                                token_length,
478
                                                entries,
479
                                                entry_count);
480
                    if (entry != NULL &&
×
481
                        !loader_plan_contains(plan,
×
482
                                              plan_length,
483
                                              entry) &&
×
484
                        plan_length < limit) {
485
                        plan[plan_length] = entry;
×
486
                        ++plan_length;
×
487
                    }
488
                }
489
                token_start = cursor + 1;
×
490
            }
491
            ++cursor;
×
492
        }
493

494
        token_end = cursor;
×
495
        while (token_start < token_end &&
×
496
               isspace((unsigned char)*token_start)) {
×
497
            ++token_start;
×
498
        }
499
        while (token_end > token_start &&
×
500
               isspace((unsigned char)token_end[-1])) {
×
501
            --token_end;
×
502
        }
503
        token_length = (size_t)(token_end - token_start);
×
504
        if (token_length > 0) {
×
505
            entry = loader_lookup_token(token_start,
×
506
                                        token_length,
507
                                        entries,
508
                                        entry_count);
509
            if (entry != NULL &&
×
510
                !loader_plan_contains(plan, plan_length, entry) &&
×
511
                plan_length < limit) {
512
                plan[plan_length] = entry;
×
513
                ++plan_length;
×
514
            }
515
        }
516
    }
517

518
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
1,655!
519
        entry = &entries[index];
1,204✔
520
        if (!entry->default_enabled) {
1,204✔
521
            continue;
602✔
522
        }
523
        if (!loader_plan_contains(plan, plan_length, entry)) {
602!
524
            plan[plan_length] = entry;
602✔
525
            ++plan_length;
602✔
526
        }
302✔
527
    }
302✔
528

529
    return plan_length;
451✔
530
}
531

532
static void
533
loader_append_chunk(char *dest,
62✔
534
                    size_t capacity,
535
                    size_t *offset,
536
                    char const *chunk)
537
{
538
    size_t available;
539
    size_t length;
540

541
    if (dest == NULL || offset == NULL || chunk == NULL) {
62!
542
        return;
×
543
    }
544

545
    if (*offset >= capacity) {
62!
546
        return;
×
547
    }
548

549
    available = capacity - *offset;
62✔
550
    if (available == 0) {
62!
551
        return;
×
552
    }
553

554
    length = strlen(chunk);
62✔
555
    if (length >= available) {
62!
556
        if (available == 0) {
×
557
            return;
×
558
        }
559
        length = available - 1u;
×
560
    }
561

562
    if (length > 0) {
62!
563
        memcpy(dest + *offset, chunk, length);
62✔
564
        *offset += length;
62✔
565
    }
22✔
566

567
    if (*offset < capacity) {
62!
568
        dest[*offset] = '\0';
62✔
569
    } else {
22✔
570
        dest[capacity - 1u] = '\0';
×
571
    }
572
}
22✔
573

574
static void
575
loader_append_key_value(char *dest,
26✔
576
                        size_t capacity,
577
                        size_t *offset,
578
                        char const *label,
579
                        char const *value)
580
{
581
    char line[128];
582
    int written;
583

584
    if (value == NULL || value[0] == '\0') {
26!
585
        return;
×
586
    }
587

588
    written = sixel_compat_snprintf(line,
36✔
589
                                    sizeof(line),
590
                                    "  %-10s: %s\n",
591
                                    label,
10✔
592
                                    value);
10✔
593
    if (written < 0) {
26!
594
        return;
×
595
    }
596

597
    if ((size_t)written >= sizeof(line)) {
26!
598
        line[sizeof(line) - 1u] = '\0';
×
599
    }
600

601
    loader_append_chunk(dest, capacity, offset, line);
26✔
602
}
10✔
603

604
static void
605
loader_extract_extension(char const *path, char *buffer, size_t capacity)
9✔
606
{
607
    char const *dot;
608
    size_t index;
609

610
    if (buffer == NULL || capacity == 0) {
9!
611
        return;
×
612
    }
613

614
    buffer[0] = '\0';
9✔
615

616
    if (path == NULL) {
9✔
617
        return;
3✔
618
    }
619

620
    dot = strrchr(path, '.');
6✔
621
    if (dot == NULL || dot[1] == '\0') {
6!
622
        return;
×
623
    }
624

625
#if defined(_WIN32)
626
    {
627
        char const *slash;
628
        char const *backslash;
629

630
        slash = strrchr(path, '/');
631
        backslash = strrchr(path, '\\');
632
        if ((slash != NULL && dot < slash) ||
633
                (backslash != NULL && dot < backslash)) {
634
            return;
635
        }
636
    }
637
#else
638
    {
639
        char const *slash;
640

641
        slash = strrchr(path, '/');
6✔
642
        if (slash != NULL && dot < slash) {
6!
643
            return;
6✔
644
        }
645
    }
646
#endif
647

648
    if (dot[1] == '\0') {
×
649
        return;
×
650
    }
651

652
    dot += 1;
×
653

654
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
×
655
        buffer[index] = (char)tolower((unsigned char)dot[index]);
×
656
    }
657
    buffer[index] = '\0';
×
658
}
3✔
659

660
sixel_allocator_t *stbi_allocator;
661

662
void *
663
stbi_malloc(size_t n)
1,210✔
664
{
665
    return sixel_allocator_malloc(stbi_allocator, n);
1,210✔
666
}
667

668
void *
669
stbi_realloc(void *p, size_t n)
319✔
670
{
671
    return sixel_allocator_realloc(stbi_allocator, p, n);
319✔
672
}
673

674
void
675
stbi_free(void *p)
1,597✔
676
{
677
    sixel_allocator_free(stbi_allocator, p);
1,597✔
678
}
1,597✔
679

680
#define STBI_MALLOC stbi_malloc
681
#define STBI_REALLOC stbi_realloc
682
#define STBI_FREE stbi_free
683

684
#define STBI_NO_STDIO 1
685
#define STB_IMAGE_IMPLEMENTATION 1
686
#define STBI_FAILURE_USERMSG 1
687
#if defined(_WIN32)
688
# define STBI_NO_THREAD_LOCALS 1  /* no tls */
689
#endif
690
#define STBI_NO_GIF
691
#define STBI_NO_PNM
692

693
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
694
# pragma GCC diagnostic push
695
# pragma GCC diagnostic ignored "-Wsign-conversion"
696
#endif
697
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
698
# pragma GCC diagnostic push
699
# pragma GCC diagnostic ignored "-Wstrict-overflow"
700
#endif
701
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
702
# pragma GCC diagnostic push
703
# pragma GCC diagnostic ignored "-Wswitch-default"
704
#endif
705
#if HAVE_DIAGNOSTIC_SHADOW
706
# pragma GCC diagnostic push
707
# pragma GCC diagnostic ignored "-Wshadow"
708
#endif
709
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
710
# pragma GCC diagnostic push
711
# pragma GCC diagnostic ignored "-Wdouble-promotion"
712
#endif
713
# if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
714
# pragma GCC diagnostic push
715
# pragma GCC diagnostic ignored "-Wunused-function"
716
#endif
717
# if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
718
# pragma GCC diagnostic push
719
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
720
#endif
721
#include "stb_image.h"
722
#if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
723
# pragma GCC diagnostic pop
724
#endif
725
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
726
# pragma GCC diagnostic pop
727
#endif
728
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
729
# pragma GCC diagnostic pop
730
#endif
731
#if HAVE_DIAGNOSTIC_SHADOW
732
# pragma GCC diagnostic pop
733
#endif
734
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
735
# pragma GCC diagnostic pop
736
#endif
737
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
738
# pragma GCC diagnostic pop
739
#endif
740
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
741
# pragma GCC diagnostic pop
742
#endif
743

744

745
# if HAVE_JPEG
746
/* import from @uobikiemukot's sdump loader.h */
747
static SIXELSTATUS
748
load_jpeg(unsigned char **result,
749
          unsigned char *data,
750
          size_t datasize,
751
          int *pwidth,
752
          int *pheight,
753
          int *ppixelformat,
754
          sixel_allocator_t *allocator)
755
{
756
    SIXELSTATUS status = SIXEL_JPEG_ERROR;
757
    JDIMENSION row_stride;
758
    size_t size;
759
    JSAMPARRAY buffer;
760
    struct jpeg_decompress_struct cinfo;
761
    struct jpeg_error_mgr pub;
762

763
    cinfo.err = jpeg_std_error(&pub);
764

765
    jpeg_create_decompress(&cinfo);
766
    jpeg_mem_src(&cinfo, data, datasize);
767
    jpeg_read_header(&cinfo, TRUE);
768

769
    /* disable colormap (indexed color), grayscale -> rgb */
770
    cinfo.quantize_colors = FALSE;
771
    cinfo.out_color_space = JCS_RGB;
772
    jpeg_start_decompress(&cinfo);
773

774
    if (cinfo.output_components != 3) {
775
        sixel_helper_set_additional_message(
776
            "load_jpeg: unknown pixel format.");
777
        status = SIXEL_BAD_INPUT;
778
        goto end;
779
    }
780

781
    *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
782

783
    if (cinfo.output_width > INT_MAX || cinfo.output_height > INT_MAX) {
784
        status = SIXEL_BAD_INTEGER_OVERFLOW;
785
        goto end;
786
    }
787
    *pwidth = (int)cinfo.output_width;
788
    *pheight = (int)cinfo.output_height;
789

790
    size = (size_t)(*pwidth * *pheight * cinfo.output_components);
791
    *result = (unsigned char *)sixel_allocator_malloc(allocator, size);
792
    if (*result == NULL) {
793
        sixel_helper_set_additional_message(
794
            "load_jpeg: sixel_allocator_malloc() failed.");
795
        status = SIXEL_BAD_ALLOCATION;
796
        goto end;
797
    }
798
    row_stride = cinfo.output_width * (unsigned int)cinfo.output_components;
799
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
800

801
    while (cinfo.output_scanline < cinfo.output_height) {
802
        jpeg_read_scanlines(&cinfo, buffer, 1);
803
        if (cinfo.err->num_warnings > 0) {
804
            sixel_helper_set_additional_message(
805
                "jpeg_read_scanlines: error/warining occuered.");
806
            status = SIXEL_BAD_INPUT;
807
            goto end;
808
        }
809
        memcpy(*result + (cinfo.output_scanline - 1) * row_stride, buffer[0], row_stride);
810
    }
811

812
    status = SIXEL_OK;
813

814
end:
815
    jpeg_finish_decompress(&cinfo);
816
    jpeg_destroy_decompress(&cinfo);
817

818
    return status;
819
}
820
# endif  /* HAVE_JPEG */
821

822

823
# if HAVE_LIBPNG
824
static void
825
read_png(png_structp png_ptr,
826
         png_bytep data,
827
         png_size_t length)
828
{
829
    sixel_chunk_t *pchunk = (sixel_chunk_t *)png_get_io_ptr(png_ptr);
830
    if (length > pchunk->size) {
831
        length = pchunk->size;
832
    }
833
    if (length > 0) {
834
        memcpy(data, pchunk->buffer, length);
835
        pchunk->buffer += length;
836
        pchunk->size -= length;
837
    }
838
}
839

840

841
static void
842
read_palette(png_structp png_ptr,
843
             png_infop info_ptr,
844
             unsigned char *palette,
845
             int ncolors,
846
             png_color *png_palette,
847
             png_color_16 *pbackground,
848
             int *transparent)
849
{
850
    png_bytep trans = NULL;
851
    int num_trans = 0;
852
    int i;
853

854
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
855
        png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
856
    }
857
    if (num_trans > 0) {
858
        *transparent = trans[0];
859
    }
860
    for (i = 0; i < ncolors; ++i) {
861
        if (pbackground && i < num_trans) {
862
            palette[i * 3 + 0] = ((0xff - trans[i]) * pbackground->red
863
                                   + trans[i] * png_palette[i].red) >> 8;
864
            palette[i * 3 + 1] = ((0xff - trans[i]) * pbackground->green
865
                                   + trans[i] * png_palette[i].green) >> 8;
866
            palette[i * 3 + 2] = ((0xff - trans[i]) * pbackground->blue
867
                                   + trans[i] * png_palette[i].blue) >> 8;
868
        } else {
869
            palette[i * 3 + 0] = png_palette[i].red;
870
            palette[i * 3 + 1] = png_palette[i].green;
871
            palette[i * 3 + 2] = png_palette[i].blue;
872
        }
873
    }
874
}
875

876
jmp_buf jmpbuf;
877

878
/* libpng error handler */
879
static void
880
png_error_callback(png_structp png_ptr, png_const_charp error_message)
881
{
882
    (void) png_ptr;
883

884
    sixel_helper_set_additional_message(error_message);
885
#if HAVE_SETJMP && HAVE_LONGJMP
886
    longjmp(jmpbuf, 1);
887
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
888
}
889

890

891
static SIXELSTATUS
892
load_png(unsigned char      /* out */ **result,
893
         unsigned char      /* in */  *buffer,
894
         size_t             /* in */  size,
895
         int                /* out */ *psx,
896
         int                /* out */ *psy,
897
         unsigned char      /* out */ **ppalette,
898
         int                /* out */ *pncolors,
899
         int                /* in */  reqcolors,
900
         int                /* out */ *pixelformat,
901
         unsigned char      /* out */ *bgcolor,
902
         int                /* out */ *transparent,
903
         sixel_allocator_t  /* in */  *allocator)
904
{
905
    SIXELSTATUS status;
906
    sixel_chunk_t read_chunk;
907
    png_uint_32 bitdepth;
908
    png_uint_32 png_status;
909
    png_structp png_ptr;
910
    png_infop info_ptr;
911
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
912
# pragma GCC diagnostic push
913
# pragma GCC diagnostic ignored "-Wclobbered"
914
#endif
915
    unsigned char **rows = NULL;
916
    png_color *png_palette = NULL;
917
    png_color_16 background;
918
    png_color_16p default_background;
919
    png_uint_32 width;
920
    png_uint_32 height;
921
    int i;
922
    int depth;
923

924
#if HAVE_SETJMP && HAVE_LONGJMP
925
    if (setjmp(jmpbuf) != 0) {
926
        sixel_allocator_free(allocator, *result);
927
        *result = NULL;
928
        status = SIXEL_PNG_ERROR;
929
        goto cleanup;
930
    }
931
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
932

933
    status = SIXEL_FALSE;
934
    *result = NULL;
935

936
    png_ptr = png_create_read_struct(
937
        PNG_LIBPNG_VER_STRING, NULL, &png_error_callback, NULL);
938
    if (!png_ptr) {
939
        sixel_helper_set_additional_message(
940
            "png_create_read_struct() failed.");
941
        status = SIXEL_PNG_ERROR;
942
        goto cleanup;
943
    }
944

945
    /*
946
     * The minimum valid PNG is 67 bytes.
947
     * https://garethrees.org/2007/11/14/pngcrush/
948
     */
949
    if (size < 67) {
950
        sixel_helper_set_additional_message("PNG data too small to be valid!");
951
        status = SIXEL_PNG_ERROR;
952
        goto cleanup;
953
    }
954

955
#if HAVE_SETJMP
956
    if (setjmp(png_jmpbuf(png_ptr)) != 0) {
957
        sixel_allocator_free(allocator, *result);
958
        *result = NULL;
959
        status = SIXEL_PNG_ERROR;
960
        goto cleanup;
961
    }
962
#endif  /* HAVE_SETJMP */
963

964
    info_ptr = png_create_info_struct(png_ptr);
965
    if (!info_ptr) {
966
        sixel_helper_set_additional_message(
967
            "png_create_info_struct() failed.");
968
        status = SIXEL_PNG_ERROR;
969
        png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
970
        goto cleanup;
971
    }
972
    read_chunk.buffer = buffer;
973
    read_chunk.size = size;
974

975
    png_set_read_fn(png_ptr,(png_voidp)&read_chunk, read_png);
976
    png_read_info(png_ptr, info_ptr);
977

978
    width = png_get_image_width(png_ptr, info_ptr);
979
    height = png_get_image_height(png_ptr, info_ptr);
980

981
    if (width > INT_MAX || height > INT_MAX) {
982
        status = SIXEL_BAD_INTEGER_OVERFLOW;
983
        goto cleanup;
984
    }
985

986
    *psx = (int)width;
987
    *psy = (int)height;
988

989
    bitdepth = png_get_bit_depth(png_ptr, info_ptr);
990
    if (bitdepth == 16) {
991
#  if HAVE_DEBUG
992
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
993
        fprintf(stderr, "stripping to 8bit...\n");
994
#  endif
995
        png_set_strip_16(png_ptr);
996
        bitdepth = 8;
997
    }
998

999
    if (bgcolor) {
1000
#  if HAVE_DEBUG
1001
        fprintf(stderr, "background color is specified [%02x, %02x, %02x]\n",
1002
                bgcolor[0], bgcolor[1], bgcolor[2]);
1003
#  endif
1004
        background.red = bgcolor[0];
1005
        background.green = bgcolor[1];
1006
        background.blue = bgcolor[2];
1007
        background.gray = (bgcolor[0] + bgcolor[1] + bgcolor[2]) / 3;
1008
    } else if (png_get_bKGD(png_ptr, info_ptr, &default_background) == PNG_INFO_bKGD) {
1009
        memcpy(&background, default_background, sizeof(background));
1010
#  if HAVE_DEBUG
1011
        fprintf(stderr, "background color is found [%02x, %02x, %02x]\n",
1012
                background.red, background.green, background.blue);
1013
#  endif
1014
    } else {
1015
        background.red = 0;
1016
        background.green = 0;
1017
        background.blue = 0;
1018
        background.gray = 0;
1019
    }
1020

1021
    switch (png_get_color_type(png_ptr, info_ptr)) {
1022
    case PNG_COLOR_TYPE_PALETTE:
1023
#  if HAVE_DEBUG
1024
        fprintf(stderr, "paletted PNG(PNG_COLOR_TYPE_PALETTE)\n");
1025
#  endif
1026
        png_status = png_get_PLTE(png_ptr, info_ptr,
1027
                                  &png_palette, pncolors);
1028
        if (png_status != PNG_INFO_PLTE || png_palette == NULL) {
1029
            sixel_helper_set_additional_message(
1030
                "PLTE chunk not found");
1031
            status = SIXEL_PNG_ERROR;
1032
            goto cleanup;
1033
        }
1034
#  if HAVE_DEBUG
1035
        fprintf(stderr, "palette colors: %d\n", *pncolors);
1036
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1037
#  endif
1038
        if (ppalette == NULL || *pncolors > reqcolors) {
1039
#  if HAVE_DEBUG
1040
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
1041
                    reqcolors);
1042
            fprintf(stderr, "expand to RGB format...\n");
1043
#  endif
1044
            png_set_background(png_ptr, &background,
1045
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1046
            png_set_palette_to_rgb(png_ptr);
1047
            png_set_strip_alpha(png_ptr);
1048
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1049
        } else {
1050
            switch (bitdepth) {
1051
            case 1:
1052
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
1053
                if (*ppalette == NULL) {
1054
                    sixel_helper_set_additional_message(
1055
                        "load_png: sixel_allocator_malloc() failed.");
1056
                    status = SIXEL_BAD_ALLOCATION;
1057
                    goto cleanup;
1058
                }
1059
                read_palette(png_ptr, info_ptr, *ppalette,
1060
                             *pncolors, png_palette, &background, transparent);
1061
                *pixelformat = SIXEL_PIXELFORMAT_PAL1;
1062
                break;
1063
            case 2:
1064
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
1065
                if (*ppalette == NULL) {
1066
                    sixel_helper_set_additional_message(
1067
                        "load_png: sixel_allocator_malloc() failed.");
1068
                    status = SIXEL_BAD_ALLOCATION;
1069
                    goto cleanup;
1070
                }
1071
                read_palette(png_ptr, info_ptr, *ppalette,
1072
                             *pncolors, png_palette, &background, transparent);
1073
                *pixelformat = SIXEL_PIXELFORMAT_PAL2;
1074
                break;
1075
            case 4:
1076
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
1077
                if (*ppalette == NULL) {
1078
                    sixel_helper_set_additional_message(
1079
                        "load_png: sixel_allocator_malloc() failed.");
1080
                    status = SIXEL_BAD_ALLOCATION;
1081
                    goto cleanup;
1082
                }
1083
                read_palette(png_ptr, info_ptr, *ppalette,
1084
                             *pncolors, png_palette, &background, transparent);
1085
                *pixelformat = SIXEL_PIXELFORMAT_PAL4;
1086
                break;
1087
            case 8:
1088
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
1089
                if (*ppalette == NULL) {
1090
                    sixel_helper_set_additional_message(
1091
                        "load_png: sixel_allocator_malloc() failed.");
1092
                    status = SIXEL_BAD_ALLOCATION;
1093
                    goto cleanup;
1094
                }
1095
                read_palette(png_ptr, info_ptr, *ppalette,
1096
                             *pncolors, png_palette, &background, transparent);
1097
                *pixelformat = SIXEL_PIXELFORMAT_PAL8;
1098
                break;
1099
            default:
1100
                png_set_background(png_ptr, &background,
1101
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1102
                png_set_palette_to_rgb(png_ptr);
1103
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1104
                break;
1105
            }
1106
        }
1107
        break;
1108
    case PNG_COLOR_TYPE_GRAY:
1109
#  if HAVE_DEBUG
1110
        fprintf(stderr, "grayscale PNG(PNG_COLOR_TYPE_GRAY)\n");
1111
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1112
#  endif
1113
        if (1 << bitdepth > reqcolors) {
1114
#  if HAVE_DEBUG
1115
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
1116
                    reqcolors);
1117
            fprintf(stderr, "expand into RGB format...\n");
1118
#  endif
1119
            png_set_background(png_ptr, &background,
1120
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1121
            png_set_gray_to_rgb(png_ptr);
1122
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1123
        } else {
1124
            switch (bitdepth) {
1125
            case 1:
1126
            case 2:
1127
            case 4:
1128
                if (ppalette) {
1129
#  if HAVE_DECL_PNG_SET_EXPAND_GRAY_1_2_4_TO_8
1130
#   if HAVE_DEBUG
1131
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
1132
                            (unsigned int)bitdepth);
1133
#   endif
1134
                    png_set_expand_gray_1_2_4_to_8(png_ptr);
1135
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
1136
#  elif HAVE_DECL_PNG_SET_GRAY_1_2_4_TO_8
1137
#   if HAVE_DEBUG
1138
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
1139
                            (unsigned int)bitdepth);
1140
#   endif
1141
                    png_set_gray_1_2_4_to_8(png_ptr);
1142
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
1143
#  else
1144
#   if HAVE_DEBUG
1145
                    fprintf(stderr, "expand into RGB format...\n");
1146
#   endif
1147
                    png_set_background(png_ptr, &background,
1148
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1149
                    png_set_gray_to_rgb(png_ptr);
1150
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1151
#  endif
1152
                } else {
1153
                    png_set_background(png_ptr, &background,
1154
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1155
                    png_set_gray_to_rgb(png_ptr);
1156
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1157
                }
1158
                break;
1159
            case 8:
1160
                if (ppalette) {
1161
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
1162
                } else {
1163
#  if HAVE_DEBUG
1164
                    fprintf(stderr, "expand into RGB format...\n");
1165
#  endif
1166
                    png_set_background(png_ptr, &background,
1167
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1168
                    png_set_gray_to_rgb(png_ptr);
1169
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1170
                }
1171
                break;
1172
            default:
1173
#  if HAVE_DEBUG
1174
                fprintf(stderr, "expand into RGB format...\n");
1175
#  endif
1176
                png_set_background(png_ptr, &background,
1177
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1178
                png_set_gray_to_rgb(png_ptr);
1179
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1180
                break;
1181
            }
1182
        }
1183
        break;
1184
    case PNG_COLOR_TYPE_GRAY_ALPHA:
1185
#  if HAVE_DEBUG
1186
        fprintf(stderr, "grayscale-alpha PNG(PNG_COLOR_TYPE_GRAY_ALPHA)\n");
1187
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1188
        fprintf(stderr, "expand to RGB format...\n");
1189
#  endif
1190
        png_set_background(png_ptr, &background,
1191
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1192
        png_set_gray_to_rgb(png_ptr);
1193
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1194
        break;
1195
    case PNG_COLOR_TYPE_RGB_ALPHA:
1196
#  if HAVE_DEBUG
1197
        fprintf(stderr, "RGBA PNG(PNG_COLOR_TYPE_RGB_ALPHA)\n");
1198
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1199
        fprintf(stderr, "expand to RGB format...\n");
1200
#  endif
1201
        png_set_background(png_ptr, &background,
1202
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1203
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1204
        break;
1205
    case PNG_COLOR_TYPE_RGB:
1206
#  if HAVE_DEBUG
1207
        fprintf(stderr, "RGB PNG(PNG_COLOR_TYPE_RGB)\n");
1208
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1209
#  endif
1210
        png_set_background(png_ptr, &background,
1211
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1212
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1213
        break;
1214
    default:
1215
        /* unknown format */
1216
        goto cleanup;
1217
    }
1218
    depth = sixel_helper_compute_depth(*pixelformat);
1219
    *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * depth));
1220
    if (*result == NULL) {
1221
        sixel_helper_set_additional_message(
1222
            "load_png: sixel_allocator_malloc() failed.");
1223
        status = SIXEL_BAD_ALLOCATION;
1224
        goto cleanup;
1225
    }
1226
    rows = (unsigned char **)sixel_allocator_malloc(allocator, (size_t)*psy * sizeof(unsigned char *));
1227
    if (rows == NULL) {
1228
        sixel_helper_set_additional_message(
1229
            "load_png: sixel_allocator_malloc() failed.");
1230
        status = SIXEL_BAD_ALLOCATION;
1231
        goto cleanup;
1232
    }
1233
    switch (*pixelformat) {
1234
    case SIXEL_PIXELFORMAT_PAL1:
1235
    case SIXEL_PIXELFORMAT_PAL2:
1236
    case SIXEL_PIXELFORMAT_PAL4:
1237
        for (i = 0; i < *psy; ++i) {
1238
            rows[i] = *result + (depth * *psx * (int)bitdepth + 7) / 8 * i;
1239
        }
1240
        break;
1241
    default:
1242
        for (i = 0; i < *psy; ++i) {
1243
            rows[i] = *result + depth * *psx * i;
1244
        }
1245
        break;
1246
    }
1247

1248
    png_read_image(png_ptr, rows);
1249

1250
    status = SIXEL_OK;
1251

1252
cleanup:
1253
    png_destroy_read_struct(&png_ptr, &info_ptr,(png_infopp)0);
1254

1255
    if (rows != NULL) {
1256
        sixel_allocator_free(allocator, rows);
1257
    }
1258

1259
    return status;
1260
}
1261
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
1262
# pragma GCC diagnostic pop
1263
#endif
1264

1265
# endif  /* HAVE_LIBPNG */
1266

1267

1268
static SIXELSTATUS
1269
load_sixel(unsigned char        /* out */ **result,
156✔
1270
           unsigned char        /* in */  *buffer,
1271
           int                  /* in */  size,
1272
           int                  /* out */ *psx,
1273
           int                  /* out */ *psy,
1274
           unsigned char        /* out */ **ppalette,
1275
           int                  /* out */ *pncolors,
1276
           int                  /* in */  reqcolors,
1277
           int                  /* out */ *ppixelformat,
1278
           sixel_allocator_t    /* in */  *allocator)
1279
{
1280
    SIXELSTATUS status = SIXEL_FALSE;
156✔
1281
    unsigned char *p = NULL;
156✔
1282
    unsigned char *palette = NULL;
156✔
1283
    int colors;
1284
    int i;
1285

1286
    /* sixel */
1287
    status = sixel_decode_raw(buffer, size,
208✔
1288
                              &p, psx, psy,
52✔
1289
                              &palette, &colors, allocator);
52✔
1290
    if (SIXEL_FAILED(status)) {
156!
1291
        goto end;
×
1292
    }
1293
    if (ppalette == NULL || colors > reqcolors) {
208!
1294
        *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
36✔
1295
        *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * 3));
36✔
1296
        if (*result == NULL) {
36!
1297
            sixel_helper_set_additional_message(
×
1298
                "load_sixel: sixel_allocator_malloc() failed.");
1299
            status = SIXEL_BAD_ALLOCATION;
×
1300
            goto end;
×
1301
        }
1302
        for (i = 0; i < *psx * *psy; ++i) {
5,670,576✔
1303
            (*result)[i * 3 + 0] = palette[p[i] * 3 + 0];
5,670,540✔
1304
            (*result)[i * 3 + 1] = palette[p[i] * 3 + 1];
5,670,540✔
1305
            (*result)[i * 3 + 2] = palette[p[i] * 3 + 2];
5,670,540✔
1306
        }
1,890,180✔
1307
    } else {
12✔
1308
        *ppixelformat = SIXEL_PIXELFORMAT_PAL8;
120✔
1309
        *result = p;
120✔
1310
        *ppalette = palette;
120✔
1311
        *pncolors = colors;
120✔
1312
        p = NULL;
120✔
1313
        palette = NULL;
120✔
1314
    }
1315

1316
end:
104✔
1317
    /*
1318
     * Release the decoded index buffer when the caller requested an RGB
1319
     * conversion.  Palette-backed callers steal ownership by nulling `p`.
1320
     */
1321
    sixel_allocator_free(allocator, p);
156✔
1322
    sixel_allocator_free(allocator, palette);
156✔
1323

1324
    return status;
156✔
1325
}
1326

1327

1328
/* detect whether given chunk is sixel stream */
1329
static int
1330
chunk_is_sixel(sixel_chunk_t const *chunk)
451✔
1331
{
1332
    unsigned char *p;
1333
    unsigned char *end;
1334

1335
    p = chunk->buffer;
451✔
1336
    end = p + chunk->size;
451✔
1337

1338
    if (chunk->size < 3) {
451!
1339
        return 0;
2✔
1340
    }
1341

1342
    p++;
449✔
1343
    if (p >= end) {
449!
1344
        return 0;
×
1345
    }
1346
    if (*(p - 1) == 0x90 || (*(p - 1) == 0x1b && *p == 0x50)) {
449!
1347
        while (p++ < end) {
570!
1348
            if (*p == 0x71) {
570✔
1349
                return 1;
156✔
1350
            } else if (*p == 0x18 || *p == 0x1a) {
414!
1351
                return 0;
×
1352
            } else if (*p < 0x20) {
414✔
1353
                continue;
6✔
1354
            } else if (*p < 0x30) {
408!
1355
                return 0;
×
1356
            } else if (*p < 0x40) {
408✔
1357
                continue;
405✔
1358
            }
1359
        }
1360
    }
1361
    return 0;
293✔
1362
}
151✔
1363

1364

1365
/* detect whether given chunk is PNM stream */
1366
static int
1367
chunk_is_pnm(sixel_chunk_t const *chunk)
295✔
1368
{
1369
    if (chunk->size < 2) {
295!
1370
        return 0;
2✔
1371
    }
1372
    if (chunk->buffer[0] == 'P' &&
303✔
1373
        chunk->buffer[1] >= '1' &&
30!
1374
        chunk->buffer[1] <= '6') {
30!
1375
        return 1;
30✔
1376
    }
1377
    return 0;
263✔
1378
}
99✔
1379

1380

1381
#if HAVE_LIBPNG
1382
/* detect whether given chunk is PNG stream */
1383
static int
1384
chunk_is_png(sixel_chunk_t const *chunk)
1385
{
1386
    if (chunk->size < 8) {
1387
        return 0;
1388
    }
1389
    if (png_check_sig(chunk->buffer, 8)) {
1390
        return 1;
1391
    }
1392
    return 0;
1393
}
1394
#endif  /* HAVE_LIBPNG */
1395

1396

1397
/* detect whether given chunk is GIF stream */
1398
static int
1399
chunk_is_gif(sixel_chunk_t const *chunk)
265✔
1400
{
1401
    if (chunk->size < 6) {
265✔
1402
        return 0;
3✔
1403
    }
1404
    if (chunk->buffer[0] == 'G' &&
269✔
1405
        chunk->buffer[1] == 'I' &&
21!
1406
        chunk->buffer[2] == 'F' &&
21!
1407
        chunk->buffer[3] == '8' &&
21!
1408
        (chunk->buffer[4] == '7' || chunk->buffer[4] == '9') &&
21!
1409
        chunk->buffer[5] == 'a') {
21!
1410
        return 1;
21✔
1411
    }
1412
    return 0;
241✔
1413
}
89✔
1414

1415

1416
#if HAVE_JPEG
1417
/* detect whether given chunk is JPEG stream */
1418
static int
1419
chunk_is_jpeg(sixel_chunk_t const *chunk)
1420
{
1421
    if (chunk->size < 2) {
1422
        return 0;
1423
    }
1424
    if (memcmp("\xFF\xD8", chunk->buffer, 2) == 0) {
1425
        return 1;
1426
    }
1427
    return 0;
1428
}
1429
#endif  /* HAVE_JPEG */
1430

1431
typedef union _fn_pointer {
1432
    sixel_load_image_function fn;
1433
    void *                    p;
1434
} fn_pointer;
1435

1436
/* load images using builtin image loaders */
1437
static SIXELSTATUS
1438
load_with_builtin(
451✔
1439
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
1440
    int                       /* in */     fstatic,      /* static */
1441
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
1442
    int                       /* in */     reqcolors,    /* reqcolors */
1443
    unsigned char             /* in */     *bgcolor,     /* background color */
1444
    int                       /* in */     loop_control, /* one of enum loop_control */
1445
    sixel_load_image_function /* in */     fn_load,      /* callback */
1446
    void                      /* in/out */ *context      /* private data for callback */
1447
)
1448
{
1449
    SIXELSTATUS status = SIXEL_FALSE;
451✔
1450
    sixel_frame_t *frame = NULL;
451✔
1451
    fn_pointer fnp;
1452
    stbi__context stb_context;
1453
    int depth;
1454
    char message[256];
1455
    int nwrite;
1456

1457
    if (chunk_is_sixel(pchunk)) {
451✔
1458
        status = sixel_frame_new(&frame, pchunk->allocator);
156✔
1459
        if (SIXEL_FAILED(status)) {
156!
1460
            goto end;
×
1461
        }
1462
        if (pchunk->size > INT_MAX) {
156!
1463
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1464
            goto end;
×
1465
        }
1466
        status = load_sixel(&frame->pixels,
128✔
1467
                            pchunk->buffer,
156✔
1468
                            (int)pchunk->size,
156✔
1469
                            &frame->width,
156✔
1470
                            &frame->height,
156✔
1471
                            fuse_palette ? &frame->palette: NULL,
132✔
1472
                            &frame->ncolors,
156✔
1473
                            reqcolors,
52✔
1474
                            &frame->pixelformat,
156✔
1475
                            pchunk->allocator);
156✔
1476
        if (SIXEL_FAILED(status)) {
156!
1477
            goto end;
×
1478
        }
1479
    } else if (chunk_is_pnm(pchunk)) {
347✔
1480
        status = sixel_frame_new(&frame, pchunk->allocator);
30✔
1481
        if (SIXEL_FAILED(status)) {
30!
1482
            goto end;
×
1483
        }
1484
        if (pchunk->size > INT_MAX) {
30!
1485
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1486
            goto end;
×
1487
        }
1488
        /* pnm */
1489
        status = load_pnm(pchunk->buffer,
40✔
1490
                          (int)pchunk->size,
30✔
1491
                          frame->allocator,
30✔
1492
                          &frame->pixels,
30✔
1493
                          &frame->width,
30✔
1494
                          &frame->height,
30✔
1495
                          fuse_palette ? &frame->palette: NULL,
10!
1496
                          &frame->ncolors,
30✔
1497
                          &frame->pixelformat);
30!
1498
        if (SIXEL_FAILED(status)) {
30!
1499
            goto end;
×
1500
        }
1501
    } else if (chunk_is_gif(pchunk)) {
275✔
1502
        fnp.fn = fn_load;
21✔
1503
        if (pchunk->size > INT_MAX) {
21!
1504
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1505
            goto end;
×
1506
        }
1507
        status = load_gif(pchunk->buffer,
28✔
1508
                          (int)pchunk->size,
21✔
1509
                          bgcolor,
7✔
1510
                          reqcolors,
7✔
1511
                          fuse_palette,
7✔
1512
                          fstatic,
7✔
1513
                          loop_control,
7✔
1514
                          fnp.p,
7✔
1515
                          context,
7✔
1516
                          pchunk->allocator);
21✔
1517
        if (SIXEL_FAILED(status)) {
21✔
1518
            goto end;
6✔
1519
        }
1520
        goto end;
15✔
1521
    } else {
1522
        /*
1523
         * Fallback to stb_image decoding when no specialized handler
1524
         * claimed the chunk.
1525
         *
1526
         *    +--------------+     +--------------------+
1527
         *    | raw chunk    | --> | stb_image decoding |
1528
         *    +--------------+     +--------------------+
1529
         *                        |
1530
         *                        v
1531
         *                +--------------------+
1532
         *                | sixel frame emit   |
1533
         *                +--------------------+
1534
         */
1535
        status = sixel_frame_new(&frame, pchunk->allocator);
244✔
1536
        if (SIXEL_FAILED(status)) {
244!
1537
            goto end;
×
1538
        }
1539
        if (pchunk->size > INT_MAX) {
244!
1540
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1541
            goto end;
×
1542
        }
1543
        stbi_allocator = pchunk->allocator;
244✔
1544
        stbi__start_mem(&stb_context,
244✔
1545
                        pchunk->buffer,
244✔
1546
                        (int)pchunk->size);
244✔
1547
        frame->pixels = stbi__load_and_postprocess_8bit(&stb_context,
406✔
1548
                                                        &frame->width,
244✔
1549
                                                        &frame->height,
244✔
1550
                                                        &depth,
1551
                                                        3);
1552
        if (frame->pixels == NULL) {
244✔
1553
            sixel_helper_set_additional_message(stbi_failure_reason());
3✔
1554
            status = SIXEL_STBI_ERROR;
3✔
1555
            goto end;
3✔
1556
        }
1557
        frame->loop_count = 1;
241✔
1558
        switch (depth) {
241!
1559
        case 1:
160✔
1560
        case 3:
1561
        case 4:
1562
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
241✔
1563
            break;
241✔
1564
        default:
1565
            nwrite = snprintf(message,
×
1566
                              sizeof(message),
1567
                              "load_with_builtin() failed.\n"
1568
                              "reason: unknown pixel-format.(depth: %d)\n",
1569
                              depth);
1570
            if (nwrite > 0) {
×
1571
                sixel_helper_set_additional_message(message);
×
1572
            }
1573
            status = SIXEL_STBI_ERROR;
×
1574
            goto end;
×
1575
        }
1576
    }
1577

1578
    status = sixel_frame_strip_alpha(frame, bgcolor);
427✔
1579
    if (SIXEL_FAILED(status)) {
427!
1580
        goto end;
×
1581
    }
1582

1583
    status = fn_load(frame, context);
427✔
1584
    if (SIXEL_FAILED(status)) {
427!
1585
        goto end;
×
1586
    }
1587

1588
    status = SIXEL_OK;
427✔
1589

1590
end:
300✔
1591
    sixel_frame_unref(frame);
451✔
1592

1593
    return status;
451✔
1594
}
1595

1596

1597
#if HAVE_JPEG
1598
/*
1599
 * Dedicated libjpeg loader wiring minimal pipeline.
1600
 *
1601
 *    +------------+     +-------------------+     +--------------------+
1602
 *    | JPEG chunk | --> | libjpeg decode    | --> | sixel frame emit   |
1603
 *    +------------+     +-------------------+     +--------------------+
1604
 */
1605
static SIXELSTATUS
1606
load_with_libjpeg(
1607
    sixel_chunk_t const       /* in */     *pchunk,
1608
    int                       /* in */     fstatic,
1609
    int                       /* in */     fuse_palette,
1610
    int                       /* in */     reqcolors,
1611
    unsigned char             /* in */     *bgcolor,
1612
    int                       /* in */     loop_control,
1613
    sixel_load_image_function /* in */     fn_load,
1614
    void                      /* in/out */ *context)
1615
{
1616
    SIXELSTATUS status = SIXEL_FALSE;
1617
    sixel_frame_t *frame = NULL;
1618

1619
    (void)fstatic;
1620
    (void)fuse_palette;
1621
    (void)reqcolors;
1622
    (void)loop_control;
1623

1624
    status = sixel_frame_new(&frame, pchunk->allocator);
1625
    if (SIXEL_FAILED(status)) {
1626
        goto end;
1627
    }
1628

1629
    status = load_jpeg(&frame->pixels,
1630
                       pchunk->buffer,
1631
                       pchunk->size,
1632
                       &frame->width,
1633
                       &frame->height,
1634
                       &frame->pixelformat,
1635
                       pchunk->allocator);
1636
    if (SIXEL_FAILED(status)) {
1637
        goto end;
1638
    }
1639

1640
    status = sixel_frame_strip_alpha(frame, bgcolor);
1641
    if (SIXEL_FAILED(status)) {
1642
        goto end;
1643
    }
1644

1645
    status = fn_load(frame, context);
1646
    if (SIXEL_FAILED(status)) {
1647
        goto end;
1648
    }
1649

1650
    status = SIXEL_OK;
1651

1652
end:
1653
    sixel_frame_unref(frame);
1654

1655
    return status;
1656
}
1657

1658
static int
1659
loader_can_try_libjpeg(sixel_chunk_t const *chunk)
1660
{
1661
    if (chunk == NULL) {
1662
        return 0;
1663
    }
1664

1665
    return chunk_is_jpeg(chunk);
1666
}
1667
#endif  /* HAVE_JPEG */
1668

1669
#if HAVE_LIBPNG
1670
/*
1671
 * Dedicated libpng loader for precise PNG decoding.
1672
 *
1673
 *    +-----------+     +------------------+     +--------------------+
1674
 *    | PNG chunk | --> | libpng decode    | --> | sixel frame emit   |
1675
 *    +-----------+     +------------------+     +--------------------+
1676
 */
1677
static SIXELSTATUS
1678
load_with_libpng(
1679
    sixel_chunk_t const       /* in */     *pchunk,
1680
    int                       /* in */     fstatic,
1681
    int                       /* in */     fuse_palette,
1682
    int                       /* in */     reqcolors,
1683
    unsigned char             /* in */     *bgcolor,
1684
    int                       /* in */     loop_control,
1685
    sixel_load_image_function /* in */     fn_load,
1686
    void                      /* in/out */ *context)
1687
{
1688
    SIXELSTATUS status = SIXEL_FALSE;
1689
    sixel_frame_t *frame = NULL;
1690

1691
    (void)fstatic;
1692
    (void)loop_control;
1693

1694
    status = sixel_frame_new(&frame, pchunk->allocator);
1695
    if (SIXEL_FAILED(status)) {
1696
        goto end;
1697
    }
1698

1699
    status = load_png(&frame->pixels,
1700
                      pchunk->buffer,
1701
                      pchunk->size,
1702
                      &frame->width,
1703
                      &frame->height,
1704
                      fuse_palette ? &frame->palette : NULL,
1705
                      &frame->ncolors,
1706
                      reqcolors,
1707
                      &frame->pixelformat,
1708
                      bgcolor,
1709
                      &frame->transparent,
1710
                      pchunk->allocator);
1711
    if (SIXEL_FAILED(status)) {
1712
        goto end;
1713
    }
1714

1715
    status = sixel_frame_strip_alpha(frame, bgcolor);
1716
    if (SIXEL_FAILED(status)) {
1717
        goto end;
1718
    }
1719

1720
    status = fn_load(frame, context);
1721
    if (SIXEL_FAILED(status)) {
1722
        goto end;
1723
    }
1724

1725
    status = SIXEL_OK;
1726

1727
end:
1728
    sixel_frame_unref(frame);
1729

1730
    return status;
1731
}
1732

1733
static int
1734
loader_can_try_libpng(sixel_chunk_t const *chunk)
1735
{
1736
    if (chunk == NULL) {
1737
        return 0;
1738
    }
1739

1740
    return chunk_is_png(chunk);
1741
}
1742
#endif  /* HAVE_LIBPNG */
1743

1744
#ifdef HAVE_GDK_PIXBUF2
1745
/*
1746
 * Loader backed by gdk-pixbuf2. The entire animation is consumed via
1747
 * GdkPixbufLoader, each frame is copied into a temporary buffer and forwarded as
1748
 * a sixel_frame_t. Loop attributes provided by gdk-pixbuf are reconciled with
1749
 * libsixel's loop control settings.
1750
 */
1751
static SIXELSTATUS
1752
load_with_gdkpixbuf(
1753
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
1754
    int                       /* in */     fstatic,      /* static */
1755
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
1756
    int                       /* in */     reqcolors,    /* reqcolors */
1757
    unsigned char             /* in */     *bgcolor,     /* background color */
1758
    int                       /* in */     loop_control, /* one of enum loop_control */
1759
    sixel_load_image_function /* in */     fn_load,      /* callback */
1760
    void                      /* in/out */ *context      /* private data for callback */
1761
)
1762
{
1763
    SIXELSTATUS status = SIXEL_FALSE;
1764
    GdkPixbuf *pixbuf;
1765
    GdkPixbufLoader *loader = NULL;
1766
    gboolean loader_closed = FALSE;  /* remember if loader was already closed */
1767
    GdkPixbufAnimation *animation;
1768
    GdkPixbufAnimationIter *it = NULL;
1769
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1770
# pragma GCC diagnostic push
1771
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1772
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1773
    GTimeVal time_val;
1774
    GTimeVal start_time;
1775
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1776
# pragma GCC diagnostic pop
1777
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1778
    sixel_frame_t *frame = NULL;
1779
    int stride;
1780
    unsigned char *p;
1781
    int i;
1782
    int depth;
1783
    int anim_loop_count = (-1);  /* (-1): infinite, >=0: finite loop count */
1784
    int delay_ms;
1785
    gboolean use_animation = FALSE;
1786

1787
    (void) fuse_palette;
1788
    (void) reqcolors;
1789
    (void) bgcolor;
1790

1791
    status = sixel_frame_new(&frame, pchunk->allocator);
1792
    if (SIXEL_FAILED(status)) {
1793
        goto end;
1794
    }
1795

1796
#if (! GLIB_CHECK_VERSION(2, 36, 0))
1797
    g_type_init();
1798
#endif
1799
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1800
# pragma GCC diagnostic push
1801
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1802
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1803
    g_get_current_time(&time_val);
1804
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1805
# pragma GCC diagnostic pop
1806
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1807
    start_time = time_val;
1808
    loader = gdk_pixbuf_loader_new();
1809
    if (loader == NULL) {
1810
        status = SIXEL_GDK_ERROR;
1811
        goto end;
1812
    }
1813
    /* feed the whole blob and close so the animation metadata becomes available */
1814
    if (! gdk_pixbuf_loader_write(loader, pchunk->buffer, pchunk->size, NULL)) {
1815
        status = SIXEL_GDK_ERROR;
1816
        goto end;
1817
    }
1818
    if (! gdk_pixbuf_loader_close(loader, NULL)) {
1819
        status = SIXEL_GDK_ERROR;
1820
        goto end;
1821
    }
1822
    loader_closed = TRUE;
1823
    pixbuf = NULL;
1824
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1825
# pragma GCC diagnostic push
1826
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1827
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1828
    animation = gdk_pixbuf_loader_get_animation(loader);
1829
    if (animation) {
1830
        /*
1831
         * +------------------------------------------------------+
1832
         * | GdkPixbuf 2.44 keeps the animation APIs available,   |
1833
         * | but marks them deprecated. We still need the         |
1834
         * | GTimeVal-driven timeline to preserve playback, so we |
1835
         * | mute the warning locally instead of abandoning       |
1836
         * | multi-frame decoding.                                |
1837
         * +------------------------------------------------------+
1838
         */
1839
        if (GDK_IS_PIXBUF_SIMPLE_ANIM(animation)) {
1840
            anim_loop_count = gdk_pixbuf_simple_anim_get_loop(
1841
                                 GDK_PIXBUF_SIMPLE_ANIM(animation))
1842
                             ? (-1)
1843
                             : 1;
1844
        } else {
1845
            GParamSpec *loop_pspec = g_object_class_find_property(
1846
                G_OBJECT_GET_CLASS(animation), "loop");
1847
            if (loop_pspec == NULL) {
1848
                loop_pspec = g_object_class_find_property(
1849
                    G_OBJECT_GET_CLASS(animation), "loop-count");
1850
            }
1851
            if (loop_pspec) {
1852
                GValue loop_value = G_VALUE_INIT;
1853
                g_value_init(&loop_value, loop_pspec->value_type);
1854
                g_object_get_property(G_OBJECT(animation),
1855
                                      g_param_spec_get_name(loop_pspec),
1856
                                      &loop_value);
1857
                if (G_VALUE_HOLDS_BOOLEAN(&loop_value)) {
1858
                    /* TRUE means "loop forever" for these properties */
1859
                    anim_loop_count = g_value_get_boolean(&loop_value)
1860
                                      ? (-1)
1861
                                      : 1;
1862
                } else if (G_VALUE_HOLDS_INT(&loop_value)) {
1863
                    int loop_int = g_value_get_int(&loop_value);
1864
                    /* GIF spec treats zero as infinite repetition */
1865
                    anim_loop_count = (loop_int <= 0) ? (-1) : loop_int;
1866
                } else if (G_VALUE_HOLDS_UINT(&loop_value)) {
1867
                    guint loop_uint = g_value_get_uint(&loop_value);
1868
                    if (loop_uint == 0U) {
1869
                        anim_loop_count = (-1);
1870
                    } else {
1871
                        anim_loop_count = loop_uint > (guint)INT_MAX
1872
                                            ? INT_MAX
1873
                                            : (int)loop_uint;
1874
                    }
1875
                }
1876
                g_value_unset(&loop_value);
1877
            }
1878
        }
1879
        if (!fstatic &&
1880
                !gdk_pixbuf_animation_is_static_image(animation)) {
1881
            use_animation = TRUE;
1882
        }
1883
    }
1884
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1885
# pragma GCC diagnostic pop
1886
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1887

1888
    if (! use_animation) {
1889
        /* fall back to single frame decoding */
1890
        pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1891
        if (pixbuf == NULL) {
1892
            goto end;
1893
        }
1894
        frame->frame_no = 0;
1895
        frame->width = gdk_pixbuf_get_width(pixbuf);
1896
        frame->height = gdk_pixbuf_get_height(pixbuf);
1897
        stride = gdk_pixbuf_get_rowstride(pixbuf);
1898
        frame->pixels = sixel_allocator_malloc(
1899
            pchunk->allocator,
1900
            (size_t)(frame->height * stride));
1901
        if (frame->pixels == NULL) {
1902
            sixel_helper_set_additional_message(
1903
                "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1904
            status = SIXEL_BAD_ALLOCATION;
1905
            goto end;
1906
        }
1907
        if (stride / frame->width == 4) {
1908
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1909
            depth = 4;
1910
        } else {
1911
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1912
            depth = 3;
1913
        }
1914
        p = gdk_pixbuf_get_pixels(pixbuf);
1915
        if (stride == frame->width * depth) {
1916
            memcpy(frame->pixels, p, (size_t)(frame->height * stride));
1917
        } else {
1918
            for (i = 0; i < frame->height; ++i) {
1919
                memcpy(frame->pixels + frame->width * depth * i,
1920
                       p + stride * i,
1921
                       (size_t)(frame->width * depth));
1922
            }
1923
        }
1924
        frame->delay = 0;
1925
        frame->multiframe = 0;
1926
        frame->loop_count = 0;
1927
        status = fn_load(frame, context);
1928
        if (status != SIXEL_OK) {
1929
            goto end;
1930
        }
1931
    } else {
1932
        gboolean finished;
1933

1934
        /* reset iterator to the beginning of the timeline */
1935
        time_val = start_time;
1936
        frame->frame_no = 0;
1937
        frame->loop_count = 0;
1938

1939
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1940
# pragma GCC diagnostic push
1941
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1942
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1943
        it = gdk_pixbuf_animation_get_iter(animation, &time_val);
1944
        if (it == NULL) {
1945
            status = SIXEL_GDK_ERROR;
1946
            goto end;
1947
        }
1948

1949
        for (;;) {
1950
            /* handle one logical loop of the animation */
1951
            finished = FALSE;
1952
            while (!gdk_pixbuf_animation_iter_on_currently_loading_frame(it)) {
1953
                /* {{{ */
1954
                pixbuf = gdk_pixbuf_animation_iter_get_pixbuf(it);
1955
                if (pixbuf == NULL) {
1956
                    finished = TRUE;
1957
                    break;
1958
                }
1959
                /* allocate a scratch copy of the current frame */
1960
                frame->width = gdk_pixbuf_get_width(pixbuf);
1961
                frame->height = gdk_pixbuf_get_height(pixbuf);
1962
                stride = gdk_pixbuf_get_rowstride(pixbuf);
1963
                frame->pixels = sixel_allocator_malloc(
1964
                    pchunk->allocator,
1965
                    (size_t)(frame->height * stride));
1966
                if (frame->pixels == NULL) {
1967
                    sixel_helper_set_additional_message(
1968
                        "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1969
                    status = SIXEL_BAD_ALLOCATION;
1970
                    goto end;
1971
                }
1972
                if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1973
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1974
                    depth = 4;
1975
                } else {
1976
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1977
                    depth = 3;
1978
                }
1979
                p = gdk_pixbuf_get_pixels(pixbuf);
1980
                if (stride == frame->width * depth) {
1981
                    memcpy(frame->pixels, p,
1982
                           (size_t)(frame->height * stride));
1983
                } else {
1984
                    for (i = 0; i < frame->height; ++i) {
1985
                        memcpy(frame->pixels + frame->width * depth * i,
1986
                               p + stride * i,
1987
                               (size_t)(frame->width * depth));
1988
                    }
1989
                }
1990
                delay_ms = gdk_pixbuf_animation_iter_get_delay_time(it);
1991
                if (delay_ms < 0) {
1992
                    delay_ms = 0;
1993
                }
1994
                /* advance the synthetic clock before asking gdk to move forward */
1995
                g_time_val_add(&time_val, delay_ms * 1000);
1996
                frame->delay = delay_ms / 10;
1997
                frame->multiframe = 1;
1998

1999
                if (!gdk_pixbuf_animation_iter_advance(it, &time_val)) {
2000
                    finished = TRUE;
2001
                }
2002
                status = fn_load(frame, context);
2003
                if (status != SIXEL_OK) {
2004
                    goto end;
2005
                }
2006
                /* release scratch pixels before decoding the next frame */
2007
                sixel_allocator_free(pchunk->allocator, frame->pixels);
2008
                frame->pixels = NULL;
2009
                frame->frame_no++;
2010

2011
                if (finished) {
2012
                    break;
2013
                }
2014
                /* }}} */
2015
            }
2016

2017
            if (frame->frame_no == 0) {
2018
                break;
2019
            }
2020

2021
            /* finished processing one full loop */
2022
            ++frame->loop_count;
2023

2024
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
2025
                break;
2026
            }
2027
            if (loop_control == SIXEL_LOOP_AUTO) {
2028
                /* obey header-provided loop count when AUTO */
2029
                if (anim_loop_count >= 0 &&
2030
                    frame->loop_count >= anim_loop_count) {
2031
                    break;
2032
                }
2033
            } else if (loop_control != SIXEL_LOOP_FORCE &&
2034
                       anim_loop_count > 0 &&
2035
                       frame->loop_count >= anim_loop_count) {
2036
                break;
2037
            }
2038

2039
            /* restart iteration from the beginning for the next pass */
2040
            g_object_unref(it);
2041
            time_val = start_time;
2042
            it = gdk_pixbuf_animation_get_iter(animation, &time_val);
2043
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2044
# pragma GCC diagnostic pop
2045
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
2046
            if (it == NULL) {
2047
                status = SIXEL_GDK_ERROR;
2048
                goto end;
2049
            }
2050
            /* next pass starts counting frames from zero again */
2051
            frame->frame_no = 0;
2052
        }
2053
    }
2054

2055
    status = SIXEL_OK;
2056

2057
end:
2058
    if (frame) {
2059
        /* drop the reference we obtained from sixel_frame_new() */
2060
        sixel_frame_unref(frame);
2061
    }
2062
    if (it) {
2063
        g_object_unref(it);
2064
    }
2065
    if (loader) {
2066
        if (!loader_closed) {
2067
            /* ensure the incremental loader is finalized even on error paths */
2068
            gdk_pixbuf_loader_close(loader, NULL);
2069
        }
2070
        g_object_unref(loader);
2071
    }
2072

2073
    return status;
2074

2075
}
2076
#endif  /* HAVE_GDK_PIXBUF2 */
2077

2078
#if HAVE_COREGRAPHICS
2079
static SIXELSTATUS
2080
load_with_coregraphics(
3✔
2081
    sixel_chunk_t const       /* in */     *pchunk,
2082
    int                       /* in */     fstatic,
2083
    int                       /* in */     fuse_palette,
2084
    int                       /* in */     reqcolors,
2085
    unsigned char             /* in */     *bgcolor,
2086
    int                       /* in */     loop_control,
2087
    sixel_load_image_function /* in */     fn_load,
2088
    void                      /* in/out */ *context)
2089
{
2090
    SIXELSTATUS status = SIXEL_FALSE;
3✔
2091
    sixel_frame_t *frame = NULL;
3✔
2092
    CFDataRef data = NULL;
3✔
2093
    CGImageSourceRef source = NULL;
3✔
2094
    CGImageRef image = NULL;
3✔
2095
    CGColorSpaceRef color_space = NULL;
3✔
2096
    CGContextRef ctx = NULL;
3✔
2097
    size_t stride;
2098
    size_t frame_count;
2099
    int anim_loop_count = (-1);
3✔
2100
    CFDictionaryRef props = NULL;
3✔
2101
    CFDictionaryRef anim_dict;
2102
    CFNumberRef loop_num;
2103
    CFDictionaryRef frame_props;
2104
    CFDictionaryRef frame_anim_dict;
2105
    CFNumberRef delay_num;
2106
    double delay_sec;
2107
    size_t i;
2108

2109
    (void) fuse_palette;
3✔
2110
    (void) reqcolors;
3✔
2111
    (void) bgcolor;
3✔
2112

2113
    status = sixel_frame_new(&frame, pchunk->allocator);
3✔
2114
    if (SIXEL_FAILED(status)) {
3!
2115
        goto end;
2116
    }
2117

2118
    data = CFDataCreate(kCFAllocatorDefault,
6✔
2119
                        pchunk->buffer,
3✔
2120
                        (CFIndex)pchunk->size);
3✔
2121
    if (! data) {
3!
2122
        status = SIXEL_FALSE;
2123
        goto end;
2124
    }
2125

2126
    source = CGImageSourceCreateWithData(data, NULL);
3✔
2127
    if (! source) {
3!
2128
        status = SIXEL_FALSE;
2129
        goto end;
2130
    }
2131

2132
    frame_count = CGImageSourceGetCount(source);
3✔
2133
    if (! frame_count) {
3!
2134
        status = SIXEL_FALSE;
3✔
2135
        goto end;
3✔
2136
    }
2137
    if (fstatic) {
2138
        frame_count = 1;
2139
    }
2140

2141
    props = CGImageSourceCopyProperties(source, NULL);
2142
    if (props) {
2143
        anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
2144
            props, kCGImagePropertyGIFDictionary);
2145
        if (anim_dict) {
2146
            loop_num = (CFNumberRef)CFDictionaryGetValue(
2147
                anim_dict, kCGImagePropertyGIFLoopCount);
2148
            if (loop_num) {
2149
                CFNumberGetValue(loop_num, kCFNumberIntType, &anim_loop_count);
2150
            }
2151
        }
2152
        CFRelease(props);
2153
    }
2154

2155
    color_space = CGColorSpaceCreateDeviceRGB();
2156
    if (! color_space) {
×
2157
        status = SIXEL_FALSE;
2158
        goto end;
2159
    }
2160

2161
    frame->loop_count = 0;
2162

2163
    for (;;) {
2164
        frame->frame_no = 0;
2165
        for (i = 0; i < frame_count; ++i) {
×
2166
            delay_sec = 0.0;
2167
            frame_props = CGImageSourceCopyPropertiesAtIndex(
2168
                source, (CFIndex)i, NULL);
2169
            if (frame_props) {
2170
                frame_anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
2171
                    frame_props, kCGImagePropertyGIFDictionary);
2172
                if (frame_anim_dict) {
2173
                    delay_num = (CFNumberRef)CFDictionaryGetValue(
2174
                        frame_anim_dict, kCGImagePropertyGIFUnclampedDelayTime);
2175
                    if (! delay_num) {
2176
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
2177
                            frame_anim_dict, kCGImagePropertyGIFDelayTime);
2178
                    }
2179
                    if (delay_num) {
2180
                        CFNumberGetValue(delay_num,
2181
                                         kCFNumberDoubleType,
2182
                                         &delay_sec);
2183
                    }
2184
                }
2185
#if defined(kCGImagePropertyPNGDictionary) && \
2186
    defined(kCGImagePropertyAPNGUnclampedDelayTime) && \
2187
    defined(kCGImagePropertyAPNGDelayTime)
2188
                if (delay_sec <= 0.0) {
2189
                    CFDictionaryRef png_frame = (CFDictionaryRef)CFDictionaryGetValue(
2190
                        frame_props, kCGImagePropertyPNGDictionary);
2191
                    if (png_frame) {
2192
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
2193
                            png_frame, kCGImagePropertyAPNGUnclampedDelayTime);
2194
                        if (! delay_num) {
2195
                            delay_num = (CFNumberRef)CFDictionaryGetValue(
2196
                                png_frame, kCGImagePropertyAPNGDelayTime);
2197
                        }
2198
                        if (delay_num) {
2199
                            CFNumberGetValue(delay_num,
2200
                                             kCFNumberDoubleType,
2201
                                             &delay_sec);
2202
                        }
2203
                    }
2204
                }
2205
#endif
2206
                CFRelease(frame_props);
2207
            }
2208
            if (delay_sec <= 0.0) {
2209
                delay_sec = 0.1;
2210
            }
2211
            frame->delay = (int)(delay_sec * 100.0 + 0.5);
2212
            if (frame->delay < 1) {
2213
                frame->delay = 1;
2214
            }
2215

2216
            image = CGImageSourceCreateImageAtIndex(source, (CFIndex)i, NULL);
2217
            if (! image) {
×
2218
                status = SIXEL_FALSE;
2219
                goto end;
2220
            }
2221

2222
            frame->width = (int)CGImageGetWidth(image);
2223
            frame->height = (int)CGImageGetHeight(image);
2224
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
2225
            stride = (size_t)frame->width * 4;
2226
            if (frame->pixels != NULL) {
2227
                sixel_allocator_free(pchunk->allocator, frame->pixels);
2228
            }
2229
            frame->pixels = sixel_allocator_malloc(
2230
                pchunk->allocator, (size_t)(frame->height * stride));
2231

2232
            if (frame->pixels == NULL) {
×
2233
                sixel_helper_set_additional_message(
2234
                    "load_with_coregraphics: sixel_allocator_malloc() failed.");
2235
                status = SIXEL_BAD_ALLOCATION;
2236
                CGImageRelease(image);
2237
                goto end;
2238
            }
2239

2240
            ctx = CGBitmapContextCreate(frame->pixels,
2241
                                        frame->width,
2242
                                        frame->height,
2243
                                        8,
2244
                                        stride,
2245
                                        color_space,
2246
                                        kCGImageAlphaPremultipliedLast |
2247
                                            kCGBitmapByteOrder32Big);
2248
            if (!ctx) {
×
2249
                CGImageRelease(image);
2250
                goto end;
2251
            }
2252

2253
            CGContextDrawImage(ctx,
2254
                               CGRectMake(0, 0, frame->width, frame->height),
2255
                               image);
2256
            CGContextRelease(ctx);
2257
            ctx = NULL;
2258

2259
            frame->multiframe = (frame_count > 1);
2260
            status = fn_load(frame, context);
2261
            CGImageRelease(image);
2262
            image = NULL;
2263
            if (status != SIXEL_OK) {
×
2264
                goto end;
2265
            }
2266
            ++frame->frame_no;
2267
        }
2268

2269
        ++frame->loop_count;
2270

2271
        if (frame_count <= 1) {
×
2272
            break;
2273
        }
2274
        if (loop_control == SIXEL_LOOP_DISABLE) {
×
2275
            break;
2276
        }
2277
        if (loop_control == SIXEL_LOOP_AUTO) {
×
2278
            if (anim_loop_count < 0) {
×
2279
                break;
2280
            }
2281
            if (anim_loop_count > 0 && frame->loop_count >= anim_loop_count) {
2282
                break;
2283
            }
2284
            continue;
2285
        }
2286
    }
2287

2288
    status = SIXEL_OK;
2289

2290
end:
2291
    if (ctx) {
3✔
2292
        CGContextRelease(ctx);
2293
    }
2294
    if (color_space) {
2295
        CGColorSpaceRelease(color_space);
2296
    }
2297
    if (image) {
2298
        CGImageRelease(image);
2299
    }
2300
    if (source) {
3✔
2301
        CFRelease(source);
3✔
2302
    }
3✔
2303
    if (data) {
3✔
2304
        CFRelease(data);
3✔
2305
    }
3✔
2306
    if (frame) {
3✔
2307
        sixel_frame_unref(frame);
3✔
2308
    }
3✔
2309
    return status;
3✔
2310
}
2311
#endif  /* HAVE_COREGRAPHICS */
2312

2313
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
2314
static SIXELSTATUS
2315
load_with_quicklook(
2316
    sixel_chunk_t const       /* in */     *pchunk,
2317
    int                       /* in */     fstatic,
2318
    int                       /* in */     fuse_palette,
2319
    int                       /* in */     reqcolors,
2320
    unsigned char             /* in */     *bgcolor,
2321
    int                       /* in */     loop_control,
2322
    sixel_load_image_function /* in */     fn_load,
2323
    void                      /* in/out */ *context)
2324
{
2325
    SIXELSTATUS status = SIXEL_FALSE;
2326
    sixel_frame_t *frame = NULL;
2327
    CFStringRef path = NULL;
2328
    CFURLRef url = NULL;
2329
    CGImageRef image = NULL;
2330
    CGColorSpaceRef color_space = NULL;
2331
    CGContextRef ctx = NULL;
2332
    CGRect bounds;
2333
    size_t stride;
2334
    unsigned char fill_color[3];
2335
    CGFloat fill_r;
2336
    CGFloat fill_g;
2337
    CGFloat fill_b;
2338
    CGFloat max_dimension;
2339
    CGSize max_size;
2340

2341
    (void)fstatic;
2342
    (void)fuse_palette;
2343
    (void)reqcolors;
2344
    (void)loop_control;
2345

2346
    if (pchunk == NULL || pchunk->source_path == NULL) {
2347
        goto end;
2348
    }
2349

2350
    loader_thumbnailer_initialize_size_hint();
2351

2352
    status = sixel_frame_new(&frame, pchunk->allocator);
2353
    if (SIXEL_FAILED(status)) {
×
2354
        goto end;
2355
    }
2356

2357
    path = CFStringCreateWithCString(kCFAllocatorDefault,
2358
                                     pchunk->source_path,
2359
                                     kCFStringEncodingUTF8);
2360
    if (path == NULL) {
×
2361
        status = SIXEL_RUNTIME_ERROR;
2362
        goto end;
2363
    }
2364

2365
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
2366
                                        path,
2367
                                        kCFURLPOSIXPathStyle,
2368
                                        false);
2369
    if (url == NULL) {
×
2370
        status = SIXEL_RUNTIME_ERROR;
2371
        goto end;
2372
    }
2373

2374
    if (thumbnailer_size_hint > 0) {
×
2375
        max_dimension = (CGFloat)thumbnailer_size_hint;
2376
    } else {
2377
        max_dimension = (CGFloat)SIXEL_THUMBNAILER_DEFAULT_SIZE;
2378
    }
2379
    max_size.width = max_dimension;
2380
    max_size.height = max_dimension;
2381
#if HAVE_QUICKLOOK_THUMBNAILING
2382
    image = sixel_quicklook_thumbnail_create(url, max_size);
2383
    if (image == NULL) {
2384
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2385
#  pragma clang diagnostic push
2386
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
2387
# endif
2388
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
2389
                                       url,
2390
                                       max_size,
2391
                                       NULL);
2392
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2393
#  pragma clang diagnostic pop
2394
# endif
2395
    }
2396
#else
2397
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2398
#  pragma clang diagnostic push
2399
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
2400
# endif
2401
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
2402
                                   url,
2403
                                   max_size,
2404
                                   NULL);
2405
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2406
#  pragma clang diagnostic pop
2407
# endif
2408
#endif
2409
    if (image == NULL) {
×
2410
        status = SIXEL_RUNTIME_ERROR;
2411
        sixel_helper_set_additional_message(
2412
            "load_with_quicklook: CQLThumbnailImageCreate() failed.");
2413
        goto end;
2414
    }
2415

2416
    color_space = CGColorSpaceCreateDeviceRGB();
2417
    if (color_space == NULL) {
×
2418
        status = SIXEL_RUNTIME_ERROR;
2419
        sixel_helper_set_additional_message(
2420
            "load_with_quicklook: CGColorSpaceCreateDeviceRGB() failed.");
2421
        goto end;
2422
    }
2423

2424
    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
2425
    frame->width = (int)CGImageGetWidth(image);
2426
    frame->height = (int)CGImageGetHeight(image);
2427
    if (frame->width <= 0 || frame->height <= 0) {
2428
        status = SIXEL_RUNTIME_ERROR;
2429
        sixel_helper_set_additional_message(
2430
            "load_with_quicklook: invalid image size detected.");
2431
        goto end;
2432
    }
2433

2434
    stride = (size_t)frame->width * 4;
2435
    frame->pixels =
2436
        sixel_allocator_malloc(pchunk->allocator,
2437
                               (size_t)frame->height * stride);
2438
    if (frame->pixels == NULL) {
×
2439
        sixel_helper_set_additional_message(
2440
            "load_with_quicklook: sixel_allocator_malloc() failed.");
2441
        status = SIXEL_BAD_ALLOCATION;
2442
        goto end;
2443
    }
2444

2445
    if (bgcolor != NULL) {
×
2446
        fill_color[0] = bgcolor[0];
2447
        fill_color[1] = bgcolor[1];
2448
        fill_color[2] = bgcolor[2];
2449
    } else {
2450
        fill_color[0] = 255;
2451
        fill_color[1] = 255;
2452
        fill_color[2] = 255;
2453
    }
2454

2455
    ctx = CGBitmapContextCreate(frame->pixels,
2456
                                frame->width,
2457
                                frame->height,
2458
                                8,
2459
                                stride,
2460
                                color_space,
2461
                                kCGImageAlphaPremultipliedLast |
2462
                                    kCGBitmapByteOrder32Big);
2463
    if (ctx == NULL) {
×
2464
        status = SIXEL_RUNTIME_ERROR;
2465
        sixel_helper_set_additional_message(
2466
            "load_with_quicklook: CGBitmapContextCreate() failed.");
2467
        goto end;
2468
    }
2469

2470
    bounds = CGRectMake(0,
2471
                        0,
2472
                        (CGFloat)frame->width,
2473
                        (CGFloat)frame->height);
2474
    fill_r = (CGFloat)fill_color[0] / 255.0f;
2475
    fill_g = (CGFloat)fill_color[1] / 255.0f;
2476
    fill_b = (CGFloat)fill_color[2] / 255.0f;
2477
    CGContextSetRGBFillColor(ctx, fill_r, fill_g, fill_b, 1.0f);
2478
    CGContextFillRect(ctx, bounds);
2479
    CGContextDrawImage(ctx, bounds, image);
2480
    CGContextFlush(ctx);
2481

2482
    /* Abort when Quick Look produced no visible pixels so other loaders run. */
2483
    {
2484
        size_t pixel_count;
2485
        size_t index;
2486
        unsigned char *pixel;
2487
        int has_content;
2488

2489
        pixel_count = (size_t)frame->width * (size_t)frame->height;
2490
        pixel = frame->pixels;
2491
        has_content = 0;
2492
        for (index = 0; index < pixel_count; ++index) {
2493
            if (pixel[0] != fill_color[0] ||
2494
                    pixel[1] != fill_color[1] ||
2495
                    pixel[2] != fill_color[2] ||
2496
                    pixel[3] != 0xff) {
2497
                has_content = 1;
2498
                break;
2499
            }
2500
            pixel += 4;
2501
        }
2502
        if (! has_content) {
×
2503
            sixel_helper_set_additional_message(
2504
                "load_with_quicklook: thumbnail contained no visible pixels.");
2505
            status = SIXEL_BAD_INPUT;
2506
            CGContextRelease(ctx);
2507
            ctx = NULL;
2508
            goto end;
2509
        }
2510
    }
2511

2512
    CGContextRelease(ctx);
2513
    ctx = NULL;
2514

2515
    frame->delay = 0;
2516
    frame->frame_no = 0;
2517
    frame->loop_count = 1;
2518
    frame->multiframe = 0;
2519
    frame->transparent = (-1);
2520

2521
    status = sixel_frame_strip_alpha(frame, fill_color);
2522
    if (SIXEL_FAILED(status)) {
×
2523
        goto end;
2524
    }
2525

2526
    status = fn_load(frame, context);
2527
    if (status != SIXEL_OK) {
×
2528
        goto end;
2529
    }
2530

2531
    status = SIXEL_OK;
2532

2533
end:
2534
    if (ctx != NULL) {
2535
        CGContextRelease(ctx);
2536
    }
2537
    if (color_space != NULL) {
2538
        CGColorSpaceRelease(color_space);
2539
    }
2540
    if (image != NULL) {
2541
        CGImageRelease(image);
2542
    }
2543
    if (url != NULL) {
2544
        CFRelease(url);
2545
    }
2546
    if (path != NULL) {
2547
        CFRelease(path);
2548
    }
2549
    if (frame != NULL) {
2550
        sixel_frame_unref(frame);
2551
    }
2552

2553
    return status;
2554
}
2555
#endif  /* HAVE_COREGRAPHICS && HAVE_QUICKLOOK */
2556

2557
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
2558

2559
# if defined(HAVE_NANOSLEEP)
2560
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
2561
# endif
2562
# if defined(HAVE_REALPATH)
2563
char * realpath(const char *restrict path, char *restrict resolved_path);
2564
# endif
2565
# if defined(HAVE_MKSTEMP)
2566
int mkstemp(char *);
2567
# endif
2568

2569
/*
2570
 * thumbnailer_message_finalize
2571
 *
2572
 * Clamp formatted messages so callers do not have to repeat truncation
2573
 * checks after calling sixel_compat_snprintf().
2574
 */
2575
static void
2576
thumbnailer_message_finalize(char *buffer, size_t capacity, int written)
×
2577
{
2578
    if (buffer == NULL || capacity == 0) {
×
2579
        return;
×
2580
    }
2581

2582
    if (written < 0) {
×
2583
        buffer[0] = '\0';
×
2584
        return;
×
2585
    }
2586

2587
    if ((size_t)written >= capacity) {
×
2588
        buffer[capacity - 1u] = '\0';
×
2589
    }
2590
}
2591

2592
/*
2593
 * thumbnailer_sleep_briefly
2594
 *
2595
 * Yield the CPU for a short duration so child polling loops avoid busy
2596
 * waiting.
2597
 *
2598
 */
2599
static void
2600
thumbnailer_sleep_briefly(void)
×
2601
{
2602
# if HAVE_NANOSLEEP
2603
    struct timespec ts;
2604
# endif
2605

2606
# if HAVE_NANOSLEEP
2607
    ts.tv_sec = 0;
×
2608
    ts.tv_nsec = 10000000L;
×
2609
    nanosleep(&ts, NULL);
×
2610
# elif defined(_WIN32)
2611
    Sleep(10);
2612
# else
2613
    (void)usleep(10000);
2614
# endif
2615
}
×
2616

2617
# if !defined(_WIN32) && defined(HAVE__REALPATH) && !defined(HAVE_REALPATH)
2618
static char *
2619
thumbnailer_resolve_without_realpath(char const *path)
2620
{
2621
    char *cwd;
2622
    char *resolved;
2623
    size_t cwd_length;
2624
    size_t path_length;
2625
    int need_separator;
2626

2627
    cwd = NULL;
2628
    resolved = NULL;
2629
    cwd_length = 0;
2630
    path_length = 0;
2631
    need_separator = 0;
2632

2633
    if (path == NULL) {
2634
        return NULL;
2635
    }
2636

2637
    if (path[0] == '/') {
2638
        path_length = strlen(path);
2639
        resolved = malloc(path_length + 1);
2640
        if (resolved == NULL) {
2641
            return NULL;
2642
        }
2643
        memcpy(resolved, path, path_length + 1);
2644

2645
        return resolved;
2646
    }
2647

2648
#  if defined(PATH_MAX)
2649
    cwd = malloc(PATH_MAX);
2650
    if (cwd != NULL) {
2651
        if (getcwd(cwd, PATH_MAX) != NULL) {
2652
            cwd_length = strlen(cwd);
2653
            path_length = strlen(path);
2654
            need_separator = 0;
2655
            if (cwd_length > 0 && cwd[cwd_length - 1] != '/') {
2656
                need_separator = 1;
2657
            }
2658
            resolved = malloc(cwd_length + need_separator + path_length + 1);
2659
            if (resolved != NULL) {
2660
                memcpy(resolved, cwd, cwd_length);
2661
                if (need_separator != 0) {
2662
                    resolved[cwd_length] = '/';
2663
                }
2664
                memcpy(resolved + cwd_length + need_separator,
2665
                       path,
2666
                       path_length + 1);
2667
            }
2668
            free(cwd);
2669
            if (resolved != NULL) {
2670
                return resolved;
2671
            }
2672
        } else {
2673
            free(cwd);
2674
        }
2675
    }
2676
#  endif  /* PATH_MAX */
2677

2678
    path_length = strlen(path);
2679
    resolved = malloc(path_length + 1);
2680
    if (resolved == NULL) {
2681
        return NULL;
2682
    }
2683
    memcpy(resolved, path, path_length + 1);
2684

2685
    return resolved;
2686
}
2687
# endif  /* !defined(_WIN32) && defined(HAVE__REALPATH) && !defined(HAVE_REALPATH) */
2688

2689
/*
2690
 * thumbnailer_resolve_path
2691
 *
2692
 * Resolve the supplied path to an absolute canonical path when possible.
2693
 *
2694
 * Arguments:
2695
 *     path - original filesystem path.
2696
 * Returns:
2697
 *     Newly allocated canonical path or NULL on failure.
2698
 */
2699
static char *
2700
thumbnailer_resolve_path(char const *path)
×
2701
{
2702
    char *resolved;
2703

2704
    resolved = NULL;
×
2705

2706
    if (path == NULL) {
×
2707
        return NULL;
×
2708
    }
2709

2710
# if defined(HAVE__FULLPATH)
2711
    resolved = _fullpath(NULL, path, 0);
2712
# elif defined(HAVE__REALPATH)
2713
    resolved = _realpath(path, NULL);
2714
# elif defined(HAVE_REALPATH)
2715
    resolved = realpath(path, NULL);
×
2716
# else
2717
    resolved = thumbnailer_resolve_without_realpath(path);
2718
# endif
2719

2720
    return resolved;
×
2721
}
2722

2723
struct thumbnailer_string_list {
2724
    char **items;
2725
    size_t length;
2726
    size_t capacity;
2727
};
2728

2729
struct thumbnailer_entry {
2730
    char *exec_line;
2731
    char *tryexec;
2732
    struct thumbnailer_string_list *mime_types;
2733
};
2734

2735
/*
2736
 * thumbnailer_strdup
2737
 *
2738
 * Duplicate a string with malloc so thumbnail helpers own their copies.
2739
 *
2740
 * Arguments:
2741
 *     src - zero-terminated string to copy; may be NULL.
2742
 * Returns:
2743
 *     Newly allocated duplicate or NULL on failure/NULL input.
2744
 */
2745
static char *
2746
thumbnailer_strdup(char const *src)
48✔
2747
{
2748
    char *copy;
2749
    size_t length;
2750

2751
    copy = NULL;
48✔
2752
    length = 0;
48✔
2753

2754
    if (src == NULL) {
48!
2755
        return NULL;
×
2756
    }
2757

2758
    length = strlen(src);
48✔
2759
    copy = malloc(length + 1);
48✔
2760
    if (copy == NULL) {
48!
2761
        return NULL;
×
2762
    }
2763
    memcpy(copy, src, length + 1);
48✔
2764

2765
    return copy;
48✔
2766
}
16✔
2767

2768
/*
2769
 * thumbnailer_string_list_new
2770
 *
2771
 * Allocate an empty expandable string list used throughout the loader.
2772
 *
2773
 * Arguments:
2774
 *     None.
2775
 * Returns:
2776
 *     Newly allocated list instance or NULL on failure.
2777
 */
2778
static struct thumbnailer_string_list *
2779
thumbnailer_string_list_new(void)
9✔
2780
{
2781
    struct thumbnailer_string_list *list;
2782

2783
    list = malloc(sizeof(*list));
9✔
2784
    if (list == NULL) {
9!
2785
        return NULL;
×
2786
    }
2787

2788
    list->items = NULL;
9✔
2789
    list->length = 0;
9✔
2790
    list->capacity = 0;
9✔
2791

2792
    return list;
9✔
2793
}
3✔
2794

2795
/*
2796
 * thumbnailer_string_list_free
2797
 *
2798
 * Release every string stored in the list and free the container itself.
2799
 *
2800
 * Arguments:
2801
 *     list - list instance produced by thumbnailer_string_list_new().
2802
 */
2803
static void
2804
thumbnailer_string_list_free(struct thumbnailer_string_list *list)
18✔
2805
{
2806
    size_t index;
2807

2808
    index = 0;
18✔
2809

2810
    if (list == NULL) {
18✔
2811
        return;
9✔
2812
    }
2813

2814
    if (list->items != NULL) {
9!
2815
        for (index = 0; index < list->length; ++index) {
36✔
2816
            free(list->items[index]);
27✔
2817
            list->items[index] = NULL;
27✔
2818
        }
9✔
2819
        free(list->items);
9✔
2820
        list->items = NULL;
9✔
2821
    }
3✔
2822

2823
    free(list);
9✔
2824
}
6✔
2825

2826
/*
2827
 * thumbnailer_string_list_append
2828
 *
2829
 * Append a copy of the supplied string to the dynamic list.
2830
 *
2831
 * Arguments:
2832
 *     list  - destination list.
2833
 *     value - string to duplicate and append.
2834
 * Returns:
2835
 *     1 on success, 0 on allocation failure or invalid input.
2836
 */
2837
static int
2838
thumbnailer_string_list_append(struct thumbnailer_string_list *list,
27✔
2839
                               char const *value)
2840
{
2841
    size_t new_capacity;
2842
    char **new_items;
2843
    char *copy;
2844

2845
    new_capacity = 0;
27✔
2846
    new_items = NULL;
27✔
2847
    copy = NULL;
27✔
2848

2849
    if (list == NULL || value == NULL) {
27!
2850
        return 0;
×
2851
    }
2852

2853
    copy = thumbnailer_strdup(value);
27✔
2854
    if (copy == NULL) {
27!
2855
        return 0;
×
2856
    }
2857

2858
    if (list->length == list->capacity) {
27✔
2859
        new_capacity = (list->capacity == 0) ? 4 : list->capacity * 2;
9!
2860
        new_items = realloc(list->items,
12✔
2861
                            new_capacity * sizeof(*list->items));
3✔
2862
        if (new_items == NULL) {
9!
2863
            free(copy);
×
2864
            return 0;
×
2865
        }
2866
        list->items = new_items;
9✔
2867
        list->capacity = new_capacity;
9✔
2868
    }
3✔
2869

2870
    list->items[list->length] = copy;
27✔
2871
    list->length += 1;
27✔
2872

2873
    return 1;
27✔
2874
}
9✔
2875

2876
/*
2877
 * thumbnailer_entry_init
2878
 *
2879
 * Prepare a thumbnailer_entry structure for population.
2880
 *
2881
 * Arguments:
2882
 *     entry - caller-provided structure to initialize.
2883
 */
2884
static void
2885
thumbnailer_entry_init(struct thumbnailer_entry *entry)
9✔
2886
{
2887
    if (entry == NULL) {
9!
2888
        return;
×
2889
    }
2890

2891
    entry->exec_line = NULL;
9✔
2892
    entry->tryexec = NULL;
9✔
2893
    entry->mime_types = NULL;
9✔
2894
}
3✔
2895

2896
/*
2897
 * thumbnailer_entry_clear
2898
 *
2899
 * Release every heap allocation associated with a thumbnailer_entry.
2900
 *
2901
 * Arguments:
2902
 *     entry - structure previously initialized with thumbnailer_entry_init().
2903
 */
2904
static void
2905
thumbnailer_entry_clear(struct thumbnailer_entry *entry)
9✔
2906
{
2907
    if (entry == NULL) {
9!
2908
        return;
×
2909
    }
2910

2911
    free(entry->exec_line);
9✔
2912
    entry->exec_line = NULL;
9✔
2913
    free(entry->tryexec);
9✔
2914
    entry->tryexec = NULL;
9✔
2915
    thumbnailer_string_list_free(entry->mime_types);
9✔
2916
    entry->mime_types = NULL;
9✔
2917
}
3✔
2918

2919
/*
2920
 * thumbnailer_join_paths
2921
 *
2922
 * Concatenate two path fragments inserting a slash when required.
2923
 *
2924
 * Arguments:
2925
 *     left  - directory prefix.
2926
 *     right - trailing component.
2927
 * Returns:
2928
 *     Newly allocated combined path or NULL on failure.
2929
 */
2930
static char *
2931
thumbnailer_join_paths(char const *left, char const *right)
36✔
2932
{
2933
    size_t left_length;
2934
    size_t right_length;
2935
    int need_separator;
2936
    char *combined;
2937

2938
    left_length = 0;
36✔
2939
    right_length = 0;
36✔
2940
    need_separator = 0;
36✔
2941
    combined = NULL;
36✔
2942

2943
    if (left == NULL || right == NULL) {
36!
2944
        return NULL;
×
2945
    }
2946

2947
    left_length = strlen(left);
36✔
2948
    right_length = strlen(right);
36✔
2949
    need_separator = 0;
36✔
2950

2951
    if (left_length > 0 && right_length > 0 &&
48!
2952
            left[left_length - 1] != '/' && right[0] != '/') {
36!
2953
        need_separator = 1;
36✔
2954
    }
12✔
2955

2956
    combined = malloc(left_length + right_length + need_separator + 1);
36✔
2957
    if (combined == NULL) {
36!
2958
        return NULL;
×
2959
    }
2960

2961
    memcpy(combined, left, left_length);
36✔
2962
    if (need_separator) {
36!
2963
        combined[left_length] = '/';
36✔
2964
        memcpy(combined + left_length + 1, right, right_length);
36✔
2965
        combined[left_length + right_length + 1] = '\0';
36✔
2966
    } else {
12✔
2967
        memcpy(combined + left_length, right, right_length);
×
2968
        combined[left_length + right_length] = '\0';
×
2969
    }
2970

2971
    return combined;
36✔
2972
}
12✔
2973

2974
/*
2975
 * thumbnailer_collect_directories
2976
 *
2977
 * Enumerate directories that may contain FreeDesktop thumbnailer
2978
 * definitions according to the XDG specification.
2979
 *
2980
 * GNOME thumbnailers follow the XDG data directory contract:
2981
 *
2982
 *     +------------------+      +---------------------------+
2983
 *     | HOME/.local/share| ---> | HOME/.local/share/        |
2984
 *     |                  |      |    thumbnailers/(*.thumbnailer)
2985
 *     +------------------+      +---------------------------+
2986
 *
2987
 *     +------------------+      +---------------------------+
2988
 *     | XDG_DATA_DIRS    | ---> | <dir>/thumbnailers/(*.thumbnailer)
2989
 *     +------------------+      +---------------------------+
2990
 *
2991
 * The helper below expands both sources so that the caller can iterate
2992
 * through every known definition in order of precedence.
2993
 *
2994
 * Arguments:
2995
 *     None.
2996
 * Returns:
2997
 *     Newly allocated list of directory paths or NULL on failure.
2998
 */
2999
static struct thumbnailer_string_list *
3000
thumbnailer_collect_directories(void)
9✔
3001
{
3002
    struct thumbnailer_string_list *dirs;
3003
    char const *xdg_data_dirs;
3004
    char const *home_dir;
3005
    char const *default_dirs;
3006
    char *candidate;
3007
    char *local_share;
3008
    char *dirs_copy;
3009
    char *token;
3010

3011
    dirs = NULL;
9✔
3012
    xdg_data_dirs = NULL;
9✔
3013
    home_dir = NULL;
9✔
3014
    default_dirs = NULL;
9✔
3015
    candidate = NULL;
9✔
3016
    local_share = NULL;
9✔
3017
    dirs_copy = NULL;
9✔
3018
    token = NULL;
9✔
3019

3020
    dirs = thumbnailer_string_list_new();
9✔
3021
    if (dirs == NULL) {
9!
3022
        return NULL;
×
3023
    }
3024

3025
    home_dir = getenv("HOME");
9✔
3026
    loader_trace_message(
15!
3027
        "thumbnailer_collect_directories: HOME=%s",
3028
        (home_dir != NULL && home_dir[0] != '\0') ? home_dir : "(unset)");
9!
3029
    if (home_dir != NULL && home_dir[0] != '\0') {
9!
3030
        local_share = thumbnailer_join_paths(home_dir,
9✔
3031
                                             ".local/share");
3032
        if (local_share != NULL) {
9!
3033
            candidate = thumbnailer_join_paths(local_share,
9✔
3034
                                               "thumbnailers");
3035
            if (candidate != NULL) {
9!
3036
                if (!thumbnailer_string_list_append(dirs, candidate)) {
9!
3037
                    free(candidate);
×
3038
                    free(local_share);
×
3039
                    thumbnailer_string_list_free(dirs);
×
3040
                    return NULL;
×
3041
                }
3042
                loader_trace_message(
9✔
3043
                    "thumbnailer_collect_directories: added %s",
3044
                    candidate);
3✔
3045
                free(candidate);
9✔
3046
                candidate = NULL;
9✔
3047
            }
3✔
3048
            free(local_share);
9✔
3049
            local_share = NULL;
9✔
3050
        }
3✔
3051
    }
3✔
3052

3053
    xdg_data_dirs = getenv("XDG_DATA_DIRS");
12✔
3054
    if (xdg_data_dirs == NULL || xdg_data_dirs[0] == '\0') {
12!
3055
        default_dirs = "/usr/local/share:/usr/share";
9✔
3056
        xdg_data_dirs = default_dirs;
9✔
3057
    }
3✔
3058
    loader_trace_message(
9✔
3059
        "thumbnailer_collect_directories: XDG_DATA_DIRS=%s",
3060
        xdg_data_dirs);
3✔
3061

3062
    dirs_copy = thumbnailer_strdup(xdg_data_dirs);
9✔
3063
    if (dirs_copy == NULL) {
9!
3064
        thumbnailer_string_list_free(dirs);
×
3065
        return NULL;
×
3066
    }
3067
    token = strtok(dirs_copy, ":");
9✔
3068
    while (token != NULL) {
27✔
3069
        candidate = thumbnailer_join_paths(token, "thumbnailers");
18✔
3070
        if (candidate != NULL) {
18!
3071
            if (!thumbnailer_string_list_append(dirs, candidate)) {
18!
3072
                free(candidate);
×
3073
                free(dirs_copy);
×
3074
                thumbnailer_string_list_free(dirs);
×
3075
                return NULL;
×
3076
            }
3077
            loader_trace_message(
18✔
3078
                "thumbnailer_collect_directories: added %s",
3079
                candidate);
6✔
3080
            free(candidate);
18✔
3081
            candidate = NULL;
18✔
3082
        }
6✔
3083
        token = strtok(NULL, ":");
18✔
3084
    }
3085
    free(dirs_copy);
9✔
3086
    dirs_copy = NULL;
9✔
3087

3088
    return dirs;
9✔
3089
}
3✔
3090

3091
/*
3092
 * thumbnailer_trim_right
3093
 *
3094
 * Remove trailing whitespace in place from a mutable string.
3095
 *
3096
 * Arguments:
3097
 *     text - string to trim; must be writable and zero-terminated.
3098
 */
3099
static void
3100
thumbnailer_trim_right(char *text)
12✔
3101
{
3102
    size_t length;
3103

3104
    length = 0;
12✔
3105

3106
    if (text == NULL) {
12!
3107
        return;
×
3108
    }
3109

3110
    length = strlen(text);
12✔
3111
    while (length > 0 && isspace((unsigned char)text[length - 1]) != 0) {
24!
3112
        text[length - 1] = '\0';
12✔
3113
        length -= 1;
12✔
3114
    }
3115
}
4✔
3116

3117
/*
3118
 * thumbnailer_trim_left
3119
 *
3120
 * Skip leading whitespace so parsers can focus on significant tokens.
3121
 *
3122
 * Arguments:
3123
 *     text - string to inspect; may be NULL.
3124
 * Returns:
3125
 *     Pointer to first non-space character or NULL when input is NULL.
3126
 */
3127
static char *
3128
thumbnailer_trim_left(char *text)
12✔
3129
{
3130
    if (text == NULL) {
12!
3131
        return NULL;
×
3132
    }
3133

3134
    while (*text != '\0' && isspace((unsigned char)*text) != 0) {
12!
3135
        text += 1;
×
3136
    }
3137

3138
    return text;
12✔
3139
}
4✔
3140

3141
/*
3142
 * thumbnailer_parse_file
3143
 *
3144
 * Populate a thumbnailer_entry by parsing a .thumbnailer ini file.
3145
 *
3146
 * Arguments:
3147
 *     path  - filesystem path to the ini file.
3148
 *     entry - output structure initialized with thumbnailer_entry_init().
3149
 * Returns:
3150
 *     1 on success, 0 on parse error or allocation failure.
3151
 */
3152
static int
3153
thumbnailer_parse_file(char const *path, struct thumbnailer_entry *entry)
×
3154
{
3155
    FILE *fp;
3156
    char line[1024];
3157
    int in_group;
3158
    char *trimmed;
3159
    char *key_end;
3160
    char *value;
3161
    char *token_start;
3162
    char *token_end;
3163
    struct thumbnailer_string_list *mime_types;
3164
    size_t index;
3165

3166
    fp = NULL;
×
3167
    in_group = 0;
×
3168
    trimmed = NULL;
×
3169
    key_end = NULL;
×
3170
    value = NULL;
×
3171
    token_start = NULL;
×
3172
    token_end = NULL;
×
3173
    mime_types = NULL;
×
3174
    index = 0;
×
3175

3176
    if (path == NULL || entry == NULL) {
×
3177
        return 0;
×
3178
    }
3179

3180
    fp = fopen(path, "r");
×
3181
    if (fp == NULL) {
×
3182
        return 0;
×
3183
    }
3184

3185
    mime_types = thumbnailer_string_list_new();
×
3186
    if (mime_types == NULL) {
×
3187
        fclose(fp);
×
3188
        fp = NULL;
×
3189
        return 0;
×
3190
    }
3191

3192
    while (fgets(line, sizeof(line), fp) != NULL) {
×
3193
        trimmed = thumbnailer_trim_left(line);
×
3194
        thumbnailer_trim_right(trimmed);
×
3195
        if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') {
×
3196
            continue;
×
3197
        }
3198
        if (trimmed[0] == '[') {
×
3199
            key_end = strchr(trimmed, ']');
×
3200
            if (key_end != NULL) {
×
3201
                *key_end = '\0';
×
3202
                if (strcmp(trimmed + 1, "Thumbnailer Entry") == 0) {
×
3203
                    in_group = 1;
×
3204
                } else {
3205
                    in_group = 0;
×
3206
                }
3207
            }
3208
            continue;
×
3209
        }
3210
        if (!in_group) {
×
3211
            continue;
×
3212
        }
3213
        key_end = strchr(trimmed, '=');
×
3214
        if (key_end == NULL) {
×
3215
            continue;
×
3216
        }
3217
        *key_end = '\0';
×
3218
        value = thumbnailer_trim_left(key_end + 1);
×
3219
        thumbnailer_trim_right(trimmed);
×
3220
        thumbnailer_trim_right(value);
×
3221
        if (strcmp(trimmed, "Exec") == 0) {
×
3222
            free(entry->exec_line);
×
3223
            entry->exec_line = thumbnailer_strdup(value);
×
3224
            if (entry->exec_line == NULL) {
×
3225
                fclose(fp);
×
3226
                fp = NULL;
×
3227
                thumbnailer_string_list_free(mime_types);
×
3228
                mime_types = NULL;
×
3229
                return 0;
×
3230
            }
3231
        } else if (strcmp(trimmed, "TryExec") == 0) {
×
3232
            free(entry->tryexec);
×
3233
            entry->tryexec = thumbnailer_strdup(value);
×
3234
            if (entry->tryexec == NULL) {
×
3235
                fclose(fp);
×
3236
                fp = NULL;
×
3237
                thumbnailer_string_list_free(mime_types);
×
3238
                mime_types = NULL;
×
3239
                return 0;
×
3240
            }
3241
        } else if (strcmp(trimmed, "MimeType") == 0) {
×
3242
            for (index = 0; index < mime_types->length; ++index) {
×
3243
                free(mime_types->items[index]);
×
3244
                mime_types->items[index] = NULL;
×
3245
            }
3246
            mime_types->length = 0;
×
3247
            token_start = value;
×
3248
            while (token_start != NULL && token_start[0] != '\0') {
×
3249
                token_end = strchr(token_start, ';');
×
3250
                if (token_end != NULL) {
×
3251
                    *token_end = '\0';
×
3252
                }
3253
                token_start = thumbnailer_trim_left(token_start);
×
3254
                thumbnailer_trim_right(token_start);
×
3255
                if (token_start[0] != '\0') {
×
3256
                    if (!thumbnailer_string_list_append(mime_types,
×
3257
                                                       token_start)) {
3258
                        fclose(fp);
×
3259
                        fp = NULL;
×
3260
                        thumbnailer_string_list_free(mime_types);
×
3261
                        mime_types = NULL;
×
3262
                        return 0;
×
3263
                    }
3264
                }
3265
                if (token_end == NULL) {
×
3266
                    break;
×
3267
                }
3268
                token_start = token_end + 1;
×
3269
            }
3270
        }
3271
    }
3272

3273
    fclose(fp);
×
3274
    fp = NULL;
×
3275

3276
    thumbnailer_string_list_free(entry->mime_types);
×
3277
    entry->mime_types = mime_types;
×
3278

3279
    return 1;
×
3280
}
3281

3282
/*
3283
 * thumbnailer_has_tryexec
3284
 *
3285
 * Confirm that the optional TryExec binary exists and is executable.
3286
 *
3287
 * Arguments:
3288
 *     tryexec - value from the .thumbnailer file; may be NULL.
3289
 * Returns:
3290
 *     1 when executable, 0 otherwise.
3291
 */
3292
static int
3293
thumbnailer_has_tryexec(char const *tryexec)
×
3294
{
3295
    char const *path_variable;
3296
    char const *start;
3297
    char const *end;
3298
    size_t length;
3299
    char *candidate;
3300
    int executable;
3301

3302
    path_variable = NULL;
×
3303
    start = NULL;
×
3304
    end = NULL;
×
3305
    length = 0;
×
3306
    candidate = NULL;
×
3307
    executable = 0;
×
3308

3309
    if (tryexec == NULL || tryexec[0] == '\0') {
×
3310
        return 1;
×
3311
    }
3312

3313
    if (strchr(tryexec, '/') != NULL) {
×
3314
        if (access(tryexec, X_OK) == 0) {
×
3315
            return 1;
×
3316
        }
3317
        return 0;
×
3318
    }
3319

3320
    path_variable = getenv("PATH");
×
3321
    if (path_variable == NULL) {
×
3322
        return 0;
×
3323
    }
3324

3325
    start = path_variable;
×
3326
    while (*start != '\0') {
×
3327
        end = strchr(start, ':');
×
3328
        if (end == NULL) {
×
3329
            end = start + strlen(start);
×
3330
        }
3331
        length = (size_t)(end - start);
×
3332
        candidate = malloc(length + strlen(tryexec) + 2);
×
3333
        if (candidate == NULL) {
×
3334
            return 0;
×
3335
        }
3336
        memcpy(candidate, start, length);
×
3337
        candidate[length] = '/';
×
3338
        strcpy(candidate + length + 1, tryexec);
×
3339
        if (access(candidate, X_OK) == 0) {
×
3340
            executable = 1;
×
3341
            free(candidate);
×
3342
            candidate = NULL;
×
3343
            break;
×
3344
        }
3345
        free(candidate);
×
3346
        candidate = NULL;
×
3347
        if (*end == '\0') {
×
3348
            break;
×
3349
        }
3350
        start = end + 1;
×
3351
    }
3352

3353
    return executable;
×
3354
}
3355

3356
/*
3357
 * thumbnailer_mime_matches
3358
 *
3359
 * Test whether a thumbnailer MIME pattern matches the probed MIME type.
3360
 *
3361
 * Arguments:
3362
 *     pattern   - literal MIME pattern or prefix ending with "slash-asterisk".
3363
 *     mime_type - MIME value obtained from file --mime-type.
3364
 * Returns:
3365
 *     1 when the pattern applies, 0 otherwise.
3366
 */
3367
static int
3368
thumbnailer_mime_matches(char const *pattern, char const *mime_type)
×
3369
{
3370
    size_t length;
3371

3372
    length = 0;
×
3373

3374
    if (pattern == NULL || mime_type == NULL) {
×
3375
        return 0;
×
3376
    }
3377

3378
    if (strcmp(pattern, mime_type) == 0) {
×
3379
        return 1;
×
3380
    }
3381

3382
    length = strlen(pattern);
×
3383
    if (length >= 2 && pattern[length - 1] == '*' &&
×
3384
            pattern[length - 2] == '/') {
×
3385
        return strncmp(pattern, mime_type, length - 1) == 0;
×
3386
    }
3387

3388
    return 0;
×
3389
}
3390

3391
/*
3392
 * thumbnailer_supports_mime
3393
 *
3394
 * Iterate over MIME patterns advertised by a thumbnailer entry.
3395
 *
3396
 * Arguments:
3397
 *     entry     - parsed thumbnailer entry with mime_types list.
3398
 *     mime_type - MIME type string to match.
3399
 * Returns:
3400
 *     1 when a match is found, 0 otherwise.
3401
 */
3402
static int
3403
thumbnailer_supports_mime(struct thumbnailer_entry *entry,
×
3404
                          char const *mime_type)
3405
{
3406
    size_t index;
3407

3408
    index = 0;
×
3409

3410
    if (entry == NULL || entry->mime_types == NULL) {
×
3411
        return 0;
×
3412
    }
3413

3414
    if (mime_type == NULL) {
×
3415
        return 0;
×
3416
    }
3417

3418
    for (index = 0; index < entry->mime_types->length; ++index) {
×
3419
        if (thumbnailer_mime_matches(entry->mime_types->items[index],
×
3420
                                     mime_type)) {
3421
            return 1;
×
3422
        }
3423
    }
3424

3425
    return 0;
×
3426
}
3427

3428
/*
3429
 * thumbnailer_shell_quote
3430
 *
3431
 * Produce a single-quoted variant of an argument for readable logging.
3432
 *
3433
 * Arguments:
3434
 *     text - unquoted argument.
3435
 * Returns:
3436
 *     Newly allocated quoted string or NULL on allocation failure.
3437
 */
3438
static char *
3439
thumbnailer_shell_quote(char const *text)
×
3440
{
3441
    size_t index;
3442
    size_t length;
3443
    size_t needed;
3444
    char *quoted;
3445
    size_t position;
3446

3447
    index = 0;
×
3448
    length = 0;
×
3449
    needed = 0;
×
3450
    quoted = NULL;
×
3451
    position = 0;
×
3452

3453
    if (text == NULL) {
×
3454
        return NULL;
×
3455
    }
3456

3457
    length = strlen(text);
×
3458
    needed = 2;
×
3459
    for (index = 0; index < length; ++index) {
×
3460
        if (text[index] == '\'') {
×
3461
            needed += 4;
×
3462
        } else {
3463
            needed += 1;
×
3464
        }
3465
    }
3466

3467
    quoted = malloc(needed + 1);
×
3468
    if (quoted == NULL) {
×
3469
        return NULL;
×
3470
    }
3471

3472
    quoted[position++] = '\'';
×
3473
    for (index = 0; index < length; ++index) {
×
3474
        if (text[index] == '\'') {
×
3475
            quoted[position++] = '\'';
×
3476
            quoted[position++] = '\\';
×
3477
            quoted[position++] = '\'';
×
3478
            quoted[position++] = '\'';
×
3479
        } else {
3480
            quoted[position++] = text[index];
×
3481
        }
3482
    }
3483
    quoted[position++] = '\'';
×
3484
    quoted[position] = '\0';
×
3485

3486
    return quoted;
×
3487
}
3488

3489
struct thumbnailer_builder {
3490
    char *buffer;
3491
    size_t length;
3492
    size_t capacity;
3493
};
3494

3495
/*
3496
 * thumbnailer_builder_reserve
3497
 *
3498
 * Grow the builder buffer so future appends fit without overflow.
3499
 *
3500
 * Arguments:
3501
 *     builder    - mutable builder instance.
3502
 *     additional - number of bytes that must fit excluding terminator.
3503
 * Returns:
3504
 *     1 on success, 0 on allocation failure.
3505
 */
3506
static int
3507
thumbnailer_builder_reserve(struct thumbnailer_builder *builder,
×
3508
                            size_t additional)
3509
{
3510
    size_t new_capacity;
3511
    char *new_buffer;
3512

3513
    new_capacity = 0;
×
3514
    new_buffer = NULL;
×
3515

3516
    if (builder->length + additional + 1 <= builder->capacity) {
×
3517
        return 1;
×
3518
    }
3519

3520
    new_capacity = (builder->capacity == 0) ? 64 : builder->capacity;
×
3521
    while (new_capacity < builder->length + additional + 1) {
×
3522
        new_capacity *= 2;
×
3523
    }
3524

3525
    new_buffer = realloc(builder->buffer, new_capacity);
×
3526
    if (new_buffer == NULL) {
×
3527
        return 0;
×
3528
    }
3529

3530
    builder->buffer = new_buffer;
×
3531
    builder->capacity = new_capacity;
×
3532

3533
    return 1;
×
3534
}
3535

3536
/*
3537
 * thumbnailer_builder_append_char
3538
 *
3539
 * Append a single character to the builder.
3540
 *
3541
 * Arguments:
3542
 *     builder - mutable builder instance.
3543
 *     ch      - character to append.
3544
 * Returns:
3545
 *     1 on success, 0 on allocation failure.
3546
 */
3547
static int
3548
thumbnailer_builder_append_char(struct thumbnailer_builder *builder,
×
3549
                                char ch)
3550
{
3551
    if (!thumbnailer_builder_reserve(builder, 1)) {
×
3552
        return 0;
×
3553
    }
3554

3555
    builder->buffer[builder->length] = ch;
×
3556
    builder->length += 1;
×
3557
    builder->buffer[builder->length] = '\0';
×
3558

3559
    return 1;
×
3560
}
3561

3562
/*
3563
 * thumbnailer_builder_append
3564
 *
3565
 * Append a string of known length to the builder buffer.
3566
 *
3567
 * Arguments:
3568
 *     builder - mutable builder instance.
3569
 *     text    - zero-terminated string to append.
3570
 * Returns:
3571
 *     1 on success, 0 on allocation failure or NULL input.
3572
 */
3573
static int
3574
thumbnailer_builder_append(struct thumbnailer_builder *builder,
×
3575
                           char const *text)
3576
{
3577
    size_t length;
3578

3579
    length = 0;
×
3580

3581
    if (text == NULL) {
×
3582
        return 1;
×
3583
    }
3584

3585
    length = strlen(text);
×
3586
    if (!thumbnailer_builder_reserve(builder, length)) {
×
3587
        return 0;
×
3588
    }
3589

3590
    memcpy(builder->buffer + builder->length, text, length);
×
3591
    builder->length += length;
×
3592
    builder->buffer[builder->length] = '\0';
×
3593

3594
    return 1;
×
3595
}
3596

3597
/*
3598
 * thumbnailer_builder_clear
3599
 *
3600
 * Reset builder length to zero while retaining allocated storage.
3601
 *
3602
 * Arguments:
3603
 *     builder - builder to reset.
3604
 */
3605
static void
3606
thumbnailer_builder_clear(struct thumbnailer_builder *builder)
×
3607
{
3608
    if (builder->buffer != NULL) {
×
3609
        builder->buffer[0] = '\0';
×
3610
    }
3611
    builder->length = 0;
×
3612
}
×
3613

3614
/*
3615
 * thumbnailer_command owns the argv array that will be passed to the
3616
 * thumbnailer helper.  The display field keeps a human readable command line
3617
 * for verbose logging without recomputing the shell quoted form.
3618
 */
3619
struct thumbnailer_command {
3620
    char **argv;
3621
    size_t argc;
3622
    char *display;
3623
};
3624

3625
/*
3626
 * thumbnailer_command_free
3627
 *
3628
 * Release argv entries, the array itself, and the formatted display copy.
3629
 *
3630
 * Arguments:
3631
 *     command - structure created by thumbnailer_build_command().
3632
 */
3633
static void
3634
thumbnailer_command_free(struct thumbnailer_command *command)
×
3635
{
3636
    size_t index;
3637

3638
    if (command == NULL) {
×
3639
        return;
×
3640
    }
3641

3642
    if (command->argv != NULL) {
×
3643
        for (index = 0; index < command->argc; ++index) {
×
3644
            free(command->argv[index]);
×
3645
            command->argv[index] = NULL;
×
3646
        }
3647
        free(command->argv);
×
3648
        command->argv = NULL;
×
3649
    }
3650

3651
    free(command->display);
×
3652
    command->display = NULL;
×
3653

3654
    free(command);
×
3655
}
3656

3657
/*
3658
 * thumbnailer_command_format
3659
 *
3660
 * Join argv entries into a human-readable command line for logging.
3661
 *
3662
 * Arguments:
3663
 *     argv - array of argument strings.
3664
 *     argc - number of entries stored in argv.
3665
 * Returns:
3666
 *     Newly allocated formatted string or NULL on allocation failure.
3667
 */
3668
static char *
3669
thumbnailer_command_format(char **argv, size_t argc)
×
3670
{
3671
    struct thumbnailer_builder builder;
3672
    char *quoted;
3673
    size_t index;
3674

3675
    builder.buffer = NULL;
×
3676
    builder.length = 0;
×
3677
    builder.capacity = 0;
×
3678
    quoted = NULL;
×
3679

3680
    for (index = 0; index < argc; ++index) {
×
3681
        if (index > 0) {
×
3682
            if (!thumbnailer_builder_append_char(&builder, ' ')) {
×
3683
                free(builder.buffer);
×
3684
                builder.buffer = NULL;
×
3685
                return NULL;
×
3686
            }
3687
        }
3688
        quoted = thumbnailer_shell_quote(argv[index]);
×
3689
        if (quoted == NULL) {
×
3690
            free(builder.buffer);
×
3691
            builder.buffer = NULL;
×
3692
            return NULL;
×
3693
        }
3694
        if (!thumbnailer_builder_append(&builder, quoted)) {
×
3695
            free(quoted);
×
3696
            quoted = NULL;
×
3697
            free(builder.buffer);
×
3698
            builder.buffer = NULL;
×
3699
            return NULL;
×
3700
        }
3701
        free(quoted);
×
3702
        quoted = NULL;
×
3703
    }
3704

3705
    return builder.buffer;
×
3706
}
3707

3708
/*
3709
 * thumbnailer_build_command
3710
 *
3711
 * Expand a .thumbnailer Exec template into an argv array that honours
3712
 * FreeDesktop substitution rules.
3713
 *
3714
 * Arguments:
3715
 *     template_command - Exec line containing % tokens.
3716
 *     input_path       - filesystem path to the source document.
3717
 *     input_uri        - URI representation for %u expansions.
3718
 *     output_path      - PNG destination path for %o expansions.
3719
 *     size             - numeric size hint passed to %s tokens.
3720
 *     mime_type        - MIME value for %m replacements.
3721
 * Returns:
3722
 *     Newly allocated command or NULL on parse/allocation failure.
3723
 */
3724
static struct thumbnailer_command *
3725
thumbnailer_build_command(char const *template_command,
×
3726
                          char const *input_path,
3727
                          char const *input_uri,
3728
                          char const *output_path,
3729
                          int size,
3730
                          char const *mime_type)
3731
{
3732
    struct thumbnailer_builder builder;
3733
    struct thumbnailer_string_list *tokens;
3734
    struct thumbnailer_command *command;
3735
    char const *ptr;
3736
    char size_text[16];
3737
    int in_single_quote;
3738
    int in_double_quote;
3739
    int escape_next;
3740
    char const *replacement;
3741
    size_t index;
3742
    int written;
3743

3744
    builder.buffer = NULL;
×
3745
    builder.length = 0;
×
3746
    builder.capacity = 0;
×
3747
    tokens = NULL;
×
3748
    command = NULL;
×
3749
    ptr = template_command;
×
3750
    size_text[0] = '\0';
×
3751
    in_single_quote = 0;
×
3752
    in_double_quote = 0;
×
3753
    escape_next = 0;
×
3754
    replacement = NULL;
×
3755
    index = 0;
×
3756

3757
    if (template_command == NULL) {
×
3758
        return NULL;
×
3759
    }
3760

3761
    tokens = thumbnailer_string_list_new();
×
3762
    if (tokens == NULL) {
×
3763
        return NULL;
×
3764
    }
3765

3766
    if (size > 0) {
×
3767
        written = sixel_compat_snprintf(size_text,
×
3768
                                        sizeof(size_text),
3769
                                        "%d",
3770
                                        size);
3771
        if (written < 0) {
×
3772
            goto error;
×
3773
        }
3774
        if ((size_t)written >= sizeof(size_text)) {
×
3775
            size_text[sizeof(size_text) - 1u] = '\0';
×
3776
        }
3777
    }
3778

3779
    while (ptr != NULL && ptr[0] != '\0') {
×
3780
        if (!in_single_quote && !in_double_quote && escape_next == 0 &&
×
3781
                (ptr[0] == ' ' || ptr[0] == '\t')) {
×
3782
            if (builder.length > 0) {
×
3783
                if (!thumbnailer_string_list_append(tokens,
×
3784
                                                    builder.buffer)) {
×
3785
                    goto error;
×
3786
                }
3787
                thumbnailer_builder_clear(&builder);
×
3788
            }
3789
            ptr += 1;
×
3790
            continue;
×
3791
        }
3792
        if (!in_single_quote && escape_next == 0 && ptr[0] == '\\') {
×
3793
            escape_next = 1;
×
3794
            ptr += 1;
×
3795
            continue;
×
3796
        }
3797
        if (!in_double_quote && escape_next == 0 && ptr[0] == '\'') {
×
3798
            in_single_quote = !in_single_quote;
×
3799
            ptr += 1;
×
3800
            continue;
×
3801
        }
3802
        if (!in_single_quote && escape_next == 0 && ptr[0] == '"') {
×
3803
            in_double_quote = !in_double_quote;
×
3804
            ptr += 1;
×
3805
            continue;
×
3806
        }
3807
        if (escape_next != 0) {
×
3808
            if (!thumbnailer_builder_append_char(&builder, ptr[0])) {
×
3809
                goto error;
×
3810
            }
3811
            escape_next = 0;
×
3812
            ptr += 1;
×
3813
            continue;
×
3814
        }
3815
        if (ptr[0] == '%' && ptr[1] != '\0') {
×
3816
            replacement = NULL;
×
3817
            ptr += 1;
×
3818
            switch (ptr[0]) {
×
3819
            case '%':
3820
                if (!thumbnailer_builder_append_char(&builder, '%')) {
×
3821
                    goto error;
×
3822
                }
3823
                break;
×
3824
            case 'i':
3825
            case 'I':
3826
                replacement = input_path;
×
3827
                break;
×
3828
            case 'u':
3829
            case 'U':
3830
                replacement = input_uri;
×
3831
                break;
×
3832
            case 'o':
3833
            case 'O':
3834
                replacement = output_path;
×
3835
                break;
×
3836
            case 's':
3837
            case 'S':
3838
                replacement = size_text;
×
3839
                break;
×
3840
            case 'm':
3841
            case 'M':
3842
                replacement = mime_type;
×
3843
                break;
×
3844
            default:
3845
                if (!thumbnailer_builder_append_char(&builder, '%') ||
×
3846
                        !thumbnailer_builder_append_char(&builder,
×
3847
                                                         ptr[0])) {
×
3848
                    goto error;
×
3849
                }
3850
                break;
×
3851
            }
3852
            if (replacement != NULL) {
×
3853
                if (!thumbnailer_builder_append(&builder, replacement)) {
×
3854
                    goto error;
×
3855
                }
3856
            }
3857
            ptr += 1;
×
3858
            continue;
×
3859
        }
3860
        if (!thumbnailer_builder_append_char(&builder, ptr[0])) {
×
3861
            goto error;
×
3862
        }
3863
        ptr += 1;
×
3864
    }
3865

3866
    if (builder.length > 0) {
×
3867
        if (!thumbnailer_string_list_append(tokens, builder.buffer)) {
×
3868
            goto error;
×
3869
        }
3870
    }
3871

3872
    command = malloc(sizeof(*command));
×
3873
    if (command == NULL) {
×
3874
        goto error;
×
3875
    }
3876

3877
    command->argc = tokens->length;
×
3878
    command->argv = NULL;
×
3879
    command->display = NULL;
×
3880

3881
    if (tokens->length == 0) {
×
3882
        goto error;
×
3883
    }
3884

3885
    command->argv = malloc(sizeof(char *) * (tokens->length + 1));
×
3886
    if (command->argv == NULL) {
×
3887
        goto error;
×
3888
    }
3889

3890
    for (index = 0; index < tokens->length; ++index) {
×
3891
        command->argv[index] = thumbnailer_strdup(tokens->items[index]);
×
3892
        if (command->argv[index] == NULL) {
×
3893
            goto error;
×
3894
        }
3895
    }
3896
    command->argv[tokens->length] = NULL;
×
3897

3898
    command->display = thumbnailer_command_format(command->argv,
×
3899
                                                  command->argc);
3900
    if (command->display == NULL) {
×
3901
        goto error;
×
3902
    }
3903

3904
    thumbnailer_string_list_free(tokens);
×
3905
    tokens = NULL;
×
3906
    if (builder.buffer != NULL) {
×
3907
        free(builder.buffer);
×
3908
        builder.buffer = NULL;
×
3909
    }
3910

3911
    return command;
×
3912

3913
error:
3914
    if (tokens != NULL) {
×
3915
        thumbnailer_string_list_free(tokens);
×
3916
        tokens = NULL;
×
3917
    }
3918
    if (builder.buffer != NULL) {
×
3919
        free(builder.buffer);
×
3920
        builder.buffer = NULL;
×
3921
    }
3922
    if (command != NULL) {
×
3923
        thumbnailer_command_free(command);
×
3924
        command = NULL;
×
3925
    }
3926

3927
    return NULL;
×
3928
}
3929

3930
/*
3931
 * thumbnailer_is_evince_thumbnailer
3932
 *
3933
 * Detect whether the selected thumbnailer maps to evince-thumbnailer so
3934
 * the stdout redirection workaround can be applied.
3935
 *
3936
 * Arguments:
3937
 *     exec_line - Exec string parsed from the .thumbnailer file.
3938
 *     tryexec   - optional TryExec value for additional matching.
3939
 * Returns:
3940
 *     1 when evince-thumbnailer is referenced, 0 otherwise.
3941
 */
3942
static int
3943
thumbnailer_is_evince_thumbnailer(char const *exec_line,
×
3944
                                  char const *tryexec)
3945
{
3946
    char const *needle;
3947
    char const *basename;
3948

3949
    needle = "evince-thumbnailer";
×
3950
    basename = NULL;
×
3951

3952
    if (exec_line != NULL && strstr(exec_line, needle) != NULL) {
×
3953
        return 1;
×
3954
    }
3955

3956
    if (tryexec != NULL) {
×
3957
        basename = strrchr(tryexec, '/');
×
3958
        if (basename != NULL) {
×
3959
            basename += 1;
×
3960
        } else {
3961
            basename = tryexec;
×
3962
        }
3963
        if (strcmp(basename, needle) == 0) {
×
3964
            return 1;
×
3965
        }
3966
        if (strstr(tryexec, needle) != NULL) {
×
3967
            return 1;
×
3968
        }
3969
    }
3970

3971
    return 0;
×
3972
}
3973

3974
/*
3975
 * thumbnailer_build_evince_command
3976
 *
3977
 * Construct an argv sequence that streams evince-thumbnailer output to
3978
 * stdout so downstream code can capture the PNG safely.
3979
 *
3980
 * Arguments:
3981
 *     input_path - source document path.
3982
 *     size       - numeric size hint forwarded to the -s option.
3983
 * Returns:
3984
 *     Newly allocated command or NULL on allocation failure.
3985
 */
3986
static struct thumbnailer_command *
3987
thumbnailer_build_evince_command(char const *input_path,
×
3988
                                 int size)
3989
{
3990
    struct thumbnailer_command *command;
3991
    char size_text[16];
3992
    size_t index;
3993
    int written;
3994

3995
    command = NULL;
×
3996
    index = 0;
×
3997

3998
    if (input_path == NULL) {
×
3999
        return NULL;
×
4000
    }
4001

4002
    command = malloc(sizeof(*command));
×
4003
    if (command == NULL) {
×
4004
        return NULL;
×
4005
    }
4006

4007
    command->argc = 5;
×
4008
    command->argv = malloc(sizeof(char *) * (command->argc + 1));
×
4009
    if (command->argv == NULL) {
×
4010
        thumbnailer_command_free(command);
×
4011
        return NULL;
×
4012
    }
4013

4014
    for (index = 0; index < command->argc + 1; ++index) {
×
4015
        command->argv[index] = NULL;
×
4016
    }
4017

4018
    written = sixel_compat_snprintf(size_text,
×
4019
                                    sizeof(size_text),
4020
                                    "%d",
4021
                                    size);
4022
    if (written < 0) {
×
4023
        size_text[0] = '\0';
×
4024
    } else if ((size_t)written >= sizeof(size_text)) {
×
4025
        size_text[sizeof(size_text) - 1u] = '\0';
×
4026
    }
4027

4028
    command->argv[0] = thumbnailer_strdup("evince-thumbnailer");
×
4029
    command->argv[1] = thumbnailer_strdup("-s");
×
4030
    command->argv[2] = thumbnailer_strdup(size_text);
×
4031
    command->argv[3] = thumbnailer_strdup(input_path);
×
4032
    command->argv[4] = thumbnailer_strdup("/dev/stdout");
×
4033
    command->argv[5] = NULL;
×
4034

4035
    for (index = 0; index < command->argc; ++index) {
×
4036
        if (command->argv[index] == NULL) {
×
4037
            thumbnailer_command_free(command);
×
4038
            return NULL;
×
4039
        }
4040
    }
4041

4042
    command->display = thumbnailer_command_format(command->argv,
×
4043
                                                  command->argc);
4044
    if (command->display == NULL) {
×
4045
        thumbnailer_command_free(command);
×
4046
        return NULL;
×
4047
    }
4048

4049
    return command;
×
4050
}
4051

4052
/*
4053
 * thumbnailer_build_file_uri
4054
 *
4055
 * Convert a filesystem path into a percent-encoded file:// URI.
4056
 *
4057
 * Arguments:
4058
 *     path - filesystem path; may be relative but will be resolved.
4059
 * Returns:
4060
 *     Newly allocated URI string or NULL on error.
4061
 */
4062
static char *
4063
thumbnailer_build_file_uri(char const *path)
×
4064
{
4065
    char *resolved;
4066
    size_t index;
4067
    size_t length;
4068
    size_t needed;
4069
    char *uri;
4070
    size_t position;
4071
    char const hex[] = "0123456789ABCDEF";
×
4072

4073
    resolved = NULL;
×
4074
    index = 0;
×
4075
    length = 0;
×
4076
    needed = 0;
×
4077
    uri = NULL;
×
4078
    position = 0;
×
4079

4080
    if (path == NULL) {
×
4081
        return NULL;
×
4082
    }
4083

4084
    resolved = thumbnailer_resolve_path(path);
×
4085
    if (resolved == NULL) {
×
4086
        return NULL;
×
4087
    }
4088

4089
    length = strlen(resolved);
×
4090
    needed = 7;
×
4091
    for (index = 0; index < length; ++index) {
×
4092
        unsigned char ch;
4093

4094
        ch = (unsigned char)resolved[index];
×
4095
        if (isalnum(ch) || ch == '-' || ch == '_' ||
×
4096
                ch == '.' || ch == '~' || ch == '/') {
×
4097
            needed += 1;
×
4098
        } else {
4099
            needed += 3;
×
4100
        }
4101
    }
4102

4103
    uri = malloc(needed + 1);
×
4104
    if (uri == NULL) {
×
4105
        free(resolved);
×
4106
        resolved = NULL;
×
4107
        return NULL;
×
4108
    }
4109

4110
    memcpy(uri, "file://", 7);
×
4111
    position = 7;
×
4112
    for (index = 0; index < length; ++index) {
×
4113
        unsigned char ch;
4114

4115
        ch = (unsigned char)resolved[index];
×
4116
        if (isalnum(ch) || ch == '-' || ch == '_' ||
×
4117
                ch == '.' || ch == '~' || ch == '/') {
×
4118
            uri[position++] = (char)ch;
×
4119
        } else {
4120
            uri[position++] = '%';
×
4121
            uri[position++] = hex[(ch >> 4) & 0xF];
×
4122
            uri[position++] = hex[ch & 0xF];
×
4123
        }
4124
    }
4125
    uri[position] = '\0';
×
4126

4127
    free(resolved);
×
4128
    resolved = NULL;
×
4129

4130
    return uri;
×
4131
}
4132

4133
/*
4134
 * thumbnailer_run_file
4135
 *
4136
 * Invoke the file(1) utility to collect metadata about the input path.
4137
 *
4138
 * Arguments:
4139
 *     path   - filesystem path forwarded to file(1).
4140
 *     option - optional argument appended after "-b".  Pass NULL to obtain
4141
 *              the human readable description and "--mime-type" for the
4142
 *              MIME identifier.
4143
 * Returns:
4144
 *     Newly allocated string trimmed of trailing whitespace or NULL on
4145
 *     failure.
4146
 */
4147
static char *
4148
thumbnailer_run_file(char const *path, char const *option)
12✔
4149
{
4150
    int pipefd[2];
4151
    pid_t pid;
4152
    ssize_t bytes_read;
4153
    char buffer[256];
4154
    size_t total;
4155
    int status;
4156
    char *result;
4157
    char *trimmed;
4158

4159
    pipefd[0] = -1;
12✔
4160
    pipefd[1] = -1;
12✔
4161
    pid = (-1);
12✔
4162
    bytes_read = 0;
12✔
4163
    total = 0;
12✔
4164
    status = 0;
12✔
4165
    result = NULL;
12✔
4166
    trimmed = NULL;
12✔
4167

4168
    if (path == NULL) {
12!
4169
        return NULL;
×
4170
    }
4171

4172
    if (pipe(pipefd) < 0) {
12!
4173
        return NULL;
×
4174
    }
4175

4176
    pid = fork();
12✔
4177
    if (pid < 0) {
20!
4178
        close(pipefd[0]);
×
4179
        close(pipefd[1]);
×
4180
        return NULL;
×
4181
    }
4182

4183
    if (pid == 0) {
24✔
4184
        char const *argv[6];
4185
        size_t arg_index;
4186

4187
        close(pipefd[0]);
12✔
4188
        if (dup2(pipefd[1], STDOUT_FILENO) < 0) {
12!
4189
            _exit(127);
×
4190
        }
4191
        close(pipefd[1]);
12✔
4192
        arg_index = 0u;
12✔
4193
        argv[arg_index++] = "file";
12✔
4194
        argv[arg_index++] = "-b";
12✔
4195
        if (option != NULL) {
12✔
4196
            argv[arg_index++] = option;
6✔
4197
        }
2✔
4198
        argv[arg_index++] = path;
12✔
4199
        argv[arg_index] = NULL;
12✔
4200
        execvp("file", (char * const *)argv);
12✔
4201
        _exit(127);
8✔
4202
    }
4203

4204
    close(pipefd[1]);
12✔
4205
    pipefd[1] = -1;
12✔
4206
    total = 0;
12✔
4207
    while ((bytes_read = read(pipefd[0], buffer + total,
24✔
4208
                              sizeof(buffer) - total - 1)) > 0) {
32✔
4209
        total += (size_t)bytes_read;
12✔
4210
        if (total >= sizeof(buffer) - 1) {
12!
4211
            break;
×
4212
        }
4213
    }
4214
    buffer[total] = '\0';
12✔
4215
    close(pipefd[0]);
12✔
4216
    pipefd[0] = -1;
12✔
4217

4218
    while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {
12!
4219
        continue;
×
4220
    }
4221

4222
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
12!
4223
        return NULL;
×
4224
    }
4225

4226
    trimmed = thumbnailer_trim_left(buffer);
12✔
4227
    thumbnailer_trim_right(trimmed);
12✔
4228
    if (trimmed[0] == '\0') {
12!
4229
        return NULL;
×
4230
    }
4231

4232
    result = thumbnailer_strdup(trimmed);
12✔
4233

4234
    return result;
12✔
4235
}
4✔
4236

4237
/*
4238
 * thumbnailer_guess_content_type
4239
 *
4240
 * Obtain the MIME identifier for the supplied path using file(1).
4241
 */
4242
static char *
4243
thumbnailer_guess_content_type(char const *path)
6✔
4244
{
4245
    return thumbnailer_run_file(path, "--mime-type");
6✔
4246
}
4247

4248
/*
4249
 * Thumbnailer supervision overview:
4250
 *
4251
 *     +-------------------+    pipe(stderr)    +-------------------+
4252
 *     | libsixel parent   | <----------------- | thumbnailer argv  |
4253
 *     |  - polls stdout   |                   |  - renders PNG     |
4254
 *     |  - polls stderr   | -----------------> |  - returns code   |
4255
 *     |  - waits pid      |    pipe(stdout)    |                   |
4256
 *     +-------------------+  posix_spawn/fork  +-------------------+
4257
 *
4258
 * Non-blocking pipes keep verbose thumbnailers from stalling the loop,
4259
 * and argv arrays mean Exec templates never pass through /bin/sh.
4260
 *
4261
 * thumbnailer_spawn is responsible for preparing pipes, launching the
4262
 * thumbnail helper, and streaming any emitted data back into libsixel.
4263
 *
4264
 *  - stderr is captured into stderr_output so verbose mode can replay the
4265
 *    diagnostics without leaking them to non-verbose invocations.
4266
 *  - stdout can optionally be redirected into a temporary file when
4267
 *    thumbnailers insist on writing image data to the standard output stream.
4268
 *  - All file descriptors are placed into non-blocking mode to avoid stalls
4269
 *    while the parent waits for the child process to complete.
4270
 * Arguments:
4271
 *     command          - prepared argv array to execute.
4272
 *     thumbnailer_name - friendly name used in log messages.
4273
 *     log_prefix       - identifier describing the current pipeline step.
4274
 *     capture_stdout   - non-zero to capture stdout into stdout_path.
4275
 *     stdout_path      - file receiving stdout when capture_stdout != 0.
4276
 * Returns:
4277
 *     SIXEL_OK on success or a libsixel error code on failure.
4278
 */
4279
static SIXELSTATUS
4280
thumbnailer_spawn(struct thumbnailer_command const *command,
×
4281
                  char const *thumbnailer_name,
4282
                  char const *log_prefix,
4283
                  int capture_stdout,
4284
                  char const *stdout_path)
4285
{
4286
    pid_t pid;
4287
    int status_code;
4288
    int wait_result;
4289
    SIXELSTATUS status;
4290
    char message[256];
4291
    int stderr_pipe[2];
4292
    int stdout_pipe[2];
4293
    int stderr_pipe_created;
4294
    int stdout_pipe_created;
4295
    int flags;
4296
    ssize_t read_result;
4297
    ssize_t stdout_read_result;
4298
    char stderr_buffer[256];
4299
    char stdout_buffer[4096];
4300
    char *stderr_output;
4301
    size_t stderr_length;
4302
    size_t stderr_capacity;
4303
    int child_exited;
4304
    int stderr_open;
4305
    int stdout_open;
4306
    int have_status;
4307
    int fatal_error;
4308
    int output_fd;
4309
    size_t write_offset;
4310
    ssize_t write_result;
4311
    size_t to_write;
4312
    char const *display_command;
4313
    int written;
4314
# if HAVE_POSIX_SPAWNP
4315
    posix_spawn_file_actions_t actions;
4316
    int spawn_result;
4317
# endif
4318

4319
    pid = (-1);
×
4320
    status_code = 0;
×
4321
    wait_result = 0;
×
4322
    status = SIXEL_RUNTIME_ERROR;
×
4323
    memset(message, 0, sizeof(message));
×
4324
    stderr_pipe[0] = -1;
×
4325
    stderr_pipe[1] = -1;
×
4326
    stdout_pipe[0] = -1;
×
4327
    stdout_pipe[1] = -1;
×
4328
    stderr_pipe_created = 0;
×
4329
    stdout_pipe_created = 0;
×
4330
    flags = 0;
×
4331
    read_result = 0;
×
4332
    stdout_read_result = 0;
×
4333
    stderr_output = NULL;
×
4334
    stderr_length = 0;
×
4335
    stderr_capacity = 0;
×
4336
    child_exited = 0;
×
4337
    stderr_open = 0;
×
4338
    stdout_open = 0;
×
4339
    have_status = 0;
×
4340
    fatal_error = 0;
×
4341
    output_fd = -1;
×
4342
    write_offset = 0;
×
4343
    write_result = 0;
×
4344
    to_write = 0;
×
4345
    display_command = NULL;
×
4346
# if HAVE_POSIX_SPAWNP
4347
    spawn_result = 0;
×
4348
# endif
4349

4350
    if (command == NULL || command->argv == NULL ||
×
4351
            command->argv[0] == NULL) {
×
4352
        return SIXEL_BAD_ARGUMENT;
×
4353
    }
4354

4355
    if (capture_stdout && stdout_path == NULL) {
×
4356
        return SIXEL_BAD_ARGUMENT;
×
4357
    }
4358

4359
    if (capture_stdout) {
×
4360
        output_fd = open(stdout_path,
×
4361
                         O_WRONLY | O_CREAT | O_TRUNC,
4362
                         0600);
4363
        if (output_fd < 0) {
×
4364
            written = sixel_compat_snprintf(message,
×
4365
                                            sizeof(message),
4366
                                            "%s: open(%s) failed (%s).",
4367
                                            log_prefix,
4368
                                            stdout_path,
4369
                                            strerror(errno));
×
4370
            thumbnailer_message_finalize(message,
×
4371
                                         sizeof(message),
4372
                                         written);
4373
            sixel_helper_set_additional_message(message);
×
4374
            goto cleanup;
×
4375
        }
4376
    }
4377

4378
    /* stderr is collected even for successful runs so verbose users can see
4379
     * the thumbnailer's own commentary.  Failing to set the pipe is not
4380
     * fatal; we continue without stderr capture in that case.
4381
     */
4382
    if (pipe(stderr_pipe) == 0) {
×
4383
        stderr_pipe_created = 1;
×
4384
        stderr_open = 1;
×
4385
    }
4386

4387
    if (capture_stdout) {
×
4388
        if (pipe(stdout_pipe) != 0) {
×
4389
            written = sixel_compat_snprintf(
×
4390
                message,
4391
                sizeof(message),
4392
                "%s: pipe() for stdout failed (%s).",
4393
                log_prefix,
4394
                strerror(errno));
×
4395
            thumbnailer_message_finalize(message,
×
4396
                                         sizeof(message),
4397
                                         written);
4398
            sixel_helper_set_additional_message(message);
×
4399
            goto cleanup;
×
4400
        }
4401
        stdout_pipe_created = 1;
×
4402
        stdout_open = 1;
×
4403
    }
4404

4405
    display_command = (command->display != NULL) ?
×
4406
            command->display : command->argv[0];
×
4407
    loader_trace_message("%s: executing %s",
×
4408
                         log_prefix,
4409
                         display_command);
4410

4411
# if HAVE_POSIX_SPAWNP
4412
    if (posix_spawn_file_actions_init(&actions) != 0) {
×
4413
        written = sixel_compat_snprintf(
×
4414
            message,
4415
            sizeof(message),
4416
            "%s: posix_spawn_file_actions_init() failed.",
4417
            log_prefix);
4418
        thumbnailer_message_finalize(message,
×
4419
                                     sizeof(message),
4420
                                     written);
4421
        sixel_helper_set_additional_message(message);
×
4422
        goto cleanup;
×
4423
    }
4424
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
×
4425
        (void)posix_spawn_file_actions_adddup2(&actions,
×
4426
                                               stderr_pipe[1],
4427
                                               STDERR_FILENO);
4428
        (void)posix_spawn_file_actions_addclose(&actions,
×
4429
                                                stderr_pipe[0]);
4430
        (void)posix_spawn_file_actions_addclose(&actions,
×
4431
                                                stderr_pipe[1]);
4432
    }
4433
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
×
4434
        (void)posix_spawn_file_actions_adddup2(&actions,
×
4435
                                               stdout_pipe[1],
4436
                                               STDOUT_FILENO);
4437
        (void)posix_spawn_file_actions_addclose(&actions,
×
4438
                                                stdout_pipe[0]);
4439
        (void)posix_spawn_file_actions_addclose(&actions,
×
4440
                                                stdout_pipe[1]);
4441
    }
4442
    if (output_fd >= 0) {
×
4443
        (void)posix_spawn_file_actions_addclose(&actions,
×
4444
                                                output_fd);
4445
    }
4446
    spawn_result = posix_spawnp(&pid,
×
4447
                                command->argv[0],
×
4448
                                &actions,
4449
                                NULL,
4450
                                (char * const *)command->argv,
×
4451
                                environ);
4452
    posix_spawn_file_actions_destroy(&actions);
×
4453
    if (spawn_result != 0) {
×
4454
        written = sixel_compat_snprintf(message,
×
4455
                                        sizeof(message),
4456
                                        "%s: posix_spawnp() failed (%s).",
4457
                                        log_prefix,
4458
                                        strerror(spawn_result));
4459
        thumbnailer_message_finalize(message,
×
4460
                                     sizeof(message),
4461
                                     written);
4462
        sixel_helper_set_additional_message(message);
×
4463
        goto cleanup;
×
4464
    }
4465
# else
4466
    pid = fork();
4467
    if (pid < 0) {
4468
        written = sixel_compat_snprintf(message,
4469
                                        sizeof(message),
4470
                                        "%s: fork() failed (%s).",
4471
                                        log_prefix,
4472
                                        strerror(errno));
4473
        thumbnailer_message_finalize(message,
4474
                                     sizeof(message),
4475
                                     written);
4476
        sixel_helper_set_additional_message(message);
4477
        goto cleanup;
4478
    }
4479
    if (pid == 0) {
4480
        if (stderr_pipe_created && stderr_pipe[1] >= 0) {
4481
            if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) {
4482
                _exit(127);
4483
            }
4484
        }
4485
        if (stdout_pipe_created && stdout_pipe[1] >= 0) {
4486
            if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) {
4487
                _exit(127);
4488
            }
4489
        }
4490
        if (stderr_pipe[0] >= 0) {
4491
            close(stderr_pipe[0]);
4492
        }
4493
        if (stderr_pipe[1] >= 0) {
4494
            close(stderr_pipe[1]);
4495
        }
4496
        if (stdout_pipe[0] >= 0) {
4497
            close(stdout_pipe[0]);
4498
        }
4499
        if (stdout_pipe[1] >= 0) {
4500
            close(stdout_pipe[1]);
4501
        }
4502
        if (output_fd >= 0) {
4503
            close(output_fd);
4504
        }
4505
        execvp(command->argv[0], (char * const *)command->argv);
4506
        _exit(127);
4507
    }
4508
# endif
4509

4510
    loader_trace_message("%s: forked child pid=%ld",
×
4511
                         log_prefix,
4512
                         (long)pid);
4513

4514
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
×
4515
        close(stderr_pipe[1]);
×
4516
        stderr_pipe[1] = -1;
×
4517
    }
4518
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
×
4519
        close(stdout_pipe[1]);
×
4520
        stdout_pipe[1] = -1;
×
4521
    }
4522

4523
    if (stderr_pipe_created && stderr_pipe[0] >= 0) {
×
4524
        flags = fcntl(stderr_pipe[0], F_GETFL, 0);
×
4525
        if (flags >= 0) {
×
4526
            (void)fcntl(stderr_pipe[0],
×
4527
                        F_SETFL,
4528
                        flags | O_NONBLOCK);
4529
        }
4530
    }
4531
    if (stdout_pipe_created && stdout_pipe[0] >= 0) {
×
4532
        flags = fcntl(stdout_pipe[0], F_GETFL, 0);
×
4533
        if (flags >= 0) {
×
4534
            (void)fcntl(stdout_pipe[0],
×
4535
                        F_SETFL,
4536
                        flags | O_NONBLOCK);
4537
        }
4538
    }
4539

4540
    /* The monitoring loop drains stderr/stdout as long as any descriptor is
4541
     * open.  Non-blocking reads ensure the parent does not deadlock if the
4542
     * child process stalls or writes data in bursts.
4543
     */
4544
    while (!child_exited || stderr_open || stdout_open) {
×
4545
        if (stderr_pipe_created && stderr_open) {
×
4546
            read_result = read(stderr_pipe[0],
×
4547
                               stderr_buffer,
4548
                               (ssize_t)sizeof(stderr_buffer));
4549
            if (read_result > 0) {
×
4550
                if (stderr_length + (size_t)read_result + 1 >
×
4551
                        stderr_capacity) {
4552
                    size_t new_capacity;
4553
                    char *new_output;
4554

4555
                    new_capacity = stderr_capacity;
×
4556
                    if (new_capacity == 0) {
×
4557
                        new_capacity = 256;
×
4558
                    }
4559
                    while (stderr_length + (size_t)read_result + 1 >
×
4560
                            new_capacity) {
4561
                        new_capacity *= 2U;
×
4562
                    }
4563
                    new_output = realloc(stderr_output, new_capacity);
×
4564
                    if (new_output == NULL) {
×
4565
                        free(stderr_output);
×
4566
                        stderr_output = NULL;
×
4567
                        stderr_capacity = 0;
×
4568
                        stderr_length = 0;
×
4569
                        stderr_open = 0;
×
4570
                        if (stderr_pipe[0] >= 0) {
×
4571
                            close(stderr_pipe[0]);
×
4572
                            stderr_pipe[0] = -1;
×
4573
                        }
4574
                        stderr_pipe_created = 0;
×
4575
                        written = sixel_compat_snprintf(message,
×
4576
                                                        sizeof(message),
4577
                                                        "%s: realloc() failed.",
4578
                                                        log_prefix);
4579
                        thumbnailer_message_finalize(message,
×
4580
                                                     sizeof(message),
4581
                                                     written);
4582
                        sixel_helper_set_additional_message(message);
×
4583
                        status = SIXEL_BAD_ALLOCATION;
×
4584
                        fatal_error = 1;
×
4585
                        break;
×
4586
                    }
4587
                    stderr_output = new_output;
×
4588
                    stderr_capacity = new_capacity;
×
4589
                }
4590
                memcpy(stderr_output + stderr_length,
×
4591
                       stderr_buffer,
4592
                       (size_t)read_result);
4593
                stderr_length += (size_t)read_result;
×
4594
                stderr_output[stderr_length] = '\0';
×
4595
            } else if (read_result == 0) {
×
4596
                stderr_open = 0;
×
4597
                if (stderr_pipe[0] >= 0) {
×
4598
                    close(stderr_pipe[0]);
×
4599
                    stderr_pipe[0] = -1;
×
4600
                }
4601
                stderr_pipe_created = 0;
×
4602
            } else if (errno == EINTR) {
×
4603
                /* retry */
4604
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4605
                /* no data */
4606
            } else {
4607
                written = sixel_compat_snprintf(message,
×
4608
                                                sizeof(message),
4609
                                                "%s: read() failed (%s).",
4610
                                                log_prefix,
4611
                                                strerror(errno));
×
4612
                thumbnailer_message_finalize(message,
×
4613
                                             sizeof(message),
4614
                                             written);
4615
                sixel_helper_set_additional_message(message);
×
4616
                stderr_open = 0;
×
4617
                if (stderr_pipe[0] >= 0) {
×
4618
                    close(stderr_pipe[0]);
×
4619
                    stderr_pipe[0] = -1;
×
4620
                }
4621
                stderr_pipe_created = 0;
×
4622
            }
4623
        }
4624

4625
        if (stdout_pipe_created && stdout_open) {
×
4626
            stdout_read_result = read(stdout_pipe[0],
×
4627
                                      stdout_buffer,
4628
                                      (ssize_t)sizeof(stdout_buffer));
4629
            if (stdout_read_result > 0) {
×
4630
                write_offset = 0;
×
4631
                while (write_offset < (size_t)stdout_read_result) {
×
4632
                    to_write = (size_t)stdout_read_result - write_offset;
×
4633
                    write_result = write(output_fd,
×
4634
                                          stdout_buffer + write_offset,
4635
                                          to_write);
4636
                    if (write_result < 0) {
×
4637
                        if (errno == EINTR) {
×
4638
                            continue;
×
4639
                        }
4640
                    written = sixel_compat_snprintf(message,
×
4641
                                                    sizeof(message),
4642
                                                    "%s: write() failed (%s).",
4643
                                                    log_prefix,
4644
                                                    strerror(errno));
×
4645
                    thumbnailer_message_finalize(message,
×
4646
                                                 sizeof(message),
4647
                                                 written);
4648
                    sixel_helper_set_additional_message(message);
×
4649
                    stdout_open = 0;
×
4650
                    fatal_error = 1;
×
4651
                    break;
×
4652
                }
4653
                    write_offset += (size_t)write_result;
×
4654
                }
4655
            } else if (stdout_read_result == 0) {
×
4656
                stdout_open = 0;
×
4657
                if (stdout_pipe[0] >= 0) {
×
4658
                    close(stdout_pipe[0]);
×
4659
                    stdout_pipe[0] = -1;
×
4660
                }
4661
                stdout_pipe_created = 0;
×
4662
            } else if (errno == EINTR) {
×
4663
                /* retry */
4664
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4665
                /* no data */
4666
            } else {
4667
                written = sixel_compat_snprintf(message,
×
4668
                                                sizeof(message),
4669
                                                "%s: read() failed (%s).",
4670
                                                log_prefix,
4671
                                                strerror(errno));
×
4672
                thumbnailer_message_finalize(message,
×
4673
                                             sizeof(message),
4674
                                             written);
4675
                sixel_helper_set_additional_message(message);
×
4676
                stdout_open = 0;
×
4677
                if (stdout_pipe[0] >= 0) {
×
4678
                    close(stdout_pipe[0]);
×
4679
                    stdout_pipe[0] = -1;
×
4680
                }
4681
                stdout_pipe_created = 0;
×
4682
            }
4683
        }
4684

4685
        if (!child_exited) {
×
4686
            wait_result = waitpid(pid, &status_code, WNOHANG);
×
4687
            if (wait_result > 0) {
×
4688
                child_exited = 1;
×
4689
                have_status = 1;
×
4690
            } else if (wait_result == 0) {
×
4691
                /* child running */
4692
            } else if (errno != EINTR) {
×
4693
            written = sixel_compat_snprintf(message,
×
4694
                                            sizeof(message),
4695
                                            "%s: waitpid() failed (%s).",
4696
                                            log_prefix,
4697
                                            strerror(errno));
×
4698
            thumbnailer_message_finalize(message,
×
4699
                                         sizeof(message),
4700
                                         written);
4701
            sixel_helper_set_additional_message(message);
×
4702
            status = SIXEL_RUNTIME_ERROR;
×
4703
            fatal_error = 1;
×
4704
            break;
×
4705
        }
4706
        }
4707

4708
        if (!child_exited || stderr_open || stdout_open) {
×
4709
            thumbnailer_sleep_briefly();
×
4710
        }
4711
    }
4712

4713
    if (!child_exited) {
×
4714
        do {
4715
            wait_result = waitpid(pid, &status_code, 0);
×
4716
        } while (wait_result < 0 && errno == EINTR);
×
4717
        if (wait_result < 0) {
×
4718
        written = sixel_compat_snprintf(message,
×
4719
                                        sizeof(message),
4720
                                        "%s: waitpid() failed (%s).",
4721
                                        log_prefix,
4722
                                        strerror(errno));
×
4723
        thumbnailer_message_finalize(message,
×
4724
                                     sizeof(message),
4725
                                     written);
4726
        sixel_helper_set_additional_message(message);
×
4727
        status = SIXEL_RUNTIME_ERROR;
×
4728
        goto cleanup;
×
4729
    }
4730
        have_status = 1;
×
4731
    }
4732

4733
    if (!have_status) {
×
4734
        written = sixel_compat_snprintf(message,
×
4735
                                        sizeof(message),
4736
                                        "%s: waitpid() failed (no status).",
4737
                                        log_prefix);
4738
        thumbnailer_message_finalize(message,
×
4739
                                     sizeof(message),
4740
                                     written);
4741
        sixel_helper_set_additional_message(message);
×
4742
        status = SIXEL_RUNTIME_ERROR;
×
4743
        goto cleanup;
×
4744
    }
4745

4746
    if (!fatal_error) {
×
4747
        if (WIFEXITED(status_code) && WEXITSTATUS(status_code) == 0) {
×
4748
            status = SIXEL_OK;
×
4749
            loader_trace_message("%s: child pid=%ld exited successfully",
×
4750
                                 log_prefix,
4751
                                 (long)pid);
4752
        } else if (WIFEXITED(status_code)) {
×
4753
            written = sixel_compat_snprintf(message,
×
4754
                                            sizeof(message),
4755
                                            "%s: %s exited with status %d.",
4756
                                            log_prefix,
4757
                                            (thumbnailer_name != NULL) ?
×
4758
                                            thumbnailer_name :
4759
                                            "thumbnailer",
4760
                                            WEXITSTATUS(status_code));
×
4761
            thumbnailer_message_finalize(message,
×
4762
                                         sizeof(message),
4763
                                         written);
4764
            sixel_helper_set_additional_message(message);
×
4765
            status = SIXEL_RUNTIME_ERROR;
×
4766
        } else if (WIFSIGNALED(status_code)) {
×
4767
            written = sixel_compat_snprintf(message,
×
4768
                                            sizeof(message),
4769
                                            "%s: %s terminated by signal %d.",
4770
                                            log_prefix,
4771
                                            (thumbnailer_name != NULL) ?
×
4772
                                            thumbnailer_name :
4773
                                            "thumbnailer",
4774
                                            WTERMSIG(status_code));
4775
            thumbnailer_message_finalize(message,
×
4776
                                         sizeof(message),
4777
                                         written);
4778
            sixel_helper_set_additional_message(message);
×
4779
            status = SIXEL_RUNTIME_ERROR;
×
4780
        } else {
4781
            written = sixel_compat_snprintf(message,
×
4782
                                            sizeof(message),
4783
                                            "%s: %s exited abnormally.",
4784
                                            log_prefix,
4785
                                            (thumbnailer_name != NULL) ?
×
4786
                                            thumbnailer_name :
4787
                                            "thumbnailer");
4788
            thumbnailer_message_finalize(message,
×
4789
                                         sizeof(message),
4790
                                         written);
4791
            sixel_helper_set_additional_message(message);
×
4792
            status = SIXEL_RUNTIME_ERROR;
×
4793
        }
4794
    }
4795

4796
cleanup:
4797
    if (stderr_output != NULL && loader_trace_enabled &&
×
4798
            stderr_length > 0) {
4799
        loader_trace_message("%s: stderr:\n%s",
×
4800
                             log_prefix,
4801
                             stderr_output);
4802
    }
4803

4804
    if (stderr_pipe[0] >= 0) {
×
4805
        close(stderr_pipe[0]);
×
4806
        stderr_pipe[0] = -1;
×
4807
    }
4808
    if (stderr_pipe[1] >= 0) {
×
4809
        close(stderr_pipe[1]);
×
4810
        stderr_pipe[1] = -1;
×
4811
    }
4812
    if (stdout_pipe[0] >= 0) {
×
4813
        close(stdout_pipe[0]);
×
4814
        stdout_pipe[0] = -1;
×
4815
    }
4816
    if (stdout_pipe[1] >= 0) {
×
4817
        close(stdout_pipe[1]);
×
4818
        stdout_pipe[1] = -1;
×
4819
    }
4820
    if (output_fd >= 0) {
×
4821
        close(output_fd);
×
4822
        output_fd = -1;
×
4823
    }
4824
    /* stderr_output accumulates all diagnostic text, so release it even when
4825
     * verbose tracing is disabled.
4826
     */
4827
    free(stderr_output);
×
4828

4829
    return status;
×
4830
}
4831

4832

4833

4834
/*
4835
 * load_with_gnome_thumbnailer
4836
 *
4837
 * Drive the FreeDesktop thumbnailer pipeline and then decode the PNG
4838
 * result using the built-in loader.
4839
 *
4840
 * GNOME thumbnail workflow overview:
4841
 *
4842
 *     +------------+    +-------------------+    +----------------+
4843
 *     | source URI | -> | .thumbnailer Exec | -> | PNG thumbnail  |
4844
 *     +------------+    +-------------------+    +----------------+
4845
 *             |                    |                        |
4846
 *             |                    v                        v
4847
 *             |           spawn via /bin/sh         load_with_builtin()
4848
 *             v
4849
 *     file --mime-type
4850
 *
4851
 * Each step logs verbose breadcrumbs so integrators can diagnose which
4852
 * thumbnailer matched, how the command was prepared, and why fallbacks
4853
 * were selected.
4854
 *
4855
 * Arguments:
4856
 *     pchunk        - source chunk representing the original document.
4857
 *     fstatic       - image static-ness flag.
4858
 *     fuse_palette  - palette usage flag.
4859
 *     reqcolors     - requested colour count.
4860
 *     bgcolor       - background colour override.
4861
 *     loop_control  - animation loop control flag.
4862
 *     fn_load       - downstream decoder callback.
4863
 *     context       - user context forwarded to fn_load.
4864
 * Returns:
4865
 *     SIXEL_OK on success or libsixel error code on failure.
4866
 */
4867
static SIXELSTATUS
4868
load_with_gnome_thumbnailer(
×
4869
    sixel_chunk_t const       /* in */     *pchunk,
4870
    int                       /* in */     fstatic,
4871
    int                       /* in */     fuse_palette,
4872
    int                       /* in */     reqcolors,
4873
    unsigned char             /* in */     *bgcolor,
4874
    int                       /* in */     loop_control,
4875
    sixel_load_image_function /* in */     fn_load,
4876
    void                      /* in/out */ *context)
4877
{
4878
    SIXELSTATUS status;
4879
    sixel_chunk_t *thumb_chunk;
4880
    char template_path[] = "/tmp/libsixel-thumb-XXXXXX";
×
4881
    char *png_path;
4882
    size_t path_length;
4883
    struct thumbnailer_string_list *directories;
4884
    size_t dir_index;
4885
    DIR *dir;
4886
    struct dirent *entry;
4887
    char *thumbnailer_path;
4888
    struct thumbnailer_entry info;
4889
    char *content_type;
4890
    char *input_uri;
4891
    struct thumbnailer_command *command;
4892
    struct thumbnailer_command *evince_command;
4893
    int executed;
4894
    int command_success;
4895
    int requested_size;
4896
    char const *log_prefix;
4897
    int fd;
4898
    int written;
4899

4900
    loader_thumbnailer_initialize_size_hint();
×
4901

4902
    status = SIXEL_FALSE;
×
4903
    thumb_chunk = NULL;
×
4904
    png_path = NULL;
×
4905
    path_length = 0;
×
4906
    fd = -1;
×
4907
    directories = NULL;
×
4908
    dir_index = 0;
×
4909
    dir = NULL;
×
4910
    entry = NULL;
×
4911
    thumbnailer_path = NULL;
×
4912
    content_type = NULL;
×
4913
    input_uri = NULL;
×
4914
    command = NULL;
×
4915
    evince_command = NULL;
×
4916
    executed = 0;
×
4917
    command_success = 0;
×
4918
    log_prefix = "load_with_gnome_thumbnailer";
×
4919
    requested_size = thumbnailer_size_hint;
×
4920
    if (requested_size <= 0) {
×
4921
        requested_size = SIXEL_THUMBNAILER_DEFAULT_SIZE;
×
4922
    }
4923

4924
    loader_trace_message("%s: thumbnail size hint=%d",
×
4925
                         log_prefix,
4926
                         requested_size);
4927

4928
    thumbnailer_entry_init(&info);
×
4929

4930
    if (pchunk->source_path == NULL) {
×
4931
        sixel_helper_set_additional_message(
×
4932
            "load_with_gnome_thumbnailer: source path is unavailable.");
4933
        status = SIXEL_BAD_ARGUMENT;
×
4934
        goto end;
×
4935
    }
4936

4937
#if defined(HAVE_MKSTEMP)
4938
    fd = mkstemp(template_path);
×
4939
#elif defined(HAVE__MKTEMP)
4940
    fd = _mktemp(template_path);
4941
#elif defined(HAVE_MKTEMP)
4942
    fd = mktemp(template_path);
4943
#endif
4944
    if (fd < 0) {
×
4945
        sixel_helper_set_additional_message(
×
4946
            "load_with_gnome_thumbnailer: mkstemp() failed.");
4947
        status = SIXEL_RUNTIME_ERROR;
×
4948
        goto end;
×
4949
    }
4950
    close(fd);
×
4951
    fd = -1;
×
4952

4953
    path_length = strlen(template_path) + 5;
×
4954
    png_path = malloc(path_length);
×
4955
    if (png_path == NULL) {
×
4956
        sixel_helper_set_additional_message(
×
4957
            "load_with_gnome_thumbnailer: malloc() failed.");
4958
        status = SIXEL_BAD_ALLOCATION;
×
4959
        unlink(template_path);
×
4960
        goto end;
×
4961
    }
4962
    written = sixel_compat_snprintf(png_path,
×
4963
                                    path_length,
4964
                                    "%s.png",
4965
                                    template_path);
4966
    thumbnailer_message_finalize(png_path,
×
4967
                                 path_length,
4968
                                 written);
4969
    if (rename(template_path, png_path) != 0) {
×
4970
        sixel_helper_set_additional_message(
×
4971
            "load_with_gnome_thumbnailer: rename() failed.");
4972
        status = SIXEL_RUNTIME_ERROR;
×
4973
        unlink(template_path);
×
4974
        goto end;
×
4975
    }
4976

4977
    content_type = thumbnailer_guess_content_type(pchunk->source_path);
×
4978
    input_uri = thumbnailer_build_file_uri(pchunk->source_path);
×
4979

4980
    loader_trace_message("%s: detected MIME type %s for %s",
×
4981
                         log_prefix,
4982
                         (content_type != NULL) ? content_type :
×
4983
                         "(unknown)",
4984
                         pchunk->source_path);
×
4985

4986
    directories = thumbnailer_collect_directories();
×
4987
    if (directories == NULL) {
×
4988
        status = SIXEL_RUNTIME_ERROR;
×
4989
        goto end;
×
4990
    }
4991

4992
    /* Iterate through every configured thumbnailer directory so we honour
4993
     * overrides in $HOME as well as desktop environment defaults discovered
4994
     * through XDG_DATA_DIRS.
4995
     */
4996
    for (dir_index = 0; dir_index < directories->length; ++dir_index) {
×
4997
        loader_trace_message("%s: checking thumbnailers in %s",
×
4998
                             log_prefix,
4999
                             directories->items[dir_index]);
×
5000

5001
        dir = opendir(directories->items[dir_index]);
×
5002
        if (dir == NULL) {
×
5003
            continue;
×
5004
        }
5005
        while ((entry = readdir(dir)) != NULL) {
×
5006
            thumbnailer_entry_clear(&info);
×
5007
            thumbnailer_entry_init(&info);
×
5008
            size_t name_length;
5009

5010
            name_length = strlen(entry->d_name);
×
5011
            if (name_length < 12 ||
×
5012
                    strcmp(entry->d_name + name_length - 12,
×
5013
                           ".thumbnailer") != 0) {
5014
                continue;
×
5015
            }
5016
            thumbnailer_path = thumbnailer_join_paths(
×
5017
                directories->items[dir_index],
×
5018
                entry->d_name);
×
5019
            if (thumbnailer_path == NULL) {
×
5020
                continue;
×
5021
            }
5022
            if (!thumbnailer_parse_file(thumbnailer_path, &info)) {
×
5023
                free(thumbnailer_path);
×
5024
                thumbnailer_path = NULL;
×
5025
                continue;
×
5026
            }
5027
            free(thumbnailer_path);
×
5028
            thumbnailer_path = NULL;
×
5029
            loader_trace_message(
×
5030
                "%s: parsed %s (TryExec=%s)",
5031
                log_prefix,
5032
                entry->d_name,
×
5033
                (info.tryexec != NULL) ? info.tryexec : "(none)");
×
5034
            if (content_type == NULL) {
×
5035
                continue;
×
5036
            }
5037
            if (!thumbnailer_has_tryexec(info.tryexec)) {
×
5038
                loader_trace_message("%s: skipping %s (TryExec missing)",
×
5039
                                     log_prefix,
5040
                                     entry->d_name);
×
5041
                continue;
×
5042
            }
5043
            if (!thumbnailer_supports_mime(&info, content_type)) {
×
5044
                loader_trace_message("%s: %s does not support %s",
×
5045
                                     log_prefix,
5046
                                     entry->d_name,
×
5047
                                     content_type);
5048
                continue;
×
5049
            }
5050
            if (info.exec_line == NULL) {
×
5051
                continue;
×
5052
            }
5053
            loader_trace_message("%s: %s supports %s with Exec=\"%s\"",
×
5054
                                 log_prefix,
5055
                                 entry->d_name,
×
5056
                                 content_type,
5057
                                 info.exec_line);
5058
            loader_trace_message("%s: preparing %s for %s",
×
5059
                                 log_prefix,
5060
                                 entry->d_name,
×
5061
                                 content_type);
5062
            command = thumbnailer_build_command(info.exec_line,
×
5063
                                                pchunk->source_path,
×
5064
                                                input_uri,
5065
                                                png_path,
5066
                                                requested_size,
5067
                                                content_type);
5068
            if (command == NULL) {
×
5069
                continue;
×
5070
            }
5071
            if (thumbnailer_is_evince_thumbnailer(info.exec_line,
×
5072
                                                  info.tryexec)) {
×
5073
                loader_trace_message(
×
5074
                    "%s: applying evince-thumbnailer stdout workaround",
5075
                    log_prefix);
5076
                /* evince-thumbnailer fails when passed an output path.
5077
                 * Redirect stdout and copy the stream instead.
5078
                 */
5079
                evince_command = thumbnailer_build_evince_command(
×
5080
                    pchunk->source_path,
×
5081
                    requested_size);
5082
                if (evince_command == NULL) {
×
5083
                    thumbnailer_command_free(command);
×
5084
                    command = NULL;
×
5085
                    continue;
×
5086
                }
5087
                thumbnailer_command_free(command);
×
5088
                command = evince_command;
×
5089
                evince_command = NULL;
×
5090
                unlink(png_path);
×
5091
                status = thumbnailer_spawn(command,
×
5092
                                           entry->d_name,
×
5093
                                           log_prefix,
5094
                                           1,
5095
                                           png_path);
5096
            } else {
5097
                unlink(png_path);
×
5098
                status = thumbnailer_spawn(command,
×
5099
                                           entry->d_name,
×
5100
                                           log_prefix,
5101
                                           0,
5102
                                           NULL);
5103
            }
5104
            thumbnailer_command_free(command);
×
5105
            command = NULL;
×
5106
            executed = 1;
×
5107
            if (SIXEL_SUCCEEDED(status)) {
×
5108
                command_success = 1;
×
5109
                loader_trace_message("%s: %s produced %s",
×
5110
                                     log_prefix,
5111
                                     entry->d_name,
×
5112
                                     png_path);
5113
                break;
×
5114
            }
5115
        }
5116
        closedir(dir);
×
5117
        dir = NULL;
×
5118
        if (command_success) {
×
5119
            break;
×
5120
        }
5121
    }
5122

5123
    if (!command_success) {
×
5124
        loader_trace_message("%s: falling back to gdk-pixbuf-thumbnailer",
×
5125
                             log_prefix);
5126
        unlink(png_path);
×
5127
        command = thumbnailer_build_command(
×
5128
            "gdk-pixbuf-thumbnailer --size=%s %i %o",
5129
            pchunk->source_path,
×
5130
            input_uri,
5131
            png_path,
5132
            requested_size,
5133
            content_type);
5134
        if (command != NULL) {
×
5135
            unlink(png_path);
×
5136
            status = thumbnailer_spawn(command,
×
5137
                                       "gdk-pixbuf-thumbnailer",
5138
                                       log_prefix,
5139
                                       0,
5140
                                       NULL);
5141
            thumbnailer_command_free(command);
×
5142
            command = NULL;
×
5143
            if (SIXEL_FAILED(status)) {
×
5144
                goto end;
×
5145
            }
5146
            executed = 1;
×
5147
            command_success = 1;
×
5148
            loader_trace_message("%s: gdk-pixbuf-thumbnailer produced %s",
×
5149
                                 log_prefix,
5150
                                 png_path);
5151
        }
5152
    }
5153

5154
    if (!executed) {
×
5155
        sixel_helper_set_additional_message(
×
5156
            "load_with_gnome_thumbnailer: no thumbnailer available.");
5157
        status = SIXEL_RUNTIME_ERROR;
×
5158
        goto end;
×
5159
    }
5160

5161
    status = sixel_chunk_new(&thumb_chunk,
×
5162
                             png_path,
5163
                             0,
5164
                             NULL,
5165
                             pchunk->allocator);
×
5166
    if (SIXEL_FAILED(status)) {
×
5167
        goto end;
×
5168
    }
5169
    status = load_with_builtin(thumb_chunk,
×
5170
                               fstatic,
5171
                               fuse_palette,
5172
                               reqcolors,
5173
                               bgcolor,
5174
                               loop_control,
5175
                               fn_load,
5176
                               context);
5177
    if (SIXEL_FAILED(status)) {
×
5178
        goto end;
×
5179
    }
5180

5181
    status = SIXEL_OK;
×
5182

5183
end:
5184
    if (command != NULL) {
×
5185
        thumbnailer_command_free(command);
×
5186
        command = NULL;
×
5187
    }
5188
    if (evince_command != NULL) {
×
5189
        thumbnailer_command_free(evince_command);
×
5190
        evince_command = NULL;
×
5191
    }
5192
    if (thumb_chunk != NULL) {
×
5193
        sixel_chunk_destroy(thumb_chunk);
×
5194
        thumb_chunk = NULL;
×
5195
    }
5196
    if (png_path != NULL) {
×
5197
        unlink(png_path);
×
5198
        free(png_path);
×
5199
        png_path = NULL;
×
5200
    }
5201
    if (fd >= 0) {
×
5202
        close(fd);
×
5203
        fd = -1;
×
5204
    }
5205
    if (directories != NULL) {
×
5206
        thumbnailer_string_list_free(directories);
×
5207
        directories = NULL;
×
5208
    }
5209
    if (dir != NULL) {
×
5210
        closedir(dir);
×
5211
        dir = NULL;
×
5212
    }
5213
    thumbnailer_entry_clear(&info);
×
5214
    free(content_type);
×
5215
    content_type = NULL;
×
5216
    free(input_uri);
×
5217
    input_uri = NULL;
×
5218

5219
    return status;
×
5220
}
5221

5222
#endif  /* HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK */
5223

5224

5225
#if HAVE_GD
5226
static int
5227
detect_file_format(int len, unsigned char *data)
5228
{
5229
    if (len > 18 && memcmp("TRUEVISION", data + len - 18, 10) == 0) {
5230
        return SIXEL_FORMAT_TGA;
5231
    }
5232

5233
    if (len > 3 && memcmp("GIF", data, 3) == 0) {
5234
        return SIXEL_FORMAT_GIF;
5235
    }
5236

5237
    if (len > 8 && memcmp("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", data, 8) == 0) {
5238
        return SIXEL_FORMAT_PNG;
5239
    }
5240

5241
    if (len > 2 && memcmp("BM", data, 2) == 0) {
5242
        return SIXEL_FORMAT_BMP;
5243
    }
5244

5245
    if (len > 2 && memcmp("\xFF\xD8", data, 2) == 0) {
5246
        return SIXEL_FORMAT_JPG;
5247
    }
5248

5249
    if (len > 2 && memcmp("\x00\x00", data, 2) == 0) {
5250
        return SIXEL_FORMAT_WBMP;
5251
    }
5252

5253
    if (len > 2 && memcmp("\x4D\x4D", data, 2) == 0) {
5254
        return SIXEL_FORMAT_TIFF;
5255
    }
5256

5257
    if (len > 2 && memcmp("\x49\x49", data, 2) == 0) {
5258
        return SIXEL_FORMAT_TIFF;
5259
    }
5260

5261
    if (len > 2 && memcmp("\033P", data, 2) == 0) {
5262
        return SIXEL_FORMAT_SIXEL;
5263
    }
5264

5265
    if (len > 2 && data[0] == 0x90  && (data[len - 1] == 0x9C || data[len - 2] == 0x9C)) {
5266
        return SIXEL_FORMAT_SIXEL;
5267
    }
5268

5269
    if (len > 1 && data[0] == 'P' && data[1] >= '1' && data[1] <= '6') {
5270
        return SIXEL_FORMAT_PNM;
5271
    }
5272

5273
    if (len > 3 && memcmp("gd2", data, 3) == 0) {
5274
        return SIXEL_FORMAT_GD2;
5275
    }
5276

5277
    if (len > 4 && memcmp("8BPS", data, 4) == 0) {
5278
        return SIXEL_FORMAT_PSD;
5279
    }
5280

5281
    if (len > 11 && memcmp("#?RADIANCE\n", data, 11) == 0) {
5282
        return SIXEL_FORMAT_HDR;
5283
    }
5284

5285
    return (-1);
5286
}
5287
#endif /* HAVE_GD */
5288

5289
#if HAVE_GD
5290

5291
static SIXELSTATUS
5292
load_with_gd(
5293
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
5294
    int                       /* in */     fstatic,      /* static */
5295
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
5296
    int                       /* in */     reqcolors,    /* reqcolors */
5297
    unsigned char             /* in */     *bgcolor,     /* background color */
5298
    int                       /* in */     loop_control, /* one of enum loop_control */
5299
    sixel_load_image_function /* in */     fn_load,      /* callback */
5300
    void                      /* in/out */ *context      /* private data for callback */
5301
)
5302
{
5303
    SIXELSTATUS status = SIXEL_FALSE;
5304
    unsigned char *p;
5305
    gdImagePtr im = NULL;
5306
    int x, y;
5307
    int c;
5308
    sixel_frame_t *frame = NULL;
5309
    int format;
5310

5311
    (void) fstatic;
5312
    (void) fuse_palette;
5313
    (void) reqcolors;
5314
    (void) bgcolor;
5315
    (void) loop_control;
5316

5317
    format = detect_file_format(pchunk->size, pchunk->buffer);
5318

5319
    if (format == SIXEL_FORMAT_GIF) {
5320
#if HAVE_DECL_GDIMAGECREATEFROMGIFANIMPTR
5321
        gdImagePtr *ims = NULL;
5322
        int frames = 0;
5323
        int i;
5324
        int *delays = NULL;
5325

5326
        ims = gdImageCreateFromGifAnimPtr(pchunk->size, pchunk->buffer,
5327
                                          &frames, &delays);
5328
        if (ims == NULL) {
5329
            status = SIXEL_GD_ERROR;
5330
            goto end;
5331
        }
5332

5333
        for (i = 0; i < frames; i++) {
5334
            im = ims[i];
5335
            if (!gdImageTrueColor(im)) {
5336
# if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
5337
                if (!gdImagePaletteToTrueColor(im)) {
5338
                    status = SIXEL_GD_ERROR;
5339
                    goto gif_end;
5340
                }
5341
# else
5342
                status = SIXEL_GD_ERROR;
5343
                goto gif_end;
5344
# endif
5345
            }
5346

5347
            status = sixel_frame_new(&frame, pchunk->allocator);
5348
            if (SIXEL_FAILED(status)) {
5349
                frame = NULL;
5350
                goto gif_end;
5351
            }
5352

5353
            frame->width = gdImageSX(im);
5354
            frame->height = gdImageSY(im);
5355
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
5356
            p = frame->pixels = sixel_allocator_malloc(
5357
                pchunk->allocator,
5358
                (size_t)(frame->width * frame->height * 3));
5359
            if (frame->pixels == NULL) {
5360
                sixel_helper_set_additional_message(
5361
                    "load_with_gd: sixel_allocator_malloc() failed.");
5362
                status = SIXEL_BAD_ALLOCATION;
5363
                sixel_frame_unref(frame);
5364
                frame = NULL;
5365
                goto gif_end;
5366
            }
5367
            for (y = 0; y < frame->height; y++) {
5368
                for (x = 0; x < frame->width; x++) {
5369
                    c = gdImageTrueColorPixel(im, x, y);
5370
                    *p++ = gdTrueColorGetRed(c);
5371
                    *p++ = gdTrueColorGetGreen(c);
5372
                    *p++ = gdTrueColorGetBlue(c);
5373
                }
5374
            }
5375

5376
            if (delays) {
5377
                frame->delay.tv_sec = delays[i] / 100;
5378
                frame->delay.tv_nsec = (delays[i] % 100) * 10000000L;
5379
            }
5380

5381
            status = fn_load(frame, context);
5382
            sixel_frame_unref(frame);
5383
            frame = NULL;
5384
            gdImageDestroy(im);
5385
            ims[i] = NULL;
5386
            if (SIXEL_FAILED(status)) {
5387
                goto gif_end;
5388
            }
5389
        }
5390

5391
        status = SIXEL_OK;
5392

5393
gif_end:
5394
        if (delays) {
5395
            gdFree(delays);
5396
        }
5397
        if (ims) {
5398
            for (i = 0; i < frames; i++) {
5399
                if (ims[i]) {
5400
                    gdImageDestroy(ims[i]);
5401
                }
5402
            }
5403
            gdFree(ims);
5404
        }
5405
        goto end;
5406
#else
5407
        status = SIXEL_GD_ERROR;
5408
        goto end;
5409
#endif
5410
    }
5411

5412
    switch (format) {
5413
#if HAVE_DECL_GDIMAGECREATEFROMPNGPTR
5414
        case SIXEL_FORMAT_PNG:
5415
            im = gdImageCreateFromPngPtr(pchunk->size, pchunk->buffer);
5416
            break;
5417
#endif  /* HAVE_DECL_GDIMAGECREATEFROMPNGPTR */
5418
#if HAVE_DECL_GDIMAGECREATEFROMBMPPTR
5419
        case SIXEL_FORMAT_BMP:
5420
            im = gdImageCreateFromBmpPtr(pchunk->size, pchunk->buffer);
5421
            break;
5422
#endif  /* HAVE_DECL_GDIMAGECREATEFROMBMPPTR */
5423
        case SIXEL_FORMAT_JPG:
5424
#if HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX
5425
            im = gdImageCreateFromJpegPtrEx(pchunk->size, pchunk->buffer, 1);
5426
#elif HAVE_DECL_GDIMAGECREATEFROMJPEGPTR
5427
            im = gdImageCreateFromJpegPtr(pchunk->size, pchunk->buffer);
5428
#endif  /* HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX */
5429
            break;
5430
#if HAVE_DECL_GDIMAGECREATEFROMTGAPTR
5431
        case SIXEL_FORMAT_TGA:
5432
            im = gdImageCreateFromTgaPtr(pchunk->size, pchunk->buffer);
5433
            break;
5434
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTGAPTR */
5435
#if HAVE_DECL_GDIMAGECREATEFROMWBMPPTR
5436
        case SIXEL_FORMAT_WBMP:
5437
            im = gdImageCreateFromWBMPPtr(pchunk->size, pchunk->buffer);
5438
            break;
5439
#endif  /* HAVE_DECL_GDIMAGECREATEFROMWBMPPTR */
5440
#if HAVE_DECL_GDIMAGECREATEFROMTIFFPTR
5441
        case SIXEL_FORMAT_TIFF:
5442
            im = gdImageCreateFromTiffPtr(pchunk->size, pchunk->buffer);
5443
            break;
5444
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTIFFPTR */
5445
#if HAVE_DECL_GDIMAGECREATEFROMGD2PTR
5446
        case SIXEL_FORMAT_GD2:
5447
            im = gdImageCreateFromGd2Ptr(pchunk->size, pchunk->buffer);
5448
            break;
5449
#endif  /* HAVE_DECL_GDIMAGECREATEFROMGD2PTR */
5450
        default:
5451
            status = SIXEL_GD_ERROR;
5452
            sixel_helper_set_additional_message(
5453
                "unexpected image format detected.");
5454
            goto end;
5455
    }
5456

5457
    if (im == NULL) {
5458
        status = SIXEL_GD_ERROR;
5459
        /* TODO: retrieve error detail */
5460
        goto end;
5461
    }
5462

5463
    if (!gdImageTrueColor(im)) {
5464
#if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
5465
        if (!gdImagePaletteToTrueColor(im)) {
5466
            gdImageDestroy(im);
5467
            status = SIXEL_GD_ERROR;
5468
            /* TODO: retrieve error detail */
5469
            goto end;
5470
        }
5471
#else
5472
        status = SIXEL_GD_ERROR;
5473
        /* TODO: retrieve error detail */
5474
        goto end;
5475
#endif
5476
    }
5477

5478
    status = sixel_frame_new(&frame, pchunk->allocator);
5479
    if (SIXEL_FAILED(status)) {
5480
        goto end;
5481
    }
5482

5483
    frame->width = gdImageSX(im);
5484
    frame->height = gdImageSY(im);
5485
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
5486
    p = frame->pixels = sixel_allocator_malloc(
5487
        pchunk->allocator, (size_t)(frame->width * frame->height * 3));
5488
    if (frame->pixels == NULL) {
5489
        sixel_helper_set_additional_message(
5490
            "load_with_gd: sixel_allocator_malloc() failed.");
5491
        status = SIXEL_BAD_ALLOCATION;
5492
        gdImageDestroy(im);
5493
        goto end;
5494
    }
5495
    for (y = 0; y < frame->height; y++) {
5496
        for (x = 0; x < frame->width; x++) {
5497
            c = gdImageTrueColorPixel(im, x, y);
5498
            *p++ = gdTrueColorGetRed(c);
5499
            *p++ = gdTrueColorGetGreen(c);
5500
            *p++ = gdTrueColorGetBlue(c);
5501
        }
5502
    }
5503
    gdImageDestroy(im);
5504

5505
    status = fn_load(frame, context);
5506
    if (SIXEL_FAILED(status)) {
5507
        goto end;
5508
    }
5509

5510
    sixel_frame_unref(frame);
5511

5512
    status = SIXEL_OK;
5513

5514
end:
5515
    return status;
5516
}
5517

5518
#endif  /* HAVE_GD */
5519

5520
#if HAVE_WIC
5521

5522
#include <windows.h>
5523
#include <wincodec.h>
5524

5525
SIXELSTATUS
5526
load_with_wic(
5527
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
5528
    int                       /* in */     fstatic,      /* static */
5529
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
5530
    int                       /* in */     reqcolors,    /* reqcolors */
5531
    unsigned char             /* in */     *bgcolor,     /* background color */
5532
    int                       /* in */     loop_control, /* one of enum loop_control */
5533
    sixel_load_image_function /* in */     fn_load,      /* callback */
5534
    void                      /* in/out */ *context      /* private data for callback */
5535
)
5536
{
5537
    HRESULT                 hr         = E_FAIL;
5538
    SIXELSTATUS             status     = SIXEL_FALSE;
5539
    IWICImagingFactory     *factory    = NULL;
5540
    IWICStream             *stream     = NULL;
5541
    IWICBitmapDecoder      *decoder    = NULL;
5542
    IWICBitmapFrameDecode  *wicframe   = NULL;
5543
    IWICFormatConverter    *conv       = NULL;
5544
    IWICBitmapSource       *src        = NULL;
5545
    IWICPalette            *wicpalette = NULL;
5546
    WICColor               *wiccolors  = NULL;
5547
    IWICMetadataQueryReader *qdecoder  = NULL;
5548
    IWICMetadataQueryReader *qframe    = NULL;
5549
    UINT                    ncolors    = 0;
5550
    sixel_frame_t          *frame      = NULL;
5551
    int                     comp       = 4;
5552
    UINT                    actual     = 0;
5553
    UINT                    i;
5554
    UINT                    frame_count = 0;
5555
    int                     anim_loop_count = (-1);
5556
    int                     is_gif;
5557
    WICColor                c;
5558

5559
    (void) reqcolors;
5560
    (void) bgcolor;
5561

5562
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
5563
    if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
5564
        return status;
5565
    }
5566

5567
    status = sixel_frame_new(&frame, pchunk->allocator);
5568
    if (SIXEL_FAILED(status)) {
5569
        goto end;
5570
    }
5571

5572
    hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
5573
                          &IID_IWICImagingFactory, (void**)&factory);
5574
    if (FAILED(hr)) {
5575
        goto end;
5576
    }
5577

5578
    hr = factory->lpVtbl->CreateStream(factory, &stream);
5579
    if (FAILED(hr)) {
5580
        goto end;
5581
    }
5582

5583
    hr = stream->lpVtbl->InitializeFromMemory(stream,
5584
                                              (BYTE*)pchunk->buffer,
5585
                                              (DWORD)pchunk->size);
5586
    if (FAILED(hr)) {
5587
        goto end;
5588
    }
5589

5590
    hr = factory->lpVtbl->CreateDecoderFromStream(factory,
5591
                                                  (IStream*)stream,
5592
                                                  NULL,
5593
                                                  WICDecodeMetadataCacheOnDemand,
5594
                                                  &decoder);
5595
    if (FAILED(hr)) {
5596
        goto end;
5597
    }
5598

5599
    is_gif = (memcmp("GIF", pchunk->buffer, 3) == 0);
5600

5601
    if (is_gif) {
5602
        hr = decoder->lpVtbl->GetFrameCount(decoder, &frame_count);
5603
        if (FAILED(hr)) {
5604
            goto end;
5605
        }
5606
        if (fstatic) {
5607
            frame_count = 1;
5608
        }
5609

5610
        hr = decoder->lpVtbl->GetMetadataQueryReader(decoder, &qdecoder);
5611
        if (SUCCEEDED(hr)) {
5612
            PROPVARIANT pv;
5613
            PropVariantInit(&pv);
5614
            hr = qdecoder->lpVtbl->GetMetadataByName(
5615
                qdecoder,
5616
                L"/appext/Application/NETSCAPE2.0/Loop",
5617
                &pv);
5618
            if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5619
                anim_loop_count = pv.uiVal;
5620
            }
5621
            PropVariantClear(&pv);
5622
            qdecoder->lpVtbl->Release(qdecoder);
5623
            qdecoder = NULL;
5624
        }
5625

5626
        frame->loop_count = 0;
5627
        for (;;) {
5628
            frame->frame_no = 0;
5629
            for (i = 0; i < frame_count; ++i) {
5630
                hr = decoder->lpVtbl->GetFrame(decoder, i, &wicframe);
5631
                if (FAILED(hr)) {
5632
                    goto end;
5633
                }
5634

5635
                hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5636
                if (FAILED(hr)) {
5637
                    goto end;
5638
                }
5639
                hr = conv->lpVtbl->Initialize(conv,
5640
                                              (IWICBitmapSource*)wicframe,
5641
                                              &GUID_WICPixelFormat32bppRGBA,
5642
                                              WICBitmapDitherTypeNone,
5643
                                              NULL,
5644
                                              0.0,
5645
                                              WICBitmapPaletteTypeCustom);
5646
                if (FAILED(hr)) {
5647
                    goto end;
5648
                }
5649

5650
                src = (IWICBitmapSource*)conv;
5651
                comp = 4;
5652
                frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5653

5654
                hr = src->lpVtbl->GetSize(
5655
                    src,
5656
                    (UINT *)&frame->width,
5657
                    (UINT *)&frame->height);
5658
                if (FAILED(hr)) {
5659
                    goto end;
5660
                }
5661

5662
                if (frame->width <= 0 || frame->height <= 0 ||
5663
                    frame->width > SIXEL_WIDTH_LIMIT ||
5664
                    frame->height > SIXEL_HEIGHT_LIMIT) {
5665
                    sixel_helper_set_additional_message(
5666
                        "load_with_wic: an invalid width or height parameter detected.");
5667
                    status = SIXEL_BAD_INPUT;
5668
                    goto end;
5669
                }
5670

5671
                frame->pixels = sixel_allocator_malloc(
5672
                    pchunk->allocator,
5673
                    (size_t)(frame->height * frame->width * comp));
5674
                if (frame->pixels == NULL) {
5675
                    hr = E_OUTOFMEMORY;
5676
                    goto end;
5677
                }
5678

5679
                {
5680
                    WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
5681
                    hr = src->lpVtbl->CopyPixels(
5682
                        src,
5683
                        &rc,
5684
                        frame->width * comp,
5685
                        (UINT)frame->width * frame->height * comp,
5686
                        frame->pixels);
5687
                    if (FAILED(hr)) {
5688
                        goto end;
5689
                    }
5690
                }
5691

5692
                frame->delay = 0;
5693
                hr = wicframe->lpVtbl->GetMetadataQueryReader(wicframe, &qframe);
5694
                if (SUCCEEDED(hr)) {
5695
                    PROPVARIANT pv;
5696
                    PropVariantInit(&pv);
5697
                    hr = qframe->lpVtbl->GetMetadataByName(
5698
                        qframe,
5699
                        L"/grctlext/Delay",
5700
                        &pv);
5701
                    if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5702
                        frame->delay = (int)(pv.uiVal) * 10;
5703
                    }
5704
                    PropVariantClear(&pv);
5705
                    qframe->lpVtbl->Release(qframe);
5706
                    qframe = NULL;
5707
                }
5708

5709
                frame->multiframe = 1;
5710
                status = fn_load(frame, context);
5711
                if (SIXEL_FAILED(status)) {
5712
                    goto end;
5713
                }
5714
                frame->pixels = NULL;
5715
                frame->palette = NULL;
5716

5717
                if (conv) {
5718
                    conv->lpVtbl->Release(conv);
5719
                    conv = NULL;
5720
                }
5721
                if (wicframe) {
5722
                    wicframe->lpVtbl->Release(wicframe);
5723
                    wicframe = NULL;
5724
                }
5725

5726
                frame->frame_no++;
5727
            }
5728

5729
            ++frame->loop_count;
5730

5731
            if (anim_loop_count < 0) {
5732
                break;
5733
            }
5734
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
5735
                break;
5736
            }
5737
            if (loop_control == SIXEL_LOOP_AUTO &&
5738
                frame->loop_count == anim_loop_count) {
5739
                break;
5740
            }
5741
        }
5742

5743
        status = SIXEL_OK;
5744
        goto end;
5745
    }
5746

5747
    hr = decoder->lpVtbl->GetFrame(decoder, 0, &wicframe);
5748
    if (FAILED(hr)) {
5749
        goto end;
5750
    }
5751

5752
    if (fuse_palette) {
5753
        hr = factory->lpVtbl->CreatePalette(factory, &wicpalette);
5754
        if (SUCCEEDED(hr)) {
5755
            hr = wicframe->lpVtbl->CopyPalette(wicframe, wicpalette);
5756
        }
5757
        if (SUCCEEDED(hr)) {
5758
            hr = wicpalette->lpVtbl->GetColorCount(wicpalette, &ncolors);
5759
        }
5760
        if (SUCCEEDED(hr) && ncolors > 0 && ncolors <= 256) {
5761
            hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5762
            if (SUCCEEDED(hr)) {
5763
                hr = conv->lpVtbl->Initialize(conv,
5764
                                              (IWICBitmapSource*)wicframe,
5765
                                              &GUID_WICPixelFormat8bppIndexed,
5766
                                              WICBitmapDitherTypeNone,
5767
                                              wicpalette,
5768
                                              0.0,
5769
                                              WICBitmapPaletteTypeCustom);
5770
            }
5771
            if (SUCCEEDED(hr)) {
5772
                src = (IWICBitmapSource*)conv;
5773
                comp = 1;
5774
                frame->pixelformat = SIXEL_PIXELFORMAT_PAL8;
5775
                frame->palette = sixel_allocator_malloc(
5776
                    pchunk->allocator,
5777
                    (size_t)ncolors * 3);
5778
                if (frame->palette == NULL) {
5779
                    hr = E_OUTOFMEMORY;
5780
                } else {
5781
                    wiccolors = (WICColor *)sixel_allocator_malloc(
5782
                        pchunk->allocator,
5783
                        (size_t)ncolors * sizeof(WICColor));
5784
                    if (wiccolors == NULL) {
5785
                        hr = E_OUTOFMEMORY;
5786
                    } else {
5787
                        actual = 0;
5788
                        hr = wicpalette->lpVtbl->GetColors(
5789
                            wicpalette, ncolors, wiccolors, &actual);
5790
                        if (SUCCEEDED(hr) && actual == ncolors) {
5791
                            for (i = 0; i < ncolors; ++i) {
5792
                                c = wiccolors[i];
5793
                                frame->palette[i * 3 + 0] = (unsigned char)((c >> 16) & 0xFF);
5794
                                frame->palette[i * 3 + 1] = (unsigned char)((c >> 8) & 0xFF);
5795
                                frame->palette[i * 3 + 2] = (unsigned char)(c & 0xFF);
5796
                            }
5797
                            frame->ncolors = (int)ncolors;
5798
                        } else {
5799
                            hr = E_FAIL;
5800
                        }
5801
                    }
5802
                }
5803
            }
5804
            if (FAILED(hr)) {
5805
                if (conv) {
5806
                    conv->lpVtbl->Release(conv);
5807
                    conv = NULL;
5808
                }
5809
                sixel_allocator_free(pchunk->allocator, frame->palette);
5810
                frame->palette = NULL;
5811
                sixel_allocator_free(pchunk->allocator, wiccolors);
5812
                wiccolors = NULL;
5813
                src = NULL;
5814
            }
5815
        }
5816
    }
5817

5818
    if (src == NULL) {
5819
        hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5820
        if (FAILED(hr)) {
5821
            goto end;
5822
        }
5823

5824
        hr = conv->lpVtbl->Initialize(conv, (IWICBitmapSource*)wicframe,
5825
                                      &GUID_WICPixelFormat32bppRGBA,
5826
                                      WICBitmapDitherTypeNone, NULL, 0.0,
5827
                                      WICBitmapPaletteTypeCustom);
5828
        if (FAILED(hr)) {
5829
            goto end;
5830
        }
5831

5832
        src = (IWICBitmapSource*)conv;
5833
        comp = 4;
5834
        frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5835
    }
5836

5837
    hr = src->lpVtbl->GetSize(
5838
        src, (UINT *)&frame->width, (UINT *)&frame->height);
5839
    if (FAILED(hr)) {
5840
        goto end;
5841
    }
5842

5843
    /* check size */
5844
    if (frame->width <= 0) {
5845
        sixel_helper_set_additional_message(
5846
            "load_with_wic: an invalid width parameter detected.");
5847
        status = SIXEL_BAD_INPUT;
5848
        goto end;
5849
    }
5850
    if (frame->height <= 0) {
5851
        sixel_helper_set_additional_message(
5852
            "load_with_wic: an invalid width parameter detected.");
5853
        status = SIXEL_BAD_INPUT;
5854
        goto end;
5855
    }
5856
    if (frame->width > SIXEL_WIDTH_LIMIT) {
5857
        sixel_helper_set_additional_message(
5858
            "load_with_wic: given width parameter is too huge.");
5859
        status = SIXEL_BAD_INPUT;
5860
        goto end;
5861
    }
5862
    if (frame->height > SIXEL_HEIGHT_LIMIT) {
5863
        sixel_helper_set_additional_message(
5864
            "load_with_wic: given height parameter is too huge.");
5865
        status = SIXEL_BAD_INPUT;
5866
        goto end;
5867
    }
5868

5869
    frame->pixels = sixel_allocator_malloc(
5870
        pchunk->allocator,
5871
        (size_t)(frame->height * frame->width * comp));
5872

5873
    {
5874
        WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
5875
        hr = src->lpVtbl->CopyPixels(
5876
            src,
5877
            &rc,                                        /* prc */
5878
            frame->width * comp,                        /* cbStride */
5879
            (UINT)frame->width * frame->height * comp,  /* cbBufferSize */
5880
            frame->pixels);                             /* pbBuffer */
5881
        if (FAILED(hr)) {
5882
            goto end;
5883
        }
5884
    }
5885

5886
    status = fn_load(frame, context);
5887
    if (SIXEL_FAILED(status)) {
5888
        goto end;
5889
    }
5890

5891
end:
5892
    if (conv) {
5893
         conv->lpVtbl->Release(conv);
5894
    }
5895
    if (wicpalette) {
5896
         wicpalette->lpVtbl->Release(wicpalette);
5897
    }
5898
    if (wiccolors) {
5899
         sixel_allocator_free(pchunk->allocator, wiccolors);
5900
    }
5901
    if (wicframe) {
5902
         wicframe->lpVtbl->Release(wicframe);
5903
    }
5904
    if (qdecoder) {
5905
         qdecoder->lpVtbl->Release(qdecoder);
5906
    }
5907
    if (qframe) {
5908
         qframe->lpVtbl->Release(qframe);
5909
    }
5910
    if (stream) {
5911
         stream->lpVtbl->Release(stream);
5912
    }
5913
    if (factory) {
5914
         factory->lpVtbl->Release(factory);
5915
    }
5916
    sixel_frame_unref(frame);
5917

5918
    CoUninitialize();
5919

5920
    if (FAILED(hr)) {
5921
        return SIXEL_FALSE;
5922
    }
5923

5924
    return SIXEL_OK;
5925
}
5926

5927
#endif /* HAVE_WIC */
5928

5929
#if HAVE_WIC
5930
static int
5931
loader_can_try_wic(sixel_chunk_t const *chunk)
5932
{
5933
    if (chunk == NULL) {
5934
        return 0;
5935
    }
5936
    if (chunk_is_gif(chunk)) {
5937
        return 0;
5938
    }
5939
    return 1;
5940
}
5941
#endif
5942

5943
static sixel_loader_entry_t const sixel_loader_entries[] = {
5944
    /*
5945
     * Fast loaders take precedence so probing prefers native decoders.
5946
     *
5947
     * 1. libpng   2. libjpeg   3. builtin   4+. remaining generic loaders
5948
     */
5949
#if HAVE_LIBPNG
5950
    { "libpng", load_with_libpng, loader_can_try_libpng, 1 },
5951
#endif
5952
#if HAVE_JPEG
5953
    { "libjpeg", load_with_libjpeg, loader_can_try_libjpeg, 1 },
5954
#endif
5955
    { "builtin", load_with_builtin, NULL, 1 },
5956
#if HAVE_WIC
5957
    { "wic", load_with_wic, loader_can_try_wic, 1 },
5958
#endif
5959
#if HAVE_COREGRAPHICS
5960
    { "coregraphics", load_with_coregraphics, NULL, 1 },
5961
#endif
5962
#ifdef HAVE_GDK_PIXBUF2
5963
    { "gdk-pixbuf2", load_with_gdkpixbuf, NULL, 1 },
5964
#endif
5965
#if HAVE_GD
5966
    { "gd", load_with_gd, NULL, 1 },
5967
#endif
5968
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
5969
    { "quicklook", load_with_quicklook, NULL, 0 },
5970
#endif
5971
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
5972
    { "gnome-thumbnailer", load_with_gnome_thumbnailer, NULL, 0 },
5973
#endif
5974
};
5975

5976
static int
5977
loader_entry_available(char const *name)
18✔
5978
{
5979
    size_t index;
5980
    size_t entry_count;
5981

5982
    if (name == NULL) {
18!
5983
        return 0;
×
5984
    }
5985

5986
    entry_count = sizeof(sixel_loader_entries) /
18✔
5987
                  sizeof(sixel_loader_entries[0]);
5988

5989
    for (index = 0; index < entry_count; ++index) {
51!
5990
        if (sixel_loader_entries[index].name != NULL &&
45!
5991
                strcmp(sixel_loader_entries[index].name, name) == 0) {
45✔
5992
            return 1;
12✔
5993
        }
5994
    }
15✔
5995

5996
    return 0;
6✔
5997
}
6✔
5998

5999
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
6000
static void
6001
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
4✔
6002
{
6003
    if (buffer == NULL || capacity == 0) {
4✔
6004
        return;
6005
    }
6006

6007
    buffer[0] = '\0';
4✔
6008
    if (source == NULL) {
4!
6009
        return;
6010
    }
6011

6012
    if (!CFStringGetCString(source,
8✔
6013
                             buffer,
4✔
6014
                             (CFIndex)capacity,
4✔
6015
                             kCFStringEncodingUTF8)) {
6016
        buffer[0] = '\0';
6017
    }
6018
}
4✔
6019

6020
static int
6021
loader_quicklook_can_decode(sixel_chunk_t const *pchunk,
3✔
6022
                            char const *filename)
6023
{
6024
    char const *path;
6025
    CFStringRef path_ref;
6026
    CFURLRef url;
6027
    CGFloat max_dimension;
6028
    CGSize max_size;
6029
    CGImageRef image;
6030
    int result;
6031

6032
    path = NULL;
3✔
6033
    path_ref = NULL;
3✔
6034
    url = NULL;
3✔
6035
    image = NULL;
3✔
6036
    result = 0;
3✔
6037

6038
    loader_thumbnailer_initialize_size_hint();
3✔
6039

6040
    if (pchunk != NULL && pchunk->source_path != NULL) {
3✔
6041
        path = pchunk->source_path;
2✔
6042
    } else if (filename != NULL) {
2✔
6043
        path = filename;
6044
    }
6045

6046
    if (path == NULL || strcmp(path, "-") == 0 ||
3✔
6047
            strstr(path, "://") != NULL) {
2✔
6048
        return 0;
1✔
6049
    }
6050

6051
    path_ref = CFStringCreateWithCString(kCFAllocatorDefault,
4✔
6052
                                         path,
2✔
6053
                                         kCFStringEncodingUTF8);
6054
    if (path_ref == NULL) {
2!
6055
        return 0;
6056
    }
6057

6058
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
4✔
6059
                                        path_ref,
2✔
6060
                                        kCFURLPOSIXPathStyle,
6061
                                        false);
6062
    CFRelease(path_ref);
2✔
6063
    path_ref = NULL;
2✔
6064
    if (url == NULL) {
2!
6065
        return 0;
6066
    }
6067

6068
    if (thumbnailer_size_hint > 0) {
2!
6069
        max_dimension = (CGFloat)thumbnailer_size_hint;
2✔
6070
    } else {
2✔
6071
        max_dimension = (CGFloat)SIXEL_THUMBNAILER_DEFAULT_SIZE;
6072
    }
6073
    max_size.width = max_dimension;
2✔
6074
    max_size.height = max_dimension;
2✔
6075

6076
#if HAVE_QUICKLOOK_THUMBNAILING
6077
    image = sixel_quicklook_thumbnail_create(url, max_size);
2✔
6078
    if (image == NULL) {
2✔
6079
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6080
#  pragma clang diagnostic push
6081
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
6082
# endif
6083
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
4✔
6084
                                       url,
2✔
6085
                                       max_size,
6086
                                       NULL);
6087
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6088
#  pragma clang diagnostic pop
6089
# endif
6090
    }
2✔
6091
#else
6092
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6093
#  pragma clang diagnostic push
6094
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
6095
# endif
6096
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
6097
                                   url,
6098
                                   max_size,
6099
                                   NULL);
6100
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6101
#  pragma clang diagnostic pop
6102
# endif
6103
#endif
6104

6105
    if (image != NULL) {
4✔
6106
        result = 1;
6107
        CGImageRelease(image);
6108
        image = NULL;
6109
    }
6110

6111
    CFRelease(url);
2✔
6112
    url = NULL;
2✔
6113

6114
    return result;
2✔
6115
}
3✔
6116
#else
6117
static int
6118
loader_quicklook_can_decode(sixel_chunk_t const *pchunk,
6119
                            char const *filename)
6120
{
6121
    (void)pchunk;
6122
    (void)filename;
6123
    return 0;
6124
}
6125
#endif
6126

6127
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6128
static void
6129
loader_probe_gnome_thumbnailers(char const *mime_type,
9✔
6130
                                int *has_directories,
6131
                                int *has_match)
6132
{
6133
    struct thumbnailer_string_list *directories;
6134
    struct thumbnailer_entry info;
6135
    size_t dir_index;
6136
    DIR *dir;
6137
    struct dirent *entry;
6138
    char *thumbnailer_path;
6139
    int match;
6140
    int directories_present;
6141
    size_t name_length;
6142

6143
    directories = NULL;
9✔
6144
    dir_index = 0;
9✔
6145
    dir = NULL;
9✔
6146
    entry = NULL;
9✔
6147
    thumbnailer_path = NULL;
9✔
6148
    match = 0;
9✔
6149
    directories_present = 0;
9✔
6150
    name_length = 0;
9✔
6151

6152
    if (has_directories != NULL) {
9!
6153
        *has_directories = 0;
9✔
6154
    }
3✔
6155
    if (has_match != NULL) {
9!
6156
        *has_match = 0;
9✔
6157
    }
3✔
6158

6159
    directories = thumbnailer_collect_directories();
9✔
6160
    if (directories == NULL) {
9!
6161
        return;
×
6162
    }
6163

6164
    if (directories->length > 0) {
9!
6165
        directories_present = 1;
9✔
6166
        if (has_directories != NULL) {
9!
6167
            *has_directories = 1;
9✔
6168
        }
3✔
6169
    }
3✔
6170

6171
    thumbnailer_entry_init(&info);
10✔
6172

6173
    if (mime_type != NULL && mime_type[0] != '\0') {
10!
6174
        for (dir_index = 0; dir_index < directories->length && match == 0;
24!
6175
                ++dir_index) {
18✔
6176
            dir = opendir(directories->items[dir_index]);
18✔
6177
            if (dir == NULL) {
18!
6178
                continue;
18✔
6179
            }
6180
            while (match == 0 && (entry = readdir(dir)) != NULL) {
×
6181
                thumbnailer_entry_clear(&info);
×
6182
                thumbnailer_entry_init(&info);
×
6183
                name_length = strlen(entry->d_name);
×
6184
                if (name_length < 12 ||
×
6185
                        strcmp(entry->d_name + name_length - 12,
×
6186
                               ".thumbnailer") != 0) {
6187
                    continue;
×
6188
                }
6189
                thumbnailer_path = thumbnailer_join_paths(
×
6190
                    directories->items[dir_index],
×
6191
                    entry->d_name);
×
6192
                if (thumbnailer_path == NULL) {
×
6193
                    continue;
×
6194
                }
6195
                if (!thumbnailer_parse_file(thumbnailer_path, &info)) {
×
6196
                    free(thumbnailer_path);
×
6197
                    thumbnailer_path = NULL;
×
6198
                    continue;
×
6199
                }
6200
                free(thumbnailer_path);
×
6201
                thumbnailer_path = NULL;
×
6202
                if (!thumbnailer_has_tryexec(info.tryexec)) {
×
6203
                    continue;
×
6204
                }
6205
                if (thumbnailer_supports_mime(&info, mime_type)) {
×
6206
                    match = 1;
×
6207
                }
6208
            }
6209
            closedir(dir);
×
6210
            dir = NULL;
×
6211
        }
6212
    }
2✔
6213

6214
    thumbnailer_entry_clear(&info);
9✔
6215
    thumbnailer_string_list_free(directories);
9✔
6216

6217
    if (directories_present && has_match != NULL) {
9!
6218
        *has_match = match;
9✔
6219
    }
3✔
6220
}
3✔
6221
#endif
6222

6223
static void
6224
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
9✔
6225
                          char const *filename)
6226
{
6227
    enum { description_length = 128 };
6228
    enum { uttype_length = 128 };
6229
    enum { extension_length = 32 };
6230
    enum { message_length = 768 };
6231
    char message[message_length];
6232
    char type_value[description_length];
6233
    char extension_text[extension_length + 2];
6234
    char uttype[uttype_length];
6235
    char desc_buffer[description_length];
6236
    char extension[extension_length];
6237
    char const *path;
6238
    char const *display_path;
6239
    char const *metadata_path;
6240
    char const *description_text;
6241
    char *mime_string;
6242
    char *description_string;
6243
    size_t offset;
6244
    int quicklook_available;
6245
    int quicklook_supported;
6246
    int gnome_available;
6247
    int gnome_has_dirs;
6248
    int gnome_has_match;
6249
    int suggestions;
6250

6251
    message[0] = '\0';
9✔
6252
    type_value[0] = '\0';
9✔
6253
    extension_text[0] = '\0';
9✔
6254
    uttype[0] = '\0';
9✔
6255
    desc_buffer[0] = '\0';
9✔
6256
    extension[0] = '\0';
9✔
6257
    path = NULL;
9✔
6258
    display_path = "(stdin)";
9✔
6259
    metadata_path = NULL;
9✔
6260
    description_text = NULL;
9✔
6261
    mime_string = NULL;
9✔
6262
    description_string = NULL;
9✔
6263
    offset = 0u;
9✔
6264
    quicklook_available = 0;
9✔
6265
    quicklook_supported = 0;
9✔
6266
    gnome_available = 0;
9✔
6267
    gnome_has_dirs = 0;
9✔
6268
    gnome_has_match = 0;
9✔
6269
    suggestions = 0;
9✔
6270

6271
    if (pchunk != NULL && pchunk->source_path != NULL) {
9!
6272
        path = pchunk->source_path;
6✔
6273
    } else if (filename != NULL) {
4!
6274
        path = filename;
×
6275
    }
6276

6277
    if (path != NULL && strcmp(path, "-") != 0) {
9!
6278
        display_path = path;
6✔
6279
    }
2✔
6280

6281
    if (path != NULL && strcmp(path, "-") != 0 &&
8!
6282
            strstr(path, "://") == NULL) {
6!
6283
        metadata_path = path;
6✔
6284
    }
2✔
6285

6286
    loader_extract_extension(path, extension, sizeof(extension));
8✔
6287

6288
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6289
    if (metadata_path != NULL) {
8✔
6290
        /*
6291
         * Collect MIME metadata via file(1) when fork() and friends are
6292
         * available.  Windows builds compiled with clang64 lack these
6293
         * interfaces, so the thumbnail helpers remain disabled there.
6294
         */
6295
        mime_string = thumbnailer_guess_content_type(metadata_path);
6✔
6296
        description_string = thumbnailer_run_file(metadata_path, NULL);
6✔
6297
    }
2✔
6298
#else
6299
    (void)metadata_path;
6300
#endif
6301

6302
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
6303
#if defined(__clang__)
6304
    /*
6305
     * Allow use of legacy UTType C APIs when compiling with the
6306
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
6307
     * so we must intentionally silence the deprecation warnings here.
6308
     */
6309
#pragma clang diagnostic push
6310
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
6311
#endif
6312
    {
6313
        CFStringRef uti_ref;
6314
        CFStringRef mime_ref;
6315
        CFStringRef ext_ref;
6316
        CFStringRef desc_ref;
6317
        CFStringRef preferred_mime;
6318
        char uti_local[uttype_length];
6319
        char desc_local[description_length];
6320
        char mime_local[64];
6321

6322
        uti_ref = NULL;
2✔
6323
        mime_ref = NULL;
2✔
6324
        ext_ref = NULL;
2✔
6325
        desc_ref = NULL;
2✔
6326
        preferred_mime = NULL;
2✔
6327
        uti_local[0] = '\0';
2✔
6328
        desc_local[0] = '\0';
2✔
6329
        mime_local[0] = '\0';
2✔
6330

6331
        if (mime_string != NULL) {
2✔
6332
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
4✔
6333
                                                 mime_string,
2✔
6334
                                                 kCFStringEncodingUTF8);
6335
        }
2✔
6336
        if (mime_ref != NULL) {
2✔
6337
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
2✔
6338
                kUTTagClassMIMEType,
2✔
6339
                mime_ref,
2✔
6340
                NULL);
6341
        }
2✔
6342
        if (uti_ref == NULL && extension[0] != '\0') {
3✔
6343
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
6344
                                                extension,
6345
                                                kCFStringEncodingUTF8);
6346
            if (ext_ref != NULL) {
6347
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
6348
                    kUTTagClassFilenameExtension,
6349
                    ext_ref,
6350
                    NULL);
6351
            }
6352
        }
6353
        if (uti_ref != NULL) {
2✔
6354
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
2✔
6355
            desc_ref = UTTypeCopyDescription(uti_ref);
2✔
6356
            if (desc_ref != NULL) {
2✔
6357
                loader_copy_cfstring(desc_ref,
4✔
6358
                                     desc_local,
2✔
6359
                                     sizeof(desc_local));
6360
                CFRelease(desc_ref);
2✔
6361
                desc_ref = NULL;
2✔
6362
            }
2✔
6363
            if (mime_string == NULL) {
4✔
6364
                preferred_mime = UTTypeCopyPreferredTagWithClass(
6365
                    uti_ref,
6366
                    kUTTagClassMIMEType);
6367
                if (preferred_mime != NULL) {
6368
                    loader_copy_cfstring(preferred_mime,
6369
                                         mime_local,
6370
                                         sizeof(mime_local));
6371
                    CFRelease(preferred_mime);
6372
                    preferred_mime = NULL;
6373
                }
6374
                if (mime_local[0] != '\0') {
6375
                    mime_string = thumbnailer_strdup(mime_local);
6376
                }
6377
            }
6378
        }
2✔
6379
        if (mime_ref != NULL) {
2✔
6380
            CFRelease(mime_ref);
2✔
6381
        }
2✔
6382
        if (ext_ref != NULL) {
4✔
6383
            CFRelease(ext_ref);
6384
        }
6385
        if (uti_ref != NULL) {
2✔
6386
            CFRelease(uti_ref);
2✔
6387
        }
2✔
6388
        if (uti_local[0] != '\0') {
2✔
6389
            sixel_compat_snprintf(uttype,
4✔
6390
                                  sizeof(uttype),
6391
                                  "%s",
6392
                                  uti_local);
2✔
6393
        }
2✔
6394
        if (desc_local[0] != '\0') {
2✔
6395
            sixel_compat_snprintf(desc_buffer,
4✔
6396
                                  sizeof(desc_buffer),
6397
                                  "%s",
6398
                                  desc_local);
2✔
6399
        }
2✔
6400
    }
6401
#if defined(__clang__)
6402
#pragma clang diagnostic pop
6403
#endif
6404
#endif
6405

6406
    if (description_string != NULL && description_string[0] != '\0') {
8!
6407
        description_text = description_string;
6✔
6408
    } else if (desc_buffer[0] != '\0') {
5!
6409
        description_text = desc_buffer;
×
6410
    } else {
6411
        description_text = "unknown content";
3✔
6412
    }
6413

6414
    sixel_compat_snprintf(type_value,
12✔
6415
                          sizeof(type_value),
6416
                          "%s",
6417
                          description_text);
3✔
6418

6419
    loader_append_chunk(message,
9✔
6420
                        sizeof(message),
6421
                        &offset,
6422
                        "diagnostic:\n");
6423
    loader_append_key_value(message,
12✔
6424
                            sizeof(message),
6425
                            &offset,
6426
                            "file",
6427
                            display_path);
3✔
6428
    loader_append_key_value(message,
12✔
6429
                            sizeof(message),
6430
                            &offset,
6431
                            "type",
6432
                            type_value);
3✔
6433

6434
    if (mime_string != NULL && mime_string[0] != '\0') {
9!
6435
        loader_append_key_value(message,
8✔
6436
                                sizeof(message),
6437
                                &offset,
6438
                                "mime",
6439
                                mime_string);
2✔
6440
    }
2✔
6441

6442
    if (uttype[0] != '\0') {
8!
6443
        loader_append_key_value(message,
4✔
6444
                                sizeof(message),
6445
                                &offset,
6446
                                "uti",
6447
                                uttype);
2✔
6448
    }
2✔
6449

6450
    if (extension[0] != '\0') {
10!
6451
        sixel_compat_snprintf(extension_text,
×
6452
                              sizeof(extension_text),
6453
                              ".%s",
6454
                              extension);
6455
        loader_append_key_value(message,
×
6456
                                sizeof(message),
6457
                                &offset,
6458
                                "extension",
6459
                                extension_text);
6460
    }
6461

6462
    loader_append_chunk(message,
9✔
6463
                        sizeof(message),
6464
                        &offset,
6465
                        "  suggestions:\n");
6466

6467
    quicklook_available = loader_entry_available("quicklook");
9✔
6468
    if (quicklook_available) {
9!
6469
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
3✔
6470
    }
3✔
6471
    if (quicklook_supported) {
12!
6472
        loader_append_chunk(message,
×
6473
                            sizeof(message),
6474
                            &offset,
6475
                            "    - QuickLook rendered a preview during "
6476
                            "the probe; try -j quicklook.\n");
6477
        suggestions += 1;
×
6478
    }
6479

6480
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6481
    gnome_available = loader_entry_available("gnome-thumbnailer");
9✔
6482
    if (gnome_available) {
9!
6483
        loader_probe_gnome_thumbnailers(mime_string,
9✔
6484
                                        &gnome_has_dirs,
6485
                                        &gnome_has_match);
6486
        if (gnome_has_dirs && gnome_has_match) {
9!
6487
            loader_append_chunk(message,
×
6488
                                sizeof(message),
6489
                                &offset,
6490
                                "    - GNOME thumbnailer definitions match "
6491
                                "this MIME type; try -j gnome-thumbnailer.\n"
6492
                                );
6493
            suggestions += 1;
×
6494
        }
6495
    }
3✔
6496
#else
6497
    (void)gnome_available;
6498
    (void)gnome_has_dirs;
6499
    (void)gnome_has_match;
6500
#endif
6501

6502
    if (suggestions == 0) {
9!
6503
        loader_append_chunk(message,
9✔
6504
                            sizeof(message),
6505
                            &offset,
6506
                            "    (no thumbnail helper hints)\n");
6507
    }
3✔
6508

6509
    if (suggestions > 0) {
9!
6510
        loader_append_chunk(message,
×
6511
                            sizeof(message),
6512
                            &offset,
6513
                            "  hint       : Enable one of the suggested "
6514
                            "loaders with -j.\n");
6515
    } else {
6516
        loader_append_chunk(message,
9✔
6517
                            sizeof(message),
6518
                            &offset,
6519
                            "  hint       : Convert the file to PNG or "
6520
                            "enable optional loaders.\n");
6521
    }
6522

6523
    sixel_helper_set_additional_message(message);
9✔
6524

6525
    free(mime_string);
9✔
6526
    free(description_string);
9✔
6527
}
9✔
6528

6529
SIXELAPI SIXELSTATUS
6530
sixel_loader_new(
457✔
6531
    sixel_loader_t   /* out */ **pploader,
6532
    sixel_allocator_t/* in */  *allocator)
6533
{
6534
    SIXELSTATUS status = SIXEL_FALSE;
457✔
6535
    sixel_loader_t *loader;
6536
    sixel_allocator_t *local_allocator;
6537

6538
    loader = NULL;
457✔
6539
    local_allocator = allocator;
457✔
6540

6541
    if (pploader == NULL) {
457!
6542
        sixel_helper_set_additional_message(
×
6543
            "sixel_loader_new: pploader is null.");
6544
        status = SIXEL_BAD_ARGUMENT;
×
6545
        goto end;
×
6546
    }
6547

6548
    if (local_allocator == NULL) {
457!
6549
        status = sixel_allocator_new(&local_allocator,
×
6550
                                     NULL,
6551
                                     NULL,
6552
                                     NULL,
6553
                                     NULL);
6554
        if (SIXEL_FAILED(status)) {
×
6555
            goto end;
×
6556
        }
6557
    } else {
6558
        sixel_allocator_ref(local_allocator);
457✔
6559
    }
6560

6561
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
457✔
6562
                                                      sizeof(*loader));
6563
    if (loader == NULL) {
457!
6564
        sixel_helper_set_additional_message(
×
6565
            "sixel_loader_new: sixel_allocator_malloc() failed.");
6566
        status = SIXEL_BAD_ALLOCATION;
×
6567
        sixel_allocator_unref(local_allocator);
×
6568
        goto end;
×
6569
    }
6570

6571
    loader->ref = 1;
457✔
6572
    loader->fstatic = 0;
457✔
6573
    loader->fuse_palette = 0;
457✔
6574
    loader->reqcolors = SIXEL_PALETTE_MAX;
457✔
6575
    loader->bgcolor[0] = 0;
457✔
6576
    loader->bgcolor[1] = 0;
457✔
6577
    loader->bgcolor[2] = 0;
457✔
6578
    loader->has_bgcolor = 0;
457✔
6579
    loader->loop_control = SIXEL_LOOP_AUTO;
457✔
6580
    loader->finsecure = 0;
457✔
6581
    loader->cancel_flag = NULL;
457✔
6582
    loader->context = NULL;
457✔
6583
    loader->assessment = NULL;
457✔
6584
    loader->loader_order = NULL;
457✔
6585
    loader->allocator = local_allocator;
457✔
6586
    loader->last_loader_name[0] = '\0';
457✔
6587
    loader->last_source_path[0] = '\0';
457✔
6588
    loader->last_input_bytes = 0u;
457✔
6589

6590
    *pploader = loader;
457✔
6591
    status = SIXEL_OK;
457✔
6592

6593
end:
304✔
6594
    return status;
457✔
6595
}
6596

6597
SIXELAPI void
6598
sixel_loader_ref(
5,006✔
6599
    sixel_loader_t /* in */ *loader)
6600
{
6601
    if (loader == NULL) {
5,006!
6602
        return;
×
6603
    }
6604

6605
    ++loader->ref;
5,006✔
6606
}
1,676✔
6607

6608
SIXELAPI void
6609
sixel_loader_unref(
5,463✔
6610
    sixel_loader_t /* in */ *loader)
6611
{
6612
    sixel_allocator_t *allocator;
6613

6614
    if (loader == NULL) {
5,463!
6615
        return;
×
6616
    }
6617

6618
    if (--loader->ref == 0) {
5,463✔
6619
        allocator = loader->allocator;
457✔
6620
        sixel_allocator_free(allocator, loader->loader_order);
457✔
6621
        sixel_allocator_free(allocator, loader);
457✔
6622
        sixel_allocator_unref(allocator);
457✔
6623
    }
153✔
6624
}
1,829✔
6625

6626
SIXELAPI SIXELSTATUS
6627
sixel_loader_setopt(
4,549✔
6628
    sixel_loader_t /* in */ *loader,
6629
    int            /* in */ option,
6630
    void const     /* in */ *value)
6631
{
6632
    SIXELSTATUS status = SIXEL_FALSE;
4,549✔
6633
    int const *flag;
6634
    unsigned char const *color;
6635
    char const *order;
6636
    char *copy;
6637
    sixel_allocator_t *allocator;
6638

6639
    flag = NULL;
4,549✔
6640
    color = NULL;
4,549✔
6641
    order = NULL;
4,549✔
6642
    copy = NULL;
4,549✔
6643
    allocator = NULL;
4,549✔
6644

6645
    if (loader == NULL) {
4,549!
6646
        sixel_helper_set_additional_message(
×
6647
            "sixel_loader_setopt: loader is null.");
6648
        status = SIXEL_BAD_ARGUMENT;
×
6649
        goto end0;
×
6650
    }
6651

6652
    sixel_loader_ref(loader);
4,549✔
6653

6654
    switch (option) {
4,549!
6655
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
304✔
6656
        flag = (int const *)value;
457✔
6657
        loader->fstatic = flag != NULL ? *flag : 0;
457!
6658
        status = SIXEL_OK;
457✔
6659
        break;
457✔
6660
    case SIXEL_LOADER_OPTION_USE_PALETTE:
304✔
6661
        flag = (int const *)value;
457✔
6662
        loader->fuse_palette = flag != NULL ? *flag : 0;
457!
6663
        status = SIXEL_OK;
457✔
6664
        break;
457✔
6665
    case SIXEL_LOADER_OPTION_REQCOLORS:
304✔
6666
        flag = (int const *)value;
457✔
6667
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
457!
6668
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
457!
6669
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
6670
        }
6671
        status = SIXEL_OK;
457✔
6672
        break;
457✔
6673
    case SIXEL_LOADER_OPTION_BGCOLOR:
304✔
6674
        if (value == NULL) {
457✔
6675
            loader->has_bgcolor = 0;
439✔
6676
        } else {
147✔
6677
            color = (unsigned char const *)value;
18✔
6678
            loader->bgcolor[0] = color[0];
18✔
6679
            loader->bgcolor[1] = color[1];
18✔
6680
            loader->bgcolor[2] = color[2];
18✔
6681
            loader->has_bgcolor = 1;
18✔
6682
        }
6683
        status = SIXEL_OK;
457✔
6684
        break;
457✔
6685
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
304✔
6686
        flag = (int const *)value;
457✔
6687
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
457!
6688
        status = SIXEL_OK;
457✔
6689
        break;
457✔
6690
    case SIXEL_LOADER_OPTION_INSECURE:
304✔
6691
        flag = (int const *)value;
457✔
6692
        loader->finsecure = flag != NULL ? *flag : 0;
457!
6693
        status = SIXEL_OK;
457✔
6694
        break;
457✔
6695
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
304✔
6696
        loader->cancel_flag = (int const *)value;
457✔
6697
        status = SIXEL_OK;
457✔
6698
        break;
457✔
6699
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
304✔
6700
        allocator = loader->allocator;
457✔
6701
        sixel_allocator_free(allocator, loader->loader_order);
457✔
6702
        loader->loader_order = NULL;
457✔
6703
        if (value != NULL) {
457!
6704
            order = (char const *)value;
×
6705
            copy = loader_strdup(order, allocator);
×
6706
            if (copy == NULL) {
×
6707
                sixel_helper_set_additional_message(
×
6708
                    "sixel_loader_setopt: loader_strdup() failed.");
6709
                status = SIXEL_BAD_ALLOCATION;
×
6710
                goto end;
×
6711
            }
6712
            loader->loader_order = copy;
×
6713
        }
6714
        status = SIXEL_OK;
457✔
6715
        break;
457✔
6716
    case SIXEL_LOADER_OPTION_CONTEXT:
304✔
6717
        loader->context = (void *)value;
457✔
6718
        loader->assessment = NULL;
457✔
6719
        status = SIXEL_OK;
457✔
6720
        break;
457✔
6721
    case SIXEL_LOADER_OPTION_ASSESSMENT:
290✔
6722
        loader->assessment = (sixel_assessment_t *)value;
436✔
6723
        status = SIXEL_OK;
436✔
6724
        break;
436✔
6725
    default:
6726
        sixel_helper_set_additional_message(
×
6727
            "sixel_loader_setopt: unknown option.");
6728
        status = SIXEL_BAD_ARGUMENT;
×
6729
        goto end;
×
6730
    }
1,523✔
6731

6732
end:
3,026✔
6733
    sixel_loader_unref(loader);
4,549✔
6734

6735
end0:
3,026✔
6736
    return status;
4,549✔
6737
}
6738

6739
SIXELAPI char const *
6740
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
842✔
6741
{
6742
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
842!
6743
        return NULL;
×
6744
    }
6745
    return loader->last_loader_name;
842✔
6746
}
282✔
6747

6748
SIXELAPI char const *
6749
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
704✔
6750
{
6751
    if (loader == NULL || loader->last_source_path[0] == '\0') {
704!
6752
        return NULL;
138✔
6753
    }
6754
    return loader->last_source_path;
566✔
6755
}
236✔
6756

6757
SIXELAPI size_t
6758
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
421✔
6759
{
6760
    if (loader == NULL) {
421!
6761
        return 0u;
×
6762
    }
6763
    return loader->last_input_bytes;
421✔
6764
}
141✔
6765

6766
SIXELAPI SIXELSTATUS
6767
sixel_loader_load_file(
457✔
6768
    sixel_loader_t         /* in */ *loader,
6769
    char const             /* in */ *filename,
6770
    sixel_load_image_function /* in */ fn_load)
6771
{
6772
    SIXELSTATUS status = SIXEL_FALSE;
457✔
6773
    sixel_chunk_t *pchunk;
6774
    sixel_loader_entry_t const *plan[
6775
        sizeof(sixel_loader_entries) / sizeof(sixel_loader_entries[0])];
6776
    size_t entry_count;
6777
    size_t plan_length;
6778
    size_t plan_index;
6779
    unsigned char *bgcolor;
6780
    int reqcolors;
6781
    sixel_assessment_t *assessment;
6782
    char const *order_override;
6783
    char const *env_order;
6784

6785
    pchunk = NULL;
457✔
6786
    entry_count = 0;
457✔
6787
    plan_length = 0;
457✔
6788
    plan_index = 0;
457✔
6789
    bgcolor = NULL;
457✔
6790
    reqcolors = 0;
457✔
6791
    assessment = NULL;
457✔
6792
    order_override = NULL;
457✔
6793
    env_order = NULL;
457✔
6794

6795
    if (loader == NULL) {
457!
6796
        sixel_helper_set_additional_message(
×
6797
            "sixel_loader_load_file: loader is null.");
6798
        status = SIXEL_BAD_ARGUMENT;
×
6799
        goto end0;
×
6800
    }
6801

6802
    sixel_loader_ref(loader);
457✔
6803

6804
    entry_count = sizeof(sixel_loader_entries) /
457✔
6805
                  sizeof(sixel_loader_entries[0]);
6806

6807
    reqcolors = loader->reqcolors;
457✔
6808
    if (reqcolors > SIXEL_PALETTE_MAX) {
457!
6809
        reqcolors = SIXEL_PALETTE_MAX;
×
6810
    }
6811

6812
    assessment = loader->assessment;
457✔
6813

6814
    /*
6815
     *  Assessment pipeline sketch:
6816
     *
6817
     *      +-------------+        +--------------+
6818
     *      | chunk read  | -----> | image decode |
6819
     *      +-------------+        +--------------+
6820
     *
6821
     *  The loader owns the hand-off.  Chunk I/O ends before any decoder runs,
6822
     *  so we time the read span in the encoder and pivot to decode once the
6823
     *  chunk is populated.
6824
     */
6825
    status = sixel_chunk_new(&pchunk,
457✔
6826
                             filename,
153✔
6827
                             loader->finsecure,
153✔
6828
                             loader->cancel_flag,
153✔
6829
                             loader->allocator);
153✔
6830
    if (status != SIXEL_OK) {
457✔
6831
        goto end;
6✔
6832
    }
6833

6834
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
451!
6835
        status = SIXEL_OK;
×
6836
        goto end;
×
6837
    }
6838

6839
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
451!
6840
        status = SIXEL_LOGIC_ERROR;
×
6841
        goto end;
×
6842
    }
6843

6844
    if (loader->has_bgcolor) {
451✔
6845
        bgcolor = loader->bgcolor;
18✔
6846
    }
6✔
6847

6848
    status = SIXEL_FALSE;
311✔
6849
    if (assessment != NULL) {
311✔
6850
        sixel_assessment_stage_transition(
3✔
6851
            assessment,
1✔
6852
            SIXEL_ASSESSMENT_STAGE_IMAGE_DECODE);
6853
    }
1✔
6854
    order_override = loader->loader_order;
451✔
6855
    /*
6856
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
6857
     * a loader order via -j/--loaders or sixel_loader_setopt().
6858
     */
6859
    if (order_override == NULL) {
451!
6860
        env_order = getenv("SIXEL_LOADER_PRIORITY_LIST");
451✔
6861
        if (env_order != NULL && env_order[0] != '\0') {
451!
6862
            order_override = env_order;
×
6863
        }
6864
    }
151✔
6865

6866
    plan_length = loader_build_plan(order_override,
602✔
6867
                                    sixel_loader_entries,
6868
                                    entry_count,
151✔
6869
                                    plan,
151✔
6870
                                    entry_count);
151✔
6871

6872
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
463✔
6873
        if (plan[plan_index] == NULL) {
454!
6874
            continue;
×
6875
        }
6876
        if (plan[plan_index]->predicate != NULL &&
454!
6877
            plan[plan_index]->predicate(pchunk) == 0) {
×
6878
            continue;
×
6879
        }
6880
        loader_trace_try(plan[plan_index]->name);
454✔
6881
        status = plan[plan_index]->backend(pchunk,
608✔
6882
                                           loader->fstatic,
154✔
6883
                                           loader->fuse_palette,
154✔
6884
                                           reqcolors,
154✔
6885
                                           bgcolor,
154✔
6886
                                           loader->loop_control,
154✔
6887
                                           fn_load,
154✔
6888
                                           loader->context);
154✔
6889
        loader_trace_result(plan[plan_index]->name, status);
454✔
6890
        if (SIXEL_SUCCEEDED(status)) {
454✔
6891
            break;
442✔
6892
        }
6893
    }
6✔
6894

6895
    if (SIXEL_FAILED(status)) {
451✔
6896
        loader_publish_diagnostic(pchunk, filename);
9✔
6897
        goto end;
9✔
6898
    }
6899

6900
    if (plan_index < plan_length &&
590!
6901
            plan[plan_index] != NULL &&
442!
6902
            plan[plan_index]->name != NULL) {
442!
6903
        (void)snprintf(loader->last_loader_name,
442✔
6904
                       sizeof(loader->last_loader_name),
6905
                       "%s",
6906
                       plan[plan_index]->name);
294✔
6907
    } else {
148✔
6908
        loader->last_loader_name[0] = '\0';
×
6909
    }
6910
    loader->last_input_bytes = pchunk->size;
442✔
6911
    if (pchunk->source_path != NULL) {
590✔
6912
        size_t path_len;
6913

6914
        path_len = strlen(pchunk->source_path);
304✔
6915
        if (path_len >= sizeof(loader->last_source_path)) {
304!
6916
            path_len = sizeof(loader->last_source_path) - 1u;
×
6917
        }
6918
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
304✔
6919
        loader->last_source_path[path_len] = '\0';
304✔
6920
    } else {
102✔
6921
        loader->last_source_path[0] = '\0';
138✔
6922
    }
6923

6924
end:
304✔
6925
    sixel_chunk_destroy(pchunk);
457✔
6926
    sixel_loader_unref(loader);
457✔
6927

6928
end0:
304✔
6929
    return status;
457✔
6930
}
6931

6932
/* load image from file */
6933

6934
SIXELAPI SIXELSTATUS
6935
sixel_helper_load_image_file(
×
6936
    char const                /* in */     *filename,     /* source file name */
6937
    int                       /* in */     fstatic,       /* whether to extract static image
6938
                                                             from animated gif */
6939
    int                       /* in */     fuse_palette,  /* whether to use paletted image,
6940
                                                             set non-zero value to try to get
6941
                                                             paletted image */
6942
    int                       /* in */     reqcolors,     /* requested number of colors,
6943
                                                             should be equal or less than
6944
                                                             SIXEL_PALETTE_MAX */
6945
    unsigned char             /* in */     *bgcolor,      /* background color, may be NULL */
6946
    int                       /* in */     loop_control,  /* one of enum loopControl */
6947
    sixel_load_image_function /* in */     fn_load,       /* callback */
6948
    int                       /* in */     finsecure,     /* true if do not verify SSL */
6949
    int const                 /* in */     *cancel_flag,  /* cancel flag, may be NULL */
6950
    void                      /* in/out */ *context,      /* private data which is passed to
6951
                                                             callback function as an
6952
                                                             argument, may be NULL */
6953
    sixel_allocator_t         /* in */     *allocator     /* allocator object, may be NULL */
6954
)
6955
{
6956
    SIXELSTATUS status = SIXEL_FALSE;
×
6957
    sixel_loader_t *loader;
6958

6959
    loader = NULL;
×
6960

6961
    status = sixel_loader_new(&loader, allocator);
×
6962
    if (SIXEL_FAILED(status)) {
×
6963
        goto end;
×
6964
    }
6965

6966
    status = sixel_loader_setopt(loader,
×
6967
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
6968
                                 &fstatic);
6969
    if (SIXEL_FAILED(status)) {
×
6970
        goto end;
×
6971
    }
6972

6973
    status = sixel_loader_setopt(loader,
×
6974
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
6975
                                 &fuse_palette);
6976
    if (SIXEL_FAILED(status)) {
×
6977
        goto end;
×
6978
    }
6979

6980
    status = sixel_loader_setopt(loader,
×
6981
                                 SIXEL_LOADER_OPTION_REQCOLORS,
6982
                                 &reqcolors);
6983
    if (SIXEL_FAILED(status)) {
×
6984
        goto end;
×
6985
    }
6986

6987
    status = sixel_loader_setopt(loader,
×
6988
                                 SIXEL_LOADER_OPTION_BGCOLOR,
6989
                                 bgcolor);
6990
    if (SIXEL_FAILED(status)) {
×
6991
        goto end;
×
6992
    }
6993

6994
    status = sixel_loader_setopt(loader,
×
6995
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
6996
                                 &loop_control);
6997
    if (SIXEL_FAILED(status)) {
×
6998
        goto end;
×
6999
    }
7000

7001
    status = sixel_loader_setopt(loader,
×
7002
                                 SIXEL_LOADER_OPTION_INSECURE,
7003
                                 &finsecure);
7004
    if (SIXEL_FAILED(status)) {
×
7005
        goto end;
×
7006
    }
7007

7008
    status = sixel_loader_setopt(loader,
×
7009
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
7010
                                 cancel_flag);
7011
    if (SIXEL_FAILED(status)) {
×
7012
        goto end;
×
7013
    }
7014

7015
    status = sixel_loader_setopt(loader,
×
7016
                                 SIXEL_LOADER_OPTION_CONTEXT,
7017
                                 context);
7018
    if (SIXEL_FAILED(status)) {
×
7019
        goto end;
×
7020
    }
7021

7022
    status = sixel_loader_load_file(loader, filename, fn_load);
×
7023

7024
end:
7025
    sixel_loader_unref(loader);
×
7026

7027
    return status;
×
7028
}
7029

7030

7031
SIXELAPI size_t
7032
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
6✔
7033
{
7034
    size_t entry_count;
7035
    size_t limit;
7036
    size_t index;
7037

7038
    entry_count = sizeof(sixel_loader_entries) /
6✔
7039
                  sizeof(sixel_loader_entries[0]);
7040

7041
    if (names != NULL && max_names > 0) {
6!
7042
        limit = entry_count;
3✔
7043
        if (limit > max_names) {
3!
7044
            limit = max_names;
×
7045
        }
7046
        for (index = 0; index < limit; ++index) {
11✔
7047
            names[index] = sixel_loader_entries[index].name;
8✔
7048
        }
4✔
7049
    }
1✔
7050

7051
    return entry_count;
6✔
7052
}
7053

7054
#if HAVE_TESTS
7055
static int
7056
test1(void)
×
7057
{
7058
    int nret = EXIT_FAILURE;
×
7059
    unsigned char *ptr = malloc(16);
×
7060

7061
    nret = EXIT_SUCCESS;
×
7062
    goto error;
×
7063

7064
    nret = EXIT_SUCCESS;
7065

7066
error:
7067
    free(ptr);
×
7068
    return nret;
×
7069
}
7070

7071

7072
SIXELAPI int
7073
sixel_loader_tests_main(void)
×
7074
{
7075
    int nret = EXIT_FAILURE;
×
7076
    size_t i;
7077
    typedef int (* testcase)(void);
7078

7079
    static testcase const testcases[] = {
7080
        test1,
7081
    };
7082

7083
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
7084
        nret = testcases[i]();
×
7085
        if (nret != EXIT_SUCCESS) {
×
7086
            goto error;
×
7087
        }
7088
    }
7089

7090
    nret = EXIT_SUCCESS;
×
7091

7092
error:
7093
    return nret;
×
7094
}
7095
#endif  /* HAVE_TESTS */
7096

7097
/* emacs Local Variables:      */
7098
/* emacs mode: c               */
7099
/* emacs tab-width: 4          */
7100
/* emacs indent-tabs-mode: nil */
7101
/* emacs c-basic-offset: 4     */
7102
/* emacs End:                  */
7103
/* vim: set expandtab ts=4 sts=4 sw=4 : */
7104
/* 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