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

saitoha / libsixel / 23878382859

02 Apr 2026 12:56AM UTC coverage: 82.595% (-0.5%) from 83.097%
23878382859

push

github

saitoha
fuzz: fix afl harness build with clang -Werror

45101 of 102773 branches covered (43.88%)

77990 of 94425 relevant lines covered (82.59%)

3568622.89 hits per line

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

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

25
#if defined(HAVE_CONFIG_H)
26
#include "config.h"
27
#endif
28

29
#if HAVE_CTYPE_H
30
#include <ctype.h>
31
#endif  /* HAVE_CTYPE_H */
32
#if HAVE_ERRNO_H
33
#include <errno.h>
34
#endif  /* HAVE_ERRNO_H */
35
#if HAVE_LIMITS_H
36
#include <limits.h>
37
#endif  /* HAVE_LIMITS_H */
38
#if HAVE_STDIO_H
39
#include <stdio.h>
40
#endif  /* HAVE_STDIO_H */
41
#if HAVE_STDLIB_H
42
#include <stdlib.h>
43
#endif  /* HAVE_STDLIB_H */
44
#if HAVE_STDINT_H
45
#include <stdint.h>
46
#endif  /* HAVE_STDINT_H */
47
#if HAVE_STRING_H
48
#include <string.h>
49
#endif  /* HAVE_STRING_H */
50
#if HAVE_SYS_STAT_H
51
#include <sys/stat.h>
52
#endif  /* HAVE_SYS_STAT_H */
53

54
/* Keep SIZE_MAX available even on strict C99 environments. */
55
#ifndef SIZE_MAX
56
#define SIZE_MAX ((size_t)-1)
57
#endif
58

59
/* Keep palette mapfile reads bounded to avoid excessive memory growth. */
60
#define SIXEL_MAPFILE_READ_MAX_BYTES (16u * 1024u * 1024u)
61

62
#include <sixel.h>
63

64
#include "compat_stub.h"
65
#include "encoder.h"
66
#include "mapfile.h"
67

68
static int
69
sixel_path_has_extension(char const *path, char const *extension)
4,405✔
70
{
71
    size_t path_len;
2,589✔
72
    size_t ext_len;
2,589✔
73
    size_t index;
2,589✔
74

75
    path_len = 0u;
4,405✔
76
    ext_len = 0u;
4,405✔
77
    index = 0u;
4,405✔
78

79
    if (path == NULL || extension == NULL) {
4,405!
80
        return 0;
81
    }
82

83
    path_len = strlen(path);
4,405✔
84
    ext_len = strlen(extension);
4,405✔
85
    if (ext_len == 0u || path_len < ext_len) {
4,405!
86
        return 0;
84✔
87
    }
88

89
    for (index = 0u; index < ext_len; ++index) {
12,668!
90
        unsigned char path_ch;
6,600✔
91
        unsigned char ext_ch;
6,600✔
92

93
        path_ch = (unsigned char)path[path_len - ext_len + index];
11,248✔
94
        ext_ch = (unsigned char)extension[index];
11,248✔
95
        if (tolower(path_ch) != tolower(ext_ch)) {
11,248✔
96
            return 0;
2,183✔
97
        }
98
    }
6,573✔
99

100
    return 1;
1,136✔
101
}
2,857✔
102

103

104
/*
105
 * Palette specification parser
106
 *
107
 *   TYPE:PATH  -> explicit format prefix
108
 *   PATH       -> rely on extension or heuristics
109
 *
110
 * The ASCII diagram below shows how the prefix is peeled:
111
 *
112
 *   [type] : [path]
113
 *    ^-- left part selects decoder/encoder when present.
114
 */
115
char const *
116
sixel_palette_strip_prefix(char const *spec,
3,074✔
117
                           sixel_palette_format_t *format_hint)
118
{
119
    char const *colon;
1,818✔
120
    size_t type_len;
1,818✔
121
    size_t index;
1,818✔
122
    char lowered[16];
1,818✔
123

124
    colon = NULL;
3,074✔
125
    type_len = 0u;
3,074✔
126
    index = 0u;
3,074✔
127

128
    if (format_hint != NULL) {
3,074✔
129
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
1,974✔
130
    }
1,298✔
131
    if (spec == NULL) {
3,074!
132
        return NULL;
133
    }
134

135
    colon = strchr(spec, ':');
3,074✔
136
    if (colon == NULL) {
3,074✔
137
        return spec;
1,049✔
138
    }
139

140
    type_len = (size_t)(colon - spec);
1,879✔
141
    if (type_len == 0u || type_len >= sizeof(lowered)) {
1,879!
142
        return spec;
143
    }
144

145
    for (index = 0u; index < type_len; ++index) {
10,422!
146
        lowered[index] = (char)tolower((unsigned char)spec[index]);
8,543✔
147
    }
5,214✔
148
    lowered[type_len] = '\0';
1,879✔
149

150
    if (strcmp(lowered, "act") == 0) {
1,879✔
151
        if (format_hint != NULL) {
40✔
152
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
20✔
153
        }
11✔
154
        return colon + 1;
40✔
155
    }
156
    if (strcmp(lowered, "pal") == 0) {
1,839✔
157
        if (format_hint != NULL) {
20!
158
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
20✔
159
        }
11✔
160
        return colon + 1;
20✔
161
    }
162
    if (strcmp(lowered, "pal-jasc") == 0) {
1,819✔
163
        if (format_hint != NULL) {
77✔
164
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
57✔
165
        }
41✔
166
        return colon + 1;
77✔
167
    }
168
    if (strcmp(lowered, "pal-riff") == 0) {
1,742✔
169
        if (format_hint != NULL) {
773✔
170
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
573✔
171
        }
402✔
172
        return colon + 1;
773✔
173
    }
174
    if (strcmp(lowered, "gpl") == 0) {
969!
175
        if (format_hint != NULL) {
297✔
176
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
197✔
177
        }
118✔
178
        return colon + 1;
297✔
179
    }
180

181
    return spec;
336✔
182
}
1,903✔
183

184
sixel_palette_format_t
185
sixel_palette_format_from_extension(char const *path)
1,974✔
186
{
187
    if (path == NULL) {
1,974!
188
        return SIXEL_PALETTE_FORMAT_NONE;
189
    }
190

191
    if (sixel_path_has_extension(path, ".act")) {
1,974✔
192
        return SIXEL_PALETTE_FORMAT_ACT;
225✔
193
    }
194
    if (sixel_path_has_extension(path, ".pal")) {
1,683✔
195
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
761✔
196
    }
197
    if (sixel_path_has_extension(path, ".gpl")) {
748✔
198
        return SIXEL_PALETTE_FORMAT_GPL;
194✔
199
    }
200

201
    return SIXEL_PALETTE_FORMAT_NONE;
402✔
202
}
1,298✔
203

204
int
205
sixel_path_has_any_extension(char const *path)
1,794✔
206
{
207
    char const *slash_forward;
1,050✔
208
#if defined(_WIN32)
209
    char const *slash_backward;
284✔
210
#endif
211
    char const *start;
1,050✔
212
    char const *dot;
1,050✔
213

214
    slash_forward = NULL;
1,794✔
215
#if defined(_WIN32)
216
    slash_backward = NULL;
568✔
217
#endif
218
    start = path;
1,794✔
219
    dot = NULL;
1,794✔
220

221
    if (path == NULL) {
1,794!
222
        return 0;
223
    }
224

225
    slash_forward = strrchr(path, '/');
1,794✔
226
#if defined(_WIN32)
227
    slash_backward = strrchr(path, '\\');
568✔
228
    if (slash_backward != NULL &&
568✔
229
            (slash_forward == NULL || slash_backward > slash_forward)) {
108✔
230
        slash_forward = slash_backward;
231
    }
232
#endif
233
    if (slash_forward == NULL) {
1,794✔
234
        start = path;
14✔
235
    } else {
11✔
236
        start = slash_forward + 1;
1,774✔
237
    }
238

239
    dot = strrchr(start, '.');
1,794✔
240
    if (dot == NULL) {
1,794✔
241
        return 0;
122✔
242
    }
243

244
    if (dot[1] == '\0') {
1,640!
245
        return 0;
×
246
    }
247

248
    return 1;
1,290✔
249
}
1,199✔
250

251
static int
252
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
918✔
253
{
254
    if (data == NULL || size < 3u) {
918!
255
        return 0;
256
    }
257
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
918!
258
        return 1;
×
259
    }
260
    return 0;
742✔
261
}
640✔
262

263
static int
264
sixel_palette_tail_is_blank(char const *tail)
134✔
265
{
266
    if (tail == NULL) {
134!
267
        return 0;
268
    }
269

270
    while (*tail == ' ' || *tail == '\t') {
134!
271
        ++tail;
×
272
    }
273

274
    return *tail == '\0';
134✔
275
}
93✔
276

277

278
static int
279
sixel_palette_contains_nul_byte(unsigned char const *data, size_t size)
493✔
280
{
281
    size_t index;
237✔
282

283
    index = 0u;
493✔
284

285
    if (data == NULL) {
493✔
286
        return 0;
287
    }
288

289
    for (index = 0u; index < size; ++index) {
335,822,172!
290
        if (data[index] == '\0') {
335,821,673✔
291
            return 1;
94✔
292
        }
293
    }
184,709,520✔
294

295
    return 0;
399✔
296
}
424✔
297

298

299
/*
300
 * Materialize palette bytes from a stream.
301
 *
302
 * The flow looks like:
303
 *
304
 *   stream --> [scratch buffer] --> [resizable heap buffer]
305
 *                  ^ looped read        ^ returned payload
306
 */
307
SIXELSTATUS
308
sixel_palette_read_stream(FILE *stream,
1,474✔
309
                          sixel_allocator_t *allocator,
310
                          unsigned char **pdata,
311
                          size_t *psize)
312
{
313
    SIXELSTATUS status;
858✔
314
    unsigned char *buffer;
858✔
315
    unsigned char *grown;
858✔
316
    size_t capacity;
858✔
317
    size_t used;
858✔
318
    size_t read_bytes;
858✔
319
    size_t needed;
858✔
320
    size_t new_capacity;
858✔
321
    unsigned char scratch[4096];
858✔
322

323
    status = SIXEL_FALSE;
1,474✔
324
    buffer = NULL;
1,474✔
325
    grown = NULL;
1,474✔
326
    capacity = 0u;
1,474✔
327
    used = 0u;
1,474✔
328
    read_bytes = 0u;
1,474✔
329
    needed = 0u;
1,474✔
330
    new_capacity = 0u;
1,474✔
331

332
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
1,474!
333
        sixel_helper_set_additional_message(
×
334
            "sixel_palette_read_stream: invalid argument.");
335
        return SIXEL_BAD_ARGUMENT;
×
336
    }
337

338
    *pdata = NULL;
1,474✔
339
    *psize = 0u;
1,474✔
340

341
    while (1) {
346,930!
342
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
318,303✔
343
        if (read_bytes == 0u) {
318,303✔
344
            if (ferror(stream)) {
1,417!
345
                sixel_helper_set_additional_message(
×
346
                    "sixel_palette_read_stream: fread() failed.");
347
                status = SIXEL_LIBC_ERROR;
×
348
                goto cleanup;
×
349
            }
350
            break;
1,417✔
351
        }
352

353
        if (used > SIZE_MAX - read_bytes) {
316,886!
354
            sixel_helper_set_additional_message(
×
355
                "sixel_palette_read_stream: size overflow.");
356
            status = SIXEL_BAD_ALLOCATION;
×
357
            goto cleanup;
×
358
        }
359
        needed = used + read_bytes;
316,886✔
360
        /* Hard-cap mapfile payload growth to 16 MiB. */
361
        if (needed > SIXEL_MAPFILE_READ_MAX_BYTES) {
316,886✔
362
            sixel_helper_set_additional_message(
57✔
363
                "sixel_palette_read_stream: palette input exceeds "
364
                "16 MiB limit.");
365
            status = SIXEL_BAD_INPUT;
57✔
366
            goto cleanup;
57✔
367
        }
368

369
        if (needed > capacity) {
316,829✔
370
            new_capacity = capacity;
2,438✔
371
            if (new_capacity == 0u) {
2,438✔
372
                new_capacity = 4096u;
1,474✔
373
            }
1,023✔
374
            while (needed > new_capacity) {
3,402!
375
                if (new_capacity > SIZE_MAX / 2u) {
964!
376
                    sixel_helper_set_additional_message(
377
                        "sixel_palette_read_stream: size overflow.");
378
                    status = SIXEL_BAD_ALLOCATION;
379
                    goto cleanup;
380
                }
381
                new_capacity *= 2u;
964✔
382
            }
383

384
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
4,107✔
385
                                                             new_capacity);
1,669✔
386
            if (grown == NULL) {
2,438!
387
                sixel_helper_set_additional_message(
×
388
                    "sixel_palette_read_stream: allocation failed.");
389
                status = SIXEL_BAD_ALLOCATION;
×
390
                goto cleanup;
×
391
            }
392

393
            if (buffer != NULL) {
2,438✔
394
                memcpy(grown, buffer, used);
964✔
395
                sixel_allocator_free(allocator, buffer);
964✔
396
            }
646✔
397

398
            buffer = grown;
1,948✔
399
            grown = NULL;
1,948✔
400
            capacity = new_capacity;
1,948✔
401
        }
1,669✔
402

403
        memcpy(buffer + used, scratch, read_bytes);
316,829✔
404
        used += read_bytes;
316,829✔
405
    }
406

407
    *pdata = buffer;
1,417✔
408
    *psize = used;
1,417✔
409
    status = SIXEL_OK;
1,417✔
410
    return status;
1,417✔
411

412
cleanup:
6✔
413
    if (grown != NULL) {
57!
414
        sixel_allocator_free(allocator, grown);
415
    }
416
    if (buffer != NULL) {
57!
417
        sixel_allocator_free(allocator, buffer);
57✔
418
    }
41✔
419
    return status;
47✔
420
}
1,023✔
421

422

423
SIXELSTATUS
424
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
1,474✔
425
{
426
    int error_value;
858✔
427
    char error_message[256];
858✔
428
    char strerror_buffer[128];
858✔
429
#if HAVE_SYS_STAT_H
430
    struct stat path_stat;
858✔
431
#endif
432

433
    if (pstream == NULL || pclose == NULL || path == NULL) {
1,474!
434
        sixel_helper_set_additional_message(
×
435
            "sixel_palette_open_read: invalid argument.");
436
        return SIXEL_BAD_ARGUMENT;
×
437
    }
438

439
    error_value = 0;
1,474✔
440
    error_message[0] = '\0';
1,474✔
441

442
    if (strcmp(path, "-") == 0) {
1,474✔
443
        *pstream = stdin;
20✔
444
        *pclose = 0;
20✔
445
        return SIXEL_OK;
20✔
446
    }
447

448
#if HAVE_SYS_STAT_H
449
    if (stat(path, &path_stat) == 0 && S_ISDIR(path_stat.st_mode)) {
1,454!
450
        sixel_compat_snprintf(error_message,
×
451
                              sizeof(error_message),
452
                              "sixel_palette_open_read: mapfile \"%s\" "
453
                              "is a directory.",
454
                              path);
455
        sixel_helper_set_additional_message(error_message);
×
456
        return SIXEL_BAD_INPUT;
×
457
    }
458
#endif
459

460
    errno = 0;
1,454✔
461
    *pstream = sixel_compat_fopen(path, "rb");
1,454✔
462
    if (*pstream == NULL) {
1,454!
463
        error_value = errno;
×
464
        sixel_compat_snprintf(error_message,
×
465
                              sizeof(error_message),
466
                              "sixel_palette_open_read: failed to open "
467
                              "\"%s\": %s.",
468
                              path,
469
                              sixel_compat_strerror(error_value,
470
                                                    strerror_buffer,
471
                                                    sizeof(strerror_buffer)));
472
        sixel_helper_set_additional_message(error_message);
×
473
        return SIXEL_LIBC_ERROR;
×
474
    }
475

476
    *pclose = 1;
1,454✔
477
    return SIXEL_OK;
1,454✔
478
}
1,023✔
479

480

481
void
482
sixel_palette_close_stream(FILE *stream, int close_stream)
1,474✔
483
{
484
    if (close_stream && stream != NULL) {
1,474!
485
        (void) fclose(stream);
1,454✔
486
    }
1,012✔
487
}
1,474✔
488

489

490
sixel_palette_format_t
491
sixel_palette_guess_format(unsigned char const *data, size_t size)
419✔
492
{
493
    size_t offset;
243✔
494
    size_t data_size;
243✔
495

496
    offset = 0u;
419✔
497
    data_size = size;
419✔
498

499
    if (data == NULL || size == 0u) {
419!
500
        return SIXEL_PALETTE_FORMAT_NONE;
501
    }
502

503
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
419!
504
            && memcmp(data + 8, "PAL ", 4) == 0) {
298!
505
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
506
    }
507

508
    if (sixel_palette_has_utf8_bom(data, size)) {
390!
509
        offset = 3u;
×
510
        data_size = size - 3u;
×
511
    }
512

513
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
419!
514
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
296✔
515
    }
516
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
57!
517
        return SIXEL_PALETTE_FORMAT_GPL;
518
    }
519
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
53!
520
        return SIXEL_PALETTE_FORMAT_ACT;
57✔
521
    }
522

523
    return SIXEL_PALETTE_FORMAT_NONE;
524
}
298✔
525

526

527
static unsigned int
528
sixel_palette_read_le16(unsigned char const *ptr)
325✔
529
{
530
    if (ptr == NULL) {
325✔
531
        return 0u;
532
    }
533
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
325✔
534
}
227✔
535

536

537
static unsigned int
538
sixel_palette_read_le32(unsigned char const *ptr)
1,066✔
539
{
540
    if (ptr == NULL) {
1,066!
541
        return 0u;
542
    }
543
    return ((unsigned int)ptr[0])
1,826✔
544
        | ((unsigned int)ptr[1] << 8)
1,066✔
545
        | ((unsigned int)ptr[2] << 16)
1,066✔
546
        | ((unsigned int)ptr[3] << 24);
1,066✔
547
}
760✔
548

549

550
/*
551
 * Adobe Color Table (*.act) reader
552
 *
553
 *   +-----------+---------------------------+
554
 *   | section   | bytes                     |
555
 *   +-----------+---------------------------+
556
 *   | palette   | 256 entries * 3 RGB bytes |
557
 *   | trailer   | optional count/start pair |
558
 *   +-----------+---------------------------+
559
 */
560
SIXELSTATUS
561
sixel_palette_parse_act(unsigned char const *data,
214✔
562
                        size_t size,
563
                        sixel_encoder_t *encoder,
564
                        sixel_dither_t **dither)
565
{
566
    SIXELSTATUS status;
126✔
567
    sixel_dither_t *local;
126✔
568
    unsigned char const *palette_start;
126✔
569
    unsigned char const *trailer;
126✔
570
    sixel_palette_t *palette_obj;
126✔
571
    int exported_colors;
126✔
572
    int start_index;
126✔
573

574
    status = SIXEL_FALSE;
214✔
575
    local = NULL;
214✔
576
    palette_start = data;
214✔
577
    trailer = NULL;
214✔
578
    palette_obj = NULL;
214✔
579
    exported_colors = 0;
214✔
580
    start_index = 0;
214✔
581

582
    if (encoder == NULL || dither == NULL) {
214!
583
        sixel_helper_set_additional_message(
×
584
            "sixel_palette_parse_act: invalid argument.");
585
        return SIXEL_BAD_ARGUMENT;
×
586
    }
587
    if (data == NULL || size < 256u * 3u) {
214!
588
        sixel_helper_set_additional_message(
×
589
            "sixel_palette_parse_act: truncated ACT palette.");
590
        return SIXEL_BAD_INPUT;
×
591
    }
592

593
    if (size == 256u * 3u) {
214✔
594
        exported_colors = 256;
14✔
595
        start_index = 0;
14✔
596
    } else if (size == 256u * 3u + 4u) {
205!
597
        trailer = data + 256u * 3u;
194✔
598
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
320✔
599
                                | (unsigned int)trailer[1]);
194✔
600
        start_index = (int)(((unsigned int)trailer[2] << 8)
320✔
601
                            | (unsigned int)trailer[3]);
194✔
602
    } else {
126✔
603
        sixel_helper_set_additional_message(
×
604
            "sixel_palette_parse_act: invalid ACT length.");
605
        return SIXEL_BAD_INPUT;
×
606
    }
607

608
    if (start_index < 0 || start_index >= 256) {
208!
609
        sixel_helper_set_additional_message(
×
610
            "sixel_palette_parse_act: ACT start index out of range.");
611
        return SIXEL_BAD_INPUT;
×
612
    }
613
    /*
614
     * Keep legacy ACT behavior for count 0 (means 256), but reject
615
     * explicit values above the 8-bit palette limit.
616
     */
617
    if (exported_colors <= 0) {
214✔
618
        exported_colors = 256;
14✔
619
    } else if (exported_colors > 256) {
205!
620
        sixel_helper_set_additional_message(
57✔
621
            "sixel_palette_parse_act: invalid ACT color count.");
622
        return SIXEL_BAD_INPUT;
57✔
623
    }
624
    if (start_index + exported_colors > 256) {
157✔
625
        sixel_helper_set_additional_message(
57✔
626
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
627
        return SIXEL_BAD_INPUT;
57✔
628
    }
629

630
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
100✔
631
    if (SIXEL_FAILED(status)) {
100!
632
        return status;
633
    }
634

635
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
100✔
636

637
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
100✔
638
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
100!
639
        sixel_dither_unref(local);
×
640
        return status;
×
641
    }
642
    status = sixel_palette_set_entries(
130✔
643
        palette_obj,
55✔
644
        palette_start + (size_t)start_index * 3u,
100✔
645
        (unsigned int)exported_colors,
55✔
646
        3,
647
        encoder->allocator);
55✔
648
    sixel_palette_unref(palette_obj);
100✔
649
    if (SIXEL_FAILED(status)) {
100!
650
        sixel_dither_unref(local);
×
651
        return status;
×
652
    }
653

654
    *dither = local;
100✔
655
    return SIXEL_OK;
100✔
656
}
137✔
657

658

659
SIXELSTATUS
660
sixel_palette_parse_pal_jasc(unsigned char const *data,
419✔
661
                             size_t size,
662
                             sixel_encoder_t *encoder,
663
                             sixel_dither_t **dither)
664
{
665
    SIXELSTATUS status;
243✔
666
    char *text;
243✔
667
    size_t index;
243✔
668
    size_t offset;
243✔
669
    char *cursor;
243✔
670
    char *line;
243✔
671
    char *line_end;
243✔
672
    int stage;
243✔
673
    int exported_colors;
243✔
674
    int parsed_colors;
243✔
675
    sixel_dither_t *local;
243✔
676
    sixel_palette_t *palette_obj;
243✔
677
    unsigned char *palette_buffer;
243✔
678
    long component;
243✔
679
    char *parse_end;
243✔
680
    int value_index;
243✔
681
    int values[3];
243✔
682
    char tail;
243✔
683

684
    status = SIXEL_FALSE;
419✔
685
    text = NULL;
419✔
686
    index = 0u;
419✔
687
    offset = 0u;
419✔
688
    cursor = NULL;
419✔
689
    line = NULL;
419✔
690
    line_end = NULL;
419✔
691
    stage = 0;
419✔
692
    exported_colors = 0;
419✔
693
    parsed_colors = 0;
419✔
694
    local = NULL;
419✔
695
    palette_obj = NULL;
419✔
696
    palette_buffer = NULL;
419✔
697
    component = 0;
419✔
698
    parse_end = NULL;
419✔
699
    value_index = 0;
419✔
700
    values[0] = 0;
419✔
701
    values[1] = 0;
419✔
702
    values[2] = 0;
419✔
703

704
    if (encoder == NULL || dither == NULL) {
419!
705
        sixel_helper_set_additional_message(
×
706
            "sixel_palette_parse_pal_jasc: invalid argument.");
707
        return SIXEL_BAD_ARGUMENT;
×
708
    }
709
    if (data == NULL || size == 0u) {
419!
710
        sixel_helper_set_additional_message(
×
711
            "sixel_palette_parse_pal_jasc: empty palette.");
712
        return SIXEL_BAD_INPUT;
×
713
    }
714
    if (size > SIZE_MAX - 1u) {
419!
715
        sixel_helper_set_additional_message(
×
716
            "sixel_palette_parse_pal_jasc: size overflow.");
717
        return SIXEL_BAD_ALLOCATION;
×
718
    }
719
    if (sixel_palette_contains_nul_byte(data, size)) {
419✔
720
        sixel_helper_set_additional_message(
57✔
721
            "sixel_palette_parse_pal_jasc: embedded NUL byte.");
722
        return SIXEL_BAD_INPUT;
57✔
723
    }
724

725
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
362✔
726
    if (text == NULL) {
362!
727
        sixel_helper_set_additional_message(
×
728
            "sixel_palette_parse_pal_jasc: allocation failed.");
729
        return SIXEL_BAD_ALLOCATION;
×
730
    }
731
    memcpy(text, data, size);
362!
732
    text[size] = '\0';
362✔
733

734
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
362!
735
        offset = 3u;
×
736
    }
737
    cursor = text + offset;
362✔
738

739
    while (*cursor != '\0') {
955!
740
        line = cursor;
1,873✔
741
        line_end = cursor;
761✔
742
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
6,026!
743
            ++line_end;
5,091✔
744
        }
745
        if (*line_end != '\0') {
935!
746
            *line_end = '\0';
935✔
747
            cursor = line_end + 1;
935✔
748
        } else {
659✔
749
            cursor = line_end;
750
        }
751
        while (*cursor == '\n' || *cursor == '\r') {
935!
752
            ++cursor;
×
753
        }
754

755
        while (*line == ' ' || *line == '\t') {
935!
756
            ++line;
×
757
        }
758
        index = strlen(line);
935✔
759
        while (index > 0u) {
935!
760
            tail = line[index - 1];
935✔
761
            if (tail != ' ' && tail != '\t') {
935!
762
                break;
761✔
763
            }
764
            line[index - 1] = '\0';
×
765
            --index;
×
766
        }
767
        if (*line == '\0') {
935!
768
            continue;
×
769
        }
770
        if (*line == '#') {
935!
771
            continue;
×
772
        }
773

774
        if (stage == 0) {
935✔
775
            if (strcmp(line, "JASC-PAL") != 0) {
362!
776
                sixel_helper_set_additional_message(
×
777
                    "sixel_palette_parse_pal_jasc: missing header.");
778
                status = SIXEL_BAD_INPUT;
×
779
                goto cleanup;
×
780
            }
781
            stage = 1;
362✔
782
            continue;
362✔
783
        }
784
        if (stage == 1) {
573✔
785
            if (strcmp(line, "0100") != 0) {
362✔
786
                sixel_helper_set_additional_message(
228✔
787
                    "sixel_palette_parse_pal_jasc: invalid version.");
788
                status = SIXEL_BAD_INPUT;
228✔
789
                goto cleanup;
228✔
790
            }
791
            stage = 2;
134✔
792
            continue;
134✔
793
        }
794
        if (stage == 2) {
211✔
795
            component = strtol(line, &parse_end, 10);
134✔
796
            /* Reject glued suffixes such as "256x" in strict mode. */
797
            if (parse_end == line || component <= 0L || component > 256L
134!
798
                    || !sixel_palette_tail_is_blank(parse_end)) {
134!
799
                sixel_helper_set_additional_message(
57✔
800
                    "sixel_palette_parse_pal_jasc: invalid color count.");
801
                status = SIXEL_BAD_INPUT;
57✔
802
                goto cleanup;
57✔
803
            }
804
            exported_colors = (int)component;
77✔
805
            if (exported_colors <= 0) {
77✔
806
                sixel_helper_set_additional_message(
807
                    "sixel_palette_parse_pal_jasc: invalid color count.");
808
                status = SIXEL_BAD_INPUT;
809
                goto cleanup;
810
            }
811
            palette_buffer = (unsigned char *)sixel_allocator_malloc(
93✔
812
                encoder->allocator,
52✔
813
                (size_t)exported_colors * 3u);
77✔
814
            if (palette_buffer == NULL) {
77!
815
                sixel_helper_set_additional_message(
×
816
                    "sixel_palette_parse_pal_jasc: allocation failed.");
817
                status = SIXEL_BAD_ALLOCATION;
×
818
                goto cleanup;
×
819
            }
820
            status = sixel_dither_new(&local, exported_colors,
129✔
821
                                      encoder->allocator);
52✔
822
            if (SIXEL_FAILED(status)) {
77!
823
                goto cleanup;
×
824
            }
825
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
77✔
826
            stage = 3;
77✔
827
            continue;
77✔
828
        }
829

830
        value_index = 0;
61✔
831
        while (value_index < 3) {
308!
832
            component = strtol(line, &parse_end, 10);
231✔
833
            if (parse_end == line || component < 0L || component > 255L) {
231!
834
                sixel_helper_set_additional_message(
×
835
                    "sixel_palette_parse_pal_jasc: invalid component.");
836
                status = SIXEL_BAD_INPUT;
×
837
                goto cleanup;
×
838
            }
839
            values[value_index] = (int)component;
231✔
840
            ++value_index;
231✔
841
            line = parse_end;
231✔
842
            while (*line == ' ' || *line == '\t') {
385!
843
                ++line;
154✔
844
            }
845
        }
846
        /* A JASC color row must be exactly 3 numeric components. */
847
        if (*line != '\0') {
77✔
848
            sixel_helper_set_additional_message(
57✔
849
                "sixel_palette_parse_pal_jasc: invalid component.");
850
            status = SIXEL_BAD_INPUT;
57✔
851
            goto cleanup;
57✔
852
        }
853

854
        if (parsed_colors >= exported_colors) {
20!
855
            sixel_helper_set_additional_message(
×
856
                "sixel_palette_parse_pal_jasc: excess entries.");
857
            status = SIXEL_BAD_INPUT;
×
858
            goto cleanup;
×
859
        }
860

861
        palette_buffer[parsed_colors * 3 + 0] =
20✔
862
            (unsigned char)values[0];
20✔
863
        palette_buffer[parsed_colors * 3 + 1] =
20✔
864
            (unsigned char)values[1];
20✔
865
        palette_buffer[parsed_colors * 3 + 2] =
20✔
866
            (unsigned char)values[2];
20✔
867
        ++parsed_colors;
20✔
868
    }
869

870
    if (stage < 3) {
20!
871
        sixel_helper_set_additional_message(
×
872
            "sixel_palette_parse_pal_jasc: incomplete header.");
873
        status = SIXEL_BAD_INPUT;
×
874
        goto cleanup;
×
875
    }
876
    if (parsed_colors != exported_colors) {
20!
877
        sixel_helper_set_additional_message(
×
878
            "sixel_palette_parse_pal_jasc: color count mismatch.");
879
        status = SIXEL_BAD_INPUT;
×
880
        goto cleanup;
×
881
    }
882

883
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
20✔
884
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
20!
885
        goto cleanup;
×
886
    }
887
    status = sixel_palette_set_entries(palette_obj,
31✔
888
                                       palette_buffer,
11✔
889
                                       (unsigned int)exported_colors,
11✔
890
                                       3,
891
                                       encoder->allocator);
11✔
892
    sixel_palette_unref(palette_obj);
20✔
893
    palette_obj = NULL;
20✔
894
    if (SIXEL_FAILED(status)) {
20!
895
        goto cleanup;
×
896
    }
897

898
    *dither = local;
20✔
899
    status = SIXEL_OK;
20✔
900

901
cleanup:
105✔
902
    if (palette_obj != NULL) {
362!
903
        sixel_palette_unref(palette_obj);
×
904
    }
905
    if (SIXEL_FAILED(status) && local != NULL) {
361!
906
        sixel_dither_unref(local);
57✔
907
    }
41✔
908
    if (palette_buffer != NULL) {
342✔
909
        sixel_allocator_free(encoder->allocator, palette_buffer);
77✔
910
    }
52✔
911
    if (text != NULL) {
362!
912
        sixel_allocator_free(encoder->allocator, text);
362✔
913
    }
257✔
914
    return status;
362✔
915
}
298✔
916

917

918
SIXELSTATUS
919
sixel_palette_parse_pal_riff(unsigned char const *data,
533✔
920
                             size_t size,
921
                             sixel_encoder_t *encoder,
922
                             sixel_dither_t **dither)
923
{
924
    SIXELSTATUS status;
309✔
925
    size_t offset;
309✔
926
    size_t chunk_size;
309✔
927
    sixel_dither_t *local;
309✔
928
    sixel_palette_t *palette_obj;
309✔
929
    unsigned char const *chunk;
309✔
930
    unsigned char *palette_buffer;
309✔
931
    unsigned int entry_count;
309✔
932
    unsigned int version;
309✔
933
    unsigned int index;
309✔
934
    size_t palette_offset;
309✔
935
    size_t remaining;
309✔
936
    size_t chunk_payload_limit;
309✔
937
    size_t chunk_padded_size;
309✔
938
    size_t next_offset;
309✔
939
    unsigned int riff_size_declared;
309✔
940
    size_t riff_size_expected;
309✔
941
    unsigned char const *data_chunk;
309✔
942
    size_t data_chunk_size;
309✔
943

944
    status = SIXEL_FALSE;
533✔
945
    offset = 0u;
533✔
946
    chunk_size = 0u;
533✔
947
    local = NULL;
533✔
948
    chunk = NULL;
533✔
949
    palette_obj = NULL;
533✔
950
    palette_buffer = NULL;
533✔
951
    entry_count = 0u;
533✔
952
    version = 0u;
533✔
953
    index = 0u;
533✔
954
    palette_offset = 0u;
533✔
955
    remaining = 0u;
533✔
956
    chunk_payload_limit = 0u;
533✔
957
    chunk_padded_size = 0u;
533✔
958
    next_offset = 0u;
533✔
959
    riff_size_declared = 0u;
533✔
960
    riff_size_expected = 0u;
533✔
961
    data_chunk = NULL;
533✔
962
    data_chunk_size = 0u;
533✔
963

964
    if (encoder == NULL || dither == NULL) {
533!
965
        sixel_helper_set_additional_message(
×
966
            "sixel_palette_parse_pal_riff: invalid argument.");
967
        return SIXEL_BAD_ARGUMENT;
×
968
    }
969
    if (data == NULL || size < 12u) {
533!
970
        sixel_helper_set_additional_message(
×
971
            "sixel_palette_parse_pal_riff: truncated palette.");
972
        return SIXEL_BAD_INPUT;
×
973
    }
974
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
533!
975
        sixel_helper_set_additional_message(
×
976
            "sixel_palette_parse_pal_riff: missing RIFF header.");
977
        return SIXEL_BAD_INPUT;
×
978
    }
979
    riff_size_declared = sixel_palette_read_le32(data + 4);
533✔
980
    riff_size_expected = size - 8u;
533✔
981
    if (riff_size_expected > 0xffffffffu ||
533!
982
            riff_size_declared != (unsigned int)riff_size_expected) {
533!
983
        sixel_helper_set_additional_message(
57✔
984
            "sixel_palette_parse_pal_riff: RIFF size mismatch.");
985
        return SIXEL_BAD_INPUT;
57✔
986
    }
987

988
    offset = 12u;
390✔
989
    /*
990
     * Keep chunk traversal overflow-safe by checking remaining bytes
991
     * with subtraction before any additive offset arithmetic.
992
     */
993
    while (offset <= size && size - offset >= 8u) {
838!
994
        remaining = size - offset;
533✔
995
        chunk = data + offset;
533✔
996
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
533✔
997
        chunk_payload_limit = remaining - 8u;
533✔
998
        if (chunk_size > chunk_payload_limit) {
533✔
999
            sixel_helper_set_additional_message(
57✔
1000
                "sixel_palette_parse_pal_riff: chunk extends past end.");
1001
            return SIXEL_BAD_INPUT;
57✔
1002
        }
1003
        if (memcmp(chunk, "data", 4) == 0) {
476✔
1004
            if (data_chunk != NULL) {
362✔
1005
                sixel_helper_set_additional_message(
57✔
1006
                    "sixel_palette_parse_pal_riff: duplicate data chunk.");
1007
                return SIXEL_BAD_INPUT;
57✔
1008
            }
1009
            data_chunk = chunk;
249✔
1010
            data_chunk_size = chunk_size;
249✔
1011
        }
216✔
1012
        chunk_padded_size = chunk_size;
428✔
1013
        if ((chunk_padded_size & 1u) != 0u) {
428✔
1014
            if (chunk_padded_size == SIZE_MAX) {
57✔
1015
                sixel_helper_set_additional_message(
1016
                    "sixel_palette_parse_pal_riff: size overflow.");
1017
                return SIXEL_BAD_ALLOCATION;
1018
            }
1019
            ++chunk_padded_size;
57✔
1020
        }
41✔
1021
        if (chunk_padded_size > chunk_payload_limit) {
419✔
1022
            sixel_helper_set_additional_message(
57✔
1023
                "sixel_palette_parse_pal_riff: chunk extends past end.");
1024
            return SIXEL_BAD_INPUT;
57✔
1025
        }
1026
        if (offset > SIZE_MAX - 8u - chunk_padded_size) {
362!
1027
            sixel_helper_set_additional_message(
1028
                "sixel_palette_parse_pal_riff: size overflow.");
1029
            return SIXEL_BAD_ALLOCATION;
1030
        }
1031
        next_offset = offset + 8u + chunk_padded_size;
362✔
1032
        offset = next_offset;
362✔
1033
    }
1034
    if (offset != size) {
305✔
1035
        sixel_helper_set_additional_message(
57✔
1036
            "sixel_palette_parse_pal_riff: trailing bytes after chunks.");
1037
        return SIXEL_BAD_INPUT;
57✔
1038
    }
1039

1040
    if (data_chunk == NULL) {
248✔
1041
        sixel_helper_set_additional_message(
57✔
1042
            "sixel_palette_parse_pal_riff: missing data chunk.");
1043
        return SIXEL_BAD_INPUT;
57✔
1044
    }
1045
    chunk = data_chunk;
191✔
1046
    chunk_size = data_chunk_size;
191✔
1047

1048
    if (chunk_size < 4u) {
191!
1049
        sixel_helper_set_additional_message(
×
1050
            "sixel_palette_parse_pal_riff: data chunk too small.");
1051
        return SIXEL_BAD_INPUT;
×
1052
    }
1053
    version = sixel_palette_read_le16(chunk + 8);
191✔
1054
    if (version != 0x0300u) {
191✔
1055
        sixel_helper_set_additional_message(
57✔
1056
            "sixel_palette_parse_pal_riff: invalid RIFF palette version.");
1057
        return SIXEL_BAD_INPUT;
57✔
1058
    }
1059
    entry_count = sixel_palette_read_le16(chunk + 10);
134✔
1060
    if (entry_count == 0u || entry_count > 256u) {
134!
1061
        sixel_helper_set_additional_message(
114✔
1062
            "sixel_palette_parse_pal_riff: invalid entry count.");
1063
        return SIXEL_BAD_INPUT;
114✔
1064
    }
1065
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
20!
1066
        sixel_helper_set_additional_message(
×
1067
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
1068
        return SIXEL_BAD_INPUT;
×
1069
    }
1070

1071
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
20✔
1072
    if (SIXEL_FAILED(status)) {
20!
1073
        return status;
1074
    }
1075
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
20✔
1076
    palette_buffer = (unsigned char *)sixel_allocator_malloc(
26✔
1077
        encoder->allocator,
11✔
1078
        (size_t)entry_count * 3u);
20✔
1079
    if (palette_buffer == NULL) {
20!
1080
        sixel_helper_set_additional_message(
×
1081
            "sixel_palette_parse_pal_riff: allocation failed.");
1082
        sixel_dither_unref(local);
×
1083
        return SIXEL_BAD_ALLOCATION;
×
1084
    }
1085
    palette_offset = 12u;
1,546✔
1086
    for (index = 0u; index < entry_count; ++index) {
5,082!
1087
        palette_buffer[index * 3u + 0u] =
5,062✔
1088
            chunk[palette_offset + index * 4u + 0u];
5,062✔
1089
        palette_buffer[index * 3u + 1u] =
5,062✔
1090
            chunk[palette_offset + index * 4u + 1u];
5,062✔
1091
        palette_buffer[index * 3u + 2u] =
5,062✔
1092
            chunk[palette_offset + index * 4u + 2u];
5,062✔
1093
    }
2,774✔
1094

1095
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
20✔
1096
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
20!
1097
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
1098
        sixel_dither_unref(local);
×
1099
        return status;
×
1100
    }
1101
    status = sixel_palette_set_entries(palette_obj,
31✔
1102
                                       palette_buffer,
11✔
1103
                                       (unsigned int)entry_count,
11✔
1104
                                       3,
1105
                                       encoder->allocator);
11✔
1106
    sixel_palette_unref(palette_obj);
20✔
1107
    palette_obj = NULL;
20✔
1108
    sixel_allocator_free(encoder->allocator, palette_buffer);
20✔
1109
    palette_buffer = NULL;
20✔
1110
    if (SIXEL_FAILED(status)) {
20!
1111
        sixel_dither_unref(local);
×
1112
        return status;
×
1113
    }
1114

1115
    *dither = local;
20✔
1116
    return SIXEL_OK;
20✔
1117
}
380✔
1118

1119

1120
SIXELSTATUS
1121
sixel_palette_parse_gpl(unsigned char const *data,
194✔
1122
                        size_t size,
1123
                        sixel_encoder_t *encoder,
1124
                        sixel_dither_t **dither)
1125
{
1126
    SIXELSTATUS status;
114✔
1127
    char *text;
114✔
1128
    size_t offset;
114✔
1129
    char *cursor;
114✔
1130
    char *line;
114✔
1131
    char *line_end;
114✔
1132
    size_t index;
114✔
1133
    int header_seen;
114✔
1134
    int parsed_colors;
114✔
1135
    unsigned char palette_bytes[256 * 3];
114✔
1136
    long component;
114✔
1137
    char *parse_end;
114✔
1138
    int value_index;
114✔
1139
    int values[3];
114✔
1140
    sixel_dither_t *local;
114✔
1141
    sixel_palette_t *palette_obj;
114✔
1142
    char tail;
114✔
1143

1144
    status = SIXEL_FALSE;
194✔
1145
    text = NULL;
194✔
1146
    offset = 0u;
194✔
1147
    cursor = NULL;
194✔
1148
    line = NULL;
194✔
1149
    line_end = NULL;
194✔
1150
    index = 0u;
194✔
1151
    header_seen = 0;
194✔
1152
    parsed_colors = 0;
194✔
1153
    component = 0;
194✔
1154
    parse_end = NULL;
194✔
1155
    value_index = 0;
194✔
1156
    values[0] = 0;
194✔
1157
    values[1] = 0;
194✔
1158
    values[2] = 0;
194✔
1159
    local = NULL;
194✔
1160
    palette_obj = NULL;
194✔
1161

1162
    if (encoder == NULL || dither == NULL) {
194!
1163
        sixel_helper_set_additional_message(
×
1164
            "sixel_palette_parse_gpl: invalid argument.");
1165
        return SIXEL_BAD_ARGUMENT;
×
1166
    }
1167
    if (data == NULL || size == 0u) {
194!
1168
        sixel_helper_set_additional_message(
×
1169
            "sixel_palette_parse_gpl: empty palette.");
1170
        return SIXEL_BAD_INPUT;
×
1171
    }
1172
    if (size > SIZE_MAX - 1u) {
194!
1173
        sixel_helper_set_additional_message(
×
1174
            "sixel_palette_parse_gpl: size overflow.");
1175
        return SIXEL_BAD_ALLOCATION;
×
1176
    }
1177
    if (sixel_palette_contains_nul_byte(data, size)) {
194✔
1178
        sixel_helper_set_additional_message(
57✔
1179
            "sixel_palette_parse_gpl: embedded NUL byte.");
1180
        return SIXEL_BAD_INPUT;
57✔
1181
    }
1182

1183
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
137✔
1184
    if (text == NULL) {
137!
1185
        sixel_helper_set_additional_message(
×
1186
            "sixel_palette_parse_gpl: allocation failed.");
1187
        return SIXEL_BAD_ALLOCATION;
×
1188
    }
1189
    memcpy(text, data, size);
137!
1190
    text[size] = '\0';
137✔
1191

1192
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
137!
1193
        offset = 3u;
×
1194
    }
1195
    cursor = text + offset;
137✔
1196

1197
    while (*cursor != '\0') {
10,578!
1198
        line = cursor;
100,737,676✔
1199
        line_end = cursor;
7,348✔
1200
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
335,767,148!
1201
            ++line_end;
335,756,650✔
1202
        }
1203
        if (*line_end != '\0') {
10,498✔
1204
            *line_end = '\0';
10,478✔
1205
            cursor = line_end + 1;
10,478✔
1206
        } else {
5,762✔
1207
            cursor = line_end;
14✔
1208
        }
1209
        while (*cursor == '\n' || *cursor == '\r') {
12,562!
1210
            ++cursor;
2,064✔
1211
        }
1212

1213
        while (*line == ' ' || *line == '\t') {
318,780,150!
1214
            ++line;
335,546,452✔
1215
        }
1216
        index = strlen(line);
10,498✔
1217
        while (index > 0u) {
10,498!
1218
            tail = line[index - 1];
10,478✔
1219
            if (tail != ' ' && tail != '\t') {
10,478!
1220
                break;
7,334✔
1221
            }
1222
            line[index - 1] = '\0';
×
1223
            --index;
×
1224
        }
1225
        if (*line == '\0') {
10,498✔
1226
            continue;
20✔
1227
        }
1228
        if (*line == '#') {
10,478✔
1229
            continue;
40✔
1230
        }
1231
        if (strncmp(line, "Name:", 5) == 0) {
10,438✔
1232
            continue;
40✔
1233
        }
1234
        if (strncmp(line, "Columns:", 8) == 0) {
10,398✔
1235
            continue;
40✔
1236
        }
1237

1238
        if (!header_seen) {
10,358✔
1239
            if (strcmp(line, "GIMP Palette") != 0) {
137!
1240
                sixel_helper_set_additional_message(
×
1241
                    "sixel_palette_parse_gpl: missing header.");
1242
                status = SIXEL_BAD_INPUT;
×
1243
                goto cleanup;
×
1244
            }
1245
            header_seen = 1;
137✔
1246
            continue;
137✔
1247
        }
1248

1249
        if (parsed_colors >= 256) {
10,221!
1250
            sixel_helper_set_additional_message(
×
1251
                "sixel_palette_parse_gpl: too many colors.");
1252
            status = SIXEL_BAD_INPUT;
×
1253
            goto cleanup;
×
1254
        }
1255

1256
        value_index = 0;
7,147✔
1257
        while (value_index < 3) {
40,827!
1258
            component = strtol(line, &parse_end, 10);
30,663✔
1259
            if (parse_end == line || component < 0L || component > 255L) {
30,663!
1260
                sixel_helper_set_additional_message(
×
1261
                    "sixel_palette_parse_gpl: invalid component.");
1262
                status = SIXEL_BAD_INPUT;
×
1263
                goto cleanup;
×
1264
            }
1265
            /*
1266
             * Keep GPL compatibility with optional trailing labels,
1267
             * but reject numeric tokens with glued suffixes like "0x".
1268
             */
1269
            if (*parse_end != '\0' && *parse_end != ' '
30,663!
1270
                    && *parse_end != '\t') {
21,414!
1271
                sixel_helper_set_additional_message(
57✔
1272
                    "sixel_palette_parse_gpl: invalid component.");
1273
                status = SIXEL_BAD_INPUT;
57✔
1274
                goto cleanup;
57✔
1275
            }
1276
            values[value_index] = (int)component;
30,606✔
1277
            ++value_index;
30,606✔
1278
            line = parse_end;
30,606✔
1279
            while (*line == ' ' || *line == '\t') {
76,261!
1280
                ++line;
45,918✔
1281
            }
1282
        }
1283

1284
        palette_bytes[parsed_colors * 3 + 0] =
10,164✔
1285
            (unsigned char)values[0];
10,164✔
1286
        palette_bytes[parsed_colors * 3 + 1] =
10,164✔
1287
            (unsigned char)values[1];
10,164✔
1288
        palette_bytes[parsed_colors * 3 + 2] =
10,164✔
1289
            (unsigned char)values[2];
10,164✔
1290
        ++parsed_colors;
10,164✔
1291
    }
1292

1293
    if (!header_seen) {
80!
1294
        sixel_helper_set_additional_message(
×
1295
            "sixel_palette_parse_gpl: header missing.");
1296
        status = SIXEL_BAD_INPUT;
×
1297
        goto cleanup;
×
1298
    }
1299
    if (parsed_colors <= 0) {
80!
1300
        sixel_helper_set_additional_message(
×
1301
            "sixel_palette_parse_gpl: no colors parsed.");
1302
        status = SIXEL_BAD_INPUT;
×
1303
        goto cleanup;
×
1304
    }
1305

1306
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
80✔
1307
    if (SIXEL_FAILED(status)) {
80!
1308
        goto cleanup;
×
1309
    }
1310
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
80✔
1311
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
80✔
1312
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
80!
1313
        goto cleanup;
×
1314
    }
1315
    status = sixel_palette_set_entries(palette_obj,
124✔
1316
                                       palette_bytes,
44✔
1317
                                       (unsigned int)parsed_colors,
44✔
1318
                                       3,
1319
                                       encoder->allocator);
44✔
1320
    sixel_palette_unref(palette_obj);
80✔
1321
    palette_obj = NULL;
80✔
1322
    if (SIXEL_FAILED(status)) {
80!
1323
        goto cleanup;
×
1324
    }
1325

1326
    *dither = local;
80✔
1327
    status = SIXEL_OK;
80✔
1328

1329
cleanup:
52✔
1330
    if (palette_obj != NULL) {
137!
1331
        sixel_palette_unref(palette_obj);
×
1332
    }
1333
    if (SIXEL_FAILED(status) && local != NULL) {
133!
1334
        sixel_dither_unref(local);
×
1335
    }
1336
    if (text != NULL) {
137!
1337
        sixel_allocator_free(encoder->allocator, text);
137✔
1338
    }
85✔
1339
    return status;
137✔
1340
}
126✔
1341

1342

1343
/*
1344
 * Palette exporters
1345
 *
1346
 *   +----------+-------------------------+
1347
 *   | format   | emission strategy       |
1348
 *   +----------+-------------------------+
1349
 *   | ACT      | fixed 256 entries + EOF |
1350
 *   | PAL JASC | textual lines           |
1351
 *   | PAL RIFF | RIFF container          |
1352
 *   | GPL      | textual lines           |
1353
 *   +----------+-------------------------+
1354
 */
1355
SIXELSTATUS
1356
sixel_palette_write_act(FILE *stream,
40✔
1357
                        unsigned char const *palette,
1358
                        size_t palette_bytes,
1359
                        int exported_colors)
1360
{
1361
    SIXELSTATUS status;
24✔
1362
    unsigned char zero_pad[256 * 3];
24✔
1363
    unsigned char trailer[4];
24✔
1364
    size_t exported_bytes;
24✔
1365
    size_t pad_bytes;
24✔
1366

1367
    status = SIXEL_FALSE;
40✔
1368
    exported_bytes = 0u;
40✔
1369

1370
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
40!
1371
        return SIXEL_BAD_ARGUMENT;
1372
    }
1373
    if (exported_colors > 256) {
40!
1374
        exported_colors = 256;
1375
    }
1376

1377
    memset(zero_pad, 0, sizeof(zero_pad));
40!
1378
    exported_bytes = (size_t)exported_colors * 3u;
40✔
1379
    if (palette_bytes < exported_bytes) {
40!
1380
        return SIXEL_BAD_ARGUMENT;
1381
    }
1382
    pad_bytes = sizeof(zero_pad) - exported_bytes;
40✔
1383

1384
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
62✔
1385
                                 & 0xffu);
22✔
1386
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
40✔
1387
    trailer[2] = 0u;
40✔
1388
    trailer[3] = 0u;
40✔
1389

1390
    if (fwrite(palette, 1, exported_bytes, stream) != exported_bytes) {
40!
1391
        status = SIXEL_LIBC_ERROR;
×
1392
        return status;
1393
    }
1394
    if (pad_bytes > 0u &&
58!
1395
            fwrite(zero_pad, 1, pad_bytes, stream) != pad_bytes) {
40!
1396
        status = SIXEL_LIBC_ERROR;
×
1397
        return status;
1398
    }
1399
    if (fwrite(trailer, 1, sizeof(trailer), stream)
60!
1400
            != sizeof(trailer)) {
22!
1401
        status = SIXEL_LIBC_ERROR;
×
1402
        return status;
1403
    }
1404

1405
    return SIXEL_OK;
28✔
1406
}
22✔
1407

1408

1409
SIXELSTATUS
1410
sixel_palette_write_pal_jasc(FILE *stream,
40✔
1411
                             unsigned char const *palette,
1412
                             int exported_colors)
1413
{
1414
    int index;
24✔
1415

1416
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
40!
1417
        return SIXEL_BAD_ARGUMENT;
1418
    }
1419
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
40!
1420
        return SIXEL_LIBC_ERROR;
1421
    }
1422
    for (index = 0; index < exported_colors; ++index) {
10,164!
1423
        if (fprintf(stream, "%d %d %d\n",
20,712!
1424
                    (int)palette[index * 3 + 0],
10,124✔
1425
                    (int)palette[index * 3 + 1],
10,124✔
1426
                    (int)palette[index * 3 + 2]) < 0) {
15,672!
1427
            return SIXEL_LIBC_ERROR;
1428
        }
1429
    }
5,548✔
1430
    return SIXEL_OK;
28✔
1431
}
22✔
1432

1433

1434
SIXELSTATUS
1435
sixel_palette_write_pal_riff(FILE *stream,
40✔
1436
                             unsigned char const *palette,
1437
                             int exported_colors)
1438
{
1439
    unsigned char size_le[4];
24✔
1440
    unsigned char data_size_le[4];
24✔
1441
    unsigned char log_palette[4 + 256 * 4];
24✔
1442
    unsigned int data_size;
24✔
1443
    unsigned int riff_size;
24✔
1444
    int index;
24✔
1445

1446
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
40!
1447
        return SIXEL_BAD_ARGUMENT;
1448
    }
1449
    if (exported_colors > 256) {
40!
1450
        exported_colors = 256;
1451
    }
1452

1453
    data_size = 4u + (unsigned int)exported_colors * 4u;
40✔
1454
    riff_size = 4u + 8u + data_size;
40✔
1455

1456
    size_le[0] = (unsigned char)(riff_size & 0xffu);
40✔
1457
    size_le[1] = (unsigned char)((riff_size >> 8) & 0xffu);
40✔
1458
    size_le[2] = (unsigned char)((riff_size >> 16) & 0xffu);
40✔
1459
    size_le[3] = (unsigned char)((riff_size >> 24) & 0xffu);
40✔
1460
    data_size_le[0] = (unsigned char)(data_size & 0xffu);
40✔
1461
    data_size_le[1] = (unsigned char)((data_size >> 8) & 0xffu);
40✔
1462
    data_size_le[2] = (unsigned char)((data_size >> 16) & 0xffu);
40✔
1463
    data_size_le[3] = (unsigned char)((data_size >> 24) & 0xffu);
40✔
1464

1465
    memset(log_palette, 0, sizeof(log_palette));
40✔
1466
    log_palette[0] = 0x00;
40✔
1467
    log_palette[1] = 0x03;
40✔
1468
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
40✔
1469
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
40✔
1470
    for (index = 0; index < exported_colors; ++index) {
10,164!
1471
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
10,124✔
1472
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
10,124✔
1473
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
10,124✔
1474
        log_palette[4 + index * 4 + 3] = 0u;
10,124✔
1475
    }
5,548✔
1476

1477
    if (fwrite("RIFF", 1, 4u, stream) != 4u) {
40!
1478
        return SIXEL_LIBC_ERROR;
1479
    }
1480
    if (fwrite(size_le, 1, sizeof(size_le), stream) != sizeof(size_le)) {
40!
1481
        return SIXEL_LIBC_ERROR;
1482
    }
1483
    if (fwrite("PAL ", 1, 4u, stream) != 4u) {
40!
1484
        return SIXEL_LIBC_ERROR;
1485
    }
1486
    if (fwrite("data", 1, 4u, stream) != 4u) {
40!
1487
        return SIXEL_LIBC_ERROR;
1488
    }
1489
    if (fwrite(data_size_le, 1, sizeof(data_size_le), stream)
60!
1490
            != sizeof(data_size_le)) {
22!
1491
        return SIXEL_LIBC_ERROR;
1492
    }
1493
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
60!
1494
            != (size_t)data_size) {
28!
1495
        return SIXEL_LIBC_ERROR;
1496
    }
1497
    return SIXEL_OK;
28✔
1498
}
22✔
1499

1500

1501
SIXELSTATUS
1502
sixel_palette_write_gpl(FILE *stream,
60✔
1503
                        unsigned char const *palette,
1504
                        int exported_colors)
1505
{
1506
    int index;
36✔
1507

1508
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
60!
1509
        return SIXEL_BAD_ARGUMENT;
1510
    }
1511
    if (fprintf(stream, "GIMP Palette\n") < 0) {
60!
1512
        return SIXEL_LIBC_ERROR;
1513
    }
1514
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
60!
1515
        return SIXEL_LIBC_ERROR;
1516
    }
1517
    if (fprintf(stream, "Columns: 16\n") < 0) {
60!
1518
        return SIXEL_LIBC_ERROR;
1519
    }
1520
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
60!
1521
        return SIXEL_LIBC_ERROR;
1522
    }
1523
    for (index = 0; index < exported_colors; ++index) {
15,246!
1524
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
31,068!
1525
                    (int)palette[index * 3 + 0],
15,186✔
1526
                    (int)palette[index * 3 + 1],
15,186✔
1527
                    (int)palette[index * 3 + 2],
15,186✔
1528
                    index) < 0) {
16,644!
1529
            return SIXEL_LIBC_ERROR;
1530
        }
1531
    }
8,322✔
1532
    return SIXEL_OK;
42✔
1533
}
33✔
1534

1535
/* emacs Local Variables:      */
1536
/* emacs mode: c               */
1537
/* emacs tab-width: 4          */
1538
/* emacs indent-tabs-mode: nil */
1539
/* emacs c-basic-offset: 4     */
1540
/* emacs End:                  */
1541
/* vim: set expandtab ts=4 : */
1542
/* 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