io-v2-utf8.js (2387B)
1 // Strict stateless UTF-8. No BOM stripping, replacement or normalization. 2 // Source carries the copied <=3-byte suffix between requests. 3 export class UTF8Error extends Error { 4 } 5 export function decodeUTF8(bytes, final) { 6 if (bytes.length > 4096 || bytes.some(b => !Number.isInteger(b) || b < 0 || b > 255)) 7 throw new UTF8Error('invalid byte chunk'); 8 const chars = []; 9 const bad = (offset, why) => { throw new UTF8Error(`${why} at byte ${offset}`); }; 10 for (let offset = 0; offset < bytes.length;) { 11 const lead = bytes[offset]; 12 if (lead <= 0x7f) { 13 chars.push(String.fromCodePoint(lead)); 14 offset++; 15 continue; 16 } 17 let count, low = 0x80, high = 0xbf, code; 18 if (lead >= 0xc2 && lead <= 0xdf) { 19 count = 2; 20 code = lead & 0x1f; 21 } 22 else if (lead >= 0xe0 && lead <= 0xef) { 23 count = 3; 24 code = lead & 0x0f; 25 if (lead === 0xe0) 26 low = 0xa0; 27 if (lead === 0xed) 28 high = 0x9f; 29 } 30 else if (lead >= 0xf0 && lead <= 0xf4) { 31 count = 4; 32 code = lead & 7; 33 if (lead === 0xf0) 34 low = 0x90; 35 if (lead === 0xf4) 36 high = 0x8f; 37 } 38 else 39 return bad(offset, 'invalid UTF-8 leading byte'); 40 const available = Math.min(count - 1, bytes.length - offset - 1); 41 for (let i = 1; i <= available; i++) { 42 const b = bytes[offset + i]; 43 if (b < (i === 1 ? low : 0x80) || b > (i === 1 ? high : 0xbf)) 44 return bad(offset, 'invalid UTF-8 sequence'); 45 code = code * 64 + (b & 63); 46 } 47 if (available < count - 1) { 48 if (final) 49 return bad(offset, 'incomplete UTF-8 sequence'); 50 return { text: chars.join(''), carry: bytes.slice(offset) }; 51 } 52 chars.push(String.fromCodePoint(code)); 53 offset += count; 54 } 55 return { text: chars.join(''), carry: [] }; 56 } 57 export function encodeUTF8(text) { 58 let count = 0; 59 for (const char of text) { 60 const scalar = char.codePointAt(0); 61 if (++count > 1024 || scalar >= 0xd800 && scalar <= 0xdfff) 62 throw new UTF8Error('invalid scalar chunk'); 63 } 64 return [...new TextEncoder().encode(text)]; 65 }