json-seal 0.11.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,6 @@
6
6
  <img src="https://img.shields.io/badge/Deterministic%20JSON-RFC%208785%20Compliant-success" alt="Deterministic JSON" />
7
7
  <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="dependencies" />
8
8
  <img src="https://img.shields.io/badge/types-TypeScript-blue" alt="types" />
9
- <img src="https://img.shields.io/bundlephobia/minzip/json-seal" alt="bundle size" />
10
9
  <img src="https://img.shields.io/github/license/cmyers/json-seal" alt="license" />
11
10
  </p>
12
11
 
@@ -24,10 +24,31 @@ export function canonicalize(value) {
24
24
  /* Strings */
25
25
  /* -------------------------------------------------------------------------- */
26
26
  function escapeString(str) {
27
- // RFC 8785 uses ECMAScript string escaping rules.
27
+ // RFC 8785 defines canonicalization in terms of ECMAScript strings (UTF‑16),
28
+ // but the final canonical form must be encoded as UTF‑8 when serialized.
29
+ // Therefore we validate UTF‑16 correctness (surrogate pairs) here.
28
30
  let out = '"';
29
31
  for (let i = 0; i < str.length; i++) {
30
32
  const c = str.charCodeAt(i);
33
+ // Surrogate handling (UTF‑16 correctness)
34
+ if (c >= 0xd800 && c <= 0xdbff) {
35
+ // High surrogate: must be followed by a low surrogate
36
+ if (i + 1 >= str.length) {
37
+ throw new Error("Invalid UTF‑16: isolated high surrogate");
38
+ }
39
+ const d = str.charCodeAt(i + 1);
40
+ if (d < 0xdc00 || d > 0xdfff) {
41
+ throw new Error("Invalid UTF‑16: high surrogate not followed by low surrogate");
42
+ }
43
+ // Valid surrogate pair → append both as-is
44
+ out += str[i] + str[i + 1];
45
+ i++; // skip the low surrogate
46
+ continue;
47
+ }
48
+ else if (c >= 0xdc00 && c <= 0xdfff) {
49
+ // Low surrogate without preceding high surrogate
50
+ throw new Error("Invalid UTF‑16: isolated low surrogate");
51
+ }
31
52
  switch (c) {
32
53
  case 0x22:
33
54
  out += '\\"';
@@ -75,20 +96,22 @@ function serializeNumber(n) {
75
96
  // RFC 8785: -0 must be serialized as 0
76
97
  if (Object.is(n, -0))
77
98
  return "0";
78
- // Use JS number → string, then normalize exponent form
99
+ // Use JS number → string, then normalize exponent form if present.
100
+ // JS already produces minimal mantissa/decimal representation.
79
101
  let s = n.toString();
80
- // Normalize exponent to uppercase E
81
- if (s.includes("e")) {
82
- const [mantissa, exp] = s.split("e");
83
- const sign = exp.startsWith("-") ? "-" : "+";
84
- let digits = exp.replace(/^[+-]/, "");
102
+ // Normalize exponent to RFC 8785 rules
103
+ const eIndex = s.indexOf("e");
104
+ if (eIndex !== -1) {
105
+ const mantissa = s.slice(0, eIndex);
106
+ let exp = s.slice(eIndex + 1); // after 'e'
107
+ const negative = exp.startsWith("-");
108
+ exp = exp.replace(/^[+-]/, "");
85
109
  // Remove leading zeros in exponent
86
- digits = digits.replace(/^0+/, "");
87
- if (digits === "")
88
- digits = "0";
110
+ exp = exp.replace(/^0+/, "");
111
+ if (exp === "")
112
+ exp = "0";
89
113
  // RFC 8785: exponent must not include "+"
90
- const normalized = `${mantissa}E${sign === "-" ? "-" : ""}${digits}`;
91
- return normalized;
114
+ s = `${mantissa}E${negative ? "-" : ""}${exp}`;
92
115
  }
93
116
  return s;
94
117
  }
@@ -106,9 +129,9 @@ function serializeObject(obj) {
106
129
  const keys = Object.keys(obj);
107
130
  // RFC 8785: duplicate keys MUST be rejected
108
131
  detectDuplicateKeys(keys);
109
- // Sort by UTF‑16 code units (JS default)
132
+ // Sort by UTF‑16 code units (JS default), as required by RFC 8785
110
133
  keys.sort();
111
- const entries = keys.map(k => `${escapeString(k)}:${canonicalize(obj[k])}`);
134
+ const entries = keys.map((k) => `${escapeString(k)}:${canonicalize(obj[k])}`);
112
135
  return `{${entries.join(",")}}`;
113
136
  }
114
137
  function detectDuplicateKeys(keys) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-seal",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "author": "Chris Myers <cmyers2015@outlook.com> (https://www.npmjs.com/~cmyers-dev)",
5
5
  "description": "Create cryptographically signed, tamper‑proof JSON backups with zero dependencies.",
6
6
  "type": "module",