percent.js 1.3 KB

12345678910111213141516171819202122232425262728293031
  1. import { decodeAscii, encodeLatin1 } from './latin1.js'
  2. import { decode2string } from './platform.js'
  3. const ERR = 'percentEncodeSet must be a string of unique increasing codepoints in range 0x20 - 0x7e'
  4. const percentMap = new Map()
  5. let hex, base
  6. export function percentEncoder(set, spaceAsPlus = false) {
  7. if (typeof set !== 'string' || /[^\x20-\x7E]/.test(set)) throw new TypeError(ERR)
  8. if (typeof spaceAsPlus !== 'boolean') throw new TypeError('spaceAsPlus must be boolean')
  9. const id = set + +spaceAsPlus
  10. const cached = percentMap.get(id)
  11. if (cached) return cached
  12. const n = encodeLatin1(set).sort() // string checked above to be ascii
  13. if (decodeAscii(n) !== set || new Set(n).size !== n.length) throw new TypeError(ERR)
  14. if (!base) {
  15. hex = Array.from({ length: 256 }, (_, i) => `%${i.toString(16).padStart(2, '0').toUpperCase()}`)
  16. base = hex.map((h, i) => (i < 0x20 || i > 0x7e ? h : String.fromCharCode(i)))
  17. }
  18. const map = base.slice() // copy
  19. for (const c of n) map[c] = hex[c]
  20. if (spaceAsPlus) map[0x20] = '+' // overrides whatever percentEncodeSet thinks about it
  21. // Input is not typechecked, for internal use only
  22. const percentEncode = (u8, start = 0, end = u8.length) => decode2string(u8, start, end, map)
  23. percentMap.set(id, percentEncode)
  24. return percentEncode
  25. }