gitoriaLog in with ident

antcolony

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit9062978c9062978ctracker: worker box sees /media/STORAGE/projects/old-tracker read-only (tracker#2 source data)mre9062978c/lib/jsoncheck.hl

4.5 KB

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

Branches

Latest commits

  • 9062978ctracker: worker box sees /media/STORAGE/projects/old-tracker read-only (tracker#2 source data)mre
  • 7f9660eeState of 2026-09-27, before the move to gitoriamre