gitoriaLog in with ident

gitoria

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commitfd981932fd981932State of 2026-09-27; bin/ no longer tracked (Hybriel commit is in README)mrefd981932/jsoncheck.hl

4.3 KB

  1. // jsoncheck.hl — WORKAROUND for hybriel ticket #12 (no soft JSON.parse): JSON.parse throws on
  2. // invalid input and a Hybriel program cannot catch it, so the framework answers 500 with the
  3. // source location. The API therefore checks the body's JSON SYNTAX by hand first (RFC 8259
  4. // grammar, nesting depth capped at 64, NO \u surrogate escapes — hl's JSON.parse refuses
  5. // them, even valid pairs) and answers 400. REMOVE this file and its one call in
  6. // api.hl (readBody) once JSON.parse has a failure path (#12).
  7. //
  8. // jsonErrorAt(text) → -1 when `text` is one valid JSON value (surrounded by whitespace),
  9. // else the 0-based character position of the first offence.
  10. static maxDepth = 64
  11. static isWs = (c) => { return c == 32 || c == 9 || c == 10 || c == 13 }
  12. static isDigit = (c) => { return c >= 48 && c <= 57 }
  13. static isHex = (c) => { return (c >= 48 && c <= 57) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102) }
  14. static hexValue = (c) => { return c <= 57 ? c - 48 : (c <= 70 ? c - 55 : c - 87) }
  15. // the 4 hex digits at `at` as a number, or -1
  16. static hexAt = (s, at) => {
  17. if (at + 4 > s.length) { return -1 }
  18. let v = 0
  19. let k = 0
  20. while (k < 4) {
  21. let c = s.charCodeAt(at + k)
  22. if (!isHex(c)) { return -1 }
  23. v = v * 16 + hexValue(c)
  24. k = k + 1
  25. }
  26. return v
  27. }
  28. static skipWs = (s, at) => {
  29. let i = at
  30. while (i < s.length && isWs(s.charCodeAt(i))) { i = i + 1 }
  31. return i
  32. }
  33. // each scanner answers the position after what it read, or -(position + 1) of an offence
  34. static bad = (i) => { return -(i + 1) }
  35. static scanString = (s, at) => {
  36. let i = at
  37. i = i + 1
  38. while (i < s.length) {
  39. let c = s.charCodeAt(i)
  40. if (c == 34) { return i + 1 }
  41. if (c < 32) { return bad(i) }
  42. if (c == 92) {
  43. i = i + 1
  44. if (i >= s.length) { return bad(i) }
  45. let e = s.charCodeAt(i)
  46. if (e == 117) {
  47. let u = hexAt(s, i + 1)
  48. if (u < 0) { return bad(i) }
  49. // hl's JSON.parse refuses EVERY surrogate escape (\uD800-\uDFFF), even a valid
  50. // pair like \ud83d\ude00 (hybriel issue, mission 004) — refuse it here: 400, not 500.
  51. // Send such characters as raw UTF-8 instead (JS JSON.stringify does).
  52. if (u >= 55296 && u <= 57343) { return bad(i) }
  53. i = i + 4
  54. } else if (!(e == 34 || e == 92 || e == 47 || e == 98 || e == 102 || e == 110 || e == 114 || e == 116)) {
  55. return bad(i)
  56. }
  57. }
  58. i = i + 1
  59. }
  60. return bad(i)
  61. }
  62. static scanDigits = (s, at) => {
  63. let i = at
  64. let start = i
  65. while (i < s.length && isDigit(s.charCodeAt(i))) { i = i + 1 }
  66. return i == start ? bad(i) : i
  67. }
  68. static scanNumber = (s, at) => {
  69. let i = at
  70. if (s.charCodeAt(i) == 45) { i = i + 1 }
  71. if (i >= s.length || !isDigit(s.charCodeAt(i))) { return bad(i) }
  72. if (s.charCodeAt(i) == 48) { i = i + 1 } else { i = scanDigits(s, i) }
  73. if (i < s.length && s.charCodeAt(i) == 46) {
  74. i = scanDigits(s, i + 1)
  75. if (i < 0) { return i }
  76. }
  77. if (i < s.length && (s.charCodeAt(i) == 101 || s.charCodeAt(i) == 69)) {
  78. i = i + 1
  79. if (i < s.length && (s.charCodeAt(i) == 43 || s.charCodeAt(i) == 45)) { i = i + 1 }
  80. i = scanDigits(s, i)
  81. }
  82. return i
  83. }
  84. static scanWord = (s, i, word) => {
  85. return s.slice(i, i + word.length) == word ? i + word.length : bad(i)
  86. }
  87. static scanValue = (s, at, depth) => {
  88. let i = at
  89. i = skipWs(s, i)
  90. if (i >= s.length) { return bad(i) }
  91. let c = s.charCodeAt(i)
  92. if (c == 34) { return scanString(s, i) }
  93. if (c == 45 || isDigit(c)) { return scanNumber(s, i) }
  94. if (c == 116) { return scanWord(s, i, 'true') }
  95. if (c == 102) { return scanWord(s, i, 'false') }
  96. if (c == 110) { return scanWord(s, i, 'null') }
  97. if (c == 123 || c == 91) {
  98. if (depth >= maxDepth) { return bad(i) }
  99. let close = c == 123 ? 125 : 93
  100. i = skipWs(s, i + 1)
  101. if (i < s.length && s.charCodeAt(i) == close) { return i + 1 }
  102. while (true) {
  103. if (c == 123) {
  104. i = skipWs(s, i)
  105. if (i >= s.length || s.charCodeAt(i) != 34) { return bad(i) }
  106. i = scanString(s, i)
  107. if (i < 0) { return i }
  108. i = skipWs(s, i)
  109. if (i >= s.length || s.charCodeAt(i) != 58) { return bad(i) }
  110. i = i + 1
  111. }
  112. i = scanValue(s, i, depth + 1)
  113. if (i < 0) { return i }
  114. i = skipWs(s, i)
  115. if (i >= s.length) { return bad(i) }
  116. let d = s.charCodeAt(i)
  117. if (d == close) { return i + 1 }
  118. if (d != 44) { return bad(i) }
  119. i = i + 1
  120. }
  121. }
  122. return bad(i)
  123. }
  124. static jsonErrorAt = (text) => {
  125. let i = scanValue(text, 0, 0)
  126. if (i < 0) { return -i - 1 }
  127. i = skipWs(text, i)
  128. return i < text.length ? i : -1
  129. }

Branches

Latest commits

  • fd981932State of 2026-09-27; bin/ no longer tracked (Hybriel commit is in README)mre
  • 4a2d7125initial commitmre