]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | /* |
2 | * Encoding and decoding basic formats: hex, base64. | |
3 | * | |
4 | * These are in-place operations which may allow an optimized implementation. | |
5 | * | |
6 | * Base-64: https://tools.ietf.org/html/rfc4648#section-4 | |
7 | */ | |
8 | ||
9 | #include "duk_internal.h" | |
10 | ||
11 | /* Shared handling for encode/decode argument. Fast path handling for | |
12 | * buffer and string values because they're the most common. In particular, | |
13 | * avoid creating a temporary string or buffer when possible. | |
14 | */ | |
15 | DUK_LOCAL const duk_uint8_t *duk__prep_codec_arg(duk_context *ctx, duk_idx_t index, duk_size_t *out_len) { | |
16 | DUK_ASSERT(duk_is_valid_index(ctx, index)); /* checked by caller */ | |
17 | if (duk_is_buffer(ctx, index)) { | |
18 | return (const duk_uint8_t *) duk_get_buffer(ctx, index, out_len); | |
19 | } else { | |
20 | return (const duk_uint8_t *) duk_to_lstring(ctx, index, out_len); | |
21 | } | |
22 | } | |
23 | ||
24 | #if defined(DUK_USE_BASE64_FASTPATH) | |
25 | DUK_LOCAL void duk__base64_encode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst) { | |
26 | duk_uint_t t; | |
27 | duk_size_t n_full, n_full3, n_final; | |
28 | const duk_uint8_t *src_end_fast; | |
29 | ||
30 | n_full = srclen / 3; /* full 3-byte -> 4-char conversions */ | |
31 | n_full3 = n_full * 3; | |
32 | n_final = srclen - n_full3; | |
33 | DUK_ASSERT_DISABLE(n_final >= 0); | |
34 | DUK_ASSERT(n_final <= 2); | |
35 | ||
36 | src_end_fast = src + n_full3; | |
37 | while (DUK_UNLIKELY(src != src_end_fast)) { | |
38 | t = (duk_uint_t) (*src++); | |
39 | t = (t << 8) + (duk_uint_t) (*src++); | |
40 | t = (t << 8) + (duk_uint_t) (*src++); | |
41 | ||
42 | *dst++ = duk_base64_enctab[t >> 18]; | |
43 | *dst++ = duk_base64_enctab[(t >> 12) & 0x3f]; | |
44 | *dst++ = duk_base64_enctab[(t >> 6) & 0x3f]; | |
45 | *dst++ = duk_base64_enctab[t & 0x3f]; | |
46 | ||
47 | #if 0 /* Tested: not faster on x64 */ | |
48 | /* aaaaaabb bbbbcccc ccdddddd */ | |
49 | dst[0] = duk_base64_enctab[(src[0] >> 2) & 0x3f]; | |
50 | dst[1] = duk_base64_enctab[((src[0] << 4) & 0x30) | ((src[1] >> 4) & 0x0f)]; | |
51 | dst[2] = duk_base64_enctab[((src[1] << 2) & 0x3f) | ((src[2] >> 6) & 0x03)]; | |
52 | dst[3] = duk_base64_enctab[src[2] & 0x3f]; | |
53 | src += 3; dst += 4; | |
54 | #endif | |
55 | } | |
56 | ||
57 | switch (n_final) { | |
58 | /* case 0: nop */ | |
59 | case 1: { | |
60 | /* XX== */ | |
61 | t = (duk_uint_t) (*src++); | |
62 | *dst++ = duk_base64_enctab[t >> 2]; /* XXXXXX-- */ | |
63 | *dst++ = duk_base64_enctab[(t << 4) & 0x3f]; /* ------XX */ | |
64 | *dst++ = DUK_ASC_EQUALS; | |
65 | *dst++ = DUK_ASC_EQUALS; | |
66 | break; | |
67 | } | |
68 | case 2: { | |
69 | /* XXX= */ | |
70 | t = (duk_uint_t) (*src++); | |
71 | t = (t << 8) + (duk_uint_t) (*src++); | |
72 | *dst++ = duk_base64_enctab[t >> 10]; /* XXXXXX-- -------- */ | |
73 | *dst++ = duk_base64_enctab[(t >> 4) & 0x3f]; /* ------XX XXXX---- */ | |
74 | *dst++ = duk_base64_enctab[(t << 2) & 0x3f]; /* -------- ----XXXX */ | |
75 | *dst++ = DUK_ASC_EQUALS; | |
76 | break; | |
77 | } | |
78 | } | |
79 | } | |
80 | #else /* DUK_USE_BASE64_FASTPATH */ | |
81 | DUK_LOCAL void duk__base64_encode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst) { | |
82 | duk_small_uint_t i, snip; | |
83 | duk_uint_t t; | |
84 | duk_uint_fast8_t x, y; | |
85 | const duk_uint8_t *src_end; | |
86 | ||
87 | src_end = src + srclen; | |
88 | ||
89 | while (src < src_end) { | |
90 | /* read 3 bytes into 't', padded by zero */ | |
91 | snip = 4; | |
92 | t = 0; | |
93 | for (i = 0; i < 3; i++) { | |
94 | t = t << 8; | |
95 | if (src >= src_end) { | |
96 | snip--; | |
97 | } else { | |
98 | t += (duk_uint_t) (*src++); | |
99 | } | |
100 | } | |
101 | ||
102 | /* | |
103 | * Missing bytes snip base64 example | |
104 | * 0 4 XXXX | |
105 | * 1 3 XXX= | |
106 | * 2 2 XX== | |
107 | */ | |
108 | ||
109 | DUK_ASSERT(snip >= 2 && snip <= 4); | |
110 | ||
111 | for (i = 0; i < 4; i++) { | |
112 | x = (duk_uint_fast8_t) ((t >> 18) & 0x3f); | |
113 | t = t << 6; | |
114 | ||
115 | /* A straightforward 64-byte lookup would be faster | |
116 | * and cleaner, but this is shorter. | |
117 | */ | |
118 | if (i >= snip) { | |
119 | y = '='; | |
120 | } else if (x <= 25) { | |
121 | y = x + 'A'; | |
122 | } else if (x <= 51) { | |
123 | y = x - 26 + 'a'; | |
124 | } else if (x <= 61) { | |
125 | y = x - 52 + '0'; | |
126 | } else if (x == 62) { | |
127 | y = '+'; | |
128 | } else { | |
129 | y = '/'; | |
130 | } | |
131 | ||
132 | *dst++ = (duk_uint8_t) y; | |
133 | } | |
134 | } | |
135 | } | |
136 | #endif /* DUK_USE_BASE64_FASTPATH */ | |
137 | ||
138 | #if defined(DUK_USE_BASE64_FASTPATH) | |
139 | DUK_LOCAL duk_bool_t duk__base64_decode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst, duk_uint8_t **out_dst_final) { | |
140 | duk_int_t x; | |
141 | duk_int_t t; | |
142 | duk_small_uint_t n_equal; | |
143 | duk_small_uint_t n_chars; | |
144 | const duk_uint8_t *src_end; | |
145 | const duk_uint8_t *src_end_safe; | |
146 | ||
147 | src_end = src + srclen; | |
148 | src_end_safe = src_end - 4; /* if 'src < src_end_safe', safe to read 4 bytes */ | |
149 | ||
150 | /* Innermost fast path processes 4 valid base-64 characters at a time | |
151 | * but bails out on whitespace, padding chars ('=') and invalid chars. | |
152 | * Once the slow path segment has been processed, we return to the | |
153 | * inner fast path again. This handles e.g. base64 with newlines | |
154 | * reasonably well because the majority of a line is in the fast path. | |
155 | */ | |
156 | for (;;) { | |
157 | /* Fast path, handle units with just actual encoding characters. */ | |
158 | ||
159 | while (src <= src_end_safe) { | |
160 | /* The lookup byte is intentionally sign extended to (at least) | |
161 | * 32 bits and then ORed. This ensures that is at least 1 byte | |
162 | * is negative, the highest bit of 't' will be set at the end | |
163 | * and we don't need to check every byte. | |
164 | */ | |
165 | DUK_DDD(DUK_DDDPRINT("fast loop: src=%p, src_end_safe=%p, src_end=%p", | |
166 | (const void *) src, (const void *) src_end_safe, (const void *) src_end)); | |
167 | ||
168 | t = (duk_int_t) duk_base64_dectab[*src++]; | |
169 | t = (t << 6) | (duk_int_t) duk_base64_dectab[*src++]; | |
170 | t = (t << 6) | (duk_int_t) duk_base64_dectab[*src++]; | |
171 | t = (t << 6) | (duk_int_t) duk_base64_dectab[*src++]; | |
172 | ||
173 | if (DUK_UNLIKELY(t < 0)) { | |
174 | DUK_DDD(DUK_DDDPRINT("fast loop unit was not clean, process one slow path unit")); | |
175 | src -= 4; | |
176 | break; | |
177 | } | |
178 | ||
179 | DUK_ASSERT(t <= 0xffffffL); | |
180 | DUK_ASSERT((t >> 24) == 0); | |
181 | *dst++ = (duk_uint8_t) (t >> 16); | |
182 | *dst++ = (duk_uint8_t) ((t >> 8) & 0xff); | |
183 | *dst++ = (duk_uint8_t) (t & 0xff); | |
184 | } | |
185 | ||
186 | /* Handle one slow path unit (or finish if we're done). */ | |
187 | ||
188 | n_equal = 0; | |
189 | n_chars = 0; | |
190 | t = 0; | |
191 | for (;;) { | |
192 | DUK_DDD(DUK_DDDPRINT("slow loop: src=%p, src_end=%p, n_chars=%ld, n_equal=%ld, t=%ld", | |
193 | (const void *) src, (const void *) src_end, (long) n_chars, (long) n_equal, (long) t)); | |
194 | ||
195 | if (DUK_UNLIKELY(src >= src_end)) { | |
196 | goto done; /* two level break */ | |
197 | } | |
198 | ||
199 | x = duk_base64_dectab[*src++]; | |
200 | if (DUK_UNLIKELY(x < 0)) { | |
201 | if (x == -2) { | |
202 | continue; /* allowed ascii whitespace */ | |
203 | } else if (x == -3) { | |
204 | n_equal++; | |
205 | t <<= 6; | |
206 | } else { | |
207 | DUK_ASSERT(x == -1); | |
208 | goto error; | |
209 | } | |
210 | } else { | |
211 | DUK_ASSERT(x >= 0 && x <= 63); | |
212 | if (n_equal > 0) { | |
213 | /* Don't allow actual chars after equal sign. */ | |
214 | goto error; | |
215 | } | |
216 | t = (t << 6) + x; | |
217 | } | |
218 | ||
219 | if (DUK_UNLIKELY(n_chars == 3)) { | |
220 | /* Emit 3 bytes and backtrack if there was padding. There's | |
221 | * always space for the whole 3 bytes so no check needed. | |
222 | */ | |
223 | DUK_ASSERT(t <= 0xffffffL); | |
224 | DUK_ASSERT((t >> 24) == 0); | |
225 | *dst++ = (duk_uint8_t) (t >> 16); | |
226 | *dst++ = (duk_uint8_t) ((t >> 8) & 0xff); | |
227 | *dst++ = (duk_uint8_t) (t & 0xff); | |
228 | ||
229 | if (DUK_UNLIKELY(n_equal > 0)) { | |
230 | DUK_ASSERT(n_equal <= 4); | |
231 | ||
232 | /* There may be whitespace between the equal signs. */ | |
233 | if (n_equal == 1) { | |
234 | /* XXX= */ | |
235 | dst -= 1; | |
236 | } else if (n_equal == 2) { | |
237 | /* XX== */ | |
238 | dst -= 2; | |
239 | } else { | |
240 | goto error; /* invalid padding */ | |
241 | } | |
242 | ||
243 | /* Continue parsing after padding, allows concatenated, | |
244 | * padded base64. | |
245 | */ | |
246 | } | |
247 | break; /* back to fast loop */ | |
248 | } else { | |
249 | n_chars++; | |
250 | } | |
251 | } | |
252 | } | |
253 | done: | |
254 | DUK_DDD(DUK_DDDPRINT("done; src=%p, src_end=%p, n_chars=%ld", | |
255 | (const void *) src, (const void *) src_end, (long) n_chars)); | |
256 | ||
257 | DUK_ASSERT(src == src_end); | |
258 | ||
259 | if (n_chars != 0) { | |
260 | /* Here we'd have the option of decoding unpadded base64 | |
261 | * (e.g. "xxxxyy" instead of "xxxxyy==". Currently not | |
262 | * accepted. | |
263 | */ | |
264 | goto error; | |
265 | } | |
266 | ||
267 | *out_dst_final = dst; | |
268 | return 1; | |
269 | ||
270 | error: | |
271 | return 0; | |
272 | } | |
273 | #else /* DUK_USE_BASE64_FASTPATH */ | |
274 | DUK_LOCAL duk_bool_t duk__base64_decode_helper(const duk_uint8_t *src, duk_size_t srclen, duk_uint8_t *dst, duk_uint8_t **out_dst_final) { | |
275 | duk_uint_t t; | |
276 | duk_uint_fast8_t x, y; | |
277 | duk_small_uint_t group_idx; | |
278 | duk_small_uint_t n_equal; | |
279 | const duk_uint8_t *src_end; | |
280 | ||
281 | src_end = src + srclen; | |
282 | t = 0; | |
283 | group_idx = 0; | |
284 | n_equal = 0; | |
285 | ||
286 | while (src < src_end) { | |
287 | x = *src++; | |
288 | ||
289 | if (x >= 'A' && x <= 'Z') { | |
290 | y = x - 'A' + 0; | |
291 | } else if (x >= 'a' && x <= 'z') { | |
292 | y = x - 'a' + 26; | |
293 | } else if (x >= '0' && x <= '9') { | |
294 | y = x - '0' + 52; | |
295 | } else if (x == '+') { | |
296 | y = 62; | |
297 | } else if (x == '/') { | |
298 | y = 63; | |
299 | } else if (x == '=') { | |
300 | /* We don't check the zero padding bytes here right now | |
301 | * (that they're actually zero). This seems to be common | |
302 | * behavior for base-64 decoders. | |
303 | */ | |
304 | ||
305 | n_equal++; | |
306 | t <<= 6; /* shift in zeroes */ | |
307 | goto skip_add; | |
308 | } else if (x == 0x09 || x == 0x0a || x == 0x0d || x == 0x20) { | |
309 | /* allow basic ASCII whitespace */ | |
310 | continue; | |
311 | } else { | |
312 | goto error; | |
313 | } | |
314 | ||
315 | if (n_equal > 0) { | |
316 | /* Don't allow mixed padding and actual chars. */ | |
317 | goto error; | |
318 | } | |
319 | t = (t << 6) + y; | |
320 | skip_add: | |
321 | ||
322 | if (group_idx == 3) { | |
323 | /* output 3 bytes from 't' */ | |
324 | *dst++ = (duk_uint8_t) ((t >> 16) & 0xff); | |
325 | *dst++ = (duk_uint8_t) ((t >> 8) & 0xff); | |
326 | *dst++ = (duk_uint8_t) (t & 0xff); | |
327 | ||
328 | if (DUK_UNLIKELY(n_equal > 0)) { | |
329 | /* Backtrack. */ | |
330 | DUK_ASSERT(n_equal <= 4); | |
331 | if (n_equal == 1) { | |
332 | dst -= 1; | |
333 | } else if (n_equal == 2) { | |
334 | dst -= 2; | |
335 | } else { | |
336 | goto error; /* invalid padding */ | |
337 | } | |
338 | ||
339 | /* Here we can choose either to end parsing and ignore | |
340 | * whatever follows, or to continue parsing in case | |
341 | * multiple (possibly padded) base64 strings have been | |
342 | * concatenated. Currently, keep on parsing. | |
343 | */ | |
344 | n_equal = 0; | |
345 | } | |
346 | ||
347 | t = 0; | |
348 | group_idx = 0; | |
349 | } else { | |
350 | group_idx++; | |
351 | } | |
352 | } | |
353 | ||
354 | if (group_idx != 0) { | |
355 | /* Here we'd have the option of decoding unpadded base64 | |
356 | * (e.g. "xxxxyy" instead of "xxxxyy==". Currently not | |
357 | * accepted. | |
358 | */ | |
359 | goto error; | |
360 | } | |
361 | ||
362 | *out_dst_final = dst; | |
363 | return 1; | |
364 | ||
365 | error: | |
366 | return 0; | |
367 | } | |
368 | #endif /* DUK_USE_BASE64_FASTPATH */ | |
369 | ||
370 | DUK_EXTERNAL const char *duk_base64_encode(duk_context *ctx, duk_idx_t index) { | |
371 | duk_hthread *thr = (duk_hthread *) ctx; | |
372 | const duk_uint8_t *src; | |
373 | duk_size_t srclen; | |
374 | duk_size_t dstlen; | |
375 | duk_uint8_t *dst; | |
376 | const char *ret; | |
377 | ||
378 | DUK_ASSERT_CTX_VALID(ctx); | |
379 | ||
380 | /* XXX: optimize for string inputs: no need to coerce to a buffer | |
381 | * which makes a copy of the input. | |
382 | */ | |
383 | ||
384 | index = duk_require_normalize_index(ctx, index); | |
385 | src = duk__prep_codec_arg(ctx, index, &srclen); | |
386 | /* Note: for srclen=0, src may be NULL */ | |
387 | ||
388 | /* Computation must not wrap; this limit works for 32-bit size_t: | |
389 | * >>> srclen = 3221225469 | |
390 | * >>> '%x' % ((srclen + 2) / 3 * 4) | |
391 | * 'fffffffc' | |
392 | */ | |
393 | if (srclen > 3221225469UL) { | |
394 | goto type_error; | |
395 | } | |
396 | dstlen = (srclen + 2) / 3 * 4; | |
397 | dst = (duk_uint8_t *) duk_push_fixed_buffer(ctx, dstlen); | |
398 | ||
399 | duk__base64_encode_helper((const duk_uint8_t *) src, srclen, dst); | |
400 | ||
401 | ret = duk_to_string(ctx, -1); | |
402 | duk_replace(ctx, index); | |
403 | return ret; | |
404 | ||
405 | type_error: | |
406 | DUK_ERROR_TYPE(thr, DUK_STR_ENCODE_FAILED); | |
407 | return NULL; /* never here */ | |
408 | } | |
409 | ||
410 | DUK_EXTERNAL void duk_base64_decode(duk_context *ctx, duk_idx_t index) { | |
411 | duk_hthread *thr = (duk_hthread *) ctx; | |
412 | const duk_uint8_t *src; | |
413 | duk_size_t srclen; | |
414 | duk_size_t dstlen; | |
415 | duk_uint8_t *dst; | |
416 | duk_uint8_t *dst_final; | |
417 | duk_bool_t retval; | |
418 | ||
419 | DUK_ASSERT_CTX_VALID(ctx); | |
420 | ||
421 | /* XXX: optimize for buffer inputs: no need to coerce to a string | |
422 | * which causes an unnecessary interning. | |
423 | */ | |
424 | ||
425 | index = duk_require_normalize_index(ctx, index); | |
426 | src = duk__prep_codec_arg(ctx, index, &srclen); | |
427 | ||
428 | /* Computation must not wrap, only srclen + 3 is at risk of | |
429 | * wrapping because after that the number gets smaller. | |
430 | * This limit works for 32-bit size_t: | |
431 | * 0x100000000 - 3 - 1 = 4294967292 | |
432 | */ | |
433 | if (srclen > 4294967292UL) { | |
434 | goto type_error; | |
435 | } | |
436 | dstlen = (srclen + 3) / 4 * 3; /* upper limit, assuming no whitespace etc */ | |
437 | dst = (duk_uint8_t *) duk_push_dynamic_buffer(ctx, dstlen); | |
438 | /* Note: for dstlen=0, dst may be NULL */ | |
439 | ||
440 | retval = duk__base64_decode_helper((const duk_uint8_t *) src, srclen, dst, &dst_final); | |
441 | if (!retval) { | |
442 | goto type_error; | |
443 | } | |
444 | ||
445 | /* XXX: convert to fixed buffer? */ | |
446 | (void) duk_resize_buffer(ctx, -1, (duk_size_t) (dst_final - dst)); | |
447 | duk_replace(ctx, index); | |
448 | return; | |
449 | ||
450 | type_error: | |
451 | DUK_ERROR_TYPE(thr, DUK_STR_DECODE_FAILED); | |
452 | } | |
453 | ||
454 | DUK_EXTERNAL const char *duk_hex_encode(duk_context *ctx, duk_idx_t index) { | |
455 | const duk_uint8_t *inp; | |
456 | duk_size_t len; | |
457 | duk_size_t i; | |
458 | duk_uint8_t *buf; | |
459 | const char *ret; | |
460 | #if defined(DUK_USE_HEX_FASTPATH) | |
461 | duk_size_t len_safe; | |
462 | duk_uint16_t *p16; | |
463 | #endif | |
464 | ||
465 | DUK_ASSERT_CTX_VALID(ctx); | |
466 | ||
467 | index = duk_require_normalize_index(ctx, index); | |
468 | inp = duk__prep_codec_arg(ctx, index, &len); | |
469 | DUK_ASSERT(inp != NULL || len == 0); | |
470 | ||
471 | /* Fixed buffer, no zeroing because we'll fill all the data. */ | |
472 | buf = (duk_uint8_t *) duk_push_buffer_raw(ctx, len * 2, DUK_BUF_FLAG_NOZERO /*flags*/); | |
473 | DUK_ASSERT(buf != NULL); | |
474 | ||
475 | #if defined(DUK_USE_HEX_FASTPATH) | |
476 | DUK_ASSERT((((duk_size_t) buf) & 0x01U) == 0); /* pointer is aligned, guaranteed for fixed buffer */ | |
477 | p16 = (duk_uint16_t *) (void *) buf; | |
478 | len_safe = len & ~0x03U; | |
479 | for (i = 0; i < len_safe; i += 4) { | |
480 | p16[0] = duk_hex_enctab[inp[i]]; | |
481 | p16[1] = duk_hex_enctab[inp[i + 1]]; | |
482 | p16[2] = duk_hex_enctab[inp[i + 2]]; | |
483 | p16[3] = duk_hex_enctab[inp[i + 3]]; | |
484 | p16 += 4; | |
485 | } | |
486 | for (; i < len; i++) { | |
487 | *p16++ = duk_hex_enctab[inp[i]]; | |
488 | } | |
489 | #else /* DUK_USE_HEX_FASTPATH */ | |
490 | for (i = 0; i < len; i++) { | |
491 | duk_small_uint_t t; | |
492 | t = (duk_small_uint_t) inp[i]; | |
493 | buf[i*2 + 0] = duk_lc_digits[t >> 4]; | |
494 | buf[i*2 + 1] = duk_lc_digits[t & 0x0f]; | |
495 | } | |
496 | #endif /* DUK_USE_HEX_FASTPATH */ | |
497 | ||
498 | /* XXX: Using a string return value forces a string intern which is | |
499 | * not always necessary. As a rough performance measure, hex encode | |
500 | * time for tests/perf/test-hex-encode.js dropped from ~35s to ~15s | |
501 | * without string coercion. Change to returning a buffer and let the | |
502 | * caller coerce to string if necessary? | |
503 | */ | |
504 | ||
505 | ret = duk_to_string(ctx, -1); | |
506 | duk_replace(ctx, index); | |
507 | return ret; | |
508 | } | |
509 | ||
510 | DUK_EXTERNAL void duk_hex_decode(duk_context *ctx, duk_idx_t index) { | |
511 | duk_hthread *thr = (duk_hthread *) ctx; | |
512 | const duk_uint8_t *inp; | |
513 | duk_size_t len; | |
514 | duk_size_t i; | |
515 | duk_int_t t; | |
516 | duk_uint8_t *buf; | |
517 | #if defined(DUK_USE_HEX_FASTPATH) | |
518 | duk_int_t chk; | |
519 | duk_uint8_t *p; | |
520 | duk_size_t len_safe; | |
521 | #endif | |
522 | ||
523 | DUK_ASSERT_CTX_VALID(ctx); | |
524 | ||
525 | index = duk_require_normalize_index(ctx, index); | |
526 | inp = duk__prep_codec_arg(ctx, index, &len); | |
527 | DUK_ASSERT(inp != NULL || len == 0); | |
528 | ||
529 | if (len & 0x01) { | |
530 | goto type_error; | |
531 | } | |
532 | ||
533 | /* Fixed buffer, no zeroing because we'll fill all the data. */ | |
534 | buf = (duk_uint8_t *) duk_push_buffer_raw(ctx, len / 2, DUK_BUF_FLAG_NOZERO /*flags*/); | |
535 | DUK_ASSERT(buf != NULL); | |
536 | ||
537 | #if defined(DUK_USE_HEX_FASTPATH) | |
538 | p = buf; | |
539 | len_safe = len & ~0x07U; | |
540 | for (i = 0; i < len_safe; i += 8) { | |
541 | t = ((duk_int_t) duk_hex_dectab_shift4[inp[i]]) | | |
542 | ((duk_int_t) duk_hex_dectab[inp[i + 1]]); | |
543 | chk = t; | |
544 | p[0] = (duk_uint8_t) t; | |
545 | t = ((duk_int_t) duk_hex_dectab_shift4[inp[i + 2]]) | | |
546 | ((duk_int_t) duk_hex_dectab[inp[i + 3]]); | |
547 | chk |= t; | |
548 | p[1] = (duk_uint8_t) t; | |
549 | t = ((duk_int_t) duk_hex_dectab_shift4[inp[i + 4]]) | | |
550 | ((duk_int_t) duk_hex_dectab[inp[i + 5]]); | |
551 | chk |= t; | |
552 | p[2] = (duk_uint8_t) t; | |
553 | t = ((duk_int_t) duk_hex_dectab_shift4[inp[i + 6]]) | | |
554 | ((duk_int_t) duk_hex_dectab[inp[i + 7]]); | |
555 | chk |= t; | |
556 | p[3] = (duk_uint8_t) t; | |
557 | p += 4; | |
558 | ||
559 | /* Check if any lookup above had a negative result. */ | |
560 | if (DUK_UNLIKELY(chk < 0)) { | |
561 | goto type_error; | |
562 | } | |
563 | } | |
564 | for (; i < len; i += 2) { | |
565 | t = (((duk_int_t) duk_hex_dectab[inp[i]]) << 4) | | |
566 | ((duk_int_t) duk_hex_dectab[inp[i + 1]]); | |
567 | if (DUK_UNLIKELY(t < 0)) { | |
568 | goto type_error; | |
569 | } | |
570 | *p++ = (duk_uint8_t) t; | |
571 | } | |
572 | #else /* DUK_USE_HEX_FASTPATH */ | |
573 | for (i = 0; i < len; i += 2) { | |
574 | /* For invalid characters the value -1 gets extended to | |
575 | * at least 16 bits. If either nybble is invalid, the | |
576 | * resulting 't' will be < 0. | |
577 | */ | |
578 | t = (((duk_int_t) duk_hex_dectab[inp[i]]) << 4) | | |
579 | ((duk_int_t) duk_hex_dectab[inp[i + 1]]); | |
580 | if (DUK_UNLIKELY(t < 0)) { | |
581 | goto type_error; | |
582 | } | |
583 | buf[i >> 1] = (duk_uint8_t) t; | |
584 | } | |
585 | #endif /* DUK_USE_HEX_FASTPATH */ | |
586 | ||
587 | duk_replace(ctx, index); | |
588 | return; | |
589 | ||
590 | type_error: | |
591 | DUK_ERROR_TYPE(thr, DUK_STR_DECODE_FAILED); | |
592 | } | |
593 | ||
594 | DUK_EXTERNAL const char *duk_json_encode(duk_context *ctx, duk_idx_t index) { | |
595 | #ifdef DUK_USE_ASSERTIONS | |
596 | duk_idx_t top_at_entry; | |
597 | #endif | |
598 | const char *ret; | |
599 | ||
600 | DUK_ASSERT_CTX_VALID(ctx); | |
601 | #ifdef DUK_USE_ASSERTIONS | |
602 | top_at_entry = duk_get_top(ctx); | |
603 | #endif | |
604 | ||
605 | index = duk_require_normalize_index(ctx, index); | |
606 | duk_bi_json_stringify_helper(ctx, | |
607 | index /*idx_value*/, | |
608 | DUK_INVALID_INDEX /*idx_replacer*/, | |
609 | DUK_INVALID_INDEX /*idx_space*/, | |
610 | 0 /*flags*/); | |
611 | DUK_ASSERT(duk_is_string(ctx, -1)); | |
612 | duk_replace(ctx, index); | |
613 | ret = duk_get_string(ctx, index); | |
614 | ||
615 | DUK_ASSERT(duk_get_top(ctx) == top_at_entry); | |
616 | ||
617 | return ret; | |
618 | } | |
619 | ||
620 | DUK_EXTERNAL void duk_json_decode(duk_context *ctx, duk_idx_t index) { | |
621 | #ifdef DUK_USE_ASSERTIONS | |
622 | duk_idx_t top_at_entry; | |
623 | #endif | |
624 | ||
625 | DUK_ASSERT_CTX_VALID(ctx); | |
626 | #ifdef DUK_USE_ASSERTIONS | |
627 | top_at_entry = duk_get_top(ctx); | |
628 | #endif | |
629 | ||
630 | index = duk_require_normalize_index(ctx, index); | |
631 | duk_bi_json_parse_helper(ctx, | |
632 | index /*idx_value*/, | |
633 | DUK_INVALID_INDEX /*idx_reviver*/, | |
634 | 0 /*flags*/); | |
635 | duk_replace(ctx, index); | |
636 | ||
637 | DUK_ASSERT(duk_get_top(ctx) == top_at_entry); | |
638 | } |