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 +0 -1
- package/dist/canonicalize.js +37 -14
- package/package.json +1 -1
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
|
|
package/dist/canonicalize.js
CHANGED
|
@@ -24,10 +24,31 @@ export function canonicalize(value) {
|
|
|
24
24
|
/* Strings */
|
|
25
25
|
/* -------------------------------------------------------------------------- */
|
|
26
26
|
function escapeString(str) {
|
|
27
|
-
// RFC 8785
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
let
|
|
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
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
110
|
+
exp = exp.replace(/^0+/, "");
|
|
111
|
+
if (exp === "")
|
|
112
|
+
exp = "0";
|
|
89
113
|
// RFC 8785: exponent must not include "+"
|
|
90
|
-
|
|
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.
|
|
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",
|