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

saitoha / libsixel / 19007822169

02 Nov 2025 05:20AM UTC coverage: 44.562% (-0.1%) from 44.683%
19007822169

push

github

saitoha
cli: when input path is missing, score candidates by name/extension/mtime and suggest likely matches

6941 of 23332 branches covered (29.75%)

296 of 451 new or added lines in 1 file covered. (65.63%)

84 existing lines in 4 files now uncovered.

9952 of 22333 relevant lines covered (44.56%)

1079746.19 hits per line

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

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

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

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

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

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

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

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

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

125
#define SIXEL_THUMBNAILER_DEFAULT_SIZE 512
126

127
static int loader_trace_enabled;
128
static int thumbnailer_size_hint = SIXEL_THUMBNAILER_DEFAULT_SIZE;
129

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

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

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

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

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

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

182
    return copy;
×
183
}
184

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

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

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

232
    if (!loader_trace_enabled) {
75!
233
        return;
75✔
234
    }
235

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

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

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

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

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

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

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

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

302
static int
303
loader_plan_contains(sixel_loader_entry_t const **plan,
1,192✔
304
                     size_t plan_length,
305
                     sixel_loader_entry_t const *entry)
306
{
307
    size_t index;
308

309
    for (index = 0; index < plan_length; ++index) {
2,384✔
310
        if (plan[index] == entry) {
1,192!
311
            return 1;
×
312
        }
313
    }
894✔
314

315
    return 0;
1,192✔
316
}
596✔
317

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

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

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

339
    return 1;
×
340
}
341

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

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

358
    return NULL;
×
359
}
360

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

396
    plan_length = 0;
447✔
397
    index = 0;
447✔
398
    cursor = order;
447✔
399
    token_start = order;
447✔
400
    token_end = order;
447✔
401
    token_length = 0;
447✔
402
    entry = NULL;
447✔
403
    limit = plan_capacity;
447✔
404

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

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

463
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
1,639!
464
        entry = &entries[index];
1,192✔
465
        if (!loader_plan_contains(plan, plan_length, entry)) {
1,192!
466
            plan[plan_length] = entry;
1,192✔
467
            ++plan_length;
1,192✔
468
        }
596✔
469
    }
596✔
470

471
    return plan_length;
447✔
472
}
473

474
sixel_allocator_t *stbi_allocator;
475

476
void *
477
stbi_malloc(size_t n)
799✔
478
{
479
    return sixel_allocator_malloc(stbi_allocator, n);
799✔
480
}
481

482
void *
483
stbi_realloc(void *p, size_t n)
192✔
484
{
485
    return sixel_allocator_realloc(stbi_allocator, p, n);
192✔
486
}
487

488
void
489
stbi_free(void *p)
1,049✔
490
{
491
    sixel_allocator_free(stbi_allocator, p);
1,049✔
492
}
1,049✔
493

494
#define STBI_MALLOC stbi_malloc
495
#define STBI_REALLOC stbi_realloc
496
#define STBI_FREE stbi_free
497

498
#define STBI_NO_STDIO 1
499
#define STB_IMAGE_IMPLEMENTATION 1
500
#define STBI_FAILURE_USERMSG 1
501
#if defined(_WIN32)
502
# define STBI_NO_THREAD_LOCALS 1  /* no tls */
503
#endif
504
#define STBI_NO_GIF
505
#define STBI_NO_PNM
506

507
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
508
# pragma GCC diagnostic push
509
# pragma GCC diagnostic ignored "-Wsign-conversion"
510
#endif
511
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
512
# pragma GCC diagnostic push
513
# pragma GCC diagnostic ignored "-Wstrict-overflow"
514
#endif
515
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
516
# pragma GCC diagnostic push
517
# pragma GCC diagnostic ignored "-Wswitch-default"
518
#endif
519
#if HAVE_DIAGNOSTIC_SHADOW
520
# pragma GCC diagnostic push
521
# pragma GCC diagnostic ignored "-Wshadow"
522
#endif
523
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
524
# pragma GCC diagnostic push
525
# pragma GCC diagnostic ignored "-Wdouble-promotion"
526
#endif
527
# if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
528
# pragma GCC diagnostic push
529
# pragma GCC diagnostic ignored "-Wunused-function"
530
#endif
531
# if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
532
# pragma GCC diagnostic push
533
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
534
#endif
535
#include "stb_image.h"
536
#if HAVE_DIAGNOSTIC_UNUSED_BUT_SET_VARIABLE
537
# pragma GCC diagnostic pop
538
#endif
539
#if HAVE_DIAGNOSTIC_UNUSED_FUNCTION
540
# pragma GCC diagnostic pop
541
#endif
542
#if HAVE_DIAGNOSTIC_DOUBLE_PROMOTION
543
# pragma GCC diagnostic pop
544
#endif
545
#if HAVE_DIAGNOSTIC_SHADOW
546
# pragma GCC diagnostic pop
547
#endif
548
#if HAVE_DIAGNOSTIC_SWITCH_DEFAULT
549
# pragma GCC diagnostic pop
550
#endif
551
#if HAVE_DIAGNOSTIC_STRICT_OVERFLOW
552
# pragma GCC diagnostic pop
553
#endif
554
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
555
# pragma GCC diagnostic pop
556
#endif
557

558

559
# if HAVE_JPEG
560
/* import from @uobikiemukot's sdump loader.h */
561
static SIXELSTATUS
562
load_jpeg(unsigned char **result,
563
          unsigned char *data,
564
          size_t datasize,
565
          int *pwidth,
566
          int *pheight,
567
          int *ppixelformat,
568
          sixel_allocator_t *allocator)
569
{
570
    SIXELSTATUS status = SIXEL_JPEG_ERROR;
571
    JDIMENSION row_stride;
572
    size_t size;
573
    JSAMPARRAY buffer;
574
    struct jpeg_decompress_struct cinfo;
575
    struct jpeg_error_mgr pub;
576

577
    cinfo.err = jpeg_std_error(&pub);
578

579
    jpeg_create_decompress(&cinfo);
580
    jpeg_mem_src(&cinfo, data, datasize);
581
    jpeg_read_header(&cinfo, TRUE);
582

583
    /* disable colormap (indexed color), grayscale -> rgb */
584
    cinfo.quantize_colors = FALSE;
585
    cinfo.out_color_space = JCS_RGB;
586
    jpeg_start_decompress(&cinfo);
587

588
    if (cinfo.output_components != 3) {
589
        sixel_helper_set_additional_message(
590
            "load_jpeg: unknown pixel format.");
591
        status = SIXEL_BAD_INPUT;
592
        goto end;
593
    }
594

595
    *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
596

597
    if (cinfo.output_width > INT_MAX || cinfo.output_height > INT_MAX) {
598
        status = SIXEL_BAD_INTEGER_OVERFLOW;
599
        goto end;
600
    }
601
    *pwidth = (int)cinfo.output_width;
602
    *pheight = (int)cinfo.output_height;
603

604
    size = (size_t)(*pwidth * *pheight * cinfo.output_components);
605
    *result = (unsigned char *)sixel_allocator_malloc(allocator, size);
606
    if (*result == NULL) {
607
        sixel_helper_set_additional_message(
608
            "load_jpeg: sixel_allocator_malloc() failed.");
609
        status = SIXEL_BAD_ALLOCATION;
610
        goto end;
611
    }
612
    row_stride = cinfo.output_width * (unsigned int)cinfo.output_components;
613
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
614

615
    while (cinfo.output_scanline < cinfo.output_height) {
616
        jpeg_read_scanlines(&cinfo, buffer, 1);
617
        if (cinfo.err->num_warnings > 0) {
618
            sixel_helper_set_additional_message(
619
                "jpeg_read_scanlines: error/warining occuered.");
620
            status = SIXEL_BAD_INPUT;
621
            goto end;
622
        }
623
        memcpy(*result + (cinfo.output_scanline - 1) * row_stride, buffer[0], row_stride);
624
    }
625

626
    status = SIXEL_OK;
627

628
end:
629
    jpeg_finish_decompress(&cinfo);
630
    jpeg_destroy_decompress(&cinfo);
631

632
    return status;
633
}
634
# endif  /* HAVE_JPEG */
635

636

637
# if HAVE_LIBPNG
638
static void
639
read_png(png_structp png_ptr,
640
         png_bytep data,
641
         png_size_t length)
642
{
643
    sixel_chunk_t *pchunk = (sixel_chunk_t *)png_get_io_ptr(png_ptr);
644
    if (length > pchunk->size) {
645
        length = pchunk->size;
646
    }
647
    if (length > 0) {
648
        memcpy(data, pchunk->buffer, length);
649
        pchunk->buffer += length;
650
        pchunk->size -= length;
651
    }
652
}
653

654

655
static void
656
read_palette(png_structp png_ptr,
657
             png_infop info_ptr,
658
             unsigned char *palette,
659
             int ncolors,
660
             png_color *png_palette,
661
             png_color_16 *pbackground,
662
             int *transparent)
663
{
664
    png_bytep trans = NULL;
665
    int num_trans = 0;
666
    int i;
667

668
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
669
        png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
670
    }
671
    if (num_trans > 0) {
672
        *transparent = trans[0];
673
    }
674
    for (i = 0; i < ncolors; ++i) {
675
        if (pbackground && i < num_trans) {
676
            palette[i * 3 + 0] = ((0xff - trans[i]) * pbackground->red
677
                                   + trans[i] * png_palette[i].red) >> 8;
678
            palette[i * 3 + 1] = ((0xff - trans[i]) * pbackground->green
679
                                   + trans[i] * png_palette[i].green) >> 8;
680
            palette[i * 3 + 2] = ((0xff - trans[i]) * pbackground->blue
681
                                   + trans[i] * png_palette[i].blue) >> 8;
682
        } else {
683
            palette[i * 3 + 0] = png_palette[i].red;
684
            palette[i * 3 + 1] = png_palette[i].green;
685
            palette[i * 3 + 2] = png_palette[i].blue;
686
        }
687
    }
688
}
689

690
jmp_buf jmpbuf;
691

692
/* libpng error handler */
693
static void
694
png_error_callback(png_structp png_ptr, png_const_charp error_message)
695
{
696
    (void) png_ptr;
697

698
    sixel_helper_set_additional_message(error_message);
699
#if HAVE_SETJMP && HAVE_LONGJMP
700
    longjmp(jmpbuf, 1);
701
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
702
}
703

704

705
static SIXELSTATUS
706
load_png(unsigned char      /* out */ **result,
707
         unsigned char      /* in */  *buffer,
708
         size_t             /* in */  size,
709
         int                /* out */ *psx,
710
         int                /* out */ *psy,
711
         unsigned char      /* out */ **ppalette,
712
         int                /* out */ *pncolors,
713
         int                /* in */  reqcolors,
714
         int                /* out */ *pixelformat,
715
         unsigned char      /* out */ *bgcolor,
716
         int                /* out */ *transparent,
717
         sixel_allocator_t  /* in */  *allocator)
718
{
719
    SIXELSTATUS status;
720
    sixel_chunk_t read_chunk;
721
    png_uint_32 bitdepth;
722
    png_uint_32 png_status;
723
    png_structp png_ptr;
724
    png_infop info_ptr;
725
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
726
# pragma GCC diagnostic push
727
# pragma GCC diagnostic ignored "-Wclobbered"
728
#endif
729
    unsigned char **rows = NULL;
730
    png_color *png_palette = NULL;
731
    png_color_16 background;
732
    png_color_16p default_background;
733
    png_uint_32 width;
734
    png_uint_32 height;
735
    int i;
736
    int depth;
737

738
#if HAVE_SETJMP && HAVE_LONGJMP
739
    if (setjmp(jmpbuf) != 0) {
740
        sixel_allocator_free(allocator, *result);
741
        *result = NULL;
742
        status = SIXEL_PNG_ERROR;
743
        goto cleanup;
744
    }
745
#endif  /* HAVE_SETJMP && HAVE_LONGJMP */
746

747
    status = SIXEL_FALSE;
748
    *result = NULL;
749

750
    png_ptr = png_create_read_struct(
751
        PNG_LIBPNG_VER_STRING, NULL, &png_error_callback, NULL);
752
    if (!png_ptr) {
753
        sixel_helper_set_additional_message(
754
            "png_create_read_struct() failed.");
755
        status = SIXEL_PNG_ERROR;
756
        goto cleanup;
757
    }
758

759
    /*
760
     * The minimum valid PNG is 67 bytes.
761
     * https://garethrees.org/2007/11/14/pngcrush/
762
     */
763
    if (size < 67) {
764
        sixel_helper_set_additional_message("PNG data too small to be valid!");
765
        status = SIXEL_PNG_ERROR;
766
        goto cleanup;
767
    }
768

769
#if HAVE_SETJMP
770
    if (setjmp(png_jmpbuf(png_ptr)) != 0) {
771
        sixel_allocator_free(allocator, *result);
772
        *result = NULL;
773
        status = SIXEL_PNG_ERROR;
774
        goto cleanup;
775
    }
776
#endif  /* HAVE_SETJMP */
777

778
    info_ptr = png_create_info_struct(png_ptr);
779
    if (!info_ptr) {
780
        sixel_helper_set_additional_message(
781
            "png_create_info_struct() failed.");
782
        status = SIXEL_PNG_ERROR;
783
        png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
784
        goto cleanup;
785
    }
786
    read_chunk.buffer = buffer;
787
    read_chunk.size = size;
788

789
    png_set_read_fn(png_ptr,(png_voidp)&read_chunk, read_png);
790
    png_read_info(png_ptr, info_ptr);
791

792
    width = png_get_image_width(png_ptr, info_ptr);
793
    height = png_get_image_height(png_ptr, info_ptr);
794

795
    if (width > INT_MAX || height > INT_MAX) {
796
        status = SIXEL_BAD_INTEGER_OVERFLOW;
797
        goto cleanup;
798
    }
799

800
    *psx = (int)width;
801
    *psy = (int)height;
802

803
    bitdepth = png_get_bit_depth(png_ptr, info_ptr);
804
    if (bitdepth == 16) {
805
#  if HAVE_DEBUG
806
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
807
        fprintf(stderr, "stripping to 8bit...\n");
808
#  endif
809
        png_set_strip_16(png_ptr);
810
        bitdepth = 8;
811
    }
812

813
    if (bgcolor) {
814
#  if HAVE_DEBUG
815
        fprintf(stderr, "background color is specified [%02x, %02x, %02x]\n",
816
                bgcolor[0], bgcolor[1], bgcolor[2]);
817
#  endif
818
        background.red = bgcolor[0];
819
        background.green = bgcolor[1];
820
        background.blue = bgcolor[2];
821
        background.gray = (bgcolor[0] + bgcolor[1] + bgcolor[2]) / 3;
822
    } else if (png_get_bKGD(png_ptr, info_ptr, &default_background) == PNG_INFO_bKGD) {
823
        memcpy(&background, default_background, sizeof(background));
824
#  if HAVE_DEBUG
825
        fprintf(stderr, "background color is found [%02x, %02x, %02x]\n",
826
                background.red, background.green, background.blue);
827
#  endif
828
    } else {
829
        background.red = 0;
830
        background.green = 0;
831
        background.blue = 0;
832
        background.gray = 0;
833
    }
834

835
    switch (png_get_color_type(png_ptr, info_ptr)) {
836
    case PNG_COLOR_TYPE_PALETTE:
837
#  if HAVE_DEBUG
838
        fprintf(stderr, "paletted PNG(PNG_COLOR_TYPE_PALETTE)\n");
839
#  endif
840
        png_status = png_get_PLTE(png_ptr, info_ptr,
841
                                  &png_palette, pncolors);
842
        if (png_status != PNG_INFO_PLTE || png_palette == NULL) {
843
            sixel_helper_set_additional_message(
844
                "PLTE chunk not found");
845
            status = SIXEL_PNG_ERROR;
846
            goto cleanup;
847
        }
848
#  if HAVE_DEBUG
849
        fprintf(stderr, "palette colors: %d\n", *pncolors);
850
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
851
#  endif
852
        if (ppalette == NULL || *pncolors > reqcolors) {
853
#  if HAVE_DEBUG
854
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
855
                    reqcolors);
856
            fprintf(stderr, "expand to RGB format...\n");
857
#  endif
858
            png_set_background(png_ptr, &background,
859
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
860
            png_set_palette_to_rgb(png_ptr);
861
            png_set_strip_alpha(png_ptr);
862
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
863
        } else {
864
            switch (bitdepth) {
865
            case 1:
866
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
867
                if (*ppalette == NULL) {
868
                    sixel_helper_set_additional_message(
869
                        "load_png: sixel_allocator_malloc() failed.");
870
                    status = SIXEL_BAD_ALLOCATION;
871
                    goto cleanup;
872
                }
873
                read_palette(png_ptr, info_ptr, *ppalette,
874
                             *pncolors, png_palette, &background, transparent);
875
                *pixelformat = SIXEL_PIXELFORMAT_PAL1;
876
                break;
877
            case 2:
878
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
879
                if (*ppalette == NULL) {
880
                    sixel_helper_set_additional_message(
881
                        "load_png: sixel_allocator_malloc() failed.");
882
                    status = SIXEL_BAD_ALLOCATION;
883
                    goto cleanup;
884
                }
885
                read_palette(png_ptr, info_ptr, *ppalette,
886
                             *pncolors, png_palette, &background, transparent);
887
                *pixelformat = SIXEL_PIXELFORMAT_PAL2;
888
                break;
889
            case 4:
890
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
891
                if (*ppalette == NULL) {
892
                    sixel_helper_set_additional_message(
893
                        "load_png: sixel_allocator_malloc() failed.");
894
                    status = SIXEL_BAD_ALLOCATION;
895
                    goto cleanup;
896
                }
897
                read_palette(png_ptr, info_ptr, *ppalette,
898
                             *pncolors, png_palette, &background, transparent);
899
                *pixelformat = SIXEL_PIXELFORMAT_PAL4;
900
                break;
901
            case 8:
902
                *ppalette = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)*pncolors * 3);
903
                if (*ppalette == NULL) {
904
                    sixel_helper_set_additional_message(
905
                        "load_png: sixel_allocator_malloc() failed.");
906
                    status = SIXEL_BAD_ALLOCATION;
907
                    goto cleanup;
908
                }
909
                read_palette(png_ptr, info_ptr, *ppalette,
910
                             *pncolors, png_palette, &background, transparent);
911
                *pixelformat = SIXEL_PIXELFORMAT_PAL8;
912
                break;
913
            default:
914
                png_set_background(png_ptr, &background,
915
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
916
                png_set_palette_to_rgb(png_ptr);
917
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
918
                break;
919
            }
920
        }
921
        break;
922
    case PNG_COLOR_TYPE_GRAY:
923
#  if HAVE_DEBUG
924
        fprintf(stderr, "grayscale PNG(PNG_COLOR_TYPE_GRAY)\n");
925
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
926
#  endif
927
        if (1 << bitdepth > reqcolors) {
928
#  if HAVE_DEBUG
929
            fprintf(stderr, "detected more colors than reqired(>%d).\n",
930
                    reqcolors);
931
            fprintf(stderr, "expand into RGB format...\n");
932
#  endif
933
            png_set_background(png_ptr, &background,
934
                               PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
935
            png_set_gray_to_rgb(png_ptr);
936
            *pixelformat = SIXEL_PIXELFORMAT_RGB888;
937
        } else {
938
            switch (bitdepth) {
939
            case 1:
940
            case 2:
941
            case 4:
942
                if (ppalette) {
943
#  if HAVE_DECL_PNG_SET_EXPAND_GRAY_1_2_4_TO_8
944
#   if HAVE_DEBUG
945
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
946
                            (unsigned int)bitdepth);
947
#   endif
948
                    png_set_expand_gray_1_2_4_to_8(png_ptr);
949
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
950
#  elif HAVE_DECL_PNG_SET_GRAY_1_2_4_TO_8
951
#   if HAVE_DEBUG
952
                    fprintf(stderr, "expand %u bpp to 8bpp format...\n",
953
                            (unsigned int)bitdepth);
954
#   endif
955
                    png_set_gray_1_2_4_to_8(png_ptr);
956
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
957
#  else
958
#   if HAVE_DEBUG
959
                    fprintf(stderr, "expand into RGB format...\n");
960
#   endif
961
                    png_set_background(png_ptr, &background,
962
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
963
                    png_set_gray_to_rgb(png_ptr);
964
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
965
#  endif
966
                } else {
967
                    png_set_background(png_ptr, &background,
968
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
969
                    png_set_gray_to_rgb(png_ptr);
970
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
971
                }
972
                break;
973
            case 8:
974
                if (ppalette) {
975
                    *pixelformat = SIXEL_PIXELFORMAT_G8;
976
                } else {
977
#  if HAVE_DEBUG
978
                    fprintf(stderr, "expand into RGB format...\n");
979
#  endif
980
                    png_set_background(png_ptr, &background,
981
                                       PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
982
                    png_set_gray_to_rgb(png_ptr);
983
                    *pixelformat = SIXEL_PIXELFORMAT_RGB888;
984
                }
985
                break;
986
            default:
987
#  if HAVE_DEBUG
988
                fprintf(stderr, "expand into RGB format...\n");
989
#  endif
990
                png_set_background(png_ptr, &background,
991
                                   PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
992
                png_set_gray_to_rgb(png_ptr);
993
                *pixelformat = SIXEL_PIXELFORMAT_RGB888;
994
                break;
995
            }
996
        }
997
        break;
998
    case PNG_COLOR_TYPE_GRAY_ALPHA:
999
#  if HAVE_DEBUG
1000
        fprintf(stderr, "grayscale-alpha PNG(PNG_COLOR_TYPE_GRAY_ALPHA)\n");
1001
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1002
        fprintf(stderr, "expand to RGB format...\n");
1003
#  endif
1004
        png_set_background(png_ptr, &background,
1005
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1006
        png_set_gray_to_rgb(png_ptr);
1007
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1008
        break;
1009
    case PNG_COLOR_TYPE_RGB_ALPHA:
1010
#  if HAVE_DEBUG
1011
        fprintf(stderr, "RGBA PNG(PNG_COLOR_TYPE_RGB_ALPHA)\n");
1012
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1013
        fprintf(stderr, "expand to RGB format...\n");
1014
#  endif
1015
        png_set_background(png_ptr, &background,
1016
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1017
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1018
        break;
1019
    case PNG_COLOR_TYPE_RGB:
1020
#  if HAVE_DEBUG
1021
        fprintf(stderr, "RGB PNG(PNG_COLOR_TYPE_RGB)\n");
1022
        fprintf(stderr, "bitdepth: %u\n", (unsigned int)bitdepth);
1023
#  endif
1024
        png_set_background(png_ptr, &background,
1025
                           PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
1026
        *pixelformat = SIXEL_PIXELFORMAT_RGB888;
1027
        break;
1028
    default:
1029
        /* unknown format */
1030
        goto cleanup;
1031
    }
1032
    depth = sixel_helper_compute_depth(*pixelformat);
1033
    *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * depth));
1034
    if (*result == NULL) {
1035
        sixel_helper_set_additional_message(
1036
            "load_png: sixel_allocator_malloc() failed.");
1037
        status = SIXEL_BAD_ALLOCATION;
1038
        goto cleanup;
1039
    }
1040
    rows = (unsigned char **)sixel_allocator_malloc(allocator, (size_t)*psy * sizeof(unsigned char *));
1041
    if (rows == NULL) {
1042
        sixel_helper_set_additional_message(
1043
            "load_png: sixel_allocator_malloc() failed.");
1044
        status = SIXEL_BAD_ALLOCATION;
1045
        goto cleanup;
1046
    }
1047
    switch (*pixelformat) {
1048
    case SIXEL_PIXELFORMAT_PAL1:
1049
    case SIXEL_PIXELFORMAT_PAL2:
1050
    case SIXEL_PIXELFORMAT_PAL4:
1051
        for (i = 0; i < *psy; ++i) {
1052
            rows[i] = *result + (depth * *psx * (int)bitdepth + 7) / 8 * i;
1053
        }
1054
        break;
1055
    default:
1056
        for (i = 0; i < *psy; ++i) {
1057
            rows[i] = *result + depth * *psx * i;
1058
        }
1059
        break;
1060
    }
1061

1062
    png_read_image(png_ptr, rows);
1063

1064
    status = SIXEL_OK;
1065

1066
cleanup:
1067
    png_destroy_read_struct(&png_ptr, &info_ptr,(png_infopp)0);
1068

1069
    if (rows != NULL) {
1070
        sixel_allocator_free(allocator, rows);
1071
    }
1072

1073
    return status;
1074
}
1075
#ifdef HAVE_DIAGNOSTIC_CLOBBERED
1076
# pragma GCC diagnostic pop
1077
#endif
1078

1079
# endif  /* HAVE_LIBPNG */
1080

1081

1082
static SIXELSTATUS
1083
load_sixel(unsigned char        /* out */ **result,
156✔
1084
           unsigned char        /* in */  *buffer,
1085
           int                  /* in */  size,
1086
           int                  /* out */ *psx,
1087
           int                  /* out */ *psy,
1088
           unsigned char        /* out */ **ppalette,
1089
           int                  /* out */ *pncolors,
1090
           int                  /* in */  reqcolors,
1091
           int                  /* out */ *ppixelformat,
1092
           sixel_allocator_t    /* in */  *allocator)
1093
{
1094
    SIXELSTATUS status = SIXEL_FALSE;
156✔
1095
    unsigned char *p = NULL;
156✔
1096
    unsigned char *palette = NULL;
156✔
1097
    int colors;
1098
    int i;
1099

1100
    /* sixel */
1101
    status = sixel_decode_raw(buffer, size,
208✔
1102
                              &p, psx, psy,
52✔
1103
                              &palette, &colors, allocator);
52✔
1104
    if (SIXEL_FAILED(status)) {
156!
1105
        goto end;
×
1106
    }
1107
    if (ppalette == NULL || colors > reqcolors) {
208!
1108
        *ppixelformat = SIXEL_PIXELFORMAT_RGB888;
36✔
1109
        *result = (unsigned char *)sixel_allocator_malloc(allocator, (size_t)(*psx * *psy * 3));
36✔
1110
        if (*result == NULL) {
36!
1111
            sixel_helper_set_additional_message(
×
1112
                "load_sixel: sixel_allocator_malloc() failed.");
1113
            status = SIXEL_BAD_ALLOCATION;
×
1114
            goto end;
×
1115
        }
1116
        for (i = 0; i < *psx * *psy; ++i) {
5,670,576✔
1117
            (*result)[i * 3 + 0] = palette[p[i] * 3 + 0];
5,670,540✔
1118
            (*result)[i * 3 + 1] = palette[p[i] * 3 + 1];
5,670,540✔
1119
            (*result)[i * 3 + 2] = palette[p[i] * 3 + 2];
5,670,540✔
1120
        }
1,890,180✔
1121
    } else {
12✔
1122
        *ppixelformat = SIXEL_PIXELFORMAT_PAL8;
120✔
1123
        *result = p;
120✔
1124
        *ppalette = palette;
120✔
1125
        *pncolors = colors;
120✔
1126
        p = NULL;
120✔
1127
        palette = NULL;
120✔
1128
    }
1129

1130
end:
104✔
1131
    /*
1132
     * Release the decoded index buffer when the caller requested an RGB
1133
     * conversion.  Palette-backed callers steal ownership by nulling `p`.
1134
     */
1135
    sixel_allocator_free(allocator, p);
156✔
1136
    sixel_allocator_free(allocator, palette);
156✔
1137

1138
    return status;
156✔
1139
}
1140

1141

1142
/* detect whether given chunk is sixel stream */
1143
static int
1144
chunk_is_sixel(sixel_chunk_t const *chunk)
353✔
1145
{
1146
    unsigned char *p;
1147
    unsigned char *end;
1148

1149
    p = chunk->buffer;
353✔
1150
    end = p + chunk->size;
353✔
1151

1152
    if (chunk->size < 3) {
353!
1153
        return 0;
2✔
1154
    }
1155

1156
    p++;
351✔
1157
    if (p >= end) {
351!
1158
        return 0;
×
1159
    }
1160
    if (*(p - 1) == 0x90 || (*(p - 1) == 0x1b && *p == 0x50)) {
351!
1161
        while (p++ < end) {
570!
1162
            if (*p == 0x71) {
570✔
1163
                return 1;
156✔
1164
            } else if (*p == 0x18 || *p == 0x1a) {
414!
1165
                return 0;
×
1166
            } else if (*p < 0x20) {
414✔
1167
                continue;
6✔
1168
            } else if (*p < 0x30) {
408!
1169
                return 0;
×
1170
            } else if (*p < 0x40) {
408✔
1171
                continue;
405✔
1172
            }
1173
        }
1174
    }
1175
    return 0;
195✔
1176
}
55✔
1177

1178

1179
/* detect whether given chunk is PNM stream */
1180
static int
1181
chunk_is_pnm(sixel_chunk_t const *chunk)
197✔
1182
{
1183
    if (chunk->size < 2) {
197!
1184
        return 0;
2✔
1185
    }
1186
    if (chunk->buffer[0] == 'P' &&
195✔
1187
        chunk->buffer[1] >= '1' &&
20!
1188
        chunk->buffer[1] <= '6') {
20!
1189
        return 1;
20✔
1190
    }
1191
    return 0;
175✔
1192
}
3✔
1193

1194

1195
#if HAVE_LIBPNG
1196
/* detect whether given chunk is PNG stream */
1197
static int
1198
chunk_is_png(sixel_chunk_t const *chunk)
1199
{
1200
    if (chunk->size < 8) {
1201
        return 0;
1202
    }
1203
    if (png_check_sig(chunk->buffer, 8)) {
1204
        return 1;
1205
    }
1206
    return 0;
1207
}
1208
#endif  /* HAVE_LIBPNG */
1209

1210

1211
/* detect whether given chunk is GIF stream */
1212
static int
1213
chunk_is_gif(sixel_chunk_t const *chunk)
177✔
1214
{
1215
    if (chunk->size < 6) {
177✔
1216
        return 0;
3✔
1217
    }
1218
    if (chunk->buffer[0] == 'G' &&
176✔
1219
        chunk->buffer[1] == 'I' &&
16!
1220
        chunk->buffer[2] == 'F' &&
16!
1221
        chunk->buffer[3] == '8' &&
16!
1222
        (chunk->buffer[4] == '7' || chunk->buffer[4] == '9') &&
16!
1223
        chunk->buffer[5] == 'a') {
16!
1224
        return 1;
16✔
1225
    }
1226
    return 0;
158✔
1227
}
3✔
1228

1229

1230
#if HAVE_JPEG
1231
/* detect whether given chunk is JPEG stream */
1232
static int
1233
chunk_is_jpeg(sixel_chunk_t const *chunk)
1234
{
1235
    if (chunk->size < 2) {
1236
        return 0;
1237
    }
1238
    if (memcmp("\xFF\xD8", chunk->buffer, 2) == 0) {
1239
        return 1;
1240
    }
1241
    return 0;
1242
}
1243
#endif  /* HAVE_JPEG */
1244

1245
typedef union _fn_pointer {
1246
    sixel_load_image_function fn;
1247
    void *                    p;
1248
} fn_pointer;
1249

1250
/* load images using builtin image loaders */
1251
static SIXELSTATUS
1252
load_with_builtin(
353✔
1253
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
1254
    int                       /* in */     fstatic,      /* static */
1255
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
1256
    int                       /* in */     reqcolors,    /* reqcolors */
1257
    unsigned char             /* in */     *bgcolor,     /* background color */
1258
    int                       /* in */     loop_control, /* one of enum loop_control */
1259
    sixel_load_image_function /* in */     fn_load,      /* callback */
1260
    void                      /* in/out */ *context      /* private data for callback */
1261
)
1262
{
1263
    SIXELSTATUS status = SIXEL_FALSE;
353✔
1264
    sixel_frame_t *frame = NULL;
353✔
1265
    fn_pointer fnp;
1266
    stbi__context stb_context;
1267
    int depth;
1268
    char message[256];
1269
    int nwrite;
1270

1271
    if (chunk_is_sixel(pchunk)) {
353✔
1272
        status = sixel_frame_new(&frame, pchunk->allocator);
156✔
1273
        if (SIXEL_FAILED(status)) {
156!
1274
            goto end;
×
1275
        }
1276
        if (pchunk->size > INT_MAX) {
156!
1277
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1278
            goto end;
×
1279
        }
1280
        status = load_sixel(&frame->pixels,
128✔
1281
                            pchunk->buffer,
156✔
1282
                            (int)pchunk->size,
156✔
1283
                            &frame->width,
156✔
1284
                            &frame->height,
156✔
1285
                            fuse_palette ? &frame->palette: NULL,
132✔
1286
                            &frame->ncolors,
156✔
1287
                            reqcolors,
52✔
1288
                            &frame->pixelformat,
156✔
1289
                            pchunk->allocator);
156✔
1290
        if (SIXEL_FAILED(status)) {
156!
1291
            goto end;
×
1292
        }
1293
    } else if (chunk_is_pnm(pchunk)) {
249!
1294
        status = sixel_frame_new(&frame, pchunk->allocator);
20✔
1295
        if (SIXEL_FAILED(status)) {
20!
1296
            goto end;
×
1297
        }
1298
        if (pchunk->size > INT_MAX) {
20!
1299
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1300
            goto end;
×
1301
        }
1302
        /* pnm */
1303
        status = load_pnm(pchunk->buffer,
20✔
1304
                          (int)pchunk->size,
20✔
1305
                          frame->allocator,
20✔
1306
                          &frame->pixels,
20✔
1307
                          &frame->width,
20✔
1308
                          &frame->height,
20✔
1309
                          fuse_palette ? &frame->palette: NULL,
×
1310
                          &frame->ncolors,
20✔
1311
                          &frame->pixelformat);
20!
1312
        if (SIXEL_FAILED(status)) {
20!
1313
            goto end;
×
1314
        }
1315
    } else if (chunk_is_gif(pchunk)) {
177✔
1316
        fnp.fn = fn_load;
16✔
1317
        if (pchunk->size > INT_MAX) {
16!
1318
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1319
            goto end;
×
1320
        }
1321
        status = load_gif(pchunk->buffer,
18✔
1322
                          (int)pchunk->size,
16✔
1323
                          bgcolor,
2✔
1324
                          reqcolors,
2✔
1325
                          fuse_palette,
2✔
1326
                          fstatic,
2✔
1327
                          loop_control,
2✔
1328
                          fnp.p,
2✔
1329
                          context,
2✔
1330
                          pchunk->allocator);
16✔
1331
        if (SIXEL_FAILED(status)) {
16!
1332
            goto end;
6✔
1333
        }
1334
        goto end;
10✔
1335
    } else {
1336
        /*
1337
         * Fallback to stb_image decoding when no specialized handler
1338
         * claimed the chunk.
1339
         *
1340
         *    +--------------+     +--------------------+
1341
         *    | raw chunk    | --> | stb_image decoding |
1342
         *    +--------------+     +--------------------+
1343
         *                        |
1344
         *                        v
1345
         *                +--------------------+
1346
         *                | sixel frame emit   |
1347
         *                +--------------------+
1348
         */
1349
        status = sixel_frame_new(&frame, pchunk->allocator);
161✔
1350
        if (SIXEL_FAILED(status)) {
161!
1351
            goto end;
×
1352
        }
1353
        if (pchunk->size > INT_MAX) {
161!
1354
            status = SIXEL_BAD_INTEGER_OVERFLOW;
×
1355
            goto end;
×
1356
        }
1357
        stbi_allocator = pchunk->allocator;
161✔
1358
        stbi__start_mem(&stb_context,
161✔
1359
                        pchunk->buffer,
161✔
1360
                        (int)pchunk->size);
161✔
1361
        frame->pixels = stbi__load_and_postprocess_8bit(&stb_context,
321✔
1362
                                                        &frame->width,
161✔
1363
                                                        &frame->height,
161✔
1364
                                                        &depth,
1365
                                                        3);
1366
        if (frame->pixels == NULL) {
161!
1367
            sixel_helper_set_additional_message(stbi_failure_reason());
3✔
1368
            status = SIXEL_STBI_ERROR;
3✔
1369
            goto end;
3✔
1370
        }
1371
        frame->loop_count = 1;
158✔
1372
        switch (depth) {
158!
1373
        case 1:
158✔
1374
        case 3:
1375
        case 4:
1376
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
158✔
1377
            break;
158✔
1378
        default:
1379
            nwrite = snprintf(message,
×
1380
                              sizeof(message),
1381
                              "load_with_builtin() failed.\n"
1382
                              "reason: unknown pixel-format.(depth: %d)\n",
1383
                              depth);
1384
            if (nwrite > 0) {
×
1385
                sixel_helper_set_additional_message(message);
×
1386
            }
1387
            status = SIXEL_STBI_ERROR;
×
1388
            goto end;
×
1389
        }
1390
    }
1391

1392
    status = sixel_frame_strip_alpha(frame, bgcolor);
334✔
1393
    if (SIXEL_FAILED(status)) {
334!
1394
        goto end;
×
1395
    }
1396

1397
    status = fn_load(frame, context);
334✔
1398
    if (SIXEL_FAILED(status)) {
334!
UNCOV
1399
        goto end;
×
1400
    }
1401

1402
    status = SIXEL_OK;
334✔
1403

1404
end:
298✔
1405
    sixel_frame_unref(frame);
353✔
1406

1407
    return status;
353✔
1408
}
1409

1410

1411
#if HAVE_JPEG
1412
/*
1413
 * Dedicated libjpeg loader wiring minimal pipeline.
1414
 *
1415
 *    +------------+     +-------------------+     +--------------------+
1416
 *    | JPEG chunk | --> | libjpeg decode    | --> | sixel frame emit   |
1417
 *    +------------+     +-------------------+     +--------------------+
1418
 */
1419
static SIXELSTATUS
1420
load_with_libjpeg(
1421
    sixel_chunk_t const       /* in */     *pchunk,
1422
    int                       /* in */     fstatic,
1423
    int                       /* in */     fuse_palette,
1424
    int                       /* in */     reqcolors,
1425
    unsigned char             /* in */     *bgcolor,
1426
    int                       /* in */     loop_control,
1427
    sixel_load_image_function /* in */     fn_load,
1428
    void                      /* in/out */ *context)
1429
{
1430
    SIXELSTATUS status = SIXEL_FALSE;
1431
    sixel_frame_t *frame = NULL;
1432

1433
    (void)fstatic;
1434
    (void)fuse_palette;
1435
    (void)reqcolors;
1436
    (void)loop_control;
1437

1438
    status = sixel_frame_new(&frame, pchunk->allocator);
1439
    if (SIXEL_FAILED(status)) {
1440
        goto end;
1441
    }
1442

1443
    status = load_jpeg(&frame->pixels,
1444
                       pchunk->buffer,
1445
                       pchunk->size,
1446
                       &frame->width,
1447
                       &frame->height,
1448
                       &frame->pixelformat,
1449
                       pchunk->allocator);
1450
    if (SIXEL_FAILED(status)) {
1451
        goto end;
1452
    }
1453

1454
    status = sixel_frame_strip_alpha(frame, bgcolor);
1455
    if (SIXEL_FAILED(status)) {
1456
        goto end;
1457
    }
1458

1459
    status = fn_load(frame, context);
1460
    if (SIXEL_FAILED(status)) {
1461
        goto end;
1462
    }
1463

1464
    status = SIXEL_OK;
1465

1466
end:
1467
    sixel_frame_unref(frame);
1468

1469
    return status;
1470
}
1471

1472
static int
1473
loader_can_try_libjpeg(sixel_chunk_t const *chunk)
1474
{
1475
    if (chunk == NULL) {
1476
        return 0;
1477
    }
1478

1479
    return chunk_is_jpeg(chunk);
1480
}
1481
#endif  /* HAVE_JPEG */
1482

1483
#if HAVE_LIBPNG
1484
/*
1485
 * Dedicated libpng loader for precise PNG decoding.
1486
 *
1487
 *    +-----------+     +------------------+     +--------------------+
1488
 *    | PNG chunk | --> | libpng decode    | --> | sixel frame emit   |
1489
 *    +-----------+     +------------------+     +--------------------+
1490
 */
1491
static SIXELSTATUS
1492
load_with_libpng(
1493
    sixel_chunk_t const       /* in */     *pchunk,
1494
    int                       /* in */     fstatic,
1495
    int                       /* in */     fuse_palette,
1496
    int                       /* in */     reqcolors,
1497
    unsigned char             /* in */     *bgcolor,
1498
    int                       /* in */     loop_control,
1499
    sixel_load_image_function /* in */     fn_load,
1500
    void                      /* in/out */ *context)
1501
{
1502
    SIXELSTATUS status = SIXEL_FALSE;
1503
    sixel_frame_t *frame = NULL;
1504

1505
    (void)fstatic;
1506
    (void)loop_control;
1507

1508
    status = sixel_frame_new(&frame, pchunk->allocator);
1509
    if (SIXEL_FAILED(status)) {
1510
        goto end;
1511
    }
1512

1513
    status = load_png(&frame->pixels,
1514
                      pchunk->buffer,
1515
                      pchunk->size,
1516
                      &frame->width,
1517
                      &frame->height,
1518
                      fuse_palette ? &frame->palette : NULL,
1519
                      &frame->ncolors,
1520
                      reqcolors,
1521
                      &frame->pixelformat,
1522
                      bgcolor,
1523
                      &frame->transparent,
1524
                      pchunk->allocator);
1525
    if (SIXEL_FAILED(status)) {
1526
        goto end;
1527
    }
1528

1529
    status = sixel_frame_strip_alpha(frame, bgcolor);
1530
    if (SIXEL_FAILED(status)) {
1531
        goto end;
1532
    }
1533

1534
    status = fn_load(frame, context);
1535
    if (SIXEL_FAILED(status)) {
1536
        goto end;
1537
    }
1538

1539
    status = SIXEL_OK;
1540

1541
end:
1542
    sixel_frame_unref(frame);
1543

1544
    return status;
1545
}
1546

1547
static int
1548
loader_can_try_libpng(sixel_chunk_t const *chunk)
1549
{
1550
    if (chunk == NULL) {
1551
        return 0;
1552
    }
1553

1554
    return chunk_is_png(chunk);
1555
}
1556
#endif  /* HAVE_LIBPNG */
1557

1558
#ifdef HAVE_GDK_PIXBUF2
1559
/*
1560
 * Loader backed by gdk-pixbuf2. The entire animation is consumed via
1561
 * GdkPixbufLoader, each frame is copied into a temporary buffer and forwarded as
1562
 * a sixel_frame_t. Loop attributes provided by gdk-pixbuf are reconciled with
1563
 * libsixel's loop control settings.
1564
 */
1565
static SIXELSTATUS
1566
load_with_gdkpixbuf(
1567
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
1568
    int                       /* in */     fstatic,      /* static */
1569
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
1570
    int                       /* in */     reqcolors,    /* reqcolors */
1571
    unsigned char             /* in */     *bgcolor,     /* background color */
1572
    int                       /* in */     loop_control, /* one of enum loop_control */
1573
    sixel_load_image_function /* in */     fn_load,      /* callback */
1574
    void                      /* in/out */ *context      /* private data for callback */
1575
)
1576
{
1577
    SIXELSTATUS status = SIXEL_FALSE;
1578
    GdkPixbuf *pixbuf;
1579
    GdkPixbufLoader *loader = NULL;
1580
    gboolean loader_closed = FALSE;  /* remember if loader was already closed */
1581
    GdkPixbufAnimation *animation;
1582
    GdkPixbufAnimationIter *it = NULL;
1583
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1584
# pragma GCC diagnostic push
1585
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1586
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1587
    GTimeVal time_val;
1588
    GTimeVal start_time;
1589
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1590
# pragma GCC diagnostic pop
1591
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1592
    sixel_frame_t *frame = NULL;
1593
    int stride;
1594
    unsigned char *p;
1595
    int i;
1596
    int depth;
1597
    int anim_loop_count = (-1);  /* (-1): infinite, >=0: finite loop count */
1598
    int delay_ms;
1599
    gboolean use_animation = FALSE;
1600

1601
    (void) fuse_palette;
1602
    (void) reqcolors;
1603
    (void) bgcolor;
1604

1605
    status = sixel_frame_new(&frame, pchunk->allocator);
1606
    if (SIXEL_FAILED(status)) {
1607
        goto end;
1608
    }
1609

1610
#if (! GLIB_CHECK_VERSION(2, 36, 0))
1611
    g_type_init();
1612
#endif
1613
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1614
# pragma GCC diagnostic push
1615
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1616
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1617
    g_get_current_time(&time_val);
1618
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1619
# pragma GCC diagnostic pop
1620
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1621
    start_time = time_val;
1622
    loader = gdk_pixbuf_loader_new();
1623
    if (loader == NULL) {
1624
        status = SIXEL_GDK_ERROR;
1625
        goto end;
1626
    }
1627
    /* feed the whole blob and close so the animation metadata becomes available */
1628
    if (! gdk_pixbuf_loader_write(loader, pchunk->buffer, pchunk->size, NULL)) {
1629
        status = SIXEL_GDK_ERROR;
1630
        goto end;
1631
    }
1632
    if (! gdk_pixbuf_loader_close(loader, NULL)) {
1633
        status = SIXEL_GDK_ERROR;
1634
        goto end;
1635
    }
1636
    loader_closed = TRUE;
1637
    pixbuf = NULL;
1638
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1639
# pragma GCC diagnostic push
1640
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1641
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1642
    animation = gdk_pixbuf_loader_get_animation(loader);
1643
    if (animation) {
1644
        /*
1645
         * +------------------------------------------------------+
1646
         * | GdkPixbuf 2.44 keeps the animation APIs available,   |
1647
         * | but marks them deprecated. We still need the         |
1648
         * | GTimeVal-driven timeline to preserve playback, so we |
1649
         * | mute the warning locally instead of abandoning       |
1650
         * | multi-frame decoding.                                |
1651
         * +------------------------------------------------------+
1652
         */
1653
        if (GDK_IS_PIXBUF_SIMPLE_ANIM(animation)) {
1654
            anim_loop_count = gdk_pixbuf_simple_anim_get_loop(
1655
                                 GDK_PIXBUF_SIMPLE_ANIM(animation))
1656
                             ? (-1)
1657
                             : 1;
1658
        } else {
1659
            GParamSpec *loop_pspec = g_object_class_find_property(
1660
                G_OBJECT_GET_CLASS(animation), "loop");
1661
            if (loop_pspec == NULL) {
1662
                loop_pspec = g_object_class_find_property(
1663
                    G_OBJECT_GET_CLASS(animation), "loop-count");
1664
            }
1665
            if (loop_pspec) {
1666
                GValue loop_value = G_VALUE_INIT;
1667
                g_value_init(&loop_value, loop_pspec->value_type);
1668
                g_object_get_property(G_OBJECT(animation),
1669
                                      g_param_spec_get_name(loop_pspec),
1670
                                      &loop_value);
1671
                if (G_VALUE_HOLDS_BOOLEAN(&loop_value)) {
1672
                    /* TRUE means "loop forever" for these properties */
1673
                    anim_loop_count = g_value_get_boolean(&loop_value)
1674
                                      ? (-1)
1675
                                      : 1;
1676
                } else if (G_VALUE_HOLDS_INT(&loop_value)) {
1677
                    int loop_int = g_value_get_int(&loop_value);
1678
                    /* GIF spec treats zero as infinite repetition */
1679
                    anim_loop_count = (loop_int <= 0) ? (-1) : loop_int;
1680
                } else if (G_VALUE_HOLDS_UINT(&loop_value)) {
1681
                    guint loop_uint = g_value_get_uint(&loop_value);
1682
                    if (loop_uint == 0U) {
1683
                        anim_loop_count = (-1);
1684
                    } else {
1685
                        anim_loop_count = loop_uint > (guint)INT_MAX
1686
                                            ? INT_MAX
1687
                                            : (int)loop_uint;
1688
                    }
1689
                }
1690
                g_value_unset(&loop_value);
1691
            }
1692
        }
1693
        if (!fstatic &&
1694
                !gdk_pixbuf_animation_is_static_image(animation)) {
1695
            use_animation = TRUE;
1696
        }
1697
    }
1698
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1699
# pragma GCC diagnostic pop
1700
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1701

1702
    if (! use_animation) {
1703
        /* fall back to single frame decoding */
1704
        pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1705
        if (pixbuf == NULL) {
1706
            goto end;
1707
        }
1708
        frame->frame_no = 0;
1709
        frame->width = gdk_pixbuf_get_width(pixbuf);
1710
        frame->height = gdk_pixbuf_get_height(pixbuf);
1711
        stride = gdk_pixbuf_get_rowstride(pixbuf);
1712
        frame->pixels = sixel_allocator_malloc(
1713
            pchunk->allocator,
1714
            (size_t)(frame->height * stride));
1715
        if (frame->pixels == NULL) {
1716
            sixel_helper_set_additional_message(
1717
                "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1718
            status = SIXEL_BAD_ALLOCATION;
1719
            goto end;
1720
        }
1721
        if (stride / frame->width == 4) {
1722
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1723
            depth = 4;
1724
        } else {
1725
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1726
            depth = 3;
1727
        }
1728
        p = gdk_pixbuf_get_pixels(pixbuf);
1729
        if (stride == frame->width * depth) {
1730
            memcpy(frame->pixels, p, (size_t)(frame->height * stride));
1731
        } else {
1732
            for (i = 0; i < frame->height; ++i) {
1733
                memcpy(frame->pixels + frame->width * depth * i,
1734
                       p + stride * i,
1735
                       (size_t)(frame->width * depth));
1736
            }
1737
        }
1738
        frame->delay = 0;
1739
        frame->multiframe = 0;
1740
        frame->loop_count = 0;
1741
        status = fn_load(frame, context);
1742
        if (status != SIXEL_OK) {
1743
            goto end;
1744
        }
1745
    } else {
1746
        gboolean finished;
1747

1748
        /* reset iterator to the beginning of the timeline */
1749
        time_val = start_time;
1750
        frame->frame_no = 0;
1751
        frame->loop_count = 0;
1752

1753
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1754
# pragma GCC diagnostic push
1755
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1756
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1757
        it = gdk_pixbuf_animation_get_iter(animation, &time_val);
1758
        if (it == NULL) {
1759
            status = SIXEL_GDK_ERROR;
1760
            goto end;
1761
        }
1762

1763
        for (;;) {
1764
            /* handle one logical loop of the animation */
1765
            finished = FALSE;
1766
            while (!gdk_pixbuf_animation_iter_on_currently_loading_frame(it)) {
1767
                /* {{{ */
1768
                pixbuf = gdk_pixbuf_animation_iter_get_pixbuf(it);
1769
                if (pixbuf == NULL) {
1770
                    finished = TRUE;
1771
                    break;
1772
                }
1773
                /* allocate a scratch copy of the current frame */
1774
                frame->width = gdk_pixbuf_get_width(pixbuf);
1775
                frame->height = gdk_pixbuf_get_height(pixbuf);
1776
                stride = gdk_pixbuf_get_rowstride(pixbuf);
1777
                frame->pixels = sixel_allocator_malloc(
1778
                    pchunk->allocator,
1779
                    (size_t)(frame->height * stride));
1780
                if (frame->pixels == NULL) {
1781
                    sixel_helper_set_additional_message(
1782
                        "load_with_gdkpixbuf: sixel_allocator_malloc() failed.");
1783
                    status = SIXEL_BAD_ALLOCATION;
1784
                    goto end;
1785
                }
1786
                if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1787
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
1788
                    depth = 4;
1789
                } else {
1790
                    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
1791
                    depth = 3;
1792
                }
1793
                p = gdk_pixbuf_get_pixels(pixbuf);
1794
                if (stride == frame->width * depth) {
1795
                    memcpy(frame->pixels, p,
1796
                           (size_t)(frame->height * stride));
1797
                } else {
1798
                    for (i = 0; i < frame->height; ++i) {
1799
                        memcpy(frame->pixels + frame->width * depth * i,
1800
                               p + stride * i,
1801
                               (size_t)(frame->width * depth));
1802
                    }
1803
                }
1804
                delay_ms = gdk_pixbuf_animation_iter_get_delay_time(it);
1805
                if (delay_ms < 0) {
1806
                    delay_ms = 0;
1807
                }
1808
                /* advance the synthetic clock before asking gdk to move forward */
1809
                g_time_val_add(&time_val, delay_ms * 1000);
1810
                frame->delay = delay_ms / 10;
1811
                frame->multiframe = 1;
1812

1813
                if (!gdk_pixbuf_animation_iter_advance(it, &time_val)) {
1814
                    finished = TRUE;
1815
                }
1816
                status = fn_load(frame, context);
1817
                if (status != SIXEL_OK) {
1818
                    goto end;
1819
                }
1820
                /* release scratch pixels before decoding the next frame */
1821
                sixel_allocator_free(pchunk->allocator, frame->pixels);
1822
                frame->pixels = NULL;
1823
                frame->frame_no++;
1824

1825
                if (finished) {
1826
                    break;
1827
                }
1828
                /* }}} */
1829
            }
1830

1831
            if (frame->frame_no == 0) {
1832
                break;
1833
            }
1834

1835
            /* finished processing one full loop */
1836
            ++frame->loop_count;
1837

1838
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
1839
                break;
1840
            }
1841
            if (loop_control == SIXEL_LOOP_AUTO) {
1842
                /* obey header-provided loop count when AUTO */
1843
                if (anim_loop_count >= 0 &&
1844
                    frame->loop_count >= anim_loop_count) {
1845
                    break;
1846
                }
1847
            } else if (loop_control != SIXEL_LOOP_FORCE &&
1848
                       anim_loop_count > 0 &&
1849
                       frame->loop_count >= anim_loop_count) {
1850
                break;
1851
            }
1852

1853
            /* restart iteration from the beginning for the next pass */
1854
            g_object_unref(it);
1855
            time_val = start_time;
1856
            it = gdk_pixbuf_animation_get_iter(animation, &time_val);
1857
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
1858
# pragma GCC diagnostic pop
1859
#endif  /* HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS */
1860
            if (it == NULL) {
1861
                status = SIXEL_GDK_ERROR;
1862
                goto end;
1863
            }
1864
            /* next pass starts counting frames from zero again */
1865
            frame->frame_no = 0;
1866
        }
1867
    }
1868

1869
    status = SIXEL_OK;
1870

1871
end:
1872
    if (frame) {
1873
        /* drop the reference we obtained from sixel_frame_new() */
1874
        sixel_frame_unref(frame);
1875
    }
1876
    if (it) {
1877
        g_object_unref(it);
1878
    }
1879
    if (loader) {
1880
        if (!loader_closed) {
1881
            /* ensure the incremental loader is finalized even on error paths */
1882
            gdk_pixbuf_loader_close(loader, NULL);
1883
        }
1884
        g_object_unref(loader);
1885
    }
1886

1887
    return status;
1888

1889
}
1890
#endif  /* HAVE_GDK_PIXBUF2 */
1891

1892
#if HAVE_COREGRAPHICS
1893
static SIXELSTATUS
1894
load_with_coregraphics(
149✔
1895
    sixel_chunk_t const       /* in */     *pchunk,
1896
    int                       /* in */     fstatic,
1897
    int                       /* in */     fuse_palette,
1898
    int                       /* in */     reqcolors,
1899
    unsigned char             /* in */     *bgcolor,
1900
    int                       /* in */     loop_control,
1901
    sixel_load_image_function /* in */     fn_load,
1902
    void                      /* in/out */ *context)
1903
{
1904
    SIXELSTATUS status = SIXEL_FALSE;
149✔
1905
    sixel_frame_t *frame = NULL;
149✔
1906
    CFDataRef data = NULL;
149✔
1907
    CGImageSourceRef source = NULL;
149✔
1908
    CGImageRef image = NULL;
149✔
1909
    CGColorSpaceRef color_space = NULL;
149✔
1910
    CGContextRef ctx = NULL;
149✔
1911
    size_t stride;
1912
    size_t frame_count;
1913
    int anim_loop_count = (-1);
149✔
1914
    CFDictionaryRef props = NULL;
149✔
1915
    CFDictionaryRef anim_dict;
1916
    CFNumberRef loop_num;
1917
    CFDictionaryRef frame_props;
1918
    CFDictionaryRef frame_anim_dict;
1919
    CFNumberRef delay_num;
1920
    double delay_sec;
1921
    size_t i;
1922

1923
    (void) fuse_palette;
149✔
1924
    (void) reqcolors;
149✔
1925
    (void) bgcolor;
149✔
1926

1927
    status = sixel_frame_new(&frame, pchunk->allocator);
149✔
1928
    if (SIXEL_FAILED(status)) {
149!
1929
        goto end;
1930
    }
1931

1932
    data = CFDataCreate(kCFAllocatorDefault,
298✔
1933
                        pchunk->buffer,
149✔
1934
                        (CFIndex)pchunk->size);
149✔
1935
    if (! data) {
149!
1936
        status = SIXEL_FALSE;
1937
        goto end;
1938
    }
1939

1940
    source = CGImageSourceCreateWithData(data, NULL);
149✔
1941
    if (! source) {
149!
1942
        status = SIXEL_FALSE;
1943
        goto end;
1944
    }
1945

1946
    frame_count = CGImageSourceGetCount(source);
149✔
1947
    if (! frame_count) {
149✔
1948
        status = SIXEL_FALSE;
55✔
1949
        goto end;
55✔
1950
    }
1951
    if (fstatic) {
94✔
1952
        frame_count = 1;
8✔
1953
    }
8✔
1954

1955
    props = CGImageSourceCopyProperties(source, NULL);
94✔
1956
    if (props) {
94✔
1957
        anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
94✔
1958
            props, kCGImagePropertyGIFDictionary);
94✔
1959
        if (anim_dict) {
94✔
1960
            loop_num = (CFNumberRef)CFDictionaryGetValue(
5✔
1961
                anim_dict, kCGImagePropertyGIFLoopCount);
5✔
1962
            if (loop_num) {
5✔
1963
                CFNumberGetValue(loop_num, kCFNumberIntType, &anim_loop_count);
5✔
1964
            }
5✔
1965
        }
5✔
1966
        CFRelease(props);
94✔
1967
    }
94✔
1968

1969
    color_space = CGColorSpaceCreateDeviceRGB();
94✔
1970
    if (! color_space) {
94!
1971
        status = SIXEL_FALSE;
1972
        goto end;
1973
    }
1974

1975
    frame->loop_count = 0;
94✔
1976

1977
    for (;;) {
94✔
1978
        frame->frame_no = 0;
96✔
1979
        for (i = 0; i < frame_count; ++i) {
229✔
1980
            delay_sec = 0.0;
136✔
1981
            frame_props = CGImageSourceCopyPropertiesAtIndex(
136✔
1982
                source, (CFIndex)i, NULL);
136✔
1983
            if (frame_props) {
136✔
1984
                frame_anim_dict = (CFDictionaryRef)CFDictionaryGetValue(
136✔
1985
                    frame_props, kCGImagePropertyGIFDictionary);
136✔
1986
                if (frame_anim_dict) {
136✔
1987
                    delay_num = (CFNumberRef)CFDictionaryGetValue(
47✔
1988
                        frame_anim_dict, kCGImagePropertyGIFUnclampedDelayTime);
47✔
1989
                    if (! delay_num) {
47✔
1990
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
1991
                            frame_anim_dict, kCGImagePropertyGIFDelayTime);
1992
                    }
1993
                    if (delay_num) {
47✔
1994
                        CFNumberGetValue(delay_num,
47✔
1995
                                         kCFNumberDoubleType,
1996
                                         &delay_sec);
1997
                    }
47✔
1998
                }
47✔
1999
#if defined(kCGImagePropertyPNGDictionary) && \
2000
    defined(kCGImagePropertyAPNGUnclampedDelayTime) && \
2001
    defined(kCGImagePropertyAPNGDelayTime)
2002
                if (delay_sec <= 0.0) {
2003
                    CFDictionaryRef png_frame = (CFDictionaryRef)CFDictionaryGetValue(
2004
                        frame_props, kCGImagePropertyPNGDictionary);
2005
                    if (png_frame) {
2006
                        delay_num = (CFNumberRef)CFDictionaryGetValue(
2007
                            png_frame, kCGImagePropertyAPNGUnclampedDelayTime);
2008
                        if (! delay_num) {
2009
                            delay_num = (CFNumberRef)CFDictionaryGetValue(
2010
                                png_frame, kCGImagePropertyAPNGDelayTime);
2011
                        }
2012
                        if (delay_num) {
2013
                            CFNumberGetValue(delay_num,
2014
                                             kCFNumberDoubleType,
2015
                                             &delay_sec);
2016
                        }
2017
                    }
2018
                }
2019
#endif
2020
                CFRelease(frame_props);
136✔
2021
            }
136✔
2022
            if (delay_sec <= 0.0) {
182✔
2023
                delay_sec = 0.1;
90✔
2024
            }
90✔
2025
            frame->delay = (int)(delay_sec * 100.0 + 0.5);
180✔
2026
            if (frame->delay < 1) {
180✔
2027
                frame->delay = 1;
2028
            }
2029

2030
            image = CGImageSourceCreateImageAtIndex(source, (CFIndex)i, NULL);
136✔
2031
            if (! image) {
136!
2032
                status = SIXEL_FALSE;
2033
                goto end;
2034
            }
2035

2036
            frame->width = (int)CGImageGetWidth(image);
136✔
2037
            frame->height = (int)CGImageGetHeight(image);
136✔
2038
            frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
136✔
2039
            stride = (size_t)frame->width * 4;
136✔
2040
            if (frame->pixels != NULL) {
136✔
2041
                sixel_allocator_free(pchunk->allocator, frame->pixels);
42✔
2042
            }
42✔
2043
            frame->pixels = sixel_allocator_malloc(
136✔
2044
                pchunk->allocator, (size_t)(frame->height * stride));
136✔
2045

2046
            if (frame->pixels == NULL) {
136!
2047
                sixel_helper_set_additional_message(
2048
                    "load_with_coregraphics: sixel_allocator_malloc() failed.");
2049
                status = SIXEL_BAD_ALLOCATION;
2050
                CGImageRelease(image);
2051
                goto end;
2052
            }
2053

2054
            ctx = CGBitmapContextCreate(frame->pixels,
272✔
2055
                                        frame->width,
136✔
2056
                                        frame->height,
136✔
2057
                                        8,
2058
                                        stride,
136✔
2059
                                        color_space,
136✔
2060
                                        kCGImageAlphaPremultipliedLast |
2061
                                            kCGBitmapByteOrder32Big);
2062
            if (!ctx) {
136!
2063
                CGImageRelease(image);
2064
                goto end;
2065
            }
2066

2067
            CGContextDrawImage(ctx,
272✔
2068
                               CGRectMake(0, 0, frame->width, frame->height),
136✔
2069
                               image);
136✔
2070
            CGContextRelease(ctx);
136✔
2071
            ctx = NULL;
136✔
2072

2073
            frame->multiframe = (frame_count > 1);
136✔
2074
            status = fn_load(frame, context);
136✔
2075
            CGImageRelease(image);
136✔
2076
            image = NULL;
136✔
2077
            if (status != SIXEL_OK) {
136✔
2078
                goto end;
3✔
2079
            }
2080
            ++frame->frame_no;
133✔
2081
        }
133✔
2082

2083
        ++frame->loop_count;
93✔
2084

2085
        if (frame_count <= 1) {
93✔
2086
            break;
88✔
2087
        }
2088
        if (loop_control == SIXEL_LOOP_DISABLE) {
5✔
2089
            break;
2✔
2090
        }
2091
        if (loop_control == SIXEL_LOOP_AUTO) {
3!
2092
            if (anim_loop_count < 0) {
3!
2093
                break;
2094
            }
2095
            if (anim_loop_count > 0 && frame->loop_count >= anim_loop_count) {
3✔
2096
                break;
1✔
2097
            }
2098
            continue;
2✔
2099
        }
2100
    }
2101

2102
    status = SIXEL_OK;
91✔
2103

2104
end:
2105
    if (ctx) {
149✔
2106
        CGContextRelease(ctx);
2107
    }
2108
    if (color_space) {
94✔
2109
        CGColorSpaceRelease(color_space);
94✔
2110
    }
94✔
2111
    if (image) {
188✔
2112
        CGImageRelease(image);
2113
    }
2114
    if (source) {
149✔
2115
        CFRelease(source);
149✔
2116
    }
149✔
2117
    if (data) {
149✔
2118
        CFRelease(data);
149✔
2119
    }
149✔
2120
    if (frame) {
149✔
2121
        sixel_frame_unref(frame);
149✔
2122
    }
149✔
2123
    return status;
149✔
2124
}
2125
#endif  /* HAVE_COREGRAPHICS */
2126

2127
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
2128
static SIXELSTATUS
2129
load_with_quicklook(
58✔
2130
    sixel_chunk_t const       /* in */     *pchunk,
2131
    int                       /* in */     fstatic,
2132
    int                       /* in */     fuse_palette,
2133
    int                       /* in */     reqcolors,
2134
    unsigned char             /* in */     *bgcolor,
2135
    int                       /* in */     loop_control,
2136
    sixel_load_image_function /* in */     fn_load,
2137
    void                      /* in/out */ *context)
2138
{
2139
    SIXELSTATUS status = SIXEL_FALSE;
58✔
2140
    sixel_frame_t *frame = NULL;
58✔
2141
    CFStringRef path = NULL;
58✔
2142
    CFURLRef url = NULL;
58✔
2143
    CGImageRef image = NULL;
58✔
2144
    CGColorSpaceRef color_space = NULL;
58✔
2145
    CGContextRef ctx = NULL;
58✔
2146
    CGRect bounds;
2147
    size_t stride;
2148
    unsigned char fill_color[3];
2149
    CGFloat fill_r;
2150
    CGFloat fill_g;
2151
    CGFloat fill_b;
2152
    CGFloat max_dimension;
2153
    CGSize max_size;
2154

2155
    (void)fstatic;
58✔
2156
    (void)fuse_palette;
58✔
2157
    (void)reqcolors;
58✔
2158
    (void)loop_control;
58✔
2159

2160
    if (pchunk == NULL || pchunk->source_path == NULL) {
58✔
2161
        goto end;
43✔
2162
    }
2163

2164
    status = sixel_frame_new(&frame, pchunk->allocator);
15✔
2165
    if (SIXEL_FAILED(status)) {
15!
2166
        goto end;
2167
    }
2168

2169
    path = CFStringCreateWithCString(kCFAllocatorDefault,
30✔
2170
                                     pchunk->source_path,
15✔
2171
                                     kCFStringEncodingUTF8);
2172
    if (path == NULL) {
15!
2173
        status = SIXEL_RUNTIME_ERROR;
2174
        goto end;
2175
    }
2176

2177
    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
30✔
2178
                                        path,
15✔
2179
                                        kCFURLPOSIXPathStyle,
2180
                                        false);
2181
    if (url == NULL) {
15!
2182
        status = SIXEL_RUNTIME_ERROR;
2183
        goto end;
2184
    }
2185

2186
    if (thumbnailer_size_hint > 0) {
15!
2187
        max_dimension = (CGFloat)thumbnailer_size_hint;
15✔
2188
    } else {
15✔
2189
        max_dimension = (CGFloat)SIXEL_THUMBNAILER_DEFAULT_SIZE;
2190
    }
2191
    max_size.width = max_dimension;
15✔
2192
    max_size.height = max_dimension;
15✔
2193
#if HAVE_QUICKLOOK_THUMBNAILING
2194
    image = sixel_quicklook_thumbnail_create(url, max_size);
15✔
2195
    if (image == NULL) {
15✔
2196
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2197
#  pragma clang diagnostic push
2198
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
2199
# endif
2200
        image = QLThumbnailImageCreate(kCFAllocatorDefault,
24✔
2201
                                       url,
12✔
2202
                                       max_size,
2203
                                       NULL);
2204
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2205
#  pragma clang diagnostic pop
2206
# endif
2207
    }
12✔
2208
#else
2209
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2210
#  pragma clang diagnostic push
2211
#  pragma clang diagnostic ignored "-Wdeprecated-declarations"
2212
# endif
2213
    image = QLThumbnailImageCreate(kCFAllocatorDefault,
2214
                                   url,
2215
                                   max_size,
2216
                                   NULL);
2217
# if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
2218
#  pragma clang diagnostic pop
2219
# endif
2220
#endif
2221
    if (image == NULL) {
15✔
2222
        status = SIXEL_RUNTIME_ERROR;
12✔
2223
        sixel_helper_set_additional_message(
12✔
2224
            "load_with_quicklook: CQLThumbnailImageCreate() failed.");
2225
        goto end;
12✔
2226
    }
2227

2228
    color_space = CGColorSpaceCreateDeviceRGB();
3✔
2229
    if (color_space == NULL) {
3!
2230
        status = SIXEL_RUNTIME_ERROR;
2231
        sixel_helper_set_additional_message(
2232
            "load_with_quicklook: CGColorSpaceCreateDeviceRGB() failed.");
2233
        goto end;
2234
    }
2235

2236
    frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
3✔
2237
    frame->width = (int)CGImageGetWidth(image);
3✔
2238
    frame->height = (int)CGImageGetHeight(image);
3✔
2239
    if (frame->width <= 0 || frame->height <= 0) {
3✔
2240
        status = SIXEL_RUNTIME_ERROR;
2241
        sixel_helper_set_additional_message(
2242
            "load_with_quicklook: invalid image size detected.");
2243
        goto end;
2244
    }
2245

2246
    stride = (size_t)frame->width * 4;
3✔
2247
    frame->pixels =
3✔
2248
        sixel_allocator_malloc(pchunk->allocator,
6✔
2249
                               (size_t)frame->height * stride);
3✔
2250
    if (frame->pixels == NULL) {
3!
2251
        sixel_helper_set_additional_message(
2252
            "load_with_quicklook: sixel_allocator_malloc() failed.");
2253
        status = SIXEL_BAD_ALLOCATION;
2254
        goto end;
2255
    }
2256

2257
    if (bgcolor != NULL) {
3!
2258
        fill_color[0] = bgcolor[0];
2259
        fill_color[1] = bgcolor[1];
2260
        fill_color[2] = bgcolor[2];
2261
    } else {
2262
        fill_color[0] = 255;
3✔
2263
        fill_color[1] = 255;
3✔
2264
        fill_color[2] = 255;
3✔
2265
    }
2266

2267
    ctx = CGBitmapContextCreate(frame->pixels,
6✔
2268
                                frame->width,
3✔
2269
                                frame->height,
3✔
2270
                                8,
2271
                                stride,
3✔
2272
                                color_space,
3✔
2273
                                kCGImageAlphaPremultipliedLast |
2274
                                    kCGBitmapByteOrder32Big);
2275
    if (ctx == NULL) {
3!
2276
        status = SIXEL_RUNTIME_ERROR;
2277
        sixel_helper_set_additional_message(
2278
            "load_with_quicklook: CGBitmapContextCreate() failed.");
2279
        goto end;
2280
    }
2281

2282
    bounds = CGRectMake(0,
3✔
2283
                        0,
2284
                        (CGFloat)frame->width,
3✔
2285
                        (CGFloat)frame->height);
3✔
2286
    fill_r = (CGFloat)fill_color[0] / 255.0f;
3✔
2287
    fill_g = (CGFloat)fill_color[1] / 255.0f;
3✔
2288
    fill_b = (CGFloat)fill_color[2] / 255.0f;
3✔
2289
    CGContextSetRGBFillColor(ctx, fill_r, fill_g, fill_b, 1.0f);
3✔
2290
    CGContextFillRect(ctx, bounds);
3✔
2291
    CGContextDrawImage(ctx, bounds, image);
3✔
2292
    CGContextFlush(ctx);
3✔
2293

2294
    /* Abort when Quick Look produced no visible pixels so other loaders run. */
2295
    {
2296
        size_t pixel_count;
2297
        size_t index;
2298
        unsigned char *pixel;
2299
        int has_content;
2300

2301
        pixel_count = (size_t)frame->width * (size_t)frame->height;
3✔
2302
        pixel = frame->pixels;
3✔
2303
        has_content = 0;
3✔
2304
        for (index = 0; index < pixel_count; ++index) {
3✔
2305
            if (pixel[0] != fill_color[0] ||
3✔
2306
                    pixel[1] != fill_color[1] ||
2307
                    pixel[2] != fill_color[2] ||
2308
                    pixel[3] != 0xff) {
2309
                has_content = 1;
3✔
2310
                break;
3✔
2311
            }
2312
            pixel += 4;
2313
        }
2314
        if (! has_content) {
3!
2315
            sixel_helper_set_additional_message(
2316
                "load_with_quicklook: thumbnail contained no visible pixels.");
2317
            status = SIXEL_BAD_INPUT;
2318
            CGContextRelease(ctx);
2319
            ctx = NULL;
2320
            goto end;
2321
        }
2322
    }
2323

2324
    CGContextRelease(ctx);
3✔
2325
    ctx = NULL;
3✔
2326

2327
    frame->delay = 0;
3✔
2328
    frame->frame_no = 0;
3✔
2329
    frame->loop_count = 1;
3✔
2330
    frame->multiframe = 0;
3✔
2331
    frame->transparent = (-1);
3✔
2332

2333
    status = sixel_frame_strip_alpha(frame, fill_color);
3✔
2334
    if (SIXEL_FAILED(status)) {
3!
2335
        goto end;
2336
    }
2337

2338
    status = fn_load(frame, context);
3✔
2339
    if (status != SIXEL_OK) {
3!
2340
        goto end;
2341
    }
2342

2343
    status = SIXEL_OK;
3✔
2344

2345
end:
2346
    if (ctx != NULL) {
58✔
2347
        CGContextRelease(ctx);
2348
    }
2349
    if (color_space != NULL) {
3✔
2350
        CGColorSpaceRelease(color_space);
3✔
2351
    }
3✔
2352
    if (image != NULL) {
3✔
2353
        CGImageRelease(image);
3✔
2354
    }
3✔
2355
    if (url != NULL) {
15✔
2356
        CFRelease(url);
15✔
2357
    }
15✔
2358
    if (path != NULL) {
15✔
2359
        CFRelease(path);
15✔
2360
    }
15✔
2361
    if (frame != NULL) {
15✔
2362
        sixel_frame_unref(frame);
15✔
2363
    }
15✔
2364

2365
    return status;
58✔
2366
}
2367
#endif  /* HAVE_COREGRAPHICS && HAVE_QUICKLOOK */
2368

2369
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
2370

2371
# if defined(HAVE_NANOSLEEP)
2372
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
2373
# endif
2374
# if defined(HAVE_REALPATH)
2375
char * realpath(const char *restrict path, char *restrict resolved_path);
2376
# endif
2377
# if defined(HAVE_MKSTEMP)
2378
int mkstemp(char *);
2379
# endif
2380

2381
static int
2382
thumbnailer_format_append_char(char *buffer,
204✔
2383
                               size_t capacity,
2384
                               size_t *position,
2385
                               char ch,
2386
                               int *truncated)
2387
{
2388
    if (buffer == NULL || position == NULL) {
204!
2389
        return 0;
×
2390
    }
2391

2392
    if (*position + 1 < capacity) {
204!
2393
        buffer[*position] = ch;
204✔
2394
        *position += 1;
204✔
2395
        buffer[*position] = '\0';
204✔
2396
    } else {
68✔
2397
        if (capacity > 0) {
×
2398
            buffer[capacity - 1] = '\0';
×
2399
        }
2400
        if (truncated != NULL) {
×
2401
            *truncated = 1;
×
2402
        }
2403
    }
2404

2405
    return 1;
204✔
2406
}
68✔
2407

2408
static int
2409
thumbnailer_format_append_text(char *buffer,
18✔
2410
                               size_t capacity,
2411
                               size_t *position,
2412
                               char const *text,
2413
                               int *truncated)
2414
{
2415
    size_t length;
2416
    size_t copy_length;
2417

2418
    if (buffer == NULL || position == NULL) {
18!
2419
        return 0;
×
2420
    }
2421

2422
    if (text == NULL) {
18!
2423
        text = "(null)";
×
2424
    }
2425

2426
    length = strlen(text);
12✔
2427
    copy_length = length;
12✔
2428

2429
    if (*position + copy_length >= capacity) {
12!
2430
        if (capacity <= *position) {
×
2431
            copy_length = 0;
×
2432
        } else {
2433
            copy_length = capacity - 1 - *position;
×
2434
        }
2435
        if (truncated != NULL) {
×
2436
            *truncated = 1;
×
2437
        }
2438
    }
2439

2440
    if (copy_length > 0) {
18!
2441
        memcpy(buffer + *position, text, copy_length);
18✔
2442
        *position += copy_length;
18✔
2443
        buffer[*position] = '\0';
18✔
2444
    } else if (*position < capacity) {
6!
2445
        buffer[*position] = '\0';
×
2446
    } else if (capacity > 0) {
×
2447
        buffer[capacity - 1] = '\0';
×
2448
    }
2449

2450
    return 1;
18✔
2451
}
6✔
2452

2453
static int
2454
thumbnailer_format_append_decimal(char *buffer,
6✔
2455
                                  size_t capacity,
2456
                                  size_t *position,
2457
                                  int value,
2458
                                  int *truncated)
2459
{
2460
    char digits[16];
2461
    size_t index;
2462
    size_t length;
2463
    unsigned int magnitude;
2464
    int negative;
2465

2466
    if (buffer == NULL || position == NULL) {
6!
2467
        return 0;
×
2468
    }
2469

2470
    index = 0;
6✔
2471
    magnitude = 0;
6✔
2472
    negative = 0;
6✔
2473

2474
    if (value < 0) {
6!
2475
        negative = 1;
×
2476
        magnitude = (unsigned int)(-value);
×
2477
    } else {
2478
        magnitude = (unsigned int)value;
6✔
2479
    }
2480

2481
    do {
2✔
2482
        digits[index] = (char)('0' + (magnitude % 10));
14✔
2483
        index += 1;
14✔
2484
        magnitude /= 10;
14✔
2485
    } while (magnitude != 0);
14✔
2486

2487
    if (negative) {
6!
2488
        digits[index] = '-';
×
2489
        index += 1;
×
2490
    }
2491

2492
    length = index;
6✔
2493

2494
    while (index > 0) {
24✔
2495
        index -= 1;
18✔
2496
        if (!thumbnailer_format_append_char(buffer,
24!
2497
                                            capacity,
6✔
2498
                                            position,
6✔
2499
                                            digits[index],
18✔
2500
                                            truncated)) {
6✔
2501
            return 0;
×
2502
        }
2503
    }
2504

2505
    if (length == 0) {
6!
2506
        if (!thumbnailer_format_append_char(buffer,
×
2507
                                            capacity,
2508
                                            position,
2509
                                            '0',
2510
                                            truncated)) {
2511
            return 0;
×
2512
        }
2513
    }
2514

2515
    return 1;
6✔
2516
}
2✔
2517

2518
static int
2519
thumbnailer_safe_format(char *buffer,
18✔
2520
                        size_t capacity,
2521
                        char const *format,
2522
                        ...)
2523
{
2524
    va_list args;
2525
    size_t position;
2526
    char const *ptr;
2527
    int truncated;
2528
    char const *text;
2529
    int value;
2530

2531
    if (buffer == NULL || capacity == 0 || format == NULL) {
18!
2532
        return 0;
×
2533
    }
2534

2535
    position = 0;
18✔
2536
    truncated = 0;
18✔
2537
    buffer[0] = '\0';
18✔
2538

2539
    va_start(args, format);
18✔
2540
    ptr = format;
18✔
2541
    while (ptr != NULL && ptr[0] != '\0') {
228!
2542
        if (ptr[0] != '%') {
210✔
2543
            if (!thumbnailer_format_append_char(buffer,
248!
2544
                                                capacity,
62✔
2545
                                                &position,
2546
                                                ptr[0],
186✔
2547
                                                &truncated)) {
2548
                va_end(args);
×
2549
                return 0;
×
2550
            }
2551
            ptr += 1;
186✔
2552
            continue;
186✔
2553
        }
2554

2555
        ptr += 1;
24✔
2556
        if (ptr[0] == '%') {
24!
2557
            if (!thumbnailer_format_append_char(buffer,
×
2558
                                                capacity,
2559
                                                &position,
2560
                                                '%',
2561
                                                &truncated)) {
2562
                va_end(args);
×
2563
                return 0;
×
2564
            }
2565
            ptr += 1;
×
2566
            continue;
×
2567
        }
2568

2569
        if (ptr[0] == 's') {
24✔
2570
            text = va_arg(args, char const *);
18✔
2571
            if (!thumbnailer_format_append_text(buffer,
24!
2572
                                                capacity,
6✔
2573
                                                &position,
2574
                                                text,
6✔
2575
                                                &truncated)) {
2576
                va_end(args);
×
2577
                return 0;
×
2578
            }
2579
            ptr += 1;
18✔
2580
            continue;
18✔
2581
        }
2582

2583
        if (ptr[0] == 'd') {
6!
2584
            value = va_arg(args, int);
6✔
2585
            if (!thumbnailer_format_append_decimal(buffer,
8!
2586
                                                   capacity,
2✔
2587
                                                   &position,
2588
                                                   value,
2✔
2589
                                                   &truncated)) {
2590
                va_end(args);
×
2591
                return 0;
×
2592
            }
2593
            ptr += 1;
6✔
2594
            continue;
6✔
2595
        }
2596

2597
        va_end(args);
×
2598
        return 0;
×
2599
    }
2600
    va_end(args);
18✔
2601

2602
    if (truncated != 0 && capacity > 0) {
18!
2603
        buffer[capacity - 1] = '\0';
×
2604
    }
2605

2606
    return 1;
18✔
2607
}
6✔
2608

2609
/*
2610
 * thumbnailer_sleep_briefly
2611
 *
2612
 * Yield the CPU for a short duration so child polling loops avoid busy
2613
 * waiting.
2614
 *
2615
 */
2616
static void
2617
thumbnailer_sleep_briefly(void)
×
2618
{
2619
# if HAVE_NANOSLEEP
2620
    struct timespec ts;
2621
# endif
2622

2623
# if HAVE_NANOSLEEP
2624
    ts.tv_sec = 0;
×
2625
    ts.tv_nsec = 10000000L;
×
2626
    nanosleep(&ts, NULL);
×
2627
# elif defined(_WIN32)
2628
    Sleep(10);
2629
# else
2630
    (void)usleep(10000);
2631
# endif
2632
}
×
2633

2634
# if !defined(_WIN32) && defined(HAVE__REALPATH) && !defined(HAVE_REALPATH)
2635
static char *
2636
thumbnailer_resolve_without_realpath(char const *path)
2637
{
2638
    char *cwd;
2639
    char *resolved;
2640
    size_t cwd_length;
2641
    size_t path_length;
2642
    int need_separator;
2643

2644
    cwd = NULL;
2645
    resolved = NULL;
2646
    cwd_length = 0;
2647
    path_length = 0;
2648
    need_separator = 0;
2649

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

2654
    if (path[0] == '/') {
2655
        path_length = strlen(path);
2656
        resolved = malloc(path_length + 1);
2657
        if (resolved == NULL) {
2658
            return NULL;
2659
        }
2660
        memcpy(resolved, path, path_length + 1);
2661

2662
        return resolved;
2663
    }
2664

2665
#  if defined(PATH_MAX)
2666
    cwd = malloc(PATH_MAX);
2667
    if (cwd != NULL) {
2668
        if (getcwd(cwd, PATH_MAX) != NULL) {
2669
            cwd_length = strlen(cwd);
2670
            path_length = strlen(path);
2671
            need_separator = 0;
2672
            if (cwd_length > 0 && cwd[cwd_length - 1] != '/') {
2673
                need_separator = 1;
2674
            }
2675
            resolved = malloc(cwd_length + need_separator + path_length + 1);
2676
            if (resolved != NULL) {
2677
                memcpy(resolved, cwd, cwd_length);
2678
                if (need_separator != 0) {
2679
                    resolved[cwd_length] = '/';
2680
                }
2681
                memcpy(resolved + cwd_length + need_separator,
2682
                       path,
2683
                       path_length + 1);
2684
            }
2685
            free(cwd);
2686
            if (resolved != NULL) {
2687
                return resolved;
2688
            }
2689
        } else {
2690
            free(cwd);
2691
        }
2692
    }
2693
#  endif  /* PATH_MAX */
2694

2695
    path_length = strlen(path);
2696
    resolved = malloc(path_length + 1);
2697
    if (resolved == NULL) {
2698
        return NULL;
2699
    }
2700
    memcpy(resolved, path, path_length + 1);
2701

2702
    return resolved;
2703
}
2704
# endif  /* !defined(_WIN32) && defined(HAVE__REALPATH) && !defined(HAVE_REALPATH) */
2705

2706
/*
2707
 * thumbnailer_resolve_path
2708
 *
2709
 * Resolve the supplied path to an absolute canonical path when possible.
2710
 *
2711
 * Arguments:
2712
 *     path - original filesystem path.
2713
 * Returns:
2714
 *     Newly allocated canonical path or NULL on failure.
2715
 */
2716
static char *
2717
thumbnailer_resolve_path(char const *path)
6✔
2718
{
2719
    char *resolved;
2720

2721
    resolved = NULL;
6✔
2722

2723
    if (path == NULL) {
6!
2724
        return NULL;
×
2725
    }
2726

2727
# if defined(HAVE__FULLPATH)
2728
    resolved = _fullpath(NULL, path, 0);
2729
# elif defined(HAVE__REALPATH)
2730
    resolved = _realpath(path, NULL);
2731
# elif defined(HAVE_REALPATH)
2732
    resolved = realpath(path, NULL);
6✔
2733
# else
2734
    resolved = thumbnailer_resolve_without_realpath(path);
2735
# endif
2736

2737
    return resolved;
6✔
2738
}
2✔
2739

2740
struct thumbnailer_string_list {
2741
    char **items;
2742
    size_t length;
2743
    size_t capacity;
2744
};
2745

2746
struct thumbnailer_entry {
2747
    char *exec_line;
2748
    char *tryexec;
2749
    struct thumbnailer_string_list *mime_types;
2750
};
2751

2752
/*
2753
 * thumbnailer_strdup
2754
 *
2755
 * Duplicate a string with malloc so thumbnail helpers own their copies.
2756
 *
2757
 * Arguments:
2758
 *     src - zero-terminated string to copy; may be NULL.
2759
 * Returns:
2760
 *     Newly allocated duplicate or NULL on failure/NULL input.
2761
 */
2762
static char *
2763
thumbnailer_strdup(char const *src)
78✔
2764
{
2765
    char *copy;
2766
    size_t length;
2767

2768
    copy = NULL;
78✔
2769
    length = 0;
78✔
2770

2771
    if (src == NULL) {
78!
2772
        return NULL;
×
2773
    }
2774

2775
    length = strlen(src);
78✔
2776
    copy = malloc(length + 1);
78✔
2777
    if (copy == NULL) {
78!
2778
        return NULL;
×
2779
    }
2780
    memcpy(copy, src, length + 1);
78✔
2781

2782
    return copy;
78✔
2783
}
26✔
2784

2785
/*
2786
 * thumbnailer_string_list_new
2787
 *
2788
 * Allocate an empty expandable string list used throughout the loader.
2789
 *
2790
 * Arguments:
2791
 *     None.
2792
 * Returns:
2793
 *     Newly allocated list instance or NULL on failure.
2794
 */
2795
static struct thumbnailer_string_list *
2796
thumbnailer_string_list_new(void)
12✔
2797
{
2798
    struct thumbnailer_string_list *list;
2799

2800
    list = malloc(sizeof(*list));
12✔
2801
    if (list == NULL) {
12!
2802
        return NULL;
×
2803
    }
2804

2805
    list->items = NULL;
12✔
2806
    list->length = 0;
12✔
2807
    list->capacity = 0;
12✔
2808

2809
    return list;
12✔
2810
}
4✔
2811

2812
/*
2813
 * thumbnailer_string_list_free
2814
 *
2815
 * Release every string stored in the list and free the container itself.
2816
 *
2817
 * Arguments:
2818
 *     list - list instance produced by thumbnailer_string_list_new().
2819
 */
2820
static void
2821
thumbnailer_string_list_free(struct thumbnailer_string_list *list)
21✔
2822
{
2823
    size_t index;
2824

2825
    index = 0;
21✔
2826

2827
    if (list == NULL) {
21✔
2828
        return;
9✔
2829
    }
2830

2831
    if (list->items != NULL) {
12!
2832
        for (index = 0; index < list->length; ++index) {
54✔
2833
            free(list->items[index]);
42✔
2834
            list->items[index] = NULL;
42✔
2835
        }
14✔
2836
        free(list->items);
12✔
2837
        list->items = NULL;
12✔
2838
    }
4✔
2839

2840
    free(list);
12✔
2841
}
7✔
2842

2843
/*
2844
 * thumbnailer_string_list_append
2845
 *
2846
 * Append a copy of the supplied string to the dynamic list.
2847
 *
2848
 * Arguments:
2849
 *     list  - destination list.
2850
 *     value - string to duplicate and append.
2851
 * Returns:
2852
 *     1 on success, 0 on allocation failure or invalid input.
2853
 */
2854
static int
2855
thumbnailer_string_list_append(struct thumbnailer_string_list *list,
42✔
2856
                               char const *value)
2857
{
2858
    size_t new_capacity;
2859
    char **new_items;
2860
    char *copy;
2861

2862
    new_capacity = 0;
42✔
2863
    new_items = NULL;
42✔
2864
    copy = NULL;
42✔
2865

2866
    if (list == NULL || value == NULL) {
42!
2867
        return 0;
×
2868
    }
2869

2870
    copy = thumbnailer_strdup(value);
42✔
2871
    if (copy == NULL) {
42!
2872
        return 0;
×
2873
    }
2874

2875
    if (list->length == list->capacity) {
42✔
2876
        new_capacity = (list->capacity == 0) ? 4 : list->capacity * 2;
12!
2877
        new_items = realloc(list->items,
16✔
2878
                            new_capacity * sizeof(*list->items));
4✔
2879
        if (new_items == NULL) {
12!
2880
            free(copy);
×
2881
            return 0;
×
2882
        }
2883
        list->items = new_items;
12✔
2884
        list->capacity = new_capacity;
12✔
2885
    }
4✔
2886

2887
    list->items[list->length] = copy;
42✔
2888
    list->length += 1;
42✔
2889

2890
    return 1;
42✔
2891
}
14✔
2892

2893
/*
2894
 * thumbnailer_entry_init
2895
 *
2896
 * Prepare a thumbnailer_entry structure for population.
2897
 *
2898
 * Arguments:
2899
 *     entry - caller-provided structure to initialize.
2900
 */
2901
static void
2902
thumbnailer_entry_init(struct thumbnailer_entry *entry)
9✔
2903
{
2904
    if (entry == NULL) {
9!
2905
        return;
×
2906
    }
2907

2908
    entry->exec_line = NULL;
9✔
2909
    entry->tryexec = NULL;
9✔
2910
    entry->mime_types = NULL;
9✔
2911
}
3✔
2912

2913
/*
2914
 * thumbnailer_entry_clear
2915
 *
2916
 * Release every heap allocation associated with a thumbnailer_entry.
2917
 *
2918
 * Arguments:
2919
 *     entry - structure previously initialized with thumbnailer_entry_init().
2920
 */
2921
static void
2922
thumbnailer_entry_clear(struct thumbnailer_entry *entry)
9✔
2923
{
2924
    if (entry == NULL) {
9!
2925
        return;
×
2926
    }
2927

2928
    free(entry->exec_line);
9✔
2929
    entry->exec_line = NULL;
9✔
2930
    free(entry->tryexec);
9✔
2931
    entry->tryexec = NULL;
9✔
2932
    thumbnailer_string_list_free(entry->mime_types);
9✔
2933
    entry->mime_types = NULL;
9✔
2934
}
3✔
2935

2936
/*
2937
 * thumbnailer_join_paths
2938
 *
2939
 * Concatenate two path fragments inserting a slash when required.
2940
 *
2941
 * Arguments:
2942
 *     left  - directory prefix.
2943
 *     right - trailing component.
2944
 * Returns:
2945
 *     Newly allocated combined path or NULL on failure.
2946
 */
2947
static char *
2948
thumbnailer_join_paths(char const *left, char const *right)
24✔
2949
{
2950
    size_t left_length;
2951
    size_t right_length;
2952
    int need_separator;
2953
    char *combined;
2954

2955
    left_length = 0;
24✔
2956
    right_length = 0;
24✔
2957
    need_separator = 0;
24✔
2958
    combined = NULL;
24✔
2959

2960
    if (left == NULL || right == NULL) {
24!
2961
        return NULL;
×
2962
    }
2963

2964
    left_length = strlen(left);
24✔
2965
    right_length = strlen(right);
24✔
2966
    need_separator = 0;
24✔
2967

2968
    if (left_length > 0 && right_length > 0 &&
32!
2969
            left[left_length - 1] != '/' && right[0] != '/') {
24!
2970
        need_separator = 1;
24✔
2971
    }
8✔
2972

2973
    combined = malloc(left_length + right_length + need_separator + 1);
24✔
2974
    if (combined == NULL) {
24!
2975
        return NULL;
×
2976
    }
2977

2978
    memcpy(combined, left, left_length);
24✔
2979
    if (need_separator) {
24!
2980
        combined[left_length] = '/';
24✔
2981
        memcpy(combined + left_length + 1, right, right_length);
24✔
2982
        combined[left_length + right_length + 1] = '\0';
24✔
2983
    } else {
8✔
2984
        memcpy(combined + left_length, right, right_length);
×
2985
        combined[left_length + right_length] = '\0';
×
2986
    }
2987

2988
    return combined;
24✔
2989
}
8✔
2990

2991
/*
2992
 * thumbnailer_collect_directories
2993
 *
2994
 * Enumerate directories that may contain FreeDesktop thumbnailer
2995
 * definitions according to the XDG specification.
2996
 *
2997
 * GNOME thumbnailers follow the XDG data directory contract:
2998
 *
2999
 *     +------------------+      +---------------------------+
3000
 *     | HOME/.local/share| ---> | HOME/.local/share/        |
3001
 *     |                  |      |    thumbnailers/(*.thumbnailer)
3002
 *     +------------------+      +---------------------------+
3003
 *
3004
 *     +------------------+      +---------------------------+
3005
 *     | XDG_DATA_DIRS    | ---> | <dir>/thumbnailers/(*.thumbnailer)
3006
 *     +------------------+      +---------------------------+
3007
 *
3008
 * The helper below expands both sources so that the caller can iterate
3009
 * through every known definition in order of precedence.
3010
 *
3011
 * Arguments:
3012
 *     None.
3013
 * Returns:
3014
 *     Newly allocated list of directory paths or NULL on failure.
3015
 */
3016
static struct thumbnailer_string_list *
3017
thumbnailer_collect_directories(void)
6✔
3018
{
3019
    struct thumbnailer_string_list *dirs;
3020
    char const *xdg_data_dirs;
3021
    char const *home_dir;
3022
    char const *default_dirs;
3023
    char *candidate;
3024
    char *local_share;
3025
    char *dirs_copy;
3026
    char *token;
3027

3028
    dirs = NULL;
6✔
3029
    xdg_data_dirs = NULL;
6✔
3030
    home_dir = NULL;
6✔
3031
    default_dirs = NULL;
6✔
3032
    candidate = NULL;
6✔
3033
    local_share = NULL;
6✔
3034
    dirs_copy = NULL;
6✔
3035
    token = NULL;
6✔
3036

3037
    dirs = thumbnailer_string_list_new();
6✔
3038
    if (dirs == NULL) {
6!
3039
        return NULL;
×
3040
    }
3041

3042
    home_dir = getenv("HOME");
6✔
3043
    loader_trace_message(
10!
3044
        "thumbnailer_collect_directories: HOME=%s",
3045
        (home_dir != NULL && home_dir[0] != '\0') ? home_dir : "(unset)");
6!
3046
    if (home_dir != NULL && home_dir[0] != '\0') {
6!
3047
        local_share = thumbnailer_join_paths(home_dir,
6✔
3048
                                             ".local/share");
3049
        if (local_share != NULL) {
6!
3050
            candidate = thumbnailer_join_paths(local_share,
6✔
3051
                                               "thumbnailers");
3052
            if (candidate != NULL) {
6!
3053
                if (!thumbnailer_string_list_append(dirs, candidate)) {
6!
3054
                    free(candidate);
×
3055
                    free(local_share);
×
3056
                    thumbnailer_string_list_free(dirs);
×
3057
                    return NULL;
×
3058
                }
3059
                loader_trace_message(
6✔
3060
                    "thumbnailer_collect_directories: added %s",
3061
                    candidate);
2✔
3062
                free(candidate);
6✔
3063
                candidate = NULL;
6✔
3064
            }
2✔
3065
            free(local_share);
6✔
3066
            local_share = NULL;
6✔
3067
        }
2✔
3068
    }
2✔
3069

3070
    xdg_data_dirs = getenv("XDG_DATA_DIRS");
8✔
3071
    if (xdg_data_dirs == NULL || xdg_data_dirs[0] == '\0') {
8!
3072
        default_dirs = "/usr/local/share:/usr/share";
6✔
3073
        xdg_data_dirs = default_dirs;
6✔
3074
    }
2✔
3075
    loader_trace_message(
6✔
3076
        "thumbnailer_collect_directories: XDG_DATA_DIRS=%s",
3077
        xdg_data_dirs);
2✔
3078

3079
    dirs_copy = thumbnailer_strdup(xdg_data_dirs);
6✔
3080
    if (dirs_copy == NULL) {
6!
3081
        thumbnailer_string_list_free(dirs);
×
3082
        return NULL;
×
3083
    }
3084
    token = strtok(dirs_copy, ":");
6✔
3085
    while (token != NULL) {
18✔
3086
        candidate = thumbnailer_join_paths(token, "thumbnailers");
12✔
3087
        if (candidate != NULL) {
12!
3088
            if (!thumbnailer_string_list_append(dirs, candidate)) {
12!
3089
                free(candidate);
×
3090
                free(dirs_copy);
×
3091
                thumbnailer_string_list_free(dirs);
×
3092
                return NULL;
×
3093
            }
3094
            loader_trace_message(
12✔
3095
                "thumbnailer_collect_directories: added %s",
3096
                candidate);
4✔
3097
            free(candidate);
12✔
3098
            candidate = NULL;
12✔
3099
        }
4✔
3100
        token = strtok(NULL, ":");
12✔
3101
    }
3102
    free(dirs_copy);
6✔
3103
    dirs_copy = NULL;
6✔
3104

3105
    return dirs;
6✔
3106
}
2✔
3107

3108
/*
3109
 * thumbnailer_trim_right
3110
 *
3111
 * Remove trailing whitespace in place from a mutable string.
3112
 *
3113
 * Arguments:
3114
 *     text - string to trim; must be writable and zero-terminated.
3115
 */
3116
static void
3117
thumbnailer_trim_right(char *text)
6✔
3118
{
3119
    size_t length;
3120

3121
    length = 0;
6✔
3122

3123
    if (text == NULL) {
6!
3124
        return;
×
3125
    }
3126

3127
    length = strlen(text);
6✔
3128
    while (length > 0 && isspace((unsigned char)text[length - 1]) != 0) {
12!
3129
        text[length - 1] = '\0';
6✔
3130
        length -= 1;
6✔
3131
    }
3132
}
2✔
3133

3134
/*
3135
 * thumbnailer_trim_left
3136
 *
3137
 * Skip leading whitespace so parsers can focus on significant tokens.
3138
 *
3139
 * Arguments:
3140
 *     text - string to inspect; may be NULL.
3141
 * Returns:
3142
 *     Pointer to first non-space character or NULL when input is NULL.
3143
 */
3144
static char *
3145
thumbnailer_trim_left(char *text)
6✔
3146
{
3147
    if (text == NULL) {
6!
3148
        return NULL;
×
3149
    }
3150

3151
    while (*text != '\0' && isspace((unsigned char)*text) != 0) {
6!
3152
        text += 1;
×
3153
    }
3154

3155
    return text;
6✔
3156
}
2✔
3157

3158
/*
3159
 * thumbnailer_parse_file
3160
 *
3161
 * Populate a thumbnailer_entry by parsing a .thumbnailer ini file.
3162
 *
3163
 * Arguments:
3164
 *     path  - filesystem path to the ini file.
3165
 *     entry - output structure initialized with thumbnailer_entry_init().
3166
 * Returns:
3167
 *     1 on success, 0 on parse error or allocation failure.
3168
 */
3169
static int
3170
thumbnailer_parse_file(char const *path, struct thumbnailer_entry *entry)
×
3171
{
3172
    FILE *fp;
3173
    char line[1024];
3174
    int in_group;
3175
    char *trimmed;
3176
    char *key_end;
3177
    char *value;
3178
    char *token_start;
3179
    char *token_end;
3180
    struct thumbnailer_string_list *mime_types;
3181
    size_t index;
3182

3183
    fp = NULL;
×
3184
    in_group = 0;
×
3185
    trimmed = NULL;
×
3186
    key_end = NULL;
×
3187
    value = NULL;
×
3188
    token_start = NULL;
×
3189
    token_end = NULL;
×
3190
    mime_types = NULL;
×
3191
    index = 0;
×
3192

3193
    if (path == NULL || entry == NULL) {
×
3194
        return 0;
×
3195
    }
3196

3197
    fp = fopen(path, "r");
×
3198
    if (fp == NULL) {
×
3199
        return 0;
×
3200
    }
3201

3202
    mime_types = thumbnailer_string_list_new();
×
3203
    if (mime_types == NULL) {
×
3204
        fclose(fp);
×
3205
        fp = NULL;
×
3206
        return 0;
×
3207
    }
3208

3209
    while (fgets(line, sizeof(line), fp) != NULL) {
×
3210
        trimmed = thumbnailer_trim_left(line);
×
3211
        thumbnailer_trim_right(trimmed);
×
3212
        if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') {
×
3213
            continue;
×
3214
        }
3215
        if (trimmed[0] == '[') {
×
3216
            key_end = strchr(trimmed, ']');
×
3217
            if (key_end != NULL) {
×
3218
                *key_end = '\0';
×
3219
                if (strcmp(trimmed + 1, "Thumbnailer Entry") == 0) {
×
3220
                    in_group = 1;
×
3221
                } else {
3222
                    in_group = 0;
×
3223
                }
3224
            }
3225
            continue;
×
3226
        }
3227
        if (!in_group) {
×
3228
            continue;
×
3229
        }
3230
        key_end = strchr(trimmed, '=');
×
3231
        if (key_end == NULL) {
×
3232
            continue;
×
3233
        }
3234
        *key_end = '\0';
×
3235
        value = thumbnailer_trim_left(key_end + 1);
×
3236
        thumbnailer_trim_right(trimmed);
×
3237
        thumbnailer_trim_right(value);
×
3238
        if (strcmp(trimmed, "Exec") == 0) {
×
3239
            free(entry->exec_line);
×
3240
            entry->exec_line = thumbnailer_strdup(value);
×
3241
            if (entry->exec_line == NULL) {
×
3242
                fclose(fp);
×
3243
                fp = NULL;
×
3244
                thumbnailer_string_list_free(mime_types);
×
3245
                mime_types = NULL;
×
3246
                return 0;
×
3247
            }
3248
        } else if (strcmp(trimmed, "TryExec") == 0) {
×
3249
            free(entry->tryexec);
×
3250
            entry->tryexec = thumbnailer_strdup(value);
×
3251
            if (entry->tryexec == NULL) {
×
3252
                fclose(fp);
×
3253
                fp = NULL;
×
3254
                thumbnailer_string_list_free(mime_types);
×
3255
                mime_types = NULL;
×
3256
                return 0;
×
3257
            }
3258
        } else if (strcmp(trimmed, "MimeType") == 0) {
×
3259
            for (index = 0; index < mime_types->length; ++index) {
×
3260
                free(mime_types->items[index]);
×
3261
                mime_types->items[index] = NULL;
×
3262
            }
3263
            mime_types->length = 0;
×
3264
            token_start = value;
×
3265
            while (token_start != NULL && token_start[0] != '\0') {
×
3266
                token_end = strchr(token_start, ';');
×
3267
                if (token_end != NULL) {
×
3268
                    *token_end = '\0';
×
3269
                }
3270
                token_start = thumbnailer_trim_left(token_start);
×
3271
                thumbnailer_trim_right(token_start);
×
3272
                if (token_start[0] != '\0') {
×
3273
                    if (!thumbnailer_string_list_append(mime_types,
×
3274
                                                       token_start)) {
3275
                        fclose(fp);
×
3276
                        fp = NULL;
×
3277
                        thumbnailer_string_list_free(mime_types);
×
3278
                        mime_types = NULL;
×
3279
                        return 0;
×
3280
                    }
3281
                }
3282
                if (token_end == NULL) {
×
3283
                    break;
×
3284
                }
3285
                token_start = token_end + 1;
×
3286
            }
3287
        }
3288
    }
3289

3290
    fclose(fp);
×
3291
    fp = NULL;
×
3292

3293
    thumbnailer_string_list_free(entry->mime_types);
×
3294
    entry->mime_types = mime_types;
×
3295

3296
    return 1;
×
3297
}
3298

3299
/*
3300
 * thumbnailer_has_tryexec
3301
 *
3302
 * Confirm that the optional TryExec binary exists and is executable.
3303
 *
3304
 * Arguments:
3305
 *     tryexec - value from the .thumbnailer file; may be NULL.
3306
 * Returns:
3307
 *     1 when executable, 0 otherwise.
3308
 */
3309
static int
3310
thumbnailer_has_tryexec(char const *tryexec)
×
3311
{
3312
    char const *path_variable;
3313
    char const *start;
3314
    char const *end;
3315
    size_t length;
3316
    char *candidate;
3317
    int executable;
3318

3319
    path_variable = NULL;
×
3320
    start = NULL;
×
3321
    end = NULL;
×
3322
    length = 0;
×
3323
    candidate = NULL;
×
3324
    executable = 0;
×
3325

3326
    if (tryexec == NULL || tryexec[0] == '\0') {
×
3327
        return 1;
×
3328
    }
3329

3330
    if (strchr(tryexec, '/') != NULL) {
×
3331
        if (access(tryexec, X_OK) == 0) {
×
3332
            return 1;
×
3333
        }
3334
        return 0;
×
3335
    }
3336

3337
    path_variable = getenv("PATH");
×
3338
    if (path_variable == NULL) {
×
3339
        return 0;
×
3340
    }
3341

3342
    start = path_variable;
×
3343
    while (*start != '\0') {
×
3344
        end = strchr(start, ':');
×
3345
        if (end == NULL) {
×
3346
            end = start + strlen(start);
×
3347
        }
3348
        length = (size_t)(end - start);
×
3349
        candidate = malloc(length + strlen(tryexec) + 2);
×
3350
        if (candidate == NULL) {
×
3351
            return 0;
×
3352
        }
3353
        memcpy(candidate, start, length);
×
3354
        candidate[length] = '/';
×
3355
        strcpy(candidate + length + 1, tryexec);
×
3356
        if (access(candidate, X_OK) == 0) {
×
3357
            executable = 1;
×
3358
            free(candidate);
×
3359
            candidate = NULL;
×
3360
            break;
×
3361
        }
3362
        free(candidate);
×
3363
        candidate = NULL;
×
3364
        if (*end == '\0') {
×
3365
            break;
×
3366
        }
3367
        start = end + 1;
×
3368
    }
3369

3370
    return executable;
×
3371
}
3372

3373
/*
3374
 * thumbnailer_mime_matches
3375
 *
3376
 * Test whether a thumbnailer MIME pattern matches the probed MIME type.
3377
 *
3378
 * Arguments:
3379
 *     pattern   - literal MIME pattern or prefix ending with "slash-asterisk".
3380
 *     mime_type - MIME value obtained from file --mime-type.
3381
 * Returns:
3382
 *     1 when the pattern applies, 0 otherwise.
3383
 */
3384
static int
3385
thumbnailer_mime_matches(char const *pattern, char const *mime_type)
×
3386
{
3387
    size_t length;
3388

3389
    length = 0;
×
3390

3391
    if (pattern == NULL || mime_type == NULL) {
×
3392
        return 0;
×
3393
    }
3394

3395
    if (strcmp(pattern, mime_type) == 0) {
×
3396
        return 1;
×
3397
    }
3398

3399
    length = strlen(pattern);
×
3400
    if (length >= 2 && pattern[length - 1] == '*' &&
×
3401
            pattern[length - 2] == '/') {
×
3402
        return strncmp(pattern, mime_type, length - 1) == 0;
×
3403
    }
3404

3405
    return 0;
×
3406
}
3407

3408
/*
3409
 * thumbnailer_supports_mime
3410
 *
3411
 * Iterate over MIME patterns advertised by a thumbnailer entry.
3412
 *
3413
 * Arguments:
3414
 *     entry     - parsed thumbnailer entry with mime_types list.
3415
 *     mime_type - MIME type string to match.
3416
 * Returns:
3417
 *     1 when a match is found, 0 otherwise.
3418
 */
3419
static int
3420
thumbnailer_supports_mime(struct thumbnailer_entry *entry,
×
3421
                          char const *mime_type)
3422
{
3423
    size_t index;
3424

3425
    index = 0;
×
3426

3427
    if (entry == NULL || entry->mime_types == NULL) {
×
3428
        return 0;
×
3429
    }
3430

3431
    if (mime_type == NULL) {
×
3432
        return 0;
×
3433
    }
3434

3435
    for (index = 0; index < entry->mime_types->length; ++index) {
×
3436
        if (thumbnailer_mime_matches(entry->mime_types->items[index],
×
3437
                                     mime_type)) {
3438
            return 1;
×
3439
        }
3440
    }
3441

3442
    return 0;
×
3443
}
3444

3445
/*
3446
 * thumbnailer_shell_quote
3447
 *
3448
 * Produce a single-quoted variant of an argument for readable logging.
3449
 *
3450
 * Arguments:
3451
 *     text - unquoted argument.
3452
 * Returns:
3453
 *     Newly allocated quoted string or NULL on allocation failure.
3454
 */
3455
static char *
3456
thumbnailer_shell_quote(char const *text)
24✔
3457
{
3458
    size_t index;
3459
    size_t length;
3460
    size_t needed;
3461
    char *quoted;
3462
    size_t position;
3463

3464
    index = 0;
24✔
3465
    length = 0;
24✔
3466
    needed = 0;
24✔
3467
    quoted = NULL;
24✔
3468
    position = 0;
24✔
3469

3470
    if (text == NULL) {
24!
3471
        return NULL;
×
3472
    }
3473

3474
    length = strlen(text);
24✔
3475
    needed = 2;
24✔
3476
    for (index = 0; index < length; ++index) {
528✔
3477
        if (text[index] == '\'') {
504!
3478
            needed += 4;
×
3479
        } else {
3480
            needed += 1;
504✔
3481
        }
3482
    }
168✔
3483

3484
    quoted = malloc(needed + 1);
24✔
3485
    if (quoted == NULL) {
24!
3486
        return NULL;
×
3487
    }
3488

3489
    quoted[position++] = '\'';
24✔
3490
    for (index = 0; index < length; ++index) {
528✔
3491
        if (text[index] == '\'') {
504!
3492
            quoted[position++] = '\'';
×
3493
            quoted[position++] = '\\';
×
3494
            quoted[position++] = '\'';
×
3495
            quoted[position++] = '\'';
×
3496
        } else {
3497
            quoted[position++] = text[index];
504✔
3498
        }
3499
    }
168✔
3500
    quoted[position++] = '\'';
24✔
3501
    quoted[position] = '\0';
24✔
3502

3503
    return quoted;
24✔
3504
}
8✔
3505

3506
struct thumbnailer_builder {
3507
    char *buffer;
3508
    size_t length;
3509
    size_t capacity;
3510
};
3511

3512
/*
3513
 * thumbnailer_builder_reserve
3514
 *
3515
 * Grow the builder buffer so future appends fit without overflow.
3516
 *
3517
 * Arguments:
3518
 *     builder    - mutable builder instance.
3519
 *     additional - number of bytes that must fit excluding terminator.
3520
 * Returns:
3521
 *     1 on success, 0 on allocation failure.
3522
 */
3523
static int
3524
thumbnailer_builder_reserve(struct thumbnailer_builder *builder,
234✔
3525
                            size_t additional)
3526
{
3527
    size_t new_capacity;
3528
    char *new_buffer;
3529

3530
    new_capacity = 0;
234✔
3531
    new_buffer = NULL;
234✔
3532

3533
    if (builder->length + additional + 1 <= builder->capacity) {
234✔
3534
        return 1;
216✔
3535
    }
3536

3537
    new_capacity = (builder->capacity == 0) ? 64 : builder->capacity;
18✔
3538
    while (new_capacity < builder->length + additional + 1) {
24✔
3539
        new_capacity *= 2;
6✔
3540
    }
3541

3542
    new_buffer = realloc(builder->buffer, new_capacity);
18✔
3543
    if (new_buffer == NULL) {
18!
3544
        return 0;
×
3545
    }
3546

3547
    builder->buffer = new_buffer;
18✔
3548
    builder->capacity = new_capacity;
18✔
3549

3550
    return 1;
18✔
3551
}
78✔
3552

3553
/*
3554
 * thumbnailer_builder_append_char
3555
 *
3556
 * Append a single character to the builder.
3557
 *
3558
 * Arguments:
3559
 *     builder - mutable builder instance.
3560
 *     ch      - character to append.
3561
 * Returns:
3562
 *     1 on success, 0 on allocation failure.
3563
 */
3564
static int
3565
thumbnailer_builder_append_char(struct thumbnailer_builder *builder,
192✔
3566
                                char ch)
3567
{
3568
    if (!thumbnailer_builder_reserve(builder, 1)) {
192!
3569
        return 0;
×
3570
    }
3571

3572
    builder->buffer[builder->length] = ch;
192✔
3573
    builder->length += 1;
192✔
3574
    builder->buffer[builder->length] = '\0';
192✔
3575

3576
    return 1;
192✔
3577
}
64✔
3578

3579
/*
3580
 * thumbnailer_builder_append
3581
 *
3582
 * Append a string of known length to the builder buffer.
3583
 *
3584
 * Arguments:
3585
 *     builder - mutable builder instance.
3586
 *     text    - zero-terminated string to append.
3587
 * Returns:
3588
 *     1 on success, 0 on allocation failure or NULL input.
3589
 */
3590
static int
3591
thumbnailer_builder_append(struct thumbnailer_builder *builder,
42✔
3592
                           char const *text)
3593
{
3594
    size_t length;
3595

3596
    length = 0;
42✔
3597

3598
    if (text == NULL) {
42!
3599
        return 1;
×
3600
    }
3601

3602
    length = strlen(text);
42✔
3603
    if (!thumbnailer_builder_reserve(builder, length)) {
42!
3604
        return 0;
×
3605
    }
3606

3607
    memcpy(builder->buffer + builder->length, text, length);
42✔
3608
    builder->length += length;
42✔
3609
    builder->buffer[builder->length] = '\0';
42✔
3610

3611
    return 1;
42✔
3612
}
14✔
3613

3614
/*
3615
 * thumbnailer_builder_clear
3616
 *
3617
 * Reset builder length to zero while retaining allocated storage.
3618
 *
3619
 * Arguments:
3620
 *     builder - builder to reset.
3621
 */
3622
static void
3623
thumbnailer_builder_clear(struct thumbnailer_builder *builder)
18✔
3624
{
3625
    if (builder->buffer != NULL) {
18!
3626
        builder->buffer[0] = '\0';
18✔
3627
    }
6✔
3628
    builder->length = 0;
18✔
3629
}
18✔
3630

3631
/*
3632
 * thumbnailer_command owns the argv array that will be passed to the
3633
 * thumbnailer helper.  The display field keeps a human readable command line
3634
 * for verbose logging without recomputing the shell quoted form.
3635
 */
3636
struct thumbnailer_command {
3637
    char **argv;
3638
    size_t argc;
3639
    char *display;
3640
};
3641

3642
/*
3643
 * thumbnailer_command_free
3644
 *
3645
 * Release argv entries, the array itself, and the formatted display copy.
3646
 *
3647
 * Arguments:
3648
 *     command - structure created by thumbnailer_build_command().
3649
 */
3650
static void
3651
thumbnailer_command_free(struct thumbnailer_command *command)
6✔
3652
{
3653
    size_t index;
3654

3655
    if (command == NULL) {
6!
3656
        return;
×
3657
    }
3658

3659
    if (command->argv != NULL) {
6!
3660
        for (index = 0; index < command->argc; ++index) {
30✔
3661
            free(command->argv[index]);
24✔
3662
            command->argv[index] = NULL;
24✔
3663
        }
8✔
3664
        free(command->argv);
6✔
3665
        command->argv = NULL;
6✔
3666
    }
2✔
3667

3668
    free(command->display);
6✔
3669
    command->display = NULL;
6✔
3670

3671
    free(command);
6✔
3672
}
2✔
3673

3674
/*
3675
 * thumbnailer_command_format
3676
 *
3677
 * Join argv entries into a human-readable command line for logging.
3678
 *
3679
 * Arguments:
3680
 *     argv - array of argument strings.
3681
 *     argc - number of entries stored in argv.
3682
 * Returns:
3683
 *     Newly allocated formatted string or NULL on allocation failure.
3684
 */
3685
static char *
3686
thumbnailer_command_format(char **argv, size_t argc)
6✔
3687
{
3688
    struct thumbnailer_builder builder;
3689
    char *quoted;
3690
    size_t index;
3691

3692
    builder.buffer = NULL;
6✔
3693
    builder.length = 0;
6✔
3694
    builder.capacity = 0;
6✔
3695
    quoted = NULL;
6✔
3696

3697
    for (index = 0; index < argc; ++index) {
30✔
3698
        if (index > 0) {
24✔
3699
            if (!thumbnailer_builder_append_char(&builder, ' ')) {
18!
3700
                free(builder.buffer);
×
3701
                builder.buffer = NULL;
×
3702
                return NULL;
×
3703
            }
3704
        }
6✔
3705
        quoted = thumbnailer_shell_quote(argv[index]);
24✔
3706
        if (quoted == NULL) {
24!
3707
            free(builder.buffer);
×
3708
            builder.buffer = NULL;
×
3709
            return NULL;
×
3710
        }
3711
        if (!thumbnailer_builder_append(&builder, quoted)) {
24!
3712
            free(quoted);
×
3713
            quoted = NULL;
×
3714
            free(builder.buffer);
×
3715
            builder.buffer = NULL;
×
3716
            return NULL;
×
3717
        }
3718
        free(quoted);
24✔
3719
        quoted = NULL;
24✔
3720
    }
8✔
3721

3722
    return builder.buffer;
6✔
3723
}
2✔
3724

3725
/*
3726
 * thumbnailer_build_command
3727
 *
3728
 * Expand a .thumbnailer Exec template into an argv array that honours
3729
 * FreeDesktop substitution rules.
3730
 *
3731
 * Arguments:
3732
 *     template_command - Exec line containing % tokens.
3733
 *     input_path       - filesystem path to the source document.
3734
 *     input_uri        - URI representation for %u expansions.
3735
 *     output_path      - PNG destination path for %o expansions.
3736
 *     size             - numeric size hint passed to %s tokens.
3737
 *     mime_type        - MIME value for %m replacements.
3738
 * Returns:
3739
 *     Newly allocated command or NULL on parse/allocation failure.
3740
 */
3741
static struct thumbnailer_command *
3742
thumbnailer_build_command(char const *template_command,
6✔
3743
                          char const *input_path,
3744
                          char const *input_uri,
3745
                          char const *output_path,
3746
                          int size,
3747
                          char const *mime_type)
3748
{
3749
    struct thumbnailer_builder builder;
3750
    struct thumbnailer_string_list *tokens;
3751
    struct thumbnailer_command *command;
3752
    char const *ptr;
3753
    char size_text[16];
3754
    int in_single_quote;
3755
    int in_double_quote;
3756
    int escape_next;
3757
    char const *replacement;
3758
    size_t index;
3759

3760
    builder.buffer = NULL;
6✔
3761
    builder.length = 0;
6✔
3762
    builder.capacity = 0;
6✔
3763
    tokens = NULL;
6✔
3764
    command = NULL;
6✔
3765
    ptr = template_command;
6✔
3766
    size_text[0] = '\0';
6✔
3767
    in_single_quote = 0;
6✔
3768
    in_double_quote = 0;
6✔
3769
    escape_next = 0;
6✔
3770
    replacement = NULL;
6✔
3771
    index = 0;
6✔
3772

3773
    if (template_command == NULL) {
6!
3774
        return NULL;
×
3775
    }
3776

3777
    tokens = thumbnailer_string_list_new();
6✔
3778
    if (tokens == NULL) {
6!
3779
        return NULL;
×
3780
    }
3781

3782
    if (size > 0) {
6!
3783
        if (!thumbnailer_safe_format(size_text,
8!
3784
                                     sizeof(size_text),
3785
                                     "%d",
3786
                                     size)) {
2✔
3787
            goto error;
×
3788
        }
3789
    }
2✔
3790

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

3878
    if (builder.length > 0) {
6!
3879
        if (!thumbnailer_string_list_append(tokens, builder.buffer)) {
6!
3880
            goto error;
×
3881
        }
3882
    }
2✔
3883

3884
    command = malloc(sizeof(*command));
6✔
3885
    if (command == NULL) {
6!
3886
        goto error;
×
3887
    }
3888

3889
    command->argc = tokens->length;
6✔
3890
    command->argv = NULL;
6✔
3891
    command->display = NULL;
6✔
3892

3893
    if (tokens->length == 0) {
6!
3894
        goto error;
×
3895
    }
3896

3897
    command->argv = malloc(sizeof(char *) * (tokens->length + 1));
6✔
3898
    if (command->argv == NULL) {
6!
3899
        goto error;
×
3900
    }
3901

3902
    for (index = 0; index < tokens->length; ++index) {
30✔
3903
        command->argv[index] = thumbnailer_strdup(tokens->items[index]);
24✔
3904
        if (command->argv[index] == NULL) {
24!
3905
            goto error;
×
3906
        }
3907
    }
8✔
3908
    command->argv[tokens->length] = NULL;
6✔
3909

3910
    command->display = thumbnailer_command_format(command->argv,
8✔
3911
                                                  command->argc);
2✔
3912
    if (command->display == NULL) {
6!
3913
        goto error;
×
3914
    }
3915

3916
    thumbnailer_string_list_free(tokens);
6✔
3917
    tokens = NULL;
6✔
3918
    if (builder.buffer != NULL) {
6!
3919
        free(builder.buffer);
6✔
3920
        builder.buffer = NULL;
6✔
3921
    }
2✔
3922

3923
    return command;
6✔
3924

3925
error:
3926
    if (tokens != NULL) {
×
3927
        thumbnailer_string_list_free(tokens);
×
3928
        tokens = NULL;
×
3929
    }
3930
    if (builder.buffer != NULL) {
×
3931
        free(builder.buffer);
×
3932
        builder.buffer = NULL;
×
3933
    }
3934
    if (command != NULL) {
×
3935
        thumbnailer_command_free(command);
×
3936
        command = NULL;
×
3937
    }
3938

3939
    return NULL;
×
3940
}
2✔
3941

3942
/*
3943
 * thumbnailer_is_evince_thumbnailer
3944
 *
3945
 * Detect whether the selected thumbnailer maps to evince-thumbnailer so
3946
 * the stdout redirection workaround can be applied.
3947
 *
3948
 * Arguments:
3949
 *     exec_line - Exec string parsed from the .thumbnailer file.
3950
 *     tryexec   - optional TryExec value for additional matching.
3951
 * Returns:
3952
 *     1 when evince-thumbnailer is referenced, 0 otherwise.
3953
 */
3954
static int
3955
thumbnailer_is_evince_thumbnailer(char const *exec_line,
×
3956
                                  char const *tryexec)
3957
{
3958
    char const *needle;
3959
    char const *basename;
3960

3961
    needle = "evince-thumbnailer";
×
3962
    basename = NULL;
×
3963

3964
    if (exec_line != NULL && strstr(exec_line, needle) != NULL) {
×
3965
        return 1;
×
3966
    }
3967

3968
    if (tryexec != NULL) {
×
3969
        basename = strrchr(tryexec, '/');
×
3970
        if (basename != NULL) {
×
3971
            basename += 1;
×
3972
        } else {
3973
            basename = tryexec;
×
3974
        }
3975
        if (strcmp(basename, needle) == 0) {
×
3976
            return 1;
×
3977
        }
3978
        if (strstr(tryexec, needle) != NULL) {
×
3979
            return 1;
×
3980
        }
3981
    }
3982

3983
    return 0;
×
3984
}
3985

3986
/*
3987
 * thumbnailer_build_evince_command
3988
 *
3989
 * Construct an argv sequence that streams evince-thumbnailer output to
3990
 * stdout so downstream code can capture the PNG safely.
3991
 *
3992
 * Arguments:
3993
 *     input_path - source document path.
3994
 *     size       - numeric size hint forwarded to the -s option.
3995
 * Returns:
3996
 *     Newly allocated command or NULL on allocation failure.
3997
 */
3998
static struct thumbnailer_command *
3999
thumbnailer_build_evince_command(char const *input_path,
×
4000
                                 int size)
4001
{
4002
    struct thumbnailer_command *command;
4003
    char size_text[16];
4004
    size_t index;
4005

4006
    command = NULL;
×
4007
    index = 0;
×
4008

4009
    if (input_path == NULL) {
×
4010
        return NULL;
×
4011
    }
4012

4013
    command = malloc(sizeof(*command));
×
4014
    if (command == NULL) {
×
4015
        return NULL;
×
4016
    }
4017

4018
    command->argc = 5;
×
4019
    command->argv = malloc(sizeof(char *) * (command->argc + 1));
×
4020
    if (command->argv == NULL) {
×
4021
        thumbnailer_command_free(command);
×
4022
        return NULL;
×
4023
    }
4024

4025
    for (index = 0; index < command->argc + 1; ++index) {
×
4026
        command->argv[index] = NULL;
×
4027
    }
4028

4029
    if (!thumbnailer_safe_format(size_text,
×
4030
                                 sizeof(size_text),
4031
                                 "%d",
4032
                                 size)) {
4033
        size_text[0] = '\0';
×
4034
    }
4035

4036
    command->argv[0] = thumbnailer_strdup("evince-thumbnailer");
×
4037
    command->argv[1] = thumbnailer_strdup("-s");
×
4038
    command->argv[2] = thumbnailer_strdup(size_text);
×
4039
    command->argv[3] = thumbnailer_strdup(input_path);
×
4040
    command->argv[4] = thumbnailer_strdup("/dev/stdout");
×
4041
    command->argv[5] = NULL;
×
4042

4043
    for (index = 0; index < command->argc; ++index) {
×
4044
        if (command->argv[index] == NULL) {
×
4045
            thumbnailer_command_free(command);
×
4046
            return NULL;
×
4047
        }
4048
    }
4049

4050
    command->display = thumbnailer_command_format(command->argv,
×
4051
                                                  command->argc);
4052
    if (command->display == NULL) {
×
4053
        thumbnailer_command_free(command);
×
4054
        return NULL;
×
4055
    }
4056

4057
    return command;
×
4058
}
4059

4060
/*
4061
 * thumbnailer_build_file_uri
4062
 *
4063
 * Convert a filesystem path into a percent-encoded file:// URI.
4064
 *
4065
 * Arguments:
4066
 *     path - filesystem path; may be relative but will be resolved.
4067
 * Returns:
4068
 *     Newly allocated URI string or NULL on error.
4069
 */
4070
static char *
4071
thumbnailer_build_file_uri(char const *path)
6✔
4072
{
4073
    char *resolved;
4074
    size_t index;
4075
    size_t length;
4076
    size_t needed;
4077
    char *uri;
4078
    size_t position;
4079
    char const hex[] = "0123456789ABCDEF";
6✔
4080

4081
    resolved = NULL;
6✔
4082
    index = 0;
6✔
4083
    length = 0;
6✔
4084
    needed = 0;
6✔
4085
    uri = NULL;
6✔
4086
    position = 0;
6✔
4087

4088
    if (path == NULL) {
6!
4089
        return NULL;
×
4090
    }
4091

4092
    resolved = thumbnailer_resolve_path(path);
6✔
4093
    if (resolved == NULL) {
6!
4094
        return NULL;
×
4095
    }
4096

4097
    length = strlen(resolved);
6✔
4098
    needed = 7;
6✔
4099
    for (index = 0; index < length; ++index) {
338✔
4100
        unsigned char ch;
4101

4102
        ch = (unsigned char)resolved[index];
332✔
4103
        if (isalnum(ch) || ch == '-' || ch == '_' ||
350!
4104
                ch == '.' || ch == '~' || ch == '/') {
54!
4105
            needed += 1;
332✔
4106
        } else {
112✔
4107
            needed += 3;
×
4108
        }
4109
    }
112✔
4110

4111
    uri = malloc(needed + 1);
6✔
4112
    if (uri == NULL) {
6!
4113
        free(resolved);
×
4114
        resolved = NULL;
×
4115
        return NULL;
×
4116
    }
4117

4118
    memcpy(uri, "file://", 7);
6✔
4119
    position = 7;
6✔
4120
    for (index = 0; index < length; ++index) {
338✔
4121
        unsigned char ch;
4122

4123
        ch = (unsigned char)resolved[index];
332✔
4124
        if (isalnum(ch) || ch == '-' || ch == '_' ||
350!
4125
                ch == '.' || ch == '~' || ch == '/') {
54!
4126
            uri[position++] = (char)ch;
332✔
4127
        } else {
112✔
4128
            uri[position++] = '%';
×
4129
            uri[position++] = hex[(ch >> 4) & 0xF];
×
4130
            uri[position++] = hex[ch & 0xF];
×
4131
        }
4132
    }
112✔
4133
    uri[position] = '\0';
6✔
4134

4135
    free(resolved);
6✔
4136
    resolved = NULL;
6✔
4137

4138
    return uri;
6✔
4139
}
2✔
4140

4141
/*
4142
 * thumbnailer_guess_content_type
4143
 *
4144
 * Invoke the file(1) utility to obtain a MIME type for the input path.
4145
 *
4146
 * Arguments:
4147
 *     path - filesystem path passed to file --mime-type.
4148
 * Returns:
4149
 *     Newly allocated MIME type string or NULL on failure.
4150
 */
4151
static char *
4152
thumbnailer_guess_content_type(char const *path)
6✔
4153
{
4154
    int pipefd[2];
4155
    pid_t pid;
4156
    ssize_t bytes_read;
4157
    char buffer[256];
4158
    size_t total;
4159
    int status;
4160
    char *result;
4161
    char *trimmed;
4162

4163
    pipefd[0] = -1;
6✔
4164
    pipefd[1] = -1;
6✔
4165
    pid = (-1);
6✔
4166
    bytes_read = 0;
6✔
4167
    total = 0;
6✔
4168
    status = 0;
6✔
4169
    result = NULL;
6✔
4170
    trimmed = NULL;
6✔
4171

4172
    if (path == NULL) {
6!
4173
        return NULL;
×
4174
    }
4175

4176
    if (pipe(pipefd) < 0) {
6!
4177
        return NULL;
×
4178
    }
4179

4180
    pid = fork();
6✔
4181
    if (pid < 0) {
10!
4182
        close(pipefd[0]);
×
4183
        close(pipefd[1]);
×
4184
        return NULL;
×
4185
    }
4186

4187
    if (pid == 0) {
12✔
4188
        char const *argv[5];
4189

4190
        close(pipefd[0]);
6✔
4191
        if (dup2(pipefd[1], STDOUT_FILENO) < 0) {
6!
4192
            _exit(127);
×
4193
        }
4194
        close(pipefd[1]);
6✔
4195
        argv[0] = "file";
6✔
4196
        argv[1] = "-b";
6✔
4197
        argv[2] = "--mime-type";
6✔
4198
        argv[3] = path;
6✔
4199
        argv[4] = NULL;
6✔
4200
        execvp("file", (char * const *)argv);
6✔
4201
        _exit(127);
4✔
4202
    }
4203

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

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

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

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

4232
    result = thumbnailer_strdup(trimmed);
6✔
4233

4234
    return result;
6✔
4235
}
2✔
4236

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

4307
    pid = (-1);
6✔
4308
    status_code = 0;
6✔
4309
    wait_result = 0;
6✔
4310
    status = SIXEL_RUNTIME_ERROR;
6✔
4311
    memset(message, 0, sizeof(message));
6✔
4312
    stderr_pipe[0] = -1;
6✔
4313
    stderr_pipe[1] = -1;
6✔
4314
    stdout_pipe[0] = -1;
6✔
4315
    stdout_pipe[1] = -1;
6✔
4316
    stderr_pipe_created = 0;
6✔
4317
    stdout_pipe_created = 0;
6✔
4318
    flags = 0;
6✔
4319
    read_result = 0;
6✔
4320
    stdout_read_result = 0;
6✔
4321
    stderr_output = NULL;
6✔
4322
    stderr_length = 0;
6✔
4323
    stderr_capacity = 0;
6✔
4324
    child_exited = 0;
6✔
4325
    stderr_open = 0;
6✔
4326
    stdout_open = 0;
6✔
4327
    have_status = 0;
6✔
4328
    fatal_error = 0;
6✔
4329
    output_fd = -1;
6✔
4330
    write_offset = 0;
6✔
4331
    write_result = 0;
6✔
4332
    to_write = 0;
6✔
4333
    display_command = NULL;
6✔
4334
# if HAVE_POSIX_SPAWNP
4335
    spawn_result = 0;
6✔
4336
# endif
4337

4338
    if (command == NULL || command->argv == NULL ||
6!
4339
            command->argv[0] == NULL) {
6!
4340
        return SIXEL_BAD_ARGUMENT;
×
4341
    }
4342

4343
    if (capture_stdout && stdout_path == NULL) {
6!
4344
        return SIXEL_BAD_ARGUMENT;
×
4345
    }
4346

4347
    if (capture_stdout) {
4!
4348
        output_fd = open(stdout_path,
×
4349
                         O_WRONLY | O_CREAT | O_TRUNC,
4350
                         0600);
4351
        if (output_fd < 0) {
×
4352
            if (!thumbnailer_safe_format(message,
×
4353
                                         sizeof(message),
4354
                                         "%s: open(%s) failed (%s).",
4355
                                         log_prefix,
4356
                                         stdout_path,
4357
                                         strerror(errno))) {
×
4358
                message[0] = '\0';
×
4359
            }
4360
            sixel_helper_set_additional_message(message);
×
4361
            goto cleanup;
×
4362
        }
4363
    }
4364

4365
    /* stderr is collected even for successful runs so verbose users can see
4366
     * the thumbnailer's own commentary.  Failing to set the pipe is not
4367
     * fatal; we continue without stderr capture in that case.
4368
     */
4369
    if (pipe(stderr_pipe) == 0) {
6!
4370
        stderr_pipe_created = 1;
6✔
4371
        stderr_open = 1;
6✔
4372
    }
2✔
4373

4374
    if (capture_stdout) {
8!
4375
        if (pipe(stdout_pipe) != 0) {
×
4376
            if (!thumbnailer_safe_format(message,
×
4377
                                         sizeof(message),
4378
                                         "%s: pipe() for stdout failed (%s).",
4379
                                         log_prefix,
4380
                                         strerror(errno))) {
×
4381
                message[0] = '\0';
×
4382
            }
4383
            sixel_helper_set_additional_message(message);
×
4384
            goto cleanup;
×
4385
        }
4386
        stdout_pipe_created = 1;
×
4387
        stdout_open = 1;
×
4388
    }
4389

4390
    display_command = (command->display != NULL) ?
10!
4391
            command->display : command->argv[0];
6!
4392
    loader_trace_message("%s: executing %s",
6✔
4393
                         log_prefix,
2✔
4394
                         display_command);
2✔
4395

4396
# if HAVE_POSIX_SPAWNP
4397
    if (posix_spawn_file_actions_init(&actions) != 0) {
6!
4398
        if (!thumbnailer_safe_format(message,
×
4399
                                     sizeof(message),
4400
                                     "%s: posix_spawn_file_actions_init() "
4401
                                     "failed.",
4402
                                     log_prefix)) {
4403
            message[0] = '\0';
×
4404
        }
4405
        sixel_helper_set_additional_message(message);
×
4406
        goto cleanup;
×
4407
    }
4408
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
6!
4409
        (void)posix_spawn_file_actions_adddup2(&actions,
6✔
4410
                                               stderr_pipe[1],
2✔
4411
                                               STDERR_FILENO);
4412
        (void)posix_spawn_file_actions_addclose(&actions,
6✔
4413
                                                stderr_pipe[0]);
2✔
4414
        (void)posix_spawn_file_actions_addclose(&actions,
6✔
4415
                                                stderr_pipe[1]);
2✔
4416
    }
2✔
4417
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
8!
4418
        (void)posix_spawn_file_actions_adddup2(&actions,
×
4419
                                               stdout_pipe[1],
4420
                                               STDOUT_FILENO);
4421
        (void)posix_spawn_file_actions_addclose(&actions,
×
4422
                                                stdout_pipe[0]);
4423
        (void)posix_spawn_file_actions_addclose(&actions,
×
4424
                                                stdout_pipe[1]);
4425
    }
4426
    if (output_fd >= 0) {
4!
4427
        (void)posix_spawn_file_actions_addclose(&actions,
×
4428
                                                output_fd);
4429
    }
4430
    spawn_result = posix_spawnp(&pid,
6✔
4431
                                command->argv[0],
6✔
4432
                                &actions,
4433
                                NULL,
4434
                                (char * const *)command->argv,
6✔
4435
                                environ);
2✔
4436
    posix_spawn_file_actions_destroy(&actions);
6✔
4437
    if (spawn_result != 0) {
6!
4438
        if (!thumbnailer_safe_format(message,
8!
4439
                                     sizeof(message),
4440
                                     "%s: posix_spawnp() failed (%s).",
4441
                                     log_prefix,
2✔
4442
                                     strerror(spawn_result))) {
2✔
4443
            message[0] = '\0';
×
4444
        }
4445
        sixel_helper_set_additional_message(message);
6✔
4446
        goto cleanup;
6✔
4447
    }
4448
# else
4449
    pid = fork();
4450
    if (pid < 0) {
4451
        if (!thumbnailer_safe_format(message,
4452
                                     sizeof(message),
4453
                                     "%s: fork() failed (%s).",
4454
                                     log_prefix,
4455
                                     strerror(errno))) {
4456
            message[0] = '\0';
4457
        }
4458
        sixel_helper_set_additional_message(message);
4459
        goto cleanup;
4460
    }
4461
    if (pid == 0) {
4462
        if (stderr_pipe_created && stderr_pipe[1] >= 0) {
4463
            if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) {
4464
                _exit(127);
4465
            }
4466
        }
4467
        if (stdout_pipe_created && stdout_pipe[1] >= 0) {
4468
            if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) {
4469
                _exit(127);
4470
            }
4471
        }
4472
        if (stderr_pipe[0] >= 0) {
4473
            close(stderr_pipe[0]);
4474
        }
4475
        if (stderr_pipe[1] >= 0) {
4476
            close(stderr_pipe[1]);
4477
        }
4478
        if (stdout_pipe[0] >= 0) {
4479
            close(stdout_pipe[0]);
4480
        }
4481
        if (stdout_pipe[1] >= 0) {
4482
            close(stdout_pipe[1]);
4483
        }
4484
        if (output_fd >= 0) {
4485
            close(output_fd);
4486
        }
4487
        execvp(command->argv[0], (char * const *)command->argv);
4488
        _exit(127);
4489
    }
4490
# endif
4491

4492
    loader_trace_message("%s: forked child pid=%ld",
×
4493
                         log_prefix,
4494
                         (long)pid);
4495

4496
    if (stderr_pipe_created && stderr_pipe[1] >= 0) {
×
4497
        close(stderr_pipe[1]);
×
4498
        stderr_pipe[1] = -1;
×
4499
    }
4500
    if (stdout_pipe_created && stdout_pipe[1] >= 0) {
×
4501
        close(stdout_pipe[1]);
×
4502
        stdout_pipe[1] = -1;
×
4503
    }
4504

4505
    if (stderr_pipe_created && stderr_pipe[0] >= 0) {
×
4506
        flags = fcntl(stderr_pipe[0], F_GETFL, 0);
×
4507
        if (flags >= 0) {
×
4508
            (void)fcntl(stderr_pipe[0],
×
4509
                        F_SETFL,
4510
                        flags | O_NONBLOCK);
4511
        }
4512
    }
4513
    if (stdout_pipe_created && stdout_pipe[0] >= 0) {
×
4514
        flags = fcntl(stdout_pipe[0], F_GETFL, 0);
×
4515
        if (flags >= 0) {
×
4516
            (void)fcntl(stdout_pipe[0],
×
4517
                        F_SETFL,
4518
                        flags | O_NONBLOCK);
4519
        }
4520
    }
4521

4522
    /* The monitoring loop drains stderr/stdout as long as any descriptor is
4523
     * open.  Non-blocking reads ensure the parent does not deadlock if the
4524
     * child process stalls or writes data in bursts.
4525
     */
4526
    while (!child_exited || stderr_open || stdout_open) {
×
4527
        if (stderr_pipe_created && stderr_open) {
×
4528
            read_result = read(stderr_pipe[0],
×
4529
                               stderr_buffer,
4530
                               (ssize_t)sizeof(stderr_buffer));
4531
            if (read_result > 0) {
×
4532
                if (stderr_length + (size_t)read_result + 1 >
×
4533
                        stderr_capacity) {
4534
                    size_t new_capacity;
4535
                    char *new_output;
4536

4537
                    new_capacity = stderr_capacity;
×
4538
                    if (new_capacity == 0) {
×
4539
                        new_capacity = 256;
×
4540
                    }
4541
                    while (stderr_length + (size_t)read_result + 1 >
×
4542
                            new_capacity) {
4543
                        new_capacity *= 2U;
×
4544
                    }
4545
                    new_output = realloc(stderr_output, new_capacity);
×
4546
                    if (new_output == NULL) {
×
4547
                        free(stderr_output);
×
4548
                        stderr_output = NULL;
×
4549
                        stderr_capacity = 0;
×
4550
                        stderr_length = 0;
×
4551
                        stderr_open = 0;
×
4552
                        if (stderr_pipe[0] >= 0) {
×
4553
                            close(stderr_pipe[0]);
×
4554
                            stderr_pipe[0] = -1;
×
4555
                        }
4556
                        stderr_pipe_created = 0;
×
4557
                        if (!thumbnailer_safe_format(message,
×
4558
                                                     sizeof(message),
4559
                                                     "%s: realloc() failed.",
4560
                                                     log_prefix)) {
4561
                            message[0] = '\0';
×
4562
                        }
4563
                        sixel_helper_set_additional_message(message);
×
4564
                        status = SIXEL_BAD_ALLOCATION;
×
4565
                        fatal_error = 1;
×
4566
                        break;
×
4567
                    }
4568
                    stderr_output = new_output;
×
4569
                    stderr_capacity = new_capacity;
×
4570
                }
4571
                memcpy(stderr_output + stderr_length,
×
4572
                       stderr_buffer,
4573
                       (size_t)read_result);
4574
                stderr_length += (size_t)read_result;
×
4575
                stderr_output[stderr_length] = '\0';
×
4576
            } else if (read_result == 0) {
×
4577
                stderr_open = 0;
×
4578
                if (stderr_pipe[0] >= 0) {
×
4579
                    close(stderr_pipe[0]);
×
4580
                    stderr_pipe[0] = -1;
×
4581
                }
4582
                stderr_pipe_created = 0;
×
4583
            } else if (errno == EINTR) {
×
4584
                /* retry */
4585
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4586
                /* no data */
4587
            } else {
4588
                if (!thumbnailer_safe_format(message,
×
4589
                                             sizeof(message),
4590
                                             "%s: read() failed (%s).",
4591
                                             log_prefix,
4592
                                             strerror(errno))) {
×
4593
                    message[0] = '\0';
×
4594
                }
4595
                sixel_helper_set_additional_message(message);
×
4596
                stderr_open = 0;
×
4597
                if (stderr_pipe[0] >= 0) {
×
4598
                    close(stderr_pipe[0]);
×
4599
                    stderr_pipe[0] = -1;
×
4600
                }
4601
                stderr_pipe_created = 0;
×
4602
            }
4603
        }
4604

4605
        if (stdout_pipe_created && stdout_open) {
×
4606
            stdout_read_result = read(stdout_pipe[0],
×
4607
                                      stdout_buffer,
4608
                                      (ssize_t)sizeof(stdout_buffer));
4609
            if (stdout_read_result > 0) {
×
4610
                write_offset = 0;
×
4611
                while (write_offset < (size_t)stdout_read_result) {
×
4612
                    to_write = (size_t)stdout_read_result - write_offset;
×
4613
                    write_result = write(output_fd,
×
4614
                                          stdout_buffer + write_offset,
4615
                                          to_write);
4616
                    if (write_result < 0) {
×
4617
                        if (errno == EINTR) {
×
4618
                            continue;
×
4619
                        }
4620
                        if (!thumbnailer_safe_format(message,
×
4621
                                                     sizeof(message),
4622
                                                     "%s: write() failed (%s).",
4623
                                                     log_prefix,
4624
                                                     strerror(errno))) {
×
4625
                            message[0] = '\0';
×
4626
                        }
4627
                        sixel_helper_set_additional_message(message);
×
4628
                        stdout_open = 0;
×
4629
                        fatal_error = 1;
×
4630
                        break;
×
4631
                    }
4632
                    write_offset += (size_t)write_result;
×
4633
                }
4634
            } else if (stdout_read_result == 0) {
×
4635
                stdout_open = 0;
×
4636
                if (stdout_pipe[0] >= 0) {
×
4637
                    close(stdout_pipe[0]);
×
4638
                    stdout_pipe[0] = -1;
×
4639
                }
4640
                stdout_pipe_created = 0;
×
4641
            } else if (errno == EINTR) {
×
4642
                /* retry */
4643
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
×
4644
                /* no data */
4645
            } else {
4646
                if (!thumbnailer_safe_format(message,
×
4647
                                             sizeof(message),
4648
                                             "%s: read() failed (%s).",
4649
                                             log_prefix,
4650
                                             strerror(errno))) {
×
4651
                    message[0] = '\0';
×
4652
                }
4653
                sixel_helper_set_additional_message(message);
×
4654
                stdout_open = 0;
×
4655
                if (stdout_pipe[0] >= 0) {
×
4656
                    close(stdout_pipe[0]);
×
4657
                    stdout_pipe[0] = -1;
×
4658
                }
4659
                stdout_pipe_created = 0;
×
4660
            }
4661
        }
4662

4663
        if (!child_exited) {
×
4664
            wait_result = waitpid(pid, &status_code, WNOHANG);
×
4665
            if (wait_result > 0) {
×
4666
                child_exited = 1;
×
4667
                have_status = 1;
×
4668
            } else if (wait_result == 0) {
×
4669
                /* child running */
4670
            } else if (errno != EINTR) {
×
4671
                if (!thumbnailer_safe_format(message,
×
4672
                                             sizeof(message),
4673
                                             "%s: waitpid() failed (%s).",
4674
                                             log_prefix,
4675
                                             strerror(errno))) {
×
4676
                    message[0] = '\0';
×
4677
                }
4678
                sixel_helper_set_additional_message(message);
×
4679
                status = SIXEL_RUNTIME_ERROR;
×
4680
                fatal_error = 1;
×
4681
                break;
×
4682
            }
4683
        }
4684

4685
        if (!child_exited || stderr_open || stdout_open) {
×
4686
            thumbnailer_sleep_briefly();
×
4687
        }
4688
    }
4689

4690
    if (!child_exited) {
×
4691
        do {
4692
            wait_result = waitpid(pid, &status_code, 0);
×
4693
        } while (wait_result < 0 && errno == EINTR);
×
4694
        if (wait_result < 0) {
×
4695
            if (!thumbnailer_safe_format(message,
×
4696
                                         sizeof(message),
4697
                                         "%s: waitpid() failed (%s).",
4698
                                         log_prefix,
4699
                                         strerror(errno))) {
×
4700
                message[0] = '\0';
×
4701
            }
4702
            sixel_helper_set_additional_message(message);
×
4703
            status = SIXEL_RUNTIME_ERROR;
×
4704
            goto cleanup;
×
4705
        }
4706
        have_status = 1;
×
4707
    }
4708

4709
    if (!have_status) {
×
4710
        if (!thumbnailer_safe_format(message,
×
4711
                                     sizeof(message),
4712
                                     "%s: waitpid() failed (no status).",
4713
                                     log_prefix)) {
4714
            message[0] = '\0';
×
4715
        }
4716
        sixel_helper_set_additional_message(message);
×
4717
        status = SIXEL_RUNTIME_ERROR;
×
4718
        goto cleanup;
×
4719
    }
4720

4721
    if (!fatal_error) {
×
4722
        if (WIFEXITED(status_code) && WEXITSTATUS(status_code) == 0) {
×
4723
            status = SIXEL_OK;
×
4724
            loader_trace_message("%s: child pid=%ld exited successfully",
×
4725
                                 log_prefix,
4726
                                 (long)pid);
4727
        } else if (WIFEXITED(status_code)) {
×
4728
            if (!thumbnailer_safe_format(message,
×
4729
                                         sizeof(message),
4730
                                         "%s: %s exited with status %d.",
4731
                                         log_prefix,
4732
                                         (thumbnailer_name != NULL) ?
×
4733
                                         thumbnailer_name :
4734
                                         "thumbnailer",
4735
                                         WEXITSTATUS(status_code))) {
×
4736
                message[0] = '\0';
×
4737
            }
4738
            sixel_helper_set_additional_message(message);
×
4739
            status = SIXEL_RUNTIME_ERROR;
×
4740
        } else if (WIFSIGNALED(status_code)) {
×
4741
            if (!thumbnailer_safe_format(message,
×
4742
                                         sizeof(message),
4743
                                         "%s: %s terminated by signal %d.",
4744
                                         log_prefix,
4745
                                         (thumbnailer_name != NULL) ?
×
4746
                                         thumbnailer_name :
4747
                                         "thumbnailer",
4748
                                         WTERMSIG(status_code))) {
4749
                message[0] = '\0';
×
4750
            }
4751
            sixel_helper_set_additional_message(message);
×
4752
            status = SIXEL_RUNTIME_ERROR;
×
4753
        } else {
4754
            if (!thumbnailer_safe_format(message,
×
4755
                                         sizeof(message),
4756
                                         "%s: %s exited abnormally.",
4757
                                         log_prefix,
4758
                                         (thumbnailer_name != NULL) ?
×
4759
                                         thumbnailer_name :
4760
                                         "thumbnailer")) {
4761
                message[0] = '\0';
×
4762
            }
4763
            sixel_helper_set_additional_message(message);
×
4764
            status = SIXEL_RUNTIME_ERROR;
×
4765
        }
4766
    }
4767

4768
cleanup:
4769
    if (stderr_output != NULL && loader_trace_enabled &&
6!
4770
            stderr_length > 0) {
4771
        loader_trace_message("%s: stderr:\n%s",
×
4772
                             log_prefix,
4773
                             stderr_output);
4774
    }
4775

4776
    if (stderr_pipe[0] >= 0) {
6!
4777
        close(stderr_pipe[0]);
6✔
4778
        stderr_pipe[0] = -1;
6✔
4779
    }
2✔
4780
    if (stderr_pipe[1] >= 0) {
6!
4781
        close(stderr_pipe[1]);
6✔
4782
        stderr_pipe[1] = -1;
6✔
4783
    }
2✔
4784
    if (stdout_pipe[0] >= 0) {
8!
4785
        close(stdout_pipe[0]);
×
4786
        stdout_pipe[0] = -1;
×
4787
    }
4788
    if (stdout_pipe[1] >= 0) {
4!
4789
        close(stdout_pipe[1]);
×
4790
        stdout_pipe[1] = -1;
×
4791
    }
4792
    if (output_fd >= 0) {
4!
4793
        close(output_fd);
×
4794
        output_fd = -1;
×
4795
    }
4796
    /* stderr_output accumulates all diagnostic text, so release it even when
4797
     * verbose tracing is disabled.
4798
     */
4799
    free(stderr_output);
6✔
4800

4801
    return status;
6✔
4802
}
2✔
4803

4804

4805

4806
/*
4807
 * load_with_gnome_thumbnailer
4808
 *
4809
 * Drive the FreeDesktop thumbnailer pipeline and then decode the PNG
4810
 * result using the built-in loader.
4811
 *
4812
 * GNOME thumbnail workflow overview:
4813
 *
4814
 *     +------------+    +-------------------+    +----------------+
4815
 *     | source URI | -> | .thumbnailer Exec | -> | PNG thumbnail  |
4816
 *     +------------+    +-------------------+    +----------------+
4817
 *             |                    |                        |
4818
 *             |                    v                        v
4819
 *             |           spawn via /bin/sh         load_with_builtin()
4820
 *             v
4821
 *     file --mime-type
4822
 *
4823
 * Each step logs verbose breadcrumbs so integrators can diagnose which
4824
 * thumbnailer matched, how the command was prepared, and why fallbacks
4825
 * were selected.
4826
 *
4827
 * Arguments:
4828
 *     pchunk        - source chunk representing the original document.
4829
 *     fstatic       - image static-ness flag.
4830
 *     fuse_palette  - palette usage flag.
4831
 *     reqcolors     - requested colour count.
4832
 *     bgcolor       - background colour override.
4833
 *     loop_control  - animation loop control flag.
4834
 *     fn_load       - downstream decoder callback.
4835
 *     context       - user context forwarded to fn_load.
4836
 * Returns:
4837
 *     SIXEL_OK on success or libsixel error code on failure.
4838
 */
4839
static SIXELSTATUS
4840
load_with_gnome_thumbnailer(
9✔
4841
    sixel_chunk_t const       /* in */     *pchunk,
4842
    int                       /* in */     fstatic,
4843
    int                       /* in */     fuse_palette,
4844
    int                       /* in */     reqcolors,
4845
    unsigned char             /* in */     *bgcolor,
4846
    int                       /* in */     loop_control,
4847
    sixel_load_image_function /* in */     fn_load,
4848
    void                      /* in/out */ *context)
4849
{
4850
    SIXELSTATUS status;
4851
    sixel_chunk_t *thumb_chunk;
4852
    char template_path[] = "/tmp/libsixel-thumb-XXXXXX";
9✔
4853
    char *png_path;
4854
    size_t path_length;
4855
    struct thumbnailer_string_list *directories;
4856
    size_t dir_index;
4857
    DIR *dir;
4858
    struct dirent *entry;
4859
    char *thumbnailer_path;
4860
    struct thumbnailer_entry info;
4861
    char *content_type;
4862
    char *input_uri;
4863
    struct thumbnailer_command *command;
4864
    struct thumbnailer_command *evince_command;
4865
    int executed;
4866
    int command_success;
4867
    int requested_size;
4868
    char const *log_prefix;
4869

4870
    status = SIXEL_FALSE;
9✔
4871
    thumb_chunk = NULL;
9✔
4872
    png_path = NULL;
9✔
4873
    path_length = 0;
9✔
4874
    int fd;
4875
    fd = -1;
9✔
4876
    directories = NULL;
9✔
4877
    dir_index = 0;
9✔
4878
    dir = NULL;
9✔
4879
    entry = NULL;
9✔
4880
    thumbnailer_path = NULL;
9✔
4881
    content_type = NULL;
9✔
4882
    input_uri = NULL;
9✔
4883
    command = NULL;
9✔
4884
    evince_command = NULL;
9✔
4885
    executed = 0;
9✔
4886
    command_success = 0;
9✔
4887
    log_prefix = "load_with_gnome_thumbnailer";
9✔
4888
    requested_size = thumbnailer_size_hint;
9✔
4889
    if (requested_size <= 0) {
9!
4890
        requested_size = SIXEL_THUMBNAILER_DEFAULT_SIZE;
×
4891
    }
4892

4893
    loader_trace_message("%s: thumbnail size hint=%d",
9✔
4894
                         log_prefix,
3✔
4895
                         requested_size);
3✔
4896

4897
    thumbnailer_entry_init(&info);
9✔
4898

4899
    if (pchunk->source_path == NULL) {
9✔
4900
        sixel_helper_set_additional_message(
3✔
4901
            "load_with_gnome_thumbnailer: source path is unavailable.");
4902
        status = SIXEL_BAD_ARGUMENT;
3✔
4903
        goto end;
3✔
4904
    }
4905

4906
#if defined(HAVE_MKSTEMP)
4907
    fd = mkstemp(template_path);
6✔
4908
#elif defined(HAVE__MKTEMP)
4909
    fd = _mktemp(template_path);
4910
#elif defined(HAVE_MKTEMP)
4911
    fd = mktemp(template_path);
4912
#endif
4913
    if (fd < 0) {
6!
4914
        sixel_helper_set_additional_message(
×
4915
            "load_with_gnome_thumbnailer: mkstemp() failed.");
4916
        status = SIXEL_RUNTIME_ERROR;
×
4917
        goto end;
×
4918
    }
4919
    close(fd);
6✔
4920
    fd = -1;
6✔
4921

4922
    path_length = strlen(template_path) + 5;
6✔
4923
    png_path = malloc(path_length);
6✔
4924
    if (png_path == NULL) {
6!
4925
        sixel_helper_set_additional_message(
×
4926
            "load_with_gnome_thumbnailer: malloc() failed.");
4927
        status = SIXEL_BAD_ALLOCATION;
×
4928
        unlink(template_path);
×
4929
        goto end;
×
4930
    }
4931
    if (!thumbnailer_safe_format(png_path,
8!
4932
                                 path_length,
2✔
4933
                                 "%s.png",
4934
                                 template_path)) {
2✔
4935
        if (path_length > 0) {
×
4936
            png_path[path_length - 1] = '\0';
×
4937
        }
4938
    }
4939
    if (rename(template_path, png_path) != 0) {
6!
4940
        sixel_helper_set_additional_message(
×
4941
            "load_with_gnome_thumbnailer: rename() failed.");
4942
        status = SIXEL_RUNTIME_ERROR;
×
4943
        unlink(template_path);
×
4944
        goto end;
×
4945
    }
4946

4947
    content_type = thumbnailer_guess_content_type(pchunk->source_path);
6✔
4948
    input_uri = thumbnailer_build_file_uri(pchunk->source_path);
6✔
4949

4950
    loader_trace_message("%s: detected MIME type %s for %s",
6✔
4951
                         log_prefix,
2✔
4952
                         (content_type != NULL) ? content_type :
2!
4953
                         "(unknown)",
4954
                         pchunk->source_path);
6!
4955

4956
    directories = thumbnailer_collect_directories();
6✔
4957
    if (directories == NULL) {
6!
4958
        status = SIXEL_RUNTIME_ERROR;
×
4959
        goto end;
×
4960
    }
4961

4962
    /* Iterate through every configured thumbnailer directory so we honour
4963
     * overrides in $HOME as well as desktop environment defaults discovered
4964
     * through XDG_DATA_DIRS.
4965
     */
4966
    for (dir_index = 0; dir_index < directories->length; ++dir_index) {
24✔
4967
        loader_trace_message("%s: checking thumbnailers in %s",
18✔
4968
                             log_prefix,
6✔
4969
                             directories->items[dir_index]);
18✔
4970

4971
        dir = opendir(directories->items[dir_index]);
18✔
4972
        if (dir == NULL) {
18!
4973
            continue;
18✔
4974
        }
4975
        while ((entry = readdir(dir)) != NULL) {
×
4976
            thumbnailer_entry_clear(&info);
×
4977
            thumbnailer_entry_init(&info);
×
4978
            size_t name_length;
4979

4980
            name_length = strlen(entry->d_name);
×
4981
            if (name_length < 12 ||
×
4982
                    strcmp(entry->d_name + name_length - 12,
×
4983
                           ".thumbnailer") != 0) {
4984
                continue;
×
4985
            }
4986
            thumbnailer_path = thumbnailer_join_paths(
×
4987
                directories->items[dir_index],
×
4988
                entry->d_name);
×
4989
            if (thumbnailer_path == NULL) {
×
4990
                continue;
×
4991
            }
4992
            if (!thumbnailer_parse_file(thumbnailer_path, &info)) {
×
4993
                free(thumbnailer_path);
×
4994
                thumbnailer_path = NULL;
×
4995
                continue;
×
4996
            }
4997
            free(thumbnailer_path);
×
4998
            thumbnailer_path = NULL;
×
4999
            loader_trace_message(
×
5000
                "%s: parsed %s (TryExec=%s)",
5001
                log_prefix,
5002
                entry->d_name,
×
5003
                (info.tryexec != NULL) ? info.tryexec : "(none)");
×
5004
            if (content_type == NULL) {
×
5005
                continue;
×
5006
            }
5007
            if (!thumbnailer_has_tryexec(info.tryexec)) {
×
5008
                loader_trace_message("%s: skipping %s (TryExec missing)",
×
5009
                                     log_prefix,
5010
                                     entry->d_name);
×
5011
                continue;
×
5012
            }
5013
            if (!thumbnailer_supports_mime(&info, content_type)) {
×
5014
                loader_trace_message("%s: %s does not support %s",
×
5015
                                     log_prefix,
5016
                                     entry->d_name,
×
5017
                                     content_type);
5018
                continue;
×
5019
            }
5020
            if (info.exec_line == NULL) {
×
5021
                continue;
×
5022
            }
5023
            loader_trace_message("%s: %s supports %s with Exec=\"%s\"",
×
5024
                                 log_prefix,
5025
                                 entry->d_name,
×
5026
                                 content_type,
5027
                                 info.exec_line);
5028
            loader_trace_message("%s: preparing %s for %s",
×
5029
                                 log_prefix,
5030
                                 entry->d_name,
×
5031
                                 content_type);
5032
            command = thumbnailer_build_command(info.exec_line,
×
5033
                                                pchunk->source_path,
×
5034
                                                input_uri,
5035
                                                png_path,
5036
                                                requested_size,
5037
                                                content_type);
5038
            if (command == NULL) {
×
5039
                continue;
×
5040
            }
5041
            if (thumbnailer_is_evince_thumbnailer(info.exec_line,
×
5042
                                                  info.tryexec)) {
×
5043
                loader_trace_message(
×
5044
                    "%s: applying evince-thumbnailer stdout workaround",
5045
                    log_prefix);
5046
                /* evince-thumbnailer fails when passed an output path.
5047
                 * Redirect stdout and copy the stream instead.
5048
                 */
5049
                evince_command = thumbnailer_build_evince_command(
×
5050
                    pchunk->source_path,
×
5051
                    requested_size);
5052
                if (evince_command == NULL) {
×
5053
                    thumbnailer_command_free(command);
×
5054
                    command = NULL;
×
5055
                    continue;
×
5056
                }
5057
                thumbnailer_command_free(command);
×
5058
                command = evince_command;
×
5059
                evince_command = NULL;
×
5060
                unlink(png_path);
×
5061
                status = thumbnailer_spawn(command,
×
5062
                                           entry->d_name,
×
5063
                                           log_prefix,
5064
                                           1,
5065
                                           png_path);
5066
            } else {
5067
                unlink(png_path);
×
5068
                status = thumbnailer_spawn(command,
×
5069
                                           entry->d_name,
×
5070
                                           log_prefix,
5071
                                           0,
5072
                                           NULL);
5073
            }
5074
            thumbnailer_command_free(command);
×
5075
            command = NULL;
×
5076
            executed = 1;
×
5077
            if (SIXEL_SUCCEEDED(status)) {
×
5078
                command_success = 1;
×
5079
                loader_trace_message("%s: %s produced %s",
×
5080
                                     log_prefix,
5081
                                     entry->d_name,
×
5082
                                     png_path);
5083
                break;
×
5084
            }
5085
        }
5086
        closedir(dir);
×
5087
        dir = NULL;
×
5088
        if (command_success) {
×
5089
            break;
×
5090
        }
5091
    }
5092

5093
    if (!command_success) {
6!
5094
        loader_trace_message("%s: falling back to gdk-pixbuf-thumbnailer",
6✔
5095
                             log_prefix);
2✔
5096
        unlink(png_path);
6✔
5097
        command = thumbnailer_build_command(
6✔
5098
            "gdk-pixbuf-thumbnailer --size=%s %i %o",
5099
            pchunk->source_path,
6✔
5100
            input_uri,
2✔
5101
            png_path,
2✔
5102
            requested_size,
2✔
5103
            content_type);
2✔
5104
        if (command != NULL) {
6!
5105
            unlink(png_path);
6✔
5106
            status = thumbnailer_spawn(command,
8✔
5107
                                       "gdk-pixbuf-thumbnailer",
5108
                                       log_prefix,
2✔
5109
                                       0,
5110
                                       NULL);
5111
            thumbnailer_command_free(command);
6✔
5112
            command = NULL;
6✔
5113
            if (SIXEL_FAILED(status)) {
6!
5114
                goto end;
6✔
5115
            }
5116
            executed = 1;
×
5117
            command_success = 1;
×
5118
            loader_trace_message("%s: gdk-pixbuf-thumbnailer produced %s",
×
5119
                                 log_prefix,
5120
                                 png_path);
5121
        }
5122
    }
5123

5124
    if (!executed) {
×
5125
        sixel_helper_set_additional_message(
×
5126
            "load_with_gnome_thumbnailer: no thumbnailer available.");
5127
        status = SIXEL_RUNTIME_ERROR;
×
5128
        goto end;
×
5129
    }
5130

5131
    status = sixel_chunk_new(&thumb_chunk,
×
5132
                             png_path,
5133
                             0,
5134
                             NULL,
5135
                             pchunk->allocator);
×
5136
    if (SIXEL_FAILED(status)) {
×
5137
        goto end;
×
5138
    }
5139
    status = load_with_builtin(thumb_chunk,
×
5140
                               fstatic,
5141
                               fuse_palette,
5142
                               reqcolors,
5143
                               bgcolor,
5144
                               loop_control,
5145
                               fn_load,
5146
                               context);
5147
    if (SIXEL_FAILED(status)) {
×
5148
        goto end;
×
5149
    }
5150

5151
    status = SIXEL_OK;
×
5152

5153
end:
6✔
5154
    if (command != NULL) {
9!
5155
        thumbnailer_command_free(command);
×
5156
        command = NULL;
×
5157
    }
5158
    if (evince_command != NULL) {
6!
5159
        thumbnailer_command_free(evince_command);
×
5160
        evince_command = NULL;
×
5161
    }
5162
    if (thumb_chunk != NULL) {
6!
5163
        sixel_chunk_destroy(thumb_chunk);
×
5164
        thumb_chunk = NULL;
×
5165
    }
5166
    if (png_path != NULL) {
8✔
5167
        unlink(png_path);
6✔
5168
        free(png_path);
6✔
5169
        png_path = NULL;
6✔
5170
    }
2✔
5171
    if (fd >= 0) {
10!
5172
        close(fd);
×
5173
        fd = -1;
×
5174
    }
5175
    if (directories != NULL) {
8✔
5176
        thumbnailer_string_list_free(directories);
6✔
5177
        directories = NULL;
6✔
5178
    }
2✔
5179
    if (dir != NULL) {
10!
5180
        closedir(dir);
×
5181
        dir = NULL;
×
5182
    }
5183
    thumbnailer_entry_clear(&info);
9✔
5184
    free(content_type);
9✔
5185
    content_type = NULL;
9✔
5186
    free(input_uri);
9✔
5187
    input_uri = NULL;
9✔
5188

5189
    return status;
9✔
5190
}
5191

5192
#endif  /* HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK */
5193

5194

5195
#if HAVE_GD
5196
static int
5197
detect_file_format(int len, unsigned char *data)
5198
{
5199
    if (len > 18 && memcmp("TRUEVISION", data + len - 18, 10) == 0) {
5200
        return SIXEL_FORMAT_TGA;
5201
    }
5202

5203
    if (len > 3 && memcmp("GIF", data, 3) == 0) {
5204
        return SIXEL_FORMAT_GIF;
5205
    }
5206

5207
    if (len > 8 && memcmp("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", data, 8) == 0) {
5208
        return SIXEL_FORMAT_PNG;
5209
    }
5210

5211
    if (len > 2 && memcmp("BM", data, 2) == 0) {
5212
        return SIXEL_FORMAT_BMP;
5213
    }
5214

5215
    if (len > 2 && memcmp("\xFF\xD8", data, 2) == 0) {
5216
        return SIXEL_FORMAT_JPG;
5217
    }
5218

5219
    if (len > 2 && memcmp("\x00\x00", data, 2) == 0) {
5220
        return SIXEL_FORMAT_WBMP;
5221
    }
5222

5223
    if (len > 2 && memcmp("\x4D\x4D", data, 2) == 0) {
5224
        return SIXEL_FORMAT_TIFF;
5225
    }
5226

5227
    if (len > 2 && memcmp("\x49\x49", data, 2) == 0) {
5228
        return SIXEL_FORMAT_TIFF;
5229
    }
5230

5231
    if (len > 2 && memcmp("\033P", data, 2) == 0) {
5232
        return SIXEL_FORMAT_SIXEL;
5233
    }
5234

5235
    if (len > 2 && data[0] == 0x90  && (data[len - 1] == 0x9C || data[len - 2] == 0x9C)) {
5236
        return SIXEL_FORMAT_SIXEL;
5237
    }
5238

5239
    if (len > 1 && data[0] == 'P' && data[1] >= '1' && data[1] <= '6') {
5240
        return SIXEL_FORMAT_PNM;
5241
    }
5242

5243
    if (len > 3 && memcmp("gd2", data, 3) == 0) {
5244
        return SIXEL_FORMAT_GD2;
5245
    }
5246

5247
    if (len > 4 && memcmp("8BPS", data, 4) == 0) {
5248
        return SIXEL_FORMAT_PSD;
5249
    }
5250

5251
    if (len > 11 && memcmp("#?RADIANCE\n", data, 11) == 0) {
5252
        return SIXEL_FORMAT_HDR;
5253
    }
5254

5255
    return (-1);
5256
}
5257
#endif /* HAVE_GD */
5258

5259
#if HAVE_GD
5260

5261
static SIXELSTATUS
5262
load_with_gd(
5263
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
5264
    int                       /* in */     fstatic,      /* static */
5265
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
5266
    int                       /* in */     reqcolors,    /* reqcolors */
5267
    unsigned char             /* in */     *bgcolor,     /* background color */
5268
    int                       /* in */     loop_control, /* one of enum loop_control */
5269
    sixel_load_image_function /* in */     fn_load,      /* callback */
5270
    void                      /* in/out */ *context      /* private data for callback */
5271
)
5272
{
5273
    SIXELSTATUS status = SIXEL_FALSE;
5274
    unsigned char *p;
5275
    gdImagePtr im = NULL;
5276
    int x, y;
5277
    int c;
5278
    sixel_frame_t *frame = NULL;
5279
    int format;
5280

5281
    (void) fstatic;
5282
    (void) fuse_palette;
5283
    (void) reqcolors;
5284
    (void) bgcolor;
5285
    (void) loop_control;
5286

5287
    format = detect_file_format(pchunk->size, pchunk->buffer);
5288

5289
    if (format == SIXEL_FORMAT_GIF) {
5290
#if HAVE_DECL_GDIMAGECREATEFROMGIFANIMPTR
5291
        gdImagePtr *ims = NULL;
5292
        int frames = 0;
5293
        int i;
5294
        int *delays = NULL;
5295

5296
        ims = gdImageCreateFromGifAnimPtr(pchunk->size, pchunk->buffer,
5297
                                          &frames, &delays);
5298
        if (ims == NULL) {
5299
            status = SIXEL_GD_ERROR;
5300
            goto end;
5301
        }
5302

5303
        for (i = 0; i < frames; i++) {
5304
            im = ims[i];
5305
            if (!gdImageTrueColor(im)) {
5306
# if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
5307
                if (!gdImagePaletteToTrueColor(im)) {
5308
                    status = SIXEL_GD_ERROR;
5309
                    goto gif_end;
5310
                }
5311
# else
5312
                status = SIXEL_GD_ERROR;
5313
                goto gif_end;
5314
# endif
5315
            }
5316

5317
            status = sixel_frame_new(&frame, pchunk->allocator);
5318
            if (SIXEL_FAILED(status)) {
5319
                frame = NULL;
5320
                goto gif_end;
5321
            }
5322

5323
            frame->width = gdImageSX(im);
5324
            frame->height = gdImageSY(im);
5325
            frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
5326
            p = frame->pixels = sixel_allocator_malloc(
5327
                pchunk->allocator,
5328
                (size_t)(frame->width * frame->height * 3));
5329
            if (frame->pixels == NULL) {
5330
                sixel_helper_set_additional_message(
5331
                    "load_with_gd: sixel_allocator_malloc() failed.");
5332
                status = SIXEL_BAD_ALLOCATION;
5333
                sixel_frame_unref(frame);
5334
                frame = NULL;
5335
                goto gif_end;
5336
            }
5337
            for (y = 0; y < frame->height; y++) {
5338
                for (x = 0; x < frame->width; x++) {
5339
                    c = gdImageTrueColorPixel(im, x, y);
5340
                    *p++ = gdTrueColorGetRed(c);
5341
                    *p++ = gdTrueColorGetGreen(c);
5342
                    *p++ = gdTrueColorGetBlue(c);
5343
                }
5344
            }
5345

5346
            if (delays) {
5347
                frame->delay.tv_sec = delays[i] / 100;
5348
                frame->delay.tv_nsec = (delays[i] % 100) * 10000000L;
5349
            }
5350

5351
            status = fn_load(frame, context);
5352
            sixel_frame_unref(frame);
5353
            frame = NULL;
5354
            gdImageDestroy(im);
5355
            ims[i] = NULL;
5356
            if (SIXEL_FAILED(status)) {
5357
                goto gif_end;
5358
            }
5359
        }
5360

5361
        status = SIXEL_OK;
5362

5363
gif_end:
5364
        if (delays) {
5365
            gdFree(delays);
5366
        }
5367
        if (ims) {
5368
            for (i = 0; i < frames; i++) {
5369
                if (ims[i]) {
5370
                    gdImageDestroy(ims[i]);
5371
                }
5372
            }
5373
            gdFree(ims);
5374
        }
5375
        goto end;
5376
#else
5377
        status = SIXEL_GD_ERROR;
5378
        goto end;
5379
#endif
5380
    }
5381

5382
    switch (format) {
5383
#if HAVE_DECL_GDIMAGECREATEFROMPNGPTR
5384
        case SIXEL_FORMAT_PNG:
5385
            im = gdImageCreateFromPngPtr(pchunk->size, pchunk->buffer);
5386
            break;
5387
#endif  /* HAVE_DECL_GDIMAGECREATEFROMPNGPTR */
5388
#if HAVE_DECL_GDIMAGECREATEFROMBMPPTR
5389
        case SIXEL_FORMAT_BMP:
5390
            im = gdImageCreateFromBmpPtr(pchunk->size, pchunk->buffer);
5391
            break;
5392
#endif  /* HAVE_DECL_GDIMAGECREATEFROMBMPPTR */
5393
        case SIXEL_FORMAT_JPG:
5394
#if HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX
5395
            im = gdImageCreateFromJpegPtrEx(pchunk->size, pchunk->buffer, 1);
5396
#elif HAVE_DECL_GDIMAGECREATEFROMJPEGPTR
5397
            im = gdImageCreateFromJpegPtr(pchunk->size, pchunk->buffer);
5398
#endif  /* HAVE_DECL_GDIMAGECREATEFROMJPEGPTREX */
5399
            break;
5400
#if HAVE_DECL_GDIMAGECREATEFROMTGAPTR
5401
        case SIXEL_FORMAT_TGA:
5402
            im = gdImageCreateFromTgaPtr(pchunk->size, pchunk->buffer);
5403
            break;
5404
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTGAPTR */
5405
#if HAVE_DECL_GDIMAGECREATEFROMWBMPPTR
5406
        case SIXEL_FORMAT_WBMP:
5407
            im = gdImageCreateFromWBMPPtr(pchunk->size, pchunk->buffer);
5408
            break;
5409
#endif  /* HAVE_DECL_GDIMAGECREATEFROMWBMPPTR */
5410
#if HAVE_DECL_GDIMAGECREATEFROMTIFFPTR
5411
        case SIXEL_FORMAT_TIFF:
5412
            im = gdImageCreateFromTiffPtr(pchunk->size, pchunk->buffer);
5413
            break;
5414
#endif  /* HAVE_DECL_GDIMAGECREATEFROMTIFFPTR */
5415
#if HAVE_DECL_GDIMAGECREATEFROMGD2PTR
5416
        case SIXEL_FORMAT_GD2:
5417
            im = gdImageCreateFromGd2Ptr(pchunk->size, pchunk->buffer);
5418
            break;
5419
#endif  /* HAVE_DECL_GDIMAGECREATEFROMGD2PTR */
5420
        default:
5421
            status = SIXEL_GD_ERROR;
5422
            sixel_helper_set_additional_message(
5423
                "unexpected image format detected.");
5424
            goto end;
5425
    }
5426

5427
    if (im == NULL) {
5428
        status = SIXEL_GD_ERROR;
5429
        /* TODO: retrieve error detail */
5430
        goto end;
5431
    }
5432

5433
    if (!gdImageTrueColor(im)) {
5434
#if HAVE_DECL_GDIMAGEPALETTETOTRUECOLOR
5435
        if (!gdImagePaletteToTrueColor(im)) {
5436
            gdImageDestroy(im);
5437
            status = SIXEL_GD_ERROR;
5438
            /* TODO: retrieve error detail */
5439
            goto end;
5440
        }
5441
#else
5442
        status = SIXEL_GD_ERROR;
5443
        /* TODO: retrieve error detail */
5444
        goto end;
5445
#endif
5446
    }
5447

5448
    status = sixel_frame_new(&frame, pchunk->allocator);
5449
    if (SIXEL_FAILED(status)) {
5450
        goto end;
5451
    }
5452

5453
    frame->width = gdImageSX(im);
5454
    frame->height = gdImageSY(im);
5455
    frame->pixelformat = SIXEL_PIXELFORMAT_RGB888;
5456
    p = frame->pixels = sixel_allocator_malloc(
5457
        pchunk->allocator, (size_t)(frame->width * frame->height * 3));
5458
    if (frame->pixels == NULL) {
5459
        sixel_helper_set_additional_message(
5460
            "load_with_gd: sixel_allocator_malloc() failed.");
5461
        status = SIXEL_BAD_ALLOCATION;
5462
        gdImageDestroy(im);
5463
        goto end;
5464
    }
5465
    for (y = 0; y < frame->height; y++) {
5466
        for (x = 0; x < frame->width; x++) {
5467
            c = gdImageTrueColorPixel(im, x, y);
5468
            *p++ = gdTrueColorGetRed(c);
5469
            *p++ = gdTrueColorGetGreen(c);
5470
            *p++ = gdTrueColorGetBlue(c);
5471
        }
5472
    }
5473
    gdImageDestroy(im);
5474

5475
    status = fn_load(frame, context);
5476
    if (SIXEL_FAILED(status)) {
5477
        goto end;
5478
    }
5479

5480
    sixel_frame_unref(frame);
5481

5482
    status = SIXEL_OK;
5483

5484
end:
5485
    return status;
5486
}
5487

5488
#endif  /* HAVE_GD */
5489

5490
#if HAVE_WIC
5491

5492
#include <windows.h>
5493
#include <wincodec.h>
5494

5495
SIXELSTATUS
5496
load_with_wic(
5497
    sixel_chunk_t const       /* in */     *pchunk,      /* image data */
5498
    int                       /* in */     fstatic,      /* static */
5499
    int                       /* in */     fuse_palette, /* whether to use palette if possible */
5500
    int                       /* in */     reqcolors,    /* reqcolors */
5501
    unsigned char             /* in */     *bgcolor,     /* background color */
5502
    int                       /* in */     loop_control, /* one of enum loop_control */
5503
    sixel_load_image_function /* in */     fn_load,      /* callback */
5504
    void                      /* in/out */ *context      /* private data for callback */
5505
)
5506
{
5507
    HRESULT                 hr         = E_FAIL;
5508
    SIXELSTATUS             status     = SIXEL_FALSE;
5509
    IWICImagingFactory     *factory    = NULL;
5510
    IWICStream             *stream     = NULL;
5511
    IWICBitmapDecoder      *decoder    = NULL;
5512
    IWICBitmapFrameDecode  *wicframe   = NULL;
5513
    IWICFormatConverter    *conv       = NULL;
5514
    IWICBitmapSource       *src        = NULL;
5515
    IWICPalette            *wicpalette = NULL;
5516
    WICColor               *wiccolors  = NULL;
5517
    IWICMetadataQueryReader *qdecoder  = NULL;
5518
    IWICMetadataQueryReader *qframe    = NULL;
5519
    UINT                    ncolors    = 0;
5520
    sixel_frame_t          *frame      = NULL;
5521
    int                     comp       = 4;
5522
    UINT                    actual     = 0;
5523
    UINT                    i;
5524
    UINT                    frame_count = 0;
5525
    int                     anim_loop_count = (-1);
5526
    int                     is_gif;
5527
    WICColor                c;
5528

5529
    (void) reqcolors;
5530
    (void) bgcolor;
5531

5532
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
5533
    if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
5534
        return status;
5535
    }
5536

5537
    status = sixel_frame_new(&frame, pchunk->allocator);
5538
    if (SIXEL_FAILED(status)) {
5539
        goto end;
5540
    }
5541

5542
    hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
5543
                          &IID_IWICImagingFactory, (void**)&factory);
5544
    if (FAILED(hr)) {
5545
        goto end;
5546
    }
5547

5548
    hr = factory->lpVtbl->CreateStream(factory, &stream);
5549
    if (FAILED(hr)) {
5550
        goto end;
5551
    }
5552

5553
    hr = stream->lpVtbl->InitializeFromMemory(stream,
5554
                                              (BYTE*)pchunk->buffer,
5555
                                              (DWORD)pchunk->size);
5556
    if (FAILED(hr)) {
5557
        goto end;
5558
    }
5559

5560
    hr = factory->lpVtbl->CreateDecoderFromStream(factory,
5561
                                                  (IStream*)stream,
5562
                                                  NULL,
5563
                                                  WICDecodeMetadataCacheOnDemand,
5564
                                                  &decoder);
5565
    if (FAILED(hr)) {
5566
        goto end;
5567
    }
5568

5569
    is_gif = (memcmp("GIF", pchunk->buffer, 3) == 0);
5570

5571
    if (is_gif) {
5572
        hr = decoder->lpVtbl->GetFrameCount(decoder, &frame_count);
5573
        if (FAILED(hr)) {
5574
            goto end;
5575
        }
5576
        if (fstatic) {
5577
            frame_count = 1;
5578
        }
5579

5580
        hr = decoder->lpVtbl->GetMetadataQueryReader(decoder, &qdecoder);
5581
        if (SUCCEEDED(hr)) {
5582
            PROPVARIANT pv;
5583
            PropVariantInit(&pv);
5584
            hr = qdecoder->lpVtbl->GetMetadataByName(
5585
                qdecoder,
5586
                L"/appext/Application/NETSCAPE2.0/Loop",
5587
                &pv);
5588
            if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5589
                anim_loop_count = pv.uiVal;
5590
            }
5591
            PropVariantClear(&pv);
5592
            qdecoder->lpVtbl->Release(qdecoder);
5593
            qdecoder = NULL;
5594
        }
5595

5596
        frame->loop_count = 0;
5597
        for (;;) {
5598
            frame->frame_no = 0;
5599
            for (i = 0; i < frame_count; ++i) {
5600
                hr = decoder->lpVtbl->GetFrame(decoder, i, &wicframe);
5601
                if (FAILED(hr)) {
5602
                    goto end;
5603
                }
5604

5605
                hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5606
                if (FAILED(hr)) {
5607
                    goto end;
5608
                }
5609
                hr = conv->lpVtbl->Initialize(conv,
5610
                                              (IWICBitmapSource*)wicframe,
5611
                                              &GUID_WICPixelFormat32bppRGBA,
5612
                                              WICBitmapDitherTypeNone,
5613
                                              NULL,
5614
                                              0.0,
5615
                                              WICBitmapPaletteTypeCustom);
5616
                if (FAILED(hr)) {
5617
                    goto end;
5618
                }
5619

5620
                src = (IWICBitmapSource*)conv;
5621
                comp = 4;
5622
                frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5623

5624
                hr = src->lpVtbl->GetSize(
5625
                    src,
5626
                    (UINT *)&frame->width,
5627
                    (UINT *)&frame->height);
5628
                if (FAILED(hr)) {
5629
                    goto end;
5630
                }
5631

5632
                if (frame->width <= 0 || frame->height <= 0 ||
5633
                    frame->width > SIXEL_WIDTH_LIMIT ||
5634
                    frame->height > SIXEL_HEIGHT_LIMIT) {
5635
                    sixel_helper_set_additional_message(
5636
                        "load_with_wic: an invalid width or height parameter detected.");
5637
                    status = SIXEL_BAD_INPUT;
5638
                    goto end;
5639
                }
5640

5641
                frame->pixels = sixel_allocator_malloc(
5642
                    pchunk->allocator,
5643
                    (size_t)(frame->height * frame->width * comp));
5644
                if (frame->pixels == NULL) {
5645
                    hr = E_OUTOFMEMORY;
5646
                    goto end;
5647
                }
5648

5649
                {
5650
                    WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
5651
                    hr = src->lpVtbl->CopyPixels(
5652
                        src,
5653
                        &rc,
5654
                        frame->width * comp,
5655
                        (UINT)frame->width * frame->height * comp,
5656
                        frame->pixels);
5657
                    if (FAILED(hr)) {
5658
                        goto end;
5659
                    }
5660
                }
5661

5662
                frame->delay = 0;
5663
                hr = wicframe->lpVtbl->GetMetadataQueryReader(wicframe, &qframe);
5664
                if (SUCCEEDED(hr)) {
5665
                    PROPVARIANT pv;
5666
                    PropVariantInit(&pv);
5667
                    hr = qframe->lpVtbl->GetMetadataByName(
5668
                        qframe,
5669
                        L"/grctlext/Delay",
5670
                        &pv);
5671
                    if (SUCCEEDED(hr) && pv.vt == VT_UI2) {
5672
                        frame->delay = (int)(pv.uiVal) * 10;
5673
                    }
5674
                    PropVariantClear(&pv);
5675
                    qframe->lpVtbl->Release(qframe);
5676
                    qframe = NULL;
5677
                }
5678

5679
                frame->multiframe = 1;
5680
                status = fn_load(frame, context);
5681
                if (SIXEL_FAILED(status)) {
5682
                    goto end;
5683
                }
5684
                frame->pixels = NULL;
5685
                frame->palette = NULL;
5686

5687
                if (conv) {
5688
                    conv->lpVtbl->Release(conv);
5689
                    conv = NULL;
5690
                }
5691
                if (wicframe) {
5692
                    wicframe->lpVtbl->Release(wicframe);
5693
                    wicframe = NULL;
5694
                }
5695

5696
                frame->frame_no++;
5697
            }
5698

5699
            ++frame->loop_count;
5700

5701
            if (anim_loop_count < 0) {
5702
                break;
5703
            }
5704
            if (loop_control == SIXEL_LOOP_DISABLE || frame->frame_no == 1) {
5705
                break;
5706
            }
5707
            if (loop_control == SIXEL_LOOP_AUTO &&
5708
                frame->loop_count == anim_loop_count) {
5709
                break;
5710
            }
5711
        }
5712

5713
        status = SIXEL_OK;
5714
        goto end;
5715
    }
5716

5717
    hr = decoder->lpVtbl->GetFrame(decoder, 0, &wicframe);
5718
    if (FAILED(hr)) {
5719
        goto end;
5720
    }
5721

5722
    if (fuse_palette) {
5723
        hr = factory->lpVtbl->CreatePalette(factory, &wicpalette);
5724
        if (SUCCEEDED(hr)) {
5725
            hr = wicframe->lpVtbl->CopyPalette(wicframe, wicpalette);
5726
        }
5727
        if (SUCCEEDED(hr)) {
5728
            hr = wicpalette->lpVtbl->GetColorCount(wicpalette, &ncolors);
5729
        }
5730
        if (SUCCEEDED(hr) && ncolors > 0 && ncolors <= 256) {
5731
            hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5732
            if (SUCCEEDED(hr)) {
5733
                hr = conv->lpVtbl->Initialize(conv,
5734
                                              (IWICBitmapSource*)wicframe,
5735
                                              &GUID_WICPixelFormat8bppIndexed,
5736
                                              WICBitmapDitherTypeNone,
5737
                                              wicpalette,
5738
                                              0.0,
5739
                                              WICBitmapPaletteTypeCustom);
5740
            }
5741
            if (SUCCEEDED(hr)) {
5742
                src = (IWICBitmapSource*)conv;
5743
                comp = 1;
5744
                frame->pixelformat = SIXEL_PIXELFORMAT_PAL8;
5745
                frame->palette = sixel_allocator_malloc(
5746
                    pchunk->allocator,
5747
                    (size_t)ncolors * 3);
5748
                if (frame->palette == NULL) {
5749
                    hr = E_OUTOFMEMORY;
5750
                } else {
5751
                    wiccolors = (WICColor *)sixel_allocator_malloc(
5752
                        pchunk->allocator,
5753
                        (size_t)ncolors * sizeof(WICColor));
5754
                    if (wiccolors == NULL) {
5755
                        hr = E_OUTOFMEMORY;
5756
                    } else {
5757
                        actual = 0;
5758
                        hr = wicpalette->lpVtbl->GetColors(
5759
                            wicpalette, ncolors, wiccolors, &actual);
5760
                        if (SUCCEEDED(hr) && actual == ncolors) {
5761
                            for (i = 0; i < ncolors; ++i) {
5762
                                c = wiccolors[i];
5763
                                frame->palette[i * 3 + 0] = (unsigned char)((c >> 16) & 0xFF);
5764
                                frame->palette[i * 3 + 1] = (unsigned char)((c >> 8) & 0xFF);
5765
                                frame->palette[i * 3 + 2] = (unsigned char)(c & 0xFF);
5766
                            }
5767
                            frame->ncolors = (int)ncolors;
5768
                        } else {
5769
                            hr = E_FAIL;
5770
                        }
5771
                    }
5772
                }
5773
            }
5774
            if (FAILED(hr)) {
5775
                if (conv) {
5776
                    conv->lpVtbl->Release(conv);
5777
                    conv = NULL;
5778
                }
5779
                sixel_allocator_free(pchunk->allocator, frame->palette);
5780
                frame->palette = NULL;
5781
                sixel_allocator_free(pchunk->allocator, wiccolors);
5782
                wiccolors = NULL;
5783
                src = NULL;
5784
            }
5785
        }
5786
    }
5787

5788
    if (src == NULL) {
5789
        hr = factory->lpVtbl->CreateFormatConverter(factory, &conv);
5790
        if (FAILED(hr)) {
5791
            goto end;
5792
        }
5793

5794
        hr = conv->lpVtbl->Initialize(conv, (IWICBitmapSource*)wicframe,
5795
                                      &GUID_WICPixelFormat32bppRGBA,
5796
                                      WICBitmapDitherTypeNone, NULL, 0.0,
5797
                                      WICBitmapPaletteTypeCustom);
5798
        if (FAILED(hr)) {
5799
            goto end;
5800
        }
5801

5802
        src = (IWICBitmapSource*)conv;
5803
        comp = 4;
5804
        frame->pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
5805
    }
5806

5807
    hr = src->lpVtbl->GetSize(
5808
        src, (UINT *)&frame->width, (UINT *)&frame->height);
5809
    if (FAILED(hr)) {
5810
        goto end;
5811
    }
5812

5813
    /* check size */
5814
    if (frame->width <= 0) {
5815
        sixel_helper_set_additional_message(
5816
            "load_with_wic: an invalid width parameter detected.");
5817
        status = SIXEL_BAD_INPUT;
5818
        goto end;
5819
    }
5820
    if (frame->height <= 0) {
5821
        sixel_helper_set_additional_message(
5822
            "load_with_wic: an invalid width parameter detected.");
5823
        status = SIXEL_BAD_INPUT;
5824
        goto end;
5825
    }
5826
    if (frame->width > SIXEL_WIDTH_LIMIT) {
5827
        sixel_helper_set_additional_message(
5828
            "load_with_wic: given width parameter is too huge.");
5829
        status = SIXEL_BAD_INPUT;
5830
        goto end;
5831
    }
5832
    if (frame->height > SIXEL_HEIGHT_LIMIT) {
5833
        sixel_helper_set_additional_message(
5834
            "load_with_wic: given height parameter is too huge.");
5835
        status = SIXEL_BAD_INPUT;
5836
        goto end;
5837
    }
5838

5839
    frame->pixels = sixel_allocator_malloc(
5840
        pchunk->allocator,
5841
        (size_t)(frame->height * frame->width * comp));
5842

5843
    {
5844
        WICRect rc = { 0, 0, (INT)frame->width, (INT)frame->height };
5845
        hr = src->lpVtbl->CopyPixels(
5846
            src,
5847
            &rc,                                        /* prc */
5848
            frame->width * comp,                        /* cbStride */
5849
            (UINT)frame->width * frame->height * comp,  /* cbBufferSize */
5850
            frame->pixels);                             /* pbBuffer */
5851
        if (FAILED(hr)) {
5852
            goto end;
5853
        }
5854
    }
5855

5856
    status = fn_load(frame, context);
5857
    if (SIXEL_FAILED(status)) {
5858
        goto end;
5859
    }
5860

5861
end:
5862
    if (conv) {
5863
         conv->lpVtbl->Release(conv);
5864
    }
5865
    if (wicpalette) {
5866
         wicpalette->lpVtbl->Release(wicpalette);
5867
    }
5868
    if (wiccolors) {
5869
         sixel_allocator_free(pchunk->allocator, wiccolors);
5870
    }
5871
    if (wicframe) {
5872
         wicframe->lpVtbl->Release(wicframe);
5873
    }
5874
    if (qdecoder) {
5875
         qdecoder->lpVtbl->Release(qdecoder);
5876
    }
5877
    if (qframe) {
5878
         qframe->lpVtbl->Release(qframe);
5879
    }
5880
    if (stream) {
5881
         stream->lpVtbl->Release(stream);
5882
    }
5883
    if (factory) {
5884
         factory->lpVtbl->Release(factory);
5885
    }
5886
    sixel_frame_unref(frame);
5887

5888
    CoUninitialize();
5889

5890
    if (FAILED(hr)) {
5891
        return SIXEL_FALSE;
5892
    }
5893

5894
    return SIXEL_OK;
5895
}
5896

5897
#endif /* HAVE_WIC */
5898

5899
#if HAVE_WIC
5900
static int
5901
loader_can_try_wic(sixel_chunk_t const *chunk)
5902
{
5903
    if (chunk == NULL) {
5904
        return 0;
5905
    }
5906
    if (chunk_is_gif(chunk)) {
5907
        return 0;
5908
    }
5909
    return 1;
5910
}
5911
#endif
5912

5913
static sixel_loader_entry_t const sixel_loader_entries[] = {
5914
#if HAVE_WIC
5915
    { "wic", load_with_wic, loader_can_try_wic },
5916
#endif
5917
#if HAVE_COREGRAPHICS
5918
    { "coregraphics", load_with_coregraphics, NULL },
5919
#endif
5920
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
5921
    { "quicklook", load_with_quicklook, NULL },
5922
#endif
5923
#ifdef HAVE_GDK_PIXBUF2
5924
    { "gdk-pixbuf2", load_with_gdkpixbuf, NULL },
5925
#endif
5926
#if HAVE_GD
5927
    { "gd", load_with_gd, NULL },
5928
#endif
5929
#if HAVE_JPEG
5930
    { "libjpeg", load_with_libjpeg, loader_can_try_libjpeg },
5931
#endif
5932
#if HAVE_LIBPNG
5933
    { "libpng", load_with_libpng, loader_can_try_libpng },
5934
#endif
5935
    { "builtin", load_with_builtin, NULL },
5936
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
5937
    { "gnome-thumbnailer", load_with_gnome_thumbnailer, NULL },
5938
#endif
5939
};
5940

5941
SIXELAPI SIXELSTATUS
5942
sixel_loader_new(
453✔
5943
    sixel_loader_t   /* out */ **pploader,
5944
    sixel_allocator_t/* in */  *allocator)
5945
{
5946
    SIXELSTATUS status = SIXEL_FALSE;
453✔
5947
    sixel_loader_t *loader;
5948
    sixel_allocator_t *local_allocator;
5949

5950
    loader = NULL;
453✔
5951
    local_allocator = allocator;
453✔
5952

5953
    if (pploader == NULL) {
453!
5954
        sixel_helper_set_additional_message(
×
5955
            "sixel_loader_new: pploader is null.");
5956
        status = SIXEL_BAD_ARGUMENT;
×
5957
        goto end;
×
5958
    }
5959

5960
    if (local_allocator == NULL) {
453!
5961
        status = sixel_allocator_new(&local_allocator,
×
5962
                                     NULL,
5963
                                     NULL,
5964
                                     NULL,
5965
                                     NULL);
5966
        if (SIXEL_FAILED(status)) {
×
5967
            goto end;
×
5968
        }
5969
    } else {
5970
        sixel_allocator_ref(local_allocator);
453✔
5971
    }
5972

5973
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
453✔
5974
                                                      sizeof(*loader));
5975
    if (loader == NULL) {
453!
5976
        sixel_helper_set_additional_message(
×
5977
            "sixel_loader_new: sixel_allocator_malloc() failed.");
5978
        status = SIXEL_BAD_ALLOCATION;
×
5979
        sixel_allocator_unref(local_allocator);
×
5980
        goto end;
×
5981
    }
5982

5983
    loader->ref = 1;
453✔
5984
    loader->fstatic = 0;
453✔
5985
    loader->fuse_palette = 0;
453✔
5986
    loader->reqcolors = SIXEL_PALETTE_MAX;
453✔
5987
    loader->bgcolor[0] = 0;
453✔
5988
    loader->bgcolor[1] = 0;
453✔
5989
    loader->bgcolor[2] = 0;
453✔
5990
    loader->has_bgcolor = 0;
453✔
5991
    loader->loop_control = SIXEL_LOOP_AUTO;
453✔
5992
    loader->finsecure = 0;
453✔
5993
    loader->cancel_flag = NULL;
453✔
5994
    loader->context = NULL;
453✔
5995
    loader->assessment = NULL;
453✔
5996
    loader->loader_order = NULL;
453✔
5997
    loader->allocator = local_allocator;
453✔
5998
    loader->last_loader_name[0] = '\0';
453✔
5999
    loader->last_source_path[0] = '\0';
453✔
6000
    loader->last_input_bytes = 0u;
453✔
6001

6002
    *pploader = loader;
453✔
6003
    status = SIXEL_OK;
453✔
6004

6005
end:
302✔
6006
    return status;
453✔
6007
}
6008

6009
SIXELAPI void
6010
sixel_loader_ref(
4,962✔
6011
    sixel_loader_t /* in */ *loader)
6012
{
6013
    if (loader == NULL) {
4,962!
6014
        return;
×
6015
    }
6016

6017
    ++loader->ref;
4,962✔
6018
}
1,654✔
6019

6020
SIXELAPI void
6021
sixel_loader_unref(
5,415✔
6022
    sixel_loader_t /* in */ *loader)
6023
{
6024
    sixel_allocator_t *allocator;
6025

6026
    if (loader == NULL) {
5,415!
6027
        return;
×
6028
    }
6029

6030
    if (--loader->ref == 0) {
5,415✔
6031
        allocator = loader->allocator;
453✔
6032
        sixel_allocator_free(allocator, loader->loader_order);
453✔
6033
        sixel_allocator_free(allocator, loader);
453✔
6034
        sixel_allocator_unref(allocator);
453✔
6035
    }
151✔
6036
}
1,805✔
6037

6038
SIXELAPI SIXELSTATUS
6039
sixel_loader_setopt(
4,509✔
6040
    sixel_loader_t /* in */ *loader,
6041
    int            /* in */ option,
6042
    void const     /* in */ *value)
6043
{
6044
    SIXELSTATUS status = SIXEL_FALSE;
4,509✔
6045
    int const *flag;
6046
    unsigned char const *color;
6047
    char const *order;
6048
    char *copy;
6049
    sixel_allocator_t *allocator;
6050

6051
    flag = NULL;
4,509✔
6052
    color = NULL;
4,509✔
6053
    order = NULL;
4,509✔
6054
    copy = NULL;
4,509✔
6055
    allocator = NULL;
4,509✔
6056

6057
    if (loader == NULL) {
4,509!
6058
        sixel_helper_set_additional_message(
×
6059
            "sixel_loader_setopt: loader is null.");
6060
        status = SIXEL_BAD_ARGUMENT;
×
6061
        goto end0;
×
6062
    }
6063

6064
    sixel_loader_ref(loader);
4,509✔
6065

6066
    switch (option) {
4,509!
6067
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
302✔
6068
        flag = (int const *)value;
453✔
6069
        loader->fstatic = flag != NULL ? *flag : 0;
453!
6070
        status = SIXEL_OK;
453✔
6071
        break;
453✔
6072
    case SIXEL_LOADER_OPTION_USE_PALETTE:
302✔
6073
        flag = (int const *)value;
453✔
6074
        loader->fuse_palette = flag != NULL ? *flag : 0;
453!
6075
        status = SIXEL_OK;
453✔
6076
        break;
453✔
6077
    case SIXEL_LOADER_OPTION_REQCOLORS:
302✔
6078
        flag = (int const *)value;
453✔
6079
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
453!
6080
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
453!
6081
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
6082
        }
6083
        status = SIXEL_OK;
453✔
6084
        break;
453✔
6085
    case SIXEL_LOADER_OPTION_BGCOLOR:
302✔
6086
        if (value == NULL) {
453✔
6087
            loader->has_bgcolor = 0;
435✔
6088
        } else {
145✔
6089
            color = (unsigned char const *)value;
18✔
6090
            loader->bgcolor[0] = color[0];
18✔
6091
            loader->bgcolor[1] = color[1];
18✔
6092
            loader->bgcolor[2] = color[2];
18✔
6093
            loader->has_bgcolor = 1;
18✔
6094
        }
6095
        status = SIXEL_OK;
453✔
6096
        break;
453✔
6097
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
302✔
6098
        flag = (int const *)value;
453✔
6099
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
453!
6100
        status = SIXEL_OK;
453✔
6101
        break;
453✔
6102
    case SIXEL_LOADER_OPTION_INSECURE:
302✔
6103
        flag = (int const *)value;
453✔
6104
        loader->finsecure = flag != NULL ? *flag : 0;
453!
6105
        status = SIXEL_OK;
453✔
6106
        break;
453✔
6107
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
302✔
6108
        loader->cancel_flag = (int const *)value;
453✔
6109
        status = SIXEL_OK;
453✔
6110
        break;
453✔
6111
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
302✔
6112
        allocator = loader->allocator;
453✔
6113
        sixel_allocator_free(allocator, loader->loader_order);
453✔
6114
        loader->loader_order = NULL;
453✔
6115
        if (value != NULL) {
453!
6116
            order = (char const *)value;
×
6117
            copy = loader_strdup(order, allocator);
×
6118
            if (copy == NULL) {
×
6119
                sixel_helper_set_additional_message(
×
6120
                    "sixel_loader_setopt: loader_strdup() failed.");
6121
                status = SIXEL_BAD_ALLOCATION;
×
6122
                goto end;
×
6123
            }
6124
            loader->loader_order = copy;
×
6125
        }
6126
        status = SIXEL_OK;
453✔
6127
        break;
453✔
6128
    case SIXEL_LOADER_OPTION_CONTEXT:
302✔
6129
        loader->context = (void *)value;
453✔
6130
        loader->assessment = NULL;
453✔
6131
        status = SIXEL_OK;
453✔
6132
        break;
453✔
6133
    case SIXEL_LOADER_OPTION_ASSESSMENT:
288✔
6134
        loader->assessment = (sixel_assessment_t *)value;
432✔
6135
        status = SIXEL_OK;
432✔
6136
        break;
432✔
6137
    default:
6138
        sixel_helper_set_additional_message(
×
6139
            "sixel_loader_setopt: unknown option.");
6140
        status = SIXEL_BAD_ARGUMENT;
×
6141
        goto end;
×
6142
    }
1,503✔
6143

6144
end:
3,006✔
6145
    sixel_loader_unref(loader);
4,509✔
6146

6147
end0:
3,006✔
6148
    return status;
4,509✔
6149
}
6150

6151
SIXELAPI char const *
6152
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
834✔
6153
{
6154
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
834!
6155
        return NULL;
×
6156
    }
6157
    return loader->last_loader_name;
834✔
6158
}
278✔
6159

6160
SIXELAPI char const *
6161
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
696✔
6162
{
6163
    if (loader == NULL || loader->last_source_path[0] == '\0') {
696!
6164
        return NULL;
138✔
6165
    }
6166
    return loader->last_source_path;
558✔
6167
}
232✔
6168

6169
SIXELAPI size_t
6170
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
417✔
6171
{
6172
    if (loader == NULL) {
417!
6173
        return 0u;
×
6174
    }
6175
    return loader->last_input_bytes;
417✔
6176
}
139✔
6177

6178
SIXELAPI SIXELSTATUS
6179
sixel_loader_load_file(
453✔
6180
    sixel_loader_t         /* in */ *loader,
6181
    char const             /* in */ *filename,
6182
    sixel_load_image_function /* in */ fn_load)
6183
{
6184
    SIXELSTATUS status = SIXEL_FALSE;
453✔
6185
    sixel_chunk_t *pchunk;
6186
    sixel_loader_entry_t const *plan[
6187
        sizeof(sixel_loader_entries) / sizeof(sixel_loader_entries[0])];
6188
    size_t entry_count;
6189
    size_t plan_length;
6190
    size_t plan_index;
6191
    unsigned char *bgcolor;
6192
    int reqcolors;
6193
    sixel_assessment_t *assessment;
6194

6195
    pchunk = NULL;
453✔
6196
    entry_count = 0;
453✔
6197
    plan_length = 0;
453✔
6198
    plan_index = 0;
453✔
6199
    bgcolor = NULL;
453✔
6200
    reqcolors = 0;
453✔
6201
    assessment = NULL;
453✔
6202

6203
    if (loader == NULL) {
453!
6204
        sixel_helper_set_additional_message(
×
6205
            "sixel_loader_load_file: loader is null.");
6206
        status = SIXEL_BAD_ARGUMENT;
×
6207
        goto end0;
×
6208
    }
6209

6210
    sixel_loader_ref(loader);
453✔
6211

6212
    entry_count = sizeof(sixel_loader_entries) /
453✔
6213
                  sizeof(sixel_loader_entries[0]);
6214

6215
    reqcolors = loader->reqcolors;
453✔
6216
    if (reqcolors > SIXEL_PALETTE_MAX) {
453!
6217
        reqcolors = SIXEL_PALETTE_MAX;
×
6218
    }
6219

6220
    assessment = loader->assessment;
453✔
6221

6222
    /*
6223
     *  Assessment pipeline sketch:
6224
     *
6225
     *      +-------------+        +--------------+
6226
     *      | chunk read | ----->  | image decode |
6227
     *      +-------------+        +--------------+
6228
     *
6229
     *  The loader owns the hand-off.  Chunk I/O ends before any decoder runs,
6230
     *  so we time the read span in the encoder and pivot to decode once the
6231
     *  chunk is populated.
6232
     */
6233

6234
    status = sixel_chunk_new(&pchunk,
453✔
6235
                             filename,
151✔
6236
                             loader->finsecure,
151✔
6237
                             loader->cancel_flag,
151✔
6238
                             loader->allocator);
151✔
6239
    if (status != SIXEL_OK) {
453✔
6240
        goto end;
6✔
6241
    }
6242

6243
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
447!
6244
        status = SIXEL_OK;
×
6245
        goto end;
×
6246
    }
6247

6248
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
447!
6249
        status = SIXEL_LOGIC_ERROR;
×
6250
        goto end;
×
6251
    }
6252

6253
    if (loader->has_bgcolor) {
447✔
6254
        bgcolor = loader->bgcolor;
18✔
6255
    }
6✔
6256

6257
    status = SIXEL_FALSE;
309✔
6258
    if (assessment != NULL) {
309✔
6259
        sixel_assessment_stage_transition(
3✔
6260
            assessment,
1✔
6261
            SIXEL_ASSESSMENT_STAGE_IMAGE_DECODE);
6262
    }
1✔
6263
    plan_length = loader_build_plan(loader->loader_order,
596✔
6264
                                    sixel_loader_entries,
6265
                                    entry_count,
149✔
6266
                                    plan,
149✔
6267
                                    entry_count);
149✔
6268

6269
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
578✔
6270
        if (plan[plan_index] == NULL) {
569!
6271
            continue;
×
6272
        }
6273
        if (plan[plan_index]->predicate != NULL &&
569!
6274
            plan[plan_index]->predicate(pchunk) == 0) {
×
6275
            continue;
×
6276
        }
6277
        loader_trace_try(plan[plan_index]->name);
569✔
6278
        status = plan[plan_index]->backend(pchunk,
834✔
6279
                                           loader->fstatic,
265✔
6280
                                           loader->fuse_palette,
265✔
6281
                                           reqcolors,
265✔
6282
                                           bgcolor,
265✔
6283
                                           loader->loop_control,
265✔
6284
                                           fn_load,
265✔
6285
                                           loader->context);
265✔
6286
        loader_trace_result(plan[plan_index]->name, status);
569✔
6287
        if (SIXEL_SUCCEEDED(status)) {
569✔
6288
            break;
438✔
6289
        }
6290
    }
119✔
6291

6292
    if (SIXEL_FAILED(status)) {
447✔
6293
        goto end;
9✔
6294
    }
6295

6296
    if (plan_index < plan_length &&
584!
6297
            plan[plan_index] != NULL &&
438!
6298
            plan[plan_index]->name != NULL) {
438!
6299
        (void)snprintf(loader->last_loader_name,
438✔
6300
                       sizeof(loader->last_loader_name),
6301
                       "%s",
6302
                       plan[plan_index]->name);
292✔
6303
    } else {
146✔
6304
        loader->last_loader_name[0] = '\0';
×
6305
    }
6306
    loader->last_input_bytes = pchunk->size;
438✔
6307
    if (pchunk->source_path != NULL) {
584✔
6308
        size_t path_len;
6309

6310
        path_len = strlen(pchunk->source_path);
300✔
6311
        if (path_len >= sizeof(loader->last_source_path)) {
300!
6312
            path_len = sizeof(loader->last_source_path) - 1u;
×
6313
        }
6314
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
300✔
6315
        loader->last_source_path[path_len] = '\0';
300✔
6316
    } else {
100✔
6317
        loader->last_source_path[0] = '\0';
138✔
6318
    }
6319

6320
end:
302✔
6321
    sixel_chunk_destroy(pchunk);
453✔
6322
    sixel_loader_unref(loader);
453✔
6323

6324
end0:
302✔
6325
    return status;
453✔
6326
}
6327

6328
/* load image from file */
6329

6330
SIXELAPI SIXELSTATUS
6331
sixel_helper_load_image_file(
×
6332
    char const                /* in */     *filename,     /* source file name */
6333
    int                       /* in */     fstatic,       /* whether to extract static image
6334
                                                             from animated gif */
6335
    int                       /* in */     fuse_palette,  /* whether to use paletted image,
6336
                                                             set non-zero value to try to get
6337
                                                             paletted image */
6338
    int                       /* in */     reqcolors,     /* requested number of colors,
6339
                                                             should be equal or less than
6340
                                                             SIXEL_PALETTE_MAX */
6341
    unsigned char             /* in */     *bgcolor,      /* background color, may be NULL */
6342
    int                       /* in */     loop_control,  /* one of enum loopControl */
6343
    sixel_load_image_function /* in */     fn_load,       /* callback */
6344
    int                       /* in */     finsecure,     /* true if do not verify SSL */
6345
    int const                 /* in */     *cancel_flag,  /* cancel flag, may be NULL */
6346
    void                      /* in/out */ *context,      /* private data which is passed to
6347
                                                             callback function as an
6348
                                                             argument, may be NULL */
6349
    sixel_allocator_t         /* in */     *allocator     /* allocator object, may be NULL */
6350
)
6351
{
6352
    SIXELSTATUS status = SIXEL_FALSE;
×
6353
    sixel_loader_t *loader;
6354

6355
    loader = NULL;
×
6356

6357
    status = sixel_loader_new(&loader, allocator);
×
6358
    if (SIXEL_FAILED(status)) {
×
6359
        goto end;
×
6360
    }
6361

6362
    status = sixel_loader_setopt(loader,
×
6363
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
6364
                                 &fstatic);
6365
    if (SIXEL_FAILED(status)) {
×
6366
        goto end;
×
6367
    }
6368

6369
    status = sixel_loader_setopt(loader,
×
6370
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
6371
                                 &fuse_palette);
6372
    if (SIXEL_FAILED(status)) {
×
6373
        goto end;
×
6374
    }
6375

6376
    status = sixel_loader_setopt(loader,
×
6377
                                 SIXEL_LOADER_OPTION_REQCOLORS,
6378
                                 &reqcolors);
6379
    if (SIXEL_FAILED(status)) {
×
6380
        goto end;
×
6381
    }
6382

6383
    status = sixel_loader_setopt(loader,
×
6384
                                 SIXEL_LOADER_OPTION_BGCOLOR,
6385
                                 bgcolor);
6386
    if (SIXEL_FAILED(status)) {
×
6387
        goto end;
×
6388
    }
6389

6390
    status = sixel_loader_setopt(loader,
×
6391
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
6392
                                 &loop_control);
6393
    if (SIXEL_FAILED(status)) {
×
6394
        goto end;
×
6395
    }
6396

6397
    status = sixel_loader_setopt(loader,
×
6398
                                 SIXEL_LOADER_OPTION_INSECURE,
6399
                                 &finsecure);
6400
    if (SIXEL_FAILED(status)) {
×
6401
        goto end;
×
6402
    }
6403

6404
    status = sixel_loader_setopt(loader,
×
6405
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
6406
                                 cancel_flag);
6407
    if (SIXEL_FAILED(status)) {
×
6408
        goto end;
×
6409
    }
6410

6411
    status = sixel_loader_setopt(loader,
×
6412
                                 SIXEL_LOADER_OPTION_CONTEXT,
6413
                                 context);
6414
    if (SIXEL_FAILED(status)) {
×
6415
        goto end;
×
6416
    }
6417

6418
    status = sixel_loader_load_file(loader, filename, fn_load);
×
6419

6420
end:
6421
    sixel_loader_unref(loader);
×
6422

6423
    return status;
×
6424
}
6425

6426

6427
SIXELAPI size_t
6428
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
6✔
6429
{
6430
    size_t entry_count;
6431
    size_t limit;
6432
    size_t index;
6433

6434
    entry_count = sizeof(sixel_loader_entries) /
6✔
6435
                  sizeof(sixel_loader_entries[0]);
6436

6437
    if (names != NULL && max_names > 0) {
6!
6438
        limit = entry_count;
3✔
6439
        if (limit > max_names) {
3!
6440
            limit = max_names;
×
6441
        }
6442
        for (index = 0; index < limit; ++index) {
11✔
6443
            names[index] = sixel_loader_entries[index].name;
8✔
6444
        }
4✔
6445
    }
1✔
6446

6447
    return entry_count;
6✔
6448
}
6449

6450
#if HAVE_TESTS
6451
static int
6452
test1(void)
×
6453
{
6454
    int nret = EXIT_FAILURE;
×
6455
    unsigned char *ptr = malloc(16);
×
6456

6457
    nret = EXIT_SUCCESS;
×
6458
    goto error;
×
6459

6460
    nret = EXIT_SUCCESS;
6461

6462
error:
6463
    free(ptr);
×
6464
    return nret;
×
6465
}
6466

6467

6468
SIXELAPI int
6469
sixel_loader_tests_main(void)
×
6470
{
6471
    int nret = EXIT_FAILURE;
×
6472
    size_t i;
6473
    typedef int (* testcase)(void);
6474

6475
    static testcase const testcases[] = {
6476
        test1,
6477
    };
6478

6479
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
6480
        nret = testcases[i]();
×
6481
        if (nret != EXIT_SUCCESS) {
×
6482
            goto error;
×
6483
        }
6484
    }
6485

6486
    nret = EXIT_SUCCESS;
×
6487

6488
error:
6489
    return nret;
×
6490
}
6491
#endif  /* HAVE_TESTS */
6492

6493
/* emacs Local Variables:      */
6494
/* emacs mode: c               */
6495
/* emacs tab-width: 4          */
6496
/* emacs indent-tabs-mode: nil */
6497
/* emacs c-basic-offset: 4     */
6498
/* emacs End:                  */
6499
/* vim: set expandtab ts=4 sts=4 sw=4 : */
6500
/* 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