component-safe-serialization.test.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. 'use strict'
  2. const test = require('tape')
  3. const fastURI = require('..')
  4. test('userinfo serialization cannot terminate the authority', (t) => {
  5. const uri = fastURI.serialize({
  6. scheme: 'http',
  7. userinfo: 'attacker.example/',
  8. host: 'trusted.example',
  9. path: '/'
  10. })
  11. t.equal(uri, 'http://attacker.example%2F@trusted.example/', 'slash is encoded as userinfo data')
  12. const reparsed = fastURI.parse(uri)
  13. t.equal(reparsed.host, 'trusted.example', 'reparsing retains the supplied host')
  14. t.equal(reparsed.userinfo, 'attacker.example%2F', 'encoded slash remains in userinfo')
  15. t.equal(
  16. fastURI.serialize({ userinfo: 'user@/?:#[]\\', host: 'example.test' }),
  17. '//user%40%2F%3F:%23%5B%5D%5C@example.test',
  18. 'all userinfo component delimiters are encoded as data'
  19. )
  20. t.end()
  21. })
  22. test('query and fragment serialization cannot inject component delimiters', (t) => {
  23. const uri = fastURI.serialize({
  24. scheme: 'x',
  25. path: '/resource',
  26. query: 'key=value#still-query',
  27. fragment: 'first#still-fragment'
  28. })
  29. t.equal(
  30. uri,
  31. 'x:/resource?key=value%23still-query#first%23still-fragment',
  32. 'raw hashes are encoded within their supplied components'
  33. )
  34. const reparsed = fastURI.parse(uri)
  35. t.equal(reparsed.query, 'key=value%23still-query', 'hash remains query data')
  36. t.equal(reparsed.fragment, 'first%23still-fragment', 'hash remains fragment data')
  37. t.end()
  38. })
  39. test('component serializers preserve each RFC 3986 literal character set', (t) => {
  40. const unreservedAndSubDelims = "AZaz09-._~!$&'()*+,;="
  41. const userinfo = unreservedAndSubDelims + ':'
  42. const queryOrFragment = unreservedAndSubDelims + ':@/?'
  43. t.equal(
  44. fastURI.serialize({ userinfo, host: 'example.test' }),
  45. '//' + userinfo + '@example.test',
  46. 'userinfo literals are preserved'
  47. )
  48. t.equal(
  49. fastURI.serialize({ query: queryOrFragment }),
  50. '?' + queryOrFragment,
  51. 'query literals are preserved'
  52. )
  53. t.equal(
  54. fastURI.serialize({ fragment: queryOrFragment }),
  55. '#' + queryOrFragment,
  56. 'fragment literals are preserved'
  57. )
  58. t.end()
  59. })
  60. test('component serializers preserve escapes and encode Unicode as UTF-8', (t) => {
  61. t.equal(
  62. fastURI.serialize({ userinfo: 'café/%2f%GG', host: 'example.test' }),
  63. '//caf%C3%A9%2F%2F%25GG@example.test',
  64. 'userinfo preserves valid escapes, uppercases hex, and escapes malformed percent data'
  65. )
  66. t.equal(
  67. fastURI.serialize({ query: '日本/%2f#%GG' }),
  68. '?%E6%97%A5%E6%9C%AC/%2F%23%25GG',
  69. 'query uses UTF-8 and does not double encode valid escapes'
  70. )
  71. t.equal(
  72. fastURI.serialize({ fragment: '😀/%3f#%GG' }),
  73. '#%F0%9F%98%80/%3F%23%25GG',
  74. 'fragment uses UTF-8 and does not double encode valid escapes'
  75. )
  76. t.equal(
  77. fastURI.serialize({ query: 'Kſ' }),
  78. '?%E2%84%AA%C5%BF',
  79. 'Unicode characters that case-fold to ASCII are still UTF-8 encoded'
  80. )
  81. t.end()
  82. })
  83. test('parsed URI serialization remains stable', (t) => {
  84. const input = 'x://user:pass@example.test/a%2Fb?x=a/b?c&y=%23#frag/a?b%23'
  85. const serialized = fastURI.serialize(fastURI.parse(input))
  86. t.equal(serialized, input, 'an already normalized parsed URI round-trips unchanged')
  87. t.equal(
  88. fastURI.serialize(fastURI.parse(serialized)),
  89. serialized,
  90. 'repeated parse and serialize cycles are stable'
  91. )
  92. t.end()
  93. })