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

saitoha / libsixel / 18762699075

23 Oct 2025 09:36PM UTC coverage: 53.085% (+0.2%) from 52.906%
18762699075

push

github

saitoha
dequant: arrange parameters

5439 of 14848 branches covered (36.63%)

0 of 7 new or added lines in 1 file covered. (0.0%)

7 existing lines in 3 files now uncovered.

7933 of 14944 relevant lines covered (53.08%)

1052730.89 hits per line

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

61.88
/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
#include <ctype.h>
32
#include <stdarg.h>
33

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

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

99
#if HAVE_QUICKLOOK_THUMBNAILING
100
CGImageRef
101
sixel_quicklook_thumbnail_create(CFURLRef url, CGSize max_size);
102
#endif
103

104
#if !defined(HAVE_MEMCPY)
105
# define memcpy(d, s, n) (bcopy ((s), (d), (n)))
106
#endif
107

108
#include <sixel.h>
109
#include "loader.h"
110
#include "frame.h"
111
#include "chunk.h"
112
#include "frompnm.h"
113
#include "fromgif.h"
114
#include "allocator.h"
115

116
#define SIXEL_THUMBNAILER_DEFAULT_SIZE 512
117

118
static int loader_trace_enabled;
119
static int thumbnailer_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
120

121
#if HAVE_POSIX_SPAWNP
122
extern char **environ;
123
#endif
124

125
/*
126
 * sixel_helper_set_loader_trace
127
 *
128
 * Toggle verbose loader tracing so debugging output can be collected.
129
 *
130
 * Arguments:
131
 *     enable - non-zero enables tracing, zero disables it.
132
 */
133
void
134
sixel_helper_set_loader_trace(int enable)
452✔
135
{
136
    loader_trace_enabled = enable ? 1 : 0;
452✔
137
}
452✔
138

139
/*
140
 * sixel_helper_set_thumbnail_size_hint
141
 *
142
 * Record the caller's preferred maximum thumbnail dimension.
143
 *
144
 * Arguments:
145
 *     size - requested dimension in pixels; non-positive resets to default.
146
 */
147
void
148
sixel_helper_set_thumbnail_size_hint(int size)
443✔
149
{
150
    if (size > 0) {
443✔
151
        thumbnailer_size_hint = size;
93✔
152
    } else {
31✔
153
        thumbnailer_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
350✔
154
    }
155
}
443✔
156

157
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
158
/*
159
 * loader_trace_message
160
 *
161
 * Emit a formatted trace message when verbose loader tracing is enabled.
162
 *
163
 * Arguments:
164
 *     format - printf-style message template.
165
 *     ...    - arguments consumed according to the format string.
166
 */
167
static void
168
loader_trace_message(char const *format, ...)
111✔
169
{
170
    va_list args;
171

172
    if (!loader_trace_enabled) {
111!
173
        return;
111✔
174
    }
175

176
    fprintf(stderr, "libsixel: ");
×
177

178
    va_start(args, format);
×
179
#if defined(__clang__)
180
# pragma clang diagnostic push
181
# pragma clang diagnostic ignored "-Wformat-nonliteral"
182
#elif defined(__GNUC__)
183
# pragma GCC diagnostic push
184
# pragma GCC diagnostic ignored "-Wformat-nonliteral"
185
#endif
186
    vfprintf(stderr, format, args);
×
187
#if defined(__clang__)
188
# pragma clang diagnostic pop
189
#elif defined(__GNUC__)
190
# pragma GCC diagnostic pop
191
#endif
192
    va_end(args);
×
193

194
    fprintf(stderr, "\n");
×
195
}
37✔
196
#endif  /* HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK */
197

198
static void
199
loader_trace_try(char const *name)
556✔
200
{
201
    if (loader_trace_enabled) {
556✔
202
        fprintf(stderr, "libsixel: trying %s loader\n", name);
11✔
203
    }
5✔
204
}
556✔
205

206
static void
207
loader_trace_result(char const *name, SIXELSTATUS status)
556✔
208
{
209
    if (!loader_trace_enabled) {
556✔
210
        return;
545✔
211
    }
212
    if (SIXEL_SUCCEEDED(status)) {
11!
213
        fprintf(stderr, "libsixel: loader %s succeeded\n", name);
9✔
214
    } else {
3✔
215
        fprintf(stderr, "libsixel: loader %s failed (%s)\n",
4✔
216
                name, sixel_helper_format_error(status));
2✔
217
    }
218
}
262✔
219

220
sixel_allocator_t *stbi_allocator;
221

222
void *
223
stbi_malloc(size_t n)
712✔
224
{
225
    return sixel_allocator_malloc(stbi_allocator, n);
712✔
226
}
227

228
void *
229
stbi_realloc(void *p, size_t n)
182✔
230
{
231
    return sixel_allocator_realloc(stbi_allocator, p, n);
182✔
232
}
233

234
void
235
stbi_free(void *p)
965✔
236
{
237
    sixel_allocator_free(stbi_allocator, p);
965✔
238
}
965✔
239

240
#define STBI_MALLOC stbi_malloc
241
#define STBI_REALLOC stbi_realloc
242
#define STBI_FREE stbi_free
243

244
#define STBI_NO_STDIO 1
245
#define STB_IMAGE_IMPLEMENTATION 1
246
#define STBI_FAILURE_USERMSG 1
247
#if defined(_WIN32)
248
# define STBI_NO_THREAD_LOCALS 1  /* no tls */
249
#endif
250
#define STBI_NO_GIF
251
#define STBI_NO_PNM
252

253
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
254
# pragma GCC diagnostic push
255
# pragma GCC diagnostic ignored "-Wsign-conversion"
256
#endif
257
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
258
# pragma GCC diagnostic push
259
# pragma GCC diagnostic ignored "-Wstrict-overflow"
260
#endif
261
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
262
# pragma GCC diagnostic push
263
# pragma GCC diagnostic ignored "-Wswitch-default"
264
#endif
265
#if HAVE_DIAGNOSTIC_SHADOW
266
# pragma GCC diagnostic push
267
# pragma GCC diagnostic ignored "-Wshadow"
268
#endif
269
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
270
# pragma GCC diagnostic push
271
# pragma GCC diagnostic ignored "-Wdouble-promotion"
272
#endif
273
# if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
274
# pragma GCC diagnostic push
275
# pragma GCC diagnostic ignored "-Wunused-function"
276
#endif
277
# if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
278
# pragma GCC diagnostic push
279
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
280
#endif
281
#include "stb_image.h"
282
#if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
283
# pragma GCC diagnostic pop
284
#endif
285
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
286
# pragma GCC diagnostic pop
287
#endif
288
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
289
# pragma GCC diagnostic pop
290
#endif
291
#if HAVE_DIAGNOSTIC_SHADOW
292
# pragma GCC diagnostic pop
293
#endif
294
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
295
# pragma GCC diagnostic pop
296
#endif
297
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
298
# pragma GCC diagnostic pop
299
#endif
300
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
301
# pragma GCC diagnostic pop
302
#endif
303

304

305
# if HAVE_JPEG
306
/* import from @uobikiemukot's sdump loader.h */
307
static SIXELSTATUS
308
load_jpeg(unsigned char **result,
309
          unsigned char *data,
310
          size_t datasize,
311
          int *pwidth,
312
          int *pheight,
313
          int *ppixelformat,
314
          sixel_allocator_t *allocator)
315
{
316
    SIXELSTATUS status = SIXEL_JPEG_ERROR;
317
    JDIMENSION row_stride;
318
    size_t size;
319
    JSAMPARRAY buffer;
320
    struct jpeg_decompress_struct cinfo;
321
    struct jpeg_error_mgr pub;
322

323
    cinfo.err = jpeg_std_error(&pub);
324

325
    jpeg_create_decompress(&cinfo);
326
    jpeg_mem_src(&cinfo, data, datasize);
327
    jpeg_read_header(&cinfo, TRUE);
328

329
    /* disable colormap (indexed color), grayscale -> rgb */
330
    cinfo.quantize_colors = FALSE;
331
    cinfo.out_color_space = JCS_RGB;
332
    jpeg_start_decompress(&cinfo);
333

334
    if (cinfo.output_components != 3) {
335
        sixel_helper_set_additional_message(
336
            "load_jpeg: unknown pixel format.");
337
        status = SIXEL_BAD_INPUT;
338
        goto end;
339
    }
340

341
    *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
342

343
    if (cinfo.output_width > INT_MAX || cinfo.output_height > INT_MAX) {
344
        status = SIXEL_BAD_INTEGER_OVERFLOW;
345
        goto end;
346
    }
347
    *pwidth = (int)cinfo.output_width;
348
    *pheight = (int)cinfo.output_height;
349

350
    size = (size_t)(*pwidth * *pheight * cinfo.output_components);
351
    *result = (unsigned char *)sixel_allocator_malloc(allocator, size);
352
    if (*result == NULL) {
353
        sixel_helper_set_additional_message(
354
            "load_jpeg: sixel_allocator_malloc() failed.");
355
        status = SIXEL_BAD_ALLOCATION;
356
        goto end;
357
    }
358
    row_stride = cinfo.output_width * (unsigned int)cinfo.output_components;
359
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
360

361
    while (cinfo.output_scanline < cinfo.output_height) {
362
        jpeg_read_scanlines(&cinfo, buffer, 1);
363
        if (cinfo.err->num_warnings > 0) {
364
            sixel_helper_set_additional_message(
365
                "jpeg_read_scanlines: error/warining occuered.");
366
            status = SIXEL_BAD_INPUT;
367
            goto end;
368
        }
369
        memcpy(*result + (cinfo.output_scanline - 1) * row_stride, buffer[0], row_stride);
370
    }
371

372
    status = SIXEL_OK;
373

374
end:
375
    jpeg_finish_decompress(&cinfo);
376
    jpeg_destroy_decompress(&cinfo);
377

378
    return status;
379
}
380
# endif  /* HAVE_JPEG */
381

382

383
# if HAVE_LIBPNG
384
static void
385
read_png(png_structp png_ptr,
386
         png_bytep data,
387
         png_size_t length)
388
{
389
    sixel_chunk_t *pchunk = (sixel_chunk_t *)png_get_io_ptr(png_ptr);
390
    if (length > pchunk->size) {
391
        length = pchunk->size;
392
    }
393
    if (length > 0) {
394
        memcpy(data, pchunk->buffer, length);
395
        pchunk->buffer += length;
396
        pchunk->size -= length;
397
    }
398
}
399

400

401
static void
402
read_palette(png_structp png_ptr,
403
             png_infop info_ptr,
404
             unsigned char *palette,
405
             int ncolors,
406
             png_color *png_palette,
407
             png_color_16 *pbackground,
408
             int *transparent)
409
{
410
    png_bytep trans = NULL;
411
    int num_trans = 0;
412
    int i;
413

414
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
415
        png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
416
    }
417
    if (num_trans > 0) {
418
        *transparent = trans[0];
419
    }
420
    for (i = 0; i < ncolors; ++i) {
421
        if (pbackground && i < num_trans) {
422
            palette[i * 3 + 0] = ((0xff - trans[i]) * pbackground->red
423
                                   + trans[i] * png_palette[i].red) >> 8;
424
            palette[i * 3 + 1] = ((0xff - trans[i]) * pbackground->green
425
                                   + trans[i] * png_palette[i].green) >> 8;
426
            palette[i * 3 + 2] = ((0xff - trans[i]) * pbackground->blue
427
                                   + trans[i] * png_palette[i].blue) >> 8;
428
        } else {
429
            palette[i * 3 + 0] = png_palette[i].red;
430
            palette[i * 3 + 1] = png_palette[i].green;
431
            palette[i * 3 + 2] = png_palette[i].blue;
432
        }
433
    }
434
}
435

436
jmp_buf jmpbuf;
437

438
/* libpng error handler */
439
static void
440
png_error_callback(png_structp png_ptr, png_const_charp error_message)
441
{
442
    (void) png_ptr;
443

444
    sixel_helper_set_additional_message(error_message);
445
#if HAVE_SETJMP && HAVE_LONGJMP
446
    longjmp(jmpbuf, 1);
447
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
448
}
449

450

451
static SIXELSTATUS
452
load_png(unsigned char      /* out */ **result,
453
         unsigned char      /* in */  *buffer,
454
         size_t             /* in */  size,
455
         int                /* out */ *psx,
456
         int                /* out */ *psy,
457
         unsigned char      /* out */ **ppalette,
458
         int                /* out */ *pncolors,
459
         int                /* in */  reqcolors,
460
         int                /* out */ *pixelformat,
461
         unsigned char      /* out */ *bgcolor,
462
         int                /* out */ *transparent,
463
         sixel_allocator_t  /* in */  *allocator)
464
{
465
    SIXELSTATUS status;
466
    sixel_chunk_t read_chunk;
467
    png_uint_32 bitdepth;
468
    png_uint_32 png_status;
469
    png_structp png_ptr;
470
    png_infop info_ptr;
471
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
472
# pragma GCC diagnostic push
473
# pragma GCC diagnostic ignored "-Wclobbered"
474
#endif
475
    unsigned char **rows = NULL;
476
    png_color *png_palette = NULL;
477
    png_color_16 background;
478
    png_color_16p default_background;
479
    png_uint_32 width;
480
    png_uint_32 height;
481
    int i;
482
    int depth;
483

484
#if HAVE_SETJMP && HAVE_LONGJMP
485
    if (setjmp(jmpbuf) != 0) {
486
        sixel_allocator_free(allocator, *result);
487
        *result = NULL;
488
        status = SIXEL_PNG_ERROR;
489
        goto cleanup;
490
    }
491
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
492

493
    status = SIXEL_FALSE;
494
    *result = NULL;
495

496
    png_ptr = png_create_read_struct(
497
        PNG_LIBPNG_VER_STRING, NULL, &png_error_callback, NULL);
498
    if (!png_ptr) {
499
        sixel_helper_set_additional_message(
500
            "png_create_read_struct() failed.");
501
        status = SIXEL_PNG_ERROR;
502
        goto cleanup;
503
    }
504

505
    /*
506
     * The minimum valid PNG is 67 bytes.
507
     * https://garethrees.org/2007/11/14/pngcrush/
508
     */
509
    if (size < 67) {
510
        sixel_helper_set_additional_message("PNG data too small to be valid!");
511
        status = SIXEL_PNG_ERROR;
512
        goto cleanup;
513
    }
514

515
#if HAVE_SETJMP
516
    if (setjmp(png_jmpbuf(png_ptr)) != 0) {
517
        sixel_allocator_free(allocator, *result);
518
        *result = NULL;
519
        status = SIXEL_PNG_ERROR;
520
        goto cleanup;
521
    }
522
#endif  /* HAVE_SETJMP */
523

524
    info_ptr = png_create_info_struct(png_ptr);
525
    if (!info_ptr) {
526
        sixel_helper_set_additional_message(
527
            "png_create_info_struct() failed.");
528
        status = SIXEL_PNG_ERROR;
529
        png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
530
        goto cleanup;
531
    }
532
    read_chunk.buffer = buffer;
533
    read_chunk.size = size;
534

535
    png_set_read_fn(png_ptr,(png_voidp)&read_chunk, read_png);
536
    png_read_info(png_ptr, info_ptr);
537

538
    width = png_get_image_width(png_ptr, info_ptr);
539
    height = png_get_image_height(png_ptr, info_ptr);
540

541
    if (width > INT_MAX || height > INT_MAX) {
542
        status = SIXEL_BAD_INTEGER_OVERFLOW;
543
        goto cleanup;
544
    }
545

546
    *psx = (int)width;
547
    *psy = (int)height;
548

549
    bitdepth = png_get_bit_depth(png_ptr, info_ptr);
550
    if (bitdepth == 16) {
551
#  if HAVE_DEBUG
552
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
553
        fprintf(stderr, "stripping to 8bit...\n");
554
#  endif
555
        png_set_strip_16(png_ptr);
556
        bitdepth = 8;
557
    }
558

559
    if (bgcolor) {
560
#  if HAVE_DEBUG
561
        fprintf(stderr, "background color is specified [%02x, %02x, %02x]\n",
562
                bgcolor[0], bgcolor[1], bgcolor[2]);
563
#  endif
564
        background.red = bgcolor[0];
565
        background.green = bgcolor[1];
566
        background.blue = bgcolor[2];
567
        background.gray = (bgcolor[0] + bgcolor[1] + bgcolor[2]) / 3;
568
    } else if (png_get_bKGD(png_ptr, info_ptr, &default_background) == PNG_INFO_bKGD) {
569
        memcpy(&background, default_background, sizeof(background));
570
#  if HAVE_DEBUG
571
        fprintf(stderr, "background color is found [%02x, %02x, %02x]\n",
572
                background.red, background.green, background.blue);
573
#  endif
574
    } else {
575
        background.red = 0;
576
        background.green = 0;
577
        background.blue = 0;
578
        background.gray = 0;
579
    }
580

581
    switch (png_get_color_type(png_ptr, info_ptr)) {
582
    case PNG_COLOR_TYPE_PALETTE:
583
#  if HAVE_DEBUG
584
        fprintf(stderr, "paletted PNG(PNG_COLOR_TYPE_PALETTE)\n");
585
#  endif
586
        png_status = png_get_PLTE(png_ptr, info_ptr,
587
                                  &png_palette, pncolors);
588
        if (png_status != PNG_INFO_PLTE || png_palette == NULL) {
589
            sixel_helper_set_additional_message(
590
                "PLTE chunk not found");
591
            status = SIXEL_PNG_ERROR;
592
            goto cleanup;
593
        }
594
#  if HAVE_DEBUG
595
        fprintf(stderr, "palette colors: %d\n", *pncolors);
596
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
597
#  endif
598
        if (ppalette == NULL || *pncolors > reqcolors) {
599
#  if HAVE_DEBUG
600
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
601
                    reqcolors);
602
            fprintf(stderr, "expand to RGB format...\n");
603
#  endif
604
            png_set_background(png_ptr, &background,
605
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
606
            png_set_palette_to_rgb(png_ptr);
607
            png_set_strip_alpha(png_ptr);
608
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
609
        } else {
610
            switch (bitdepth) {
611
            case 1:
612
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
613
                if (*ppalette == NULL) {
614
                    sixel_helper_set_additional_message(
615
                        "load_png: sixel_allocator_malloc() failed.");
616
                    status = SIXEL_BAD_ALLOCATION;
617
                    goto cleanup;
618
                }
619
                read_palette(png_ptr, info_ptr, *ppalette,
620
                             *pncolors, png_palette, &background, transparent);
621
                *pixelformat = SIXEL_PIXELFORMAT_PAL1;
622
                break;
623
            case 2:
624
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
625
                if (*ppalette == NULL) {
626
                    sixel_helper_set_additional_message(
627
                        "load_png: sixel_allocator_malloc() failed.");
628
                    status = SIXEL_BAD_ALLOCATION;
629
                    goto cleanup;
630
                }
631
                read_palette(png_ptr, info_ptr, *ppalette,
632
                             *pncolors, png_palette, &background, transparent);
633
                *pixelformat = SIXEL_PIXELFORMAT_PAL2;
634
                break;
635
            case 4:
636
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
637
                if (*ppalette == NULL) {
638
                    sixel_helper_set_additional_message(
639
                        "load_png: sixel_allocator_malloc() failed.");
640
                    status = SIXEL_BAD_ALLOCATION;
641
                    goto cleanup;
642
                }
643
                read_palette(png_ptr, info_ptr, *ppalette,
644
                             *pncolors, png_palette, &background, transparent);
645
                *pixelformat = SIXEL_PIXELFORMAT_PAL4;
646
                break;
647
            case 8:
648
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
649
                if (*ppalette == NULL) {
650
                    sixel_helper_set_additional_message(
651
                        "load_png: sixel_allocator_malloc() failed.");
652
                    status = SIXEL_BAD_ALLOCATION;
653
                    goto cleanup;
654
                }
655
                read_palette(png_ptr, info_ptr, *ppalette,
656
                             *pncolors, png_palette, &background, transparent);
657
                *pixelformat = SIXEL_PIXELFORMAT_PAL8;
658
                break;
659
            default:
660
                png_set_background(png_ptr, &background,
661
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
662
                png_set_palette_to_rgb(png_ptr);
663
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
664
                break;
665
            }
666
        }
667
        break;
668
    case PNG_COLOR_TYPE_GRAY:
669
#  if HAVE_DEBUG
670
        fprintf(stderr, "grayscale PNG(PNG_COLOR_TYPE_GRAY)\n");
671
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
672
#  endif
673
        if (1 << bitdepth > reqcolors) {
674
#  if HAVE_DEBUG
675
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
676
                    reqcolors);
677
            fprintf(stderr, "expand into RGB format...\n");
678
#  endif
679
            png_set_background(png_ptr, &background,
680
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
681
            png_set_gray_to_rgb(png_ptr);
682
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
683
        } else {
684
            switch (bitdepth) {
685
            case 1:
686
            case 2:
687
            case 4:
688
                if (ppalette) {
689
#  if HAVE_DECL_PNG_SET_EXPAND_GRAY_1_2_4_TO_8
690
#   if HAVE_DEBUG
691
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
692
                            (unsigned int)bitdepth);
693
#   endif
694
                    png_set_expand_gray_1_2_4_to_8(png_ptr);
695
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
696
#  elif HAVE_DECL_PNG_SET_GRAY_1_2_4_TO_8
697
#   if HAVE_DEBUG
698
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
699
                            (unsigned int)bitdepth);
700
#   endif
701
                    png_set_gray_1_2_4_to_8(png_ptr);
702
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
703
#  else
704
#   if HAVE_DEBUG
705
                    fprintf(stderr, "expand into RGB format...\n");
706
#   endif
707
                    png_set_background(png_ptr, &background,
708
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
709
                    png_set_gray_to_rgb(png_ptr);
710
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
711
#  endif
712
                } else {
713
                    png_set_background(png_ptr, &background,
714
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
715
                    png_set_gray_to_rgb(png_ptr);
716
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
717
                }
718
                break;
719
            case 8:
720
                if (ppalette) {
721
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
722
                } else {
723
#  if HAVE_DEBUG
724
                    fprintf(stderr, "expand into RGB format...\n");
725
#  endif
726
                    png_set_background(png_ptr, &background,
727
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
728
                    png_set_gray_to_rgb(png_ptr);
729
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
730
                }
731
                break;
732
            default:
733
#  if HAVE_DEBUG
734
                fprintf(stderr, "expand into RGB format...\n");
735
#  endif
736
                png_set_background(png_ptr, &background,
737
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
738
                png_set_gray_to_rgb(png_ptr);
739
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
740
                break;
741
            }
742
        }
743
        break;
744
    case PNG_COLOR_TYPE_GRAY_ALPHA:
745
#  if HAVE_DEBUG
746
        fprintf(stderr, "grayscale-alpha PNG(PNG_COLOR_TYPE_GRAY_ALPHA)\n");
747
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
748
        fprintf(stderr, "expand to RGB format...\n");
749
#  endif
750
        png_set_background(png_ptr, &background,
751
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
752
        png_set_gray_to_rgb(png_ptr);
753
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
754
        break;
755
    case PNG_COLOR_TYPE_RGB_ALPHA:
756
#  if HAVE_DEBUG
757
        fprintf(stderr, "RGBA PNG(PNG_COLOR_TYPE_RGB_ALPHA)\n");
758
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
759
        fprintf(stderr, "expand to RGB format...\n");
760
#  endif
761
        png_set_background(png_ptr, &background,
762
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
763
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
764
        break;
765
    case PNG_COLOR_TYPE_RGB:
766
#  if HAVE_DEBUG
767
        fprintf(stderr, "RGB PNG(PNG_COLOR_TYPE_RGB)\n");
768
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
769
#  endif
770
        png_set_background(png_ptr, &background,
771
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
772
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
773
        break;
774
    default:
775
        /* unknown format */
776
        goto cleanup;
777
    }
778
    depth = sixel_helper_compute_depth(*pixelformat);
779
    *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * depth));
780
    if (*result == NULL) {
781
        sixel_helper_set_additional_message(
782
            "load_png: sixel_allocator_malloc() failed.");
783
        status = SIXEL_BAD_ALLOCATION;
784
        goto cleanup;
785
    }
786
    rows = (unsigned char **)sixel_allocator_malloc(allocator, (size_t)*psy * sizeof(unsigned char *));
787
    if (rows == NULL) {
788
        sixel_helper_set_additional_message(
789
            "load_png: sixel_allocator_malloc() failed.");
790
        status = SIXEL_BAD_ALLOCATION;
791
        goto cleanup;
792
    }
793
    switch (*pixelformat) {
794
    case SIXEL_PIXELFORMAT_PAL1:
795
    case SIXEL_PIXELFORMAT_PAL2:
796
    case SIXEL_PIXELFORMAT_PAL4:
797
        for (i = 0; i < *psy; ++i) {
798
            rows[i] = *result + (depth * *psx * (int)bitdepth + 7) / 8 * i;
799
        }
800
        break;
801
    default:
802
        for (i = 0; i < *psy; ++i) {
803
            rows[i] = *result + depth * *psx * i;
804
        }
805
        break;
806
    }
807

808
    png_read_image(png_ptr, rows);
809

810
    status = SIXEL_OK;
811

812
cleanup:
813
    png_destroy_read_struct(&png_ptr, &info_ptr,(png_infopp)0);
814

815
    if (rows != NULL) {
816
        sixel_allocator_free(allocator, rows);
817
    }
818

819
    return status;
820
}
821
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
822
# pragma GCC diagnostic pop
823
#endif
824

825
# endif  /* HAVE_LIBPNG */
826

827

828
static SIXELSTATUS
829
load_sixel(unsigned char        /* out */ **result,
156✔
830
           unsigned char        /* in */  *buffer,
831
           int                  /* in */  size,
832
           int                  /* out */ *psx,
833
           int                  /* out */ *psy,
834
           unsigned char        /* out */ **ppalette,
835
           int                  /* out */ *pncolors,
836
           int                  /* in */  reqcolors,
837
           int                  /* out */ *ppixelformat,
838
           sixel_allocator_t    /* in */  *allocator)
839
{
840
    SIXELSTATUS status = SIXEL_FALSE;
156✔
841
    unsigned char *p = NULL;
156✔
842
    unsigned char *palette = NULL;
156✔
843
    int colors;
844
    int i;
845

846
    /* sixel */
847
    status = sixel_decode_raw(buffer, size,
208✔
848
                              &p, psx, psy,
52✔
849
                              &palette, &colors, allocator);
52✔
850
    if (SIXEL_FAILED(status)) {
156!
851
        goto end;
×
852
    }
853
    if (ppalette == NULL || colors > reqcolors) {
208!
854
        *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
36✔
855
        *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * 3));
36✔
856
        if (*result == NULL) {
36!
857
            sixel_helper_set_additional_message(
×
858
                "load_sixel: sixel_allocator_malloc() failed.");
859
            status = SIXEL_BAD_ALLOCATION;
×
860
            goto end;
×
861
        }
862
        for (i = 0; i < *psx * *psy; ++i) {
5,670,576✔
863
            (*result)[i * 3 + 0] = palette[p[i] * 3 + 0];
5,670,540✔
864
            (*result)[i * 3 + 1] = palette[p[i] * 3 + 1];
5,670,540✔
865
            (*result)[i * 3 + 2] = palette[p[i] * 3 + 2];
5,670,540✔
866
        }
1,890,180✔
867
    } else {
12✔
868
        *ppixelformat = SIXEL_PIXELFORMAT_PAL8;
120✔
869
        *result = p;
120✔
870
        *ppalette = palette;
120✔
871
        *pncolors = colors;
120✔
872
        p = NULL;
120✔
873
        palette = NULL;
120✔
874
    }
875

876
end:
104✔
877
    sixel_allocator_free(allocator, palette);
156✔
878
    sixel_allocator_free(allocator, p);
156✔
879

880
    return status;
156✔
881
}
882

883

884
/* detect whether given chunk is sixel stream */
885
static int
886
chunk_is_sixel(sixel_chunk_t const *chunk)
342✔
887
{
888
    unsigned char *p;
889
    unsigned char *end;
890

891
    p = chunk->buffer;
342✔
892
    end = p + chunk->size;
342✔
893

894
    if (chunk->size < 3) {
342!
895
        return 0;
2✔
896
    }
897

898
    p++;
340✔
899
    if (p >= end) {
340!
900
        return 0;
×
901
    }
902
    if (*(p - 1) == 0x90 || (*(p - 1) == 0x1b && *p == 0x50)) {
340!
903
        while (p++ < end) {
570!
904
            if (*p == 0x71) {
570✔
905
                return 1;
156✔
906
            } else if (*p == 0x18 || *p == 0x1a) {
414!
907
                return 0;
×
908
            } else if (*p < 0x20) {
414✔
909
                continue;
6✔
910
            } else if (*p < 0x30) {
408!
911
                return 0;
×
912
            } else if (*p < 0x40) {
408✔
913
                continue;
405✔
914
            }
915
        }
916
    }
917
    return 0;
184✔
918
}
56✔
919

920

921
/* detect whether given chunk is PNM stream */
922
static int
923
chunk_is_pnm(sixel_chunk_t const *chunk)
186✔
924
{
925
    if (chunk->size < 2) {
186!
926
        return 0;
2✔
927
    }
928
    if (chunk->buffer[0] == 'P' &&
184✔
929
        chunk->buffer[1] >= '1' &&
20!
930
        chunk->buffer[1] <= '6') {
20!
931
        return 1;
20✔
932
    }
933
    return 0;
164✔
934
}
4✔
935

936

937
#if HAVE_LIBPNG
938
/* detect whether given chunk is PNG stream */
939
static int
940
chunk_is_png(sixel_chunk_t const *chunk)
941
{
942
    if (chunk->size < 8) {
943
        return 0;
944
    }
945
    if (png_check_sig(chunk->buffer, 8)) {
946
        return 1;
947
    }
948
    return 0;
949
}
950
#endif  /* HAVE_LIBPNG */
951

952

953
/* detect whether given chunk is GIF stream */
954
static int
955
chunk_is_gif(sixel_chunk_t const *chunk)
166✔
956
{
957
    if (chunk->size < 6) {
166✔
958
        return 0;
3✔
959
    }
960
    if (chunk->buffer[0] == 'G' &&
165✔
961
        chunk->buffer[1] == 'I' &&
16!
962
        chunk->buffer[2] == 'F' &&
16!
963
        chunk->buffer[3] == '8' &&
16!
964
        (chunk->buffer[4] == '7' || chunk->buffer[4] == '9') &&
16!
965
        chunk->buffer[5] == 'a') {
16!
966
        return 1;
16✔
967
    }
968
    return 0;
147✔
969
}
4✔
970

971

972
#if HAVE_JPEG
973
/* detect whether given chunk is JPEG stream */
974
static int
975
chunk_is_jpeg(sixel_chunk_t const *chunk)
976
{
977
    if (chunk->size < 2) {
978
        return 0;
979
    }
980
    if (memcmp("\xFF\xD8", chunk->buffer, 2) == 0) {
981
        return 1;
982
    }
983
    return 0;
984
}
985
#endif  /* HAVE_JPEG */
986

987
typedef union _fn_pointer {
988
    sixel_load_image_function fn;
989
    void *                    p;
990
} fn_pointer;
991

992
/* load images using builtin image loaders */
993
static SIXELSTATUS
994
load_with_builtin(
342✔
995
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
996
    int                       /* in */     fstatic,      /* static */
997
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
998
    int                       /* in */     reqcolors,    /* reqcolors */
999
    unsigned char             /* in */     *bgcolor,     /* background color */
1000
    int                       /* in */     loop_control, /* one of enum loop_control */
1001
    sixel_load_image_function /* in */     fn_load,      /* callback */
1002
    void                      /* in/out */ *context      /* private data for callback */
1003
)
1004
{
1005
    SIXELSTATUS status = SIXEL_FALSE;
342✔
1006
    sixel_frame_t *frame = NULL;
342✔
1007
    char message[256];
1008
    int nwrite;
1009
    fn_pointer fnp;
1010

1011
    if (chunk_is_sixel(pchunk)) {
342✔
1012
        status = sixel_frame_new(&frame, pchunk->allocator);
156✔
1013
        if (SIXEL_FAILED(status)) {
156!
1014
            goto end;
×
1015
        }
1016
        if (pchunk->size > INT_MAX) {
156!
1017
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1018
            goto end;
×
1019
        }
1020
        status = load_sixel(&frame->pixels,
128✔
1021
                            pchunk->buffer,
156✔
1022
                            (int)pchunk->size,
156✔
1023
                            &frame->width,
156✔
1024
                            &frame->height,
156✔
1025
                            fuse_palette ? &frame->palette: NULL,
132✔
1026
                            &frame->ncolors,
156✔
1027
                            reqcolors,
52✔
1028
                            &frame->pixelformat,
156✔
1029
                            pchunk->allocator);
156✔
1030
        if (SIXEL_FAILED(status)) {
156!
1031
            goto end;
×
1032
        }
1033
    } else if (chunk_is_pnm(pchunk)) {
238!
1034
        status = sixel_frame_new(&frame, pchunk->allocator);
20✔
1035
        if (SIXEL_FAILED(status)) {
20!
1036
            goto end;
×
1037
        }
1038
        if (pchunk->size > INT_MAX) {
20!
1039
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1040
            goto end;
×
1041
        }
1042
        /* pnm */
1043
        status = load_pnm(pchunk->buffer,
20✔
1044
                          (int)pchunk->size,
20✔
1045
                          frame->allocator,
20✔
1046
                          &frame->pixels,
20✔
1047
                          &frame->width,
20✔
1048
                          &frame->height,
20✔
1049
                          fuse_palette ? &frame->palette: NULL,
×
1050
                          &frame->ncolors,
20✔
1051
                          &frame->pixelformat);
20!
1052
        if (SIXEL_FAILED(status)) {
20!
1053
            goto end;
×
1054
        }
1055
    }
1056
#if HAVE_JPEG
1057
    else if (chunk_is_jpeg(pchunk)) {
1058
        status = sixel_frame_new(&frame, pchunk->allocator);
1059
        if (SIXEL_FAILED(status)) {
1060
            goto end;
1061
        }
1062
        status = load_jpeg(&frame->pixels,
1063
                           pchunk->buffer,
1064
                           pchunk->size,
1065
                           &frame->width,
1066
                           &frame->height,
1067
                           &frame->pixelformat,
1068
                           pchunk->allocator);
1069

1070
        if (SIXEL_FAILED(status)) {
1071
            goto end;
1072
        }
1073
    }
1074
#endif  /* HAVE_JPEG */
1075
#if HAVE_LIBPNG
1076
    else if (chunk_is_png(pchunk)) {
1077
        status = sixel_frame_new(&frame, pchunk->allocator);
1078
        if (SIXEL_FAILED(status)) {
1079
            goto end;
1080
        }
1081
        status = load_png(&frame->pixels,
1082
                          pchunk->buffer,
1083
                          pchunk->size,
1084
                          &frame->width,
1085
                          &frame->height,
1086
                          fuse_palette ? &frame->palette: NULL,
1087
                          &frame->ncolors,
1088
                          reqcolors,
1089
                          &frame->pixelformat,
1090
                          bgcolor,
1091
                          &frame->transparent,
1092
                          pchunk->allocator);
1093
        if (SIXEL_FAILED(status)) {
1094
            goto end;
1095
        }
1096
    }
1097
#endif  /* HAVE_LIBPNG */
1098
    else if (chunk_is_gif(pchunk)) {
166✔
1099
        fnp.fn = fn_load;
16✔
1100
        if (pchunk->size > INT_MAX) {
16!
1101
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1102
            goto end;
×
1103
        }
1104
        status = load_gif(pchunk->buffer,
18✔
1105
                          (int)pchunk->size,
16✔
1106
                          bgcolor,
2✔
1107
                          reqcolors,
2✔
1108
                          fuse_palette,
2✔
1109
                          fstatic,
2✔
1110
                          loop_control,
2✔
1111
                          fnp.p,
2✔
1112
                          context,
2✔
1113
                          pchunk->allocator);
16✔
1114
        if (SIXEL_FAILED(status)) {
16!
1115
            goto end;
6✔
1116
        }
1117
        goto end;
10✔
1118
    } else {
1119
        stbi__context s;
1120
        int depth;
1121

1122
        status = sixel_frame_new(&frame, pchunk->allocator);
150✔
1123
        if (SIXEL_FAILED(status)) {
150!
1124
            goto end;
2✔
1125
        }
1126
        stbi_allocator = pchunk->allocator;
150✔
1127
        if (pchunk->size > INT_MAX) {
150!
1128
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1129
            goto end;
×
1130
        }
1131
        stbi__start_mem(&s, pchunk->buffer, (int)pchunk->size);
150✔
1132
        frame->pixels = stbi__load_and_postprocess_8bit(&s, &frame->width, &frame->height, &depth, 3);
150✔
1133
        if (!frame->pixels) {
150✔
1134
            sixel_helper_set_additional_message(stbi_failure_reason());
3✔
1135
            status = SIXEL_STBI_ERROR;
3✔
1136
            goto end;
3✔
1137
        }
1138
        frame->loop_count = 1;
147✔
1139

1140
        switch (depth) {
147!
1141
        case 1:
146✔
1142
        case 3:
1143
        case 4:
1144
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
147✔
1145
            break;
147✔
1146
        default:
1147
            nwrite = sprintf(message,
×
1148
                             "load_with_builtin() failed.\n"
1149
                             "reason: unknown pixel-format.(depth: %d)\n",
1150
                             depth);
1151
            if (nwrite > 0) {
×
1152
                sixel_helper_set_additional_message(message);
×
1153
            }
1154
            goto end;
×
1155
        }
1156
    }
1157

1158
    status = sixel_frame_strip_alpha(frame, bgcolor);
323✔
1159
    if (SIXEL_FAILED(status)) {
323!
1160
        goto end;
×
1161
    }
1162

1163
    status = fn_load(frame, context);
323✔
1164
    if (SIXEL_FAILED(status)) {
323✔
1165
        goto end;
3✔
1166
    }
1167

1168
    status = SIXEL_OK;
320✔
1169

1170
end:
286✔
1171
    sixel_frame_unref(frame);
342✔
1172

1173
    return status;
342✔
1174
}
1175

1176

1177
#ifdef HAVE_GDK_PIXBUF2
1178
/*
1179
 * Loader backed by gdk-pixbuf2. The entire animation is consumed via
1180
 * GdkPixbufLoader, each frame is copied into a temporary buffer and forwarded as
1181
 * a sixel_frame_t. Loop attributes provided by gdk-pixbuf are reconciled with
1182
 * libsixel's loop control settings.
1183
 */
1184
static SIXELSTATUS
1185
load_with_gdkpixbuf(
1186
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
1187
    int                       /* in */     fstatic,      /* static */
1188
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
1189
    int                       /* in */     reqcolors,    /* reqcolors */
1190
    unsigned char             /* in */     *bgcolor,     /* background color */
1191
    int                       /* in */     loop_control, /* one of enum loop_control */
1192
    sixel_load_image_function /* in */     fn_load,      /* callback */
1193
    void                      /* in/out */ *context      /* private data for callback */
1194
)
1195
{
1196
    SIXELSTATUS status = SIXEL_FALSE;
1197
    GdkPixbuf *pixbuf;
1198
    GdkPixbufAnimation *animation;
1199
    GdkPixbufLoader *loader = NULL;
1200
    GdkPixbufAnimationIter *it = NULL;
1201
    gboolean loader_closed = FALSE;  /* remember if loader was already closed */
1202
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1203
# pragma GCC diagnostic push
1204
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1205
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1206
    GTimeVal time_val;
1207
    GTimeVal start_time;
1208
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1209
# pragma GCC diagnostic pop
1210
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1211
    sixel_frame_t *frame = NULL;
1212
    int stride;
1213
    unsigned char *p;
1214
    int i;
1215
    int depth;
1216
    int anim_loop_count = (-1);  /* (-1): infinite, >=0: finite loop count */
1217
    int delay_ms;
1218

1219
    (void) fuse_palette;
1220
    (void) reqcolors;
1221
    (void) bgcolor;
1222

1223
    status = sixel_frame_new(&frame, pchunk->allocator);
1224
    if (SIXEL_FAILED(status)) {
1225
        goto end;
1226
    }
1227

1228
#if (! GLIB_CHECK_VERSION(2, 36, 0))
1229
    g_type_init();
1230
#endif
1231
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1232
# pragma GCC diagnostic push
1233
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1234
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1235
    g_get_current_time(&time_val);
1236
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1237
# pragma GCC diagnostic pop
1238
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1239
    start_time = time_val;
1240
    loader = gdk_pixbuf_loader_new();
1241
    if (loader == NULL) {
1242
        status = SIXEL_GDK_ERROR;
1243
        goto end;
1244
    }
1245
    /* feed the whole blob and close so the animation metadata becomes available */
1246
    if (! gdk_pixbuf_loader_write(loader, pchunk->buffer, pchunk->size, NULL)) {
1247
        status = SIXEL_GDK_ERROR;
1248
        goto end;
1249
    }
1250
    if (! gdk_pixbuf_loader_close(loader, NULL)) {
1251
        status = SIXEL_GDK_ERROR;
1252
        goto end;
1253
    }
1254
    loader_closed = TRUE;
1255
    animation = gdk_pixbuf_loader_get_animation(loader);
1256
    if (animation) {
1257
        /* inspect animation object to determine built-in loop semantics */
1258
        if (GDK_IS_PIXBUF_SIMPLE_ANIM(animation)) {
1259
            anim_loop_count = gdk_pixbuf_simple_anim_get_loop(
1260
                                 GDK_PIXBUF_SIMPLE_ANIM(animation)) ? (-1) : 1;
1261
        } else {
1262
            GParamSpec *loop_pspec = g_object_class_find_property(
1263
                G_OBJECT_GET_CLASS(animation), "loop");
1264
            if (loop_pspec == NULL) {
1265
                loop_pspec = g_object_class_find_property(
1266
                    G_OBJECT_GET_CLASS(animation), "loop-count");
1267
            }
1268
            if (loop_pspec) {
1269
                GValue loop_value = G_VALUE_INIT;
1270
                g_value_init(&loop_value, loop_pspec->value_type);
1271
                g_object_get_property(G_OBJECT(animation),
1272
                                      g_param_spec_get_name(loop_pspec),
1273
                                      &loop_value);
1274
                if (G_VALUE_HOLDS_BOOLEAN(&loop_value)) {
1275
                    /* TRUE means "loop forever" for these properties */
1276
                    anim_loop_count = g_value_get_boolean(&loop_value) ? (-1) : 1;
1277
                } else if (G_VALUE_HOLDS_INT(&loop_value)) {
1278
                    int loop_int = g_value_get_int(&loop_value);
1279
                    /* GIF spec treats zero as infinite repetition */
1280
                    anim_loop_count = (loop_int <= 0) ? (-1) : loop_int;
1281
                } else if (G_VALUE_HOLDS_UINT(&loop_value)) {
1282
                    guint loop_uint = g_value_get_uint(&loop_value);
1283
                    if (loop_uint == 0U) {
1284
                        anim_loop_count = (-1);
1285
                    } else {
1286
                        anim_loop_count = loop_uint > (guint)INT_MAX
1287
                                            ? INT_MAX
1288
                                            : (int)loop_uint;
1289
                    }
1290
                }
1291
                g_value_unset(&loop_value);
1292
            }
1293
        }
1294
    }
1295
    if (!animation || fstatic || gdk_pixbuf_animation_is_static_image(animation)) {
1296
        /* fall back to single frame decoding */
1297
        pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1298
        if (pixbuf == NULL) {
1299
            goto end;
1300
        }
1301
        frame->frame_no = 0;
1302
        frame->width = gdk_pixbuf_get_width(pixbuf);
1303
        frame->height = gdk_pixbuf_get_height(pixbuf);
1304
        stride = gdk_pixbuf_get_rowstride(pixbuf);
1305
        frame->pixels = sixel_allocator_malloc(pchunk->allocator, (size_t)(frame->height * stride));
1306
        if (frame->pixels == NULL) {
1307
            sixel_helper_set_additional_message(
1308
                "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1309
            status = SIXEL_BAD_ALLOCATION;
1310
            goto end;
1311
        }
1312
        if (stride / frame->width == 4) {
1313
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1314
            depth = 4;
1315
        } else {
1316
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1317
            depth = 3;
1318
        }
1319
        p = gdk_pixbuf_get_pixels(pixbuf);
1320
        if (stride == frame->width * depth) {
1321
            memcpy(frame->pixels, gdk_pixbuf_get_pixels(pixbuf),
1322
                   (size_t)(frame->height * stride));
1323
        } else {
1324
            for (i = 0; i < frame->height; ++i) {
1325
                memcpy(frame->pixels + frame->width * depth * i,
1326
                       p + stride * i,
1327
                       (size_t)(frame->width * depth));
1328
            }
1329
        }
1330
        status = fn_load(frame, context);
1331
        if (status != SIXEL_OK) {
1332
            goto end;
1333
        }
1334
        /* scratch buffer no longer needed after callback */
1335
        sixel_allocator_free(pchunk->allocator, frame->pixels);
1336
        frame->pixels = NULL;
1337
    } else {
1338
        gboolean finished;
1339

1340
        /* reset iterator to the beginning of the timeline */
1341
        time_val = start_time;
1342
        frame->frame_no = 0;
1343
        frame->loop_count = 0;
1344

1345
        it = gdk_pixbuf_animation_get_iter(animation, &time_val);
1346
        if (it == NULL) {
1347
            status = SIXEL_GDK_ERROR;
1348
            goto end;
1349
        }
1350

1351
        for (;;) {
1352
            /* handle one logical loop of the animation */
1353
            finished = FALSE;
1354
            while (!gdk_pixbuf_animation_iter_on_currently_loading_frame(it)) {
1355

1356
                pixbuf = gdk_pixbuf_animation_iter_get_pixbuf(it);
1357
                if (pixbuf == NULL) {
1358
                    finished = TRUE;
1359
                    break;
1360
                }
1361
                /* allocate a scratch copy of the current frame */
1362
                frame->width = gdk_pixbuf_get_width(pixbuf);
1363
                frame->height = gdk_pixbuf_get_height(pixbuf);
1364
                stride = gdk_pixbuf_get_rowstride(pixbuf);
1365
                frame->pixels = sixel_allocator_malloc(
1366
                    pchunk->allocator,
1367
                    (size_t)(frame->height * stride));
1368
                if (frame->pixels == NULL) {
1369
                    sixel_helper_set_additional_message(
1370
                        "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1371
                    status = SIXEL_BAD_ALLOCATION;
1372
                    goto end;
1373
                }
1374
                if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1375
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1376
                    depth = 4;
1377
                } else {
1378
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1379
                    depth = 3;
1380
                }
1381
                p = gdk_pixbuf_get_pixels(pixbuf);
1382
                if (stride == frame->width * depth) {
1383
                    memcpy(frame->pixels, p,
1384
                           (size_t)(frame->height * stride));
1385
                } else {
1386
                    for (i = 0; i < frame->height; ++i) {
1387
                        memcpy(frame->pixels + frame->width * depth * i,
1388
                               p + stride * i,
1389
                               (size_t)(frame->width * depth));
1390
                    }
1391
                }
1392
                delay_ms = gdk_pixbuf_animation_iter_get_delay_time(it);
1393
                if (delay_ms < 0) {
1394
                    delay_ms = 0;
1395
                }
1396
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1397
# pragma GCC diagnostic push
1398
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1399
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1400
                /* advance the synthetic clock before asking gdk to move forward */
1401
                g_time_val_add(&time_val, delay_ms * 1000);
1402
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1403
# pragma GCC diagnostic pop
1404
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1405
                frame->delay = delay_ms / 10;
1406
                frame->multiframe = 1;
1407

1408
                if (!gdk_pixbuf_animation_iter_advance(it, &time_val)) {
1409
                    finished = TRUE;
1410
                }
1411
                status = fn_load(frame, context);
1412
                if (status != SIXEL_OK) {
1413
                    goto end;
1414
                }
1415
                /* release scratch pixels before decoding the next frame */
1416
                sixel_allocator_free(pchunk->allocator, frame->pixels);
1417
                frame->pixels = NULL;
1418
                frame->frame_no++;
1419

1420
                if (finished) {
1421
                    break;
1422
                }
1423
            }
1424

1425
            if (frame->frame_no == 0) {
1426
                break;
1427
            }
1428

1429
            /* finished processing one full loop */
1430
            ++frame->loop_count;
1431

1432
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
1433
                break;
1434
            }
1435
            if (loop_control == SIXEL_LOOP_AUTO) {
1436
                /* obey header-provided loop count when AUTO */
1437
                if (anim_loop_count >= 0 &&
1438
                    frame->loop_count >= anim_loop_count) {
1439
                    break;
1440
                }
1441
            } else if (loop_control != SIXEL_LOOP_FORCE &&
1442
                       anim_loop_count > 0 &&
1443
                       frame->loop_count >= anim_loop_count) {
1444
                break;
1445
            }
1446

1447
            /* restart iteration from the beginning for the next pass */
1448
            g_object_unref(it);
1449
            time_val = start_time;
1450
            it = gdk_pixbuf_animation_get_iter(animation, &time_val);
1451
            if (it == NULL) {
1452
                status = SIXEL_GDK_ERROR;
1453
                goto end;
1454
            }
1455
            /* next pass starts counting frames from zero again */
1456
            frame->frame_no = 0;
1457
        }
1458
    }
1459

1460
    status = SIXEL_OK;
1461

1462
end:
1463
    if (frame) {
1464
        /* drop the reference we obtained from sixel_frame_new() */
1465
        sixel_frame_unref(frame);
1466
    }
1467
    if (it) {
1468
        g_object_unref(it);
1469
    }
1470
    if (loader) {
1471
        if (!loader_closed) {
1472
            /* ensure the incremental loader is finalized even on error paths */
1473
            gdk_pixbuf_loader_close(loader, NULL);
1474
        }
1475
        g_object_unref(loader);
1476
    }
1477

1478
    return status;
1479

1480
}
1481
#endif  /* HAVE_GDK_PIXBUF2 */
1482

1483
#if HAVE_COREGRAPHICS
1484
static SIXELSTATUS
1485
load_with_coregraphics(
143✔
1486
    sixel_chunk_t const       /* in */     *pchunk,
1487
    int                       /* in */     fstatic,
1488
    int                       /* in */     fuse_palette,
1489
    int                       /* in */     reqcolors,
1490
    unsigned char             /* in */     *bgcolor,
1491
    int                       /* in */     loop_control,
1492
    sixel_load_image_function /* in */     fn_load,
1493
    void                      /* in/out */ *context)
1494
{
1495
    SIXELSTATUS status = SIXEL_FALSE;
143✔
1496
    sixel_frame_t *frame = NULL;
143✔
1497
    CFDataRef data = NULL;
143✔
1498
    CGImageSourceRef source = NULL;
143✔
1499
    CGImageRef image = NULL;
143✔
1500
    CGColorSpaceRef color_space = NULL;
143✔
1501
    CGContextRef ctx = NULL;
143✔
1502
    size_t stride;
1503
    size_t frame_count;
1504
    int anim_loop_count = (-1);
143✔
1505
    CFDictionaryRef props = NULL;
143✔
1506
    CFDictionaryRef anim_dict;
1507
    CFNumberRef loop_num;
1508
    CFDictionaryRef frame_props;
1509
    CFDictionaryRef frame_anim_dict;
1510
    CFNumberRef delay_num;
1511
    double delay_sec;
1512
    size_t i;
1513

1514
    (void) fuse_palette;
143✔
1515
    (void) reqcolors;
143✔
1516
    (void) bgcolor;
143✔
1517

1518
    status = sixel_frame_new(&frame, pchunk->allocator);
143✔
1519
    if (SIXEL_FAILED(status)) {
143!
1520
        goto end;
1521
    }
1522

1523
    data = CFDataCreate(kCFAllocatorDefault,
286✔
1524
                        pchunk->buffer,
143✔
1525
                        (CFIndex)pchunk->size);
143✔
1526
    if (! data) {
143!
1527
        status = SIXEL_FALSE;
1528
        goto end;
1529
    }
1530

1531
    source = CGImageSourceCreateWithData(data, NULL);
143✔
1532
    if (! source) {
143!
1533
        status = SIXEL_FALSE;
1534
        goto end;
1535
    }
1536

1537
    frame_count = CGImageSourceGetCount(source);
143✔
1538
    if (! frame_count) {
143✔
1539
        status = SIXEL_FALSE;
55✔
1540
        goto end;
55✔
1541
    }
1542
    if (fstatic) {
88✔
1543
        frame_count = 1;
7✔
1544
    }
7✔
1545

1546
    props = CGImageSourceCopyProperties(source, NULL);
88✔
1547
    if (props) {
88✔
1548
        anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
88✔
1549
            props, kCGImagePropertyGIFDictionary);
88✔
1550
        if (anim_dict) {
88✔
1551
            loop_num = (CFNumberRef)CFDictionaryGetValue(
5✔
1552
                anim_dict, kCGImagePropertyGIFLoopCount);
5✔
1553
            if (loop_num) {
5✔
1554
                CFNumberGetValue(loop_num, kCFNumberIntType, &anim_loop_count);
5✔
1555
            }
5✔
1556
        }
5✔
1557
        CFRelease(props);
88✔
1558
    }
88✔
1559

1560
    color_space = CGColorSpaceCreateDeviceRGB();
88✔
1561
    if (! color_space) {
88!
1562
        status = SIXEL_FALSE;
1563
        goto end;
1564
    }
1565

1566
    frame->loop_count = 0;
88✔
1567

1568
    for (;;) {
88✔
1569
        frame->frame_no = 0;
90✔
1570
        for (i = 0; i < frame_count; ++i) {
216✔
1571
            delay_sec = 0.0;
130✔
1572
            frame_props = CGImageSourceCopyPropertiesAtIndex(
130✔
1573
                source, (CFIndex)i, NULL);
130✔
1574
            if (frame_props) {
130✔
1575
                frame_anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
130✔
1576
                    frame_props, kCGImagePropertyGIFDictionary);
130✔
1577
                if (frame_anim_dict) {
130✔
1578
                    delay_num = (CFNumberRef)CFDictionaryGetValue(
47✔
1579
                        frame_anim_dict, kCGImagePropertyGIFUnclampedDelayTime);
47✔
1580
                    if (! delay_num) {
47✔
1581
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
1582
                            frame_anim_dict, kCGImagePropertyGIFDelayTime);
1583
                    }
1584
                    if (delay_num) {
47✔
1585
                        CFNumberGetValue(delay_num,
47✔
1586
                                         kCFNumberDoubleType,
1587
                                         &delay_sec);
1588
                    }
47✔
1589
                }
47✔
1590
#if defined(kCGImagePropertyPNGDictionary) && \
1591
    defined(kCGImagePropertyAPNGUnclampedDelayTime) && \
1592
    defined(kCGImagePropertyAPNGDelayTime)
1593
                if (delay_sec <= 0.0) {
1594
                    CFDictionaryRef png_frame = (CFDictionaryRef)CFDictionaryGetValue(
1595
                        frame_props, kCGImagePropertyPNGDictionary);
1596
                    if (png_frame) {
1597
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
1598
                            png_frame, kCGImagePropertyAPNGUnclampedDelayTime);
1599
                        if (! delay_num) {
1600
                            delay_num = (CFNumberRef)CFDictionaryGetValue(
1601
                                png_frame, kCGImagePropertyAPNGDelayTime);
1602
                        }
1603
                        if (delay_num) {
1604
                            CFNumberGetValue(delay_num,
1605
                                             kCFNumberDoubleType,
1606
                                             &delay_sec);
1607
                        }
1608
                    }
1609
                }
1610
#endif
1611
                CFRelease(frame_props);
130✔
1612
            }
130✔
1613
            if (delay_sec <= 0.0) {
176✔
1614
                delay_sec = 0.1;
84✔
1615
            }
84✔
1616
            frame->delay = (int)(delay_sec * 100.0 + 0.5);
168✔
1617
            if (frame->delay < 1) {
168✔
1618
                frame->delay = 1;
1619
            }
1620

1621
            image = CGImageSourceCreateImageAtIndex(source, (CFIndex)i, NULL);
130✔
1622
            if (! image) {
130!
1623
                status = SIXEL_FALSE;
1624
                goto end;
1625
            }
1626

1627
            frame->width = (int)CGImageGetWidth(image);
130✔
1628
            frame->height = (int)CGImageGetHeight(image);
130✔
1629
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
130✔
1630
            stride = (size_t)frame->width * 4;
130✔
1631
            frame->pixels = sixel_allocator_malloc(
130✔
1632
                pchunk->allocator, (size_t)(frame->height * stride));
130✔
1633

1634
            if (frame->pixels == NULL) {
130!
1635
                sixel_helper_set_additional_message(
1636
                    "load_with_coregraphics: sixel_allocator_malloc() failed.");
1637
                status = SIXEL_BAD_ALLOCATION;
1638
                CGImageRelease(image);
1639
                goto end;
1640
            }
1641

1642
            ctx = CGBitmapContextCreate(frame->pixels,
260✔
1643
                                        frame->width,
130✔
1644
                                        frame->height,
130✔
1645
                                        8,
1646
                                        stride,
130✔
1647
                                        color_space,
130✔
1648
                                        kCGImageAlphaPremultipliedLast |
1649
                                            kCGBitmapByteOrder32Big);
1650
            if (!ctx) {
130!
1651
                CGImageRelease(image);
1652
                goto end;
1653
            }
1654

1655
            CGContextDrawImage(ctx,
260✔
1656
                               CGRectMake(0, 0, frame->width, frame->height),
130✔
1657
                               image);
130✔
1658
            CGContextRelease(ctx);
130✔
1659
            ctx = NULL;
130✔
1660

1661
            frame->multiframe = (frame_count > 1);
130✔
1662
            status = fn_load(frame, context);
130✔
1663
            sixel_allocator_free(pchunk->allocator, frame->pixels);
130✔
1664
            frame->pixels = NULL;
130✔
1665
            CGImageRelease(image);
130✔
1666
            image = NULL;
130✔
1667
            if (status != SIXEL_OK) {
130✔
1668
                goto end;
4✔
1669
            }
1670
            ++frame->frame_no;
126✔
1671
        }
126✔
1672

1673
        ++frame->loop_count;
86✔
1674

1675
        if (frame_count <= 1) {
86✔
1676
            break;
81✔
1677
        }
1678
        if (loop_control == SIXEL_LOOP_DISABLE) {
5✔
1679
            break;
2✔
1680
        }
1681
        if (loop_control == SIXEL_LOOP_AUTO) {
3!
1682
            if (anim_loop_count < 0) {
3!
1683
                break;
1684
            }
1685
            if (anim_loop_count > 0 && frame->loop_count >= anim_loop_count) {
3✔
1686
                break;
1✔
1687
            }
1688
            continue;
2✔
1689
        }
1690
    }
1691

1692
    status = SIXEL_OK;
84✔
1693

1694
end:
1695
    if (ctx) {
143✔
1696
        CGContextRelease(ctx);
1697
    }
1698
    if (color_space) {
88✔
1699
        CGColorSpaceRelease(color_space);
88✔
1700
    }
88✔
1701
    if (image) {
176✔
1702
        CGImageRelease(image);
1703
    }
1704
    if (source) {
143✔
1705
        CFRelease(source);
143✔
1706
    }
143✔
1707
    if (data) {
143✔
1708
        CFRelease(data);
143✔
1709
    }
143✔
1710
    if (frame) {
143✔
1711
        sixel_allocator_free(pchunk->allocator, frame->pixels);
143✔
1712
        sixel_allocator_free(pchunk->allocator, frame->palette);
143✔
1713
        sixel_allocator_free(pchunk->allocator, frame);
143✔
1714
    }
143✔
1715
    return status;
143✔
1716
}
1717
#endif  /* HAVE_COREGRAPHICS */
1718

1719
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1720
static SIXELSTATUS
1721
load_with_quicklook(
59✔
1722
    sixel_chunk_t const       /* in */     *pchunk,
1723
    int                       /* in */     fstatic,
1724
    int                       /* in */     fuse_palette,
1725
    int                       /* in */     reqcolors,
1726
    unsigned char             /* in */     *bgcolor,
1727
    int                       /* in */     loop_control,
1728
    sixel_load_image_function /* in */     fn_load,
1729
    void                      /* in/out */ *context)
1730
{
1731
    SIXELSTATUS status = SIXEL_FALSE;
59✔
1732
    sixel_frame_t *frame = NULL;
59✔
1733
    CFStringRef path = NULL;
59✔
1734
    CFURLRef url = NULL;
59✔
1735
    CGImageRef image = NULL;
59✔
1736
    CGColorSpaceRef color_space = NULL;
59✔
1737
    CGContextRef ctx = NULL;
59✔
1738
    CGRect bounds;
1739
    size_t stride;
1740
    unsigned char fill_color[3];
1741
    CGFloat fill_r;
1742
    CGFloat fill_g;
1743
    CGFloat fill_b;
1744
    CGFloat max_dimension;
1745
    CGSize max_size;
1746

1747
    (void)fstatic;
59✔
1748
    (void)fuse_palette;
59✔
1749
    (void)reqcolors;
59✔
1750
    (void)loop_control;
59✔
1751

1752
    if (pchunk == NULL || pchunk->source_path == NULL) {
59✔
1753
        goto end;
43✔
1754
    }
1755

1756
    status = sixel_frame_new(&frame, pchunk->allocator);
16✔
1757
    if (SIXEL_FAILED(status)) {
16!
1758
        goto end;
1759
    }
1760

1761
    path = CFStringCreateWithCString(kCFAllocatorDefault,
32✔
1762
                                     pchunk->source_path,
16✔
1763
                                     kCFStringEncodingUTF8);
1764
    if (path == NULL) {
16!
1765
        status = SIXEL_RUNTIME_ERROR;
1766
        goto end;
1767
    }
1768

1769
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
32✔
1770
                                        path,
16✔
1771
                                        kCFURLPOSIXPathStyle,
1772
                                        false);
1773
    if (url == NULL) {
16!
1774
        status = SIXEL_RUNTIME_ERROR;
1775
        goto end;
1776
    }
1777

1778
    if (thumbnailer_size_hint > 0) {
16!
1779
        max_dimension = (CGFloat)thumbnailer_size_hint;
16✔
1780
    } else {
16✔
1781
        max_dimension = (CGFloat)SIXEL_THUMBNAILER_DEFAULT_SIZE;
1782
    }
1783
    max_size.width = max_dimension;
16✔
1784
    max_size.height = max_dimension;
16✔
1785
#if HAVE_QUICKLOOK_THUMBNAILING
1786
    image = sixel_quicklook_thumbnail_create(url, max_size);
16✔
1787
    if (image == NULL) {
16✔
1788
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1789
#  pragma clang diagnostic push
1790
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
1791
# endif
1792
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
24✔
1793
                                       url,
12✔
1794
                                       max_size,
1795
                                       NULL);
1796
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1797
#  pragma clang diagnostic pop
1798
# endif
1799
    }
12✔
1800
#else
1801
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1802
#  pragma clang diagnostic push
1803
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
1804
# endif
1805
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
1806
                                   url,
1807
                                   max_size,
1808
                                   NULL);
1809
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1810
#  pragma clang diagnostic pop
1811
# endif
1812
#endif
1813
    if (image == NULL) {
16✔
1814
        status = SIXEL_RUNTIME_ERROR;
12✔
1815
        sixel_helper_set_additional_message(
12✔
1816
            "load_with_quicklook: CQLThumbnailImageCreate() failed.");
1817
        goto end;
12✔
1818
    }
1819

1820
    color_space = CGColorSpaceCreateDeviceRGB();
4✔
1821
    if (color_space == NULL) {
4!
1822
        status = SIXEL_RUNTIME_ERROR;
1823
        sixel_helper_set_additional_message(
1824
            "load_with_quicklook: CGColorSpaceCreateDeviceRGB() failed.");
1825
        goto end;
1826
    }
1827

1828
    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
4✔
1829
    frame->width = (int)CGImageGetWidth(image);
4✔
1830
    frame->height = (int)CGImageGetHeight(image);
4✔
1831
    if (frame->width <= 0 || frame->height <= 0) {
4✔
1832
        status = SIXEL_RUNTIME_ERROR;
1833
        sixel_helper_set_additional_message(
1834
            "load_with_quicklook: invalid image size detected.");
1835
        goto end;
1836
    }
1837

1838
    stride = (size_t)frame->width * 4;
4✔
1839
    frame->pixels =
4✔
1840
        sixel_allocator_malloc(pchunk->allocator,
8✔
1841
                               (size_t)frame->height * stride);
4✔
1842
    if (frame->pixels == NULL) {
4!
1843
        sixel_helper_set_additional_message(
1844
            "load_with_quicklook: sixel_allocator_malloc() failed.");
1845
        status = SIXEL_BAD_ALLOCATION;
1846
        goto end;
1847
    }
1848

1849
    if (bgcolor != NULL) {
4!
1850
        fill_color[0] = bgcolor[0];
1851
        fill_color[1] = bgcolor[1];
1852
        fill_color[2] = bgcolor[2];
1853
    } else {
1854
        fill_color[0] = 255;
4✔
1855
        fill_color[1] = 255;
4✔
1856
        fill_color[2] = 255;
4✔
1857
    }
1858

1859
    ctx = CGBitmapContextCreate(frame->pixels,
8✔
1860
                                frame->width,
4✔
1861
                                frame->height,
4✔
1862
                                8,
1863
                                stride,
4✔
1864
                                color_space,
4✔
1865
                                kCGImageAlphaPremultipliedLast |
1866
                                    kCGBitmapByteOrder32Big);
1867
    if (ctx == NULL) {
4!
1868
        status = SIXEL_RUNTIME_ERROR;
1869
        sixel_helper_set_additional_message(
1870
            "load_with_quicklook: CGBitmapContextCreate() failed.");
1871
        goto end;
1872
    }
1873

1874
    bounds = CGRectMake(0,
4✔
1875
                        0,
1876
                        (CGFloat)frame->width,
4✔
1877
                        (CGFloat)frame->height);
4✔
1878
    fill_r = (CGFloat)fill_color[0] / 255.0f;
4✔
1879
    fill_g = (CGFloat)fill_color[1] / 255.0f;
4✔
1880
    fill_b = (CGFloat)fill_color[2] / 255.0f;
4✔
1881
    CGContextSetRGBFillColor(ctx, fill_r, fill_g, fill_b, 1.0f);
4✔
1882
    CGContextFillRect(ctx, bounds);
4✔
1883
    CGContextDrawImage(ctx, bounds, image);
4✔
1884
    CGContextFlush(ctx);
4✔
1885

1886
    /* Abort when Quick Look produced no visible pixels so other loaders run. */
1887
    {
1888
        size_t pixel_count;
1889
        size_t index;
1890
        unsigned char *pixel;
1891
        int has_content;
1892

1893
        pixel_count = (size_t)frame->width * (size_t)frame->height;
4✔
1894
        pixel = frame->pixels;
4✔
1895
        has_content = 0;
4✔
1896
        for (index = 0; index < pixel_count; ++index) {
4✔
1897
            if (pixel[0] != fill_color[0] ||
4✔
1898
                    pixel[1] != fill_color[1] ||
1899
                    pixel[2] != fill_color[2] ||
1900
                    pixel[3] != 0xff) {
1901
                has_content = 1;
4✔
1902
                break;
4✔
1903
            }
1904
            pixel += 4;
1905
        }
1906
        if (! has_content) {
4!
1907
            sixel_helper_set_additional_message(
1908
                "load_with_quicklook: thumbnail contained no visible pixels.");
1909
            status = SIXEL_BAD_INPUT;
1910
            CGContextRelease(ctx);
1911
            ctx = NULL;
1912
            goto end;
1913
        }
1914
    }
1915

1916
    CGContextRelease(ctx);
4✔
1917
    ctx = NULL;
4✔
1918

1919
    frame->delay = 0;
4✔
1920
    frame->frame_no = 0;
4✔
1921
    frame->loop_count = 1;
4✔
1922
    frame->multiframe = 0;
4✔
1923
    frame->transparent = (-1);
4✔
1924

1925
    status = sixel_frame_strip_alpha(frame, fill_color);
4✔
1926
    if (SIXEL_FAILED(status)) {
4!
1927
        goto end;
1928
    }
1929

1930
    status = fn_load(frame, context);
4✔
1931
    sixel_allocator_free(pchunk->allocator, frame->pixels);
4✔
1932
    frame->pixels = NULL;
4✔
1933
    if (status != SIXEL_OK) {
4✔
1934
        goto end;
1✔
1935
    }
1936

1937
    status = SIXEL_OK;
3✔
1938

1939
end:
1940
    if (ctx != NULL) {
59✔
1941
        CGContextRelease(ctx);
1942
    }
1943
    if (color_space != NULL) {
4✔
1944
        CGColorSpaceRelease(color_space);
4✔
1945
    }
4✔
1946
    if (image != NULL) {
4✔
1947
        CGImageRelease(image);
4✔
1948
    }
4✔
1949
    if (url != NULL) {
16✔
1950
        CFRelease(url);
16✔
1951
    }
16✔
1952
    if (path != NULL) {
16✔
1953
        CFRelease(path);
16✔
1954
    }
16✔
1955
    if (frame != NULL) {
16✔
1956
        sixel_allocator_free(pchunk->allocator, frame->pixels);
16✔
1957
        sixel_allocator_free(pchunk->allocator, frame->palette);
16✔
1958
        sixel_allocator_free(pchunk->allocator, frame);
16✔
1959
    }
16✔
1960

1961
    return status;
59✔
1962
}
1963
#endif  /* HAVE_COREGRAPHICS && HAVE_QUICKLOOK */
1964

1965
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
1966

1967
# if defined(HAVE_NANOSLEEP)
1968
int nanosleep (const struct timespec *rqtp, struct timespec *rmtp);
1969
# endif
1970
# if defined(HAVE_REALPATH)
1971
char * realpath (const char *restrict path, char *restrict resolved_path);
1972
# endif
1973
# if defined(HAVE_MKSTEMP)
1974
int mkstemp (char *);
1975
# endif
1976

1977
static int
1978
thumbnailer_format_append_char(char *buffer,
306✔
1979
                               size_t capacity,
1980
                               size_t *position,
1981
                               char ch,
1982
                               int *truncated)
1983
{
1984
    if (buffer == NULL || position == NULL) {
306!
1985
        return 0;
×
1986
    }
1987

1988
    if (*position + 1 < capacity) {
306!
1989
        buffer[*position] = ch;
306✔
1990
        *position += 1;
306✔
1991
        buffer[*position] = '\0';
306✔
1992
    } else {
102✔
1993
        if (capacity > 0) {
×
1994
            buffer[capacity - 1] = '\0';
×
1995
        }
1996
        if (truncated != NULL) {
×
1997
            *truncated = 1;
×
1998
        }
1999
    }
2000

2001
    return 1;
306✔
2002
}
102✔
2003

2004
static int
2005
thumbnailer_format_append_text(char *buffer,
27✔
2006
                               size_t capacity,
2007
                               size_t *position,
2008
                               char const *text,
2009
                               int *truncated)
2010
{
2011
    size_t length;
2012
    size_t copy_length;
2013

2014
    if (buffer == NULL || position == NULL) {
27!
2015
        return 0;
×
2016
    }
2017

2018
    if (text == NULL) {
27!
2019
        text = "(null)";
×
2020
    }
2021

2022
    length = strlen(text);
18✔
2023
    copy_length = length;
18✔
2024

2025
    if (*position + copy_length >= capacity) {
18!
2026
        if (capacity <= *position) {
×
2027
            copy_length = 0;
×
2028
        } else {
2029
            copy_length = capacity - 1 - *position;
×
2030
        }
2031
        if (truncated != NULL) {
×
2032
            *truncated = 1;
×
2033
        }
2034
    }
2035

2036
    if (copy_length > 0) {
27!
2037
        memcpy(buffer + *position, text, copy_length);
27✔
2038
        *position += copy_length;
27✔
2039
        buffer[*position] = '\0';
27✔
2040
    } else if (*position < capacity) {
9!
2041
        buffer[*position] = '\0';
×
2042
    } else if (capacity > 0) {
×
2043
        buffer[capacity - 1] = '\0';
×
2044
    }
2045

2046
    return 1;
27✔
2047
}
9✔
2048

2049
static int
2050
thumbnailer_format_append_decimal(char *buffer,
9✔
2051
                                  size_t capacity,
2052
                                  size_t *position,
2053
                                  int value,
2054
                                  int *truncated)
2055
{
2056
    char digits[16];
2057
    size_t index;
2058
    size_t length;
2059
    unsigned int magnitude;
2060
    int negative;
2061

2062
    if (buffer == NULL || position == NULL) {
9!
2063
        return 0;
×
2064
    }
2065

2066
    index = 0;
9✔
2067
    magnitude = 0;
9✔
2068
    negative = 0;
9✔
2069

2070
    if (value < 0) {
9!
2071
        negative = 1;
×
2072
        magnitude = (unsigned int)(-value);
×
2073
    } else {
2074
        magnitude = (unsigned int)value;
9✔
2075
    }
2076

2077
    do {
3✔
2078
        digits[index] = (char)('0' + (magnitude % 10));
21✔
2079
        index += 1;
21✔
2080
        magnitude /= 10;
21✔
2081
    } while (magnitude != 0);
21✔
2082

2083
    if (negative) {
9!
2084
        digits[index] = '-';
×
2085
        index += 1;
×
2086
    }
2087

2088
    length = index;
9✔
2089

2090
    while (index > 0) {
36✔
2091
        index -= 1;
27✔
2092
        if (!thumbnailer_format_append_char(buffer,
36!
2093
                                            capacity,
9✔
2094
                                            position,
9✔
2095
                                            digits[index],
27✔
2096
                                            truncated)) {
9✔
2097
            return 0;
×
2098
        }
2099
    }
2100

2101
    if (length == 0) {
9!
2102
        if (!thumbnailer_format_append_char(buffer,
×
2103
                                            capacity,
2104
                                            position,
2105
                                            '0',
2106
                                            truncated)) {
2107
            return 0;
×
2108
        }
2109
    }
2110

2111
    return 1;
9✔
2112
}
3✔
2113

2114
static int
2115
thumbnailer_safe_format(char *buffer,
27✔
2116
                        size_t capacity,
2117
                        char const *format,
2118
                        ...)
2119
{
2120
    va_list args;
2121
    size_t position;
2122
    char const *ptr;
2123
    int truncated;
2124
    char const *text;
2125
    int value;
2126

2127
    if (buffer == NULL || capacity == 0 || format == NULL) {
27!
2128
        return 0;
×
2129
    }
2130

2131
    position = 0;
27✔
2132
    truncated = 0;
27✔
2133
    buffer[0] = '\0';
27✔
2134

2135
    va_start(args, format);
27✔
2136
    ptr = format;
27✔
2137
    while (ptr != NULL && ptr[0] != '\0') {
342!
2138
        if (ptr[0] != '%') {
315✔
2139
            if (!thumbnailer_format_append_char(buffer,
372!
2140
                                                capacity,
93✔
2141
                                                &position,
2142
                                                ptr[0],
279✔
2143
                                                &truncated)) {
2144
                va_end(args);
×
2145
                return 0;
×
2146
            }
2147
            ptr += 1;
279✔
2148
            continue;
279✔
2149
        }
2150

2151
        ptr += 1;
36✔
2152
        if (ptr[0] == '%') {
36!
2153
            if (!thumbnailer_format_append_char(buffer,
×
2154
                                                capacity,
2155
                                                &position,
2156
                                                '%',
2157
                                                &truncated)) {
2158
                va_end(args);
×
2159
                return 0;
×
2160
            }
2161
            ptr += 1;
×
2162
            continue;
×
2163
        }
2164

2165
        if (ptr[0] == 's') {
36✔
2166
            text = va_arg(args, char const *);
27✔
2167
            if (!thumbnailer_format_append_text(buffer,
36!
2168
                                                capacity,
9✔
2169
                                                &position,
2170
                                                text,
9✔
2171
                                                &truncated)) {
2172
                va_end(args);
×
2173
                return 0;
×
2174
            }
2175
            ptr += 1;
27✔
2176
            continue;
27✔
2177
        }
2178

2179
        if (ptr[0] == 'd') {
9!
2180
            value = va_arg(args, int);
9✔
2181
            if (!thumbnailer_format_append_decimal(buffer,
12!
2182
                                                   capacity,
3✔
2183
                                                   &position,
2184
                                                   value,
3✔
2185
                                                   &truncated)) {
2186
                va_end(args);
×
2187
                return 0;
×
2188
            }
2189
            ptr += 1;
9✔
2190
            continue;
9✔
2191
        }
2192

2193
        va_end(args);
×
2194
        return 0;
×
2195
    }
2196
    va_end(args);
27✔
2197

2198
    if (truncated != 0 && capacity > 0) {
27!
2199
        buffer[capacity - 1] = '\0';
×
2200
    }
2201

2202
    return 1;
27✔
2203
}
9✔
2204

2205
/*
2206
 * thumbnailer_sleep_briefly
2207
 *
2208
 * Yield the CPU for a short duration so child polling loops avoid busy
2209
 * waiting.
2210
 *
2211
 */
2212
static void
2213
thumbnailer_sleep_briefly(void)
×
2214
{
2215
# if HAVE_NANOSLEEP
2216
    struct timespec ts;
2217
# endif
2218

2219
# if HAVE_NANOSLEEP
2220
    ts.tv_sec = 0;
×
2221
    ts.tv_nsec = 10000000L;
×
2222
    nanosleep(&ts, NULL);
×
2223
# elif defined(_WIN32)
2224
    Sleep(10);
2225
# else
2226
    (void)usleep(10000);
2227
# endif
2228
}
×
2229

2230
# if !defined(_WIN32) && defined(HAVE__REALPATH) && !defined(HAVE_REALPATH)
2231
static char *
2232
thumbnailer_resolve_without_realpath(char const *path)
2233
{
2234
    char *cwd;
2235
    char *resolved;
2236
    size_t cwd_length;
2237
    size_t path_length;
2238
    int need_separator;
2239

2240
    cwd = NULL;
2241
    resolved = NULL;
2242
    cwd_length = 0;
2243
    path_length = 0;
2244
    need_separator = 0;
2245

2246
    if (path == NULL) {
2247
        return NULL;
2248
    }
2249

2250
    if (path[0] == '/') {
2251
        path_length = strlen(path);
2252
        resolved = malloc(path_length + 1);
2253
        if (resolved == NULL) {
2254
            return NULL;
2255
        }
2256
        memcpy(resolved, path, path_length + 1);
2257

2258
        return resolved;
2259
    }
2260

2261
#  if defined(PATH_MAX)
2262
    cwd = malloc(PATH_MAX);
2263
    if (cwd != NULL) {
2264
        if (getcwd(cwd, PATH_MAX) != NULL) {
2265
            cwd_length = strlen(cwd);
2266
            path_length = strlen(path);
2267
            need_separator = 0;
2268
            if (cwd_length > 0 && cwd[cwd_length - 1] != '/') {
2269
                need_separator = 1;
2270
            }
2271
            resolved = malloc(cwd_length + need_separator + path_length + 1);
2272
            if (resolved != NULL) {
2273
                memcpy(resolved, cwd, cwd_length);
2274
                if (need_separator != 0) {
2275
                    resolved[cwd_length] = '/';
2276
                }
2277
                memcpy(resolved + cwd_length + need_separator,
2278
                       path,
2279
                       path_length + 1);
2280
            }
2281
            free(cwd);
2282
            if (resolved != NULL) {
2283
                return resolved;
2284
            }
2285
        } else {
2286
            free(cwd);
2287
        }
2288
    }
2289
#  endif  /* PATH_MAX */
2290

2291
    path_length = strlen(path);
2292
    resolved = malloc(path_length + 1);
2293
    if (resolved == NULL) {
2294
        return NULL;
2295
    }
2296
    memcpy(resolved, path, path_length + 1);
2297

2298
    return resolved;
2299
}
2300
# endif  /* !defined(_WIN32) && defined(HAVE__REALPATH) && !defined(HAVE_REALPATH) */
2301

2302
/*
2303
 * thumbnailer_resolve_path
2304
 *
2305
 * Resolve the supplied path to an absolute canonical path when possible.
2306
 *
2307
 * Arguments:
2308
 *     path - original filesystem path.
2309
 * Returns:
2310
 *     Newly allocated canonical path or NULL on failure.
2311
 */
2312
static char *
2313
thumbnailer_resolve_path(char const *path)
9✔
2314
{
2315
    char *resolved;
2316

2317
    resolved = NULL;
9✔
2318

2319
    if (path == NULL) {
9!
2320
        return NULL;
×
2321
    }
2322

2323
# if defined(HAVE__FULLPATH)
2324
    resolved = _fullpath(NULL, path, 0);
2325
# elif defined(HAVE__REALPATH)
2326
    resolved = _realpath(path, NULL);
2327
# elif defined(HAVE_REALPATH)
2328
    resolved = realpath(path, NULL);
9✔
2329
# else
2330
    resolved = thumbnailer_resolve_without_realpath(path);
2331
# endif
2332

2333
    return resolved;
9✔
2334
}
3✔
2335

2336
struct thumbnailer_string_list {
2337
    char **items;
2338
    size_t length;
2339
    size_t capacity;
2340
};
2341

2342
struct thumbnailer_entry {
2343
    char *exec_line;
2344
    char *tryexec;
2345
    struct thumbnailer_string_list *mime_types;
2346
};
2347

2348
/*
2349
 * thumbnailer_strdup
2350
 *
2351
 * Duplicate a string with malloc so thumbnail helpers own their copies.
2352
 *
2353
 * Arguments:
2354
 *     src - zero-terminated string to copy; may be NULL.
2355
 * Returns:
2356
 *     Newly allocated duplicate or NULL on failure/NULL input.
2357
 */
2358
static char *
2359
thumbnailer_strdup(char const *src)
117✔
2360
{
2361
    char *copy;
2362
    size_t length;
2363

2364
    copy = NULL;
117✔
2365
    length = 0;
117✔
2366

2367
    if (src == NULL) {
117!
2368
        return NULL;
×
2369
    }
2370

2371
    length = strlen(src);
117✔
2372
    copy = malloc(length + 1);
117✔
2373
    if (copy == NULL) {
117!
2374
        return NULL;
×
2375
    }
2376
    memcpy(copy, src, length + 1);
117✔
2377

2378
    return copy;
117✔
2379
}
39✔
2380

2381
/*
2382
 * thumbnailer_string_list_new
2383
 *
2384
 * Allocate an empty expandable string list used throughout the loader.
2385
 *
2386
 * Arguments:
2387
 *     None.
2388
 * Returns:
2389
 *     Newly allocated list instance or NULL on failure.
2390
 */
2391
static struct thumbnailer_string_list *
2392
thumbnailer_string_list_new(void)
18✔
2393
{
2394
    struct thumbnailer_string_list *list;
2395

2396
    list = malloc(sizeof(*list));
18✔
2397
    if (list == NULL) {
18!
2398
        return NULL;
×
2399
    }
2400

2401
    list->items = NULL;
18✔
2402
    list->length = 0;
18✔
2403
    list->capacity = 0;
18✔
2404

2405
    return list;
18✔
2406
}
6✔
2407

2408
/*
2409
 * thumbnailer_string_list_free
2410
 *
2411
 * Release every string stored in the list and free the container itself.
2412
 *
2413
 * Arguments:
2414
 *     list - list instance produced by thumbnailer_string_list_new().
2415
 */
2416
static void
2417
thumbnailer_string_list_free(struct thumbnailer_string_list *list)
30✔
2418
{
2419
    size_t index;
2420

2421
    index = 0;
30✔
2422

2423
    if (list == NULL) {
30✔
2424
        return;
12✔
2425
    }
2426

2427
    if (list->items != NULL) {
18!
2428
        for (index = 0; index < list->length; ++index) {
81✔
2429
            free(list->items[index]);
63✔
2430
            list->items[index] = NULL;
63✔
2431
        }
21✔
2432
        free(list->items);
18✔
2433
        list->items = NULL;
18✔
2434
    }
6✔
2435

2436
    free(list);
18✔
2437
}
10✔
2438

2439
/*
2440
 * thumbnailer_string_list_append
2441
 *
2442
 * Append a copy of the supplied string to the dynamic list.
2443
 *
2444
 * Arguments:
2445
 *     list  - destination list.
2446
 *     value - string to duplicate and append.
2447
 * Returns:
2448
 *     1 on success, 0 on allocation failure or invalid input.
2449
 */
2450
static int
2451
thumbnailer_string_list_append(struct thumbnailer_string_list *list,
63✔
2452
                               char const *value)
2453
{
2454
    size_t new_capacity;
2455
    char **new_items;
2456
    char *copy;
2457

2458
    new_capacity = 0;
63✔
2459
    new_items = NULL;
63✔
2460
    copy = NULL;
63✔
2461

2462
    if (list == NULL || value == NULL) {
63!
2463
        return 0;
×
2464
    }
2465

2466
    copy = thumbnailer_strdup(value);
63✔
2467
    if (copy == NULL) {
63!
2468
        return 0;
×
2469
    }
2470

2471
    if (list->length == list->capacity) {
63✔
2472
        new_capacity = (list->capacity == 0) ? 4 : list->capacity * 2;
18!
2473
        new_items = realloc(list->items,
24✔
2474
                            new_capacity * sizeof(*list->items));
6✔
2475
        if (new_items == NULL) {
18!
2476
            free(copy);
×
2477
            return 0;
×
2478
        }
2479
        list->items = new_items;
18✔
2480
        list->capacity = new_capacity;
18✔
2481
    }
6✔
2482

2483
    list->items[list->length] = copy;
63✔
2484
    list->length += 1;
63✔
2485

2486
    return 1;
63✔
2487
}
21✔
2488

2489
/*
2490
 * thumbnailer_entry_init
2491
 *
2492
 * Prepare a thumbnailer_entry structure for population.
2493
 *
2494
 * Arguments:
2495
 *     entry - caller-provided structure to initialize.
2496
 */
2497
static void
2498
thumbnailer_entry_init(struct thumbnailer_entry *entry)
12✔
2499
{
2500
    if (entry == NULL) {
12!
2501
        return;
×
2502
    }
2503

2504
    entry->exec_line = NULL;
12✔
2505
    entry->tryexec = NULL;
12✔
2506
    entry->mime_types = NULL;
12✔
2507
}
4✔
2508

2509
/*
2510
 * thumbnailer_entry_clear
2511
 *
2512
 * Release every heap allocation associated with a thumbnailer_entry.
2513
 *
2514
 * Arguments:
2515
 *     entry - structure previously initialized with thumbnailer_entry_init().
2516
 */
2517
static void
2518
thumbnailer_entry_clear(struct thumbnailer_entry *entry)
12✔
2519
{
2520
    if (entry == NULL) {
12!
2521
        return;
×
2522
    }
2523

2524
    free(entry->exec_line);
12✔
2525
    entry->exec_line = NULL;
12✔
2526
    free(entry->tryexec);
12✔
2527
    entry->tryexec = NULL;
12✔
2528
    thumbnailer_string_list_free(entry->mime_types);
12✔
2529
    entry->mime_types = NULL;
12✔
2530
}
4✔
2531

2532
/*
2533
 * thumbnailer_join_paths
2534
 *
2535
 * Concatenate two path fragments inserting a slash when required.
2536
 *
2537
 * Arguments:
2538
 *     left  - directory prefix.
2539
 *     right - trailing component.
2540
 * Returns:
2541
 *     Newly allocated combined path or NULL on failure.
2542
 */
2543
static char *
2544
thumbnailer_join_paths(char const *left, char const *right)
36✔
2545
{
2546
    size_t left_length;
2547
    size_t right_length;
2548
    int need_separator;
2549
    char *combined;
2550

2551
    left_length = 0;
36✔
2552
    right_length = 0;
36✔
2553
    need_separator = 0;
36✔
2554
    combined = NULL;
36✔
2555

2556
    if (left == NULL || right == NULL) {
36!
2557
        return NULL;
×
2558
    }
2559

2560
    left_length = strlen(left);
36✔
2561
    right_length = strlen(right);
36✔
2562
    need_separator = 0;
36✔
2563

2564
    if (left_length > 0 && right_length > 0 &&
48!
2565
            left[left_length - 1] != '/' && right[0] != '/') {
36!
2566
        need_separator = 1;
36✔
2567
    }
12✔
2568

2569
    combined = malloc(left_length + right_length + need_separator + 1);
36✔
2570
    if (combined == NULL) {
36!
2571
        return NULL;
×
2572
    }
2573

2574
    memcpy(combined, left, left_length);
36✔
2575
    if (need_separator) {
36!
2576
        combined[left_length] = '/';
36✔
2577
        memcpy(combined + left_length + 1, right, right_length);
36✔
2578
        combined[left_length + right_length + 1] = '\0';
36✔
2579
    } else {
12✔
2580
        memcpy(combined + left_length, right, right_length);
×
2581
        combined[left_length + right_length] = '\0';
×
2582
    }
2583

2584
    return combined;
36✔
2585
}
12✔
2586

2587
/*
2588
 * thumbnailer_collect_directories
2589
 *
2590
 * Enumerate directories that may contain FreeDesktop thumbnailer
2591
 * definitions according to the XDG specification.
2592
 *
2593
 * GNOME thumbnailers follow the XDG data directory contract:
2594
 *
2595
 *     +------------------+      +---------------------------+
2596
 *     | HOME/.local/share| ---> | HOME/.local/share/        |
2597
 *     |                  |      |    thumbnailers/(*.thumbnailer)
2598
 *     +------------------+      +---------------------------+
2599
 *
2600
 *     +------------------+      +---------------------------+
2601
 *     | XDG_DATA_DIRS    | ---> | <dir>/thumbnailers/(*.thumbnailer)
2602
 *     +------------------+      +---------------------------+
2603
 *
2604
 * The helper below expands both sources so that the caller can iterate
2605
 * through every known definition in order of precedence.
2606
 *
2607
 * Arguments:
2608
 *     None.
2609
 * Returns:
2610
 *     Newly allocated list of directory paths or NULL on failure.
2611
 */
2612
static struct thumbnailer_string_list *
2613
thumbnailer_collect_directories(void)
9✔
2614
{
2615
    struct thumbnailer_string_list *dirs;
2616
    char const *xdg_data_dirs;
2617
    char const *home_dir;
2618
    char const *default_dirs;
2619
    char *candidate;
2620
    char *local_share;
2621
    char *dirs_copy;
2622
    char *token;
2623

2624
    dirs = NULL;
9✔
2625
    xdg_data_dirs = NULL;
9✔
2626
    home_dir = NULL;
9✔
2627
    default_dirs = NULL;
9✔
2628
    candidate = NULL;
9✔
2629
    local_share = NULL;
9✔
2630
    dirs_copy = NULL;
9✔
2631
    token = NULL;
9✔
2632

2633
    dirs = thumbnailer_string_list_new();
9✔
2634
    if (dirs == NULL) {
9!
2635
        return NULL;
×
2636
    }
2637

2638
    home_dir = getenv("HOME");
9✔
2639
    loader_trace_message(
15!
2640
        "thumbnailer_collect_directories: HOME=%s",
2641
        (home_dir != NULL && home_dir[0] != '\0') ? home_dir : "(unset)");
9!
2642
    if (home_dir != NULL && home_dir[0] != '\0') {
9!
2643
        local_share = thumbnailer_join_paths(home_dir,
9✔
2644
                                             ".local/share");
2645
        if (local_share != NULL) {
9!
2646
            candidate = thumbnailer_join_paths(local_share,
9✔
2647
                                               "thumbnailers");
2648
            if (candidate != NULL) {
9!
2649
                if (!thumbnailer_string_list_append(dirs, candidate)) {
9!
2650
                    free(candidate);
×
2651
                    free(local_share);
×
2652
                    thumbnailer_string_list_free(dirs);
×
2653
                    return NULL;
×
2654
                }
2655
                loader_trace_message(
9✔
2656
                    "thumbnailer_collect_directories: added %s",
2657
                    candidate);
3✔
2658
                free(candidate);
9✔
2659
                candidate = NULL;
9✔
2660
            }
3✔
2661
            free(local_share);
9✔
2662
            local_share = NULL;
9✔
2663
        }
3✔
2664
    }
3✔
2665

2666
    xdg_data_dirs = getenv("XDG_DATA_DIRS");
12✔
2667
    if (xdg_data_dirs == NULL || xdg_data_dirs[0] == '\0') {
12!
2668
        default_dirs = "/usr/local/share:/usr/share";
9✔
2669
        xdg_data_dirs = default_dirs;
9✔
2670
    }
3✔
2671
    loader_trace_message(
9✔
2672
        "thumbnailer_collect_directories: XDG_DATA_DIRS=%s",
2673
        xdg_data_dirs);
3✔
2674

2675
    dirs_copy = thumbnailer_strdup(xdg_data_dirs);
9✔
2676
    if (dirs_copy == NULL) {
9!
2677
        thumbnailer_string_list_free(dirs);
×
2678
        return NULL;
×
2679
    }
2680
    token = strtok(dirs_copy, ":");
9✔
2681
    while (token != NULL) {
27✔
2682
        candidate = thumbnailer_join_paths(token, "thumbnailers");
18✔
2683
        if (candidate != NULL) {
18!
2684
            if (!thumbnailer_string_list_append(dirs, candidate)) {
18!
2685
                free(candidate);
×
2686
                free(dirs_copy);
×
2687
                thumbnailer_string_list_free(dirs);
×
2688
                return NULL;
×
2689
            }
2690
            loader_trace_message(
18✔
2691
                "thumbnailer_collect_directories: added %s",
2692
                candidate);
6✔
2693
            free(candidate);
18✔
2694
            candidate = NULL;
18✔
2695
        }
6✔
2696
        token = strtok(NULL, ":");
18✔
2697
    }
2698
    free(dirs_copy);
9✔
2699
    dirs_copy = NULL;
9✔
2700

2701
    return dirs;
9✔
2702
}
3✔
2703

2704
/*
2705
 * thumbnailer_trim_right
2706
 *
2707
 * Remove trailing whitespace in place from a mutable string.
2708
 *
2709
 * Arguments:
2710
 *     text - string to trim; must be writable and zero-terminated.
2711
 */
2712
static void
2713
thumbnailer_trim_right(char *text)
9✔
2714
{
2715
    size_t length;
2716

2717
    length = 0;
9✔
2718

2719
    if (text == NULL) {
9!
2720
        return;
×
2721
    }
2722

2723
    length = strlen(text);
9✔
2724
    while (length > 0 && isspace((unsigned char)text[length - 1]) != 0) {
18!
2725
        text[length - 1] = '\0';
9✔
2726
        length -= 1;
9✔
2727
    }
2728
}
3✔
2729

2730
/*
2731
 * thumbnailer_trim_left
2732
 *
2733
 * Skip leading whitespace so parsers can focus on significant tokens.
2734
 *
2735
 * Arguments:
2736
 *     text - string to inspect; may be NULL.
2737
 * Returns:
2738
 *     Pointer to first non-space character or NULL when input is NULL.
2739
 */
2740
static char *
2741
thumbnailer_trim_left(char *text)
9✔
2742
{
2743
    if (text == NULL) {
9!
2744
        return NULL;
×
2745
    }
2746

2747
    while (*text != '\0' && isspace((unsigned char)*text) != 0) {
9!
2748
        text += 1;
×
2749
    }
2750

2751
    return text;
9✔
2752
}
3✔
2753

2754
/*
2755
 * thumbnailer_parse_file
2756
 *
2757
 * Populate a thumbnailer_entry by parsing a .thumbnailer ini file.
2758
 *
2759
 * Arguments:
2760
 *     path  - filesystem path to the ini file.
2761
 *     entry - output structure initialized with thumbnailer_entry_init().
2762
 * Returns:
2763
 *     1 on success, 0 on parse error or allocation failure.
2764
 */
2765
static int
2766
thumbnailer_parse_file(char const *path, struct thumbnailer_entry *entry)
×
2767
{
2768
    FILE *fp;
2769
    char line[1024];
2770
    int in_group;
2771
    char *trimmed;
2772
    char *key_end;
2773
    char *value;
2774
    char *token_start;
2775
    char *token_end;
2776
    struct thumbnailer_string_list *mime_types;
2777
    size_t index;
2778

2779
    fp = NULL;
×
2780
    in_group = 0;
×
2781
    trimmed = NULL;
×
2782
    key_end = NULL;
×
2783
    value = NULL;
×
2784
    token_start = NULL;
×
2785
    token_end = NULL;
×
2786
    mime_types = NULL;
×
2787
    index = 0;
×
2788

2789
    if (path == NULL || entry == NULL) {
×
2790
        return 0;
×
2791
    }
2792

2793
    fp = fopen(path, "r");
×
2794
    if (fp == NULL) {
×
2795
        return 0;
×
2796
    }
2797

2798
    mime_types = thumbnailer_string_list_new();
×
2799
    if (mime_types == NULL) {
×
2800
        fclose(fp);
×
2801
        fp = NULL;
×
2802
        return 0;
×
2803
    }
2804

2805
    while (fgets(line, sizeof(line), fp) != NULL) {
×
2806
        trimmed = thumbnailer_trim_left(line);
×
2807
        thumbnailer_trim_right(trimmed);
×
2808
        if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') {
×
2809
            continue;
×
2810
        }
2811
        if (trimmed[0] == '[') {
×
2812
            key_end = strchr(trimmed, ']');
×
2813
            if (key_end != NULL) {
×
2814
                *key_end = '\0';
×
2815
                if (strcmp(trimmed + 1, "Thumbnailer Entry") == 0) {
×
2816
                    in_group = 1;
×
2817
                } else {
2818
                    in_group = 0;
×
2819
                }
2820
            }
2821
            continue;
×
2822
        }
2823
        if (!in_group) {
×
2824
            continue;
×
2825
        }
2826
        key_end = strchr(trimmed, '=');
×
2827
        if (key_end == NULL) {
×
2828
            continue;
×
2829
        }
2830
        *key_end = '\0';
×
2831
        value = thumbnailer_trim_left(key_end + 1);
×
2832
        thumbnailer_trim_right(trimmed);
×
2833
        thumbnailer_trim_right(value);
×
2834
        if (strcmp(trimmed, "Exec") == 0) {
×
2835
            free(entry->exec_line);
×
2836
            entry->exec_line = thumbnailer_strdup(value);
×
2837
            if (entry->exec_line == NULL) {
×
2838
                fclose(fp);
×
2839
                fp = NULL;
×
2840
                thumbnailer_string_list_free(mime_types);
×
2841
                mime_types = NULL;
×
2842
                return 0;
×
2843
            }
2844
        } else if (strcmp(trimmed, "TryExec") == 0) {
×
2845
            free(entry->tryexec);
×
2846
            entry->tryexec = thumbnailer_strdup(value);
×
2847
            if (entry->tryexec == NULL) {
×
2848
                fclose(fp);
×
2849
                fp = NULL;
×
2850
                thumbnailer_string_list_free(mime_types);
×
2851
                mime_types = NULL;
×
2852
                return 0;
×
2853
            }
2854
        } else if (strcmp(trimmed, "MimeType") == 0) {
×
2855
            for (index = 0; index < mime_types->length; ++index) {
×
2856
                free(mime_types->items[index]);
×
2857
                mime_types->items[index] = NULL;
×
2858
            }
2859
            mime_types->length = 0;
×
2860
            token_start = value;
×
2861
            while (token_start != NULL && token_start[0] != '\0') {
×
2862
                token_end = strchr(token_start, ';');
×
2863
                if (token_end != NULL) {
×
2864
                    *token_end = '\0';
×
2865
                }
2866
                token_start = thumbnailer_trim_left(token_start);
×
2867
                thumbnailer_trim_right(token_start);
×
2868
                if (token_start[0] != '\0') {
×
2869
                    if (!thumbnailer_string_list_append(mime_types,
×
2870
                                                       token_start)) {
2871
                        fclose(fp);
×
2872
                        fp = NULL;
×
2873
                        thumbnailer_string_list_free(mime_types);
×
2874
                        mime_types = NULL;
×
2875
                        return 0;
×
2876
                    }
2877
                }
2878
                if (token_end == NULL) {
×
2879
                    break;
×
2880
                }
2881
                token_start = token_end + 1;
×
2882
            }
2883
        }
2884
    }
2885

2886
    fclose(fp);
×
2887
    fp = NULL;
×
2888

2889
    thumbnailer_string_list_free(entry->mime_types);
×
2890
    entry->mime_types = mime_types;
×
2891

2892
    return 1;
×
2893
}
2894

2895
/*
2896
 * thumbnailer_has_tryexec
2897
 *
2898
 * Confirm that the optional TryExec binary exists and is executable.
2899
 *
2900
 * Arguments:
2901
 *     tryexec - value from the .thumbnailer file; may be NULL.
2902
 * Returns:
2903
 *     1 when executable, 0 otherwise.
2904
 */
2905
static int
2906
thumbnailer_has_tryexec(char const *tryexec)
×
2907
{
2908
    char const *path_variable;
2909
    char const *start;
2910
    char const *end;
2911
    size_t length;
2912
    char *candidate;
2913
    int executable;
2914

2915
    path_variable = NULL;
×
2916
    start = NULL;
×
2917
    end = NULL;
×
2918
    length = 0;
×
2919
    candidate = NULL;
×
2920
    executable = 0;
×
2921

2922
    if (tryexec == NULL || tryexec[0] == '\0') {
×
2923
        return 1;
×
2924
    }
2925

2926
    if (strchr(tryexec, '/') != NULL) {
×
2927
        if (access(tryexec, X_OK) == 0) {
×
2928
            return 1;
×
2929
        }
2930
        return 0;
×
2931
    }
2932

2933
    path_variable = getenv("PATH");
×
2934
    if (path_variable == NULL) {
×
2935
        return 0;
×
2936
    }
2937

2938
    start = path_variable;
×
2939
    while (*start != '\0') {
×
2940
        end = strchr(start, ':');
×
2941
        if (end == NULL) {
×
2942
            end = start + strlen(start);
×
2943
        }
2944
        length = (size_t)(end - start);
×
2945
        candidate = malloc(length + strlen(tryexec) + 2);
×
2946
        if (candidate == NULL) {
×
2947
            return 0;
×
2948
        }
2949
        memcpy(candidate, start, length);
×
2950
        candidate[length] = '/';
×
2951
        strcpy(candidate + length + 1, tryexec);
×
2952
        if (access(candidate, X_OK) == 0) {
×
2953
            executable = 1;
×
2954
            free(candidate);
×
2955
            candidate = NULL;
×
2956
            break;
×
2957
        }
2958
        free(candidate);
×
2959
        candidate = NULL;
×
2960
        if (*end == '\0') {
×
2961
            break;
×
2962
        }
2963
        start = end + 1;
×
2964
    }
2965

2966
    return executable;
×
2967
}
2968

2969
/*
2970
 * thumbnailer_mime_matches
2971
 *
2972
 * Test whether a thumbnailer MIME pattern matches the probed MIME type.
2973
 *
2974
 * Arguments:
2975
 *     pattern   - literal MIME pattern or prefix ending with "slash-asterisk".
2976
 *     mime_type - MIME value obtained from file --mime-type.
2977
 * Returns:
2978
 *     1 when the pattern applies, 0 otherwise.
2979
 */
2980
static int
2981
thumbnailer_mime_matches(char const *pattern, char const *mime_type)
×
2982
{
2983
    size_t length;
2984

2985
    length = 0;
×
2986

2987
    if (pattern == NULL || mime_type == NULL) {
×
2988
        return 0;
×
2989
    }
2990

2991
    if (strcmp(pattern, mime_type) == 0) {
×
2992
        return 1;
×
2993
    }
2994

2995
    length = strlen(pattern);
×
2996
    if (length >= 2 && pattern[length - 1] == '*' &&
×
2997
            pattern[length - 2] == '/') {
×
2998
        return strncmp(pattern, mime_type, length - 1) == 0;
×
2999
    }
3000

3001
    return 0;
×
3002
}
3003

3004
/*
3005
 * thumbnailer_supports_mime
3006
 *
3007
 * Iterate over MIME patterns advertised by a thumbnailer entry.
3008
 *
3009
 * Arguments:
3010
 *     entry     - parsed thumbnailer entry with mime_types list.
3011
 *     mime_type - MIME type string to match.
3012
 * Returns:
3013
 *     1 when a match is found, 0 otherwise.
3014
 */
3015
static int
3016
thumbnailer_supports_mime(struct thumbnailer_entry *entry,
×
3017
                          char const *mime_type)
3018
{
3019
    size_t index;
3020

3021
    index = 0;
×
3022

3023
    if (entry == NULL || entry->mime_types == NULL) {
×
3024
        return 0;
×
3025
    }
3026

3027
    if (mime_type == NULL) {
×
3028
        return 0;
×
3029
    }
3030

3031
    for (index = 0; index < entry->mime_types->length; ++index) {
×
3032
        if (thumbnailer_mime_matches(entry->mime_types->items[index],
×
3033
                                     mime_type)) {
3034
            return 1;
×
3035
        }
3036
    }
3037

3038
    return 0;
×
3039
}
3040

3041
/*
3042
 * thumbnailer_shell_quote
3043
 *
3044
 * Produce a single-quoted variant of an argument for readable logging.
3045
 *
3046
 * Arguments:
3047
 *     text - unquoted argument.
3048
 * Returns:
3049
 *     Newly allocated quoted string or NULL on allocation failure.
3050
 */
3051
static char *
3052
thumbnailer_shell_quote(char const *text)
36✔
3053
{
3054
    size_t index;
3055
    size_t length;
3056
    size_t needed;
3057
    char *quoted;
3058
    size_t position;
3059

3060
    index = 0;
36✔
3061
    length = 0;
36✔
3062
    needed = 0;
36✔
3063
    quoted = NULL;
36✔
3064
    position = 0;
36✔
3065

3066
    if (text == NULL) {
36!
3067
        return NULL;
×
3068
    }
3069

3070
    length = strlen(text);
36✔
3071
    needed = 2;
36✔
3072
    for (index = 0; index < length; ++index) {
783✔
3073
        if (text[index] == '\'') {
747!
3074
            needed += 4;
×
3075
        } else {
3076
            needed += 1;
747✔
3077
        }
3078
    }
249✔
3079

3080
    quoted = malloc(needed + 1);
36✔
3081
    if (quoted == NULL) {
36!
3082
        return NULL;
×
3083
    }
3084

3085
    quoted[position++] = '\'';
36✔
3086
    for (index = 0; index < length; ++index) {
783✔
3087
        if (text[index] == '\'') {
747!
3088
            quoted[position++] = '\'';
×
3089
            quoted[position++] = '\\';
×
3090
            quoted[position++] = '\'';
×
3091
            quoted[position++] = '\'';
×
3092
        } else {
3093
            quoted[position++] = text[index];
747✔
3094
        }
3095
    }
249✔
3096
    quoted[position++] = '\'';
36✔
3097
    quoted[position] = '\0';
36✔
3098

3099
    return quoted;
36✔
3100
}
12✔
3101

3102
struct thumbnailer_builder {
3103
    char *buffer;
3104
    size_t length;
3105
    size_t capacity;
3106
};
3107

3108
/*
3109
 * thumbnailer_builder_reserve
3110
 *
3111
 * Grow the builder buffer so future appends fit without overflow.
3112
 *
3113
 * Arguments:
3114
 *     builder    - mutable builder instance.
3115
 *     additional - number of bytes that must fit excluding terminator.
3116
 * Returns:
3117
 *     1 on success, 0 on allocation failure.
3118
 */
3119
static int
3120
thumbnailer_builder_reserve(struct thumbnailer_builder *builder,
351✔
3121
                            size_t additional)
3122
{
3123
    size_t new_capacity;
3124
    char *new_buffer;
3125

3126
    new_capacity = 0;
351✔
3127
    new_buffer = NULL;
351✔
3128

3129
    if (builder->length + additional + 1 <= builder->capacity) {
351✔
3130
        return 1;
324✔
3131
    }
3132

3133
    new_capacity = (builder->capacity == 0) ? 64 : builder->capacity;
27✔
3134
    while (new_capacity < builder->length + additional + 1) {
36✔
3135
        new_capacity *= 2;
9✔
3136
    }
3137

3138
    new_buffer = realloc(builder->buffer, new_capacity);
27✔
3139
    if (new_buffer == NULL) {
27!
3140
        return 0;
×
3141
    }
3142

3143
    builder->buffer = new_buffer;
27✔
3144
    builder->capacity = new_capacity;
27✔
3145

3146
    return 1;
27✔
3147
}
117✔
3148

3149
/*
3150
 * thumbnailer_builder_append_char
3151
 *
3152
 * Append a single character to the builder.
3153
 *
3154
 * Arguments:
3155
 *     builder - mutable builder instance.
3156
 *     ch      - character to append.
3157
 * Returns:
3158
 *     1 on success, 0 on allocation failure.
3159
 */
3160
static int
3161
thumbnailer_builder_append_char(struct thumbnailer_builder *builder,
288✔
3162
                                char ch)
3163
{
3164
    if (!thumbnailer_builder_reserve(builder, 1)) {
288!
3165
        return 0;
×
3166
    }
3167

3168
    builder->buffer[builder->length] = ch;
288✔
3169
    builder->length += 1;
288✔
3170
    builder->buffer[builder->length] = '\0';
288✔
3171

3172
    return 1;
288✔
3173
}
96✔
3174

3175
/*
3176
 * thumbnailer_builder_append
3177
 *
3178
 * Append a string of known length to the builder buffer.
3179
 *
3180
 * Arguments:
3181
 *     builder - mutable builder instance.
3182
 *     text    - zero-terminated string to append.
3183
 * Returns:
3184
 *     1 on success, 0 on allocation failure or NULL input.
3185
 */
3186
static int
3187
thumbnailer_builder_append(struct thumbnailer_builder *builder,
63✔
3188
                           char const *text)
3189
{
3190
    size_t length;
3191

3192
    length = 0;
63✔
3193

3194
    if (text == NULL) {
63!
3195
        return 1;
×
3196
    }
3197

3198
    length = strlen(text);
63✔
3199
    if (!thumbnailer_builder_reserve(builder, length)) {
63!
3200
        return 0;
×
3201
    }
3202

3203
    memcpy(builder->buffer + builder->length, text, length);
63✔
3204
    builder->length += length;
63✔
3205
    builder->buffer[builder->length] = '\0';
63✔
3206

3207
    return 1;
63✔
3208
}
21✔
3209

3210
/*
3211
 * thumbnailer_builder_clear
3212
 *
3213
 * Reset builder length to zero while retaining allocated storage.
3214
 *
3215
 * Arguments:
3216
 *     builder - builder to reset.
3217
 */
3218
static void
3219
thumbnailer_builder_clear(struct thumbnailer_builder *builder)
27✔
3220
{
3221
    if (builder->buffer != NULL) {
27!
3222
        builder->buffer[0] = '\0';
27✔
3223
    }
9✔
3224
    builder->length = 0;
27✔
3225
}
27✔
3226

3227
/*
3228
 * thumbnailer_command owns the argv array that will be passed to the
3229
 * thumbnailer helper.  The display field keeps a human readable command line
3230
 * for verbose logging without recomputing the shell quoted form.
3231
 */
3232
struct thumbnailer_command {
3233
    char **argv;
3234
    size_t argc;
3235
    char *display;
3236
};
3237

3238
/*
3239
 * thumbnailer_command_free
3240
 *
3241
 * Release argv entries, the array itself, and the formatted display copy.
3242
 *
3243
 * Arguments:
3244
 *     command - structure created by thumbnailer_build_command().
3245
 */
3246
static void
3247
thumbnailer_command_free(struct thumbnailer_command *command)
9✔
3248
{
3249
    size_t index;
3250

3251
    if (command == NULL) {
9!
3252
        return;
×
3253
    }
3254

3255
    if (command->argv != NULL) {
9!
3256
        for (index = 0; index < command->argc; ++index) {
45✔
3257
            free(command->argv[index]);
36✔
3258
            command->argv[index] = NULL;
36✔
3259
        }
12✔
3260
        free(command->argv);
9✔
3261
        command->argv = NULL;
9✔
3262
    }
3✔
3263

3264
    free(command->display);
9✔
3265
    command->display = NULL;
9✔
3266

3267
    free(command);
9✔
3268
}
3✔
3269

3270
/*
3271
 * thumbnailer_command_format
3272
 *
3273
 * Join argv entries into a human-readable command line for logging.
3274
 *
3275
 * Arguments:
3276
 *     argv - array of argument strings.
3277
 *     argc - number of entries stored in argv.
3278
 * Returns:
3279
 *     Newly allocated formatted string or NULL on allocation failure.
3280
 */
3281
static char *
3282
thumbnailer_command_format(char **argv, size_t argc)
9✔
3283
{
3284
    struct thumbnailer_builder builder;
3285
    char *quoted;
3286
    size_t index;
3287

3288
    builder.buffer = NULL;
9✔
3289
    builder.length = 0;
9✔
3290
    builder.capacity = 0;
9✔
3291
    quoted = NULL;
9✔
3292

3293
    for (index = 0; index < argc; ++index) {
45✔
3294
        if (index > 0) {
36✔
3295
            if (!thumbnailer_builder_append_char(&builder, ' ')) {
27!
3296
                free(builder.buffer);
×
3297
                builder.buffer = NULL;
×
3298
                return NULL;
×
3299
            }
3300
        }
9✔
3301
        quoted = thumbnailer_shell_quote(argv[index]);
36✔
3302
        if (quoted == NULL) {
36!
3303
            free(builder.buffer);
×
3304
            builder.buffer = NULL;
×
3305
            return NULL;
×
3306
        }
3307
        if (!thumbnailer_builder_append(&builder, quoted)) {
36!
3308
            free(quoted);
×
3309
            quoted = NULL;
×
3310
            free(builder.buffer);
×
3311
            builder.buffer = NULL;
×
3312
            return NULL;
×
3313
        }
3314
        free(quoted);
36✔
3315
        quoted = NULL;
36✔
3316
    }
12✔
3317

3318
    return builder.buffer;
9✔
3319
}
3✔
3320

3321
/*
3322
 * thumbnailer_build_command
3323
 *
3324
 * Expand a .thumbnailer Exec template into an argv array that honours
3325
 * FreeDesktop substitution rules.
3326
 *
3327
 * Arguments:
3328
 *     template_command - Exec line containing % tokens.
3329
 *     input_path       - filesystem path to the source document.
3330
 *     input_uri        - URI representation for %u expansions.
3331
 *     output_path      - PNG destination path for %o expansions.
3332
 *     size             - numeric size hint passed to %s tokens.
3333
 *     mime_type        - MIME value for %m replacements.
3334
 * Returns:
3335
 *     Newly allocated command or NULL on parse/allocation failure.
3336
 */
3337
static struct thumbnailer_command *
3338
thumbnailer_build_command(char const *template_command,
9✔
3339
                          char const *input_path,
3340
                          char const *input_uri,
3341
                          char const *output_path,
3342
                          int size,
3343
                          char const *mime_type)
3344
{
3345
    struct thumbnailer_builder builder;
3346
    struct thumbnailer_string_list *tokens;
3347
    struct thumbnailer_command *command;
3348
    char const *ptr;
3349
    char size_text[16];
3350
    int in_single_quote;
3351
    int in_double_quote;
3352
    int escape_next;
3353
    char const *replacement;
3354
    size_t index;
3355

3356
    builder.buffer = NULL;
9✔
3357
    builder.length = 0;
9✔
3358
    builder.capacity = 0;
9✔
3359
    tokens = NULL;
9✔
3360
    command = NULL;
9✔
3361
    ptr = template_command;
9✔
3362
    size_text[0] = '\0';
9✔
3363
    in_single_quote = 0;
9✔
3364
    in_double_quote = 0;
9✔
3365
    escape_next = 0;
9✔
3366
    replacement = NULL;
9✔
3367
    index = 0;
9✔
3368

3369
    if (template_command == NULL) {
9!
3370
        return NULL;
×
3371
    }
3372

3373
    tokens = thumbnailer_string_list_new();
9✔
3374
    if (tokens == NULL) {
9!
3375
        return NULL;
×
3376
    }
3377

3378
    if (size > 0) {
9!
3379
        if (!thumbnailer_safe_format(size_text,
12!
3380
                                     sizeof(size_text),
3381
                                     "%d",
3382
                                     size)) {
3✔
3383
            goto error;
×
3384
        }
3385
    }
3✔
3386

3387
    while (ptr != NULL && ptr[0] != '\0') {
324!
3388
        if (!in_single_quote && !in_double_quote && escape_next == 0 &&
411!
3389
                (ptr[0] == ' ' || ptr[0] == '\t')) {
315!
3390
            if (builder.length > 0) {
27!
3391
                if (!thumbnailer_string_list_append(tokens,
36!
3392
                                                    builder.buffer)) {
27✔
3393
                    goto error;
×
3394
                }
3395
                thumbnailer_builder_clear(&builder);
27✔
3396
            }
9✔
3397
            ptr += 1;
27✔
3398
            continue;
27✔
3399
        }
3400
        if (!in_single_quote && escape_next == 0 && ptr[0] == '\\') {
288!
3401
            escape_next = 1;
×
3402
            ptr += 1;
×
3403
            continue;
×
3404
        }
3405
        if (!in_double_quote && escape_next == 0 && ptr[0] == '\'') {
288!
3406
            in_single_quote = !in_single_quote;
×
3407
            ptr += 1;
×
3408
            continue;
×
3409
        }
3410
        if (!in_single_quote && escape_next == 0 && ptr[0] == '"') {
288!
3411
            in_double_quote = !in_double_quote;
×
3412
            ptr += 1;
×
3413
            continue;
×
3414
        }
3415
        if (escape_next != 0) {
288!
3416
            if (!thumbnailer_builder_append_char(&builder, ptr[0])) {
×
3417
                goto error;
×
3418
            }
3419
            escape_next = 0;
×
3420
            ptr += 1;
×
3421
            continue;
×
3422
        }
3423
        if (ptr[0] == '%' && ptr[1] != '\0') {
288!
3424
            replacement = NULL;
27✔
3425
            ptr += 1;
27✔
3426
            switch (ptr[0]) {
27!
3427
            case '%':
3428
                if (!thumbnailer_builder_append_char(&builder, '%')) {
×
3429
                    goto error;
×
3430
                }
3431
                break;
×
3432
            case 'i':
6✔
3433
            case 'I':
3434
                replacement = input_path;
9✔
3435
                break;
9✔
3436
            case 'u':
3437
            case 'U':
3438
                replacement = input_uri;
×
3439
                break;
×
3440
            case 'o':
6✔
3441
            case 'O':
3442
                replacement = output_path;
9✔
3443
                break;
9✔
3444
            case 's':
6✔
3445
            case 'S':
3446
                replacement = size_text;
9✔
3447
                break;
9✔
3448
            case 'm':
3449
            case 'M':
3450
                replacement = mime_type;
×
3451
                break;
×
3452
            default:
3453
                if (!thumbnailer_builder_append_char(&builder, '%') ||
×
3454
                        !thumbnailer_builder_append_char(&builder,
×
3455
                                                         ptr[0])) {
×
3456
                    goto error;
×
3457
                }
3458
                break;
×
3459
            }
3460
            if (replacement != NULL) {
27!
3461
                if (!thumbnailer_builder_append(&builder, replacement)) {
27!
3462
                    goto error;
×
3463
                }
3464
            }
9✔
3465
            ptr += 1;
27✔
3466
            continue;
27✔
3467
        }
3468
        if (!thumbnailer_builder_append_char(&builder, ptr[0])) {
261!
3469
            goto error;
×
3470
        }
3471
        ptr += 1;
261✔
3472
    }
3473

3474
    if (builder.length > 0) {
9!
3475
        if (!thumbnailer_string_list_append(tokens, builder.buffer)) {
9!
3476
            goto error;
×
3477
        }
3478
    }
3✔
3479

3480
    command = malloc(sizeof(*command));
9✔
3481
    if (command == NULL) {
9!
3482
        goto error;
×
3483
    }
3484

3485
    command->argc = tokens->length;
9✔
3486
    command->argv = NULL;
9✔
3487
    command->display = NULL;
9✔
3488

3489
    if (tokens->length == 0) {
9!
3490
        goto error;
×
3491
    }
3492

3493
    command->argv = malloc(sizeof(char *) * (tokens->length + 1));
9✔
3494
    if (command->argv == NULL) {
9!
3495
        goto error;
×
3496
    }
3497

3498
    for (index = 0; index < tokens->length; ++index) {
45✔
3499
        command->argv[index] = thumbnailer_strdup(tokens->items[index]);
36✔
3500
        if (command->argv[index] == NULL) {
36!
3501
            goto error;
×
3502
        }
3503
    }
12✔
3504
    command->argv[tokens->length] = NULL;
9✔
3505

3506
    command->display = thumbnailer_command_format(command->argv,
12✔
3507
                                                  command->argc);
3✔
3508
    if (command->display == NULL) {
9!
3509
        goto error;
×
3510
    }
3511

3512
    thumbnailer_string_list_free(tokens);
9✔
3513
    tokens = NULL;
9✔
3514
    if (builder.buffer != NULL) {
9!
3515
        free(builder.buffer);
9✔
3516
        builder.buffer = NULL;
9✔
3517
    }
3✔
3518

3519
    return command;
9✔
3520

3521
error:
3522
    if (tokens != NULL) {
×
3523
        thumbnailer_string_list_free(tokens);
×
3524
        tokens = NULL;
×
3525
    }
3526
    if (builder.buffer != NULL) {
×
3527
        free(builder.buffer);
×
3528
        builder.buffer = NULL;
×
3529
    }
3530
    if (command != NULL) {
×
3531
        thumbnailer_command_free(command);
×
3532
        command = NULL;
×
3533
    }
3534

3535
    return NULL;
×
3536
}
3✔
3537

3538
/*
3539
 * thumbnailer_is_evince_thumbnailer
3540
 *
3541
 * Detect whether the selected thumbnailer maps to evince-thumbnailer so
3542
 * the stdout redirection workaround can be applied.
3543
 *
3544
 * Arguments:
3545
 *     exec_line - Exec string parsed from the .thumbnailer file.
3546
 *     tryexec   - optional TryExec value for additional matching.
3547
 * Returns:
3548
 *     1 when evince-thumbnailer is referenced, 0 otherwise.
3549
 */
3550
static int
3551
thumbnailer_is_evince_thumbnailer(char const *exec_line,
×
3552
                                  char const *tryexec)
3553
{
3554
    char const *needle;
3555
    char const *basename;
3556

3557
    needle = "evince-thumbnailer";
×
3558
    basename = NULL;
×
3559

3560
    if (exec_line != NULL && strstr(exec_line, needle) != NULL) {
×
3561
        return 1;
×
3562
    }
3563

3564
    if (tryexec != NULL) {
×
3565
        basename = strrchr(tryexec, '/');
×
3566
        if (basename != NULL) {
×
3567
            basename += 1;
×
3568
        } else {
3569
            basename = tryexec;
×
3570
        }
3571
        if (strcmp(basename, needle) == 0) {
×
3572
            return 1;
×
3573
        }
3574
        if (strstr(tryexec, needle) != NULL) {
×
3575
            return 1;
×
3576
        }
3577
    }
3578

3579
    return 0;
×
3580
}
3581

3582
/*
3583
 * thumbnailer_build_evince_command
3584
 *
3585
 * Construct an argv sequence that streams evince-thumbnailer output to
3586
 * stdout so downstream code can capture the PNG safely.
3587
 *
3588
 * Arguments:
3589
 *     input_path - source document path.
3590
 *     size       - numeric size hint forwarded to the -s option.
3591
 * Returns:
3592
 *     Newly allocated command or NULL on allocation failure.
3593
 */
3594
static struct thumbnailer_command *
3595
thumbnailer_build_evince_command(char const *input_path,
×
3596
                                 int size)
3597
{
3598
    struct thumbnailer_command *command;
3599
    char size_text[16];
3600
    size_t index;
3601

3602
    command = NULL;
×
3603
    index = 0;
×
3604

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

3609
    command = malloc(sizeof(*command));
×
3610
    if (command == NULL) {
×
3611
        return NULL;
×
3612
    }
3613

3614
    command->argc = 5;
×
3615
    command->argv = malloc(sizeof(char *) * (command->argc + 1));
×
3616
    if (command->argv == NULL) {
×
3617
        thumbnailer_command_free(command);
×
3618
        return NULL;
×
3619
    }
3620

3621
    for (index = 0; index < command->argc + 1; ++index) {
×
3622
        command->argv[index] = NULL;
×
3623
    }
3624

3625
    if (!thumbnailer_safe_format(size_text,
×
3626
                                 sizeof(size_text),
3627
                                 "%d",
3628
                                 size)) {
3629
        size_text[0] = '\0';
×
3630
    }
3631

3632
    command->argv[0] = thumbnailer_strdup("evince-thumbnailer");
×
3633
    command->argv[1] = thumbnailer_strdup("-s");
×
3634
    command->argv[2] = thumbnailer_strdup(size_text);
×
3635
    command->argv[3] = thumbnailer_strdup(input_path);
×
3636
    command->argv[4] = thumbnailer_strdup("/dev/stdout");
×
3637
    command->argv[5] = NULL;
×
3638

3639
    for (index = 0; index < command->argc; ++index) {
×
3640
        if (command->argv[index] == NULL) {
×
3641
            thumbnailer_command_free(command);
×
3642
            return NULL;
×
3643
        }
3644
    }
3645

3646
    command->display = thumbnailer_command_format(command->argv,
×
3647
                                                  command->argc);
3648
    if (command->display == NULL) {
×
3649
        thumbnailer_command_free(command);
×
3650
        return NULL;
×
3651
    }
3652

3653
    return command;
×
3654
}
3655

3656
/*
3657
 * thumbnailer_build_file_uri
3658
 *
3659
 * Convert a filesystem path into a percent-encoded file:// URI.
3660
 *
3661
 * Arguments:
3662
 *     path - filesystem path; may be relative but will be resolved.
3663
 * Returns:
3664
 *     Newly allocated URI string or NULL on error.
3665
 */
3666
static char *
3667
thumbnailer_build_file_uri(char const *path)
9✔
3668
{
3669
    char *resolved;
3670
    size_t index;
3671
    size_t length;
3672
    size_t needed;
3673
    char *uri;
3674
    size_t position;
3675
    char const hex[] = "0123456789ABCDEF";
9✔
3676

3677
    resolved = NULL;
9✔
3678
    index = 0;
9✔
3679
    length = 0;
9✔
3680
    needed = 0;
9✔
3681
    uri = NULL;
9✔
3682
    position = 0;
9✔
3683

3684
    if (path == NULL) {
9!
3685
        return NULL;
×
3686
    }
3687

3688
    resolved = thumbnailer_resolve_path(path);
9✔
3689
    if (resolved == NULL) {
9!
3690
        return NULL;
×
3691
    }
3692

3693
    length = strlen(resolved);
9✔
3694
    needed = 7;
9✔
3695
    for (index = 0; index < length; ++index) {
498✔
3696
        unsigned char ch;
3697

3698
        ch = (unsigned char)resolved[index];
489✔
3699
        if (isalnum(ch) || ch == '-' || ch == '_' ||
514!
3700
                ch == '.' || ch == '~' || ch == '/') {
76!
3701
            needed += 1;
489✔
3702
        } else {
165✔
3703
            needed += 3;
×
3704
        }
3705
    }
165✔
3706

3707
    uri = malloc(needed + 1);
9✔
3708
    if (uri == NULL) {
9!
3709
        free(resolved);
×
3710
        resolved = NULL;
×
3711
        return NULL;
×
3712
    }
3713

3714
    memcpy(uri, "file://", 7);
9✔
3715
    position = 7;
9✔
3716
    for (index = 0; index < length; ++index) {
498✔
3717
        unsigned char ch;
3718

3719
        ch = (unsigned char)resolved[index];
489✔
3720
        if (isalnum(ch) || ch == '-' || ch == '_' ||
514!
3721
                ch == '.' || ch == '~' || ch == '/') {
76!
3722
            uri[position++] = (char)ch;
489✔
3723
        } else {
165✔
3724
            uri[position++] = '%';
×
3725
            uri[position++] = hex[(ch >> 4) & 0xF];
×
3726
            uri[position++] = hex[ch & 0xF];
×
3727
        }
3728
    }
165✔
3729
    uri[position] = '\0';
9✔
3730

3731
    free(resolved);
9✔
3732
    resolved = NULL;
9✔
3733

3734
    return uri;
9✔
3735
}
3✔
3736

3737
/*
3738
 * thumbnailer_guess_content_type
3739
 *
3740
 * Invoke the file(1) utility to obtain a MIME type for the input path.
3741
 *
3742
 * Arguments:
3743
 *     path - filesystem path passed to file --mime-type.
3744
 * Returns:
3745
 *     Newly allocated MIME type string or NULL on failure.
3746
 */
3747
static char *
3748
thumbnailer_guess_content_type(char const *path)
9✔
3749
{
3750
    int pipefd[2];
3751
    pid_t pid;
3752
    ssize_t bytes_read;
3753
    char buffer[256];
3754
    size_t total;
3755
    int status;
3756
    char *result;
3757
    char *trimmed;
3758

3759
    pipefd[0] = -1;
9✔
3760
    pipefd[1] = -1;
9✔
3761
    pid = (-1);
9✔
3762
    bytes_read = 0;
9✔
3763
    total = 0;
9✔
3764
    status = 0;
9✔
3765
    result = NULL;
9✔
3766
    trimmed = NULL;
9✔
3767

3768
    if (path == NULL) {
9!
3769
        return NULL;
×
3770
    }
3771

3772
    if (pipe(pipefd) < 0) {
9!
3773
        return NULL;
×
3774
    }
3775

3776
    pid = fork();
9✔
3777
    if (pid < 0) {
15!
3778
        close(pipefd[0]);
×
3779
        close(pipefd[1]);
×
3780
        return NULL;
×
3781
    }
3782

3783
    if (pid == 0) {
18✔
3784
        char const *argv[5];
3785

3786
        close(pipefd[0]);
9✔
3787
        if (dup2(pipefd[1], STDOUT_FILENO) < 0) {
9!
3788
            _exit(127);
×
3789
        }
3790
        close(pipefd[1]);
9✔
3791
        argv[0] = "file";
9✔
3792
        argv[1] = "-b";
9✔
3793
        argv[2] = "--mime-type";
9✔
3794
        argv[3] = path;
9✔
3795
        argv[4] = NULL;
9✔
3796
        execvp("file", (char * const *)argv);
9✔
3797
        _exit(127);
6✔
3798
    }
3799

3800
    close(pipefd[1]);
9✔
3801
    pipefd[1] = -1;
9✔
3802
    total = 0;
9✔
3803
    while ((bytes_read = read(pipefd[0], buffer + total,
18✔
3804
                              sizeof(buffer) - total - 1)) > 0) {
24✔
3805
        total += (size_t)bytes_read;
9✔
3806
        if (total >= sizeof(buffer) - 1) {
9!
3807
            break;
×
3808
        }
3809
    }
3810
    buffer[total] = '\0';
9✔
3811
    close(pipefd[0]);
9✔
3812
    pipefd[0] = -1;
9✔
3813

3814
    while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {
9!
3815
        continue;
×
3816
    }
3817

3818
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
9!
3819
        return NULL;
×
3820
    }
3821

3822
    trimmed = thumbnailer_trim_left(buffer);
9✔
3823
    thumbnailer_trim_right(trimmed);
9✔
3824
    if (trimmed[0] == '\0') {
9!
3825
        return NULL;
×
3826
    }
3827

3828
    result = thumbnailer_strdup(trimmed);
9✔
3829

3830
    return result;
9✔
3831
}
3✔
3832

3833
/*
3834
 * Thumbnailer supervision overview:
3835
 *
3836
 *     +-------------------+    pipe(stderr)    +-------------------+
3837
 *     | libsixel parent   | <----------------- | thumbnailer argv  |
3838
 *     |  - polls stdout   |                   |  - renders PNG     |
3839
 *     |  - polls stderr   | -----------------> |  - returns code   |
3840
 *     |  - waits pid      |    pipe(stdout)    |                   |
3841
 *     +-------------------+  posix_spawn/fork  +-------------------+
3842
 *
3843
 * Non-blocking pipes keep verbose thumbnailers from stalling the loop,
3844
 * and argv arrays mean Exec templates never pass through /bin/sh.
3845
 *
3846
 * thumbnailer_spawn is responsible for preparing pipes, launching the
3847
 * thumbnail helper, and streaming any emitted data back into libsixel.
3848
 *
3849
 *  - stderr is captured into stderr_output so verbose mode can replay the
3850
 *    diagnostics without leaking them to non-verbose invocations.
3851
 *  - stdout can optionally be redirected into a temporary file when
3852
 *    thumbnailers insist on writing image data to the standard output stream.
3853
 *  - All file descriptors are placed into non-blocking mode to avoid stalls
3854
 *    while the parent waits for the child process to complete.
3855
 * Arguments:
3856
 *     command          - prepared argv array to execute.
3857
 *     thumbnailer_name - friendly name used in log messages.
3858
 *     log_prefix       - identifier describing the current pipeline step.
3859
 *     capture_stdout   - non-zero to capture stdout into stdout_path.
3860
 *     stdout_path      - file receiving stdout when capture_stdout != 0.
3861
 * Returns:
3862
 *     SIXEL_OK on success or a libsixel error code on failure.
3863
 */
3864
static SIXELSTATUS
3865
thumbnailer_spawn(struct thumbnailer_command const *command,
9✔
3866
                  char const *thumbnailer_name,
3867
                  char const *log_prefix,
3868
                  int capture_stdout,
3869
                  char const *stdout_path)
3870
{
3871
    pid_t pid;
3872
    int status_code;
3873
    int wait_result;
3874
    SIXELSTATUS status;
3875
    char message[256];
3876
    int stderr_pipe[2];
3877
    int stdout_pipe[2];
3878
    int stderr_pipe_created;
3879
    int stdout_pipe_created;
3880
    int flags;
3881
    ssize_t read_result;
3882
    ssize_t stdout_read_result;
3883
    char stderr_buffer[256];
3884
    char stdout_buffer[4096];
3885
    char *stderr_output;
3886
    size_t stderr_length;
3887
    size_t stderr_capacity;
3888
    int child_exited;
3889
    int stderr_open;
3890
    int stdout_open;
3891
    int have_status;
3892
    int fatal_error;
3893
    int output_fd;
3894
    size_t write_offset;
3895
    ssize_t write_result;
3896
    size_t to_write;
3897
    char const *display_command;
3898
# if HAVE_POSIX_SPAWNP
3899
    posix_spawn_file_actions_t actions;
3900
    int spawn_result;
3901
# endif
3902

3903
    pid = (-1);
9✔
3904
    status_code = 0;
9✔
3905
    wait_result = 0;
9✔
3906
    status = SIXEL_RUNTIME_ERROR;
9✔
3907
    memset(message, 0, sizeof(message));
9✔
3908
    stderr_pipe[0] = -1;
9✔
3909
    stderr_pipe[1] = -1;
9✔
3910
    stdout_pipe[0] = -1;
9✔
3911
    stdout_pipe[1] = -1;
9✔
3912
    stderr_pipe_created = 0;
9✔
3913
    stdout_pipe_created = 0;
9✔
3914
    flags = 0;
9✔
3915
    read_result = 0;
9✔
3916
    stdout_read_result = 0;
9✔
3917
    stderr_output = NULL;
9✔
3918
    stderr_length = 0;
9✔
3919
    stderr_capacity = 0;
9✔
3920
    child_exited = 0;
9✔
3921
    stderr_open = 0;
9✔
3922
    stdout_open = 0;
9✔
3923
    have_status = 0;
9✔
3924
    fatal_error = 0;
9✔
3925
    output_fd = -1;
9✔
3926
    write_offset = 0;
9✔
3927
    write_result = 0;
9✔
3928
    to_write = 0;
9✔
3929
    display_command = NULL;
9✔
3930
# if HAVE_POSIX_SPAWNP
3931
    spawn_result = 0;
9✔
3932
# endif
3933

3934
    if (command == NULL || command->argv == NULL ||
9!
3935
            command->argv[0] == NULL) {
9!
3936
        return SIXEL_BAD_ARGUMENT;
×
3937
    }
3938

3939
    if (capture_stdout && stdout_path == NULL) {
9!
3940
        return SIXEL_BAD_ARGUMENT;
×
3941
    }
3942

3943
    if (capture_stdout) {
6!
3944
        output_fd = open(stdout_path,
×
3945
                         O_WRONLY | O_CREAT | O_TRUNC,
3946
                         0600);
3947
        if (output_fd < 0) {
×
3948
            if (!thumbnailer_safe_format(message,
×
3949
                                         sizeof(message),
3950
                                         "%s: open(%s) failed (%s).",
3951
                                         log_prefix,
3952
                                         stdout_path,
3953
                                         strerror(errno))) {
×
3954
                message[0] = '\0';
×
3955
            }
3956
            sixel_helper_set_additional_message(message);
×
3957
            goto cleanup;
×
3958
        }
3959
    }
3960

3961
    /* stderr is collected even for successful runs so verbose users can see
3962
     * the thumbnailer's own commentary.  Failing to set the pipe is not
3963
     * fatal; we continue without stderr capture in that case.
3964
     */
3965
    if (pipe(stderr_pipe) == 0) {
9!
3966
        stderr_pipe_created = 1;
9✔
3967
        stderr_open = 1;
9✔
3968
    }
3✔
3969

3970
    if (capture_stdout) {
12!
3971
        if (pipe(stdout_pipe) != 0) {
×
3972
            if (!thumbnailer_safe_format(message,
×
3973
                                         sizeof(message),
3974
                                         "%s: pipe() for stdout failed (%s).",
3975
                                         log_prefix,
3976
                                         strerror(errno))) {
×
3977
                message[0] = '\0';
×
3978
            }
3979
            sixel_helper_set_additional_message(message);
×
3980
            goto cleanup;
×
3981
        }
3982
        stdout_pipe_created = 1;
×
3983
        stdout_open = 1;
×
3984
    }
3985

3986
    display_command = (command->display != NULL) ?
15!
3987
            command->display : command->argv[0];
9!
3988
    loader_trace_message("%s: executing %s",
9✔
3989
                         log_prefix,
3✔
3990
                         display_command);
3✔
3991

3992
# if HAVE_POSIX_SPAWNP
3993
    if (posix_spawn_file_actions_init(&actions) != 0) {
9!
3994
        if (!thumbnailer_safe_format(message,
×
3995
                                     sizeof(message),
3996
                                     "%s: posix_spawn_file_actions_init() "
3997
                                     "failed.",
3998
                                     log_prefix)) {
3999
            message[0] = '\0';
×
4000
        }
4001
        sixel_helper_set_additional_message(message);
×
4002
        goto cleanup;
×
4003
    }
4004
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
9!
4005
        (void)posix_spawn_file_actions_adddup2(&actions,
9✔
4006
                                               stderr_pipe[1],
3✔
4007
                                               STDERR_FILENO);
4008
        (void)posix_spawn_file_actions_addclose(&actions,
9✔
4009
                                                stderr_pipe[0]);
3✔
4010
        (void)posix_spawn_file_actions_addclose(&actions,
9✔
4011
                                                stderr_pipe[1]);
3✔
4012
    }
3✔
4013
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
12!
4014
        (void)posix_spawn_file_actions_adddup2(&actions,
×
4015
                                               stdout_pipe[1],
4016
                                               STDOUT_FILENO);
4017
        (void)posix_spawn_file_actions_addclose(&actions,
×
4018
                                                stdout_pipe[0]);
4019
        (void)posix_spawn_file_actions_addclose(&actions,
×
4020
                                                stdout_pipe[1]);
4021
    }
4022
    if (output_fd >= 0) {
6!
4023
        (void)posix_spawn_file_actions_addclose(&actions,
×
4024
                                                output_fd);
4025
    }
4026
    spawn_result = posix_spawnp(&pid,
9✔
4027
                                command->argv[0],
9✔
4028
                                &actions,
4029
                                NULL,
4030
                                (char * const *)command->argv,
9✔
4031
                                environ);
3✔
4032
    posix_spawn_file_actions_destroy(&actions);
9✔
4033
    if (spawn_result != 0) {
9!
4034
        if (!thumbnailer_safe_format(message,
12!
4035
                                     sizeof(message),
4036
                                     "%s: posix_spawnp() failed (%s).",
4037
                                     log_prefix,
3✔
4038
                                     strerror(spawn_result))) {
3✔
4039
            message[0] = '\0';
×
4040
        }
4041
        sixel_helper_set_additional_message(message);
9✔
4042
        goto cleanup;
9✔
4043
    }
4044
# else
4045
    pid = fork();
4046
    if (pid < 0) {
4047
        if (!thumbnailer_safe_format(message,
4048
                                     sizeof(message),
4049
                                     "%s: fork() failed (%s).",
4050
                                     log_prefix,
4051
                                     strerror(errno))) {
4052
            message[0] = '\0';
4053
        }
4054
        sixel_helper_set_additional_message(message);
4055
        goto cleanup;
4056
    }
4057
    if (pid == 0) {
4058
        if (stderr_pipe_created && stderr_pipe[1] >= 0) {
4059
            if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) {
4060
                _exit(127);
4061
            }
4062
        }
4063
        if (stdout_pipe_created && stdout_pipe[1] >= 0) {
4064
            if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) {
4065
                _exit(127);
4066
            }
4067
        }
4068
        if (stderr_pipe[0] >= 0) {
4069
            close(stderr_pipe[0]);
4070
        }
4071
        if (stderr_pipe[1] >= 0) {
4072
            close(stderr_pipe[1]);
4073
        }
4074
        if (stdout_pipe[0] >= 0) {
4075
            close(stdout_pipe[0]);
4076
        }
4077
        if (stdout_pipe[1] >= 0) {
4078
            close(stdout_pipe[1]);
4079
        }
4080
        if (output_fd >= 0) {
4081
            close(output_fd);
4082
        }
4083
        execvp(command->argv[0], (char * const *)command->argv);
4084
        _exit(127);
4085
    }
4086
# endif
4087

4088
    loader_trace_message("%s: forked child pid=%ld",
×
4089
                         log_prefix,
4090
                         (long)pid);
4091

4092
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
×
4093
        close(stderr_pipe[1]);
×
4094
        stderr_pipe[1] = -1;
×
4095
    }
4096
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
×
4097
        close(stdout_pipe[1]);
×
4098
        stdout_pipe[1] = -1;
×
4099
    }
4100

4101
    if (stderr_pipe_created && stderr_pipe[0] >= 0) {
×
4102
        flags = fcntl(stderr_pipe[0], F_GETFL, 0);
×
4103
        if (flags >= 0) {
×
4104
            (void)fcntl(stderr_pipe[0],
×
4105
                        F_SETFL,
4106
                        flags | O_NONBLOCK);
4107
        }
4108
    }
4109
    if (stdout_pipe_created && stdout_pipe[0] >= 0) {
×
4110
        flags = fcntl(stdout_pipe[0], F_GETFL, 0);
×
4111
        if (flags >= 0) {
×
4112
            (void)fcntl(stdout_pipe[0],
×
4113
                        F_SETFL,
4114
                        flags | O_NONBLOCK);
4115
        }
4116
    }
4117

4118
    /* The monitoring loop drains stderr/stdout as long as any descriptor is
4119
     * open.  Non-blocking reads ensure the parent does not deadlock if the
4120
     * child process stalls or writes data in bursts.
4121
     */
4122
    while (!child_exited || stderr_open || stdout_open) {
×
4123
        if (stderr_pipe_created && stderr_open) {
×
4124
            read_result = read(stderr_pipe[0],
×
4125
                               stderr_buffer,
4126
                               (ssize_t)sizeof(stderr_buffer));
4127
            if (read_result > 0) {
×
4128
                if (stderr_length + (size_t)read_result + 1 >
×
4129
                        stderr_capacity) {
4130
                    size_t new_capacity;
4131
                    char *new_output;
4132

4133
                    new_capacity = stderr_capacity;
×
4134
                    if (new_capacity == 0) {
×
4135
                        new_capacity = 256;
×
4136
                    }
4137
                    while (stderr_length + (size_t)read_result + 1 >
×
4138
                            new_capacity) {
4139
                        new_capacity *= 2U;
×
4140
                    }
4141
                    new_output = realloc(stderr_output, new_capacity);
×
4142
                    if (new_output == NULL) {
×
4143
                        free(stderr_output);
×
4144
                        stderr_output = NULL;
×
4145
                        stderr_capacity = 0;
×
4146
                        stderr_length = 0;
×
4147
                        stderr_open = 0;
×
4148
                        if (stderr_pipe[0] >= 0) {
×
4149
                            close(stderr_pipe[0]);
×
4150
                            stderr_pipe[0] = -1;
×
4151
                        }
4152
                        stderr_pipe_created = 0;
×
4153
                        if (!thumbnailer_safe_format(message,
×
4154
                                                     sizeof(message),
4155
                                                     "%s: realloc() failed.",
4156
                                                     log_prefix)) {
4157
                            message[0] = '\0';
×
4158
                        }
4159
                        sixel_helper_set_additional_message(message);
×
4160
                        status = SIXEL_BAD_ALLOCATION;
×
4161
                        fatal_error = 1;
×
4162
                        break;
×
4163
                    }
4164
                    stderr_output = new_output;
×
4165
                    stderr_capacity = new_capacity;
×
4166
                }
4167
                memcpy(stderr_output + stderr_length,
×
4168
                       stderr_buffer,
4169
                       (size_t)read_result);
4170
                stderr_length += (size_t)read_result;
×
4171
                stderr_output[stderr_length] = '\0';
×
4172
            } else if (read_result == 0) {
×
4173
                stderr_open = 0;
×
4174
                if (stderr_pipe[0] >= 0) {
×
4175
                    close(stderr_pipe[0]);
×
4176
                    stderr_pipe[0] = -1;
×
4177
                }
4178
                stderr_pipe_created = 0;
×
4179
            } else if (errno == EINTR) {
×
4180
                /* retry */
4181
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4182
                /* no data */
4183
            } else {
4184
                if (!thumbnailer_safe_format(message,
×
4185
                                             sizeof(message),
4186
                                             "%s: read() failed (%s).",
4187
                                             log_prefix,
4188
                                             strerror(errno))) {
×
4189
                    message[0] = '\0';
×
4190
                }
4191
                sixel_helper_set_additional_message(message);
×
4192
                stderr_open = 0;
×
4193
                if (stderr_pipe[0] >= 0) {
×
4194
                    close(stderr_pipe[0]);
×
4195
                    stderr_pipe[0] = -1;
×
4196
                }
4197
                stderr_pipe_created = 0;
×
4198
            }
4199
        }
4200

4201
        if (stdout_pipe_created && stdout_open) {
×
4202
            stdout_read_result = read(stdout_pipe[0],
×
4203
                                      stdout_buffer,
4204
                                      (ssize_t)sizeof(stdout_buffer));
4205
            if (stdout_read_result > 0) {
×
4206
                write_offset = 0;
×
4207
                while (write_offset < (size_t)stdout_read_result) {
×
4208
                    to_write = (size_t)stdout_read_result - write_offset;
×
4209
                    write_result = write(output_fd,
×
4210
                                          stdout_buffer + write_offset,
4211
                                          to_write);
4212
                    if (write_result < 0) {
×
4213
                        if (errno == EINTR) {
×
4214
                            continue;
×
4215
                        }
4216
                        if (!thumbnailer_safe_format(message,
×
4217
                                                     sizeof(message),
4218
                                                     "%s: write() failed (%s).",
4219
                                                     log_prefix,
4220
                                                     strerror(errno))) {
×
4221
                            message[0] = '\0';
×
4222
                        }
4223
                        sixel_helper_set_additional_message(message);
×
4224
                        stdout_open = 0;
×
4225
                        fatal_error = 1;
×
4226
                        break;
×
4227
                    }
4228
                    write_offset += (size_t)write_result;
×
4229
                }
4230
            } else if (stdout_read_result == 0) {
×
4231
                stdout_open = 0;
×
4232
                if (stdout_pipe[0] >= 0) {
×
4233
                    close(stdout_pipe[0]);
×
4234
                    stdout_pipe[0] = -1;
×
4235
                }
4236
                stdout_pipe_created = 0;
×
4237
            } else if (errno == EINTR) {
×
4238
                /* retry */
4239
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4240
                /* no data */
4241
            } else {
4242
                if (!thumbnailer_safe_format(message,
×
4243
                                             sizeof(message),
4244
                                             "%s: read() failed (%s).",
4245
                                             log_prefix,
4246
                                             strerror(errno))) {
×
4247
                    message[0] = '\0';
×
4248
                }
4249
                sixel_helper_set_additional_message(message);
×
4250
                stdout_open = 0;
×
4251
                if (stdout_pipe[0] >= 0) {
×
4252
                    close(stdout_pipe[0]);
×
4253
                    stdout_pipe[0] = -1;
×
4254
                }
4255
                stdout_pipe_created = 0;
×
4256
            }
4257
        }
4258

4259
        if (!child_exited) {
×
4260
            wait_result = waitpid(pid, &status_code, WNOHANG);
×
4261
            if (wait_result > 0) {
×
4262
                child_exited = 1;
×
4263
                have_status = 1;
×
4264
            } else if (wait_result == 0) {
×
4265
                /* child running */
4266
            } else if (errno != EINTR) {
×
4267
                if (!thumbnailer_safe_format(message,
×
4268
                                             sizeof(message),
4269
                                             "%s: waitpid() failed (%s).",
4270
                                             log_prefix,
4271
                                             strerror(errno))) {
×
4272
                    message[0] = '\0';
×
4273
                }
4274
                sixel_helper_set_additional_message(message);
×
4275
                status = SIXEL_RUNTIME_ERROR;
×
4276
                fatal_error = 1;
×
4277
                break;
×
4278
            }
4279
        }
4280

4281
        if (!child_exited || stderr_open || stdout_open) {
×
4282
            thumbnailer_sleep_briefly();
×
4283
        }
4284
    }
4285

4286
    if (!child_exited) {
×
4287
        do {
4288
            wait_result = waitpid(pid, &status_code, 0);
×
4289
        } while (wait_result < 0 && errno == EINTR);
×
4290
        if (wait_result < 0) {
×
4291
            if (!thumbnailer_safe_format(message,
×
4292
                                         sizeof(message),
4293
                                         "%s: waitpid() failed (%s).",
4294
                                         log_prefix,
4295
                                         strerror(errno))) {
×
4296
                message[0] = '\0';
×
4297
            }
4298
            sixel_helper_set_additional_message(message);
×
4299
            status = SIXEL_RUNTIME_ERROR;
×
4300
            goto cleanup;
×
4301
        }
4302
        have_status = 1;
×
4303
    }
4304

4305
    if (!have_status) {
×
4306
        if (!thumbnailer_safe_format(message,
×
4307
                                     sizeof(message),
4308
                                     "%s: waitpid() failed (no status).",
4309
                                     log_prefix)) {
4310
            message[0] = '\0';
×
4311
        }
4312
        sixel_helper_set_additional_message(message);
×
4313
        status = SIXEL_RUNTIME_ERROR;
×
4314
        goto cleanup;
×
4315
    }
4316

4317
    if (!fatal_error) {
×
4318
        if (WIFEXITED(status_code) && WEXITSTATUS(status_code) == 0) {
×
4319
            status = SIXEL_OK;
×
4320
            loader_trace_message("%s: child pid=%ld exited successfully",
×
4321
                                 log_prefix,
4322
                                 (long)pid);
4323
        } else if (WIFEXITED(status_code)) {
×
4324
            if (!thumbnailer_safe_format(message,
×
4325
                                         sizeof(message),
4326
                                         "%s: %s exited with status %d.",
4327
                                         log_prefix,
4328
                                         (thumbnailer_name != NULL) ?
×
4329
                                         thumbnailer_name :
4330
                                         "thumbnailer",
4331
                                         WEXITSTATUS(status_code))) {
×
4332
                message[0] = '\0';
×
4333
            }
4334
            sixel_helper_set_additional_message(message);
×
4335
            status = SIXEL_RUNTIME_ERROR;
×
4336
        } else if (WIFSIGNALED(status_code)) {
×
4337
            if (!thumbnailer_safe_format(message,
×
4338
                                         sizeof(message),
4339
                                         "%s: %s terminated by signal %d.",
4340
                                         log_prefix,
4341
                                         (thumbnailer_name != NULL) ?
×
4342
                                         thumbnailer_name :
4343
                                         "thumbnailer",
4344
                                         WTERMSIG(status_code))) {
4345
                message[0] = '\0';
×
4346
            }
4347
            sixel_helper_set_additional_message(message);
×
4348
            status = SIXEL_RUNTIME_ERROR;
×
4349
        } else {
4350
            if (!thumbnailer_safe_format(message,
×
4351
                                         sizeof(message),
4352
                                         "%s: %s exited abnormally.",
4353
                                         log_prefix,
4354
                                         (thumbnailer_name != NULL) ?
×
4355
                                         thumbnailer_name :
4356
                                         "thumbnailer")) {
4357
                message[0] = '\0';
×
4358
            }
4359
            sixel_helper_set_additional_message(message);
×
4360
            status = SIXEL_RUNTIME_ERROR;
×
4361
        }
4362
    }
4363

4364
cleanup:
4365
    if (stderr_output != NULL && loader_trace_enabled &&
9!
4366
            stderr_length > 0) {
4367
        loader_trace_message("%s: stderr:\n%s",
×
4368
                             log_prefix,
4369
                             stderr_output);
4370
    }
4371

4372
    if (stderr_pipe[0] >= 0) {
9!
4373
        close(stderr_pipe[0]);
9✔
4374
        stderr_pipe[0] = -1;
9✔
4375
    }
3✔
4376
    if (stderr_pipe[1] >= 0) {
9!
4377
        close(stderr_pipe[1]);
9✔
4378
        stderr_pipe[1] = -1;
9✔
4379
    }
3✔
4380
    if (stdout_pipe[0] >= 0) {
12!
4381
        close(stdout_pipe[0]);
×
4382
        stdout_pipe[0] = -1;
×
4383
    }
4384
    if (stdout_pipe[1] >= 0) {
6!
4385
        close(stdout_pipe[1]);
×
4386
        stdout_pipe[1] = -1;
×
4387
    }
4388
    if (output_fd >= 0) {
6!
4389
        close(output_fd);
×
4390
        output_fd = -1;
×
4391
    }
4392
    /* stderr_output accumulates all diagnostic text, so release it even when
4393
     * verbose tracing is disabled.
4394
     */
4395
    free(stderr_output);
9✔
4396

4397
    return status;
9✔
4398
}
3✔
4399

4400

4401

4402
/*
4403
 * load_with_gnome_thumbnailer
4404
 *
4405
 * Drive the FreeDesktop thumbnailer pipeline and then decode the PNG
4406
 * result using the built-in loader.
4407
 *
4408
 * GNOME thumbnail workflow overview:
4409
 *
4410
 *     +------------+    +-------------------+    +----------------+
4411
 *     | source URI | -> | .thumbnailer Exec | -> | PNG thumbnail  |
4412
 *     +------------+    +-------------------+    +----------------+
4413
 *             |                    |                        |
4414
 *             |                    v                        v
4415
 *             |           spawn via /bin/sh         load_with_builtin()
4416
 *             v
4417
 *     file --mime-type
4418
 *
4419
 * Each step logs verbose breadcrumbs so integrators can diagnose which
4420
 * thumbnailer matched, how the command was prepared, and why fallbacks
4421
 * were selected.
4422
 *
4423
 * Arguments:
4424
 *     pchunk        - source chunk representing the original document.
4425
 *     fstatic       - image static-ness flag.
4426
 *     fuse_palette  - palette usage flag.
4427
 *     reqcolors     - requested colour count.
4428
 *     bgcolor       - background colour override.
4429
 *     loop_control  - animation loop control flag.
4430
 *     fn_load       - downstream decoder callback.
4431
 *     context       - user context forwarded to fn_load.
4432
 * Returns:
4433
 *     SIXEL_OK on success or libsixel error code on failure.
4434
 */
4435
static SIXELSTATUS
4436
load_with_gnome_thumbnailer(
12✔
4437
    sixel_chunk_t const       /* in */     *pchunk,
4438
    int                       /* in */     fstatic,
4439
    int                       /* in */     fuse_palette,
4440
    int                       /* in */     reqcolors,
4441
    unsigned char             /* in */     *bgcolor,
4442
    int                       /* in */     loop_control,
4443
    sixel_load_image_function /* in */     fn_load,
4444
    void                      /* in/out */ *context)
4445
{
4446
    SIXELSTATUS status;
4447
    sixel_chunk_t *thumb_chunk;
4448
    char template_path[] = "/tmp/libsixel-thumb-XXXXXX";
12✔
4449
    char *png_path;
4450
    size_t path_length;
4451
    struct thumbnailer_string_list *directories;
4452
    size_t dir_index;
4453
    DIR *dir;
4454
    struct dirent *entry;
4455
    char *thumbnailer_path;
4456
    struct thumbnailer_entry info;
4457
    char *content_type;
4458
    char *input_uri;
4459
    struct thumbnailer_command *command;
4460
    struct thumbnailer_command *evince_command;
4461
    int executed;
4462
    int command_success;
4463
    int requested_size;
4464
    char const *log_prefix;
4465

4466
    status = SIXEL_FALSE;
12✔
4467
    thumb_chunk = NULL;
12✔
4468
    png_path = NULL;
12✔
4469
    path_length = 0;
12✔
4470
    int fd;
4471
    fd = -1;
12✔
4472
    directories = NULL;
12✔
4473
    dir_index = 0;
12✔
4474
    dir = NULL;
12✔
4475
    entry = NULL;
12✔
4476
    thumbnailer_path = NULL;
12✔
4477
    content_type = NULL;
12✔
4478
    input_uri = NULL;
12✔
4479
    command = NULL;
12✔
4480
    evince_command = NULL;
12✔
4481
    executed = 0;
12✔
4482
    command_success = 0;
12✔
4483
    log_prefix = "load_with_gnome_thumbnailer";
12✔
4484
    requested_size = thumbnailer_size_hint;
12✔
4485
    if (requested_size <= 0) {
12!
4486
        requested_size = SIXEL_THUMBNAILER_DEFAULT_SIZE;
×
4487
    }
4488

4489
    loader_trace_message("%s: thumbnail size hint=%d",
12✔
4490
                         log_prefix,
4✔
4491
                         requested_size);
4✔
4492

4493
    thumbnailer_entry_init(&info);
12✔
4494

4495
    if (pchunk->source_path == NULL) {
12✔
4496
        sixel_helper_set_additional_message(
3✔
4497
            "load_with_gnome_thumbnailer: source path is unavailable.");
4498
        status = SIXEL_BAD_ARGUMENT;
3✔
4499
        goto end;
3✔
4500
    }
4501

4502
#if defined(HAVE_MKSTEMP)
4503
    fd = mkstemp(template_path);
9✔
4504
#elif defined(HAVE__MKTEMP)
4505
    fd = _mktemp(template_path);
4506
#elif defined(HAVE_MKTEMP)
4507
    fd = mktemp(template_path);
4508
#endif
4509
    if (fd < 0) {
9!
4510
        sixel_helper_set_additional_message(
×
4511
            "load_with_gnome_thumbnailer: mkstemp() failed.");
4512
        status = SIXEL_RUNTIME_ERROR;
×
4513
        goto end;
×
4514
    }
4515
    close(fd);
9✔
4516
    fd = -1;
9✔
4517

4518
    path_length = strlen(template_path) + 5;
9✔
4519
    png_path = malloc(path_length);
9✔
4520
    if (png_path == NULL) {
9!
4521
        sixel_helper_set_additional_message(
×
4522
            "load_with_gnome_thumbnailer: malloc() failed.");
4523
        status = SIXEL_BAD_ALLOCATION;
×
4524
        unlink(template_path);
×
4525
        goto end;
×
4526
    }
4527
    if (!thumbnailer_safe_format(png_path,
12!
4528
                                 path_length,
3✔
4529
                                 "%s.png",
4530
                                 template_path)) {
3✔
4531
        if (path_length > 0) {
×
4532
            png_path[path_length - 1] = '\0';
×
4533
        }
4534
    }
4535
    if (rename(template_path, png_path) != 0) {
9!
4536
        sixel_helper_set_additional_message(
×
4537
            "load_with_gnome_thumbnailer: rename() failed.");
4538
        status = SIXEL_RUNTIME_ERROR;
×
4539
        unlink(template_path);
×
4540
        goto end;
×
4541
    }
4542

4543
    content_type = thumbnailer_guess_content_type(pchunk->source_path);
9✔
4544
    input_uri = thumbnailer_build_file_uri(pchunk->source_path);
9✔
4545

4546
    loader_trace_message("%s: detected MIME type %s for %s",
9✔
4547
                         log_prefix,
3✔
4548
                         (content_type != NULL) ? content_type :
3!
4549
                         "(unknown)",
4550
                         pchunk->source_path);
9!
4551

4552
    directories = thumbnailer_collect_directories();
9✔
4553
    if (directories == NULL) {
9!
4554
        status = SIXEL_RUNTIME_ERROR;
×
4555
        goto end;
×
4556
    }
4557

4558
    /* Iterate through every configured thumbnailer directory so we honour
4559
     * overrides in $HOME as well as desktop environment defaults discovered
4560
     * through XDG_DATA_DIRS.
4561
     */
4562
    for (dir_index = 0; dir_index < directories->length; ++dir_index) {
36✔
4563
        loader_trace_message("%s: checking thumbnailers in %s",
27✔
4564
                             log_prefix,
9✔
4565
                             directories->items[dir_index]);
27✔
4566

4567
        dir = opendir(directories->items[dir_index]);
27✔
4568
        if (dir == NULL) {
27!
4569
            continue;
27✔
4570
        }
4571
        while ((entry = readdir(dir)) != NULL) {
×
4572
            thumbnailer_entry_clear(&info);
×
4573
            thumbnailer_entry_init(&info);
×
4574
            size_t name_length;
4575

4576
            name_length = strlen(entry->d_name);
×
4577
            if (name_length < 12 ||
×
4578
                    strcmp(entry->d_name + name_length - 12,
×
4579
                           ".thumbnailer") != 0) {
4580
                continue;
×
4581
            }
4582
            thumbnailer_path = thumbnailer_join_paths(
×
4583
                directories->items[dir_index],
×
4584
                entry->d_name);
×
4585
            if (thumbnailer_path == NULL) {
×
4586
                continue;
×
4587
            }
4588
            if (!thumbnailer_parse_file(thumbnailer_path, &info)) {
×
4589
                free(thumbnailer_path);
×
4590
                thumbnailer_path = NULL;
×
4591
                continue;
×
4592
            }
4593
            free(thumbnailer_path);
×
4594
            thumbnailer_path = NULL;
×
4595
            loader_trace_message(
×
4596
                "%s: parsed %s (TryExec=%s)",
4597
                log_prefix,
4598
                entry->d_name,
×
4599
                (info.tryexec != NULL) ? info.tryexec : "(none)");
×
4600
            if (content_type == NULL) {
×
4601
                continue;
×
4602
            }
4603
            if (!thumbnailer_has_tryexec(info.tryexec)) {
×
4604
                loader_trace_message("%s: skipping %s (TryExec missing)",
×
4605
                                     log_prefix,
4606
                                     entry->d_name);
×
4607
                continue;
×
4608
            }
4609
            if (!thumbnailer_supports_mime(&info, content_type)) {
×
4610
                loader_trace_message("%s: %s does not support %s",
×
4611
                                     log_prefix,
4612
                                     entry->d_name,
×
4613
                                     content_type);
4614
                continue;
×
4615
            }
4616
            if (info.exec_line == NULL) {
×
4617
                continue;
×
4618
            }
4619
            loader_trace_message("%s: %s supports %s with Exec=\"%s\"",
×
4620
                                 log_prefix,
4621
                                 entry->d_name,
×
4622
                                 content_type,
4623
                                 info.exec_line);
4624
            loader_trace_message("%s: preparing %s for %s",
×
4625
                                 log_prefix,
4626
                                 entry->d_name,
×
4627
                                 content_type);
4628
            command = thumbnailer_build_command(info.exec_line,
×
4629
                                                pchunk->source_path,
×
4630
                                                input_uri,
4631
                                                png_path,
4632
                                                requested_size,
4633
                                                content_type);
4634
            if (command == NULL) {
×
4635
                continue;
×
4636
            }
4637
            if (thumbnailer_is_evince_thumbnailer(info.exec_line,
×
4638
                                                  info.tryexec)) {
×
4639
                loader_trace_message(
×
4640
                    "%s: applying evince-thumbnailer stdout workaround",
4641
                    log_prefix);
4642
                /* evince-thumbnailer fails when passed an output path.
4643
                 * Redirect stdout and copy the stream instead.
4644
                 */
4645
                evince_command = thumbnailer_build_evince_command(
×
4646
                    pchunk->source_path,
×
4647
                    requested_size);
4648
                if (evince_command == NULL) {
×
4649
                    thumbnailer_command_free(command);
×
4650
                    command = NULL;
×
4651
                    continue;
×
4652
                }
4653
                thumbnailer_command_free(command);
×
4654
                command = evince_command;
×
4655
                evince_command = NULL;
×
4656
                unlink(png_path);
×
4657
                status = thumbnailer_spawn(command,
×
4658
                                           entry->d_name,
×
4659
                                           log_prefix,
4660
                                           1,
4661
                                           png_path);
4662
            } else {
4663
                unlink(png_path);
×
4664
                status = thumbnailer_spawn(command,
×
4665
                                           entry->d_name,
×
4666
                                           log_prefix,
4667
                                           0,
4668
                                           NULL);
4669
            }
4670
            thumbnailer_command_free(command);
×
4671
            command = NULL;
×
4672
            executed = 1;
×
4673
            if (SIXEL_SUCCEEDED(status)) {
×
4674
                command_success = 1;
×
4675
                loader_trace_message("%s: %s produced %s",
×
4676
                                     log_prefix,
4677
                                     entry->d_name,
×
4678
                                     png_path);
4679
                break;
×
4680
            }
4681
        }
4682
        closedir(dir);
×
4683
        dir = NULL;
×
4684
        if (command_success) {
×
4685
            break;
×
4686
        }
4687
    }
4688

4689
    if (!command_success) {
9!
4690
        loader_trace_message("%s: falling back to gdk-pixbuf-thumbnailer",
9✔
4691
                             log_prefix);
3✔
4692
        unlink(png_path);
9✔
4693
        command = thumbnailer_build_command(
9✔
4694
            "gdk-pixbuf-thumbnailer --size=%s %i %o",
4695
            pchunk->source_path,
9✔
4696
            input_uri,
3✔
4697
            png_path,
3✔
4698
            requested_size,
3✔
4699
            content_type);
3✔
4700
        if (command != NULL) {
9!
4701
            unlink(png_path);
9✔
4702
            status = thumbnailer_spawn(command,
12✔
4703
                                       "gdk-pixbuf-thumbnailer",
4704
                                       log_prefix,
3✔
4705
                                       0,
4706
                                       NULL);
4707
            thumbnailer_command_free(command);
9✔
4708
            command = NULL;
9✔
4709
            if (SIXEL_FAILED(status)) {
9!
4710
                goto end;
9✔
4711
            }
4712
            executed = 1;
×
4713
            command_success = 1;
×
4714
            loader_trace_message("%s: gdk-pixbuf-thumbnailer produced %s",
×
4715
                                 log_prefix,
4716
                                 png_path);
4717
        }
4718
    }
4719

4720
    if (!executed) {
×
4721
        sixel_helper_set_additional_message(
×
4722
            "load_with_gnome_thumbnailer: no thumbnailer available.");
4723
        status = SIXEL_RUNTIME_ERROR;
×
4724
        goto end;
×
4725
    }
4726

4727
    status = sixel_chunk_new(&thumb_chunk,
×
4728
                             png_path,
4729
                             0,
4730
                             NULL,
4731
                             pchunk->allocator);
×
4732
    if (SIXEL_FAILED(status)) {
×
4733
        goto end;
×
4734
    }
4735
    status = load_with_builtin(thumb_chunk,
×
4736
                               fstatic,
4737
                               fuse_palette,
4738
                               reqcolors,
4739
                               bgcolor,
4740
                               loop_control,
4741
                               fn_load,
4742
                               context);
4743
    if (SIXEL_FAILED(status)) {
×
4744
        goto end;
×
4745
    }
4746

4747
    status = SIXEL_OK;
×
4748

4749
end:
8✔
4750
    if (command != NULL) {
12!
4751
        thumbnailer_command_free(command);
×
4752
        command = NULL;
×
4753
    }
4754
    if (evince_command != NULL) {
8!
4755
        thumbnailer_command_free(evince_command);
×
4756
        evince_command = NULL;
×
4757
    }
4758
    if (thumb_chunk != NULL) {
8!
4759
        sixel_chunk_destroy(thumb_chunk);
×
4760
        thumb_chunk = NULL;
×
4761
    }
4762
    if (png_path != NULL) {
11✔
4763
        unlink(png_path);
9✔
4764
        free(png_path);
9✔
4765
        png_path = NULL;
9✔
4766
    }
3✔
4767
    if (fd >= 0) {
14!
4768
        close(fd);
×
4769
        fd = -1;
×
4770
    }
4771
    if (directories != NULL) {
11✔
4772
        thumbnailer_string_list_free(directories);
9✔
4773
        directories = NULL;
9✔
4774
    }
3✔
4775
    if (dir != NULL) {
14!
4776
        closedir(dir);
×
4777
        dir = NULL;
×
4778
    }
4779
    thumbnailer_entry_clear(&info);
12✔
4780
    free(content_type);
12✔
4781
    content_type = NULL;
12✔
4782
    free(input_uri);
12✔
4783
    input_uri = NULL;
12✔
4784

4785
    return status;
12✔
4786
}
4787

4788
#endif  /* HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK */
4789

4790

4791
#if HAVE_GD
4792
static int
4793
detect_file_format(int len, unsigned char *data)
4794
{
4795
    if (len > 18 && memcmp("TRUEVISION", data + len - 18, 10) == 0) {
4796
        return SIXEL_FORMAT_TGA;
4797
    }
4798

4799
    if (len > 3 && memcmp("GIF", data, 3) == 0) {
4800
        return SIXEL_FORMAT_GIF;
4801
    }
4802

4803
    if (len > 8 && memcmp("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", data, 8) == 0) {
4804
        return SIXEL_FORMAT_PNG;
4805
    }
4806

4807
    if (len > 2 && memcmp("BM", data, 2) == 0) {
4808
        return SIXEL_FORMAT_BMP;
4809
    }
4810

4811
    if (len > 2 && memcmp("\xFF\xD8", data, 2) == 0) {
4812
        return SIXEL_FORMAT_JPG;
4813
    }
4814

4815
    if (len > 2 && memcmp("\x00\x00", data, 2) == 0) {
4816
        return SIXEL_FORMAT_WBMP;
4817
    }
4818

4819
    if (len > 2 && memcmp("\x4D\x4D", data, 2) == 0) {
4820
        return SIXEL_FORMAT_TIFF;
4821
    }
4822

4823
    if (len > 2 && memcmp("\x49\x49", data, 2) == 0) {
4824
        return SIXEL_FORMAT_TIFF;
4825
    }
4826

4827
    if (len > 2 && memcmp("\033P", data, 2) == 0) {
4828
        return SIXEL_FORMAT_SIXEL;
4829
    }
4830

4831
    if (len > 2 && data[0] == 0x90  && (data[len - 1] == 0x9C || data[len - 2] == 0x9C)) {
4832
        return SIXEL_FORMAT_SIXEL;
4833
    }
4834

4835
    if (len > 1 && data[0] == 'P' && data[1] >= '1' && data[1] <= '6') {
4836
        return SIXEL_FORMAT_PNM;
4837
    }
4838

4839
    if (len > 3 && memcmp("gd2", data, 3) == 0) {
4840
        return SIXEL_FORMAT_GD2;
4841
    }
4842

4843
    if (len > 4 && memcmp("8BPS", data, 4) == 0) {
4844
        return SIXEL_FORMAT_PSD;
4845
    }
4846

4847
    if (len > 11 && memcmp("#?RADIANCE\n", data, 11) == 0) {
4848
        return SIXEL_FORMAT_HDR;
4849
    }
4850

4851
    return (-1);
4852
}
4853
#endif /* HAVE_GD */
4854

4855
#if HAVE_GD
4856

4857
static SIXELSTATUS
4858
load_with_gd(
4859
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
4860
    int                       /* in */     fstatic,      /* static */
4861
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
4862
    int                       /* in */     reqcolors,    /* reqcolors */
4863
    unsigned char             /* in */     *bgcolor,     /* background color */
4864
    int                       /* in */     loop_control, /* one of enum loop_control */
4865
    sixel_load_image_function /* in */     fn_load,      /* callback */
4866
    void                      /* in/out */ *context      /* private data for callback */
4867
)
4868
{
4869
    SIXELSTATUS status = SIXEL_FALSE;
4870
    unsigned char *p;
4871
    gdImagePtr im = NULL;
4872
    int x, y;
4873
    int c;
4874
    sixel_frame_t *frame = NULL;
4875
    int format;
4876

4877
    (void) fstatic;
4878
    (void) fuse_palette;
4879
    (void) reqcolors;
4880
    (void) bgcolor;
4881
    (void) loop_control;
4882

4883
    format = detect_file_format(pchunk->size, pchunk->buffer);
4884

4885
    if (format == SIXEL_FORMAT_GIF) {
4886
#if HAVE_DECL_GDIMAGECREATEFROMGIFANIMPTR
4887
        gdImagePtr *ims = NULL;
4888
        int frames = 0;
4889
        int i;
4890
        int *delays = NULL;
4891

4892
        ims = gdImageCreateFromGifAnimPtr(pchunk->size, pchunk->buffer,
4893
                                          &frames, &delays);
4894
        if (ims == NULL) {
4895
            status = SIXEL_GD_ERROR;
4896
            goto end;
4897
        }
4898

4899
        for (i = 0; i < frames; i++) {
4900
            im = ims[i];
4901
            if (!gdImageTrueColor(im)) {
4902
# if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
4903
                if (!gdImagePaletteToTrueColor(im)) {
4904
                    status = SIXEL_GD_ERROR;
4905
                    goto gif_end;
4906
                }
4907
# else
4908
                status = SIXEL_GD_ERROR;
4909
                goto gif_end;
4910
# endif
4911
            }
4912

4913
            status = sixel_frame_new(&frame, pchunk->allocator);
4914
            if (SIXEL_FAILED(status)) {
4915
                frame = NULL;
4916
                goto gif_end;
4917
            }
4918

4919
            frame->width = gdImageSX(im);
4920
            frame->height = gdImageSY(im);
4921
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
4922
            p = frame->pixels = sixel_allocator_malloc(
4923
                pchunk->allocator,
4924
                (size_t)(frame->width * frame->height * 3));
4925
            if (frame->pixels == NULL) {
4926
                sixel_helper_set_additional_message(
4927
                    "load_with_gd: sixel_allocator_malloc() failed.");
4928
                status = SIXEL_BAD_ALLOCATION;
4929
                sixel_frame_unref(frame);
4930
                frame = NULL;
4931
                goto gif_end;
4932
            }
4933
            for (y = 0; y < frame->height; y++) {
4934
                for (x = 0; x < frame->width; x++) {
4935
                    c = gdImageTrueColorPixel(im, x, y);
4936
                    *p++ = gdTrueColorGetRed(c);
4937
                    *p++ = gdTrueColorGetGreen(c);
4938
                    *p++ = gdTrueColorGetBlue(c);
4939
                }
4940
            }
4941

4942
            if (delays) {
4943
                frame->delay.tv_sec = delays[i] / 100;
4944
                frame->delay.tv_nsec = (delays[i] % 100) * 10000000L;
4945
            }
4946

4947
            status = fn_load(frame, context);
4948
            sixel_frame_unref(frame);
4949
            frame = NULL;
4950
            gdImageDestroy(im);
4951
            ims[i] = NULL;
4952
            if (SIXEL_FAILED(status)) {
4953
                goto gif_end;
4954
            }
4955
        }
4956

4957
        status = SIXEL_OK;
4958

4959
gif_end:
4960
        if (delays) {
4961
            gdFree(delays);
4962
        }
4963
        if (ims) {
4964
            for (i = 0; i < frames; i++) {
4965
                if (ims[i]) {
4966
                    gdImageDestroy(ims[i]);
4967
                }
4968
            }
4969
            gdFree(ims);
4970
        }
4971
        goto end;
4972
#else
4973
        status = SIXEL_GD_ERROR;
4974
        goto end;
4975
#endif
4976
    }
4977

4978
    switch (format) {
4979
#if HAVE_DECL_GDIMAGECREATEFROMPNGPTR
4980
        case SIXEL_FORMAT_PNG:
4981
            im = gdImageCreateFromPngPtr(pchunk->size, pchunk->buffer);
4982
            break;
4983
#endif  /* HAVE_DECL_GDIMAGECREATEFROMPNGPTR */
4984
#if HAVE_DECL_GDIMAGECREATEFROMBMPPTR
4985
        case SIXEL_FORMAT_BMP:
4986
            im = gdImageCreateFromBmpPtr(pchunk->size, pchunk->buffer);
4987
            break;
4988
#endif  /* HAVE_DECL_GDIMAGECREATEFROMBMPPTR */
4989
        case SIXEL_FORMAT_JPG:
4990
#if HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX
4991
            im = gdImageCreateFromJpegPtrEx(pchunk->size, pchunk->buffer, 1);
4992
#elif HAVE_DECL_GDIMAGECREATEFROMJPEGPTR
4993
            im = gdImageCreateFromJpegPtr(pchunk->size, pchunk->buffer);
4994
#endif  /* HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX */
4995
            break;
4996
#if HAVE_DECL_GDIMAGECREATEFROMTGAPTR
4997
        case SIXEL_FORMAT_TGA:
4998
            im = gdImageCreateFromTgaPtr(pchunk->size, pchunk->buffer);
4999
            break;
5000
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTGAPTR */
5001
#if HAVE_DECL_GDIMAGECREATEFROMWBMPPTR
5002
        case SIXEL_FORMAT_WBMP:
5003
            im = gdImageCreateFromWBMPPtr(pchunk->size, pchunk->buffer);
5004
            break;
5005
#endif  /* HAVE_DECL_GDIMAGECREATEFROMWBMPPTR */
5006
#if HAVE_DECL_GDIMAGECREATEFROMTIFFPTR
5007
        case SIXEL_FORMAT_TIFF:
5008
            im = gdImageCreateFromTiffPtr(pchunk->size, pchunk->buffer);
5009
            break;
5010
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTIFFPTR */
5011
#if HAVE_DECL_GDIMAGECREATEFROMGD2PTR
5012
        case SIXEL_FORMAT_GD2:
5013
            im = gdImageCreateFromGd2Ptr(pchunk->size, pchunk->buffer);
5014
            break;
5015
#endif  /* HAVE_DECL_GDIMAGECREATEFROMGD2PTR */
5016
        default:
5017
            status = SIXEL_GD_ERROR;
5018
            sixel_helper_set_additional_message(
5019
                "unexpected image format detected.");
5020
            goto end;
5021
    }
5022

5023
    if (im == NULL) {
5024
        status = SIXEL_GD_ERROR;
5025
        /* TODO: retrieve error detail */
5026
        goto end;
5027
    }
5028

5029
    if (!gdImageTrueColor(im)) {
5030
#if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
5031
        if (!gdImagePaletteToTrueColor(im)) {
5032
            gdImageDestroy(im);
5033
            status = SIXEL_GD_ERROR;
5034
            /* TODO: retrieve error detail */
5035
            goto end;
5036
        }
5037
#else
5038
        status = SIXEL_GD_ERROR;
5039
        /* TODO: retrieve error detail */
5040
        goto end;
5041
#endif
5042
    }
5043

5044
    status = sixel_frame_new(&frame, pchunk->allocator);
5045
    if (SIXEL_FAILED(status)) {
5046
        goto end;
5047
    }
5048

5049
    frame->width = gdImageSX(im);
5050
    frame->height = gdImageSY(im);
5051
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
5052
    p = frame->pixels = sixel_allocator_malloc(
5053
        pchunk->allocator, (size_t)(frame->width * frame->height * 3));
5054
    if (frame->pixels == NULL) {
5055
        sixel_helper_set_additional_message(
5056
            "load_with_gd: sixel_allocator_malloc() failed.");
5057
        status = SIXEL_BAD_ALLOCATION;
5058
        gdImageDestroy(im);
5059
        goto end;
5060
    }
5061
    for (y = 0; y < frame->height; y++) {
5062
        for (x = 0; x < frame->width; x++) {
5063
            c = gdImageTrueColorPixel(im, x, y);
5064
            *p++ = gdTrueColorGetRed(c);
5065
            *p++ = gdTrueColorGetGreen(c);
5066
            *p++ = gdTrueColorGetBlue(c);
5067
        }
5068
    }
5069
    gdImageDestroy(im);
5070

5071
    status = fn_load(frame, context);
5072
    if (SIXEL_FAILED(status)) {
5073
        goto end;
5074
    }
5075

5076
    sixel_frame_unref(frame);
5077

5078
    status = SIXEL_OK;
5079

5080
end:
5081
    return status;
5082
}
5083

5084
#endif  /* HAVE_GD */
5085

5086
#if HAVE_WIC
5087

5088
#include <windows.h>
5089
#include <wincodec.h>
5090

5091
SIXELSTATUS
5092
load_with_wic(
5093
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
5094
    int                       /* in */     fstatic,      /* static */
5095
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
5096
    int                       /* in */     reqcolors,    /* reqcolors */
5097
    unsigned char             /* in */     *bgcolor,     /* background color */
5098
    int                       /* in */     loop_control, /* one of enum loop_control */
5099
    sixel_load_image_function /* in */     fn_load,      /* callback */
5100
    void                      /* in/out */ *context      /* private data for callback */
5101
)
5102
{
5103
    HRESULT                 hr         = E_FAIL;
5104
    SIXELSTATUS             status     = SIXEL_FALSE;
5105
    IWICImagingFactory     *factory    = NULL;
5106
    IWICStream             *stream     = NULL;
5107
    IWICBitmapDecoder      *decoder    = NULL;
5108
    IWICBitmapFrameDecode  *wicframe   = NULL;
5109
    IWICFormatConverter    *conv       = NULL;
5110
    IWICBitmapSource       *src        = NULL;
5111
    IWICPalette            *wicpalette = NULL;
5112
    WICColor               *wiccolors  = NULL;
5113
    IWICMetadataQueryReader *qdecoder  = NULL;
5114
    IWICMetadataQueryReader *qframe    = NULL;
5115
    UINT                    ncolors    = 0;
5116
    sixel_frame_t          *frame      = NULL;
5117
    int                     comp       = 4;
5118
    UINT                    actual     = 0;
5119
    UINT                    i;
5120
    UINT                    frame_count = 0;
5121
    int                     anim_loop_count = (-1);
5122
    int                     is_gif;
5123
    WICColor                c;
5124

5125
    (void) reqcolors;
5126
    (void) bgcolor;
5127

5128
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
5129
    if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
5130
        return status;
5131
    }
5132

5133
    status = sixel_frame_new(&frame, pchunk->allocator);
5134
    if (SIXEL_FAILED(status)) {
5135
        goto end;
5136
    }
5137

5138
    hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
5139
                          &IID_IWICImagingFactory, (void**)&factory);
5140
    if (FAILED(hr)) {
5141
        goto end;
5142
    }
5143

5144
    hr = factory->lpVtbl->CreateStream(factory, &stream);
5145
    if (FAILED(hr)) {
5146
        goto end;
5147
    }
5148

5149
    hr = stream->lpVtbl->InitializeFromMemory(stream,
5150
                                              (BYTE*)pchunk->buffer,
5151
                                              (DWORD)pchunk->size);
5152
    if (FAILED(hr)) {
5153
        goto end;
5154
    }
5155

5156
    hr = factory->lpVtbl->CreateDecoderFromStream(factory,
5157
                                                  (IStream*)stream,
5158
                                                  NULL,
5159
                                                  WICDecodeMetadataCacheOnDemand,
5160
                                                  &decoder);
5161
    if (FAILED(hr)) {
5162
        goto end;
5163
    }
5164

5165
    is_gif = (memcmp("GIF", pchunk->buffer, 3) == 0);
5166

5167
    if (is_gif) {
5168
        hr = decoder->lpVtbl->GetFrameCount(decoder, &frame_count);
5169
        if (FAILED(hr)) {
5170
            goto end;
5171
        }
5172
        if (fstatic) {
5173
            frame_count = 1;
5174
        }
5175

5176
        hr = decoder->lpVtbl->GetMetadataQueryReader(decoder, &qdecoder);
5177
        if (SUCCEEDED(hr)) {
5178
            PROPVARIANT pv;
5179
            PropVariantInit(&pv);
5180
            hr = qdecoder->lpVtbl->GetMetadataByName(
5181
                qdecoder,
5182
                L"/appext/Application/NETSCAPE2.0/Loop",
5183
                &pv);
5184
            if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5185
                anim_loop_count = pv.uiVal;
5186
            }
5187
            PropVariantClear(&pv);
5188
            qdecoder->lpVtbl->Release(qdecoder);
5189
            qdecoder = NULL;
5190
        }
5191

5192
        frame->loop_count = 0;
5193
        for (;;) {
5194
            frame->frame_no = 0;
5195
            for (i = 0; i < frame_count; ++i) {
5196
                hr = decoder->lpVtbl->GetFrame(decoder, i, &wicframe);
5197
                if (FAILED(hr)) {
5198
                    goto end;
5199
                }
5200

5201
                hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5202
                if (FAILED(hr)) {
5203
                    goto end;
5204
                }
5205
                hr = conv->lpVtbl->Initialize(conv,
5206
                                              (IWICBitmapSource*)wicframe,
5207
                                              &GUID_WICPixelFormat32bppRGBA,
5208
                                              WICBitmapDitherTypeNone,
5209
                                              NULL,
5210
                                              0.0,
5211
                                              WICBitmapPaletteTypeCustom);
5212
                if (FAILED(hr)) {
5213
                    goto end;
5214
                }
5215

5216
                src = (IWICBitmapSource*)conv;
5217
                comp = 4;
5218
                frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5219

5220
                hr = src->lpVtbl->GetSize(
5221
                    src,
5222
                    (UINT *)&frame->width,
5223
                    (UINT *)&frame->height);
5224
                if (FAILED(hr)) {
5225
                    goto end;
5226
                }
5227

5228
                if (frame->width <= 0 || frame->height <= 0 ||
5229
                    frame->width > SIXEL_WIDTH_LIMIT ||
5230
                    frame->height > SIXEL_HEIGHT_LIMIT) {
5231
                    sixel_helper_set_additional_message(
5232
                        "load_with_wic: an invalid width or height parameter detected.");
5233
                    status = SIXEL_BAD_INPUT;
5234
                    goto end;
5235
                }
5236

5237
                frame->pixels = sixel_allocator_malloc(
5238
                    pchunk->allocator,
5239
                    (size_t)(frame->height * frame->width * comp));
5240
                if (frame->pixels == NULL) {
5241
                    hr = E_OUTOFMEMORY;
5242
                    goto end;
5243
                }
5244

5245
                {
5246
                    WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
5247
                    hr = src->lpVtbl->CopyPixels(
5248
                        src,
5249
                        &rc,
5250
                        frame->width * comp,
5251
                        (UINT)frame->width * frame->height * comp,
5252
                        frame->pixels);
5253
                    if (FAILED(hr)) {
5254
                        goto end;
5255
                    }
5256
                }
5257

5258
                frame->delay = 0;
5259
                hr = wicframe->lpVtbl->GetMetadataQueryReader(wicframe, &qframe);
5260
                if (SUCCEEDED(hr)) {
5261
                    PROPVARIANT pv;
5262
                    PropVariantInit(&pv);
5263
                    hr = qframe->lpVtbl->GetMetadataByName(
5264
                        qframe,
5265
                        L"/grctlext/Delay",
5266
                        &pv);
5267
                    if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5268
                        frame->delay = (int)(pv.uiVal) * 10;
5269
                    }
5270
                    PropVariantClear(&pv);
5271
                    qframe->lpVtbl->Release(qframe);
5272
                    qframe = NULL;
5273
                }
5274

5275
                frame->multiframe = 1;
5276
                status = fn_load(frame, context);
5277
                if (SIXEL_FAILED(status)) {
5278
                    goto end;
5279
                }
5280
                frame->pixels = NULL;
5281
                frame->palette = NULL;
5282

5283
                if (conv) {
5284
                    conv->lpVtbl->Release(conv);
5285
                    conv = NULL;
5286
                }
5287
                if (wicframe) {
5288
                    wicframe->lpVtbl->Release(wicframe);
5289
                    wicframe = NULL;
5290
                }
5291

5292
                frame->frame_no++;
5293
            }
5294

5295
            ++frame->loop_count;
5296

5297
            if (anim_loop_count < 0) {
5298
                break;
5299
            }
5300
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
5301
                break;
5302
            }
5303
            if (loop_control == SIXEL_LOOP_AUTO &&
5304
                frame->loop_count == anim_loop_count) {
5305
                break;
5306
            }
5307
        }
5308

5309
        status = SIXEL_OK;
5310
        goto end;
5311
    }
5312

5313
    hr = decoder->lpVtbl->GetFrame(decoder, 0, &wicframe);
5314
    if (FAILED(hr)) {
5315
        goto end;
5316
    }
5317

5318
    if (fuse_palette) {
5319
        hr = factory->lpVtbl->CreatePalette(factory, &wicpalette);
5320
        if (SUCCEEDED(hr)) {
5321
            hr = wicframe->lpVtbl->CopyPalette(wicframe, wicpalette);
5322
        }
5323
        if (SUCCEEDED(hr)) {
5324
            hr = wicpalette->lpVtbl->GetColorCount(wicpalette, &ncolors);
5325
        }
5326
        if (SUCCEEDED(hr) && ncolors > 0 && ncolors <= 256) {
5327
            hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5328
            if (SUCCEEDED(hr)) {
5329
                hr = conv->lpVtbl->Initialize(conv,
5330
                                              (IWICBitmapSource*)wicframe,
5331
                                              &GUID_WICPixelFormat8bppIndexed,
5332
                                              WICBitmapDitherTypeNone,
5333
                                              wicpalette,
5334
                                              0.0,
5335
                                              WICBitmapPaletteTypeCustom);
5336
            }
5337
            if (SUCCEEDED(hr)) {
5338
                src = (IWICBitmapSource*)conv;
5339
                comp = 1;
5340
                frame->pixelformat = SIXEL_PIXELFORMAT_PAL8;
5341
                frame->palette = sixel_allocator_malloc(
5342
                    pchunk->allocator,
5343
                    (size_t)ncolors * 3);
5344
                if (frame->palette == NULL) {
5345
                    hr = E_OUTOFMEMORY;
5346
                } else {
5347
                    wiccolors = (WICColor *)sixel_allocator_malloc(
5348
                        pchunk->allocator,
5349
                        (size_t)ncolors * sizeof(WICColor));
5350
                    if (wiccolors == NULL) {
5351
                        hr = E_OUTOFMEMORY;
5352
                    } else {
5353
                        actual = 0;
5354
                        hr = wicpalette->lpVtbl->GetColors(
5355
                            wicpalette, ncolors, wiccolors, &actual);
5356
                        if (SUCCEEDED(hr) && actual == ncolors) {
5357
                            for (i = 0; i < ncolors; ++i) {
5358
                                c = wiccolors[i];
5359
                                frame->palette[i * 3 + 0] = (unsigned char)((c >> 16) & 0xFF);
5360
                                frame->palette[i * 3 + 1] = (unsigned char)((c >> 8) & 0xFF);
5361
                                frame->palette[i * 3 + 2] = (unsigned char)(c & 0xFF);
5362
                            }
5363
                            frame->ncolors = (int)ncolors;
5364
                        } else {
5365
                            hr = E_FAIL;
5366
                        }
5367
                    }
5368
                }
5369
            }
5370
            if (FAILED(hr)) {
5371
                if (conv) {
5372
                    conv->lpVtbl->Release(conv);
5373
                    conv = NULL;
5374
                }
5375
                sixel_allocator_free(pchunk->allocator, frame->palette);
5376
                frame->palette = NULL;
5377
                sixel_allocator_free(pchunk->allocator, wiccolors);
5378
                wiccolors = NULL;
5379
                src = NULL;
5380
            }
5381
        }
5382
    }
5383

5384
    if (src == NULL) {
5385
        hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5386
        if (FAILED(hr)) {
5387
            goto end;
5388
        }
5389

5390
        hr = conv->lpVtbl->Initialize(conv, (IWICBitmapSource*)wicframe,
5391
                                      &GUID_WICPixelFormat32bppRGBA,
5392
                                      WICBitmapDitherTypeNone, NULL, 0.0,
5393
                                      WICBitmapPaletteTypeCustom);
5394
        if (FAILED(hr)) {
5395
            goto end;
5396
        }
5397

5398
        src = (IWICBitmapSource*)conv;
5399
        comp = 4;
5400
        frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5401
    }
5402

5403
    hr = src->lpVtbl->GetSize(
5404
        src, (UINT *)&frame->width, (UINT *)&frame->height);
5405
    if (FAILED(hr)) {
5406
        goto end;
5407
    }
5408

5409
    /* check size */
5410
    if (frame->width <= 0) {
5411
        sixel_helper_set_additional_message(
5412
            "load_with_wic: an invalid width parameter detected.");
5413
        status = SIXEL_BAD_INPUT;
5414
        goto end;
5415
    }
5416
    if (frame->height <= 0) {
5417
        sixel_helper_set_additional_message(
5418
            "load_with_wic: an invalid width parameter detected.");
5419
        status = SIXEL_BAD_INPUT;
5420
        goto end;
5421
    }
5422
    if (frame->width > SIXEL_WIDTH_LIMIT) {
5423
        sixel_helper_set_additional_message(
5424
            "load_with_wic: given width parameter is too huge.");
5425
        status = SIXEL_BAD_INPUT;
5426
        goto end;
5427
    }
5428
    if (frame->height > SIXEL_HEIGHT_LIMIT) {
5429
        sixel_helper_set_additional_message(
5430
            "load_with_wic: given height parameter is too huge.");
5431
        status = SIXEL_BAD_INPUT;
5432
        goto end;
5433
    }
5434

5435
    frame->pixels = sixel_allocator_malloc(
5436
        pchunk->allocator,
5437
        (size_t)(frame->height * frame->width * comp));
5438

5439
    {
5440
        WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
5441
        hr = src->lpVtbl->CopyPixels(
5442
            src,
5443
            &rc,                                        /* prc */
5444
            frame->width * comp,                        /* cbStride */
5445
            (UINT)frame->width * frame->height * comp,  /* cbBufferSize */
5446
            frame->pixels);                             /* pbBuffer */
5447
        if (FAILED(hr)) {
5448
            goto end;
5449
        }
5450
    }
5451

5452
    status = fn_load(frame, context);
5453
    if (SIXEL_FAILED(status)) {
5454
        goto end;
5455
    }
5456

5457
end:
5458
    if (conv) {
5459
         conv->lpVtbl->Release(conv);
5460
    }
5461
    if (wicpalette) {
5462
         wicpalette->lpVtbl->Release(wicpalette);
5463
    }
5464
    if (wiccolors) {
5465
         sixel_allocator_free(pchunk->allocator, wiccolors);
5466
    }
5467
    if (wicframe) {
5468
         wicframe->lpVtbl->Release(wicframe);
5469
    }
5470
    if (qdecoder) {
5471
         qdecoder->lpVtbl->Release(qdecoder);
5472
    }
5473
    if (qframe) {
5474
         qframe->lpVtbl->Release(qframe);
5475
    }
5476
    if (stream) {
5477
         stream->lpVtbl->Release(stream);
5478
    }
5479
    if (factory) {
5480
         factory->lpVtbl->Release(factory);
5481
    }
5482
    sixel_frame_unref(frame);
5483

5484
    CoUninitialize();
5485

5486
    if (FAILED(hr)) {
5487
        return SIXEL_FALSE;
5488
    }
5489

5490
    return SIXEL_OK;
5491
}
5492

5493
#endif /* HAVE_WIC */
5494

5495
/* load image from file */
5496

5497
SIXELAPI SIXELSTATUS
5498
sixel_helper_load_image_file(
443✔
5499
    char const                /* in */     *filename,     /* source file name */
5500
    int                       /* in */     fstatic,       /* whether to extract static image from animated gif */
5501
    int                       /* in */     fuse_palette,  /* whether to use paletted image, set non-zero value to try to get paletted image */
5502
    int                       /* in */     reqcolors,     /* requested number of colors, should be equal or less than SIXEL_PALETTE_MAX */
5503
    unsigned char             /* in */     *bgcolor,      /* background color, may be NULL */
5504
    int                       /* in */     loop_control,  /* one of enum loopControl */
5505
    sixel_load_image_function /* in */     fn_load,       /* callback */
5506
    int                       /* in */     finsecure,     /* true if do not verify SSL */
5507
    int const                 /* in */     *cancel_flag,  /* cancel flag, may be NULL */
5508
    void                      /* in/out */ *context,      /* private data which is passed to callback function as an argument, may be NULL */
5509
    sixel_allocator_t         /* in */     *allocator     /* allocator object, may be NULL */
5510
)
5511
{
5512
    SIXELSTATUS status = SIXEL_FALSE;
443✔
5513
    sixel_chunk_t *pchunk = NULL;
443✔
5514

5515
    /* normalize reqested colors */
5516
    if (reqcolors > SIXEL_PALETTE_MAX) {
443!
5517
        reqcolors = SIXEL_PALETTE_MAX;
×
5518
    }
5519

5520
    /* create new chunk object from file */
5521
    status = sixel_chunk_new(&pchunk, filename, finsecure, cancel_flag, allocator);
443✔
5522
    if (status != SIXEL_OK) {
443✔
5523
        goto end;
14✔
5524
    }
5525

5526
    /* if input date is empty or 1 byte LF, ignore it and return successfully */
5527
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
429!
UNCOV
5528
        status = SIXEL_OK;
×
UNCOV
5529
        goto end;
×
5530
    }
5531

5532
    /* assertion */
5533
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
429!
5534
        status = SIXEL_LOGIC_ERROR;
×
5535
        goto end;
×
5536
    }
5537

5538
    status = SIXEL_FALSE;
429✔
5539
#ifdef HAVE_WIC
5540
    if (SIXEL_FAILED(status) && !chunk_is_gif(pchunk)) {
5541
        loader_trace_try("wic");
5542
        status = load_with_wic(pchunk,
5543
                               fstatic,
5544
                               fuse_palette,
5545
                               reqcolors,
5546
                               bgcolor,
5547
                               loop_control,
5548
                               fn_load,
5549
                               context);
5550
        loader_trace_result("wic", status);
5551
    }
5552
#endif  /* HAVE_WIC */
5553
#ifdef HAVE_COREGRAPHICS
5554
    if (SIXEL_FAILED(status)) {
143✔
5555
        loader_trace_try("coregraphics");
143✔
5556
        status = load_with_coregraphics(pchunk,
286✔
5557
                                        fstatic,
143✔
5558
                                        fuse_palette,
143✔
5559
                                        reqcolors,
143✔
5560
                                        bgcolor,
143✔
5561
                                        loop_control,
143✔
5562
                                        fn_load,
143✔
5563
                                        context);
143✔
5564
        loader_trace_result("coregraphics", status);
143✔
5565
    }
143✔
5566
#endif  /* HAVE_COREGRAPHICS */
5567
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
5568
    if (SIXEL_FAILED(status)) {
227✔
5569
        loader_trace_try("quicklook");
59✔
5570
        status = load_with_quicklook(pchunk,
118✔
5571
                                     fstatic,
59✔
5572
                                     fuse_palette,
59✔
5573
                                     reqcolors,
59✔
5574
                                     bgcolor,
59✔
5575
                                     loop_control,
59✔
5576
                                     fn_load,
59✔
5577
                                     context);
59✔
5578
        loader_trace_result("quicklook", status);
59✔
5579
    }
59✔
5580
#endif  /* HAVE_COREGRAPHICS && HAVE_QUICKLOOK */
5581
#ifdef HAVE_GDK_PIXBUF2
5582
    if (SIXEL_FAILED(status)) {
5583
        loader_trace_try("gdk-pixbuf2");
5584
        status = load_with_gdkpixbuf(pchunk,
5585
                                     fstatic,
5586
                                     fuse_palette,
5587
                                     reqcolors,
5588
                                     bgcolor,
5589
                                     loop_control,
5590
                                     fn_load,
5591
                                     context);
5592
        loader_trace_result("gdk-pixbuf2", status);
5593
    }
5594
#endif  /* HAVE_GDK_PIXBUF2 */
5595
#if HAVE_GD
5596
    if (SIXEL_FAILED(status)) {
5597
        loader_trace_try("gd");
5598
        status = load_with_gd(pchunk,
5599
                              fstatic,
5600
                              fuse_palette,
5601
                              reqcolors,
5602
                              bgcolor,
5603
                              loop_control,
5604
                              fn_load,
5605
                              context);
5606
        loader_trace_result("gd", status);
5607
    }
5608
#endif  /* HAVE_GD */
5609
    if (SIXEL_FAILED(status)) {
348!
5610
        loader_trace_try("builtin");
342✔
5611
        status = load_with_builtin(pchunk,
398✔
5612
                                   fstatic,
56✔
5613
                                   fuse_palette,
56✔
5614
                                   reqcolors,
56✔
5615
                                   bgcolor,
56✔
5616
                                   loop_control,
56✔
5617
                                   fn_load,
56✔
5618
                                   context);
56✔
5619
        loader_trace_result("builtin", status);
342✔
5620
    }
56✔
5621
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
5622
    if (SIXEL_FAILED(status)) {
394✔
5623
        loader_trace_try("gnome-thumbnailer");
12✔
5624
        status = load_with_gnome_thumbnailer(pchunk,
16✔
5625
                                             fstatic,
4✔
5626
                                             fuse_palette,
4✔
5627
                                             reqcolors,
4✔
5628
                                             bgcolor,
4✔
5629
                                             loop_control,
4✔
5630
                                             fn_load,
4✔
5631
                                             context);
4✔
5632
        loader_trace_result("gnome-thumbnailer", status);
12✔
5633
    }
4✔
5634
#endif
5635
    if (SIXEL_FAILED(status)) {
429✔
5636
        goto end;
12✔
5637
    }
5638

5639
end:
278✔
5640
    sixel_chunk_destroy(pchunk);
443✔
5641

5642
    return status;
443✔
5643
}
5644

5645

5646
#if HAVE_TESTS
5647
static int
5648
test1(void)
×
5649
{
5650
    int nret = EXIT_FAILURE;
×
5651
    unsigned char *ptr = malloc(16);
×
5652

5653
    nret = EXIT_SUCCESS;
×
5654
    goto error;
×
5655

5656
    nret = EXIT_SUCCESS;
5657

5658
error:
5659
    free(ptr);
×
5660
    return nret;
×
5661
}
5662

5663

5664
SIXELAPI int
5665
sixel_loader_tests_main(void)
×
5666
{
5667
    int nret = EXIT_FAILURE;
×
5668
    size_t i;
5669
    typedef int (* testcase)(void);
5670

5671
    static testcase const testcases[] = {
5672
        test1,
5673
    };
5674

5675
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
5676
        nret = testcases[i]();
×
5677
        if (nret != EXIT_SUCCESS) {
×
5678
            goto error;
×
5679
        }
5680
    }
5681

5682
    nret = EXIT_SUCCESS;
×
5683

5684
error:
5685
    return nret;
×
5686
}
5687
#endif  /* HAVE_TESTS */
5688

5689
/* emacs Local Variables:      */
5690
/* emacs mode: c               */
5691
/* emacs tab-width: 4          */
5692
/* emacs indent-tabs-mode: nil */
5693
/* emacs c-basic-offset: 4     */
5694
/* emacs End:                  */
5695
/* vim: set expandtab ts=4 sts=4 sw=4 : */
5696
/* 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