Coverage Report

Created: 2026-07-16 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/moddable/modules/data/text/decoder/textdecoder.c
Line
Count
Source
1
/*
2
* Copyright (c) 2021-2022  Moddable Tech, Inc.
3
*
4
*   This file is part of the Moddable SDK Runtime.
5
*
6
*   The Moddable SDK Runtime is free software: you can redistribute it and/or modify
7
*   it under the terms of the GNU Lesser General Public License as published by
8
*   the Free Software Foundation, either version 3 of the License, or
9
*   (at your option) any later version.
10
*
11
*   The Moddable SDK Runtime is distributed in the hope that it will be useful,
12
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
13
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
*   GNU Lesser General Public License for more details.
15
*
16
*   You should have received a copy of the GNU Lesser General Public License
17
*   along with the Moddable SDK Runtime.  If not, see <http://www.gnu.org/licenses/>.
18
*
19
*/
20
21
#include "xsmc.h"
22
#include "xsHost.h"
23
#ifdef kPocoRotation
24
  // Moddable SDK
25
  #include "mc.xs.h"      // for xsID_ values
26
27
  #define VALIDATE 1
28
#else
29
  // xst, xsnap, etc
30
  #include <stdbool.h>
31
32
  #define xsID_ignoreBOM (xsID("ignoreBOM"))
33
  #define xsID_fatal (xsID("fatal"))
34
  #define xsID_stream (xsID("stream"))
35
#endif
36
37
typedef struct {
38
  uint8_t   ignoreBOM;
39
  uint8_t   fatal;
40
41
  // left over when streaming
42
  uint8_t   bufferLength;
43
  uint8_t   buffer[12];
44
} modTextDecoderRecord, *modTextDecoder;
45
46
static uint8_t isLegalUTF8(const uint8_t *source, int length);
47
48
void xs_textdecoder_destructor(void *data)
49
0
{
50
0
}
51
52
void xs_textdecoder(xsMachine *the)
53
31.9k
{
54
31.9k
  modTextDecoderRecord decoder;
55
31.9k
  int argc = xsmcArgc;
56
57
31.9k
  if (argc && c_strcmp(xsmcToString(xsArg(0)), "utf-8"))
58
1
    xsRangeError("unsupported encoding");
59
60
31.9k
#if !VALIDATE
61
31.9k
  xsmcGet(xsResult, xsTarget, xsID("prototype"));
62
31.9k
  xsResult = xsNewHostInstance(xsResult);
63
31.9k
  xsThis = xsResult;
64
31.9k
  xsmcSetHostDestructor(xsThis, NULL);
65
31.9k
  c_memset(&decoder, 0, sizeof(decoder));
66
31.9k
#endif
67
68
31.9k
  decoder.ignoreBOM = false;
69
31.9k
  decoder.fatal = false;
70
31.9k
  decoder.bufferLength = 0;
71
31.9k
  if (argc >= 2) {
72
0
    xsmcVars(1);
73
74
0
    xsmcGet(xsVar(0), xsArg(1), xsID_ignoreBOM);
75
0
    decoder.ignoreBOM = xsmcTest(xsVar(0));
76
77
0
    xsmcGet(xsVar(0), xsArg(1), xsID_fatal);
78
0
    decoder.fatal = xsmcTest(xsVar(0));
79
0
  }
80
81
31.9k
  xsmcSetHostChunk(xsThis, &decoder, sizeof(decoder));
82
31.9k
}
83
84
/*
85
  UTF-8 BOM is sequence 0xEF,0xBB,0xBF
86
  Replacement character sequence in UTF-8 is 0xEF 0xBF 0xBD
87
  null character maps to 0xC0, 0x80
88
  
89
  implementation overallocates by 3 bytes if BOM is present and ignoreBOM is false
90
*/
91
92
void xs_textdecoder_decode(xsMachine *the)
93
31.9k
{
94
31.9k
  uint8_t *src, *srcEnd, *dst, *dst3;
95
31.9k
  uint8_t *buffer;
96
31.9k
  xsUnsignedValue srcLength, bufferLength;
97
31.9k
  modTextDecoder td;
98
31.9k
  uint8_t srcOffset = 0;
99
31.9k
  uint32_t outLength = 0;
100
31.9k
  uint8_t stream = 0;
101
31.9k
  int argc = xsmcArgc;
102
103
31.9k
  if (argc > 1) {
104
0
    xsmcVars(1);
105
106
0
    xsmcGet(xsVar(0), xsArg(1), xsID_stream);
107
0
    stream = xsmcToBoolean(xsVar(0));
108
0
  }
109
110
31.9k
  if (argc) {
111
31.9k
    xsmcGetBufferReadable(xsArg(0), (void **)&src, &srcLength);
112
31.9k
    srcEnd = src + srcLength;
113
31.9k
  }
114
0
  else
115
0
    src = srcEnd = NULL;
116
117
#if VALIDATE
118
  td = xsmcGetHostChunkValidate(xsThis, xs_textdecoder_destructor);
119
#else
120
31.9k
  td = xsmcGetHostChunk(xsThis);
121
31.9k
#endif
122
31.9k
  buffer = td->buffer;
123
31.9k
  bufferLength = td->bufferLength;
124
125
161M
  while ((src < srcEnd) || bufferLength) {
126
161M
    unsigned char first, clen, i;
127
161M
    uint8_t utf8[4];
128
129
161M
    if (bufferLength) {
130
0
      bufferLength--;
131
0
      first = *buffer++;
132
0
    }
133
161M
    else
134
161M
      first = c_read8(src++);
135
161M
    if (first < 0x80) {
136
149M
      outLength += (0 == first) ? 2 : 1;
137
149M
      continue;
138
149M
    }
139
140
12.2M
    if (0xC0 == (first & 0xE0))
141
1.39M
      clen = 1;
142
10.8M
    else if (0xE0 == (first & 0xF0))
143
690k
      clen = 2;
144
10.1M
    else if (0xF0 == (first & 0xF0))
145
3.32M
      clen = 3;
146
6.85M
    else if (td->fatal)
147
0
      goto fatal;
148
6.85M
    else {
149
6.85M
      outLength += 3;
150
6.85M
      continue;
151
6.85M
    }
152
153
5.41M
    if (clen > ((srcEnd - src) + bufferLength)) {
154
1.17k
      if (stream)
155
0
        break; // decode to here. remainder saved below
156
157
1.17k
      if (td->fatal)
158
0
        goto fatal;
159
160
1.17k
      outLength += 3;
161
1.17k
      if (!src)    // flush
162
0
        break;
163
1.17k
      continue;
164
1.17k
    }
165
166
5.41M
    utf8[0] = first;
167
18.1M
    for (i = 0; i < clen; i++) {
168
12.7M
      if (i < bufferLength)
169
0
        utf8[i + 1] = buffer[i];
170
12.7M
      else
171
12.7M
        utf8[i + 1] = c_read8(src + i - bufferLength);
172
12.7M
    }
173
174
5.41M
    if (!isLegalUTF8(utf8, clen + 1)) {
175
5.09M
      if (td->fatal)
176
0
        goto fatal;
177
178
5.09M
      uint8_t lower = 0x80, upper = 0xBF;
179
5.09M
      if (0xE0 == first)
180
18.5k
        lower = 0xA0;
181
5.07M
      else if (0xED == first)
182
50.4k
        lower = 0x9F;
183
5.02M
      else if (0xF0 == first)
184
27.6k
        lower = 0x90;
185
4.99M
      else if (0xF4 == first)
186
19.6k
        lower = 0x8F;
187
4.97M
      else if (first > 0xF4)  // no valid next byte
188
3.17M
        clen = 0;
189
190
5.09M
      const uint8_t *s = &utf8[1];
191
5.20M
      while (clen-- > 0) {
192
2.02M
        uint8_t c = *s++;
193
2.02M
        if ((lower <= c) && (c <= upper)) {
194
117k
          if (bufferLength) {
195
0
            bufferLength--;
196
0
            buffer++;
197
0
          }
198
117k
          else
199
117k
            src++;
200
117k
        }
201
1.90M
        else
202
1.90M
          break;
203
2.02M
      }
204
205
5.09M
      outLength += 3;
206
5.09M
      continue;
207
5.09M
    }
208
209
323k
#if mxCESU8
210
323k
    outLength += (3 == clen) ? 6 : (clen + 1);
211
#else
212
    outLength += clen + 1;
213
#endif
214
323k
    if (bufferLength) {
215
0
      if (bufferLength >= clen) {
216
0
        bufferLength -= clen;
217
0
        buffer += clen;
218
0
      }
219
0
      else {
220
0
        src += clen - bufferLength; 
221
0
        bufferLength = 0;
222
0
      }
223
0
    }
224
323k
    else
225
323k
      src += clen;
226
323k
  }
227
228
31.9k
  xsmcSetStringBuffer(xsResult, NULL, outLength + 1);
229
230
31.9k
  if (argc) {
231
31.9k
    xsmcGetBufferReadable(xsArg(0), (void **)&src, &srcLength);
232
31.9k
    srcEnd = src + srcLength;
233
31.9k
    src += srcOffset;
234
31.9k
  }
235
0
  else
236
0
    src = srcEnd = NULL;
237
238
#if VALIDATE
239
  td = xsmcGetHostChunkValidate(xsThis, xs_textdecoder_destructor);
240
#else
241
31.9k
  td = xsmcGetHostChunk(xsThis);
242
31.9k
#endif
243
31.9k
  buffer = td->buffer;
244
31.9k
  bufferLength = td->bufferLength;
245
246
31.9k
  dst = (uint8_t *)xsmcToString(xsResult);
247
31.9k
  dst3 = td->ignoreBOM ? NULL : (dst + 3);
248
249
161M
  while ((src < srcEnd) || bufferLength) {
250
161M
    unsigned char first, clen, i, firstFromBuffer;
251
161M
    uint8_t utf8[4];
252
253
161M
    if (bufferLength) {
254
0
      bufferLength--;
255
0
      first = *buffer++;
256
0
      firstFromBuffer = 1;
257
0
    }
258
161M
    else {
259
161M
      first = c_read8(src++);
260
161M
      firstFromBuffer = 0;
261
161M
    }
262
161M
    if (first < 0x80) {
263
149M
      if (first)
264
144M
        *dst++ = first;
265
4.75M
      else {
266
4.75M
        *dst++ = 0xC0;
267
4.75M
        *dst++ = 0x80;
268
4.75M
      }
269
149M
      continue;
270
149M
    }
271
272
12.2M
    if (0xC0 == (first & 0xE0))
273
1.39M
      clen = 1;
274
10.8M
    else if (0xE0 == (first & 0xF0))
275
690k
      clen = 2;
276
10.1M
    else if (0xF0 == (first & 0xF0))
277
3.32M
      clen = 3;
278
6.85M
    else {
279
6.85M
      *dst++ = 0xEF;
280
6.85M
      *dst++ = 0xBF;
281
6.85M
      *dst++ = 0xBD;
282
6.85M
      continue;
283
6.85M
    }
284
285
5.41M
    if (clen > ((srcEnd - src) + bufferLength)) {
286
1.17k
      if (stream) {
287
        // put back "first". remainder saved below.
288
0
        if (firstFromBuffer) {
289
0
          buffer--;
290
0
          bufferLength++;
291
0
        }
292
0
        else
293
0
          src--;
294
0
        break;
295
        
296
0
      }
297
298
1.17k
      *dst++ = 0xEF;
299
1.17k
      *dst++ = 0xBF;
300
1.17k
      *dst++ = 0xBD;
301
1.17k
      if (!src)
302
0
        break; // flush
303
1.17k
      continue;
304
1.17k
    }
305
306
5.41M
    utf8[0] = first;
307
18.1M
    for (i = 0; i < clen; i++) {
308
12.7M
      if (i < bufferLength)
309
0
        utf8[i + 1] = buffer[i];
310
12.7M
      else
311
12.7M
        utf8[i + 1] = c_read8(src + i - bufferLength);
312
12.7M
    }
313
314
5.41M
    if (!isLegalUTF8(utf8, clen + 1)) {
315
5.09M
      *dst++ = 0xEF;
316
5.09M
      *dst++ = 0xBF;
317
5.09M
      *dst++ = 0xBD;
318
319
5.09M
      uint8_t lower = 0x80, upper = 0xBF;
320
5.09M
      if (0xE0 == first)
321
18.5k
        lower = 0xA0;
322
5.07M
      else if (0xED == first)
323
50.4k
        lower = 0x9F;
324
5.02M
      else if (0xF0 == first)
325
27.6k
        lower = 0x90;
326
4.99M
      else if (0xF4 == first)
327
19.6k
        lower = 0x8F;
328
4.97M
      else if (first > 0xF4)  // no valid next byte
329
3.17M
        clen = 0;
330
331
5.09M
      const uint8_t *s = &utf8[1];
332
5.20M
      while (clen-- > 0) {
333
2.02M
        uint8_t c = *s++;
334
2.02M
        if ((lower <= c) && (c <= upper)) {
335
117k
          if (bufferLength) {
336
0
            bufferLength--;
337
0
            buffer++;
338
0
          }
339
117k
          else
340
117k
            src++;
341
117k
        }
342
1.90M
        else
343
1.90M
          break;
344
2.02M
      }
345
346
5.09M
      continue;
347
5.09M
    }
348
349
323k
#if mxCESU8
350
323k
    if (3 != clen) {
351
288k
      *dst++ = first;
352
735k
      for (i = 0; i < clen; i++)
353
446k
        *dst++ = utf8[i + 1];
354
288k
    }
355
34.5k
    else {
356
34.5k
      xsIntegerValue c;
357
34.5k
      fxUTF8Decode((xsStringValue)utf8, &c);
358
34.5k
      c -= 0x10000;
359
34.5k
      fxUTF8Encode((xsStringValue)dst, 0xD800 + (c >> 10));
360
34.5k
      dst += 3;
361
34.5k
      fxUTF8Encode((xsStringValue)dst, 0xDC00 + (c & 0x3FF));
362
34.5k
      dst += 3;
363
34.5k
    }
364
#else
365
    *dst++ = first;
366
    for (i = 0; i < clen; i++)
367
      *dst++ = utf8[i + 1];
368
#endif
369
    
370
323k
    if ((0xEF == first) && (dst == dst3)) {
371
272
      if ((0xBF == dst[-1]) && (0xBB == dst[-2]))
372
255
        dst -= 3;
373
272
    }
374
375
323k
    if (bufferLength) {
376
0
      if (bufferLength >= clen) {
377
0
        bufferLength -= clen;
378
0
        buffer += clen;
379
0
      }
380
0
      else {
381
0
        src += clen - bufferLength; 
382
0
        bufferLength = 0;
383
0
      }
384
0
    }
385
323k
    else
386
323k
      src += clen;
387
323k
  }
388
31.9k
  *dst++ = 0;
389
390
31.9k
  if (src) {
391
31.9k
    c_memcpy(td->buffer, buffer, bufferLength);
392
31.9k
    c_memcpy(td->buffer + bufferLength, src, srcEnd - src);
393
31.9k
    td->bufferLength = bufferLength + (srcEnd - src);
394
31.9k
  }
395
0
  else 
396
0
    td->bufferLength =  0;   // flush
397
398
31.9k
  return;
399
400
0
fatal:
401
0
  xsTypeError("invalid utf-8");
402
31.9k
}
403
404
void xs_textdecoder_get_encoding(xsMachine *the)
405
0
{
406
0
  xsmcSetString(xsResult, "utf-8");
407
0
}
408
409
void xs_textdecoder_get_ignoreBOM(xsMachine *the)
410
0
{
411
#if VALIDATE
412
  modTextDecoder td = xsmcGetHostChunkValidate(xsThis, xs_textdecoder_destructor);
413
#else
414
0
  modTextDecoder td = xsmcGetHostChunk(xsThis);
415
0
#endif
416
0
  xsmcSetBoolean(xsResult, td->ignoreBOM);
417
0
}
418
419
void xs_textdecoder_get_fatal(xsMachine *the)
420
0
{
421
#if VALIDATE
422
  modTextDecoder td = xsmcGetHostChunkValidate(xsThis, xs_textdecoder_destructor);
423
#else
424
0
  modTextDecoder td = xsmcGetHostChunk(xsThis);
425
0
#endif
426
0
  xsmcSetBoolean(xsResult, td->fatal);
427
0
}
428
429
#if !VALIDATE
430
void modInstallTextDecoder(xsMachine *the)
431
31.9k
{
432
31.9k
  #define kPrototype (0)
433
31.9k
  #define kConstructor (1)
434
31.9k
  #define kScratch (2)
435
436
63.8k
  xsBeginHost(the);
437
63.8k
  xsmcVars(3);
438
439
63.8k
  xsVar(kPrototype) = xsNewHostObject(NULL);
440
63.8k
  xsVar(kConstructor) = xsNewHostConstructor(xs_textdecoder, 2, xsVar(kPrototype));
441
63.8k
  xsmcDefine(xsGlobal, xsID("TextDecoder"), xsVar(kConstructor), xsDontEnum);
442
443
63.8k
  xsVar(kScratch) = xsNewHostFunction(xs_textdecoder_decode, 1);
444
63.8k
  xsmcDefine(xsVar(kPrototype), xsID("decode"), xsVar(kScratch), xsDontEnum);
445
63.8k
  xsVar(kScratch) = xsNewHostFunction(xs_textdecoder_get_encoding, 0);
446
63.8k
  xsmcDefine(xsVar(kPrototype), xsID("encoding"), xsVar(kScratch), xsIsGetter | xsDontEnum);
447
63.8k
  xsVar(kScratch) = xsNewHostFunction(xs_textdecoder_get_ignoreBOM, 0);
448
63.8k
  xsmcDefine(xsVar(kPrototype), xsID("ignoreBOM"), xsVar(kScratch), xsIsGetter | xsDontEnum);
449
63.8k
  xsVar(kScratch) = xsNewHostFunction(xs_textdecoder_get_fatal, 0);
450
63.8k
  xsmcDefine(xsVar(kPrototype), xsID("fatal"), xsVar(kScratch), xsIsGetter | xsDontEnum);
451
452
63.8k
  xsEndHost(the);
453
31.9k
}
454
#endif
455
456
/*
457
 * Copyright 2001-2004 Unicode, Inc.
458
 * 
459
 * Disclaimer
460
 * 
461
 * This source code is provided as is by Unicode, Inc. No claims are
462
 * made as to fitness for any particular purpose. No warranties of any
463
 * kind are expressed or implied. The recipient agrees to determine
464
 * applicability of information provided. If this file has been
465
 * purchased on magnetic or optical media from Unicode, Inc., the
466
 * sole remedy for any claim will be exchange of defective media
467
 * within 90 days of receipt.
468
 * 
469
 * Limitations on Rights to Redistribute This Code
470
 * 
471
 * Unicode, Inc. hereby grants the right to freely use the information
472
 * supplied in this file in the creation of products supporting the
473
 * Unicode Standard, and to make copies of this file in any form
474
 * for internal or external distribution as long as this notice
475
 * remains attached.
476
 */
477
 
478
10.8M
uint8_t isLegalUTF8(const uint8_t *source, int length) {
479
10.8M
    uint8_t a;
480
10.8M
    const uint8_t *srcptr = source+length;
481
10.8M
    switch (length) {
482
0
    default: return false;
483
  /* Everything else falls through when "true"... */
484
6.65M
    case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
485
1.66M
    case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
486
3.36M
    case 2: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
487
488
691k
  switch (*source) {
489
      /* no fall-through in this inner switch */
490
8.05k
      case 0xE0: if (a < 0xA0) return false; break;
491
6.93k
      case 0xED: if (a > 0x9F) return false; break;
492
6.04k
      case 0xF0: if (a < 0x90) return false; break;
493
5.68k
      case 0xF4: if (a > 0x8F) return false; break;
494
671k
      default:   if (a < 0x80) return false;
495
691k
  }
496
497
685k
    case 1: if (*source >= 0x80 && *source < 0xC2) return false;
498
10.8M
    }
499
669k
    if (*source > 0xF4) return false;
500
647k
    return true;
501
669k
}
502