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

saitoha / libsixel / 19341862756

13 Nov 2025 05:01PM UTC coverage: 44.404% (-0.7%) from 45.065%
19341862756

push

github

saitoha
build: guard metadata probe on platforms without fork

8298 of 28238 branches covered (29.39%)

12030 of 27092 relevant lines covered (44.4%)

1115823.19 hits per line

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

45.39
/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_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
130

131
#if HAVE_POSIX_SPAWNP
132
extern char **environ;
133
#endif
134

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

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

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

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

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

183
    return copy;
×
184
}
185

186
/*
187
 * sixel_helper_set_loader_trace
188
 *
189
 * Toggle verbose loader tracing so debugging output can be collected.
190
 *
191
 * Arguments:
192
 *     enable - non-zero enables tracing, zero disables it.
193
 */
194
void
195
sixel_helper_set_loader_trace(int enable)
466✔
196
{
197
    loader_trace_enabled = enable ? 1 : 0;
466✔
198
}
466✔
199

200
/*
201
 * sixel_helper_set_thumbnail_size_hint
202
 *
203
 * Record the caller's preferred maximum thumbnail dimension.
204
 *
205
 * Arguments:
206
 *     size - requested dimension in pixels; non-positive resets to default.
207
 */
208
void
209
sixel_helper_set_thumbnail_size_hint(int size)
457✔
210
{
211
    if (size > 0) {
457✔
212
        thumbnailer_size_hint = size;
93✔
213
    } else {
31✔
214
        thumbnailer_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
364✔
215
    }
216
}
457✔
217

218
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
219
/*
220
 * loader_trace_message
221
 *
222
 * Emit a formatted trace message when verbose loader tracing is enabled.
223
 *
224
 * Arguments:
225
 *     format - printf-style message template.
226
 *     ...    - arguments consumed according to the format string.
227
 */
228
static void
229
loader_trace_message(char const *format, ...)
45✔
230
{
231
    va_list args;
232

233
    if (!loader_trace_enabled) {
45!
234
        return;
45✔
235
    }
236

237
    fprintf(stderr, "libsixel: ");
×
238

239
    va_start(args, format);
×
240
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
241
# if defined(__clang__)
242
#  pragma clang diagnostic push
243
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
244
# elif defined(__GNUC__)
245
#  pragma GCC diagnostic push
246
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
247
# endif
248
#endif
249
    vfprintf(stderr, format, args);
×
250
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
251
# if defined(__clang__)
252
#  pragma clang diagnostic pop
253
# elif defined(__GNUC__)
254
#  pragma GCC diagnostic pop
255
# endif
256
#endif
257
    va_end(args);
×
258

259
    fprintf(stderr, "\n");
×
260
}
15✔
261
#endif  /* HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK */
262

263
static void
264
loader_trace_try(char const *name)
510✔
265
{
266
    if (loader_trace_enabled) {
510✔
267
        fprintf(stderr, "libsixel: trying %s loader\n", name);
10✔
268
    }
4✔
269
}
510✔
270

271
static void
272
loader_trace_result(char const *name, SIXELSTATUS status)
510✔
273
{
274
    if (!loader_trace_enabled) {
510✔
275
        return;
500✔
276
    }
277
    if (SIXEL_SUCCEEDED(status)) {
10!
278
        fprintf(stderr, "libsixel: loader %s succeeded\n", name);
9✔
279
    } else {
3✔
280
        fprintf(stderr, "libsixel: loader %s failed (%s)\n",
2✔
281
                name, sixel_helper_format_error(status));
1✔
282
    }
283
}
210✔
284

285
typedef SIXELSTATUS (*sixel_loader_backend)(
286
    sixel_chunk_t const       *pchunk,
287
    int                        fstatic,
288
    int                        fuse_palette,
289
    int                        reqcolors,
290
    unsigned char             *bgcolor,
291
    int                        loop_control,
292
    sixel_load_image_function  fn_load,
293
    void                      *context);
294

295
typedef int (*sixel_loader_predicate)(sixel_chunk_t const *pchunk);
296

297
typedef struct sixel_loader_entry {
298
    char const              *name;
299
    sixel_loader_backend     backend;
300
    sixel_loader_predicate   predicate;
301
    int                      default_enabled;
302
} sixel_loader_entry_t;
303

304
static int
305
loader_plan_contains(sixel_loader_entry_t const **plan,
602✔
306
                     size_t plan_length,
307
                     sixel_loader_entry_t const *entry)
308
{
309
    size_t index;
310

311
    for (index = 0; index < plan_length; ++index) {
753!
312
        if (plan[index] == entry) {
151!
313
            return 1;
×
314
        }
315
    }
151✔
316

317
    return 0;
602✔
318
}
302✔
319

320
static int
321
loader_token_matches(char const *token,
×
322
                     size_t token_length,
323
                     char const *name)
324
{
325
    size_t index;
326
    unsigned char left;
327
    unsigned char right;
328

329
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
×
330
        left = (unsigned char)token[index];
×
331
        right = (unsigned char)name[index];
×
332
        if (tolower(left) != tolower(right)) {
×
333
            return 0;
×
334
        }
335
    }
336

337
    if (index != token_length || name[index] != '\0') {
×
338
        return 0;
×
339
    }
340

341
    return 1;
×
342
}
343

344
static sixel_loader_entry_t const *
345
loader_lookup_token(char const *token,
×
346
                    size_t token_length,
347
                    sixel_loader_entry_t const *entries,
348
                    size_t entry_count)
349
{
350
    size_t index;
351

352
    for (index = 0; index < entry_count; ++index) {
×
353
        if (loader_token_matches(token,
×
354
                                 token_length,
355
                                 entries[index].name)) {
×
356
            return &entries[index];
×
357
        }
358
    }
359

360
    return NULL;
×
361
}
362

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

398
    plan_length = 0;
451✔
399
    index = 0;
451✔
400
    cursor = order;
451✔
401
    token_start = order;
451✔
402
    token_end = order;
451✔
403
    token_length = 0;
451✔
404
    entry = NULL;
451✔
405
    limit = plan_capacity;
451✔
406

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

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

465
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
1,655!
466
        entry = &entries[index];
1,204✔
467
        if (!entry->default_enabled) {
1,204✔
468
            continue;
602✔
469
        }
470
        if (!loader_plan_contains(plan, plan_length, entry)) {
602!
471
            plan[plan_length] = entry;
602✔
472
            ++plan_length;
602✔
473
        }
302✔
474
    }
302✔
475

476
    return plan_length;
451✔
477
}
478

479
static void
480
loader_append_chunk(char *dest,
62✔
481
                    size_t capacity,
482
                    size_t *offset,
483
                    char const *chunk)
484
{
485
    size_t available;
486
    size_t length;
487

488
    if (dest == NULL || offset == NULL || chunk == NULL) {
62!
489
        return;
×
490
    }
491

492
    if (*offset >= capacity) {
62!
493
        return;
×
494
    }
495

496
    available = capacity - *offset;
62✔
497
    if (available == 0) {
62!
498
        return;
×
499
    }
500

501
    length = strlen(chunk);
62✔
502
    if (length >= available) {
62!
503
        if (available == 0) {
×
504
            return;
×
505
        }
506
        length = available - 1u;
×
507
    }
508

509
    if (length > 0) {
62!
510
        memcpy(dest + *offset, chunk, length);
62✔
511
        *offset += length;
62✔
512
    }
22✔
513

514
    if (*offset < capacity) {
62!
515
        dest[*offset] = '\0';
62✔
516
    } else {
22✔
517
        dest[capacity - 1u] = '\0';
×
518
    }
519
}
22✔
520

521
static void
522
loader_append_key_value(char *dest,
26✔
523
                        size_t capacity,
524
                        size_t *offset,
525
                        char const *label,
526
                        char const *value)
527
{
528
    char line[128];
529
    int written;
530

531
    if (value == NULL || value[0] == '\0') {
26!
532
        return;
×
533
    }
534

535
    written = sixel_compat_snprintf(line,
36✔
536
                                    sizeof(line),
537
                                    "  %-10s: %s\n",
538
                                    label,
10✔
539
                                    value);
10✔
540
    if (written < 0) {
26!
541
        return;
×
542
    }
543

544
    if ((size_t)written >= sizeof(line)) {
26!
545
        line[sizeof(line) - 1u] = '\0';
×
546
    }
547

548
    loader_append_chunk(dest, capacity, offset, line);
26✔
549
}
10✔
550

551
static void
552
loader_extract_extension(char const *path, char *buffer, size_t capacity)
9✔
553
{
554
    char const *dot;
555
    size_t index;
556

557
    if (buffer == NULL || capacity == 0) {
9!
558
        return;
×
559
    }
560

561
    buffer[0] = '\0';
9✔
562

563
    if (path == NULL) {
9✔
564
        return;
3✔
565
    }
566

567
    dot = strrchr(path, '.');
6✔
568
    if (dot == NULL || dot[1] == '\0') {
6!
569
        return;
×
570
    }
571

572
#if defined(_WIN32)
573
    {
574
        char const *slash;
575
        char const *backslash;
576

577
        slash = strrchr(path, '/');
578
        backslash = strrchr(path, '\\');
579
        if ((slash != NULL && dot < slash) ||
580
                (backslash != NULL && dot < backslash)) {
581
            return;
582
        }
583
    }
584
#else
585
    {
586
        char const *slash;
587

588
        slash = strrchr(path, '/');
6✔
589
        if (slash != NULL && dot < slash) {
6!
590
            return;
6✔
591
        }
592
    }
593
#endif
594

595
    if (dot[1] == '\0') {
×
596
        return;
×
597
    }
598

599
    dot += 1;
×
600

601
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
×
602
        buffer[index] = (char)tolower((unsigned char)dot[index]);
×
603
    }
604
    buffer[index] = '\0';
×
605
}
3✔
606

607
sixel_allocator_t *stbi_allocator;
608

609
void *
610
stbi_malloc(size_t n)
817✔
611
{
612
    return sixel_allocator_malloc(stbi_allocator, n);
817✔
613
}
614

615
void *
616
stbi_realloc(void *p, size_t n)
221✔
617
{
618
    return sixel_allocator_realloc(stbi_allocator, p, n);
221✔
619
}
620

621
void
622
stbi_free(void *p)
1,082✔
623
{
624
    sixel_allocator_free(stbi_allocator, p);
1,082✔
625
}
1,082✔
626

627
#define STBI_MALLOC stbi_malloc
628
#define STBI_REALLOC stbi_realloc
629
#define STBI_FREE stbi_free
630

631
#define STBI_NO_STDIO 1
632
#define STB_IMAGE_IMPLEMENTATION 1
633
#define STBI_FAILURE_USERMSG 1
634
#if defined(_WIN32)
635
# define STBI_NO_THREAD_LOCALS 1  /* no tls */
636
#endif
637
#define STBI_NO_GIF
638
#define STBI_NO_PNM
639

640
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
641
# pragma GCC diagnostic push
642
# pragma GCC diagnostic ignored "-Wsign-conversion"
643
#endif
644
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
645
# pragma GCC diagnostic push
646
# pragma GCC diagnostic ignored "-Wstrict-overflow"
647
#endif
648
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
649
# pragma GCC diagnostic push
650
# pragma GCC diagnostic ignored "-Wswitch-default"
651
#endif
652
#if HAVE_DIAGNOSTIC_SHADOW
653
# pragma GCC diagnostic push
654
# pragma GCC diagnostic ignored "-Wshadow"
655
#endif
656
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
657
# pragma GCC diagnostic push
658
# pragma GCC diagnostic ignored "-Wdouble-promotion"
659
#endif
660
# if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
661
# pragma GCC diagnostic push
662
# pragma GCC diagnostic ignored "-Wunused-function"
663
#endif
664
# if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
665
# pragma GCC diagnostic push
666
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
667
#endif
668
#include "stb_image.h"
669
#if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
670
# pragma GCC diagnostic pop
671
#endif
672
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
673
# pragma GCC diagnostic pop
674
#endif
675
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
676
# pragma GCC diagnostic pop
677
#endif
678
#if HAVE_DIAGNOSTIC_SHADOW
679
# pragma GCC diagnostic pop
680
#endif
681
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
682
# pragma GCC diagnostic pop
683
#endif
684
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
685
# pragma GCC diagnostic pop
686
#endif
687
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
688
# pragma GCC diagnostic pop
689
#endif
690

691

692
# if HAVE_JPEG
693
/* import from @uobikiemukot's sdump loader.h */
694
static SIXELSTATUS
695
load_jpeg(unsigned char **result,
696
          unsigned char *data,
697
          size_t datasize,
698
          int *pwidth,
699
          int *pheight,
700
          int *ppixelformat,
701
          sixel_allocator_t *allocator)
702
{
703
    SIXELSTATUS status = SIXEL_JPEG_ERROR;
704
    JDIMENSION row_stride;
705
    size_t size;
706
    JSAMPARRAY buffer;
707
    struct jpeg_decompress_struct cinfo;
708
    struct jpeg_error_mgr pub;
709

710
    cinfo.err = jpeg_std_error(&pub);
711

712
    jpeg_create_decompress(&cinfo);
713
    jpeg_mem_src(&cinfo, data, datasize);
714
    jpeg_read_header(&cinfo, TRUE);
715

716
    /* disable colormap (indexed color), grayscale -> rgb */
717
    cinfo.quantize_colors = FALSE;
718
    cinfo.out_color_space = JCS_RGB;
719
    jpeg_start_decompress(&cinfo);
720

721
    if (cinfo.output_components != 3) {
722
        sixel_helper_set_additional_message(
723
            "load_jpeg: unknown pixel format.");
724
        status = SIXEL_BAD_INPUT;
725
        goto end;
726
    }
727

728
    *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
729

730
    if (cinfo.output_width > INT_MAX || cinfo.output_height > INT_MAX) {
731
        status = SIXEL_BAD_INTEGER_OVERFLOW;
732
        goto end;
733
    }
734
    *pwidth = (int)cinfo.output_width;
735
    *pheight = (int)cinfo.output_height;
736

737
    size = (size_t)(*pwidth * *pheight * cinfo.output_components);
738
    *result = (unsigned char *)sixel_allocator_malloc(allocator, size);
739
    if (*result == NULL) {
740
        sixel_helper_set_additional_message(
741
            "load_jpeg: sixel_allocator_malloc() failed.");
742
        status = SIXEL_BAD_ALLOCATION;
743
        goto end;
744
    }
745
    row_stride = cinfo.output_width * (unsigned int)cinfo.output_components;
746
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
747

748
    while (cinfo.output_scanline < cinfo.output_height) {
749
        jpeg_read_scanlines(&cinfo, buffer, 1);
750
        if (cinfo.err->num_warnings > 0) {
751
            sixel_helper_set_additional_message(
752
                "jpeg_read_scanlines: error/warining occuered.");
753
            status = SIXEL_BAD_INPUT;
754
            goto end;
755
        }
756
        memcpy(*result + (cinfo.output_scanline - 1) * row_stride, buffer[0], row_stride);
757
    }
758

759
    status = SIXEL_OK;
760

761
end:
762
    jpeg_finish_decompress(&cinfo);
763
    jpeg_destroy_decompress(&cinfo);
764

765
    return status;
766
}
767
# endif  /* HAVE_JPEG */
768

769

770
# if HAVE_LIBPNG
771
static void
772
read_png(png_structp png_ptr,
773
         png_bytep data,
774
         png_size_t length)
775
{
776
    sixel_chunk_t *pchunk = (sixel_chunk_t *)png_get_io_ptr(png_ptr);
777
    if (length > pchunk->size) {
778
        length = pchunk->size;
779
    }
780
    if (length > 0) {
781
        memcpy(data, pchunk->buffer, length);
782
        pchunk->buffer += length;
783
        pchunk->size -= length;
784
    }
785
}
786

787

788
static void
789
read_palette(png_structp png_ptr,
790
             png_infop info_ptr,
791
             unsigned char *palette,
792
             int ncolors,
793
             png_color *png_palette,
794
             png_color_16 *pbackground,
795
             int *transparent)
796
{
797
    png_bytep trans = NULL;
798
    int num_trans = 0;
799
    int i;
800

801
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
802
        png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
803
    }
804
    if (num_trans > 0) {
805
        *transparent = trans[0];
806
    }
807
    for (i = 0; i < ncolors; ++i) {
808
        if (pbackground && i < num_trans) {
809
            palette[i * 3 + 0] = ((0xff - trans[i]) * pbackground->red
810
                                   + trans[i] * png_palette[i].red) >> 8;
811
            palette[i * 3 + 1] = ((0xff - trans[i]) * pbackground->green
812
                                   + trans[i] * png_palette[i].green) >> 8;
813
            palette[i * 3 + 2] = ((0xff - trans[i]) * pbackground->blue
814
                                   + trans[i] * png_palette[i].blue) >> 8;
815
        } else {
816
            palette[i * 3 + 0] = png_palette[i].red;
817
            palette[i * 3 + 1] = png_palette[i].green;
818
            palette[i * 3 + 2] = png_palette[i].blue;
819
        }
820
    }
821
}
822

823
jmp_buf jmpbuf;
824

825
/* libpng error handler */
826
static void
827
png_error_callback(png_structp png_ptr, png_const_charp error_message)
828
{
829
    (void) png_ptr;
830

831
    sixel_helper_set_additional_message(error_message);
832
#if HAVE_SETJMP && HAVE_LONGJMP
833
    longjmp(jmpbuf, 1);
834
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
835
}
836

837

838
static SIXELSTATUS
839
load_png(unsigned char      /* out */ **result,
840
         unsigned char      /* in */  *buffer,
841
         size_t             /* in */  size,
842
         int                /* out */ *psx,
843
         int                /* out */ *psy,
844
         unsigned char      /* out */ **ppalette,
845
         int                /* out */ *pncolors,
846
         int                /* in */  reqcolors,
847
         int                /* out */ *pixelformat,
848
         unsigned char      /* out */ *bgcolor,
849
         int                /* out */ *transparent,
850
         sixel_allocator_t  /* in */  *allocator)
851
{
852
    SIXELSTATUS status;
853
    sixel_chunk_t read_chunk;
854
    png_uint_32 bitdepth;
855
    png_uint_32 png_status;
856
    png_structp png_ptr;
857
    png_infop info_ptr;
858
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
859
# pragma GCC diagnostic push
860
# pragma GCC diagnostic ignored "-Wclobbered"
861
#endif
862
    unsigned char **rows = NULL;
863
    png_color *png_palette = NULL;
864
    png_color_16 background;
865
    png_color_16p default_background;
866
    png_uint_32 width;
867
    png_uint_32 height;
868
    int i;
869
    int depth;
870

871
#if HAVE_SETJMP && HAVE_LONGJMP
872
    if (setjmp(jmpbuf) != 0) {
873
        sixel_allocator_free(allocator, *result);
874
        *result = NULL;
875
        status = SIXEL_PNG_ERROR;
876
        goto cleanup;
877
    }
878
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
879

880
    status = SIXEL_FALSE;
881
    *result = NULL;
882

883
    png_ptr = png_create_read_struct(
884
        PNG_LIBPNG_VER_STRING, NULL, &png_error_callback, NULL);
885
    if (!png_ptr) {
886
        sixel_helper_set_additional_message(
887
            "png_create_read_struct() failed.");
888
        status = SIXEL_PNG_ERROR;
889
        goto cleanup;
890
    }
891

892
    /*
893
     * The minimum valid PNG is 67 bytes.
894
     * https://garethrees.org/2007/11/14/pngcrush/
895
     */
896
    if (size < 67) {
897
        sixel_helper_set_additional_message("PNG data too small to be valid!");
898
        status = SIXEL_PNG_ERROR;
899
        goto cleanup;
900
    }
901

902
#if HAVE_SETJMP
903
    if (setjmp(png_jmpbuf(png_ptr)) != 0) {
904
        sixel_allocator_free(allocator, *result);
905
        *result = NULL;
906
        status = SIXEL_PNG_ERROR;
907
        goto cleanup;
908
    }
909
#endif  /* HAVE_SETJMP */
910

911
    info_ptr = png_create_info_struct(png_ptr);
912
    if (!info_ptr) {
913
        sixel_helper_set_additional_message(
914
            "png_create_info_struct() failed.");
915
        status = SIXEL_PNG_ERROR;
916
        png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
917
        goto cleanup;
918
    }
919
    read_chunk.buffer = buffer;
920
    read_chunk.size = size;
921

922
    png_set_read_fn(png_ptr,(png_voidp)&read_chunk, read_png);
923
    png_read_info(png_ptr, info_ptr);
924

925
    width = png_get_image_width(png_ptr, info_ptr);
926
    height = png_get_image_height(png_ptr, info_ptr);
927

928
    if (width > INT_MAX || height > INT_MAX) {
929
        status = SIXEL_BAD_INTEGER_OVERFLOW;
930
        goto cleanup;
931
    }
932

933
    *psx = (int)width;
934
    *psy = (int)height;
935

936
    bitdepth = png_get_bit_depth(png_ptr, info_ptr);
937
    if (bitdepth == 16) {
938
#  if HAVE_DEBUG
939
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
940
        fprintf(stderr, "stripping to 8bit...\n");
941
#  endif
942
        png_set_strip_16(png_ptr);
943
        bitdepth = 8;
944
    }
945

946
    if (bgcolor) {
947
#  if HAVE_DEBUG
948
        fprintf(stderr, "background color is specified [%02x, %02x, %02x]\n",
949
                bgcolor[0], bgcolor[1], bgcolor[2]);
950
#  endif
951
        background.red = bgcolor[0];
952
        background.green = bgcolor[1];
953
        background.blue = bgcolor[2];
954
        background.gray = (bgcolor[0] + bgcolor[1] + bgcolor[2]) / 3;
955
    } else if (png_get_bKGD(png_ptr, info_ptr, &default_background) == PNG_INFO_bKGD) {
956
        memcpy(&background, default_background, sizeof(background));
957
#  if HAVE_DEBUG
958
        fprintf(stderr, "background color is found [%02x, %02x, %02x]\n",
959
                background.red, background.green, background.blue);
960
#  endif
961
    } else {
962
        background.red = 0;
963
        background.green = 0;
964
        background.blue = 0;
965
        background.gray = 0;
966
    }
967

968
    switch (png_get_color_type(png_ptr, info_ptr)) {
969
    case PNG_COLOR_TYPE_PALETTE:
970
#  if HAVE_DEBUG
971
        fprintf(stderr, "paletted PNG(PNG_COLOR_TYPE_PALETTE)\n");
972
#  endif
973
        png_status = png_get_PLTE(png_ptr, info_ptr,
974
                                  &png_palette, pncolors);
975
        if (png_status != PNG_INFO_PLTE || png_palette == NULL) {
976
            sixel_helper_set_additional_message(
977
                "PLTE chunk not found");
978
            status = SIXEL_PNG_ERROR;
979
            goto cleanup;
980
        }
981
#  if HAVE_DEBUG
982
        fprintf(stderr, "palette colors: %d\n", *pncolors);
983
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
984
#  endif
985
        if (ppalette == NULL || *pncolors > reqcolors) {
986
#  if HAVE_DEBUG
987
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
988
                    reqcolors);
989
            fprintf(stderr, "expand to RGB format...\n");
990
#  endif
991
            png_set_background(png_ptr, &background,
992
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
993
            png_set_palette_to_rgb(png_ptr);
994
            png_set_strip_alpha(png_ptr);
995
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
996
        } else {
997
            switch (bitdepth) {
998
            case 1:
999
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
1000
                if (*ppalette == NULL) {
1001
                    sixel_helper_set_additional_message(
1002
                        "load_png: sixel_allocator_malloc() failed.");
1003
                    status = SIXEL_BAD_ALLOCATION;
1004
                    goto cleanup;
1005
                }
1006
                read_palette(png_ptr, info_ptr, *ppalette,
1007
                             *pncolors, png_palette, &background, transparent);
1008
                *pixelformat = SIXEL_PIXELFORMAT_PAL1;
1009
                break;
1010
            case 2:
1011
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
1012
                if (*ppalette == NULL) {
1013
                    sixel_helper_set_additional_message(
1014
                        "load_png: sixel_allocator_malloc() failed.");
1015
                    status = SIXEL_BAD_ALLOCATION;
1016
                    goto cleanup;
1017
                }
1018
                read_palette(png_ptr, info_ptr, *ppalette,
1019
                             *pncolors, png_palette, &background, transparent);
1020
                *pixelformat = SIXEL_PIXELFORMAT_PAL2;
1021
                break;
1022
            case 4:
1023
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
1024
                if (*ppalette == NULL) {
1025
                    sixel_helper_set_additional_message(
1026
                        "load_png: sixel_allocator_malloc() failed.");
1027
                    status = SIXEL_BAD_ALLOCATION;
1028
                    goto cleanup;
1029
                }
1030
                read_palette(png_ptr, info_ptr, *ppalette,
1031
                             *pncolors, png_palette, &background, transparent);
1032
                *pixelformat = SIXEL_PIXELFORMAT_PAL4;
1033
                break;
1034
            case 8:
1035
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
1036
                if (*ppalette == NULL) {
1037
                    sixel_helper_set_additional_message(
1038
                        "load_png: sixel_allocator_malloc() failed.");
1039
                    status = SIXEL_BAD_ALLOCATION;
1040
                    goto cleanup;
1041
                }
1042
                read_palette(png_ptr, info_ptr, *ppalette,
1043
                             *pncolors, png_palette, &background, transparent);
1044
                *pixelformat = SIXEL_PIXELFORMAT_PAL8;
1045
                break;
1046
            default:
1047
                png_set_background(png_ptr, &background,
1048
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1049
                png_set_palette_to_rgb(png_ptr);
1050
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1051
                break;
1052
            }
1053
        }
1054
        break;
1055
    case PNG_COLOR_TYPE_GRAY:
1056
#  if HAVE_DEBUG
1057
        fprintf(stderr, "grayscale PNG(PNG_COLOR_TYPE_GRAY)\n");
1058
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1059
#  endif
1060
        if (1 << bitdepth > reqcolors) {
1061
#  if HAVE_DEBUG
1062
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
1063
                    reqcolors);
1064
            fprintf(stderr, "expand into RGB format...\n");
1065
#  endif
1066
            png_set_background(png_ptr, &background,
1067
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1068
            png_set_gray_to_rgb(png_ptr);
1069
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1070
        } else {
1071
            switch (bitdepth) {
1072
            case 1:
1073
            case 2:
1074
            case 4:
1075
                if (ppalette) {
1076
#  if HAVE_DECL_PNG_SET_EXPAND_GRAY_1_2_4_TO_8
1077
#   if HAVE_DEBUG
1078
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
1079
                            (unsigned int)bitdepth);
1080
#   endif
1081
                    png_set_expand_gray_1_2_4_to_8(png_ptr);
1082
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
1083
#  elif HAVE_DECL_PNG_SET_GRAY_1_2_4_TO_8
1084
#   if HAVE_DEBUG
1085
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
1086
                            (unsigned int)bitdepth);
1087
#   endif
1088
                    png_set_gray_1_2_4_to_8(png_ptr);
1089
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
1090
#  else
1091
#   if HAVE_DEBUG
1092
                    fprintf(stderr, "expand into RGB format...\n");
1093
#   endif
1094
                    png_set_background(png_ptr, &background,
1095
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1096
                    png_set_gray_to_rgb(png_ptr);
1097
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1098
#  endif
1099
                } else {
1100
                    png_set_background(png_ptr, &background,
1101
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1102
                    png_set_gray_to_rgb(png_ptr);
1103
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1104
                }
1105
                break;
1106
            case 8:
1107
                if (ppalette) {
1108
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
1109
                } else {
1110
#  if HAVE_DEBUG
1111
                    fprintf(stderr, "expand into RGB format...\n");
1112
#  endif
1113
                    png_set_background(png_ptr, &background,
1114
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1115
                    png_set_gray_to_rgb(png_ptr);
1116
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1117
                }
1118
                break;
1119
            default:
1120
#  if HAVE_DEBUG
1121
                fprintf(stderr, "expand into RGB format...\n");
1122
#  endif
1123
                png_set_background(png_ptr, &background,
1124
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1125
                png_set_gray_to_rgb(png_ptr);
1126
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1127
                break;
1128
            }
1129
        }
1130
        break;
1131
    case PNG_COLOR_TYPE_GRAY_ALPHA:
1132
#  if HAVE_DEBUG
1133
        fprintf(stderr, "grayscale-alpha PNG(PNG_COLOR_TYPE_GRAY_ALPHA)\n");
1134
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1135
        fprintf(stderr, "expand to RGB format...\n");
1136
#  endif
1137
        png_set_background(png_ptr, &background,
1138
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1139
        png_set_gray_to_rgb(png_ptr);
1140
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1141
        break;
1142
    case PNG_COLOR_TYPE_RGB_ALPHA:
1143
#  if HAVE_DEBUG
1144
        fprintf(stderr, "RGBA PNG(PNG_COLOR_TYPE_RGB_ALPHA)\n");
1145
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1146
        fprintf(stderr, "expand to RGB format...\n");
1147
#  endif
1148
        png_set_background(png_ptr, &background,
1149
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1150
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1151
        break;
1152
    case PNG_COLOR_TYPE_RGB:
1153
#  if HAVE_DEBUG
1154
        fprintf(stderr, "RGB PNG(PNG_COLOR_TYPE_RGB)\n");
1155
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1156
#  endif
1157
        png_set_background(png_ptr, &background,
1158
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1159
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1160
        break;
1161
    default:
1162
        /* unknown format */
1163
        goto cleanup;
1164
    }
1165
    depth = sixel_helper_compute_depth(*pixelformat);
1166
    *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * depth));
1167
    if (*result == NULL) {
1168
        sixel_helper_set_additional_message(
1169
            "load_png: sixel_allocator_malloc() failed.");
1170
        status = SIXEL_BAD_ALLOCATION;
1171
        goto cleanup;
1172
    }
1173
    rows = (unsigned char **)sixel_allocator_malloc(allocator, (size_t)*psy * sizeof(unsigned char *));
1174
    if (rows == NULL) {
1175
        sixel_helper_set_additional_message(
1176
            "load_png: sixel_allocator_malloc() failed.");
1177
        status = SIXEL_BAD_ALLOCATION;
1178
        goto cleanup;
1179
    }
1180
    switch (*pixelformat) {
1181
    case SIXEL_PIXELFORMAT_PAL1:
1182
    case SIXEL_PIXELFORMAT_PAL2:
1183
    case SIXEL_PIXELFORMAT_PAL4:
1184
        for (i = 0; i < *psy; ++i) {
1185
            rows[i] = *result + (depth * *psx * (int)bitdepth + 7) / 8 * i;
1186
        }
1187
        break;
1188
    default:
1189
        for (i = 0; i < *psy; ++i) {
1190
            rows[i] = *result + depth * *psx * i;
1191
        }
1192
        break;
1193
    }
1194

1195
    png_read_image(png_ptr, rows);
1196

1197
    status = SIXEL_OK;
1198

1199
cleanup:
1200
    png_destroy_read_struct(&png_ptr, &info_ptr,(png_infopp)0);
1201

1202
    if (rows != NULL) {
1203
        sixel_allocator_free(allocator, rows);
1204
    }
1205

1206
    return status;
1207
}
1208
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
1209
# pragma GCC diagnostic pop
1210
#endif
1211

1212
# endif  /* HAVE_LIBPNG */
1213

1214

1215
static SIXELSTATUS
1216
load_sixel(unsigned char        /* out */ **result,
156✔
1217
           unsigned char        /* in */  *buffer,
1218
           int                  /* in */  size,
1219
           int                  /* out */ *psx,
1220
           int                  /* out */ *psy,
1221
           unsigned char        /* out */ **ppalette,
1222
           int                  /* out */ *pncolors,
1223
           int                  /* in */  reqcolors,
1224
           int                  /* out */ *ppixelformat,
1225
           sixel_allocator_t    /* in */  *allocator)
1226
{
1227
    SIXELSTATUS status = SIXEL_FALSE;
156✔
1228
    unsigned char *p = NULL;
156✔
1229
    unsigned char *palette = NULL;
156✔
1230
    int colors;
1231
    int i;
1232

1233
    /* sixel */
1234
    status = sixel_decode_raw(buffer, size,
208✔
1235
                              &p, psx, psy,
52✔
1236
                              &palette, &colors, allocator);
52✔
1237
    if (SIXEL_FAILED(status)) {
156!
1238
        goto end;
×
1239
    }
1240
    if (ppalette == NULL || colors > reqcolors) {
208!
1241
        *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
36✔
1242
        *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * 3));
36✔
1243
        if (*result == NULL) {
36!
1244
            sixel_helper_set_additional_message(
×
1245
                "load_sixel: sixel_allocator_malloc() failed.");
1246
            status = SIXEL_BAD_ALLOCATION;
×
1247
            goto end;
×
1248
        }
1249
        for (i = 0; i < *psx * *psy; ++i) {
5,670,576✔
1250
            (*result)[i * 3 + 0] = palette[p[i] * 3 + 0];
5,670,540✔
1251
            (*result)[i * 3 + 1] = palette[p[i] * 3 + 1];
5,670,540✔
1252
            (*result)[i * 3 + 2] = palette[p[i] * 3 + 2];
5,670,540✔
1253
        }
1,890,180✔
1254
    } else {
12✔
1255
        *ppixelformat = SIXEL_PIXELFORMAT_PAL8;
120✔
1256
        *result = p;
120✔
1257
        *ppalette = palette;
120✔
1258
        *pncolors = colors;
120✔
1259
        p = NULL;
120✔
1260
        palette = NULL;
120✔
1261
    }
1262

1263
end:
104✔
1264
    /*
1265
     * Release the decoded index buffer when the caller requested an RGB
1266
     * conversion.  Palette-backed callers steal ownership by nulling `p`.
1267
     */
1268
    sixel_allocator_free(allocator, p);
156✔
1269
    sixel_allocator_free(allocator, palette);
156✔
1270

1271
    return status;
156✔
1272
}
1273

1274

1275
/* detect whether given chunk is sixel stream */
1276
static int
1277
chunk_is_sixel(sixel_chunk_t const *chunk)
359✔
1278
{
1279
    unsigned char *p;
1280
    unsigned char *end;
1281

1282
    p = chunk->buffer;
359✔
1283
    end = p + chunk->size;
359✔
1284

1285
    if (chunk->size < 3) {
359!
1286
        return 0;
2✔
1287
    }
1288

1289
    p++;
357✔
1290
    if (p >= end) {
357!
1291
        return 0;
×
1292
    }
1293
    if (*(p - 1) == 0x90 || (*(p - 1) == 0x1b && *p == 0x50)) {
357!
1294
        while (p++ < end) {
570!
1295
            if (*p == 0x71) {
570✔
1296
                return 1;
156✔
1297
            } else if (*p == 0x18 || *p == 0x1a) {
414!
1298
                return 0;
×
1299
            } else if (*p < 0x20) {
414✔
1300
                continue;
6✔
1301
            } else if (*p < 0x30) {
408!
1302
                return 0;
×
1303
            } else if (*p < 0x40) {
408✔
1304
                continue;
405✔
1305
            }
1306
        }
1307
    }
1308
    return 0;
201✔
1309
}
59✔
1310

1311

1312
/* detect whether given chunk is PNM stream */
1313
static int
1314
chunk_is_pnm(sixel_chunk_t const *chunk)
203✔
1315
{
1316
    if (chunk->size < 2) {
203!
1317
        return 0;
2✔
1318
    }
1319
    if (chunk->buffer[0] == 'P' &&
202✔
1320
        chunk->buffer[1] >= '1' &&
21!
1321
        chunk->buffer[1] <= '6') {
21!
1322
        return 1;
21✔
1323
    }
1324
    return 0;
180✔
1325
}
7✔
1326

1327

1328
#if HAVE_LIBPNG
1329
/* detect whether given chunk is PNG stream */
1330
static int
1331
chunk_is_png(sixel_chunk_t const *chunk)
1332
{
1333
    if (chunk->size < 8) {
1334
        return 0;
1335
    }
1336
    if (png_check_sig(chunk->buffer, 8)) {
1337
        return 1;
1338
    }
1339
    return 0;
1340
}
1341
#endif  /* HAVE_LIBPNG */
1342

1343

1344
/* detect whether given chunk is GIF stream */
1345
static int
1346
chunk_is_gif(sixel_chunk_t const *chunk)
182✔
1347
{
1348
    if (chunk->size < 6) {
182✔
1349
        return 0;
3✔
1350
    }
1351
    if (chunk->buffer[0] == 'G' &&
181✔
1352
        chunk->buffer[1] == 'I' &&
16!
1353
        chunk->buffer[2] == 'F' &&
16!
1354
        chunk->buffer[3] == '8' &&
16!
1355
        (chunk->buffer[4] == '7' || chunk->buffer[4] == '9') &&
16!
1356
        chunk->buffer[5] == 'a') {
16!
1357
        return 1;
16✔
1358
    }
1359
    return 0;
163✔
1360
}
6✔
1361

1362

1363
#if HAVE_JPEG
1364
/* detect whether given chunk is JPEG stream */
1365
static int
1366
chunk_is_jpeg(sixel_chunk_t const *chunk)
1367
{
1368
    if (chunk->size < 2) {
1369
        return 0;
1370
    }
1371
    if (memcmp("\xFF\xD8", chunk->buffer, 2) == 0) {
1372
        return 1;
1373
    }
1374
    return 0;
1375
}
1376
#endif  /* HAVE_JPEG */
1377

1378
typedef union _fn_pointer {
1379
    sixel_load_image_function fn;
1380
    void *                    p;
1381
} fn_pointer;
1382

1383
/* load images using builtin image loaders */
1384
static SIXELSTATUS
1385
load_with_builtin(
359✔
1386
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
1387
    int                       /* in */     fstatic,      /* static */
1388
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
1389
    int                       /* in */     reqcolors,    /* reqcolors */
1390
    unsigned char             /* in */     *bgcolor,     /* background color */
1391
    int                       /* in */     loop_control, /* one of enum loop_control */
1392
    sixel_load_image_function /* in */     fn_load,      /* callback */
1393
    void                      /* in/out */ *context      /* private data for callback */
1394
)
1395
{
1396
    SIXELSTATUS status = SIXEL_FALSE;
359✔
1397
    sixel_frame_t *frame = NULL;
359✔
1398
    fn_pointer fnp;
1399
    stbi__context stb_context;
1400
    int depth;
1401
    char message[256];
1402
    int nwrite;
1403

1404
    if (chunk_is_sixel(pchunk)) {
359✔
1405
        status = sixel_frame_new(&frame, pchunk->allocator);
156✔
1406
        if (SIXEL_FAILED(status)) {
156!
1407
            goto end;
×
1408
        }
1409
        if (pchunk->size > INT_MAX) {
156!
1410
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1411
            goto end;
×
1412
        }
1413
        status = load_sixel(&frame->pixels,
128✔
1414
                            pchunk->buffer,
156✔
1415
                            (int)pchunk->size,
156✔
1416
                            &frame->width,
156✔
1417
                            &frame->height,
156✔
1418
                            fuse_palette ? &frame->palette: NULL,
132✔
1419
                            &frame->ncolors,
156✔
1420
                            reqcolors,
52✔
1421
                            &frame->pixelformat,
156✔
1422
                            pchunk->allocator);
156✔
1423
        if (SIXEL_FAILED(status)) {
156!
1424
            goto end;
×
1425
        }
1426
    } else if (chunk_is_pnm(pchunk)) {
255✔
1427
        status = sixel_frame_new(&frame, pchunk->allocator);
21✔
1428
        if (SIXEL_FAILED(status)) {
21!
1429
            goto end;
×
1430
        }
1431
        if (pchunk->size > INT_MAX) {
21!
1432
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1433
            goto end;
×
1434
        }
1435
        /* pnm */
1436
        status = load_pnm(pchunk->buffer,
22✔
1437
                          (int)pchunk->size,
21✔
1438
                          frame->allocator,
21✔
1439
                          &frame->pixels,
21✔
1440
                          &frame->width,
21✔
1441
                          &frame->height,
21✔
1442
                          fuse_palette ? &frame->palette: NULL,
1!
1443
                          &frame->ncolors,
21✔
1444
                          &frame->pixelformat);
21!
1445
        if (SIXEL_FAILED(status)) {
21!
1446
            goto end;
×
1447
        }
1448
    } else if (chunk_is_gif(pchunk)) {
183✔
1449
        fnp.fn = fn_load;
16✔
1450
        if (pchunk->size > INT_MAX) {
16!
1451
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1452
            goto end;
×
1453
        }
1454
        status = load_gif(pchunk->buffer,
18✔
1455
                          (int)pchunk->size,
16✔
1456
                          bgcolor,
2✔
1457
                          reqcolors,
2✔
1458
                          fuse_palette,
2✔
1459
                          fstatic,
2✔
1460
                          loop_control,
2✔
1461
                          fnp.p,
2✔
1462
                          context,
2✔
1463
                          pchunk->allocator);
16✔
1464
        if (SIXEL_FAILED(status)) {
16!
1465
            goto end;
6✔
1466
        }
1467
        goto end;
10✔
1468
    } else {
1469
        /*
1470
         * Fallback to stb_image decoding when no specialized handler
1471
         * claimed the chunk.
1472
         *
1473
         *    +--------------+     +--------------------+
1474
         *    | raw chunk    | --> | stb_image decoding |
1475
         *    +--------------+     +--------------------+
1476
         *                        |
1477
         *                        v
1478
         *                +--------------------+
1479
         *                | sixel frame emit   |
1480
         *                +--------------------+
1481
         */
1482
        status = sixel_frame_new(&frame, pchunk->allocator);
166✔
1483
        if (SIXEL_FAILED(status)) {
166!
1484
            goto end;
×
1485
        }
1486
        if (pchunk->size > INT_MAX) {
166!
1487
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1488
            goto end;
×
1489
        }
1490
        stbi_allocator = pchunk->allocator;
166✔
1491
        stbi__start_mem(&stb_context,
166✔
1492
                        pchunk->buffer,
166✔
1493
                        (int)pchunk->size);
166✔
1494
        frame->pixels = stbi__load_and_postprocess_8bit(&stb_context,
328✔
1495
                                                        &frame->width,
166✔
1496
                                                        &frame->height,
166✔
1497
                                                        &depth,
1498
                                                        3);
1499
        if (frame->pixels == NULL) {
166✔
1500
            sixel_helper_set_additional_message(stbi_failure_reason());
3✔
1501
            status = SIXEL_STBI_ERROR;
3✔
1502
            goto end;
3✔
1503
        }
1504
        frame->loop_count = 1;
163✔
1505
        switch (depth) {
163!
1506
        case 1:
160✔
1507
        case 3:
1508
        case 4:
1509
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
163✔
1510
            break;
163✔
1511
        default:
1512
            nwrite = snprintf(message,
×
1513
                              sizeof(message),
1514
                              "load_with_builtin() failed.\n"
1515
                              "reason: unknown pixel-format.(depth: %d)\n",
1516
                              depth);
1517
            if (nwrite > 0) {
×
1518
                sixel_helper_set_additional_message(message);
×
1519
            }
1520
            status = SIXEL_STBI_ERROR;
×
1521
            goto end;
×
1522
        }
1523
    }
1524

1525
    status = sixel_frame_strip_alpha(frame, bgcolor);
340✔
1526
    if (SIXEL_FAILED(status)) {
340!
1527
        goto end;
×
1528
    }
1529

1530
    status = fn_load(frame, context);
340✔
1531
    if (SIXEL_FAILED(status)) {
340!
1532
        goto end;
×
1533
    }
1534

1535
    status = SIXEL_OK;
340✔
1536

1537
end:
300✔
1538
    sixel_frame_unref(frame);
359✔
1539

1540
    return status;
359✔
1541
}
1542

1543

1544
#if HAVE_JPEG
1545
/*
1546
 * Dedicated libjpeg loader wiring minimal pipeline.
1547
 *
1548
 *    +------------+     +-------------------+     +--------------------+
1549
 *    | JPEG chunk | --> | libjpeg decode    | --> | sixel frame emit   |
1550
 *    +------------+     +-------------------+     +--------------------+
1551
 */
1552
static SIXELSTATUS
1553
load_with_libjpeg(
1554
    sixel_chunk_t const       /* in */     *pchunk,
1555
    int                       /* in */     fstatic,
1556
    int                       /* in */     fuse_palette,
1557
    int                       /* in */     reqcolors,
1558
    unsigned char             /* in */     *bgcolor,
1559
    int                       /* in */     loop_control,
1560
    sixel_load_image_function /* in */     fn_load,
1561
    void                      /* in/out */ *context)
1562
{
1563
    SIXELSTATUS status = SIXEL_FALSE;
1564
    sixel_frame_t *frame = NULL;
1565

1566
    (void)fstatic;
1567
    (void)fuse_palette;
1568
    (void)reqcolors;
1569
    (void)loop_control;
1570

1571
    status = sixel_frame_new(&frame, pchunk->allocator);
1572
    if (SIXEL_FAILED(status)) {
1573
        goto end;
1574
    }
1575

1576
    status = load_jpeg(&frame->pixels,
1577
                       pchunk->buffer,
1578
                       pchunk->size,
1579
                       &frame->width,
1580
                       &frame->height,
1581
                       &frame->pixelformat,
1582
                       pchunk->allocator);
1583
    if (SIXEL_FAILED(status)) {
1584
        goto end;
1585
    }
1586

1587
    status = sixel_frame_strip_alpha(frame, bgcolor);
1588
    if (SIXEL_FAILED(status)) {
1589
        goto end;
1590
    }
1591

1592
    status = fn_load(frame, context);
1593
    if (SIXEL_FAILED(status)) {
1594
        goto end;
1595
    }
1596

1597
    status = SIXEL_OK;
1598

1599
end:
1600
    sixel_frame_unref(frame);
1601

1602
    return status;
1603
}
1604

1605
static int
1606
loader_can_try_libjpeg(sixel_chunk_t const *chunk)
1607
{
1608
    if (chunk == NULL) {
1609
        return 0;
1610
    }
1611

1612
    return chunk_is_jpeg(chunk);
1613
}
1614
#endif  /* HAVE_JPEG */
1615

1616
#if HAVE_LIBPNG
1617
/*
1618
 * Dedicated libpng loader for precise PNG decoding.
1619
 *
1620
 *    +-----------+     +------------------+     +--------------------+
1621
 *    | PNG chunk | --> | libpng decode    | --> | sixel frame emit   |
1622
 *    +-----------+     +------------------+     +--------------------+
1623
 */
1624
static SIXELSTATUS
1625
load_with_libpng(
1626
    sixel_chunk_t const       /* in */     *pchunk,
1627
    int                       /* in */     fstatic,
1628
    int                       /* in */     fuse_palette,
1629
    int                       /* in */     reqcolors,
1630
    unsigned char             /* in */     *bgcolor,
1631
    int                       /* in */     loop_control,
1632
    sixel_load_image_function /* in */     fn_load,
1633
    void                      /* in/out */ *context)
1634
{
1635
    SIXELSTATUS status = SIXEL_FALSE;
1636
    sixel_frame_t *frame = NULL;
1637

1638
    (void)fstatic;
1639
    (void)loop_control;
1640

1641
    status = sixel_frame_new(&frame, pchunk->allocator);
1642
    if (SIXEL_FAILED(status)) {
1643
        goto end;
1644
    }
1645

1646
    status = load_png(&frame->pixels,
1647
                      pchunk->buffer,
1648
                      pchunk->size,
1649
                      &frame->width,
1650
                      &frame->height,
1651
                      fuse_palette ? &frame->palette : NULL,
1652
                      &frame->ncolors,
1653
                      reqcolors,
1654
                      &frame->pixelformat,
1655
                      bgcolor,
1656
                      &frame->transparent,
1657
                      pchunk->allocator);
1658
    if (SIXEL_FAILED(status)) {
1659
        goto end;
1660
    }
1661

1662
    status = sixel_frame_strip_alpha(frame, bgcolor);
1663
    if (SIXEL_FAILED(status)) {
1664
        goto end;
1665
    }
1666

1667
    status = fn_load(frame, context);
1668
    if (SIXEL_FAILED(status)) {
1669
        goto end;
1670
    }
1671

1672
    status = SIXEL_OK;
1673

1674
end:
1675
    sixel_frame_unref(frame);
1676

1677
    return status;
1678
}
1679

1680
static int
1681
loader_can_try_libpng(sixel_chunk_t const *chunk)
1682
{
1683
    if (chunk == NULL) {
1684
        return 0;
1685
    }
1686

1687
    return chunk_is_png(chunk);
1688
}
1689
#endif  /* HAVE_LIBPNG */
1690

1691
#ifdef HAVE_GDK_PIXBUF2
1692
/*
1693
 * Loader backed by gdk-pixbuf2. The entire animation is consumed via
1694
 * GdkPixbufLoader, each frame is copied into a temporary buffer and forwarded as
1695
 * a sixel_frame_t. Loop attributes provided by gdk-pixbuf are reconciled with
1696
 * libsixel's loop control settings.
1697
 */
1698
static SIXELSTATUS
1699
load_with_gdkpixbuf(
1700
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
1701
    int                       /* in */     fstatic,      /* static */
1702
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
1703
    int                       /* in */     reqcolors,    /* reqcolors */
1704
    unsigned char             /* in */     *bgcolor,     /* background color */
1705
    int                       /* in */     loop_control, /* one of enum loop_control */
1706
    sixel_load_image_function /* in */     fn_load,      /* callback */
1707
    void                      /* in/out */ *context      /* private data for callback */
1708
)
1709
{
1710
    SIXELSTATUS status = SIXEL_FALSE;
1711
    GdkPixbuf *pixbuf;
1712
    GdkPixbufLoader *loader = NULL;
1713
    gboolean loader_closed = FALSE;  /* remember if loader was already closed */
1714
    GdkPixbufAnimation *animation;
1715
    GdkPixbufAnimationIter *it = NULL;
1716
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1717
# pragma GCC diagnostic push
1718
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1719
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1720
    GTimeVal time_val;
1721
    GTimeVal start_time;
1722
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1723
# pragma GCC diagnostic pop
1724
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1725
    sixel_frame_t *frame = NULL;
1726
    int stride;
1727
    unsigned char *p;
1728
    int i;
1729
    int depth;
1730
    int anim_loop_count = (-1);  /* (-1): infinite, >=0: finite loop count */
1731
    int delay_ms;
1732
    gboolean use_animation = FALSE;
1733

1734
    (void) fuse_palette;
1735
    (void) reqcolors;
1736
    (void) bgcolor;
1737

1738
    status = sixel_frame_new(&frame, pchunk->allocator);
1739
    if (SIXEL_FAILED(status)) {
1740
        goto end;
1741
    }
1742

1743
#if (! GLIB_CHECK_VERSION(2, 36, 0))
1744
    g_type_init();
1745
#endif
1746
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1747
# pragma GCC diagnostic push
1748
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1749
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1750
    g_get_current_time(&time_val);
1751
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1752
# pragma GCC diagnostic pop
1753
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1754
    start_time = time_val;
1755
    loader = gdk_pixbuf_loader_new();
1756
    if (loader == NULL) {
1757
        status = SIXEL_GDK_ERROR;
1758
        goto end;
1759
    }
1760
    /* feed the whole blob and close so the animation metadata becomes available */
1761
    if (! gdk_pixbuf_loader_write(loader, pchunk->buffer, pchunk->size, NULL)) {
1762
        status = SIXEL_GDK_ERROR;
1763
        goto end;
1764
    }
1765
    if (! gdk_pixbuf_loader_close(loader, NULL)) {
1766
        status = SIXEL_GDK_ERROR;
1767
        goto end;
1768
    }
1769
    loader_closed = TRUE;
1770
    pixbuf = NULL;
1771
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1772
# pragma GCC diagnostic push
1773
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1774
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1775
    animation = gdk_pixbuf_loader_get_animation(loader);
1776
    if (animation) {
1777
        /*
1778
         * +------------------------------------------------------+
1779
         * | GdkPixbuf 2.44 keeps the animation APIs available,   |
1780
         * | but marks them deprecated. We still need the         |
1781
         * | GTimeVal-driven timeline to preserve playback, so we |
1782
         * | mute the warning locally instead of abandoning       |
1783
         * | multi-frame decoding.                                |
1784
         * +------------------------------------------------------+
1785
         */
1786
        if (GDK_IS_PIXBUF_SIMPLE_ANIM(animation)) {
1787
            anim_loop_count = gdk_pixbuf_simple_anim_get_loop(
1788
                                 GDK_PIXBUF_SIMPLE_ANIM(animation))
1789
                             ? (-1)
1790
                             : 1;
1791
        } else {
1792
            GParamSpec *loop_pspec = g_object_class_find_property(
1793
                G_OBJECT_GET_CLASS(animation), "loop");
1794
            if (loop_pspec == NULL) {
1795
                loop_pspec = g_object_class_find_property(
1796
                    G_OBJECT_GET_CLASS(animation), "loop-count");
1797
            }
1798
            if (loop_pspec) {
1799
                GValue loop_value = G_VALUE_INIT;
1800
                g_value_init(&loop_value, loop_pspec->value_type);
1801
                g_object_get_property(G_OBJECT(animation),
1802
                                      g_param_spec_get_name(loop_pspec),
1803
                                      &loop_value);
1804
                if (G_VALUE_HOLDS_BOOLEAN(&loop_value)) {
1805
                    /* TRUE means "loop forever" for these properties */
1806
                    anim_loop_count = g_value_get_boolean(&loop_value)
1807
                                      ? (-1)
1808
                                      : 1;
1809
                } else if (G_VALUE_HOLDS_INT(&loop_value)) {
1810
                    int loop_int = g_value_get_int(&loop_value);
1811
                    /* GIF spec treats zero as infinite repetition */
1812
                    anim_loop_count = (loop_int <= 0) ? (-1) : loop_int;
1813
                } else if (G_VALUE_HOLDS_UINT(&loop_value)) {
1814
                    guint loop_uint = g_value_get_uint(&loop_value);
1815
                    if (loop_uint == 0U) {
1816
                        anim_loop_count = (-1);
1817
                    } else {
1818
                        anim_loop_count = loop_uint > (guint)INT_MAX
1819
                                            ? INT_MAX
1820
                                            : (int)loop_uint;
1821
                    }
1822
                }
1823
                g_value_unset(&loop_value);
1824
            }
1825
        }
1826
        if (!fstatic &&
1827
                !gdk_pixbuf_animation_is_static_image(animation)) {
1828
            use_animation = TRUE;
1829
        }
1830
    }
1831
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1832
# pragma GCC diagnostic pop
1833
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1834

1835
    if (! use_animation) {
1836
        /* fall back to single frame decoding */
1837
        pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1838
        if (pixbuf == NULL) {
1839
            goto end;
1840
        }
1841
        frame->frame_no = 0;
1842
        frame->width = gdk_pixbuf_get_width(pixbuf);
1843
        frame->height = gdk_pixbuf_get_height(pixbuf);
1844
        stride = gdk_pixbuf_get_rowstride(pixbuf);
1845
        frame->pixels = sixel_allocator_malloc(
1846
            pchunk->allocator,
1847
            (size_t)(frame->height * stride));
1848
        if (frame->pixels == NULL) {
1849
            sixel_helper_set_additional_message(
1850
                "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1851
            status = SIXEL_BAD_ALLOCATION;
1852
            goto end;
1853
        }
1854
        if (stride / frame->width == 4) {
1855
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1856
            depth = 4;
1857
        } else {
1858
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1859
            depth = 3;
1860
        }
1861
        p = gdk_pixbuf_get_pixels(pixbuf);
1862
        if (stride == frame->width * depth) {
1863
            memcpy(frame->pixels, p, (size_t)(frame->height * stride));
1864
        } else {
1865
            for (i = 0; i < frame->height; ++i) {
1866
                memcpy(frame->pixels + frame->width * depth * i,
1867
                       p + stride * i,
1868
                       (size_t)(frame->width * depth));
1869
            }
1870
        }
1871
        frame->delay = 0;
1872
        frame->multiframe = 0;
1873
        frame->loop_count = 0;
1874
        status = fn_load(frame, context);
1875
        if (status != SIXEL_OK) {
1876
            goto end;
1877
        }
1878
    } else {
1879
        gboolean finished;
1880

1881
        /* reset iterator to the beginning of the timeline */
1882
        time_val = start_time;
1883
        frame->frame_no = 0;
1884
        frame->loop_count = 0;
1885

1886
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1887
# pragma GCC diagnostic push
1888
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1889
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1890
        it = gdk_pixbuf_animation_get_iter(animation, &time_val);
1891
        if (it == NULL) {
1892
            status = SIXEL_GDK_ERROR;
1893
            goto end;
1894
        }
1895

1896
        for (;;) {
1897
            /* handle one logical loop of the animation */
1898
            finished = FALSE;
1899
            while (!gdk_pixbuf_animation_iter_on_currently_loading_frame(it)) {
1900
                /* {{{ */
1901
                pixbuf = gdk_pixbuf_animation_iter_get_pixbuf(it);
1902
                if (pixbuf == NULL) {
1903
                    finished = TRUE;
1904
                    break;
1905
                }
1906
                /* allocate a scratch copy of the current frame */
1907
                frame->width = gdk_pixbuf_get_width(pixbuf);
1908
                frame->height = gdk_pixbuf_get_height(pixbuf);
1909
                stride = gdk_pixbuf_get_rowstride(pixbuf);
1910
                frame->pixels = sixel_allocator_malloc(
1911
                    pchunk->allocator,
1912
                    (size_t)(frame->height * stride));
1913
                if (frame->pixels == NULL) {
1914
                    sixel_helper_set_additional_message(
1915
                        "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1916
                    status = SIXEL_BAD_ALLOCATION;
1917
                    goto end;
1918
                }
1919
                if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1920
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1921
                    depth = 4;
1922
                } else {
1923
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1924
                    depth = 3;
1925
                }
1926
                p = gdk_pixbuf_get_pixels(pixbuf);
1927
                if (stride == frame->width * depth) {
1928
                    memcpy(frame->pixels, p,
1929
                           (size_t)(frame->height * stride));
1930
                } else {
1931
                    for (i = 0; i < frame->height; ++i) {
1932
                        memcpy(frame->pixels + frame->width * depth * i,
1933
                               p + stride * i,
1934
                               (size_t)(frame->width * depth));
1935
                    }
1936
                }
1937
                delay_ms = gdk_pixbuf_animation_iter_get_delay_time(it);
1938
                if (delay_ms < 0) {
1939
                    delay_ms = 0;
1940
                }
1941
                /* advance the synthetic clock before asking gdk to move forward */
1942
                g_time_val_add(&time_val, delay_ms * 1000);
1943
                frame->delay = delay_ms / 10;
1944
                frame->multiframe = 1;
1945

1946
                if (!gdk_pixbuf_animation_iter_advance(it, &time_val)) {
1947
                    finished = TRUE;
1948
                }
1949
                status = fn_load(frame, context);
1950
                if (status != SIXEL_OK) {
1951
                    goto end;
1952
                }
1953
                /* release scratch pixels before decoding the next frame */
1954
                sixel_allocator_free(pchunk->allocator, frame->pixels);
1955
                frame->pixels = NULL;
1956
                frame->frame_no++;
1957

1958
                if (finished) {
1959
                    break;
1960
                }
1961
                /* }}} */
1962
            }
1963

1964
            if (frame->frame_no == 0) {
1965
                break;
1966
            }
1967

1968
            /* finished processing one full loop */
1969
            ++frame->loop_count;
1970

1971
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
1972
                break;
1973
            }
1974
            if (loop_control == SIXEL_LOOP_AUTO) {
1975
                /* obey header-provided loop count when AUTO */
1976
                if (anim_loop_count >= 0 &&
1977
                    frame->loop_count >= anim_loop_count) {
1978
                    break;
1979
                }
1980
            } else if (loop_control != SIXEL_LOOP_FORCE &&
1981
                       anim_loop_count > 0 &&
1982
                       frame->loop_count >= anim_loop_count) {
1983
                break;
1984
            }
1985

1986
            /* restart iteration from the beginning for the next pass */
1987
            g_object_unref(it);
1988
            time_val = start_time;
1989
            it = gdk_pixbuf_animation_get_iter(animation, &time_val);
1990
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1991
# pragma GCC diagnostic pop
1992
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1993
            if (it == NULL) {
1994
                status = SIXEL_GDK_ERROR;
1995
                goto end;
1996
            }
1997
            /* next pass starts counting frames from zero again */
1998
            frame->frame_no = 0;
1999
        }
2000
    }
2001

2002
    status = SIXEL_OK;
2003

2004
end:
2005
    if (frame) {
2006
        /* drop the reference we obtained from sixel_frame_new() */
2007
        sixel_frame_unref(frame);
2008
    }
2009
    if (it) {
2010
        g_object_unref(it);
2011
    }
2012
    if (loader) {
2013
        if (!loader_closed) {
2014
            /* ensure the incremental loader is finalized even on error paths */
2015
            gdk_pixbuf_loader_close(loader, NULL);
2016
        }
2017
        g_object_unref(loader);
2018
    }
2019

2020
    return status;
2021

2022
}
2023
#endif  /* HAVE_GDK_PIXBUF2 */
2024

2025
#if HAVE_COREGRAPHICS
2026
static SIXELSTATUS
2027
load_with_coregraphics(
151✔
2028
    sixel_chunk_t const       /* in */     *pchunk,
2029
    int                       /* in */     fstatic,
2030
    int                       /* in */     fuse_palette,
2031
    int                       /* in */     reqcolors,
2032
    unsigned char             /* in */     *bgcolor,
2033
    int                       /* in */     loop_control,
2034
    sixel_load_image_function /* in */     fn_load,
2035
    void                      /* in/out */ *context)
2036
{
2037
    SIXELSTATUS status = SIXEL_FALSE;
151✔
2038
    sixel_frame_t *frame = NULL;
151✔
2039
    CFDataRef data = NULL;
151✔
2040
    CGImageSourceRef source = NULL;
151✔
2041
    CGImageRef image = NULL;
151✔
2042
    CGColorSpaceRef color_space = NULL;
151✔
2043
    CGContextRef ctx = NULL;
151✔
2044
    size_t stride;
2045
    size_t frame_count;
2046
    int anim_loop_count = (-1);
151✔
2047
    CFDictionaryRef props = NULL;
151✔
2048
    CFDictionaryRef anim_dict;
2049
    CFNumberRef loop_num;
2050
    CFDictionaryRef frame_props;
2051
    CFDictionaryRef frame_anim_dict;
2052
    CFNumberRef delay_num;
2053
    double delay_sec;
2054
    size_t i;
2055

2056
    (void) fuse_palette;
151✔
2057
    (void) reqcolors;
151✔
2058
    (void) bgcolor;
151✔
2059

2060
    status = sixel_frame_new(&frame, pchunk->allocator);
151✔
2061
    if (SIXEL_FAILED(status)) {
151!
2062
        goto end;
2063
    }
2064

2065
    data = CFDataCreate(kCFAllocatorDefault,
302✔
2066
                        pchunk->buffer,
151✔
2067
                        (CFIndex)pchunk->size);
151✔
2068
    if (! data) {
151!
2069
        status = SIXEL_FALSE;
2070
        goto end;
2071
    }
2072

2073
    source = CGImageSourceCreateWithData(data, NULL);
151✔
2074
    if (! source) {
151!
2075
        status = SIXEL_FALSE;
2076
        goto end;
2077
    }
2078

2079
    frame_count = CGImageSourceGetCount(source);
151✔
2080
    if (! frame_count) {
151✔
2081
        status = SIXEL_FALSE;
55✔
2082
        goto end;
55✔
2083
    }
2084
    if (fstatic) {
96✔
2085
        frame_count = 1;
8✔
2086
    }
8✔
2087

2088
    props = CGImageSourceCopyProperties(source, NULL);
96✔
2089
    if (props) {
96✔
2090
        anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
96✔
2091
            props, kCGImagePropertyGIFDictionary);
96✔
2092
        if (anim_dict) {
96✔
2093
            loop_num = (CFNumberRef)CFDictionaryGetValue(
5✔
2094
                anim_dict, kCGImagePropertyGIFLoopCount);
5✔
2095
            if (loop_num) {
5✔
2096
                CFNumberGetValue(loop_num, kCFNumberIntType, &anim_loop_count);
5✔
2097
            }
5✔
2098
        }
5✔
2099
        CFRelease(props);
96✔
2100
    }
96✔
2101

2102
    color_space = CGColorSpaceCreateDeviceRGB();
96✔
2103
    if (! color_space) {
96!
2104
        status = SIXEL_FALSE;
2105
        goto end;
2106
    }
2107

2108
    frame->loop_count = 0;
96✔
2109

2110
    for (;;) {
96✔
2111
        frame->frame_no = 0;
98✔
2112
        for (i = 0; i < frame_count; ++i) {
232✔
2113
            delay_sec = 0.0;
138✔
2114
            frame_props = CGImageSourceCopyPropertiesAtIndex(
138✔
2115
                source, (CFIndex)i, NULL);
138✔
2116
            if (frame_props) {
138✔
2117
                frame_anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
138✔
2118
                    frame_props, kCGImagePropertyGIFDictionary);
138✔
2119
                if (frame_anim_dict) {
138✔
2120
                    delay_num = (CFNumberRef)CFDictionaryGetValue(
47✔
2121
                        frame_anim_dict, kCGImagePropertyGIFUnclampedDelayTime);
47✔
2122
                    if (! delay_num) {
47✔
2123
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
2124
                            frame_anim_dict, kCGImagePropertyGIFDelayTime);
2125
                    }
2126
                    if (delay_num) {
47✔
2127
                        CFNumberGetValue(delay_num,
47✔
2128
                                         kCFNumberDoubleType,
2129
                                         &delay_sec);
2130
                    }
47✔
2131
                }
47✔
2132
#if defined(kCGImagePropertyPNGDictionary) && \
2133
    defined(kCGImagePropertyAPNGUnclampedDelayTime) && \
2134
    defined(kCGImagePropertyAPNGDelayTime)
2135
                if (delay_sec <= 0.0) {
2136
                    CFDictionaryRef png_frame = (CFDictionaryRef)CFDictionaryGetValue(
2137
                        frame_props, kCGImagePropertyPNGDictionary);
2138
                    if (png_frame) {
2139
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
2140
                            png_frame, kCGImagePropertyAPNGUnclampedDelayTime);
2141
                        if (! delay_num) {
2142
                            delay_num = (CFNumberRef)CFDictionaryGetValue(
2143
                                png_frame, kCGImagePropertyAPNGDelayTime);
2144
                        }
2145
                        if (delay_num) {
2146
                            CFNumberGetValue(delay_num,
2147
                                             kCFNumberDoubleType,
2148
                                             &delay_sec);
2149
                        }
2150
                    }
2151
                }
2152
#endif
2153
                CFRelease(frame_props);
138✔
2154
            }
138✔
2155
            if (delay_sec <= 0.0) {
184✔
2156
                delay_sec = 0.1;
92✔
2157
            }
92✔
2158
            frame->delay = (int)(delay_sec * 100.0 + 0.5);
184✔
2159
            if (frame->delay < 1) {
184✔
2160
                frame->delay = 1;
2161
            }
2162

2163
            image = CGImageSourceCreateImageAtIndex(source, (CFIndex)i, NULL);
138✔
2164
            if (! image) {
138✔
2165
                status = SIXEL_FALSE;
1✔
2166
                goto end;
1✔
2167
            }
2168

2169
            frame->width = (int)CGImageGetWidth(image);
137✔
2170
            frame->height = (int)CGImageGetHeight(image);
137✔
2171
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
137✔
2172
            stride = (size_t)frame->width * 4;
137✔
2173
            if (frame->pixels != NULL) {
137✔
2174
                sixel_allocator_free(pchunk->allocator, frame->pixels);
42✔
2175
            }
42✔
2176
            frame->pixels = sixel_allocator_malloc(
137✔
2177
                pchunk->allocator, (size_t)(frame->height * stride));
137✔
2178

2179
            if (frame->pixels == NULL) {
137!
2180
                sixel_helper_set_additional_message(
2181
                    "load_with_coregraphics: sixel_allocator_malloc() failed.");
2182
                status = SIXEL_BAD_ALLOCATION;
2183
                CGImageRelease(image);
2184
                goto end;
2185
            }
2186

2187
            ctx = CGBitmapContextCreate(frame->pixels,
274✔
2188
                                        frame->width,
137✔
2189
                                        frame->height,
137✔
2190
                                        8,
2191
                                        stride,
137✔
2192
                                        color_space,
137✔
2193
                                        kCGImageAlphaPremultipliedLast |
2194
                                            kCGBitmapByteOrder32Big);
2195
            if (!ctx) {
137!
2196
                CGImageRelease(image);
2197
                goto end;
2198
            }
2199

2200
            CGContextDrawImage(ctx,
274✔
2201
                               CGRectMake(0, 0, frame->width, frame->height),
137✔
2202
                               image);
137✔
2203
            CGContextRelease(ctx);
137✔
2204
            ctx = NULL;
137✔
2205

2206
            frame->multiframe = (frame_count > 1);
137✔
2207
            status = fn_load(frame, context);
137✔
2208
            CGImageRelease(image);
137✔
2209
            image = NULL;
137✔
2210
            if (status != SIXEL_OK) {
137✔
2211
                goto end;
3✔
2212
            }
2213
            ++frame->frame_no;
134✔
2214
        }
134✔
2215

2216
        ++frame->loop_count;
94✔
2217

2218
        if (frame_count <= 1) {
94✔
2219
            break;
89✔
2220
        }
2221
        if (loop_control == SIXEL_LOOP_DISABLE) {
5✔
2222
            break;
2✔
2223
        }
2224
        if (loop_control == SIXEL_LOOP_AUTO) {
3!
2225
            if (anim_loop_count < 0) {
3!
2226
                break;
2227
            }
2228
            if (anim_loop_count > 0 && frame->loop_count >= anim_loop_count) {
3✔
2229
                break;
1✔
2230
            }
2231
            continue;
2✔
2232
        }
2233
    }
2234

2235
    status = SIXEL_OK;
92✔
2236

2237
end:
2238
    if (ctx) {
151✔
2239
        CGContextRelease(ctx);
2240
    }
2241
    if (color_space) {
96✔
2242
        CGColorSpaceRelease(color_space);
96✔
2243
    }
96✔
2244
    if (image) {
192✔
2245
        CGImageRelease(image);
2246
    }
2247
    if (source) {
151✔
2248
        CFRelease(source);
151✔
2249
    }
151✔
2250
    if (data) {
151✔
2251
        CFRelease(data);
151✔
2252
    }
151✔
2253
    if (frame) {
151✔
2254
        sixel_frame_unref(frame);
151✔
2255
    }
151✔
2256
    return status;
151✔
2257
}
2258
#endif  /* HAVE_COREGRAPHICS */
2259

2260
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
2261
static SIXELSTATUS
2262
load_with_quicklook(
2263
    sixel_chunk_t const       /* in */     *pchunk,
2264
    int                       /* in */     fstatic,
2265
    int                       /* in */     fuse_palette,
2266
    int                       /* in */     reqcolors,
2267
    unsigned char             /* in */     *bgcolor,
2268
    int                       /* in */     loop_control,
2269
    sixel_load_image_function /* in */     fn_load,
2270
    void                      /* in/out */ *context)
2271
{
2272
    SIXELSTATUS status = SIXEL_FALSE;
2273
    sixel_frame_t *frame = NULL;
2274
    CFStringRef path = NULL;
2275
    CFURLRef url = NULL;
2276
    CGImageRef image = NULL;
2277
    CGColorSpaceRef color_space = NULL;
2278
    CGContextRef ctx = NULL;
2279
    CGRect bounds;
2280
    size_t stride;
2281
    unsigned char fill_color[3];
2282
    CGFloat fill_r;
2283
    CGFloat fill_g;
2284
    CGFloat fill_b;
2285
    CGFloat max_dimension;
2286
    CGSize max_size;
2287

2288
    (void)fstatic;
2289
    (void)fuse_palette;
2290
    (void)reqcolors;
2291
    (void)loop_control;
2292

2293
    if (pchunk == NULL || pchunk->source_path == NULL) {
2294
        goto end;
2295
    }
2296

2297
    status = sixel_frame_new(&frame, pchunk->allocator);
2298
    if (SIXEL_FAILED(status)) {
×
2299
        goto end;
2300
    }
2301

2302
    path = CFStringCreateWithCString(kCFAllocatorDefault,
2303
                                     pchunk->source_path,
2304
                                     kCFStringEncodingUTF8);
2305
    if (path == NULL) {
×
2306
        status = SIXEL_RUNTIME_ERROR;
2307
        goto end;
2308
    }
2309

2310
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
2311
                                        path,
2312
                                        kCFURLPOSIXPathStyle,
2313
                                        false);
2314
    if (url == NULL) {
×
2315
        status = SIXEL_RUNTIME_ERROR;
2316
        goto end;
2317
    }
2318

2319
    if (thumbnailer_size_hint > 0) {
×
2320
        max_dimension = (CGFloat)thumbnailer_size_hint;
2321
    } else {
2322
        max_dimension = (CGFloat)SIXEL_THUMBNAILER_DEFAULT_SIZE;
2323
    }
2324
    max_size.width = max_dimension;
2325
    max_size.height = max_dimension;
2326
#if HAVE_QUICKLOOK_THUMBNAILING
2327
    image = sixel_quicklook_thumbnail_create(url, max_size);
2328
    if (image == NULL) {
2329
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2330
#  pragma clang diagnostic push
2331
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
2332
# endif
2333
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
2334
                                       url,
2335
                                       max_size,
2336
                                       NULL);
2337
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2338
#  pragma clang diagnostic pop
2339
# endif
2340
    }
2341
#else
2342
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2343
#  pragma clang diagnostic push
2344
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
2345
# endif
2346
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
2347
                                   url,
2348
                                   max_size,
2349
                                   NULL);
2350
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2351
#  pragma clang diagnostic pop
2352
# endif
2353
#endif
2354
    if (image == NULL) {
×
2355
        status = SIXEL_RUNTIME_ERROR;
2356
        sixel_helper_set_additional_message(
2357
            "load_with_quicklook: CQLThumbnailImageCreate() failed.");
2358
        goto end;
2359
    }
2360

2361
    color_space = CGColorSpaceCreateDeviceRGB();
2362
    if (color_space == NULL) {
×
2363
        status = SIXEL_RUNTIME_ERROR;
2364
        sixel_helper_set_additional_message(
2365
            "load_with_quicklook: CGColorSpaceCreateDeviceRGB() failed.");
2366
        goto end;
2367
    }
2368

2369
    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
2370
    frame->width = (int)CGImageGetWidth(image);
2371
    frame->height = (int)CGImageGetHeight(image);
2372
    if (frame->width <= 0 || frame->height <= 0) {
2373
        status = SIXEL_RUNTIME_ERROR;
2374
        sixel_helper_set_additional_message(
2375
            "load_with_quicklook: invalid image size detected.");
2376
        goto end;
2377
    }
2378

2379
    stride = (size_t)frame->width * 4;
2380
    frame->pixels =
2381
        sixel_allocator_malloc(pchunk->allocator,
2382
                               (size_t)frame->height * stride);
2383
    if (frame->pixels == NULL) {
×
2384
        sixel_helper_set_additional_message(
2385
            "load_with_quicklook: sixel_allocator_malloc() failed.");
2386
        status = SIXEL_BAD_ALLOCATION;
2387
        goto end;
2388
    }
2389

2390
    if (bgcolor != NULL) {
×
2391
        fill_color[0] = bgcolor[0];
2392
        fill_color[1] = bgcolor[1];
2393
        fill_color[2] = bgcolor[2];
2394
    } else {
2395
        fill_color[0] = 255;
2396
        fill_color[1] = 255;
2397
        fill_color[2] = 255;
2398
    }
2399

2400
    ctx = CGBitmapContextCreate(frame->pixels,
2401
                                frame->width,
2402
                                frame->height,
2403
                                8,
2404
                                stride,
2405
                                color_space,
2406
                                kCGImageAlphaPremultipliedLast |
2407
                                    kCGBitmapByteOrder32Big);
2408
    if (ctx == NULL) {
×
2409
        status = SIXEL_RUNTIME_ERROR;
2410
        sixel_helper_set_additional_message(
2411
            "load_with_quicklook: CGBitmapContextCreate() failed.");
2412
        goto end;
2413
    }
2414

2415
    bounds = CGRectMake(0,
2416
                        0,
2417
                        (CGFloat)frame->width,
2418
                        (CGFloat)frame->height);
2419
    fill_r = (CGFloat)fill_color[0] / 255.0f;
2420
    fill_g = (CGFloat)fill_color[1] / 255.0f;
2421
    fill_b = (CGFloat)fill_color[2] / 255.0f;
2422
    CGContextSetRGBFillColor(ctx, fill_r, fill_g, fill_b, 1.0f);
2423
    CGContextFillRect(ctx, bounds);
2424
    CGContextDrawImage(ctx, bounds, image);
2425
    CGContextFlush(ctx);
2426

2427
    /* Abort when Quick Look produced no visible pixels so other loaders run. */
2428
    {
2429
        size_t pixel_count;
2430
        size_t index;
2431
        unsigned char *pixel;
2432
        int has_content;
2433

2434
        pixel_count = (size_t)frame->width * (size_t)frame->height;
2435
        pixel = frame->pixels;
2436
        has_content = 0;
2437
        for (index = 0; index < pixel_count; ++index) {
2438
            if (pixel[0] != fill_color[0] ||
2439
                    pixel[1] != fill_color[1] ||
2440
                    pixel[2] != fill_color[2] ||
2441
                    pixel[3] != 0xff) {
2442
                has_content = 1;
2443
                break;
2444
            }
2445
            pixel += 4;
2446
        }
2447
        if (! has_content) {
×
2448
            sixel_helper_set_additional_message(
2449
                "load_with_quicklook: thumbnail contained no visible pixels.");
2450
            status = SIXEL_BAD_INPUT;
2451
            CGContextRelease(ctx);
2452
            ctx = NULL;
2453
            goto end;
2454
        }
2455
    }
2456

2457
    CGContextRelease(ctx);
2458
    ctx = NULL;
2459

2460
    frame->delay = 0;
2461
    frame->frame_no = 0;
2462
    frame->loop_count = 1;
2463
    frame->multiframe = 0;
2464
    frame->transparent = (-1);
2465

2466
    status = sixel_frame_strip_alpha(frame, fill_color);
2467
    if (SIXEL_FAILED(status)) {
×
2468
        goto end;
2469
    }
2470

2471
    status = fn_load(frame, context);
2472
    if (status != SIXEL_OK) {
×
2473
        goto end;
2474
    }
2475

2476
    status = SIXEL_OK;
2477

2478
end:
2479
    if (ctx != NULL) {
2480
        CGContextRelease(ctx);
2481
    }
2482
    if (color_space != NULL) {
2483
        CGColorSpaceRelease(color_space);
2484
    }
2485
    if (image != NULL) {
2486
        CGImageRelease(image);
2487
    }
2488
    if (url != NULL) {
2489
        CFRelease(url);
2490
    }
2491
    if (path != NULL) {
2492
        CFRelease(path);
2493
    }
2494
    if (frame != NULL) {
2495
        sixel_frame_unref(frame);
2496
    }
2497

2498
    return status;
2499
}
2500
#endif  /* HAVE_COREGRAPHICS && HAVE_QUICKLOOK */
2501

2502
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
2503

2504
# if defined(HAVE_NANOSLEEP)
2505
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
2506
# endif
2507
# if defined(HAVE_REALPATH)
2508
char * realpath(const char *restrict path, char *restrict resolved_path);
2509
# endif
2510
# if defined(HAVE_MKSTEMP)
2511
int mkstemp(char *);
2512
# endif
2513

2514
/*
2515
 * thumbnailer_message_finalize
2516
 *
2517
 * Clamp formatted messages so callers do not have to repeat truncation
2518
 * checks after calling sixel_compat_snprintf().
2519
 */
2520
static void
2521
thumbnailer_message_finalize(char *buffer, size_t capacity, int written)
×
2522
{
2523
    if (buffer == NULL || capacity == 0) {
×
2524
        return;
×
2525
    }
2526

2527
    if (written < 0) {
×
2528
        buffer[0] = '\0';
×
2529
        return;
×
2530
    }
2531

2532
    if ((size_t)written >= capacity) {
×
2533
        buffer[capacity - 1u] = '\0';
×
2534
    }
2535
}
2536

2537
/*
2538
 * thumbnailer_sleep_briefly
2539
 *
2540
 * Yield the CPU for a short duration so child polling loops avoid busy
2541
 * waiting.
2542
 *
2543
 */
2544
static void
2545
thumbnailer_sleep_briefly(void)
×
2546
{
2547
# if HAVE_NANOSLEEP
2548
    struct timespec ts;
2549
# endif
2550

2551
# if HAVE_NANOSLEEP
2552
    ts.tv_sec = 0;
×
2553
    ts.tv_nsec = 10000000L;
×
2554
    nanosleep(&ts, NULL);
×
2555
# elif defined(_WIN32)
2556
    Sleep(10);
2557
# else
2558
    (void)usleep(10000);
2559
# endif
2560
}
×
2561

2562
# if !defined(_WIN32) && defined(HAVE__REALPATH) && !defined(HAVE_REALPATH)
2563
static char *
2564
thumbnailer_resolve_without_realpath(char const *path)
2565
{
2566
    char *cwd;
2567
    char *resolved;
2568
    size_t cwd_length;
2569
    size_t path_length;
2570
    int need_separator;
2571

2572
    cwd = NULL;
2573
    resolved = NULL;
2574
    cwd_length = 0;
2575
    path_length = 0;
2576
    need_separator = 0;
2577

2578
    if (path == NULL) {
2579
        return NULL;
2580
    }
2581

2582
    if (path[0] == '/') {
2583
        path_length = strlen(path);
2584
        resolved = malloc(path_length + 1);
2585
        if (resolved == NULL) {
2586
            return NULL;
2587
        }
2588
        memcpy(resolved, path, path_length + 1);
2589

2590
        return resolved;
2591
    }
2592

2593
#  if defined(PATH_MAX)
2594
    cwd = malloc(PATH_MAX);
2595
    if (cwd != NULL) {
2596
        if (getcwd(cwd, PATH_MAX) != NULL) {
2597
            cwd_length = strlen(cwd);
2598
            path_length = strlen(path);
2599
            need_separator = 0;
2600
            if (cwd_length > 0 && cwd[cwd_length - 1] != '/') {
2601
                need_separator = 1;
2602
            }
2603
            resolved = malloc(cwd_length + need_separator + path_length + 1);
2604
            if (resolved != NULL) {
2605
                memcpy(resolved, cwd, cwd_length);
2606
                if (need_separator != 0) {
2607
                    resolved[cwd_length] = '/';
2608
                }
2609
                memcpy(resolved + cwd_length + need_separator,
2610
                       path,
2611
                       path_length + 1);
2612
            }
2613
            free(cwd);
2614
            if (resolved != NULL) {
2615
                return resolved;
2616
            }
2617
        } else {
2618
            free(cwd);
2619
        }
2620
    }
2621
#  endif  /* PATH_MAX */
2622

2623
    path_length = strlen(path);
2624
    resolved = malloc(path_length + 1);
2625
    if (resolved == NULL) {
2626
        return NULL;
2627
    }
2628
    memcpy(resolved, path, path_length + 1);
2629

2630
    return resolved;
2631
}
2632
# endif  /* !defined(_WIN32) && defined(HAVE__REALPATH) && !defined(HAVE_REALPATH) */
2633

2634
/*
2635
 * thumbnailer_resolve_path
2636
 *
2637
 * Resolve the supplied path to an absolute canonical path when possible.
2638
 *
2639
 * Arguments:
2640
 *     path - original filesystem path.
2641
 * Returns:
2642
 *     Newly allocated canonical path or NULL on failure.
2643
 */
2644
static char *
2645
thumbnailer_resolve_path(char const *path)
×
2646
{
2647
    char *resolved;
2648

2649
    resolved = NULL;
×
2650

2651
    if (path == NULL) {
×
2652
        return NULL;
×
2653
    }
2654

2655
# if defined(HAVE__FULLPATH)
2656
    resolved = _fullpath(NULL, path, 0);
2657
# elif defined(HAVE__REALPATH)
2658
    resolved = _realpath(path, NULL);
2659
# elif defined(HAVE_REALPATH)
2660
    resolved = realpath(path, NULL);
×
2661
# else
2662
    resolved = thumbnailer_resolve_without_realpath(path);
2663
# endif
2664

2665
    return resolved;
×
2666
}
2667

2668
struct thumbnailer_string_list {
2669
    char **items;
2670
    size_t length;
2671
    size_t capacity;
2672
};
2673

2674
struct thumbnailer_entry {
2675
    char *exec_line;
2676
    char *tryexec;
2677
    struct thumbnailer_string_list *mime_types;
2678
};
2679

2680
/*
2681
 * thumbnailer_strdup
2682
 *
2683
 * Duplicate a string with malloc so thumbnail helpers own their copies.
2684
 *
2685
 * Arguments:
2686
 *     src - zero-terminated string to copy; may be NULL.
2687
 * Returns:
2688
 *     Newly allocated duplicate or NULL on failure/NULL input.
2689
 */
2690
static char *
2691
thumbnailer_strdup(char const *src)
48✔
2692
{
2693
    char *copy;
2694
    size_t length;
2695

2696
    copy = NULL;
48✔
2697
    length = 0;
48✔
2698

2699
    if (src == NULL) {
48!
2700
        return NULL;
×
2701
    }
2702

2703
    length = strlen(src);
48✔
2704
    copy = malloc(length + 1);
48✔
2705
    if (copy == NULL) {
48!
2706
        return NULL;
×
2707
    }
2708
    memcpy(copy, src, length + 1);
48✔
2709

2710
    return copy;
48✔
2711
}
16✔
2712

2713
/*
2714
 * thumbnailer_string_list_new
2715
 *
2716
 * Allocate an empty expandable string list used throughout the loader.
2717
 *
2718
 * Arguments:
2719
 *     None.
2720
 * Returns:
2721
 *     Newly allocated list instance or NULL on failure.
2722
 */
2723
static struct thumbnailer_string_list *
2724
thumbnailer_string_list_new(void)
9✔
2725
{
2726
    struct thumbnailer_string_list *list;
2727

2728
    list = malloc(sizeof(*list));
9✔
2729
    if (list == NULL) {
9!
2730
        return NULL;
×
2731
    }
2732

2733
    list->items = NULL;
9✔
2734
    list->length = 0;
9✔
2735
    list->capacity = 0;
9✔
2736

2737
    return list;
9✔
2738
}
3✔
2739

2740
/*
2741
 * thumbnailer_string_list_free
2742
 *
2743
 * Release every string stored in the list and free the container itself.
2744
 *
2745
 * Arguments:
2746
 *     list - list instance produced by thumbnailer_string_list_new().
2747
 */
2748
static void
2749
thumbnailer_string_list_free(struct thumbnailer_string_list *list)
18✔
2750
{
2751
    size_t index;
2752

2753
    index = 0;
18✔
2754

2755
    if (list == NULL) {
18✔
2756
        return;
9✔
2757
    }
2758

2759
    if (list->items != NULL) {
9!
2760
        for (index = 0; index < list->length; ++index) {
36✔
2761
            free(list->items[index]);
27✔
2762
            list->items[index] = NULL;
27✔
2763
        }
9✔
2764
        free(list->items);
9✔
2765
        list->items = NULL;
9✔
2766
    }
3✔
2767

2768
    free(list);
9✔
2769
}
6✔
2770

2771
/*
2772
 * thumbnailer_string_list_append
2773
 *
2774
 * Append a copy of the supplied string to the dynamic list.
2775
 *
2776
 * Arguments:
2777
 *     list  - destination list.
2778
 *     value - string to duplicate and append.
2779
 * Returns:
2780
 *     1 on success, 0 on allocation failure or invalid input.
2781
 */
2782
static int
2783
thumbnailer_string_list_append(struct thumbnailer_string_list *list,
27✔
2784
                               char const *value)
2785
{
2786
    size_t new_capacity;
2787
    char **new_items;
2788
    char *copy;
2789

2790
    new_capacity = 0;
27✔
2791
    new_items = NULL;
27✔
2792
    copy = NULL;
27✔
2793

2794
    if (list == NULL || value == NULL) {
27!
2795
        return 0;
×
2796
    }
2797

2798
    copy = thumbnailer_strdup(value);
27✔
2799
    if (copy == NULL) {
27!
2800
        return 0;
×
2801
    }
2802

2803
    if (list->length == list->capacity) {
27✔
2804
        new_capacity = (list->capacity == 0) ? 4 : list->capacity * 2;
9!
2805
        new_items = realloc(list->items,
12✔
2806
                            new_capacity * sizeof(*list->items));
3✔
2807
        if (new_items == NULL) {
9!
2808
            free(copy);
×
2809
            return 0;
×
2810
        }
2811
        list->items = new_items;
9✔
2812
        list->capacity = new_capacity;
9✔
2813
    }
3✔
2814

2815
    list->items[list->length] = copy;
27✔
2816
    list->length += 1;
27✔
2817

2818
    return 1;
27✔
2819
}
9✔
2820

2821
/*
2822
 * thumbnailer_entry_init
2823
 *
2824
 * Prepare a thumbnailer_entry structure for population.
2825
 *
2826
 * Arguments:
2827
 *     entry - caller-provided structure to initialize.
2828
 */
2829
static void
2830
thumbnailer_entry_init(struct thumbnailer_entry *entry)
9✔
2831
{
2832
    if (entry == NULL) {
9!
2833
        return;
×
2834
    }
2835

2836
    entry->exec_line = NULL;
9✔
2837
    entry->tryexec = NULL;
9✔
2838
    entry->mime_types = NULL;
9✔
2839
}
3✔
2840

2841
/*
2842
 * thumbnailer_entry_clear
2843
 *
2844
 * Release every heap allocation associated with a thumbnailer_entry.
2845
 *
2846
 * Arguments:
2847
 *     entry - structure previously initialized with thumbnailer_entry_init().
2848
 */
2849
static void
2850
thumbnailer_entry_clear(struct thumbnailer_entry *entry)
9✔
2851
{
2852
    if (entry == NULL) {
9!
2853
        return;
×
2854
    }
2855

2856
    free(entry->exec_line);
9✔
2857
    entry->exec_line = NULL;
9✔
2858
    free(entry->tryexec);
9✔
2859
    entry->tryexec = NULL;
9✔
2860
    thumbnailer_string_list_free(entry->mime_types);
9✔
2861
    entry->mime_types = NULL;
9✔
2862
}
3✔
2863

2864
/*
2865
 * thumbnailer_join_paths
2866
 *
2867
 * Concatenate two path fragments inserting a slash when required.
2868
 *
2869
 * Arguments:
2870
 *     left  - directory prefix.
2871
 *     right - trailing component.
2872
 * Returns:
2873
 *     Newly allocated combined path or NULL on failure.
2874
 */
2875
static char *
2876
thumbnailer_join_paths(char const *left, char const *right)
36✔
2877
{
2878
    size_t left_length;
2879
    size_t right_length;
2880
    int need_separator;
2881
    char *combined;
2882

2883
    left_length = 0;
36✔
2884
    right_length = 0;
36✔
2885
    need_separator = 0;
36✔
2886
    combined = NULL;
36✔
2887

2888
    if (left == NULL || right == NULL) {
36!
2889
        return NULL;
×
2890
    }
2891

2892
    left_length = strlen(left);
36✔
2893
    right_length = strlen(right);
36✔
2894
    need_separator = 0;
36✔
2895

2896
    if (left_length > 0 && right_length > 0 &&
48!
2897
            left[left_length - 1] != '/' && right[0] != '/') {
36!
2898
        need_separator = 1;
36✔
2899
    }
12✔
2900

2901
    combined = malloc(left_length + right_length + need_separator + 1);
36✔
2902
    if (combined == NULL) {
36!
2903
        return NULL;
×
2904
    }
2905

2906
    memcpy(combined, left, left_length);
36✔
2907
    if (need_separator) {
36!
2908
        combined[left_length] = '/';
36✔
2909
        memcpy(combined + left_length + 1, right, right_length);
36✔
2910
        combined[left_length + right_length + 1] = '\0';
36✔
2911
    } else {
12✔
2912
        memcpy(combined + left_length, right, right_length);
×
2913
        combined[left_length + right_length] = '\0';
×
2914
    }
2915

2916
    return combined;
36✔
2917
}
12✔
2918

2919
/*
2920
 * thumbnailer_collect_directories
2921
 *
2922
 * Enumerate directories that may contain FreeDesktop thumbnailer
2923
 * definitions according to the XDG specification.
2924
 *
2925
 * GNOME thumbnailers follow the XDG data directory contract:
2926
 *
2927
 *     +------------------+      +---------------------------+
2928
 *     | HOME/.local/share| ---> | HOME/.local/share/        |
2929
 *     |                  |      |    thumbnailers/(*.thumbnailer)
2930
 *     +------------------+      +---------------------------+
2931
 *
2932
 *     +------------------+      +---------------------------+
2933
 *     | XDG_DATA_DIRS    | ---> | <dir>/thumbnailers/(*.thumbnailer)
2934
 *     +------------------+      +---------------------------+
2935
 *
2936
 * The helper below expands both sources so that the caller can iterate
2937
 * through every known definition in order of precedence.
2938
 *
2939
 * Arguments:
2940
 *     None.
2941
 * Returns:
2942
 *     Newly allocated list of directory paths or NULL on failure.
2943
 */
2944
static struct thumbnailer_string_list *
2945
thumbnailer_collect_directories(void)
9✔
2946
{
2947
    struct thumbnailer_string_list *dirs;
2948
    char const *xdg_data_dirs;
2949
    char const *home_dir;
2950
    char const *default_dirs;
2951
    char *candidate;
2952
    char *local_share;
2953
    char *dirs_copy;
2954
    char *token;
2955

2956
    dirs = NULL;
9✔
2957
    xdg_data_dirs = NULL;
9✔
2958
    home_dir = NULL;
9✔
2959
    default_dirs = NULL;
9✔
2960
    candidate = NULL;
9✔
2961
    local_share = NULL;
9✔
2962
    dirs_copy = NULL;
9✔
2963
    token = NULL;
9✔
2964

2965
    dirs = thumbnailer_string_list_new();
9✔
2966
    if (dirs == NULL) {
9!
2967
        return NULL;
×
2968
    }
2969

2970
    home_dir = getenv("HOME");
9✔
2971
    loader_trace_message(
15!
2972
        "thumbnailer_collect_directories: HOME=%s",
2973
        (home_dir != NULL && home_dir[0] != '\0') ? home_dir : "(unset)");
9!
2974
    if (home_dir != NULL && home_dir[0] != '\0') {
9!
2975
        local_share = thumbnailer_join_paths(home_dir,
9✔
2976
                                             ".local/share");
2977
        if (local_share != NULL) {
9!
2978
            candidate = thumbnailer_join_paths(local_share,
9✔
2979
                                               "thumbnailers");
2980
            if (candidate != NULL) {
9!
2981
                if (!thumbnailer_string_list_append(dirs, candidate)) {
9!
2982
                    free(candidate);
×
2983
                    free(local_share);
×
2984
                    thumbnailer_string_list_free(dirs);
×
2985
                    return NULL;
×
2986
                }
2987
                loader_trace_message(
9✔
2988
                    "thumbnailer_collect_directories: added %s",
2989
                    candidate);
3✔
2990
                free(candidate);
9✔
2991
                candidate = NULL;
9✔
2992
            }
3✔
2993
            free(local_share);
9✔
2994
            local_share = NULL;
9✔
2995
        }
3✔
2996
    }
3✔
2997

2998
    xdg_data_dirs = getenv("XDG_DATA_DIRS");
12✔
2999
    if (xdg_data_dirs == NULL || xdg_data_dirs[0] == '\0') {
12!
3000
        default_dirs = "/usr/local/share:/usr/share";
9✔
3001
        xdg_data_dirs = default_dirs;
9✔
3002
    }
3✔
3003
    loader_trace_message(
9✔
3004
        "thumbnailer_collect_directories: XDG_DATA_DIRS=%s",
3005
        xdg_data_dirs);
3✔
3006

3007
    dirs_copy = thumbnailer_strdup(xdg_data_dirs);
9✔
3008
    if (dirs_copy == NULL) {
9!
3009
        thumbnailer_string_list_free(dirs);
×
3010
        return NULL;
×
3011
    }
3012
    token = strtok(dirs_copy, ":");
9✔
3013
    while (token != NULL) {
27✔
3014
        candidate = thumbnailer_join_paths(token, "thumbnailers");
18✔
3015
        if (candidate != NULL) {
18!
3016
            if (!thumbnailer_string_list_append(dirs, candidate)) {
18!
3017
                free(candidate);
×
3018
                free(dirs_copy);
×
3019
                thumbnailer_string_list_free(dirs);
×
3020
                return NULL;
×
3021
            }
3022
            loader_trace_message(
18✔
3023
                "thumbnailer_collect_directories: added %s",
3024
                candidate);
6✔
3025
            free(candidate);
18✔
3026
            candidate = NULL;
18✔
3027
        }
6✔
3028
        token = strtok(NULL, ":");
18✔
3029
    }
3030
    free(dirs_copy);
9✔
3031
    dirs_copy = NULL;
9✔
3032

3033
    return dirs;
9✔
3034
}
3✔
3035

3036
/*
3037
 * thumbnailer_trim_right
3038
 *
3039
 * Remove trailing whitespace in place from a mutable string.
3040
 *
3041
 * Arguments:
3042
 *     text - string to trim; must be writable and zero-terminated.
3043
 */
3044
static void
3045
thumbnailer_trim_right(char *text)
12✔
3046
{
3047
    size_t length;
3048

3049
    length = 0;
12✔
3050

3051
    if (text == NULL) {
12!
3052
        return;
×
3053
    }
3054

3055
    length = strlen(text);
12✔
3056
    while (length > 0 && isspace((unsigned char)text[length - 1]) != 0) {
24!
3057
        text[length - 1] = '\0';
12✔
3058
        length -= 1;
12✔
3059
    }
3060
}
4✔
3061

3062
/*
3063
 * thumbnailer_trim_left
3064
 *
3065
 * Skip leading whitespace so parsers can focus on significant tokens.
3066
 *
3067
 * Arguments:
3068
 *     text - string to inspect; may be NULL.
3069
 * Returns:
3070
 *     Pointer to first non-space character or NULL when input is NULL.
3071
 */
3072
static char *
3073
thumbnailer_trim_left(char *text)
12✔
3074
{
3075
    if (text == NULL) {
12!
3076
        return NULL;
×
3077
    }
3078

3079
    while (*text != '\0' && isspace((unsigned char)*text) != 0) {
12!
3080
        text += 1;
×
3081
    }
3082

3083
    return text;
12✔
3084
}
4✔
3085

3086
/*
3087
 * thumbnailer_parse_file
3088
 *
3089
 * Populate a thumbnailer_entry by parsing a .thumbnailer ini file.
3090
 *
3091
 * Arguments:
3092
 *     path  - filesystem path to the ini file.
3093
 *     entry - output structure initialized with thumbnailer_entry_init().
3094
 * Returns:
3095
 *     1 on success, 0 on parse error or allocation failure.
3096
 */
3097
static int
3098
thumbnailer_parse_file(char const *path, struct thumbnailer_entry *entry)
×
3099
{
3100
    FILE *fp;
3101
    char line[1024];
3102
    int in_group;
3103
    char *trimmed;
3104
    char *key_end;
3105
    char *value;
3106
    char *token_start;
3107
    char *token_end;
3108
    struct thumbnailer_string_list *mime_types;
3109
    size_t index;
3110

3111
    fp = NULL;
×
3112
    in_group = 0;
×
3113
    trimmed = NULL;
×
3114
    key_end = NULL;
×
3115
    value = NULL;
×
3116
    token_start = NULL;
×
3117
    token_end = NULL;
×
3118
    mime_types = NULL;
×
3119
    index = 0;
×
3120

3121
    if (path == NULL || entry == NULL) {
×
3122
        return 0;
×
3123
    }
3124

3125
    fp = fopen(path, "r");
×
3126
    if (fp == NULL) {
×
3127
        return 0;
×
3128
    }
3129

3130
    mime_types = thumbnailer_string_list_new();
×
3131
    if (mime_types == NULL) {
×
3132
        fclose(fp);
×
3133
        fp = NULL;
×
3134
        return 0;
×
3135
    }
3136

3137
    while (fgets(line, sizeof(line), fp) != NULL) {
×
3138
        trimmed = thumbnailer_trim_left(line);
×
3139
        thumbnailer_trim_right(trimmed);
×
3140
        if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') {
×
3141
            continue;
×
3142
        }
3143
        if (trimmed[0] == '[') {
×
3144
            key_end = strchr(trimmed, ']');
×
3145
            if (key_end != NULL) {
×
3146
                *key_end = '\0';
×
3147
                if (strcmp(trimmed + 1, "Thumbnailer Entry") == 0) {
×
3148
                    in_group = 1;
×
3149
                } else {
3150
                    in_group = 0;
×
3151
                }
3152
            }
3153
            continue;
×
3154
        }
3155
        if (!in_group) {
×
3156
            continue;
×
3157
        }
3158
        key_end = strchr(trimmed, '=');
×
3159
        if (key_end == NULL) {
×
3160
            continue;
×
3161
        }
3162
        *key_end = '\0';
×
3163
        value = thumbnailer_trim_left(key_end + 1);
×
3164
        thumbnailer_trim_right(trimmed);
×
3165
        thumbnailer_trim_right(value);
×
3166
        if (strcmp(trimmed, "Exec") == 0) {
×
3167
            free(entry->exec_line);
×
3168
            entry->exec_line = thumbnailer_strdup(value);
×
3169
            if (entry->exec_line == NULL) {
×
3170
                fclose(fp);
×
3171
                fp = NULL;
×
3172
                thumbnailer_string_list_free(mime_types);
×
3173
                mime_types = NULL;
×
3174
                return 0;
×
3175
            }
3176
        } else if (strcmp(trimmed, "TryExec") == 0) {
×
3177
            free(entry->tryexec);
×
3178
            entry->tryexec = thumbnailer_strdup(value);
×
3179
            if (entry->tryexec == NULL) {
×
3180
                fclose(fp);
×
3181
                fp = NULL;
×
3182
                thumbnailer_string_list_free(mime_types);
×
3183
                mime_types = NULL;
×
3184
                return 0;
×
3185
            }
3186
        } else if (strcmp(trimmed, "MimeType") == 0) {
×
3187
            for (index = 0; index < mime_types->length; ++index) {
×
3188
                free(mime_types->items[index]);
×
3189
                mime_types->items[index] = NULL;
×
3190
            }
3191
            mime_types->length = 0;
×
3192
            token_start = value;
×
3193
            while (token_start != NULL && token_start[0] != '\0') {
×
3194
                token_end = strchr(token_start, ';');
×
3195
                if (token_end != NULL) {
×
3196
                    *token_end = '\0';
×
3197
                }
3198
                token_start = thumbnailer_trim_left(token_start);
×
3199
                thumbnailer_trim_right(token_start);
×
3200
                if (token_start[0] != '\0') {
×
3201
                    if (!thumbnailer_string_list_append(mime_types,
×
3202
                                                       token_start)) {
3203
                        fclose(fp);
×
3204
                        fp = NULL;
×
3205
                        thumbnailer_string_list_free(mime_types);
×
3206
                        mime_types = NULL;
×
3207
                        return 0;
×
3208
                    }
3209
                }
3210
                if (token_end == NULL) {
×
3211
                    break;
×
3212
                }
3213
                token_start = token_end + 1;
×
3214
            }
3215
        }
3216
    }
3217

3218
    fclose(fp);
×
3219
    fp = NULL;
×
3220

3221
    thumbnailer_string_list_free(entry->mime_types);
×
3222
    entry->mime_types = mime_types;
×
3223

3224
    return 1;
×
3225
}
3226

3227
/*
3228
 * thumbnailer_has_tryexec
3229
 *
3230
 * Confirm that the optional TryExec binary exists and is executable.
3231
 *
3232
 * Arguments:
3233
 *     tryexec - value from the .thumbnailer file; may be NULL.
3234
 * Returns:
3235
 *     1 when executable, 0 otherwise.
3236
 */
3237
static int
3238
thumbnailer_has_tryexec(char const *tryexec)
×
3239
{
3240
    char const *path_variable;
3241
    char const *start;
3242
    char const *end;
3243
    size_t length;
3244
    char *candidate;
3245
    int executable;
3246

3247
    path_variable = NULL;
×
3248
    start = NULL;
×
3249
    end = NULL;
×
3250
    length = 0;
×
3251
    candidate = NULL;
×
3252
    executable = 0;
×
3253

3254
    if (tryexec == NULL || tryexec[0] == '\0') {
×
3255
        return 1;
×
3256
    }
3257

3258
    if (strchr(tryexec, '/') != NULL) {
×
3259
        if (access(tryexec, X_OK) == 0) {
×
3260
            return 1;
×
3261
        }
3262
        return 0;
×
3263
    }
3264

3265
    path_variable = getenv("PATH");
×
3266
    if (path_variable == NULL) {
×
3267
        return 0;
×
3268
    }
3269

3270
    start = path_variable;
×
3271
    while (*start != '\0') {
×
3272
        end = strchr(start, ':');
×
3273
        if (end == NULL) {
×
3274
            end = start + strlen(start);
×
3275
        }
3276
        length = (size_t)(end - start);
×
3277
        candidate = malloc(length + strlen(tryexec) + 2);
×
3278
        if (candidate == NULL) {
×
3279
            return 0;
×
3280
        }
3281
        memcpy(candidate, start, length);
×
3282
        candidate[length] = '/';
×
3283
        strcpy(candidate + length + 1, tryexec);
×
3284
        if (access(candidate, X_OK) == 0) {
×
3285
            executable = 1;
×
3286
            free(candidate);
×
3287
            candidate = NULL;
×
3288
            break;
×
3289
        }
3290
        free(candidate);
×
3291
        candidate = NULL;
×
3292
        if (*end == '\0') {
×
3293
            break;
×
3294
        }
3295
        start = end + 1;
×
3296
    }
3297

3298
    return executable;
×
3299
}
3300

3301
/*
3302
 * thumbnailer_mime_matches
3303
 *
3304
 * Test whether a thumbnailer MIME pattern matches the probed MIME type.
3305
 *
3306
 * Arguments:
3307
 *     pattern   - literal MIME pattern or prefix ending with "slash-asterisk".
3308
 *     mime_type - MIME value obtained from file --mime-type.
3309
 * Returns:
3310
 *     1 when the pattern applies, 0 otherwise.
3311
 */
3312
static int
3313
thumbnailer_mime_matches(char const *pattern, char const *mime_type)
×
3314
{
3315
    size_t length;
3316

3317
    length = 0;
×
3318

3319
    if (pattern == NULL || mime_type == NULL) {
×
3320
        return 0;
×
3321
    }
3322

3323
    if (strcmp(pattern, mime_type) == 0) {
×
3324
        return 1;
×
3325
    }
3326

3327
    length = strlen(pattern);
×
3328
    if (length >= 2 && pattern[length - 1] == '*' &&
×
3329
            pattern[length - 2] == '/') {
×
3330
        return strncmp(pattern, mime_type, length - 1) == 0;
×
3331
    }
3332

3333
    return 0;
×
3334
}
3335

3336
/*
3337
 * thumbnailer_supports_mime
3338
 *
3339
 * Iterate over MIME patterns advertised by a thumbnailer entry.
3340
 *
3341
 * Arguments:
3342
 *     entry     - parsed thumbnailer entry with mime_types list.
3343
 *     mime_type - MIME type string to match.
3344
 * Returns:
3345
 *     1 when a match is found, 0 otherwise.
3346
 */
3347
static int
3348
thumbnailer_supports_mime(struct thumbnailer_entry *entry,
×
3349
                          char const *mime_type)
3350
{
3351
    size_t index;
3352

3353
    index = 0;
×
3354

3355
    if (entry == NULL || entry->mime_types == NULL) {
×
3356
        return 0;
×
3357
    }
3358

3359
    if (mime_type == NULL) {
×
3360
        return 0;
×
3361
    }
3362

3363
    for (index = 0; index < entry->mime_types->length; ++index) {
×
3364
        if (thumbnailer_mime_matches(entry->mime_types->items[index],
×
3365
                                     mime_type)) {
3366
            return 1;
×
3367
        }
3368
    }
3369

3370
    return 0;
×
3371
}
3372

3373
/*
3374
 * thumbnailer_shell_quote
3375
 *
3376
 * Produce a single-quoted variant of an argument for readable logging.
3377
 *
3378
 * Arguments:
3379
 *     text - unquoted argument.
3380
 * Returns:
3381
 *     Newly allocated quoted string or NULL on allocation failure.
3382
 */
3383
static char *
3384
thumbnailer_shell_quote(char const *text)
×
3385
{
3386
    size_t index;
3387
    size_t length;
3388
    size_t needed;
3389
    char *quoted;
3390
    size_t position;
3391

3392
    index = 0;
×
3393
    length = 0;
×
3394
    needed = 0;
×
3395
    quoted = NULL;
×
3396
    position = 0;
×
3397

3398
    if (text == NULL) {
×
3399
        return NULL;
×
3400
    }
3401

3402
    length = strlen(text);
×
3403
    needed = 2;
×
3404
    for (index = 0; index < length; ++index) {
×
3405
        if (text[index] == '\'') {
×
3406
            needed += 4;
×
3407
        } else {
3408
            needed += 1;
×
3409
        }
3410
    }
3411

3412
    quoted = malloc(needed + 1);
×
3413
    if (quoted == NULL) {
×
3414
        return NULL;
×
3415
    }
3416

3417
    quoted[position++] = '\'';
×
3418
    for (index = 0; index < length; ++index) {
×
3419
        if (text[index] == '\'') {
×
3420
            quoted[position++] = '\'';
×
3421
            quoted[position++] = '\\';
×
3422
            quoted[position++] = '\'';
×
3423
            quoted[position++] = '\'';
×
3424
        } else {
3425
            quoted[position++] = text[index];
×
3426
        }
3427
    }
3428
    quoted[position++] = '\'';
×
3429
    quoted[position] = '\0';
×
3430

3431
    return quoted;
×
3432
}
3433

3434
struct thumbnailer_builder {
3435
    char *buffer;
3436
    size_t length;
3437
    size_t capacity;
3438
};
3439

3440
/*
3441
 * thumbnailer_builder_reserve
3442
 *
3443
 * Grow the builder buffer so future appends fit without overflow.
3444
 *
3445
 * Arguments:
3446
 *     builder    - mutable builder instance.
3447
 *     additional - number of bytes that must fit excluding terminator.
3448
 * Returns:
3449
 *     1 on success, 0 on allocation failure.
3450
 */
3451
static int
3452
thumbnailer_builder_reserve(struct thumbnailer_builder *builder,
×
3453
                            size_t additional)
3454
{
3455
    size_t new_capacity;
3456
    char *new_buffer;
3457

3458
    new_capacity = 0;
×
3459
    new_buffer = NULL;
×
3460

3461
    if (builder->length + additional + 1 <= builder->capacity) {
×
3462
        return 1;
×
3463
    }
3464

3465
    new_capacity = (builder->capacity == 0) ? 64 : builder->capacity;
×
3466
    while (new_capacity < builder->length + additional + 1) {
×
3467
        new_capacity *= 2;
×
3468
    }
3469

3470
    new_buffer = realloc(builder->buffer, new_capacity);
×
3471
    if (new_buffer == NULL) {
×
3472
        return 0;
×
3473
    }
3474

3475
    builder->buffer = new_buffer;
×
3476
    builder->capacity = new_capacity;
×
3477

3478
    return 1;
×
3479
}
3480

3481
/*
3482
 * thumbnailer_builder_append_char
3483
 *
3484
 * Append a single character to the builder.
3485
 *
3486
 * Arguments:
3487
 *     builder - mutable builder instance.
3488
 *     ch      - character to append.
3489
 * Returns:
3490
 *     1 on success, 0 on allocation failure.
3491
 */
3492
static int
3493
thumbnailer_builder_append_char(struct thumbnailer_builder *builder,
×
3494
                                char ch)
3495
{
3496
    if (!thumbnailer_builder_reserve(builder, 1)) {
×
3497
        return 0;
×
3498
    }
3499

3500
    builder->buffer[builder->length] = ch;
×
3501
    builder->length += 1;
×
3502
    builder->buffer[builder->length] = '\0';
×
3503

3504
    return 1;
×
3505
}
3506

3507
/*
3508
 * thumbnailer_builder_append
3509
 *
3510
 * Append a string of known length to the builder buffer.
3511
 *
3512
 * Arguments:
3513
 *     builder - mutable builder instance.
3514
 *     text    - zero-terminated string to append.
3515
 * Returns:
3516
 *     1 on success, 0 on allocation failure or NULL input.
3517
 */
3518
static int
3519
thumbnailer_builder_append(struct thumbnailer_builder *builder,
×
3520
                           char const *text)
3521
{
3522
    size_t length;
3523

3524
    length = 0;
×
3525

3526
    if (text == NULL) {
×
3527
        return 1;
×
3528
    }
3529

3530
    length = strlen(text);
×
3531
    if (!thumbnailer_builder_reserve(builder, length)) {
×
3532
        return 0;
×
3533
    }
3534

3535
    memcpy(builder->buffer + builder->length, text, length);
×
3536
    builder->length += length;
×
3537
    builder->buffer[builder->length] = '\0';
×
3538

3539
    return 1;
×
3540
}
3541

3542
/*
3543
 * thumbnailer_builder_clear
3544
 *
3545
 * Reset builder length to zero while retaining allocated storage.
3546
 *
3547
 * Arguments:
3548
 *     builder - builder to reset.
3549
 */
3550
static void
3551
thumbnailer_builder_clear(struct thumbnailer_builder *builder)
×
3552
{
3553
    if (builder->buffer != NULL) {
×
3554
        builder->buffer[0] = '\0';
×
3555
    }
3556
    builder->length = 0;
×
3557
}
×
3558

3559
/*
3560
 * thumbnailer_command owns the argv array that will be passed to the
3561
 * thumbnailer helper.  The display field keeps a human readable command line
3562
 * for verbose logging without recomputing the shell quoted form.
3563
 */
3564
struct thumbnailer_command {
3565
    char **argv;
3566
    size_t argc;
3567
    char *display;
3568
};
3569

3570
/*
3571
 * thumbnailer_command_free
3572
 *
3573
 * Release argv entries, the array itself, and the formatted display copy.
3574
 *
3575
 * Arguments:
3576
 *     command - structure created by thumbnailer_build_command().
3577
 */
3578
static void
3579
thumbnailer_command_free(struct thumbnailer_command *command)
×
3580
{
3581
    size_t index;
3582

3583
    if (command == NULL) {
×
3584
        return;
×
3585
    }
3586

3587
    if (command->argv != NULL) {
×
3588
        for (index = 0; index < command->argc; ++index) {
×
3589
            free(command->argv[index]);
×
3590
            command->argv[index] = NULL;
×
3591
        }
3592
        free(command->argv);
×
3593
        command->argv = NULL;
×
3594
    }
3595

3596
    free(command->display);
×
3597
    command->display = NULL;
×
3598

3599
    free(command);
×
3600
}
3601

3602
/*
3603
 * thumbnailer_command_format
3604
 *
3605
 * Join argv entries into a human-readable command line for logging.
3606
 *
3607
 * Arguments:
3608
 *     argv - array of argument strings.
3609
 *     argc - number of entries stored in argv.
3610
 * Returns:
3611
 *     Newly allocated formatted string or NULL on allocation failure.
3612
 */
3613
static char *
3614
thumbnailer_command_format(char **argv, size_t argc)
×
3615
{
3616
    struct thumbnailer_builder builder;
3617
    char *quoted;
3618
    size_t index;
3619

3620
    builder.buffer = NULL;
×
3621
    builder.length = 0;
×
3622
    builder.capacity = 0;
×
3623
    quoted = NULL;
×
3624

3625
    for (index = 0; index < argc; ++index) {
×
3626
        if (index > 0) {
×
3627
            if (!thumbnailer_builder_append_char(&builder, ' ')) {
×
3628
                free(builder.buffer);
×
3629
                builder.buffer = NULL;
×
3630
                return NULL;
×
3631
            }
3632
        }
3633
        quoted = thumbnailer_shell_quote(argv[index]);
×
3634
        if (quoted == NULL) {
×
3635
            free(builder.buffer);
×
3636
            builder.buffer = NULL;
×
3637
            return NULL;
×
3638
        }
3639
        if (!thumbnailer_builder_append(&builder, quoted)) {
×
3640
            free(quoted);
×
3641
            quoted = NULL;
×
3642
            free(builder.buffer);
×
3643
            builder.buffer = NULL;
×
3644
            return NULL;
×
3645
        }
3646
        free(quoted);
×
3647
        quoted = NULL;
×
3648
    }
3649

3650
    return builder.buffer;
×
3651
}
3652

3653
/*
3654
 * thumbnailer_build_command
3655
 *
3656
 * Expand a .thumbnailer Exec template into an argv array that honours
3657
 * FreeDesktop substitution rules.
3658
 *
3659
 * Arguments:
3660
 *     template_command - Exec line containing % tokens.
3661
 *     input_path       - filesystem path to the source document.
3662
 *     input_uri        - URI representation for %u expansions.
3663
 *     output_path      - PNG destination path for %o expansions.
3664
 *     size             - numeric size hint passed to %s tokens.
3665
 *     mime_type        - MIME value for %m replacements.
3666
 * Returns:
3667
 *     Newly allocated command or NULL on parse/allocation failure.
3668
 */
3669
static struct thumbnailer_command *
3670
thumbnailer_build_command(char const *template_command,
×
3671
                          char const *input_path,
3672
                          char const *input_uri,
3673
                          char const *output_path,
3674
                          int size,
3675
                          char const *mime_type)
3676
{
3677
    struct thumbnailer_builder builder;
3678
    struct thumbnailer_string_list *tokens;
3679
    struct thumbnailer_command *command;
3680
    char const *ptr;
3681
    char size_text[16];
3682
    int in_single_quote;
3683
    int in_double_quote;
3684
    int escape_next;
3685
    char const *replacement;
3686
    size_t index;
3687
    int written;
3688

3689
    builder.buffer = NULL;
×
3690
    builder.length = 0;
×
3691
    builder.capacity = 0;
×
3692
    tokens = NULL;
×
3693
    command = NULL;
×
3694
    ptr = template_command;
×
3695
    size_text[0] = '\0';
×
3696
    in_single_quote = 0;
×
3697
    in_double_quote = 0;
×
3698
    escape_next = 0;
×
3699
    replacement = NULL;
×
3700
    index = 0;
×
3701

3702
    if (template_command == NULL) {
×
3703
        return NULL;
×
3704
    }
3705

3706
    tokens = thumbnailer_string_list_new();
×
3707
    if (tokens == NULL) {
×
3708
        return NULL;
×
3709
    }
3710

3711
    if (size > 0) {
×
3712
        written = sixel_compat_snprintf(size_text,
×
3713
                                        sizeof(size_text),
3714
                                        "%d",
3715
                                        size);
3716
        if (written < 0) {
×
3717
            goto error;
×
3718
        }
3719
        if ((size_t)written >= sizeof(size_text)) {
×
3720
            size_text[sizeof(size_text) - 1u] = '\0';
×
3721
        }
3722
    }
3723

3724
    while (ptr != NULL && ptr[0] != '\0') {
×
3725
        if (!in_single_quote && !in_double_quote && escape_next == 0 &&
×
3726
                (ptr[0] == ' ' || ptr[0] == '\t')) {
×
3727
            if (builder.length > 0) {
×
3728
                if (!thumbnailer_string_list_append(tokens,
×
3729
                                                    builder.buffer)) {
×
3730
                    goto error;
×
3731
                }
3732
                thumbnailer_builder_clear(&builder);
×
3733
            }
3734
            ptr += 1;
×
3735
            continue;
×
3736
        }
3737
        if (!in_single_quote && escape_next == 0 && ptr[0] == '\\') {
×
3738
            escape_next = 1;
×
3739
            ptr += 1;
×
3740
            continue;
×
3741
        }
3742
        if (!in_double_quote && escape_next == 0 && ptr[0] == '\'') {
×
3743
            in_single_quote = !in_single_quote;
×
3744
            ptr += 1;
×
3745
            continue;
×
3746
        }
3747
        if (!in_single_quote && escape_next == 0 && ptr[0] == '"') {
×
3748
            in_double_quote = !in_double_quote;
×
3749
            ptr += 1;
×
3750
            continue;
×
3751
        }
3752
        if (escape_next != 0) {
×
3753
            if (!thumbnailer_builder_append_char(&builder, ptr[0])) {
×
3754
                goto error;
×
3755
            }
3756
            escape_next = 0;
×
3757
            ptr += 1;
×
3758
            continue;
×
3759
        }
3760
        if (ptr[0] == '%' && ptr[1] != '\0') {
×
3761
            replacement = NULL;
×
3762
            ptr += 1;
×
3763
            switch (ptr[0]) {
×
3764
            case '%':
3765
                if (!thumbnailer_builder_append_char(&builder, '%')) {
×
3766
                    goto error;
×
3767
                }
3768
                break;
×
3769
            case 'i':
3770
            case 'I':
3771
                replacement = input_path;
×
3772
                break;
×
3773
            case 'u':
3774
            case 'U':
3775
                replacement = input_uri;
×
3776
                break;
×
3777
            case 'o':
3778
            case 'O':
3779
                replacement = output_path;
×
3780
                break;
×
3781
            case 's':
3782
            case 'S':
3783
                replacement = size_text;
×
3784
                break;
×
3785
            case 'm':
3786
            case 'M':
3787
                replacement = mime_type;
×
3788
                break;
×
3789
            default:
3790
                if (!thumbnailer_builder_append_char(&builder, '%') ||
×
3791
                        !thumbnailer_builder_append_char(&builder,
×
3792
                                                         ptr[0])) {
×
3793
                    goto error;
×
3794
                }
3795
                break;
×
3796
            }
3797
            if (replacement != NULL) {
×
3798
                if (!thumbnailer_builder_append(&builder, replacement)) {
×
3799
                    goto error;
×
3800
                }
3801
            }
3802
            ptr += 1;
×
3803
            continue;
×
3804
        }
3805
        if (!thumbnailer_builder_append_char(&builder, ptr[0])) {
×
3806
            goto error;
×
3807
        }
3808
        ptr += 1;
×
3809
    }
3810

3811
    if (builder.length > 0) {
×
3812
        if (!thumbnailer_string_list_append(tokens, builder.buffer)) {
×
3813
            goto error;
×
3814
        }
3815
    }
3816

3817
    command = malloc(sizeof(*command));
×
3818
    if (command == NULL) {
×
3819
        goto error;
×
3820
    }
3821

3822
    command->argc = tokens->length;
×
3823
    command->argv = NULL;
×
3824
    command->display = NULL;
×
3825

3826
    if (tokens->length == 0) {
×
3827
        goto error;
×
3828
    }
3829

3830
    command->argv = malloc(sizeof(char *) * (tokens->length + 1));
×
3831
    if (command->argv == NULL) {
×
3832
        goto error;
×
3833
    }
3834

3835
    for (index = 0; index < tokens->length; ++index) {
×
3836
        command->argv[index] = thumbnailer_strdup(tokens->items[index]);
×
3837
        if (command->argv[index] == NULL) {
×
3838
            goto error;
×
3839
        }
3840
    }
3841
    command->argv[tokens->length] = NULL;
×
3842

3843
    command->display = thumbnailer_command_format(command->argv,
×
3844
                                                  command->argc);
3845
    if (command->display == NULL) {
×
3846
        goto error;
×
3847
    }
3848

3849
    thumbnailer_string_list_free(tokens);
×
3850
    tokens = NULL;
×
3851
    if (builder.buffer != NULL) {
×
3852
        free(builder.buffer);
×
3853
        builder.buffer = NULL;
×
3854
    }
3855

3856
    return command;
×
3857

3858
error:
3859
    if (tokens != NULL) {
×
3860
        thumbnailer_string_list_free(tokens);
×
3861
        tokens = NULL;
×
3862
    }
3863
    if (builder.buffer != NULL) {
×
3864
        free(builder.buffer);
×
3865
        builder.buffer = NULL;
×
3866
    }
3867
    if (command != NULL) {
×
3868
        thumbnailer_command_free(command);
×
3869
        command = NULL;
×
3870
    }
3871

3872
    return NULL;
×
3873
}
3874

3875
/*
3876
 * thumbnailer_is_evince_thumbnailer
3877
 *
3878
 * Detect whether the selected thumbnailer maps to evince-thumbnailer so
3879
 * the stdout redirection workaround can be applied.
3880
 *
3881
 * Arguments:
3882
 *     exec_line - Exec string parsed from the .thumbnailer file.
3883
 *     tryexec   - optional TryExec value for additional matching.
3884
 * Returns:
3885
 *     1 when evince-thumbnailer is referenced, 0 otherwise.
3886
 */
3887
static int
3888
thumbnailer_is_evince_thumbnailer(char const *exec_line,
×
3889
                                  char const *tryexec)
3890
{
3891
    char const *needle;
3892
    char const *basename;
3893

3894
    needle = "evince-thumbnailer";
×
3895
    basename = NULL;
×
3896

3897
    if (exec_line != NULL && strstr(exec_line, needle) != NULL) {
×
3898
        return 1;
×
3899
    }
3900

3901
    if (tryexec != NULL) {
×
3902
        basename = strrchr(tryexec, '/');
×
3903
        if (basename != NULL) {
×
3904
            basename += 1;
×
3905
        } else {
3906
            basename = tryexec;
×
3907
        }
3908
        if (strcmp(basename, needle) == 0) {
×
3909
            return 1;
×
3910
        }
3911
        if (strstr(tryexec, needle) != NULL) {
×
3912
            return 1;
×
3913
        }
3914
    }
3915

3916
    return 0;
×
3917
}
3918

3919
/*
3920
 * thumbnailer_build_evince_command
3921
 *
3922
 * Construct an argv sequence that streams evince-thumbnailer output to
3923
 * stdout so downstream code can capture the PNG safely.
3924
 *
3925
 * Arguments:
3926
 *     input_path - source document path.
3927
 *     size       - numeric size hint forwarded to the -s option.
3928
 * Returns:
3929
 *     Newly allocated command or NULL on allocation failure.
3930
 */
3931
static struct thumbnailer_command *
3932
thumbnailer_build_evince_command(char const *input_path,
×
3933
                                 int size)
3934
{
3935
    struct thumbnailer_command *command;
3936
    char size_text[16];
3937
    size_t index;
3938
    int written;
3939

3940
    command = NULL;
×
3941
    index = 0;
×
3942

3943
    if (input_path == NULL) {
×
3944
        return NULL;
×
3945
    }
3946

3947
    command = malloc(sizeof(*command));
×
3948
    if (command == NULL) {
×
3949
        return NULL;
×
3950
    }
3951

3952
    command->argc = 5;
×
3953
    command->argv = malloc(sizeof(char *) * (command->argc + 1));
×
3954
    if (command->argv == NULL) {
×
3955
        thumbnailer_command_free(command);
×
3956
        return NULL;
×
3957
    }
3958

3959
    for (index = 0; index < command->argc + 1; ++index) {
×
3960
        command->argv[index] = NULL;
×
3961
    }
3962

3963
    written = sixel_compat_snprintf(size_text,
×
3964
                                    sizeof(size_text),
3965
                                    "%d",
3966
                                    size);
3967
    if (written < 0) {
×
3968
        size_text[0] = '\0';
×
3969
    } else if ((size_t)written >= sizeof(size_text)) {
×
3970
        size_text[sizeof(size_text) - 1u] = '\0';
×
3971
    }
3972

3973
    command->argv[0] = thumbnailer_strdup("evince-thumbnailer");
×
3974
    command->argv[1] = thumbnailer_strdup("-s");
×
3975
    command->argv[2] = thumbnailer_strdup(size_text);
×
3976
    command->argv[3] = thumbnailer_strdup(input_path);
×
3977
    command->argv[4] = thumbnailer_strdup("/dev/stdout");
×
3978
    command->argv[5] = NULL;
×
3979

3980
    for (index = 0; index < command->argc; ++index) {
×
3981
        if (command->argv[index] == NULL) {
×
3982
            thumbnailer_command_free(command);
×
3983
            return NULL;
×
3984
        }
3985
    }
3986

3987
    command->display = thumbnailer_command_format(command->argv,
×
3988
                                                  command->argc);
3989
    if (command->display == NULL) {
×
3990
        thumbnailer_command_free(command);
×
3991
        return NULL;
×
3992
    }
3993

3994
    return command;
×
3995
}
3996

3997
/*
3998
 * thumbnailer_build_file_uri
3999
 *
4000
 * Convert a filesystem path into a percent-encoded file:// URI.
4001
 *
4002
 * Arguments:
4003
 *     path - filesystem path; may be relative but will be resolved.
4004
 * Returns:
4005
 *     Newly allocated URI string or NULL on error.
4006
 */
4007
static char *
4008
thumbnailer_build_file_uri(char const *path)
×
4009
{
4010
    char *resolved;
4011
    size_t index;
4012
    size_t length;
4013
    size_t needed;
4014
    char *uri;
4015
    size_t position;
4016
    char const hex[] = "0123456789ABCDEF";
×
4017

4018
    resolved = NULL;
×
4019
    index = 0;
×
4020
    length = 0;
×
4021
    needed = 0;
×
4022
    uri = NULL;
×
4023
    position = 0;
×
4024

4025
    if (path == NULL) {
×
4026
        return NULL;
×
4027
    }
4028

4029
    resolved = thumbnailer_resolve_path(path);
×
4030
    if (resolved == NULL) {
×
4031
        return NULL;
×
4032
    }
4033

4034
    length = strlen(resolved);
×
4035
    needed = 7;
×
4036
    for (index = 0; index < length; ++index) {
×
4037
        unsigned char ch;
4038

4039
        ch = (unsigned char)resolved[index];
×
4040
        if (isalnum(ch) || ch == '-' || ch == '_' ||
×
4041
                ch == '.' || ch == '~' || ch == '/') {
×
4042
            needed += 1;
×
4043
        } else {
4044
            needed += 3;
×
4045
        }
4046
    }
4047

4048
    uri = malloc(needed + 1);
×
4049
    if (uri == NULL) {
×
4050
        free(resolved);
×
4051
        resolved = NULL;
×
4052
        return NULL;
×
4053
    }
4054

4055
    memcpy(uri, "file://", 7);
×
4056
    position = 7;
×
4057
    for (index = 0; index < length; ++index) {
×
4058
        unsigned char ch;
4059

4060
        ch = (unsigned char)resolved[index];
×
4061
        if (isalnum(ch) || ch == '-' || ch == '_' ||
×
4062
                ch == '.' || ch == '~' || ch == '/') {
×
4063
            uri[position++] = (char)ch;
×
4064
        } else {
4065
            uri[position++] = '%';
×
4066
            uri[position++] = hex[(ch >> 4) & 0xF];
×
4067
            uri[position++] = hex[ch & 0xF];
×
4068
        }
4069
    }
4070
    uri[position] = '\0';
×
4071

4072
    free(resolved);
×
4073
    resolved = NULL;
×
4074

4075
    return uri;
×
4076
}
4077

4078
/*
4079
 * thumbnailer_run_file
4080
 *
4081
 * Invoke the file(1) utility to collect metadata about the input path.
4082
 *
4083
 * Arguments:
4084
 *     path   - filesystem path forwarded to file(1).
4085
 *     option - optional argument appended after "-b".  Pass NULL to obtain
4086
 *              the human readable description and "--mime-type" for the
4087
 *              MIME identifier.
4088
 * Returns:
4089
 *     Newly allocated string trimmed of trailing whitespace or NULL on
4090
 *     failure.
4091
 */
4092
static char *
4093
thumbnailer_run_file(char const *path, char const *option)
12✔
4094
{
4095
    int pipefd[2];
4096
    pid_t pid;
4097
    ssize_t bytes_read;
4098
    char buffer[256];
4099
    size_t total;
4100
    int status;
4101
    char *result;
4102
    char *trimmed;
4103

4104
    pipefd[0] = -1;
12✔
4105
    pipefd[1] = -1;
12✔
4106
    pid = (-1);
12✔
4107
    bytes_read = 0;
12✔
4108
    total = 0;
12✔
4109
    status = 0;
12✔
4110
    result = NULL;
12✔
4111
    trimmed = NULL;
12✔
4112

4113
    if (path == NULL) {
12!
4114
        return NULL;
×
4115
    }
4116

4117
    if (pipe(pipefd) < 0) {
12!
4118
        return NULL;
×
4119
    }
4120

4121
    pid = fork();
12✔
4122
    if (pid < 0) {
20!
4123
        close(pipefd[0]);
×
4124
        close(pipefd[1]);
×
4125
        return NULL;
×
4126
    }
4127

4128
    if (pid == 0) {
24✔
4129
        char const *argv[6];
4130
        size_t arg_index;
4131

4132
        close(pipefd[0]);
12✔
4133
        if (dup2(pipefd[1], STDOUT_FILENO) < 0) {
12!
4134
            _exit(127);
×
4135
        }
4136
        close(pipefd[1]);
12✔
4137
        arg_index = 0u;
12✔
4138
        argv[arg_index++] = "file";
12✔
4139
        argv[arg_index++] = "-b";
12✔
4140
        if (option != NULL) {
12✔
4141
            argv[arg_index++] = option;
6✔
4142
        }
2✔
4143
        argv[arg_index++] = path;
12✔
4144
        argv[arg_index] = NULL;
12✔
4145
        execvp("file", (char * const *)argv);
12✔
4146
        _exit(127);
8✔
4147
    }
4148

4149
    close(pipefd[1]);
12✔
4150
    pipefd[1] = -1;
12✔
4151
    total = 0;
12✔
4152
    while ((bytes_read = read(pipefd[0], buffer + total,
24✔
4153
                              sizeof(buffer) - total - 1)) > 0) {
32✔
4154
        total += (size_t)bytes_read;
12✔
4155
        if (total >= sizeof(buffer) - 1) {
12!
4156
            break;
×
4157
        }
4158
    }
4159
    buffer[total] = '\0';
12✔
4160
    close(pipefd[0]);
12✔
4161
    pipefd[0] = -1;
12✔
4162

4163
    while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {
12!
4164
        continue;
×
4165
    }
4166

4167
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
12!
4168
        return NULL;
×
4169
    }
4170

4171
    trimmed = thumbnailer_trim_left(buffer);
12✔
4172
    thumbnailer_trim_right(trimmed);
12✔
4173
    if (trimmed[0] == '\0') {
12!
4174
        return NULL;
×
4175
    }
4176

4177
    result = thumbnailer_strdup(trimmed);
12✔
4178

4179
    return result;
12✔
4180
}
4✔
4181

4182
/*
4183
 * thumbnailer_guess_content_type
4184
 *
4185
 * Obtain the MIME identifier for the supplied path using file(1).
4186
 */
4187
static char *
4188
thumbnailer_guess_content_type(char const *path)
6✔
4189
{
4190
    return thumbnailer_run_file(path, "--mime-type");
6✔
4191
}
4192

4193
/*
4194
 * Thumbnailer supervision overview:
4195
 *
4196
 *     +-------------------+    pipe(stderr)    +-------------------+
4197
 *     | libsixel parent   | <----------------- | thumbnailer argv  |
4198
 *     |  - polls stdout   |                   |  - renders PNG     |
4199
 *     |  - polls stderr   | -----------------> |  - returns code   |
4200
 *     |  - waits pid      |    pipe(stdout)    |                   |
4201
 *     +-------------------+  posix_spawn/fork  +-------------------+
4202
 *
4203
 * Non-blocking pipes keep verbose thumbnailers from stalling the loop,
4204
 * and argv arrays mean Exec templates never pass through /bin/sh.
4205
 *
4206
 * thumbnailer_spawn is responsible for preparing pipes, launching the
4207
 * thumbnail helper, and streaming any emitted data back into libsixel.
4208
 *
4209
 *  - stderr is captured into stderr_output so verbose mode can replay the
4210
 *    diagnostics without leaking them to non-verbose invocations.
4211
 *  - stdout can optionally be redirected into a temporary file when
4212
 *    thumbnailers insist on writing image data to the standard output stream.
4213
 *  - All file descriptors are placed into non-blocking mode to avoid stalls
4214
 *    while the parent waits for the child process to complete.
4215
 * Arguments:
4216
 *     command          - prepared argv array to execute.
4217
 *     thumbnailer_name - friendly name used in log messages.
4218
 *     log_prefix       - identifier describing the current pipeline step.
4219
 *     capture_stdout   - non-zero to capture stdout into stdout_path.
4220
 *     stdout_path      - file receiving stdout when capture_stdout != 0.
4221
 * Returns:
4222
 *     SIXEL_OK on success or a libsixel error code on failure.
4223
 */
4224
static SIXELSTATUS
4225
thumbnailer_spawn(struct thumbnailer_command const *command,
×
4226
                  char const *thumbnailer_name,
4227
                  char const *log_prefix,
4228
                  int capture_stdout,
4229
                  char const *stdout_path)
4230
{
4231
    pid_t pid;
4232
    int status_code;
4233
    int wait_result;
4234
    SIXELSTATUS status;
4235
    char message[256];
4236
    int stderr_pipe[2];
4237
    int stdout_pipe[2];
4238
    int stderr_pipe_created;
4239
    int stdout_pipe_created;
4240
    int flags;
4241
    ssize_t read_result;
4242
    ssize_t stdout_read_result;
4243
    char stderr_buffer[256];
4244
    char stdout_buffer[4096];
4245
    char *stderr_output;
4246
    size_t stderr_length;
4247
    size_t stderr_capacity;
4248
    int child_exited;
4249
    int stderr_open;
4250
    int stdout_open;
4251
    int have_status;
4252
    int fatal_error;
4253
    int output_fd;
4254
    size_t write_offset;
4255
    ssize_t write_result;
4256
    size_t to_write;
4257
    char const *display_command;
4258
    int written;
4259
# if HAVE_POSIX_SPAWNP
4260
    posix_spawn_file_actions_t actions;
4261
    int spawn_result;
4262
# endif
4263

4264
    pid = (-1);
×
4265
    status_code = 0;
×
4266
    wait_result = 0;
×
4267
    status = SIXEL_RUNTIME_ERROR;
×
4268
    memset(message, 0, sizeof(message));
×
4269
    stderr_pipe[0] = -1;
×
4270
    stderr_pipe[1] = -1;
×
4271
    stdout_pipe[0] = -1;
×
4272
    stdout_pipe[1] = -1;
×
4273
    stderr_pipe_created = 0;
×
4274
    stdout_pipe_created = 0;
×
4275
    flags = 0;
×
4276
    read_result = 0;
×
4277
    stdout_read_result = 0;
×
4278
    stderr_output = NULL;
×
4279
    stderr_length = 0;
×
4280
    stderr_capacity = 0;
×
4281
    child_exited = 0;
×
4282
    stderr_open = 0;
×
4283
    stdout_open = 0;
×
4284
    have_status = 0;
×
4285
    fatal_error = 0;
×
4286
    output_fd = -1;
×
4287
    write_offset = 0;
×
4288
    write_result = 0;
×
4289
    to_write = 0;
×
4290
    display_command = NULL;
×
4291
# if HAVE_POSIX_SPAWNP
4292
    spawn_result = 0;
×
4293
# endif
4294

4295
    if (command == NULL || command->argv == NULL ||
×
4296
            command->argv[0] == NULL) {
×
4297
        return SIXEL_BAD_ARGUMENT;
×
4298
    }
4299

4300
    if (capture_stdout && stdout_path == NULL) {
×
4301
        return SIXEL_BAD_ARGUMENT;
×
4302
    }
4303

4304
    if (capture_stdout) {
×
4305
        output_fd = open(stdout_path,
×
4306
                         O_WRONLY | O_CREAT | O_TRUNC,
4307
                         0600);
4308
        if (output_fd < 0) {
×
4309
            written = sixel_compat_snprintf(message,
×
4310
                                            sizeof(message),
4311
                                            "%s: open(%s) failed (%s).",
4312
                                            log_prefix,
4313
                                            stdout_path,
4314
                                            strerror(errno));
×
4315
            thumbnailer_message_finalize(message,
×
4316
                                         sizeof(message),
4317
                                         written);
4318
            sixel_helper_set_additional_message(message);
×
4319
            goto cleanup;
×
4320
        }
4321
    }
4322

4323
    /* stderr is collected even for successful runs so verbose users can see
4324
     * the thumbnailer's own commentary.  Failing to set the pipe is not
4325
     * fatal; we continue without stderr capture in that case.
4326
     */
4327
    if (pipe(stderr_pipe) == 0) {
×
4328
        stderr_pipe_created = 1;
×
4329
        stderr_open = 1;
×
4330
    }
4331

4332
    if (capture_stdout) {
×
4333
        if (pipe(stdout_pipe) != 0) {
×
4334
            written = sixel_compat_snprintf(
×
4335
                message,
4336
                sizeof(message),
4337
                "%s: pipe() for stdout failed (%s).",
4338
                log_prefix,
4339
                strerror(errno));
×
4340
            thumbnailer_message_finalize(message,
×
4341
                                         sizeof(message),
4342
                                         written);
4343
            sixel_helper_set_additional_message(message);
×
4344
            goto cleanup;
×
4345
        }
4346
        stdout_pipe_created = 1;
×
4347
        stdout_open = 1;
×
4348
    }
4349

4350
    display_command = (command->display != NULL) ?
×
4351
            command->display : command->argv[0];
×
4352
    loader_trace_message("%s: executing %s",
×
4353
                         log_prefix,
4354
                         display_command);
4355

4356
# if HAVE_POSIX_SPAWNP
4357
    if (posix_spawn_file_actions_init(&actions) != 0) {
×
4358
        written = sixel_compat_snprintf(
×
4359
            message,
4360
            sizeof(message),
4361
            "%s: posix_spawn_file_actions_init() failed.",
4362
            log_prefix);
4363
        thumbnailer_message_finalize(message,
×
4364
                                     sizeof(message),
4365
                                     written);
4366
        sixel_helper_set_additional_message(message);
×
4367
        goto cleanup;
×
4368
    }
4369
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
×
4370
        (void)posix_spawn_file_actions_adddup2(&actions,
×
4371
                                               stderr_pipe[1],
4372
                                               STDERR_FILENO);
4373
        (void)posix_spawn_file_actions_addclose(&actions,
×
4374
                                                stderr_pipe[0]);
4375
        (void)posix_spawn_file_actions_addclose(&actions,
×
4376
                                                stderr_pipe[1]);
4377
    }
4378
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
×
4379
        (void)posix_spawn_file_actions_adddup2(&actions,
×
4380
                                               stdout_pipe[1],
4381
                                               STDOUT_FILENO);
4382
        (void)posix_spawn_file_actions_addclose(&actions,
×
4383
                                                stdout_pipe[0]);
4384
        (void)posix_spawn_file_actions_addclose(&actions,
×
4385
                                                stdout_pipe[1]);
4386
    }
4387
    if (output_fd >= 0) {
×
4388
        (void)posix_spawn_file_actions_addclose(&actions,
×
4389
                                                output_fd);
4390
    }
4391
    spawn_result = posix_spawnp(&pid,
×
4392
                                command->argv[0],
×
4393
                                &actions,
4394
                                NULL,
4395
                                (char * const *)command->argv,
×
4396
                                environ);
4397
    posix_spawn_file_actions_destroy(&actions);
×
4398
    if (spawn_result != 0) {
×
4399
        written = sixel_compat_snprintf(message,
×
4400
                                        sizeof(message),
4401
                                        "%s: posix_spawnp() failed (%s).",
4402
                                        log_prefix,
4403
                                        strerror(spawn_result));
4404
        thumbnailer_message_finalize(message,
×
4405
                                     sizeof(message),
4406
                                     written);
4407
        sixel_helper_set_additional_message(message);
×
4408
        goto cleanup;
×
4409
    }
4410
# else
4411
    pid = fork();
4412
    if (pid < 0) {
4413
        written = sixel_compat_snprintf(message,
4414
                                        sizeof(message),
4415
                                        "%s: fork() failed (%s).",
4416
                                        log_prefix,
4417
                                        strerror(errno));
4418
        thumbnailer_message_finalize(message,
4419
                                     sizeof(message),
4420
                                     written);
4421
        sixel_helper_set_additional_message(message);
4422
        goto cleanup;
4423
    }
4424
    if (pid == 0) {
4425
        if (stderr_pipe_created && stderr_pipe[1] >= 0) {
4426
            if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) {
4427
                _exit(127);
4428
            }
4429
        }
4430
        if (stdout_pipe_created && stdout_pipe[1] >= 0) {
4431
            if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) {
4432
                _exit(127);
4433
            }
4434
        }
4435
        if (stderr_pipe[0] >= 0) {
4436
            close(stderr_pipe[0]);
4437
        }
4438
        if (stderr_pipe[1] >= 0) {
4439
            close(stderr_pipe[1]);
4440
        }
4441
        if (stdout_pipe[0] >= 0) {
4442
            close(stdout_pipe[0]);
4443
        }
4444
        if (stdout_pipe[1] >= 0) {
4445
            close(stdout_pipe[1]);
4446
        }
4447
        if (output_fd >= 0) {
4448
            close(output_fd);
4449
        }
4450
        execvp(command->argv[0], (char * const *)command->argv);
4451
        _exit(127);
4452
    }
4453
# endif
4454

4455
    loader_trace_message("%s: forked child pid=%ld",
×
4456
                         log_prefix,
4457
                         (long)pid);
4458

4459
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
×
4460
        close(stderr_pipe[1]);
×
4461
        stderr_pipe[1] = -1;
×
4462
    }
4463
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
×
4464
        close(stdout_pipe[1]);
×
4465
        stdout_pipe[1] = -1;
×
4466
    }
4467

4468
    if (stderr_pipe_created && stderr_pipe[0] >= 0) {
×
4469
        flags = fcntl(stderr_pipe[0], F_GETFL, 0);
×
4470
        if (flags >= 0) {
×
4471
            (void)fcntl(stderr_pipe[0],
×
4472
                        F_SETFL,
4473
                        flags | O_NONBLOCK);
4474
        }
4475
    }
4476
    if (stdout_pipe_created && stdout_pipe[0] >= 0) {
×
4477
        flags = fcntl(stdout_pipe[0], F_GETFL, 0);
×
4478
        if (flags >= 0) {
×
4479
            (void)fcntl(stdout_pipe[0],
×
4480
                        F_SETFL,
4481
                        flags | O_NONBLOCK);
4482
        }
4483
    }
4484

4485
    /* The monitoring loop drains stderr/stdout as long as any descriptor is
4486
     * open.  Non-blocking reads ensure the parent does not deadlock if the
4487
     * child process stalls or writes data in bursts.
4488
     */
4489
    while (!child_exited || stderr_open || stdout_open) {
×
4490
        if (stderr_pipe_created && stderr_open) {
×
4491
            read_result = read(stderr_pipe[0],
×
4492
                               stderr_buffer,
4493
                               (ssize_t)sizeof(stderr_buffer));
4494
            if (read_result > 0) {
×
4495
                if (stderr_length + (size_t)read_result + 1 >
×
4496
                        stderr_capacity) {
4497
                    size_t new_capacity;
4498
                    char *new_output;
4499

4500
                    new_capacity = stderr_capacity;
×
4501
                    if (new_capacity == 0) {
×
4502
                        new_capacity = 256;
×
4503
                    }
4504
                    while (stderr_length + (size_t)read_result + 1 >
×
4505
                            new_capacity) {
4506
                        new_capacity *= 2U;
×
4507
                    }
4508
                    new_output = realloc(stderr_output, new_capacity);
×
4509
                    if (new_output == NULL) {
×
4510
                        free(stderr_output);
×
4511
                        stderr_output = NULL;
×
4512
                        stderr_capacity = 0;
×
4513
                        stderr_length = 0;
×
4514
                        stderr_open = 0;
×
4515
                        if (stderr_pipe[0] >= 0) {
×
4516
                            close(stderr_pipe[0]);
×
4517
                            stderr_pipe[0] = -1;
×
4518
                        }
4519
                        stderr_pipe_created = 0;
×
4520
                        written = sixel_compat_snprintf(message,
×
4521
                                                        sizeof(message),
4522
                                                        "%s: realloc() failed.",
4523
                                                        log_prefix);
4524
                        thumbnailer_message_finalize(message,
×
4525
                                                     sizeof(message),
4526
                                                     written);
4527
                        sixel_helper_set_additional_message(message);
×
4528
                        status = SIXEL_BAD_ALLOCATION;
×
4529
                        fatal_error = 1;
×
4530
                        break;
×
4531
                    }
4532
                    stderr_output = new_output;
×
4533
                    stderr_capacity = new_capacity;
×
4534
                }
4535
                memcpy(stderr_output + stderr_length,
×
4536
                       stderr_buffer,
4537
                       (size_t)read_result);
4538
                stderr_length += (size_t)read_result;
×
4539
                stderr_output[stderr_length] = '\0';
×
4540
            } else if (read_result == 0) {
×
4541
                stderr_open = 0;
×
4542
                if (stderr_pipe[0] >= 0) {
×
4543
                    close(stderr_pipe[0]);
×
4544
                    stderr_pipe[0] = -1;
×
4545
                }
4546
                stderr_pipe_created = 0;
×
4547
            } else if (errno == EINTR) {
×
4548
                /* retry */
4549
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4550
                /* no data */
4551
            } else {
4552
                written = sixel_compat_snprintf(message,
×
4553
                                                sizeof(message),
4554
                                                "%s: read() failed (%s).",
4555
                                                log_prefix,
4556
                                                strerror(errno));
×
4557
                thumbnailer_message_finalize(message,
×
4558
                                             sizeof(message),
4559
                                             written);
4560
                sixel_helper_set_additional_message(message);
×
4561
                stderr_open = 0;
×
4562
                if (stderr_pipe[0] >= 0) {
×
4563
                    close(stderr_pipe[0]);
×
4564
                    stderr_pipe[0] = -1;
×
4565
                }
4566
                stderr_pipe_created = 0;
×
4567
            }
4568
        }
4569

4570
        if (stdout_pipe_created && stdout_open) {
×
4571
            stdout_read_result = read(stdout_pipe[0],
×
4572
                                      stdout_buffer,
4573
                                      (ssize_t)sizeof(stdout_buffer));
4574
            if (stdout_read_result > 0) {
×
4575
                write_offset = 0;
×
4576
                while (write_offset < (size_t)stdout_read_result) {
×
4577
                    to_write = (size_t)stdout_read_result - write_offset;
×
4578
                    write_result = write(output_fd,
×
4579
                                          stdout_buffer + write_offset,
4580
                                          to_write);
4581
                    if (write_result < 0) {
×
4582
                        if (errno == EINTR) {
×
4583
                            continue;
×
4584
                        }
4585
                    written = sixel_compat_snprintf(message,
×
4586
                                                    sizeof(message),
4587
                                                    "%s: write() failed (%s).",
4588
                                                    log_prefix,
4589
                                                    strerror(errno));
×
4590
                    thumbnailer_message_finalize(message,
×
4591
                                                 sizeof(message),
4592
                                                 written);
4593
                    sixel_helper_set_additional_message(message);
×
4594
                    stdout_open = 0;
×
4595
                    fatal_error = 1;
×
4596
                    break;
×
4597
                }
4598
                    write_offset += (size_t)write_result;
×
4599
                }
4600
            } else if (stdout_read_result == 0) {
×
4601
                stdout_open = 0;
×
4602
                if (stdout_pipe[0] >= 0) {
×
4603
                    close(stdout_pipe[0]);
×
4604
                    stdout_pipe[0] = -1;
×
4605
                }
4606
                stdout_pipe_created = 0;
×
4607
            } else if (errno == EINTR) {
×
4608
                /* retry */
4609
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4610
                /* no data */
4611
            } else {
4612
                written = sixel_compat_snprintf(message,
×
4613
                                                sizeof(message),
4614
                                                "%s: read() failed (%s).",
4615
                                                log_prefix,
4616
                                                strerror(errno));
×
4617
                thumbnailer_message_finalize(message,
×
4618
                                             sizeof(message),
4619
                                             written);
4620
                sixel_helper_set_additional_message(message);
×
4621
                stdout_open = 0;
×
4622
                if (stdout_pipe[0] >= 0) {
×
4623
                    close(stdout_pipe[0]);
×
4624
                    stdout_pipe[0] = -1;
×
4625
                }
4626
                stdout_pipe_created = 0;
×
4627
            }
4628
        }
4629

4630
        if (!child_exited) {
×
4631
            wait_result = waitpid(pid, &status_code, WNOHANG);
×
4632
            if (wait_result > 0) {
×
4633
                child_exited = 1;
×
4634
                have_status = 1;
×
4635
            } else if (wait_result == 0) {
×
4636
                /* child running */
4637
            } else if (errno != EINTR) {
×
4638
            written = sixel_compat_snprintf(message,
×
4639
                                            sizeof(message),
4640
                                            "%s: waitpid() failed (%s).",
4641
                                            log_prefix,
4642
                                            strerror(errno));
×
4643
            thumbnailer_message_finalize(message,
×
4644
                                         sizeof(message),
4645
                                         written);
4646
            sixel_helper_set_additional_message(message);
×
4647
            status = SIXEL_RUNTIME_ERROR;
×
4648
            fatal_error = 1;
×
4649
            break;
×
4650
        }
4651
        }
4652

4653
        if (!child_exited || stderr_open || stdout_open) {
×
4654
            thumbnailer_sleep_briefly();
×
4655
        }
4656
    }
4657

4658
    if (!child_exited) {
×
4659
        do {
4660
            wait_result = waitpid(pid, &status_code, 0);
×
4661
        } while (wait_result < 0 && errno == EINTR);
×
4662
        if (wait_result < 0) {
×
4663
        written = sixel_compat_snprintf(message,
×
4664
                                        sizeof(message),
4665
                                        "%s: waitpid() failed (%s).",
4666
                                        log_prefix,
4667
                                        strerror(errno));
×
4668
        thumbnailer_message_finalize(message,
×
4669
                                     sizeof(message),
4670
                                     written);
4671
        sixel_helper_set_additional_message(message);
×
4672
        status = SIXEL_RUNTIME_ERROR;
×
4673
        goto cleanup;
×
4674
    }
4675
        have_status = 1;
×
4676
    }
4677

4678
    if (!have_status) {
×
4679
        written = sixel_compat_snprintf(message,
×
4680
                                        sizeof(message),
4681
                                        "%s: waitpid() failed (no status).",
4682
                                        log_prefix);
4683
        thumbnailer_message_finalize(message,
×
4684
                                     sizeof(message),
4685
                                     written);
4686
        sixel_helper_set_additional_message(message);
×
4687
        status = SIXEL_RUNTIME_ERROR;
×
4688
        goto cleanup;
×
4689
    }
4690

4691
    if (!fatal_error) {
×
4692
        if (WIFEXITED(status_code) && WEXITSTATUS(status_code) == 0) {
×
4693
            status = SIXEL_OK;
×
4694
            loader_trace_message("%s: child pid=%ld exited successfully",
×
4695
                                 log_prefix,
4696
                                 (long)pid);
4697
        } else if (WIFEXITED(status_code)) {
×
4698
            written = sixel_compat_snprintf(message,
×
4699
                                            sizeof(message),
4700
                                            "%s: %s exited with status %d.",
4701
                                            log_prefix,
4702
                                            (thumbnailer_name != NULL) ?
×
4703
                                            thumbnailer_name :
4704
                                            "thumbnailer",
4705
                                            WEXITSTATUS(status_code));
×
4706
            thumbnailer_message_finalize(message,
×
4707
                                         sizeof(message),
4708
                                         written);
4709
            sixel_helper_set_additional_message(message);
×
4710
            status = SIXEL_RUNTIME_ERROR;
×
4711
        } else if (WIFSIGNALED(status_code)) {
×
4712
            written = sixel_compat_snprintf(message,
×
4713
                                            sizeof(message),
4714
                                            "%s: %s terminated by signal %d.",
4715
                                            log_prefix,
4716
                                            (thumbnailer_name != NULL) ?
×
4717
                                            thumbnailer_name :
4718
                                            "thumbnailer",
4719
                                            WTERMSIG(status_code));
4720
            thumbnailer_message_finalize(message,
×
4721
                                         sizeof(message),
4722
                                         written);
4723
            sixel_helper_set_additional_message(message);
×
4724
            status = SIXEL_RUNTIME_ERROR;
×
4725
        } else {
4726
            written = sixel_compat_snprintf(message,
×
4727
                                            sizeof(message),
4728
                                            "%s: %s exited abnormally.",
4729
                                            log_prefix,
4730
                                            (thumbnailer_name != NULL) ?
×
4731
                                            thumbnailer_name :
4732
                                            "thumbnailer");
4733
            thumbnailer_message_finalize(message,
×
4734
                                         sizeof(message),
4735
                                         written);
4736
            sixel_helper_set_additional_message(message);
×
4737
            status = SIXEL_RUNTIME_ERROR;
×
4738
        }
4739
    }
4740

4741
cleanup:
4742
    if (stderr_output != NULL && loader_trace_enabled &&
×
4743
            stderr_length > 0) {
4744
        loader_trace_message("%s: stderr:\n%s",
×
4745
                             log_prefix,
4746
                             stderr_output);
4747
    }
4748

4749
    if (stderr_pipe[0] >= 0) {
×
4750
        close(stderr_pipe[0]);
×
4751
        stderr_pipe[0] = -1;
×
4752
    }
4753
    if (stderr_pipe[1] >= 0) {
×
4754
        close(stderr_pipe[1]);
×
4755
        stderr_pipe[1] = -1;
×
4756
    }
4757
    if (stdout_pipe[0] >= 0) {
×
4758
        close(stdout_pipe[0]);
×
4759
        stdout_pipe[0] = -1;
×
4760
    }
4761
    if (stdout_pipe[1] >= 0) {
×
4762
        close(stdout_pipe[1]);
×
4763
        stdout_pipe[1] = -1;
×
4764
    }
4765
    if (output_fd >= 0) {
×
4766
        close(output_fd);
×
4767
        output_fd = -1;
×
4768
    }
4769
    /* stderr_output accumulates all diagnostic text, so release it even when
4770
     * verbose tracing is disabled.
4771
     */
4772
    free(stderr_output);
×
4773

4774
    return status;
×
4775
}
4776

4777

4778

4779
/*
4780
 * load_with_gnome_thumbnailer
4781
 *
4782
 * Drive the FreeDesktop thumbnailer pipeline and then decode the PNG
4783
 * result using the built-in loader.
4784
 *
4785
 * GNOME thumbnail workflow overview:
4786
 *
4787
 *     +------------+    +-------------------+    +----------------+
4788
 *     | source URI | -> | .thumbnailer Exec | -> | PNG thumbnail  |
4789
 *     +------------+    +-------------------+    +----------------+
4790
 *             |                    |                        |
4791
 *             |                    v                        v
4792
 *             |           spawn via /bin/sh         load_with_builtin()
4793
 *             v
4794
 *     file --mime-type
4795
 *
4796
 * Each step logs verbose breadcrumbs so integrators can diagnose which
4797
 * thumbnailer matched, how the command was prepared, and why fallbacks
4798
 * were selected.
4799
 *
4800
 * Arguments:
4801
 *     pchunk        - source chunk representing the original document.
4802
 *     fstatic       - image static-ness flag.
4803
 *     fuse_palette  - palette usage flag.
4804
 *     reqcolors     - requested colour count.
4805
 *     bgcolor       - background colour override.
4806
 *     loop_control  - animation loop control flag.
4807
 *     fn_load       - downstream decoder callback.
4808
 *     context       - user context forwarded to fn_load.
4809
 * Returns:
4810
 *     SIXEL_OK on success or libsixel error code on failure.
4811
 */
4812
static SIXELSTATUS
4813
load_with_gnome_thumbnailer(
×
4814
    sixel_chunk_t const       /* in */     *pchunk,
4815
    int                       /* in */     fstatic,
4816
    int                       /* in */     fuse_palette,
4817
    int                       /* in */     reqcolors,
4818
    unsigned char             /* in */     *bgcolor,
4819
    int                       /* in */     loop_control,
4820
    sixel_load_image_function /* in */     fn_load,
4821
    void                      /* in/out */ *context)
4822
{
4823
    SIXELSTATUS status;
4824
    sixel_chunk_t *thumb_chunk;
4825
    char template_path[] = "/tmp/libsixel-thumb-XXXXXX";
×
4826
    char *png_path;
4827
    size_t path_length;
4828
    struct thumbnailer_string_list *directories;
4829
    size_t dir_index;
4830
    DIR *dir;
4831
    struct dirent *entry;
4832
    char *thumbnailer_path;
4833
    struct thumbnailer_entry info;
4834
    char *content_type;
4835
    char *input_uri;
4836
    struct thumbnailer_command *command;
4837
    struct thumbnailer_command *evince_command;
4838
    int executed;
4839
    int command_success;
4840
    int requested_size;
4841
    char const *log_prefix;
4842
    int fd;
4843
    int written;
4844

4845
    status = SIXEL_FALSE;
×
4846
    thumb_chunk = NULL;
×
4847
    png_path = NULL;
×
4848
    path_length = 0;
×
4849
    fd = -1;
×
4850
    directories = NULL;
×
4851
    dir_index = 0;
×
4852
    dir = NULL;
×
4853
    entry = NULL;
×
4854
    thumbnailer_path = NULL;
×
4855
    content_type = NULL;
×
4856
    input_uri = NULL;
×
4857
    command = NULL;
×
4858
    evince_command = NULL;
×
4859
    executed = 0;
×
4860
    command_success = 0;
×
4861
    log_prefix = "load_with_gnome_thumbnailer";
×
4862
    requested_size = thumbnailer_size_hint;
×
4863
    if (requested_size <= 0) {
×
4864
        requested_size = SIXEL_THUMBNAILER_DEFAULT_SIZE;
×
4865
    }
4866

4867
    loader_trace_message("%s: thumbnail size hint=%d",
×
4868
                         log_prefix,
4869
                         requested_size);
4870

4871
    thumbnailer_entry_init(&info);
×
4872

4873
    if (pchunk->source_path == NULL) {
×
4874
        sixel_helper_set_additional_message(
×
4875
            "load_with_gnome_thumbnailer: source path is unavailable.");
4876
        status = SIXEL_BAD_ARGUMENT;
×
4877
        goto end;
×
4878
    }
4879

4880
#if defined(HAVE_MKSTEMP)
4881
    fd = mkstemp(template_path);
×
4882
#elif defined(HAVE__MKTEMP)
4883
    fd = _mktemp(template_path);
4884
#elif defined(HAVE_MKTEMP)
4885
    fd = mktemp(template_path);
4886
#endif
4887
    if (fd < 0) {
×
4888
        sixel_helper_set_additional_message(
×
4889
            "load_with_gnome_thumbnailer: mkstemp() failed.");
4890
        status = SIXEL_RUNTIME_ERROR;
×
4891
        goto end;
×
4892
    }
4893
    close(fd);
×
4894
    fd = -1;
×
4895

4896
    path_length = strlen(template_path) + 5;
×
4897
    png_path = malloc(path_length);
×
4898
    if (png_path == NULL) {
×
4899
        sixel_helper_set_additional_message(
×
4900
            "load_with_gnome_thumbnailer: malloc() failed.");
4901
        status = SIXEL_BAD_ALLOCATION;
×
4902
        unlink(template_path);
×
4903
        goto end;
×
4904
    }
4905
    written = sixel_compat_snprintf(png_path,
×
4906
                                    path_length,
4907
                                    "%s.png",
4908
                                    template_path);
4909
    thumbnailer_message_finalize(png_path,
×
4910
                                 path_length,
4911
                                 written);
4912
    if (rename(template_path, png_path) != 0) {
×
4913
        sixel_helper_set_additional_message(
×
4914
            "load_with_gnome_thumbnailer: rename() failed.");
4915
        status = SIXEL_RUNTIME_ERROR;
×
4916
        unlink(template_path);
×
4917
        goto end;
×
4918
    }
4919

4920
    content_type = thumbnailer_guess_content_type(pchunk->source_path);
×
4921
    input_uri = thumbnailer_build_file_uri(pchunk->source_path);
×
4922

4923
    loader_trace_message("%s: detected MIME type %s for %s",
×
4924
                         log_prefix,
4925
                         (content_type != NULL) ? content_type :
×
4926
                         "(unknown)",
4927
                         pchunk->source_path);
×
4928

4929
    directories = thumbnailer_collect_directories();
×
4930
    if (directories == NULL) {
×
4931
        status = SIXEL_RUNTIME_ERROR;
×
4932
        goto end;
×
4933
    }
4934

4935
    /* Iterate through every configured thumbnailer directory so we honour
4936
     * overrides in $HOME as well as desktop environment defaults discovered
4937
     * through XDG_DATA_DIRS.
4938
     */
4939
    for (dir_index = 0; dir_index < directories->length; ++dir_index) {
×
4940
        loader_trace_message("%s: checking thumbnailers in %s",
×
4941
                             log_prefix,
4942
                             directories->items[dir_index]);
×
4943

4944
        dir = opendir(directories->items[dir_index]);
×
4945
        if (dir == NULL) {
×
4946
            continue;
×
4947
        }
4948
        while ((entry = readdir(dir)) != NULL) {
×
4949
            thumbnailer_entry_clear(&info);
×
4950
            thumbnailer_entry_init(&info);
×
4951
            size_t name_length;
4952

4953
            name_length = strlen(entry->d_name);
×
4954
            if (name_length < 12 ||
×
4955
                    strcmp(entry->d_name + name_length - 12,
×
4956
                           ".thumbnailer") != 0) {
4957
                continue;
×
4958
            }
4959
            thumbnailer_path = thumbnailer_join_paths(
×
4960
                directories->items[dir_index],
×
4961
                entry->d_name);
×
4962
            if (thumbnailer_path == NULL) {
×
4963
                continue;
×
4964
            }
4965
            if (!thumbnailer_parse_file(thumbnailer_path, &info)) {
×
4966
                free(thumbnailer_path);
×
4967
                thumbnailer_path = NULL;
×
4968
                continue;
×
4969
            }
4970
            free(thumbnailer_path);
×
4971
            thumbnailer_path = NULL;
×
4972
            loader_trace_message(
×
4973
                "%s: parsed %s (TryExec=%s)",
4974
                log_prefix,
4975
                entry->d_name,
×
4976
                (info.tryexec != NULL) ? info.tryexec : "(none)");
×
4977
            if (content_type == NULL) {
×
4978
                continue;
×
4979
            }
4980
            if (!thumbnailer_has_tryexec(info.tryexec)) {
×
4981
                loader_trace_message("%s: skipping %s (TryExec missing)",
×
4982
                                     log_prefix,
4983
                                     entry->d_name);
×
4984
                continue;
×
4985
            }
4986
            if (!thumbnailer_supports_mime(&info, content_type)) {
×
4987
                loader_trace_message("%s: %s does not support %s",
×
4988
                                     log_prefix,
4989
                                     entry->d_name,
×
4990
                                     content_type);
4991
                continue;
×
4992
            }
4993
            if (info.exec_line == NULL) {
×
4994
                continue;
×
4995
            }
4996
            loader_trace_message("%s: %s supports %s with Exec=\"%s\"",
×
4997
                                 log_prefix,
4998
                                 entry->d_name,
×
4999
                                 content_type,
5000
                                 info.exec_line);
5001
            loader_trace_message("%s: preparing %s for %s",
×
5002
                                 log_prefix,
5003
                                 entry->d_name,
×
5004
                                 content_type);
5005
            command = thumbnailer_build_command(info.exec_line,
×
5006
                                                pchunk->source_path,
×
5007
                                                input_uri,
5008
                                                png_path,
5009
                                                requested_size,
5010
                                                content_type);
5011
            if (command == NULL) {
×
5012
                continue;
×
5013
            }
5014
            if (thumbnailer_is_evince_thumbnailer(info.exec_line,
×
5015
                                                  info.tryexec)) {
×
5016
                loader_trace_message(
×
5017
                    "%s: applying evince-thumbnailer stdout workaround",
5018
                    log_prefix);
5019
                /* evince-thumbnailer fails when passed an output path.
5020
                 * Redirect stdout and copy the stream instead.
5021
                 */
5022
                evince_command = thumbnailer_build_evince_command(
×
5023
                    pchunk->source_path,
×
5024
                    requested_size);
5025
                if (evince_command == NULL) {
×
5026
                    thumbnailer_command_free(command);
×
5027
                    command = NULL;
×
5028
                    continue;
×
5029
                }
5030
                thumbnailer_command_free(command);
×
5031
                command = evince_command;
×
5032
                evince_command = NULL;
×
5033
                unlink(png_path);
×
5034
                status = thumbnailer_spawn(command,
×
5035
                                           entry->d_name,
×
5036
                                           log_prefix,
5037
                                           1,
5038
                                           png_path);
5039
            } else {
5040
                unlink(png_path);
×
5041
                status = thumbnailer_spawn(command,
×
5042
                                           entry->d_name,
×
5043
                                           log_prefix,
5044
                                           0,
5045
                                           NULL);
5046
            }
5047
            thumbnailer_command_free(command);
×
5048
            command = NULL;
×
5049
            executed = 1;
×
5050
            if (SIXEL_SUCCEEDED(status)) {
×
5051
                command_success = 1;
×
5052
                loader_trace_message("%s: %s produced %s",
×
5053
                                     log_prefix,
5054
                                     entry->d_name,
×
5055
                                     png_path);
5056
                break;
×
5057
            }
5058
        }
5059
        closedir(dir);
×
5060
        dir = NULL;
×
5061
        if (command_success) {
×
5062
            break;
×
5063
        }
5064
    }
5065

5066
    if (!command_success) {
×
5067
        loader_trace_message("%s: falling back to gdk-pixbuf-thumbnailer",
×
5068
                             log_prefix);
5069
        unlink(png_path);
×
5070
        command = thumbnailer_build_command(
×
5071
            "gdk-pixbuf-thumbnailer --size=%s %i %o",
5072
            pchunk->source_path,
×
5073
            input_uri,
5074
            png_path,
5075
            requested_size,
5076
            content_type);
5077
        if (command != NULL) {
×
5078
            unlink(png_path);
×
5079
            status = thumbnailer_spawn(command,
×
5080
                                       "gdk-pixbuf-thumbnailer",
5081
                                       log_prefix,
5082
                                       0,
5083
                                       NULL);
5084
            thumbnailer_command_free(command);
×
5085
            command = NULL;
×
5086
            if (SIXEL_FAILED(status)) {
×
5087
                goto end;
×
5088
            }
5089
            executed = 1;
×
5090
            command_success = 1;
×
5091
            loader_trace_message("%s: gdk-pixbuf-thumbnailer produced %s",
×
5092
                                 log_prefix,
5093
                                 png_path);
5094
        }
5095
    }
5096

5097
    if (!executed) {
×
5098
        sixel_helper_set_additional_message(
×
5099
            "load_with_gnome_thumbnailer: no thumbnailer available.");
5100
        status = SIXEL_RUNTIME_ERROR;
×
5101
        goto end;
×
5102
    }
5103

5104
    status = sixel_chunk_new(&thumb_chunk,
×
5105
                             png_path,
5106
                             0,
5107
                             NULL,
5108
                             pchunk->allocator);
×
5109
    if (SIXEL_FAILED(status)) {
×
5110
        goto end;
×
5111
    }
5112
    status = load_with_builtin(thumb_chunk,
×
5113
                               fstatic,
5114
                               fuse_palette,
5115
                               reqcolors,
5116
                               bgcolor,
5117
                               loop_control,
5118
                               fn_load,
5119
                               context);
5120
    if (SIXEL_FAILED(status)) {
×
5121
        goto end;
×
5122
    }
5123

5124
    status = SIXEL_OK;
×
5125

5126
end:
5127
    if (command != NULL) {
×
5128
        thumbnailer_command_free(command);
×
5129
        command = NULL;
×
5130
    }
5131
    if (evince_command != NULL) {
×
5132
        thumbnailer_command_free(evince_command);
×
5133
        evince_command = NULL;
×
5134
    }
5135
    if (thumb_chunk != NULL) {
×
5136
        sixel_chunk_destroy(thumb_chunk);
×
5137
        thumb_chunk = NULL;
×
5138
    }
5139
    if (png_path != NULL) {
×
5140
        unlink(png_path);
×
5141
        free(png_path);
×
5142
        png_path = NULL;
×
5143
    }
5144
    if (fd >= 0) {
×
5145
        close(fd);
×
5146
        fd = -1;
×
5147
    }
5148
    if (directories != NULL) {
×
5149
        thumbnailer_string_list_free(directories);
×
5150
        directories = NULL;
×
5151
    }
5152
    if (dir != NULL) {
×
5153
        closedir(dir);
×
5154
        dir = NULL;
×
5155
    }
5156
    thumbnailer_entry_clear(&info);
×
5157
    free(content_type);
×
5158
    content_type = NULL;
×
5159
    free(input_uri);
×
5160
    input_uri = NULL;
×
5161

5162
    return status;
×
5163
}
5164

5165
#endif  /* HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK */
5166

5167

5168
#if HAVE_GD
5169
static int
5170
detect_file_format(int len, unsigned char *data)
5171
{
5172
    if (len > 18 && memcmp("TRUEVISION", data + len - 18, 10) == 0) {
5173
        return SIXEL_FORMAT_TGA;
5174
    }
5175

5176
    if (len > 3 && memcmp("GIF", data, 3) == 0) {
5177
        return SIXEL_FORMAT_GIF;
5178
    }
5179

5180
    if (len > 8 && memcmp("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", data, 8) == 0) {
5181
        return SIXEL_FORMAT_PNG;
5182
    }
5183

5184
    if (len > 2 && memcmp("BM", data, 2) == 0) {
5185
        return SIXEL_FORMAT_BMP;
5186
    }
5187

5188
    if (len > 2 && memcmp("\xFF\xD8", data, 2) == 0) {
5189
        return SIXEL_FORMAT_JPG;
5190
    }
5191

5192
    if (len > 2 && memcmp("\x00\x00", data, 2) == 0) {
5193
        return SIXEL_FORMAT_WBMP;
5194
    }
5195

5196
    if (len > 2 && memcmp("\x4D\x4D", data, 2) == 0) {
5197
        return SIXEL_FORMAT_TIFF;
5198
    }
5199

5200
    if (len > 2 && memcmp("\x49\x49", data, 2) == 0) {
5201
        return SIXEL_FORMAT_TIFF;
5202
    }
5203

5204
    if (len > 2 && memcmp("\033P", data, 2) == 0) {
5205
        return SIXEL_FORMAT_SIXEL;
5206
    }
5207

5208
    if (len > 2 && data[0] == 0x90  && (data[len - 1] == 0x9C || data[len - 2] == 0x9C)) {
5209
        return SIXEL_FORMAT_SIXEL;
5210
    }
5211

5212
    if (len > 1 && data[0] == 'P' && data[1] >= '1' && data[1] <= '6') {
5213
        return SIXEL_FORMAT_PNM;
5214
    }
5215

5216
    if (len > 3 && memcmp("gd2", data, 3) == 0) {
5217
        return SIXEL_FORMAT_GD2;
5218
    }
5219

5220
    if (len > 4 && memcmp("8BPS", data, 4) == 0) {
5221
        return SIXEL_FORMAT_PSD;
5222
    }
5223

5224
    if (len > 11 && memcmp("#?RADIANCE\n", data, 11) == 0) {
5225
        return SIXEL_FORMAT_HDR;
5226
    }
5227

5228
    return (-1);
5229
}
5230
#endif /* HAVE_GD */
5231

5232
#if HAVE_GD
5233

5234
static SIXELSTATUS
5235
load_with_gd(
5236
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
5237
    int                       /* in */     fstatic,      /* static */
5238
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
5239
    int                       /* in */     reqcolors,    /* reqcolors */
5240
    unsigned char             /* in */     *bgcolor,     /* background color */
5241
    int                       /* in */     loop_control, /* one of enum loop_control */
5242
    sixel_load_image_function /* in */     fn_load,      /* callback */
5243
    void                      /* in/out */ *context      /* private data for callback */
5244
)
5245
{
5246
    SIXELSTATUS status = SIXEL_FALSE;
5247
    unsigned char *p;
5248
    gdImagePtr im = NULL;
5249
    int x, y;
5250
    int c;
5251
    sixel_frame_t *frame = NULL;
5252
    int format;
5253

5254
    (void) fstatic;
5255
    (void) fuse_palette;
5256
    (void) reqcolors;
5257
    (void) bgcolor;
5258
    (void) loop_control;
5259

5260
    format = detect_file_format(pchunk->size, pchunk->buffer);
5261

5262
    if (format == SIXEL_FORMAT_GIF) {
5263
#if HAVE_DECL_GDIMAGECREATEFROMGIFANIMPTR
5264
        gdImagePtr *ims = NULL;
5265
        int frames = 0;
5266
        int i;
5267
        int *delays = NULL;
5268

5269
        ims = gdImageCreateFromGifAnimPtr(pchunk->size, pchunk->buffer,
5270
                                          &frames, &delays);
5271
        if (ims == NULL) {
5272
            status = SIXEL_GD_ERROR;
5273
            goto end;
5274
        }
5275

5276
        for (i = 0; i < frames; i++) {
5277
            im = ims[i];
5278
            if (!gdImageTrueColor(im)) {
5279
# if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
5280
                if (!gdImagePaletteToTrueColor(im)) {
5281
                    status = SIXEL_GD_ERROR;
5282
                    goto gif_end;
5283
                }
5284
# else
5285
                status = SIXEL_GD_ERROR;
5286
                goto gif_end;
5287
# endif
5288
            }
5289

5290
            status = sixel_frame_new(&frame, pchunk->allocator);
5291
            if (SIXEL_FAILED(status)) {
5292
                frame = NULL;
5293
                goto gif_end;
5294
            }
5295

5296
            frame->width = gdImageSX(im);
5297
            frame->height = gdImageSY(im);
5298
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
5299
            p = frame->pixels = sixel_allocator_malloc(
5300
                pchunk->allocator,
5301
                (size_t)(frame->width * frame->height * 3));
5302
            if (frame->pixels == NULL) {
5303
                sixel_helper_set_additional_message(
5304
                    "load_with_gd: sixel_allocator_malloc() failed.");
5305
                status = SIXEL_BAD_ALLOCATION;
5306
                sixel_frame_unref(frame);
5307
                frame = NULL;
5308
                goto gif_end;
5309
            }
5310
            for (y = 0; y < frame->height; y++) {
5311
                for (x = 0; x < frame->width; x++) {
5312
                    c = gdImageTrueColorPixel(im, x, y);
5313
                    *p++ = gdTrueColorGetRed(c);
5314
                    *p++ = gdTrueColorGetGreen(c);
5315
                    *p++ = gdTrueColorGetBlue(c);
5316
                }
5317
            }
5318

5319
            if (delays) {
5320
                frame->delay.tv_sec = delays[i] / 100;
5321
                frame->delay.tv_nsec = (delays[i] % 100) * 10000000L;
5322
            }
5323

5324
            status = fn_load(frame, context);
5325
            sixel_frame_unref(frame);
5326
            frame = NULL;
5327
            gdImageDestroy(im);
5328
            ims[i] = NULL;
5329
            if (SIXEL_FAILED(status)) {
5330
                goto gif_end;
5331
            }
5332
        }
5333

5334
        status = SIXEL_OK;
5335

5336
gif_end:
5337
        if (delays) {
5338
            gdFree(delays);
5339
        }
5340
        if (ims) {
5341
            for (i = 0; i < frames; i++) {
5342
                if (ims[i]) {
5343
                    gdImageDestroy(ims[i]);
5344
                }
5345
            }
5346
            gdFree(ims);
5347
        }
5348
        goto end;
5349
#else
5350
        status = SIXEL_GD_ERROR;
5351
        goto end;
5352
#endif
5353
    }
5354

5355
    switch (format) {
5356
#if HAVE_DECL_GDIMAGECREATEFROMPNGPTR
5357
        case SIXEL_FORMAT_PNG:
5358
            im = gdImageCreateFromPngPtr(pchunk->size, pchunk->buffer);
5359
            break;
5360
#endif  /* HAVE_DECL_GDIMAGECREATEFROMPNGPTR */
5361
#if HAVE_DECL_GDIMAGECREATEFROMBMPPTR
5362
        case SIXEL_FORMAT_BMP:
5363
            im = gdImageCreateFromBmpPtr(pchunk->size, pchunk->buffer);
5364
            break;
5365
#endif  /* HAVE_DECL_GDIMAGECREATEFROMBMPPTR */
5366
        case SIXEL_FORMAT_JPG:
5367
#if HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX
5368
            im = gdImageCreateFromJpegPtrEx(pchunk->size, pchunk->buffer, 1);
5369
#elif HAVE_DECL_GDIMAGECREATEFROMJPEGPTR
5370
            im = gdImageCreateFromJpegPtr(pchunk->size, pchunk->buffer);
5371
#endif  /* HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX */
5372
            break;
5373
#if HAVE_DECL_GDIMAGECREATEFROMTGAPTR
5374
        case SIXEL_FORMAT_TGA:
5375
            im = gdImageCreateFromTgaPtr(pchunk->size, pchunk->buffer);
5376
            break;
5377
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTGAPTR */
5378
#if HAVE_DECL_GDIMAGECREATEFROMWBMPPTR
5379
        case SIXEL_FORMAT_WBMP:
5380
            im = gdImageCreateFromWBMPPtr(pchunk->size, pchunk->buffer);
5381
            break;
5382
#endif  /* HAVE_DECL_GDIMAGECREATEFROMWBMPPTR */
5383
#if HAVE_DECL_GDIMAGECREATEFROMTIFFPTR
5384
        case SIXEL_FORMAT_TIFF:
5385
            im = gdImageCreateFromTiffPtr(pchunk->size, pchunk->buffer);
5386
            break;
5387
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTIFFPTR */
5388
#if HAVE_DECL_GDIMAGECREATEFROMGD2PTR
5389
        case SIXEL_FORMAT_GD2:
5390
            im = gdImageCreateFromGd2Ptr(pchunk->size, pchunk->buffer);
5391
            break;
5392
#endif  /* HAVE_DECL_GDIMAGECREATEFROMGD2PTR */
5393
        default:
5394
            status = SIXEL_GD_ERROR;
5395
            sixel_helper_set_additional_message(
5396
                "unexpected image format detected.");
5397
            goto end;
5398
    }
5399

5400
    if (im == NULL) {
5401
        status = SIXEL_GD_ERROR;
5402
        /* TODO: retrieve error detail */
5403
        goto end;
5404
    }
5405

5406
    if (!gdImageTrueColor(im)) {
5407
#if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
5408
        if (!gdImagePaletteToTrueColor(im)) {
5409
            gdImageDestroy(im);
5410
            status = SIXEL_GD_ERROR;
5411
            /* TODO: retrieve error detail */
5412
            goto end;
5413
        }
5414
#else
5415
        status = SIXEL_GD_ERROR;
5416
        /* TODO: retrieve error detail */
5417
        goto end;
5418
#endif
5419
    }
5420

5421
    status = sixel_frame_new(&frame, pchunk->allocator);
5422
    if (SIXEL_FAILED(status)) {
5423
        goto end;
5424
    }
5425

5426
    frame->width = gdImageSX(im);
5427
    frame->height = gdImageSY(im);
5428
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
5429
    p = frame->pixels = sixel_allocator_malloc(
5430
        pchunk->allocator, (size_t)(frame->width * frame->height * 3));
5431
    if (frame->pixels == NULL) {
5432
        sixel_helper_set_additional_message(
5433
            "load_with_gd: sixel_allocator_malloc() failed.");
5434
        status = SIXEL_BAD_ALLOCATION;
5435
        gdImageDestroy(im);
5436
        goto end;
5437
    }
5438
    for (y = 0; y < frame->height; y++) {
5439
        for (x = 0; x < frame->width; x++) {
5440
            c = gdImageTrueColorPixel(im, x, y);
5441
            *p++ = gdTrueColorGetRed(c);
5442
            *p++ = gdTrueColorGetGreen(c);
5443
            *p++ = gdTrueColorGetBlue(c);
5444
        }
5445
    }
5446
    gdImageDestroy(im);
5447

5448
    status = fn_load(frame, context);
5449
    if (SIXEL_FAILED(status)) {
5450
        goto end;
5451
    }
5452

5453
    sixel_frame_unref(frame);
5454

5455
    status = SIXEL_OK;
5456

5457
end:
5458
    return status;
5459
}
5460

5461
#endif  /* HAVE_GD */
5462

5463
#if HAVE_WIC
5464

5465
#include <windows.h>
5466
#include <wincodec.h>
5467

5468
SIXELSTATUS
5469
load_with_wic(
5470
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
5471
    int                       /* in */     fstatic,      /* static */
5472
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
5473
    int                       /* in */     reqcolors,    /* reqcolors */
5474
    unsigned char             /* in */     *bgcolor,     /* background color */
5475
    int                       /* in */     loop_control, /* one of enum loop_control */
5476
    sixel_load_image_function /* in */     fn_load,      /* callback */
5477
    void                      /* in/out */ *context      /* private data for callback */
5478
)
5479
{
5480
    HRESULT                 hr         = E_FAIL;
5481
    SIXELSTATUS             status     = SIXEL_FALSE;
5482
    IWICImagingFactory     *factory    = NULL;
5483
    IWICStream             *stream     = NULL;
5484
    IWICBitmapDecoder      *decoder    = NULL;
5485
    IWICBitmapFrameDecode  *wicframe   = NULL;
5486
    IWICFormatConverter    *conv       = NULL;
5487
    IWICBitmapSource       *src        = NULL;
5488
    IWICPalette            *wicpalette = NULL;
5489
    WICColor               *wiccolors  = NULL;
5490
    IWICMetadataQueryReader *qdecoder  = NULL;
5491
    IWICMetadataQueryReader *qframe    = NULL;
5492
    UINT                    ncolors    = 0;
5493
    sixel_frame_t          *frame      = NULL;
5494
    int                     comp       = 4;
5495
    UINT                    actual     = 0;
5496
    UINT                    i;
5497
    UINT                    frame_count = 0;
5498
    int                     anim_loop_count = (-1);
5499
    int                     is_gif;
5500
    WICColor                c;
5501

5502
    (void) reqcolors;
5503
    (void) bgcolor;
5504

5505
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
5506
    if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
5507
        return status;
5508
    }
5509

5510
    status = sixel_frame_new(&frame, pchunk->allocator);
5511
    if (SIXEL_FAILED(status)) {
5512
        goto end;
5513
    }
5514

5515
    hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
5516
                          &IID_IWICImagingFactory, (void**)&factory);
5517
    if (FAILED(hr)) {
5518
        goto end;
5519
    }
5520

5521
    hr = factory->lpVtbl->CreateStream(factory, &stream);
5522
    if (FAILED(hr)) {
5523
        goto end;
5524
    }
5525

5526
    hr = stream->lpVtbl->InitializeFromMemory(stream,
5527
                                              (BYTE*)pchunk->buffer,
5528
                                              (DWORD)pchunk->size);
5529
    if (FAILED(hr)) {
5530
        goto end;
5531
    }
5532

5533
    hr = factory->lpVtbl->CreateDecoderFromStream(factory,
5534
                                                  (IStream*)stream,
5535
                                                  NULL,
5536
                                                  WICDecodeMetadataCacheOnDemand,
5537
                                                  &decoder);
5538
    if (FAILED(hr)) {
5539
        goto end;
5540
    }
5541

5542
    is_gif = (memcmp("GIF", pchunk->buffer, 3) == 0);
5543

5544
    if (is_gif) {
5545
        hr = decoder->lpVtbl->GetFrameCount(decoder, &frame_count);
5546
        if (FAILED(hr)) {
5547
            goto end;
5548
        }
5549
        if (fstatic) {
5550
            frame_count = 1;
5551
        }
5552

5553
        hr = decoder->lpVtbl->GetMetadataQueryReader(decoder, &qdecoder);
5554
        if (SUCCEEDED(hr)) {
5555
            PROPVARIANT pv;
5556
            PropVariantInit(&pv);
5557
            hr = qdecoder->lpVtbl->GetMetadataByName(
5558
                qdecoder,
5559
                L"/appext/Application/NETSCAPE2.0/Loop",
5560
                &pv);
5561
            if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5562
                anim_loop_count = pv.uiVal;
5563
            }
5564
            PropVariantClear(&pv);
5565
            qdecoder->lpVtbl->Release(qdecoder);
5566
            qdecoder = NULL;
5567
        }
5568

5569
        frame->loop_count = 0;
5570
        for (;;) {
5571
            frame->frame_no = 0;
5572
            for (i = 0; i < frame_count; ++i) {
5573
                hr = decoder->lpVtbl->GetFrame(decoder, i, &wicframe);
5574
                if (FAILED(hr)) {
5575
                    goto end;
5576
                }
5577

5578
                hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5579
                if (FAILED(hr)) {
5580
                    goto end;
5581
                }
5582
                hr = conv->lpVtbl->Initialize(conv,
5583
                                              (IWICBitmapSource*)wicframe,
5584
                                              &GUID_WICPixelFormat32bppRGBA,
5585
                                              WICBitmapDitherTypeNone,
5586
                                              NULL,
5587
                                              0.0,
5588
                                              WICBitmapPaletteTypeCustom);
5589
                if (FAILED(hr)) {
5590
                    goto end;
5591
                }
5592

5593
                src = (IWICBitmapSource*)conv;
5594
                comp = 4;
5595
                frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5596

5597
                hr = src->lpVtbl->GetSize(
5598
                    src,
5599
                    (UINT *)&frame->width,
5600
                    (UINT *)&frame->height);
5601
                if (FAILED(hr)) {
5602
                    goto end;
5603
                }
5604

5605
                if (frame->width <= 0 || frame->height <= 0 ||
5606
                    frame->width > SIXEL_WIDTH_LIMIT ||
5607
                    frame->height > SIXEL_HEIGHT_LIMIT) {
5608
                    sixel_helper_set_additional_message(
5609
                        "load_with_wic: an invalid width or height parameter detected.");
5610
                    status = SIXEL_BAD_INPUT;
5611
                    goto end;
5612
                }
5613

5614
                frame->pixels = sixel_allocator_malloc(
5615
                    pchunk->allocator,
5616
                    (size_t)(frame->height * frame->width * comp));
5617
                if (frame->pixels == NULL) {
5618
                    hr = E_OUTOFMEMORY;
5619
                    goto end;
5620
                }
5621

5622
                {
5623
                    WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
5624
                    hr = src->lpVtbl->CopyPixels(
5625
                        src,
5626
                        &rc,
5627
                        frame->width * comp,
5628
                        (UINT)frame->width * frame->height * comp,
5629
                        frame->pixels);
5630
                    if (FAILED(hr)) {
5631
                        goto end;
5632
                    }
5633
                }
5634

5635
                frame->delay = 0;
5636
                hr = wicframe->lpVtbl->GetMetadataQueryReader(wicframe, &qframe);
5637
                if (SUCCEEDED(hr)) {
5638
                    PROPVARIANT pv;
5639
                    PropVariantInit(&pv);
5640
                    hr = qframe->lpVtbl->GetMetadataByName(
5641
                        qframe,
5642
                        L"/grctlext/Delay",
5643
                        &pv);
5644
                    if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5645
                        frame->delay = (int)(pv.uiVal) * 10;
5646
                    }
5647
                    PropVariantClear(&pv);
5648
                    qframe->lpVtbl->Release(qframe);
5649
                    qframe = NULL;
5650
                }
5651

5652
                frame->multiframe = 1;
5653
                status = fn_load(frame, context);
5654
                if (SIXEL_FAILED(status)) {
5655
                    goto end;
5656
                }
5657
                frame->pixels = NULL;
5658
                frame->palette = NULL;
5659

5660
                if (conv) {
5661
                    conv->lpVtbl->Release(conv);
5662
                    conv = NULL;
5663
                }
5664
                if (wicframe) {
5665
                    wicframe->lpVtbl->Release(wicframe);
5666
                    wicframe = NULL;
5667
                }
5668

5669
                frame->frame_no++;
5670
            }
5671

5672
            ++frame->loop_count;
5673

5674
            if (anim_loop_count < 0) {
5675
                break;
5676
            }
5677
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
5678
                break;
5679
            }
5680
            if (loop_control == SIXEL_LOOP_AUTO &&
5681
                frame->loop_count == anim_loop_count) {
5682
                break;
5683
            }
5684
        }
5685

5686
        status = SIXEL_OK;
5687
        goto end;
5688
    }
5689

5690
    hr = decoder->lpVtbl->GetFrame(decoder, 0, &wicframe);
5691
    if (FAILED(hr)) {
5692
        goto end;
5693
    }
5694

5695
    if (fuse_palette) {
5696
        hr = factory->lpVtbl->CreatePalette(factory, &wicpalette);
5697
        if (SUCCEEDED(hr)) {
5698
            hr = wicframe->lpVtbl->CopyPalette(wicframe, wicpalette);
5699
        }
5700
        if (SUCCEEDED(hr)) {
5701
            hr = wicpalette->lpVtbl->GetColorCount(wicpalette, &ncolors);
5702
        }
5703
        if (SUCCEEDED(hr) && ncolors > 0 && ncolors <= 256) {
5704
            hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5705
            if (SUCCEEDED(hr)) {
5706
                hr = conv->lpVtbl->Initialize(conv,
5707
                                              (IWICBitmapSource*)wicframe,
5708
                                              &GUID_WICPixelFormat8bppIndexed,
5709
                                              WICBitmapDitherTypeNone,
5710
                                              wicpalette,
5711
                                              0.0,
5712
                                              WICBitmapPaletteTypeCustom);
5713
            }
5714
            if (SUCCEEDED(hr)) {
5715
                src = (IWICBitmapSource*)conv;
5716
                comp = 1;
5717
                frame->pixelformat = SIXEL_PIXELFORMAT_PAL8;
5718
                frame->palette = sixel_allocator_malloc(
5719
                    pchunk->allocator,
5720
                    (size_t)ncolors * 3);
5721
                if (frame->palette == NULL) {
5722
                    hr = E_OUTOFMEMORY;
5723
                } else {
5724
                    wiccolors = (WICColor *)sixel_allocator_malloc(
5725
                        pchunk->allocator,
5726
                        (size_t)ncolors * sizeof(WICColor));
5727
                    if (wiccolors == NULL) {
5728
                        hr = E_OUTOFMEMORY;
5729
                    } else {
5730
                        actual = 0;
5731
                        hr = wicpalette->lpVtbl->GetColors(
5732
                            wicpalette, ncolors, wiccolors, &actual);
5733
                        if (SUCCEEDED(hr) && actual == ncolors) {
5734
                            for (i = 0; i < ncolors; ++i) {
5735
                                c = wiccolors[i];
5736
                                frame->palette[i * 3 + 0] = (unsigned char)((c >> 16) & 0xFF);
5737
                                frame->palette[i * 3 + 1] = (unsigned char)((c >> 8) & 0xFF);
5738
                                frame->palette[i * 3 + 2] = (unsigned char)(c & 0xFF);
5739
                            }
5740
                            frame->ncolors = (int)ncolors;
5741
                        } else {
5742
                            hr = E_FAIL;
5743
                        }
5744
                    }
5745
                }
5746
            }
5747
            if (FAILED(hr)) {
5748
                if (conv) {
5749
                    conv->lpVtbl->Release(conv);
5750
                    conv = NULL;
5751
                }
5752
                sixel_allocator_free(pchunk->allocator, frame->palette);
5753
                frame->palette = NULL;
5754
                sixel_allocator_free(pchunk->allocator, wiccolors);
5755
                wiccolors = NULL;
5756
                src = NULL;
5757
            }
5758
        }
5759
    }
5760

5761
    if (src == NULL) {
5762
        hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5763
        if (FAILED(hr)) {
5764
            goto end;
5765
        }
5766

5767
        hr = conv->lpVtbl->Initialize(conv, (IWICBitmapSource*)wicframe,
5768
                                      &GUID_WICPixelFormat32bppRGBA,
5769
                                      WICBitmapDitherTypeNone, NULL, 0.0,
5770
                                      WICBitmapPaletteTypeCustom);
5771
        if (FAILED(hr)) {
5772
            goto end;
5773
        }
5774

5775
        src = (IWICBitmapSource*)conv;
5776
        comp = 4;
5777
        frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5778
    }
5779

5780
    hr = src->lpVtbl->GetSize(
5781
        src, (UINT *)&frame->width, (UINT *)&frame->height);
5782
    if (FAILED(hr)) {
5783
        goto end;
5784
    }
5785

5786
    /* check size */
5787
    if (frame->width <= 0) {
5788
        sixel_helper_set_additional_message(
5789
            "load_with_wic: an invalid width parameter detected.");
5790
        status = SIXEL_BAD_INPUT;
5791
        goto end;
5792
    }
5793
    if (frame->height <= 0) {
5794
        sixel_helper_set_additional_message(
5795
            "load_with_wic: an invalid width parameter detected.");
5796
        status = SIXEL_BAD_INPUT;
5797
        goto end;
5798
    }
5799
    if (frame->width > SIXEL_WIDTH_LIMIT) {
5800
        sixel_helper_set_additional_message(
5801
            "load_with_wic: given width parameter is too huge.");
5802
        status = SIXEL_BAD_INPUT;
5803
        goto end;
5804
    }
5805
    if (frame->height > SIXEL_HEIGHT_LIMIT) {
5806
        sixel_helper_set_additional_message(
5807
            "load_with_wic: given height parameter is too huge.");
5808
        status = SIXEL_BAD_INPUT;
5809
        goto end;
5810
    }
5811

5812
    frame->pixels = sixel_allocator_malloc(
5813
        pchunk->allocator,
5814
        (size_t)(frame->height * frame->width * comp));
5815

5816
    {
5817
        WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
5818
        hr = src->lpVtbl->CopyPixels(
5819
            src,
5820
            &rc,                                        /* prc */
5821
            frame->width * comp,                        /* cbStride */
5822
            (UINT)frame->width * frame->height * comp,  /* cbBufferSize */
5823
            frame->pixels);                             /* pbBuffer */
5824
        if (FAILED(hr)) {
5825
            goto end;
5826
        }
5827
    }
5828

5829
    status = fn_load(frame, context);
5830
    if (SIXEL_FAILED(status)) {
5831
        goto end;
5832
    }
5833

5834
end:
5835
    if (conv) {
5836
         conv->lpVtbl->Release(conv);
5837
    }
5838
    if (wicpalette) {
5839
         wicpalette->lpVtbl->Release(wicpalette);
5840
    }
5841
    if (wiccolors) {
5842
         sixel_allocator_free(pchunk->allocator, wiccolors);
5843
    }
5844
    if (wicframe) {
5845
         wicframe->lpVtbl->Release(wicframe);
5846
    }
5847
    if (qdecoder) {
5848
         qdecoder->lpVtbl->Release(qdecoder);
5849
    }
5850
    if (qframe) {
5851
         qframe->lpVtbl->Release(qframe);
5852
    }
5853
    if (stream) {
5854
         stream->lpVtbl->Release(stream);
5855
    }
5856
    if (factory) {
5857
         factory->lpVtbl->Release(factory);
5858
    }
5859
    sixel_frame_unref(frame);
5860

5861
    CoUninitialize();
5862

5863
    if (FAILED(hr)) {
5864
        return SIXEL_FALSE;
5865
    }
5866

5867
    return SIXEL_OK;
5868
}
5869

5870
#endif /* HAVE_WIC */
5871

5872
#if HAVE_WIC
5873
static int
5874
loader_can_try_wic(sixel_chunk_t const *chunk)
5875
{
5876
    if (chunk == NULL) {
5877
        return 0;
5878
    }
5879
    if (chunk_is_gif(chunk)) {
5880
        return 0;
5881
    }
5882
    return 1;
5883
}
5884
#endif
5885

5886
static sixel_loader_entry_t const sixel_loader_entries[] = {
5887
#if HAVE_WIC
5888
    { "wic", load_with_wic, loader_can_try_wic, 1 },
5889
#endif
5890
#if HAVE_COREGRAPHICS
5891
    { "coregraphics", load_with_coregraphics, NULL, 1 },
5892
#endif
5893
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
5894
    { "quicklook", load_with_quicklook, NULL, 0 },
5895
#endif
5896
#ifdef HAVE_GDK_PIXBUF2
5897
    { "gdk-pixbuf2", load_with_gdkpixbuf, NULL, 1 },
5898
#endif
5899
#if HAVE_GD
5900
    { "gd", load_with_gd, NULL, 1 },
5901
#endif
5902
#if HAVE_JPEG
5903
    { "libjpeg", load_with_libjpeg, loader_can_try_libjpeg, 1 },
5904
#endif
5905
#if HAVE_LIBPNG
5906
    { "libpng", load_with_libpng, loader_can_try_libpng, 1 },
5907
#endif
5908
    { "builtin", load_with_builtin, NULL, 1 },
5909
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
5910
    { "gnome-thumbnailer", load_with_gnome_thumbnailer, NULL, 0 },
5911
#endif
5912
};
5913

5914
static int
5915
loader_entry_available(char const *name)
18✔
5916
{
5917
    size_t index;
5918
    size_t entry_count;
5919

5920
    if (name == NULL) {
18!
5921
        return 0;
×
5922
    }
5923

5924
    entry_count = sizeof(sixel_loader_entries) /
18✔
5925
                  sizeof(sixel_loader_entries[0]);
5926

5927
    for (index = 0; index < entry_count; ++index) {
48!
5928
        if (sixel_loader_entries[index].name != NULL &&
42!
5929
                strcmp(sixel_loader_entries[index].name, name) == 0) {
42✔
5930
            return 1;
12✔
5931
        }
5932
    }
12✔
5933

5934
    return 0;
6✔
5935
}
6✔
5936

5937
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
5938
static void
5939
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
4✔
5940
{
5941
    if (buffer == NULL || capacity == 0) {
4✔
5942
        return;
5943
    }
5944

5945
    buffer[0] = '\0';
4✔
5946
    if (source == NULL) {
4!
5947
        return;
5948
    }
5949

5950
    if (!CFStringGetCString(source,
8✔
5951
                             buffer,
4✔
5952
                             (CFIndex)capacity,
4✔
5953
                             kCFStringEncodingUTF8)) {
5954
        buffer[0] = '\0';
5955
    }
5956
}
4✔
5957

5958
static int
5959
loader_quicklook_can_decode(sixel_chunk_t const *pchunk,
3✔
5960
                            char const *filename)
5961
{
5962
    char const *path;
5963
    CFStringRef path_ref;
5964
    CFURLRef url;
5965
    CGFloat max_dimension;
5966
    CGSize max_size;
5967
    CGImageRef image;
5968
    int result;
5969

5970
    path = NULL;
3✔
5971
    path_ref = NULL;
3✔
5972
    url = NULL;
3✔
5973
    image = NULL;
3✔
5974
    result = 0;
3✔
5975

5976
    if (pchunk != NULL && pchunk->source_path != NULL) {
3✔
5977
        path = pchunk->source_path;
2✔
5978
    } else if (filename != NULL) {
2✔
5979
        path = filename;
5980
    }
5981

5982
    if (path == NULL || strcmp(path, "-") == 0 ||
3✔
5983
            strstr(path, "://") != NULL) {
2✔
5984
        return 0;
1✔
5985
    }
5986

5987
    path_ref = CFStringCreateWithCString(kCFAllocatorDefault,
4✔
5988
                                         path,
2✔
5989
                                         kCFStringEncodingUTF8);
5990
    if (path_ref == NULL) {
2!
5991
        return 0;
5992
    }
5993

5994
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
4✔
5995
                                        path_ref,
2✔
5996
                                        kCFURLPOSIXPathStyle,
5997
                                        false);
5998
    CFRelease(path_ref);
2✔
5999
    path_ref = NULL;
2✔
6000
    if (url == NULL) {
2!
6001
        return 0;
6002
    }
6003

6004
    if (thumbnailer_size_hint > 0) {
2!
6005
        max_dimension = (CGFloat)thumbnailer_size_hint;
2✔
6006
    } else {
2✔
6007
        max_dimension = (CGFloat)SIXEL_THUMBNAILER_DEFAULT_SIZE;
6008
    }
6009
    max_size.width = max_dimension;
2✔
6010
    max_size.height = max_dimension;
2✔
6011

6012
#if HAVE_QUICKLOOK_THUMBNAILING
6013
    image = sixel_quicklook_thumbnail_create(url, max_size);
2✔
6014
    if (image == NULL) {
2✔
6015
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6016
#  pragma clang diagnostic push
6017
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
6018
# endif
6019
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
4✔
6020
                                       url,
2✔
6021
                                       max_size,
6022
                                       NULL);
6023
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6024
#  pragma clang diagnostic pop
6025
# endif
6026
    }
2✔
6027
#else
6028
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6029
#  pragma clang diagnostic push
6030
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
6031
# endif
6032
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
6033
                                   url,
6034
                                   max_size,
6035
                                   NULL);
6036
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6037
#  pragma clang diagnostic pop
6038
# endif
6039
#endif
6040

6041
    if (image != NULL) {
4✔
6042
        result = 1;
6043
        CGImageRelease(image);
6044
        image = NULL;
6045
    }
6046

6047
    CFRelease(url);
2✔
6048
    url = NULL;
2✔
6049

6050
    return result;
2✔
6051
}
3✔
6052
#else
6053
static int
6054
loader_quicklook_can_decode(sixel_chunk_t const *pchunk,
6055
                            char const *filename)
6056
{
6057
    (void)pchunk;
6058
    (void)filename;
6059
    return 0;
6060
}
6061
#endif
6062

6063
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6064
static void
6065
loader_probe_gnome_thumbnailers(char const *mime_type,
9✔
6066
                                int *has_directories,
6067
                                int *has_match)
6068
{
6069
    struct thumbnailer_string_list *directories;
6070
    struct thumbnailer_entry info;
6071
    size_t dir_index;
6072
    DIR *dir;
6073
    struct dirent *entry;
6074
    char *thumbnailer_path;
6075
    int match;
6076
    int directories_present;
6077
    size_t name_length;
6078

6079
    directories = NULL;
9✔
6080
    dir_index = 0;
9✔
6081
    dir = NULL;
9✔
6082
    entry = NULL;
9✔
6083
    thumbnailer_path = NULL;
9✔
6084
    match = 0;
9✔
6085
    directories_present = 0;
9✔
6086
    name_length = 0;
9✔
6087

6088
    if (has_directories != NULL) {
9!
6089
        *has_directories = 0;
9✔
6090
    }
3✔
6091
    if (has_match != NULL) {
9!
6092
        *has_match = 0;
9✔
6093
    }
3✔
6094

6095
    directories = thumbnailer_collect_directories();
9✔
6096
    if (directories == NULL) {
9!
6097
        return;
×
6098
    }
6099

6100
    if (directories->length > 0) {
9!
6101
        directories_present = 1;
9✔
6102
        if (has_directories != NULL) {
9!
6103
            *has_directories = 1;
9✔
6104
        }
3✔
6105
    }
3✔
6106

6107
    thumbnailer_entry_init(&info);
10✔
6108

6109
    if (mime_type != NULL && mime_type[0] != '\0') {
10!
6110
        for (dir_index = 0; dir_index < directories->length && match == 0;
24!
6111
                ++dir_index) {
18✔
6112
            dir = opendir(directories->items[dir_index]);
18✔
6113
            if (dir == NULL) {
18!
6114
                continue;
18✔
6115
            }
6116
            while (match == 0 && (entry = readdir(dir)) != NULL) {
×
6117
                thumbnailer_entry_clear(&info);
×
6118
                thumbnailer_entry_init(&info);
×
6119
                name_length = strlen(entry->d_name);
×
6120
                if (name_length < 12 ||
×
6121
                        strcmp(entry->d_name + name_length - 12,
×
6122
                               ".thumbnailer") != 0) {
6123
                    continue;
×
6124
                }
6125
                thumbnailer_path = thumbnailer_join_paths(
×
6126
                    directories->items[dir_index],
×
6127
                    entry->d_name);
×
6128
                if (thumbnailer_path == NULL) {
×
6129
                    continue;
×
6130
                }
6131
                if (!thumbnailer_parse_file(thumbnailer_path, &info)) {
×
6132
                    free(thumbnailer_path);
×
6133
                    thumbnailer_path = NULL;
×
6134
                    continue;
×
6135
                }
6136
                free(thumbnailer_path);
×
6137
                thumbnailer_path = NULL;
×
6138
                if (!thumbnailer_has_tryexec(info.tryexec)) {
×
6139
                    continue;
×
6140
                }
6141
                if (thumbnailer_supports_mime(&info, mime_type)) {
×
6142
                    match = 1;
×
6143
                }
6144
            }
6145
            closedir(dir);
×
6146
            dir = NULL;
×
6147
        }
6148
    }
2✔
6149

6150
    thumbnailer_entry_clear(&info);
9✔
6151
    thumbnailer_string_list_free(directories);
9✔
6152

6153
    if (directories_present && has_match != NULL) {
9!
6154
        *has_match = match;
9✔
6155
    }
3✔
6156
}
3✔
6157
#endif
6158

6159
static void
6160
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
9✔
6161
                          char const *filename)
6162
{
6163
    enum { description_length = 128 };
6164
    enum { uttype_length = 128 };
6165
    enum { extension_length = 32 };
6166
    enum { message_length = 768 };
6167
    char message[message_length];
6168
    char type_value[description_length];
6169
    char extension_text[extension_length + 2];
6170
    char uttype[uttype_length];
6171
    char desc_buffer[description_length];
6172
    char extension[extension_length];
6173
    char const *path;
6174
    char const *display_path;
6175
    char const *metadata_path;
6176
    char const *description_text;
6177
    char *mime_string;
6178
    char *description_string;
6179
    size_t offset;
6180
    int quicklook_available;
6181
    int quicklook_supported;
6182
    int gnome_available;
6183
    int gnome_has_dirs;
6184
    int gnome_has_match;
6185
    int suggestions;
6186

6187
    message[0] = '\0';
9✔
6188
    type_value[0] = '\0';
9✔
6189
    extension_text[0] = '\0';
9✔
6190
    uttype[0] = '\0';
9✔
6191
    desc_buffer[0] = '\0';
9✔
6192
    extension[0] = '\0';
9✔
6193
    path = NULL;
9✔
6194
    display_path = "(stdin)";
9✔
6195
    metadata_path = NULL;
9✔
6196
    description_text = NULL;
9✔
6197
    mime_string = NULL;
9✔
6198
    description_string = NULL;
9✔
6199
    offset = 0u;
9✔
6200
    quicklook_available = 0;
9✔
6201
    quicklook_supported = 0;
9✔
6202
    gnome_available = 0;
9✔
6203
    gnome_has_dirs = 0;
9✔
6204
    gnome_has_match = 0;
9✔
6205
    suggestions = 0;
9✔
6206

6207
    if (pchunk != NULL && pchunk->source_path != NULL) {
9!
6208
        path = pchunk->source_path;
6✔
6209
    } else if (filename != NULL) {
4!
6210
        path = filename;
×
6211
    }
6212

6213
    if (path != NULL && strcmp(path, "-") != 0) {
9!
6214
        display_path = path;
6✔
6215
    }
2✔
6216

6217
    if (path != NULL && strcmp(path, "-") != 0 &&
8!
6218
            strstr(path, "://") == NULL) {
6!
6219
        metadata_path = path;
6✔
6220
    }
2✔
6221

6222
    loader_extract_extension(path, extension, sizeof(extension));
8✔
6223

6224
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6225
    if (metadata_path != NULL) {
8✔
6226
        /*
6227
         * Collect MIME metadata via file(1) when fork() and friends are
6228
         * available.  Windows builds compiled with clang64 lack these
6229
         * interfaces, so the thumbnail helpers remain disabled there.
6230
         */
6231
        mime_string = thumbnailer_guess_content_type(metadata_path);
6✔
6232
        description_string = thumbnailer_run_file(metadata_path, NULL);
6✔
6233
    }
2✔
6234
#else
6235
    (void)metadata_path;
6236
#endif
6237

6238
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
6239
#if defined(__clang__)
6240
    /*
6241
     * Allow use of legacy UTType C APIs when compiling with the
6242
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
6243
     * so we must intentionally silence the deprecation warnings here.
6244
     */
6245
#pragma clang diagnostic push
6246
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
6247
#endif
6248
    {
6249
        CFStringRef uti_ref;
6250
        CFStringRef mime_ref;
6251
        CFStringRef ext_ref;
6252
        CFStringRef desc_ref;
6253
        CFStringRef preferred_mime;
6254
        char uti_local[uttype_length];
6255
        char desc_local[description_length];
6256
        char mime_local[64];
6257

6258
        uti_ref = NULL;
2✔
6259
        mime_ref = NULL;
2✔
6260
        ext_ref = NULL;
2✔
6261
        desc_ref = NULL;
2✔
6262
        preferred_mime = NULL;
2✔
6263
        uti_local[0] = '\0';
2✔
6264
        desc_local[0] = '\0';
2✔
6265
        mime_local[0] = '\0';
2✔
6266

6267
        if (mime_string != NULL) {
2✔
6268
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
4✔
6269
                                                 mime_string,
2✔
6270
                                                 kCFStringEncodingUTF8);
6271
        }
2✔
6272
        if (mime_ref != NULL) {
2✔
6273
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
2✔
6274
                kUTTagClassMIMEType,
2✔
6275
                mime_ref,
2✔
6276
                NULL);
6277
        }
2✔
6278
        if (uti_ref == NULL && extension[0] != '\0') {
3✔
6279
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
6280
                                                extension,
6281
                                                kCFStringEncodingUTF8);
6282
            if (ext_ref != NULL) {
6283
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
6284
                    kUTTagClassFilenameExtension,
6285
                    ext_ref,
6286
                    NULL);
6287
            }
6288
        }
6289
        if (uti_ref != NULL) {
2✔
6290
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
2✔
6291
            desc_ref = UTTypeCopyDescription(uti_ref);
2✔
6292
            if (desc_ref != NULL) {
2✔
6293
                loader_copy_cfstring(desc_ref,
4✔
6294
                                     desc_local,
2✔
6295
                                     sizeof(desc_local));
6296
                CFRelease(desc_ref);
2✔
6297
                desc_ref = NULL;
2✔
6298
            }
2✔
6299
            if (mime_string == NULL) {
4✔
6300
                preferred_mime = UTTypeCopyPreferredTagWithClass(
6301
                    uti_ref,
6302
                    kUTTagClassMIMEType);
6303
                if (preferred_mime != NULL) {
6304
                    loader_copy_cfstring(preferred_mime,
6305
                                         mime_local,
6306
                                         sizeof(mime_local));
6307
                    CFRelease(preferred_mime);
6308
                    preferred_mime = NULL;
6309
                }
6310
                if (mime_local[0] != '\0') {
6311
                    mime_string = thumbnailer_strdup(mime_local);
6312
                }
6313
            }
6314
        }
2✔
6315
        if (mime_ref != NULL) {
2✔
6316
            CFRelease(mime_ref);
2✔
6317
        }
2✔
6318
        if (ext_ref != NULL) {
4✔
6319
            CFRelease(ext_ref);
6320
        }
6321
        if (uti_ref != NULL) {
2✔
6322
            CFRelease(uti_ref);
2✔
6323
        }
2✔
6324
        if (uti_local[0] != '\0') {
2✔
6325
            sixel_compat_snprintf(uttype,
4✔
6326
                                  sizeof(uttype),
6327
                                  "%s",
6328
                                  uti_local);
2✔
6329
        }
2✔
6330
        if (desc_local[0] != '\0') {
2✔
6331
            sixel_compat_snprintf(desc_buffer,
4✔
6332
                                  sizeof(desc_buffer),
6333
                                  "%s",
6334
                                  desc_local);
2✔
6335
        }
2✔
6336
    }
6337
#if defined(__clang__)
6338
#pragma clang diagnostic pop
6339
#endif
6340
#endif
6341

6342
    if (description_string != NULL && description_string[0] != '\0') {
8!
6343
        description_text = description_string;
6✔
6344
    } else if (desc_buffer[0] != '\0') {
5!
6345
        description_text = desc_buffer;
×
6346
    } else {
6347
        description_text = "unknown content";
3✔
6348
    }
6349

6350
    sixel_compat_snprintf(type_value,
12✔
6351
                          sizeof(type_value),
6352
                          "%s",
6353
                          description_text);
3✔
6354

6355
    loader_append_chunk(message,
9✔
6356
                        sizeof(message),
6357
                        &offset,
6358
                        "diagnostic:\n");
6359
    loader_append_key_value(message,
12✔
6360
                            sizeof(message),
6361
                            &offset,
6362
                            "file",
6363
                            display_path);
3✔
6364
    loader_append_key_value(message,
12✔
6365
                            sizeof(message),
6366
                            &offset,
6367
                            "type",
6368
                            type_value);
3✔
6369

6370
    if (mime_string != NULL && mime_string[0] != '\0') {
9!
6371
        loader_append_key_value(message,
8✔
6372
                                sizeof(message),
6373
                                &offset,
6374
                                "mime",
6375
                                mime_string);
2✔
6376
    }
2✔
6377

6378
    if (uttype[0] != '\0') {
8!
6379
        loader_append_key_value(message,
4✔
6380
                                sizeof(message),
6381
                                &offset,
6382
                                "uti",
6383
                                uttype);
2✔
6384
    }
2✔
6385

6386
    if (extension[0] != '\0') {
10!
6387
        sixel_compat_snprintf(extension_text,
×
6388
                              sizeof(extension_text),
6389
                              ".%s",
6390
                              extension);
6391
        loader_append_key_value(message,
×
6392
                                sizeof(message),
6393
                                &offset,
6394
                                "extension",
6395
                                extension_text);
6396
    }
6397

6398
    loader_append_chunk(message,
9✔
6399
                        sizeof(message),
6400
                        &offset,
6401
                        "  suggestions:\n");
6402

6403
    quicklook_available = loader_entry_available("quicklook");
9✔
6404
    if (quicklook_available) {
9!
6405
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
3✔
6406
    }
3✔
6407
    if (quicklook_supported) {
12!
6408
        loader_append_chunk(message,
×
6409
                            sizeof(message),
6410
                            &offset,
6411
                            "    - QuickLook rendered a preview during "
6412
                            "the probe; try -j quicklook.\n");
6413
        suggestions += 1;
×
6414
    }
6415

6416
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
6417
    gnome_available = loader_entry_available("gnome-thumbnailer");
9✔
6418
    if (gnome_available) {
9!
6419
        loader_probe_gnome_thumbnailers(mime_string,
9✔
6420
                                        &gnome_has_dirs,
6421
                                        &gnome_has_match);
6422
        if (gnome_has_dirs && gnome_has_match) {
9!
6423
            loader_append_chunk(message,
×
6424
                                sizeof(message),
6425
                                &offset,
6426
                                "    - GNOME thumbnailer definitions match "
6427
                                "this MIME type; try -j gnome-thumbnailer.\n"
6428
                                );
6429
            suggestions += 1;
×
6430
        }
6431
    }
3✔
6432
#else
6433
    (void)gnome_available;
6434
    (void)gnome_has_dirs;
6435
    (void)gnome_has_match;
6436
#endif
6437

6438
    if (suggestions == 0) {
9!
6439
        loader_append_chunk(message,
9✔
6440
                            sizeof(message),
6441
                            &offset,
6442
                            "    (no thumbnail helper hints)\n");
6443
    }
3✔
6444

6445
    if (suggestions > 0) {
9!
6446
        loader_append_chunk(message,
×
6447
                            sizeof(message),
6448
                            &offset,
6449
                            "  hint       : Enable one of the suggested "
6450
                            "loaders with -j.\n");
6451
    } else {
6452
        loader_append_chunk(message,
9✔
6453
                            sizeof(message),
6454
                            &offset,
6455
                            "  hint       : Convert the file to PNG or "
6456
                            "enable optional loaders.\n");
6457
    }
6458

6459
    sixel_helper_set_additional_message(message);
9✔
6460

6461
    free(mime_string);
9✔
6462
    free(description_string);
9✔
6463
}
9✔
6464

6465
SIXELAPI SIXELSTATUS
6466
sixel_loader_new(
457✔
6467
    sixel_loader_t   /* out */ **pploader,
6468
    sixel_allocator_t/* in */  *allocator)
6469
{
6470
    SIXELSTATUS status = SIXEL_FALSE;
457✔
6471
    sixel_loader_t *loader;
6472
    sixel_allocator_t *local_allocator;
6473

6474
    loader = NULL;
457✔
6475
    local_allocator = allocator;
457✔
6476

6477
    if (pploader == NULL) {
457!
6478
        sixel_helper_set_additional_message(
×
6479
            "sixel_loader_new: pploader is null.");
6480
        status = SIXEL_BAD_ARGUMENT;
×
6481
        goto end;
×
6482
    }
6483

6484
    if (local_allocator == NULL) {
457!
6485
        status = sixel_allocator_new(&local_allocator,
×
6486
                                     NULL,
6487
                                     NULL,
6488
                                     NULL,
6489
                                     NULL);
6490
        if (SIXEL_FAILED(status)) {
×
6491
            goto end;
×
6492
        }
6493
    } else {
6494
        sixel_allocator_ref(local_allocator);
457✔
6495
    }
6496

6497
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
457✔
6498
                                                      sizeof(*loader));
6499
    if (loader == NULL) {
457!
6500
        sixel_helper_set_additional_message(
×
6501
            "sixel_loader_new: sixel_allocator_malloc() failed.");
6502
        status = SIXEL_BAD_ALLOCATION;
×
6503
        sixel_allocator_unref(local_allocator);
×
6504
        goto end;
×
6505
    }
6506

6507
    loader->ref = 1;
457✔
6508
    loader->fstatic = 0;
457✔
6509
    loader->fuse_palette = 0;
457✔
6510
    loader->reqcolors = SIXEL_PALETTE_MAX;
457✔
6511
    loader->bgcolor[0] = 0;
457✔
6512
    loader->bgcolor[1] = 0;
457✔
6513
    loader->bgcolor[2] = 0;
457✔
6514
    loader->has_bgcolor = 0;
457✔
6515
    loader->loop_control = SIXEL_LOOP_AUTO;
457✔
6516
    loader->finsecure = 0;
457✔
6517
    loader->cancel_flag = NULL;
457✔
6518
    loader->context = NULL;
457✔
6519
    loader->assessment = NULL;
457✔
6520
    loader->loader_order = NULL;
457✔
6521
    loader->allocator = local_allocator;
457✔
6522
    loader->last_loader_name[0] = '\0';
457✔
6523
    loader->last_source_path[0] = '\0';
457✔
6524
    loader->last_input_bytes = 0u;
457✔
6525

6526
    *pploader = loader;
457✔
6527
    status = SIXEL_OK;
457✔
6528

6529
end:
304✔
6530
    return status;
457✔
6531
}
6532

6533
SIXELAPI void
6534
sixel_loader_ref(
5,006✔
6535
    sixel_loader_t /* in */ *loader)
6536
{
6537
    if (loader == NULL) {
5,006!
6538
        return;
×
6539
    }
6540

6541
    ++loader->ref;
5,006✔
6542
}
1,676✔
6543

6544
SIXELAPI void
6545
sixel_loader_unref(
5,463✔
6546
    sixel_loader_t /* in */ *loader)
6547
{
6548
    sixel_allocator_t *allocator;
6549

6550
    if (loader == NULL) {
5,463!
6551
        return;
×
6552
    }
6553

6554
    if (--loader->ref == 0) {
5,463✔
6555
        allocator = loader->allocator;
457✔
6556
        sixel_allocator_free(allocator, loader->loader_order);
457✔
6557
        sixel_allocator_free(allocator, loader);
457✔
6558
        sixel_allocator_unref(allocator);
457✔
6559
    }
153✔
6560
}
1,829✔
6561

6562
SIXELAPI SIXELSTATUS
6563
sixel_loader_setopt(
4,549✔
6564
    sixel_loader_t /* in */ *loader,
6565
    int            /* in */ option,
6566
    void const     /* in */ *value)
6567
{
6568
    SIXELSTATUS status = SIXEL_FALSE;
4,549✔
6569
    int const *flag;
6570
    unsigned char const *color;
6571
    char const *order;
6572
    char *copy;
6573
    sixel_allocator_t *allocator;
6574

6575
    flag = NULL;
4,549✔
6576
    color = NULL;
4,549✔
6577
    order = NULL;
4,549✔
6578
    copy = NULL;
4,549✔
6579
    allocator = NULL;
4,549✔
6580

6581
    if (loader == NULL) {
4,549!
6582
        sixel_helper_set_additional_message(
×
6583
            "sixel_loader_setopt: loader is null.");
6584
        status = SIXEL_BAD_ARGUMENT;
×
6585
        goto end0;
×
6586
    }
6587

6588
    sixel_loader_ref(loader);
4,549✔
6589

6590
    switch (option) {
4,549!
6591
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
304✔
6592
        flag = (int const *)value;
457✔
6593
        loader->fstatic = flag != NULL ? *flag : 0;
457!
6594
        status = SIXEL_OK;
457✔
6595
        break;
457✔
6596
    case SIXEL_LOADER_OPTION_USE_PALETTE:
304✔
6597
        flag = (int const *)value;
457✔
6598
        loader->fuse_palette = flag != NULL ? *flag : 0;
457!
6599
        status = SIXEL_OK;
457✔
6600
        break;
457✔
6601
    case SIXEL_LOADER_OPTION_REQCOLORS:
304✔
6602
        flag = (int const *)value;
457✔
6603
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
457!
6604
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
457!
6605
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
6606
        }
6607
        status = SIXEL_OK;
457✔
6608
        break;
457✔
6609
    case SIXEL_LOADER_OPTION_BGCOLOR:
304✔
6610
        if (value == NULL) {
457✔
6611
            loader->has_bgcolor = 0;
439✔
6612
        } else {
147✔
6613
            color = (unsigned char const *)value;
18✔
6614
            loader->bgcolor[0] = color[0];
18✔
6615
            loader->bgcolor[1] = color[1];
18✔
6616
            loader->bgcolor[2] = color[2];
18✔
6617
            loader->has_bgcolor = 1;
18✔
6618
        }
6619
        status = SIXEL_OK;
457✔
6620
        break;
457✔
6621
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
304✔
6622
        flag = (int const *)value;
457✔
6623
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
457!
6624
        status = SIXEL_OK;
457✔
6625
        break;
457✔
6626
    case SIXEL_LOADER_OPTION_INSECURE:
304✔
6627
        flag = (int const *)value;
457✔
6628
        loader->finsecure = flag != NULL ? *flag : 0;
457!
6629
        status = SIXEL_OK;
457✔
6630
        break;
457✔
6631
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
304✔
6632
        loader->cancel_flag = (int const *)value;
457✔
6633
        status = SIXEL_OK;
457✔
6634
        break;
457✔
6635
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
304✔
6636
        allocator = loader->allocator;
457✔
6637
        sixel_allocator_free(allocator, loader->loader_order);
457✔
6638
        loader->loader_order = NULL;
457✔
6639
        if (value != NULL) {
457!
6640
            order = (char const *)value;
×
6641
            copy = loader_strdup(order, allocator);
×
6642
            if (copy == NULL) {
×
6643
                sixel_helper_set_additional_message(
×
6644
                    "sixel_loader_setopt: loader_strdup() failed.");
6645
                status = SIXEL_BAD_ALLOCATION;
×
6646
                goto end;
×
6647
            }
6648
            loader->loader_order = copy;
×
6649
        }
6650
        status = SIXEL_OK;
457✔
6651
        break;
457✔
6652
    case SIXEL_LOADER_OPTION_CONTEXT:
304✔
6653
        loader->context = (void *)value;
457✔
6654
        loader->assessment = NULL;
457✔
6655
        status = SIXEL_OK;
457✔
6656
        break;
457✔
6657
    case SIXEL_LOADER_OPTION_ASSESSMENT:
290✔
6658
        loader->assessment = (sixel_assessment_t *)value;
436✔
6659
        status = SIXEL_OK;
436✔
6660
        break;
436✔
6661
    default:
6662
        sixel_helper_set_additional_message(
×
6663
            "sixel_loader_setopt: unknown option.");
6664
        status = SIXEL_BAD_ARGUMENT;
×
6665
        goto end;
×
6666
    }
1,523✔
6667

6668
end:
3,026✔
6669
    sixel_loader_unref(loader);
4,549✔
6670

6671
end0:
3,026✔
6672
    return status;
4,549✔
6673
}
6674

6675
SIXELAPI char const *
6676
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
842✔
6677
{
6678
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
842!
6679
        return NULL;
×
6680
    }
6681
    return loader->last_loader_name;
842✔
6682
}
282✔
6683

6684
SIXELAPI char const *
6685
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
704✔
6686
{
6687
    if (loader == NULL || loader->last_source_path[0] == '\0') {
704!
6688
        return NULL;
138✔
6689
    }
6690
    return loader->last_source_path;
566✔
6691
}
236✔
6692

6693
SIXELAPI size_t
6694
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
421✔
6695
{
6696
    if (loader == NULL) {
421!
6697
        return 0u;
×
6698
    }
6699
    return loader->last_input_bytes;
421✔
6700
}
141✔
6701

6702
SIXELAPI SIXELSTATUS
6703
sixel_loader_load_file(
457✔
6704
    sixel_loader_t         /* in */ *loader,
6705
    char const             /* in */ *filename,
6706
    sixel_load_image_function /* in */ fn_load)
6707
{
6708
    SIXELSTATUS status = SIXEL_FALSE;
457✔
6709
    sixel_chunk_t *pchunk;
6710
    sixel_loader_entry_t const *plan[
6711
        sizeof(sixel_loader_entries) / sizeof(sixel_loader_entries[0])];
6712
    size_t entry_count;
6713
    size_t plan_length;
6714
    size_t plan_index;
6715
    unsigned char *bgcolor;
6716
    int reqcolors;
6717
    sixel_assessment_t *assessment;
6718

6719
    pchunk = NULL;
457✔
6720
    entry_count = 0;
457✔
6721
    plan_length = 0;
457✔
6722
    plan_index = 0;
457✔
6723
    bgcolor = NULL;
457✔
6724
    reqcolors = 0;
457✔
6725
    assessment = NULL;
457✔
6726

6727
    if (loader == NULL) {
457!
6728
        sixel_helper_set_additional_message(
×
6729
            "sixel_loader_load_file: loader is null.");
6730
        status = SIXEL_BAD_ARGUMENT;
×
6731
        goto end0;
×
6732
    }
6733

6734
    sixel_loader_ref(loader);
457✔
6735

6736
    entry_count = sizeof(sixel_loader_entries) /
457✔
6737
                  sizeof(sixel_loader_entries[0]);
6738

6739
    reqcolors = loader->reqcolors;
457✔
6740
    if (reqcolors > SIXEL_PALETTE_MAX) {
457!
6741
        reqcolors = SIXEL_PALETTE_MAX;
×
6742
    }
6743

6744
    assessment = loader->assessment;
457✔
6745

6746
    /*
6747
     *  Assessment pipeline sketch:
6748
     *
6749
     *      +-------------+        +--------------+
6750
     *      | chunk read | ----->  | image decode |
6751
     *      +-------------+        +--------------+
6752
     *
6753
     *  The loader owns the hand-off.  Chunk I/O ends before any decoder runs,
6754
     *  so we time the read span in the encoder and pivot to decode once the
6755
     *  chunk is populated.
6756
     */
6757

6758
    status = sixel_chunk_new(&pchunk,
457✔
6759
                             filename,
153✔
6760
                             loader->finsecure,
153✔
6761
                             loader->cancel_flag,
153✔
6762
                             loader->allocator);
153✔
6763
    if (status != SIXEL_OK) {
457✔
6764
        goto end;
6✔
6765
    }
6766

6767
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
451!
6768
        status = SIXEL_OK;
×
6769
        goto end;
×
6770
    }
6771

6772
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
451!
6773
        status = SIXEL_LOGIC_ERROR;
×
6774
        goto end;
×
6775
    }
6776

6777
    if (loader->has_bgcolor) {
451✔
6778
        bgcolor = loader->bgcolor;
18✔
6779
    }
6✔
6780

6781
    status = SIXEL_FALSE;
311✔
6782
    if (assessment != NULL) {
311✔
6783
        sixel_assessment_stage_transition(
3✔
6784
            assessment,
1✔
6785
            SIXEL_ASSESSMENT_STAGE_IMAGE_DECODE);
6786
    }
1✔
6787
    plan_length = loader_build_plan(loader->loader_order,
602✔
6788
                                    sixel_loader_entries,
6789
                                    entry_count,
151✔
6790
                                    plan,
151✔
6791
                                    entry_count);
151✔
6792

6793
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
519✔
6794
        if (plan[plan_index] == NULL) {
510!
6795
            continue;
×
6796
        }
6797
        if (plan[plan_index]->predicate != NULL &&
510!
6798
            plan[plan_index]->predicate(pchunk) == 0) {
×
6799
            continue;
×
6800
        }
6801
        loader_trace_try(plan[plan_index]->name);
510✔
6802
        status = plan[plan_index]->backend(pchunk,
720✔
6803
                                           loader->fstatic,
210✔
6804
                                           loader->fuse_palette,
210✔
6805
                                           reqcolors,
210✔
6806
                                           bgcolor,
210✔
6807
                                           loader->loop_control,
210✔
6808
                                           fn_load,
210✔
6809
                                           loader->context);
210✔
6810
        loader_trace_result(plan[plan_index]->name, status);
510✔
6811
        if (SIXEL_SUCCEEDED(status)) {
510✔
6812
            break;
442✔
6813
        }
6814
    }
62✔
6815

6816
    if (SIXEL_FAILED(status)) {
451✔
6817
        loader_publish_diagnostic(pchunk, filename);
9✔
6818
        goto end;
9✔
6819
    }
6820

6821
    if (plan_index < plan_length &&
590!
6822
            plan[plan_index] != NULL &&
442!
6823
            plan[plan_index]->name != NULL) {
442!
6824
        (void)snprintf(loader->last_loader_name,
442✔
6825
                       sizeof(loader->last_loader_name),
6826
                       "%s",
6827
                       plan[plan_index]->name);
294✔
6828
    } else {
148✔
6829
        loader->last_loader_name[0] = '\0';
×
6830
    }
6831
    loader->last_input_bytes = pchunk->size;
442✔
6832
    if (pchunk->source_path != NULL) {
590✔
6833
        size_t path_len;
6834

6835
        path_len = strlen(pchunk->source_path);
304✔
6836
        if (path_len >= sizeof(loader->last_source_path)) {
304!
6837
            path_len = sizeof(loader->last_source_path) - 1u;
×
6838
        }
6839
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
304✔
6840
        loader->last_source_path[path_len] = '\0';
304✔
6841
    } else {
102✔
6842
        loader->last_source_path[0] = '\0';
138✔
6843
    }
6844

6845
end:
304✔
6846
    sixel_chunk_destroy(pchunk);
457✔
6847
    sixel_loader_unref(loader);
457✔
6848

6849
end0:
304✔
6850
    return status;
457✔
6851
}
6852

6853
/* load image from file */
6854

6855
SIXELAPI SIXELSTATUS
6856
sixel_helper_load_image_file(
×
6857
    char const                /* in */     *filename,     /* source file name */
6858
    int                       /* in */     fstatic,       /* whether to extract static image
6859
                                                             from animated gif */
6860
    int                       /* in */     fuse_palette,  /* whether to use paletted image,
6861
                                                             set non-zero value to try to get
6862
                                                             paletted image */
6863
    int                       /* in */     reqcolors,     /* requested number of colors,
6864
                                                             should be equal or less than
6865
                                                             SIXEL_PALETTE_MAX */
6866
    unsigned char             /* in */     *bgcolor,      /* background color, may be NULL */
6867
    int                       /* in */     loop_control,  /* one of enum loopControl */
6868
    sixel_load_image_function /* in */     fn_load,       /* callback */
6869
    int                       /* in */     finsecure,     /* true if do not verify SSL */
6870
    int const                 /* in */     *cancel_flag,  /* cancel flag, may be NULL */
6871
    void                      /* in/out */ *context,      /* private data which is passed to
6872
                                                             callback function as an
6873
                                                             argument, may be NULL */
6874
    sixel_allocator_t         /* in */     *allocator     /* allocator object, may be NULL */
6875
)
6876
{
6877
    SIXELSTATUS status = SIXEL_FALSE;
×
6878
    sixel_loader_t *loader;
6879

6880
    loader = NULL;
×
6881

6882
    status = sixel_loader_new(&loader, allocator);
×
6883
    if (SIXEL_FAILED(status)) {
×
6884
        goto end;
×
6885
    }
6886

6887
    status = sixel_loader_setopt(loader,
×
6888
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
6889
                                 &fstatic);
6890
    if (SIXEL_FAILED(status)) {
×
6891
        goto end;
×
6892
    }
6893

6894
    status = sixel_loader_setopt(loader,
×
6895
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
6896
                                 &fuse_palette);
6897
    if (SIXEL_FAILED(status)) {
×
6898
        goto end;
×
6899
    }
6900

6901
    status = sixel_loader_setopt(loader,
×
6902
                                 SIXEL_LOADER_OPTION_REQCOLORS,
6903
                                 &reqcolors);
6904
    if (SIXEL_FAILED(status)) {
×
6905
        goto end;
×
6906
    }
6907

6908
    status = sixel_loader_setopt(loader,
×
6909
                                 SIXEL_LOADER_OPTION_BGCOLOR,
6910
                                 bgcolor);
6911
    if (SIXEL_FAILED(status)) {
×
6912
        goto end;
×
6913
    }
6914

6915
    status = sixel_loader_setopt(loader,
×
6916
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
6917
                                 &loop_control);
6918
    if (SIXEL_FAILED(status)) {
×
6919
        goto end;
×
6920
    }
6921

6922
    status = sixel_loader_setopt(loader,
×
6923
                                 SIXEL_LOADER_OPTION_INSECURE,
6924
                                 &finsecure);
6925
    if (SIXEL_FAILED(status)) {
×
6926
        goto end;
×
6927
    }
6928

6929
    status = sixel_loader_setopt(loader,
×
6930
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
6931
                                 cancel_flag);
6932
    if (SIXEL_FAILED(status)) {
×
6933
        goto end;
×
6934
    }
6935

6936
    status = sixel_loader_setopt(loader,
×
6937
                                 SIXEL_LOADER_OPTION_CONTEXT,
6938
                                 context);
6939
    if (SIXEL_FAILED(status)) {
×
6940
        goto end;
×
6941
    }
6942

6943
    status = sixel_loader_load_file(loader, filename, fn_load);
×
6944

6945
end:
6946
    sixel_loader_unref(loader);
×
6947

6948
    return status;
×
6949
}
6950

6951

6952
SIXELAPI size_t
6953
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
6✔
6954
{
6955
    size_t entry_count;
6956
    size_t limit;
6957
    size_t index;
6958

6959
    entry_count = sizeof(sixel_loader_entries) /
6✔
6960
                  sizeof(sixel_loader_entries[0]);
6961

6962
    if (names != NULL && max_names > 0) {
6!
6963
        limit = entry_count;
3✔
6964
        if (limit > max_names) {
3!
6965
            limit = max_names;
×
6966
        }
6967
        for (index = 0; index < limit; ++index) {
11✔
6968
            names[index] = sixel_loader_entries[index].name;
8✔
6969
        }
4✔
6970
    }
1✔
6971

6972
    return entry_count;
6✔
6973
}
6974

6975
#if HAVE_TESTS
6976
static int
6977
test1(void)
×
6978
{
6979
    int nret = EXIT_FAILURE;
×
6980
    unsigned char *ptr = malloc(16);
×
6981

6982
    nret = EXIT_SUCCESS;
×
6983
    goto error;
×
6984

6985
    nret = EXIT_SUCCESS;
6986

6987
error:
6988
    free(ptr);
×
6989
    return nret;
×
6990
}
6991

6992

6993
SIXELAPI int
6994
sixel_loader_tests_main(void)
×
6995
{
6996
    int nret = EXIT_FAILURE;
×
6997
    size_t i;
6998
    typedef int (* testcase)(void);
6999

7000
    static testcase const testcases[] = {
7001
        test1,
7002
    };
7003

7004
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
7005
        nret = testcases[i]();
×
7006
        if (nret != EXIT_SUCCESS) {
×
7007
            goto error;
×
7008
        }
7009
    }
7010

7011
    nret = EXIT_SUCCESS;
×
7012

7013
error:
7014
    return nret;
×
7015
}
7016
#endif  /* HAVE_TESTS */
7017

7018
/* emacs Local Variables:      */
7019
/* emacs mode: c               */
7020
/* emacs tab-width: 4          */
7021
/* emacs indent-tabs-mode: nil */
7022
/* emacs c-basic-offset: 4     */
7023
/* emacs End:                  */
7024
/* vim: set expandtab ts=4 sts=4 sw=4 : */
7025
/* 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