@uncefact/untp-utils 0.1.0 → 0.2.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.
Files changed (220) hide show
  1. package/README.md +23 -1
  2. package/build/artefacts/detect-version-from-context.d.ts +46 -0
  3. package/build/artefacts/detect-version-from-context.js +77 -0
  4. package/build/artefacts/detect-version-from-context.js.map +1 -0
  5. package/build/artefacts/detect-version-from-context.test.d.ts +1 -0
  6. package/build/artefacts/detect-version-from-context.test.js +69 -0
  7. package/build/artefacts/detect-version-from-context.test.js.map +1 -0
  8. package/build/artefacts/index.d.ts +3 -0
  9. package/build/artefacts/index.js +4 -0
  10. package/build/artefacts/index.js.map +1 -0
  11. package/build/artefacts/urls.d.ts +79 -0
  12. package/build/artefacts/urls.js +133 -0
  13. package/build/artefacts/urls.js.map +1 -0
  14. package/build/artefacts/urls.test.d.ts +1 -0
  15. package/build/artefacts/urls.test.js +91 -0
  16. package/build/artefacts/urls.test.js.map +1 -0
  17. package/build/artefacts/version.d.ts +9 -0
  18. package/build/artefacts/version.js +17 -0
  19. package/build/artefacts/version.js.map +1 -0
  20. package/build/artefacts/version.test.d.ts +1 -0
  21. package/build/artefacts/version.test.js +16 -0
  22. package/build/artefacts/version.test.js.map +1 -0
  23. package/build/cache/in-memory-ttl-cache.d.ts +2 -0
  24. package/build/cache/in-memory-ttl-cache.js +76 -0
  25. package/build/cache/in-memory-ttl-cache.js.map +1 -0
  26. package/build/cache/in-memory-ttl-cache.test.d.ts +1 -0
  27. package/build/cache/in-memory-ttl-cache.test.js +236 -0
  28. package/build/cache/in-memory-ttl-cache.test.js.map +1 -0
  29. package/build/cache/index.d.ts +2 -0
  30. package/build/cache/index.js +2 -0
  31. package/build/cache/index.js.map +1 -0
  32. package/build/cache/ttl-cache.d.ts +22 -0
  33. package/build/cache/ttl-cache.js +2 -0
  34. package/build/cache/ttl-cache.js.map +1 -0
  35. package/build/common/as-non-empty-string.d.ts +7 -0
  36. package/build/common/as-non-empty-string.js +10 -0
  37. package/build/common/as-non-empty-string.js.map +1 -0
  38. package/build/common/as-non-empty-string.test.d.ts +1 -0
  39. package/build/common/as-non-empty-string.test.js +25 -0
  40. package/build/common/as-non-empty-string.test.js.map +1 -0
  41. package/build/common/require-string.d.ts +12 -0
  42. package/build/common/require-string.js +27 -0
  43. package/build/common/require-string.js.map +1 -0
  44. package/build/common/require-string.test.d.ts +1 -0
  45. package/build/common/require-string.test.js +53 -0
  46. package/build/common/require-string.test.js.map +1 -0
  47. package/build/conformity-vocabulary/codes.d.ts +10 -0
  48. package/build/conformity-vocabulary/codes.js +10 -0
  49. package/build/conformity-vocabulary/codes.js.map +1 -0
  50. package/build/conformity-vocabulary/errors.d.ts +44 -0
  51. package/build/conformity-vocabulary/errors.js +68 -0
  52. package/build/conformity-vocabulary/errors.js.map +1 -0
  53. package/build/conformity-vocabulary/index.d.ts +6 -0
  54. package/build/conformity-vocabulary/index.js +7 -0
  55. package/build/conformity-vocabulary/index.js.map +1 -0
  56. package/build/conformity-vocabulary/parse-conformity-catalogue.d.ts +26 -0
  57. package/build/conformity-vocabulary/parse-conformity-catalogue.js +106 -0
  58. package/build/conformity-vocabulary/parse-conformity-catalogue.js.map +1 -0
  59. package/build/conformity-vocabulary/parse-conformity-catalogue.test.d.ts +1 -0
  60. package/build/conformity-vocabulary/parse-conformity-catalogue.test.js +327 -0
  61. package/build/conformity-vocabulary/parse-conformity-catalogue.test.js.map +1 -0
  62. package/build/conformity-vocabulary/parse-conformity-scheme.d.ts +28 -0
  63. package/build/conformity-vocabulary/parse-conformity-scheme.js +47 -0
  64. package/build/conformity-vocabulary/parse-conformity-scheme.js.map +1 -0
  65. package/build/conformity-vocabulary/parse-conformity-scheme.test.d.ts +1 -0
  66. package/build/conformity-vocabulary/parse-conformity-scheme.test.js +284 -0
  67. package/build/conformity-vocabulary/parse-conformity-scheme.test.js.map +1 -0
  68. package/build/conformity-vocabulary/parsers/v0-7-0.parser.d.ts +19 -0
  69. package/build/conformity-vocabulary/parsers/v0-7-0.parser.js +219 -0
  70. package/build/conformity-vocabulary/parsers/v0-7-0.parser.js.map +1 -0
  71. package/build/conformity-vocabulary/types.d.ts +180 -0
  72. package/build/conformity-vocabulary/types.js +2 -0
  73. package/build/conformity-vocabulary/types.js.map +1 -0
  74. package/build/conformity-vocabulary/validate-conformity-claim.d.ts +26 -0
  75. package/build/conformity-vocabulary/validate-conformity-claim.js +165 -0
  76. package/build/conformity-vocabulary/validate-conformity-claim.js.map +1 -0
  77. package/build/conformity-vocabulary/validate-conformity-claim.test.d.ts +1 -0
  78. package/build/conformity-vocabulary/validate-conformity-claim.test.js +478 -0
  79. package/build/conformity-vocabulary/validate-conformity-claim.test.js.map +1 -0
  80. package/build/http-headers/index.d.ts +5 -0
  81. package/build/http-headers/index.js +6 -0
  82. package/build/http-headers/index.js.map +1 -0
  83. package/build/http-headers/is-safe-header-value.d.ts +10 -0
  84. package/build/http-headers/is-safe-header-value.js +15 -0
  85. package/build/http-headers/is-safe-header-value.js.map +1 -0
  86. package/build/http-headers/is-safe-header-value.test.d.ts +1 -0
  87. package/build/http-headers/is-safe-header-value.test.js +37 -0
  88. package/build/http-headers/is-safe-header-value.test.js.map +1 -0
  89. package/build/http-headers/parse-entity-tag.d.ts +9 -0
  90. package/build/http-headers/parse-entity-tag.js +18 -0
  91. package/build/http-headers/parse-entity-tag.js.map +1 -0
  92. package/build/http-headers/parse-entity-tag.test.d.ts +1 -0
  93. package/build/http-headers/parse-entity-tag.test.js +41 -0
  94. package/build/http-headers/parse-entity-tag.test.js.map +1 -0
  95. package/build/http-headers/parse-imf-date.d.ts +13 -0
  96. package/build/http-headers/parse-imf-date.js +23 -0
  97. package/build/http-headers/parse-imf-date.js.map +1 -0
  98. package/build/http-headers/parse-imf-date.test.d.ts +1 -0
  99. package/build/http-headers/parse-imf-date.test.js +31 -0
  100. package/build/http-headers/parse-imf-date.test.js.map +1 -0
  101. package/build/http-headers/parse-media-type.d.ts +8 -0
  102. package/build/http-headers/parse-media-type.js +21 -0
  103. package/build/http-headers/parse-media-type.js.map +1 -0
  104. package/build/http-headers/parse-media-type.test.d.ts +1 -0
  105. package/build/http-headers/parse-media-type.test.js +39 -0
  106. package/build/http-headers/parse-media-type.test.js.map +1 -0
  107. package/build/http-headers/user-agent.d.ts +35 -0
  108. package/build/http-headers/user-agent.js +45 -0
  109. package/build/http-headers/user-agent.js.map +1 -0
  110. package/build/http-headers/user-agent.test.d.ts +1 -0
  111. package/build/http-headers/user-agent.test.js +27 -0
  112. package/build/http-headers/user-agent.test.js.map +1 -0
  113. package/build/index.d.ts +1 -1
  114. package/build/index.js +1 -1
  115. package/build/index.js.map +1 -1
  116. package/build/loaders/errors.d.ts +21 -0
  117. package/build/loaders/errors.js +45 -0
  118. package/build/loaders/errors.js.map +1 -0
  119. package/build/loaders/index.d.ts +3 -0
  120. package/build/loaders/index.js +4 -0
  121. package/build/loaders/index.js.map +1 -0
  122. package/build/loaders/jsonld-document-loader.d.ts +46 -0
  123. package/build/loaders/jsonld-document-loader.js +34 -0
  124. package/build/loaders/jsonld-document-loader.js.map +1 -0
  125. package/build/loaders/jsonld-document-loader.test.d.ts +1 -0
  126. package/build/loaders/jsonld-document-loader.test.js +71 -0
  127. package/build/loaders/jsonld-document-loader.test.js.map +1 -0
  128. package/build/loaders/schema-loader.d.ts +21 -0
  129. package/build/loaders/schema-loader.js +67 -0
  130. package/build/loaders/schema-loader.js.map +1 -0
  131. package/build/loaders/schema-loader.ssrf.test.d.ts +1 -0
  132. package/build/loaders/schema-loader.ssrf.test.js +55 -0
  133. package/build/loaders/schema-loader.ssrf.test.js.map +1 -0
  134. package/build/loaders/schema-loader.test.d.ts +1 -0
  135. package/build/loaders/schema-loader.test.js +162 -0
  136. package/build/loaders/schema-loader.test.js.map +1 -0
  137. package/build/multibase-digest/multibase-digest.d.ts +23 -0
  138. package/build/multibase-digest/multibase-digest.js +40 -0
  139. package/build/multibase-digest/multibase-digest.js.map +1 -1
  140. package/build/multibase-digest/multibase-digest.test.js +46 -0
  141. package/build/multibase-digest/multibase-digest.test.js.map +1 -1
  142. package/build/node/errors.d.ts +41 -0
  143. package/build/node/errors.js +80 -0
  144. package/build/node/errors.js.map +1 -0
  145. package/build/node/index.d.ts +3 -0
  146. package/build/node/index.js +4 -0
  147. package/build/node/index.js.map +1 -0
  148. package/build/node/is-private-ip.d.ts +41 -0
  149. package/build/node/is-private-ip.js +138 -0
  150. package/build/node/is-private-ip.js.map +1 -0
  151. package/build/node/is-private-ip.test.d.ts +1 -0
  152. package/build/node/is-private-ip.test.js +96 -0
  153. package/build/node/is-private-ip.test.js.map +1 -0
  154. package/build/node/validate-public-url.d.ts +49 -0
  155. package/build/node/validate-public-url.js +94 -0
  156. package/build/node/validate-public-url.js.map +1 -0
  157. package/build/node/validate-public-url.test.d.ts +1 -0
  158. package/build/node/validate-public-url.test.js +177 -0
  159. package/build/node/validate-public-url.test.js.map +1 -0
  160. package/build/resolvers/errors.d.ts +43 -0
  161. package/build/resolvers/errors.js +99 -0
  162. package/build/resolvers/errors.js.map +1 -0
  163. package/build/resolvers/index.d.ts +5 -0
  164. package/build/resolvers/index.js +6 -0
  165. package/build/resolvers/index.js.map +1 -0
  166. package/build/resolvers/resolve-document-if-changed.d.ts +48 -0
  167. package/build/resolvers/resolve-document-if-changed.js +42 -0
  168. package/build/resolvers/resolve-document-if-changed.js.map +1 -0
  169. package/build/resolvers/resolve-document-if-changed.test.d.ts +1 -0
  170. package/build/resolvers/resolve-document-if-changed.test.js +109 -0
  171. package/build/resolvers/resolve-document-if-changed.test.js.map +1 -0
  172. package/build/resolvers/resolve-document.d.ts +86 -0
  173. package/build/resolvers/resolve-document.js +239 -0
  174. package/build/resolvers/resolve-document.js.map +1 -0
  175. package/build/resolvers/resolve-document.test.d.ts +1 -0
  176. package/build/resolvers/resolve-document.test.js +299 -0
  177. package/build/resolvers/resolve-document.test.js.map +1 -0
  178. package/build/resolvers/resolve-json-document.d.ts +40 -0
  179. package/build/resolvers/resolve-json-document.js +42 -0
  180. package/build/resolvers/resolve-json-document.js.map +1 -0
  181. package/build/resolvers/resolve-json-document.test.d.ts +1 -0
  182. package/build/resolvers/resolve-json-document.test.js +61 -0
  183. package/build/resolvers/resolve-json-document.test.js.map +1 -0
  184. package/build/structured-error.d.ts +49 -0
  185. package/build/structured-error.js +17 -0
  186. package/build/structured-error.js.map +1 -0
  187. package/build/structured-error.test.d.ts +1 -0
  188. package/build/structured-error.test.js +112 -0
  189. package/build/structured-error.test.js.map +1 -0
  190. package/build/validation/describe-jsonld-failure.d.ts +50 -0
  191. package/build/validation/describe-jsonld-failure.js +104 -0
  192. package/build/validation/describe-jsonld-failure.js.map +1 -0
  193. package/build/validation/describe-jsonld-failure.test.d.ts +1 -0
  194. package/build/validation/describe-jsonld-failure.test.js +141 -0
  195. package/build/validation/describe-jsonld-failure.test.js.map +1 -0
  196. package/build/validation/errors.d.ts +40 -0
  197. package/build/validation/errors.js +75 -0
  198. package/build/validation/errors.js.map +1 -0
  199. package/build/validation/index.d.ts +4 -0
  200. package/build/validation/index.js +5 -0
  201. package/build/validation/index.js.map +1 -0
  202. package/build/validation/validate-against-schemas.d.ts +22 -0
  203. package/build/validation/validate-against-schemas.js +65 -0
  204. package/build/validation/validate-against-schemas.js.map +1 -0
  205. package/build/validation/validate-against-schemas.test.d.ts +1 -0
  206. package/build/validation/validate-against-schemas.test.js +169 -0
  207. package/build/validation/validate-against-schemas.test.js.map +1 -0
  208. package/build/validation/validate-jsonld.context-cache.test.d.ts +1 -0
  209. package/build/validation/validate-jsonld.context-cache.test.js +32 -0
  210. package/build/validation/validate-jsonld.context-cache.test.js.map +1 -0
  211. package/build/validation/validate-jsonld.d.ts +33 -0
  212. package/build/validation/validate-jsonld.js +99 -0
  213. package/build/validation/validate-jsonld.js.map +1 -0
  214. package/build/validation/validate-jsonld.ssrf.test.d.ts +1 -0
  215. package/build/validation/validate-jsonld.ssrf.test.js +108 -0
  216. package/build/validation/validate-jsonld.ssrf.test.js.map +1 -0
  217. package/build/validation/validate-jsonld.test.d.ts +1 -0
  218. package/build/validation/validate-jsonld.test.js +200 -0
  219. package/build/validation/validate-jsonld.test.js.map +1 -0
  220. package/package.json +64 -3
@@ -0,0 +1,41 @@
1
+ import { parseEntityTag } from './parse-entity-tag.js';
2
+ describe('parseEntityTag', () => {
3
+ describe('accepts valid entity-tags', () => {
4
+ it.each([
5
+ '""',
6
+ '"abc"',
7
+ '"33a64df551425fcc55e4d42a148795d9f25f89d4"',
8
+ 'W/"abc"',
9
+ 'W/""',
10
+ '"a-b-c.d"',
11
+ '"!@#$%^&*()"',
12
+ ])('%s', (value) => {
13
+ expect(parseEntityTag(value)).toBe(value);
14
+ });
15
+ });
16
+ describe('rejects malformed entity-tags', () => {
17
+ it.each([
18
+ ['unquoted value', 'abc'],
19
+ ['single-quoted value', "'abc'"],
20
+ ['missing closing quote', '"abc'],
21
+ ['missing opening quote', 'abc"'],
22
+ ['embedded double quote', '"a"b"'],
23
+ ['lower-case w prefix', 'w/"abc"'],
24
+ ['w prefix without slash', 'W"abc"'],
25
+ ['empty string', ''],
26
+ ['trailing whitespace', '"abc" '],
27
+ ['multiple etags (not supported)', '"abc", "def"'],
28
+ ])('%s', (_label, value) => {
29
+ expect(parseEntityTag(value)).toBeUndefined();
30
+ });
31
+ });
32
+ describe('rejects header-injection vectors', () => {
33
+ it('rejects CRLF in the value', () => {
34
+ expect(parseEntityTag('"abc"\r\nX-Evil: pwn')).toBeUndefined();
35
+ });
36
+ it('rejects embedded null byte', () => {
37
+ expect(parseEntityTag('"a\x00b"')).toBeUndefined();
38
+ });
39
+ });
40
+ });
41
+ //# sourceMappingURL=parse-entity-tag.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-entity-tag.test.js","sourceRoot":"","sources":["../../src/http-headers/parse-entity-tag.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACzC,EAAE,CAAC,IAAI,CAAC;YACN,IAAI;YACJ,OAAO;YACP,4CAA4C;YAC5C,SAAS;YACT,MAAM;YACN,WAAW;YACX,cAAc;SACf,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;YACjB,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;QAC7C,EAAE,CAAC,IAAI,CAAC;YACN,CAAC,gBAAgB,EAAE,KAAK,CAAC;YACzB,CAAC,qBAAqB,EAAE,OAAO,CAAC;YAChC,CAAC,uBAAuB,EAAE,MAAM,CAAC;YACjC,CAAC,uBAAuB,EAAE,MAAM,CAAC;YACjC,CAAC,uBAAuB,EAAE,OAAO,CAAC;YAClC,CAAC,qBAAqB,EAAE,SAAS,CAAC;YAClC,CAAC,wBAAwB,EAAE,QAAQ,CAAC;YACpC,CAAC,cAAc,EAAE,EAAE,CAAC;YACpB,CAAC,qBAAqB,EAAE,QAAQ,CAAC;YACjC,CAAC,gCAAgC,EAAE,cAAc,CAAC;SACnD,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YACzB,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAChD,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;YACnC,MAAM,CAAC,cAAc,CAAC,sBAAsB,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Validates an HTTP `Last-Modified` / `If-Modified-Since` / `Date` value
3
+ * per RFC 7231 §7.1.1.1 IMF-fixdate. Returns the value verbatim if it
4
+ * matches the strict IMF-fixdate grammar; otherwise returns `undefined`.
5
+ *
6
+ * Strict by design: only IMF-fixdate is accepted. Servers that send
7
+ * RFC 850 or asctime() format produce `undefined` here. If you hit a
8
+ * real upstream sending a legacy format, broaden this validator with a
9
+ * deliberate test case rather than relaxing it ad hoc.
10
+ *
11
+ * @see https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1
12
+ */
13
+ export declare function parseImfDate(value: string): string | undefined;
@@ -0,0 +1,23 @@
1
+ import { isSafeHeaderValue } from './is-safe-header-value.js';
2
+ // RFC 7231 §7.1.1.1 IMF-fixdate: the only timestamp format servers SHOULD
3
+ // generate. RFC 850 and asctime() are receivable per the RFC but
4
+ // effectively dead in modern HTTP; this validator rejects them.
5
+ const IMF_FIXDATE_RE = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/;
6
+ /**
7
+ * Validates an HTTP `Last-Modified` / `If-Modified-Since` / `Date` value
8
+ * per RFC 7231 §7.1.1.1 IMF-fixdate. Returns the value verbatim if it
9
+ * matches the strict IMF-fixdate grammar; otherwise returns `undefined`.
10
+ *
11
+ * Strict by design: only IMF-fixdate is accepted. Servers that send
12
+ * RFC 850 or asctime() format produce `undefined` here. If you hit a
13
+ * real upstream sending a legacy format, broaden this validator with a
14
+ * deliberate test case rather than relaxing it ad hoc.
15
+ *
16
+ * @see https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1
17
+ */
18
+ export function parseImfDate(value) {
19
+ if (!isSafeHeaderValue(value))
20
+ return undefined;
21
+ return IMF_FIXDATE_RE.test(value) ? value : undefined;
22
+ }
23
+ //# sourceMappingURL=parse-imf-date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-imf-date.js","sourceRoot":"","sources":["../../src/http-headers/parse-imf-date.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,0EAA0E;AAC1E,iEAAiE;AACjE,gEAAgE;AAChE,MAAM,cAAc,GAClB,sHAAsH,CAAC;AAEzH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAChD,OAAO,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { parseImfDate } from './parse-imf-date.js';
2
+ describe('parseImfDate', () => {
3
+ describe('accepts valid IMF-fixdate', () => {
4
+ it.each(['Sun, 06 Nov 1994 08:49:37 GMT', 'Wed, 21 May 2026 12:00:00 GMT', 'Mon, 01 Jan 2024 00:00:00 GMT'])('%s', (value) => {
5
+ expect(parseImfDate(value)).toBe(value);
6
+ });
7
+ });
8
+ describe('rejects malformed dates', () => {
9
+ it.each([
10
+ ['lowercase day name', 'sun, 06 Nov 1994 08:49:37 GMT'],
11
+ ['missing comma', 'Sun 06 Nov 1994 08:49:37 GMT'],
12
+ ['1-digit day', 'Sun, 6 Nov 1994 08:49:37 GMT'],
13
+ ['2-digit year', 'Sun, 06 Nov 94 08:49:37 GMT'],
14
+ ['no GMT suffix', 'Sun, 06 Nov 1994 08:49:37'],
15
+ ['UTC instead of GMT', 'Sun, 06 Nov 1994 08:49:37 UTC'],
16
+ ['ISO-8601', '2026-05-21T12:00:00Z'],
17
+ ['RFC 850 format', 'Sunday, 06-Nov-94 08:49:37 GMT'],
18
+ ['asctime format', 'Sun Nov 6 08:49:37 1994'],
19
+ ['empty string', ''],
20
+ ['gibberish', 'not a date'],
21
+ ])('%s', (_label, value) => {
22
+ expect(parseImfDate(value)).toBeUndefined();
23
+ });
24
+ });
25
+ describe('rejects header-injection vectors', () => {
26
+ it('rejects CRLF in the value', () => {
27
+ expect(parseImfDate('Sun, 06 Nov 1994 08:49:37 GMT\r\nX-Evil: pwn')).toBeUndefined();
28
+ });
29
+ });
30
+ });
31
+ //# sourceMappingURL=parse-imf-date.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-imf-date.test.js","sourceRoot":"","sources":["../../src/http-headers/parse-imf-date.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACzC,EAAE,CAAC,IAAI,CAAC,CAAC,+BAA+B,EAAE,+BAA+B,EAAE,+BAA+B,CAAC,CAAC,CAC1G,IAAI,EACJ,CAAC,KAAK,EAAE,EAAE;YACR,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACvC,EAAE,CAAC,IAAI,CAAC;YACN,CAAC,oBAAoB,EAAE,+BAA+B,CAAC;YACvD,CAAC,eAAe,EAAE,8BAA8B,CAAC;YACjD,CAAC,aAAa,EAAE,8BAA8B,CAAC;YAC/C,CAAC,cAAc,EAAE,6BAA6B,CAAC;YAC/C,CAAC,eAAe,EAAE,2BAA2B,CAAC;YAC9C,CAAC,oBAAoB,EAAE,+BAA+B,CAAC;YACvD,CAAC,UAAU,EAAE,sBAAsB,CAAC;YACpC,CAAC,gBAAgB,EAAE,gCAAgC,CAAC;YACpD,CAAC,gBAAgB,EAAE,0BAA0B,CAAC;YAC9C,CAAC,cAAc,EAAE,EAAE,CAAC;YACpB,CAAC,WAAW,EAAE,YAAY,CAAC;SAC5B,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YACzB,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAChD,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;YACnC,MAAM,CAAC,YAAY,CAAC,8CAA8C,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACvF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Validates an HTTP `Content-Type` value per RFC 7231 §3.1.1.1. Returns
3
+ * the value verbatim if it matches the media-type grammar (`type/subtype`
4
+ * with optional `; name=value` parameters); otherwise returns `undefined`.
5
+ *
6
+ * @see https://www.rfc-editor.org/rfc/rfc7231#section-3.1.1.1
7
+ */
8
+ export declare function parseMediaType(value: string): string | undefined;
@@ -0,0 +1,21 @@
1
+ import { isSafeHeaderValue } from './is-safe-header-value.js';
2
+ // RFC 7231 §3.1.1.1: media-type = type "/" subtype *( OWS ";" OWS parameter )
3
+ // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "."
4
+ // / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
5
+ const TOKEN = "[!#$%&'*+\\-.^_`|~0-9A-Za-z]+";
6
+ const QUOTED = '"[^"]*"';
7
+ const PARAM = `\\s*;\\s*${TOKEN}=(?:${TOKEN}|${QUOTED})`;
8
+ const MEDIA_TYPE_RE = new RegExp(`^${TOKEN}\\/${TOKEN}(?:${PARAM})*$`);
9
+ /**
10
+ * Validates an HTTP `Content-Type` value per RFC 7231 §3.1.1.1. Returns
11
+ * the value verbatim if it matches the media-type grammar (`type/subtype`
12
+ * with optional `; name=value` parameters); otherwise returns `undefined`.
13
+ *
14
+ * @see https://www.rfc-editor.org/rfc/rfc7231#section-3.1.1.1
15
+ */
16
+ export function parseMediaType(value) {
17
+ if (!isSafeHeaderValue(value))
18
+ return undefined;
19
+ return MEDIA_TYPE_RE.test(value) ? value : undefined;
20
+ }
21
+ //# sourceMappingURL=parse-media-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-media-type.js","sourceRoot":"","sources":["../../src/http-headers/parse-media-type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,8EAA8E;AAC9E,oEAAoE;AACpE,sDAAsD;AACtD,MAAM,KAAK,GAAG,+BAA+B,CAAC;AAC9C,MAAM,MAAM,GAAG,SAAS,CAAC;AACzB,MAAM,KAAK,GAAG,YAAY,KAAK,OAAO,KAAK,IAAI,MAAM,GAAG,CAAC;AACzD,MAAM,aAAa,GAAG,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,KAAK,MAAM,KAAK,KAAK,CAAC,CAAC;AAEvE;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAChD,OAAO,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ import { parseMediaType } from './parse-media-type.js';
2
+ describe('parseMediaType', () => {
3
+ describe('accepts valid media types', () => {
4
+ it.each([
5
+ 'application/json',
6
+ 'text/plain',
7
+ 'application/vnd.api+json',
8
+ 'application/x-www-form-urlencoded',
9
+ 'image/svg+xml',
10
+ 'text/plain; charset=utf-8',
11
+ 'text/plain;charset=utf-8',
12
+ 'application/json; charset=utf-8; profile=foo',
13
+ 'text/plain; charset="utf-8"',
14
+ 'multipart/form-data; boundary="----WebKitFormBoundary"',
15
+ ])('%s', (value) => {
16
+ expect(parseMediaType(value)).toBe(value);
17
+ });
18
+ });
19
+ describe('rejects malformed media types', () => {
20
+ it.each([
21
+ ['missing slash', 'application'],
22
+ ['empty type', '/json'],
23
+ ['empty subtype', 'application/'],
24
+ ['whitespace in type', 'app lication/json'],
25
+ ['whitespace in subtype', 'application/js on'],
26
+ ['comma-separated list (not supported by this validator)', 'application/json, text/plain'],
27
+ ['empty string', ''],
28
+ ['unquoted parameter value with whitespace', 'text/plain; charset=ut f-8'],
29
+ ])('%s', (_label, value) => {
30
+ expect(parseMediaType(value)).toBeUndefined();
31
+ });
32
+ });
33
+ describe('rejects header-injection vectors', () => {
34
+ it('rejects CRLF in the value', () => {
35
+ expect(parseMediaType('application/json\r\nX-Evil: pwn')).toBeUndefined();
36
+ });
37
+ });
38
+ });
39
+ //# sourceMappingURL=parse-media-type.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-media-type.test.js","sourceRoot":"","sources":["../../src/http-headers/parse-media-type.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACzC,EAAE,CAAC,IAAI,CAAC;YACN,kBAAkB;YAClB,YAAY;YACZ,0BAA0B;YAC1B,mCAAmC;YACnC,eAAe;YACf,2BAA2B;YAC3B,0BAA0B;YAC1B,8CAA8C;YAC9C,6BAA6B;YAC7B,wDAAwD;SACzD,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;YACjB,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;QAC7C,EAAE,CAAC,IAAI,CAAC;YACN,CAAC,eAAe,EAAE,aAAa,CAAC;YAChC,CAAC,YAAY,EAAE,OAAO,CAAC;YACvB,CAAC,eAAe,EAAE,cAAc,CAAC;YACjC,CAAC,oBAAoB,EAAE,mBAAmB,CAAC;YAC3C,CAAC,uBAAuB,EAAE,mBAAmB,CAAC;YAC9C,CAAC,wDAAwD,EAAE,8BAA8B,CAAC;YAC1F,CAAC,cAAc,EAAE,EAAE,CAAC;YACpB,CAAC,0CAA0C,EAAE,4BAA4B,CAAC;SAC3E,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YACzB,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAChD,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;YACnC,MAAM,CAAC,cAAc,CAAC,iCAAiC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `User-Agent` sent on every guarded fetch (`resolveDocument` and
3
+ * everything composed on it) unless overridden.
4
+ *
5
+ * Anonymous (UA-less) automated fetches risk being challenged by bot
6
+ * protection on well-known context hosts (observed 2026-08-12 on a
7
+ * production RI: w3.org returned 429 challenge pages to UA-less fetches
8
+ * while identified requests from the same host passed), so every request
9
+ * identifies itself. See uncefact/tests-untp#886 for the fetch-per-issuance
10
+ * pattern that provoked it. Operators override via the `RI_HTTP_USER_AGENT`
11
+ * environment variable (read per request, so it applies without a rebuild),
12
+ * and a caller-supplied `user-agent` header takes precedence over both.
13
+ *
14
+ * These live in `http-headers` (not `resolvers`) so boot-time validation can
15
+ * import them in environments that cannot load the resolver stack's undici
16
+ * dependency (e.g. jsdom test environments).
17
+ */
18
+ export declare const DEFAULT_USER_AGENT = "uncefact-untp-utils (+https://github.com/uncefact/tests-untp)";
19
+ /** Environment variable that overrides {@link DEFAULT_USER_AGENT}. */
20
+ export declare const USER_AGENT_ENV_VAR = "RI_HTTP_USER_AGENT";
21
+ /**
22
+ * Whether `value` can be sent as an HTTP `User-Agent` field value: non-blank,
23
+ * free of control characters (notably CR/LF, which would corrupt or split
24
+ * the request's header block; HTAB is also rejected, deliberately stricter
25
+ * than the wire grammar), and within the fetch ByteString range. undici
26
+ * converts header values to ByteString and throws on any UTF-16 code unit
27
+ * above U+00FF (undici@6.25.0 `lib/web/fetch/webidl.js`), so a value the
28
+ * ByteString check fails here would otherwise pass boot validation and then
29
+ * break every guarded fetch at request time. Latin-1 text (e.g. `café`)
30
+ * remains sendable and accepted.
31
+ *
32
+ * Exported so deployments can validate their configured override at boot
33
+ * (fail-fast) instead of meeting the guarded fetch's request-time failure.
34
+ */
35
+ export declare function isValidHttpUserAgent(value: string): boolean;
@@ -0,0 +1,45 @@
1
+ import { isSafeHeaderValue } from './is-safe-header-value.js';
2
+ /**
3
+ * `User-Agent` sent on every guarded fetch (`resolveDocument` and
4
+ * everything composed on it) unless overridden.
5
+ *
6
+ * Anonymous (UA-less) automated fetches risk being challenged by bot
7
+ * protection on well-known context hosts (observed 2026-08-12 on a
8
+ * production RI: w3.org returned 429 challenge pages to UA-less fetches
9
+ * while identified requests from the same host passed), so every request
10
+ * identifies itself. See uncefact/tests-untp#886 for the fetch-per-issuance
11
+ * pattern that provoked it. Operators override via the `RI_HTTP_USER_AGENT`
12
+ * environment variable (read per request, so it applies without a rebuild),
13
+ * and a caller-supplied `user-agent` header takes precedence over both.
14
+ *
15
+ * These live in `http-headers` (not `resolvers`) so boot-time validation can
16
+ * import them in environments that cannot load the resolver stack's undici
17
+ * dependency (e.g. jsdom test environments).
18
+ */
19
+ export const DEFAULT_USER_AGENT = 'uncefact-untp-utils (+https://github.com/uncefact/tests-untp)';
20
+ /** Environment variable that overrides {@link DEFAULT_USER_AGENT}. */
21
+ export const USER_AGENT_ENV_VAR = 'RI_HTTP_USER_AGENT';
22
+ /**
23
+ * Whether `value` can be sent as an HTTP `User-Agent` field value: non-blank,
24
+ * free of control characters (notably CR/LF, which would corrupt or split
25
+ * the request's header block; HTAB is also rejected, deliberately stricter
26
+ * than the wire grammar), and within the fetch ByteString range. undici
27
+ * converts header values to ByteString and throws on any UTF-16 code unit
28
+ * above U+00FF (undici@6.25.0 `lib/web/fetch/webidl.js`), so a value the
29
+ * ByteString check fails here would otherwise pass boot validation and then
30
+ * break every guarded fetch at request time. Latin-1 text (e.g. `café`)
31
+ * remains sendable and accepted.
32
+ *
33
+ * Exported so deployments can validate their configured override at boot
34
+ * (fail-fast) instead of meeting the guarded fetch's request-time failure.
35
+ */
36
+ export function isValidHttpUserAgent(value) {
37
+ if (value.trim() === '' || !isSafeHeaderValue(value))
38
+ return false;
39
+ for (let i = 0; i < value.length; i += 1) {
40
+ if (value.charCodeAt(i) > 0xff)
41
+ return false;
42
+ }
43
+ return true;
44
+ }
45
+ //# sourceMappingURL=user-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-agent.js","sourceRoot":"","sources":["../../src/http-headers/user-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,+DAA+D,CAAC;AAElG,sEAAsE;AACtE,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;YAAE,OAAO,KAAK,CAAC;IAC/C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { DEFAULT_USER_AGENT, isValidHttpUserAgent } from './user-agent.js';
2
+ describe('isValidHttpUserAgent', () => {
3
+ it('accepts an ordinary product token', () => {
4
+ expect(isValidHttpUserAgent('acme-ri/1.2 (+https://ri.acme.example)')).toBe(true);
5
+ });
6
+ it('accepts the library default', () => {
7
+ expect(isValidHttpUserAgent(DEFAULT_USER_AGENT)).toBe(true);
8
+ });
9
+ it('accepts Latin-1 text, which undici can send', () => {
10
+ expect(isValidHttpUserAgent('café-agent')).toBe(true);
11
+ expect(isValidHttpUserAgent('agentÿ')).toBe(true);
12
+ });
13
+ it('rejects blank and whitespace-only values', () => {
14
+ expect(isValidHttpUserAgent('')).toBe(false);
15
+ expect(isValidHttpUserAgent(' ')).toBe(false);
16
+ });
17
+ it('rejects control characters, including CR/LF and tab', () => {
18
+ expect(isValidHttpUserAgent('evil\r\nX-Injected: 1')).toBe(false);
19
+ expect(isValidHttpUserAgent('a\tb')).toBe(false);
20
+ expect(isValidHttpUserAgent('a\u0000b')).toBe(false);
21
+ });
22
+ it('rejects code units above U+00FF, which undici refuses as ByteString', () => {
23
+ expect(isValidHttpUserAgent('agentĀ')).toBe(false);
24
+ expect(isValidHttpUserAgent('agent\u{1f642}')).toBe(false);
25
+ });
26
+ });
27
+ //# sourceMappingURL=user-agent.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-agent.test.js","sourceRoot":"","sources":["../../src/http-headers/user-agent.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE3E,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,oBAAoB,CAAC,wCAAwC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,oBAAoB,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7C,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,oBAAoB,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClE,MAAM,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjD,MAAM,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/build/index.d.ts CHANGED
@@ -1 +1 @@
1
- export * from './multibase-digest/index.js';
1
+ export * from './structured-error.js';
package/build/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export * from './multibase-digest/index.js';
1
+ export * from './structured-error.js';
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { StructuredError } from '../structured-error.js';
2
+ /**
3
+ * Base for every diagnostic from `@uncefact/untp-utils/loaders`.
4
+ * Catch to handle any schema-loader failure generically; catch a concrete
5
+ * subclass for specific handling.
6
+ */
7
+ export declare class SchemaLoaderError extends StructuredError {
8
+ }
9
+ /** The network request to the schema URL rejected (DNS failure, connection refused, etc.). */
10
+ export declare class SchemaLoaderNetworkError extends SchemaLoaderError {
11
+ constructor(url: string, cause: unknown);
12
+ }
13
+ /** The schema URL responded with a non-2xx HTTP status. */
14
+ export declare class SchemaLoaderHttpError extends SchemaLoaderError {
15
+ readonly status: number;
16
+ constructor(url: string, status: number);
17
+ }
18
+ /** The schema URL responded with a body that is not parseable as JSON. */
19
+ export declare class SchemaLoaderInvalidJsonError extends SchemaLoaderError {
20
+ constructor(url: string, cause: unknown);
21
+ }
@@ -0,0 +1,45 @@
1
+ import { StructuredError } from '../structured-error.js';
2
+ /**
3
+ * Base for every diagnostic from `@uncefact/untp-utils/loaders`.
4
+ * Catch to handle any schema-loader failure generically; catch a concrete
5
+ * subclass for specific handling.
6
+ */
7
+ export class SchemaLoaderError extends StructuredError {
8
+ }
9
+ /** The network request to the schema URL rejected (DNS failure, connection refused, etc.). */
10
+ export class SchemaLoaderNetworkError extends SchemaLoaderError {
11
+ constructor(url, cause) {
12
+ super({
13
+ code: 'schema-loader.network-error',
14
+ message: `Could not reach ${url}.`,
15
+ received: cause instanceof Error ? cause.message : String(cause),
16
+ expected: `a 2xx response from ${url}`,
17
+ cause,
18
+ });
19
+ }
20
+ }
21
+ /** The schema URL responded with a non-2xx HTTP status. */
22
+ export class SchemaLoaderHttpError extends SchemaLoaderError {
23
+ status;
24
+ constructor(url, status) {
25
+ super({
26
+ code: 'schema-loader.http-error',
27
+ message: `${url} returned status ${status}.`,
28
+ received: status,
29
+ expected: '2xx',
30
+ });
31
+ this.status = status;
32
+ }
33
+ }
34
+ /** The schema URL responded with a body that is not parseable as JSON. */
35
+ export class SchemaLoaderInvalidJsonError extends SchemaLoaderError {
36
+ constructor(url, cause) {
37
+ super({
38
+ code: 'schema-loader.invalid-json',
39
+ message: `${url} returned a body that is not valid JSON.`,
40
+ received: cause instanceof Error ? cause.message : String(cause),
41
+ cause,
42
+ });
43
+ }
44
+ }
45
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/loaders/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,eAAe;CAAG;AAEzD,8FAA8F;AAC9F,MAAM,OAAO,wBAAyB,SAAQ,iBAAiB;IAC7D,YAAY,GAAW,EAAE,KAAc;QACrC,KAAK,CAAC;YACJ,IAAI,EAAE,6BAA6B;YACnC,OAAO,EAAE,mBAAmB,GAAG,GAAG;YAClC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAChE,QAAQ,EAAE,uBAAuB,GAAG,EAAE;YACtC,KAAK;SACN,CAAC,CAAC;IACL,CAAC;CACF;AAED,2DAA2D;AAC3D,MAAM,OAAO,qBAAsB,SAAQ,iBAAiB;IACjD,MAAM,CAAS;IACxB,YAAY,GAAW,EAAE,MAAc;QACrC,KAAK,CAAC;YACJ,IAAI,EAAE,0BAA0B;YAChC,OAAO,EAAE,GAAG,GAAG,oBAAoB,MAAM,GAAG;YAC5C,QAAQ,EAAE,MAAM;YAChB,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,0EAA0E;AAC1E,MAAM,OAAO,4BAA6B,SAAQ,iBAAiB;IACjE,YAAY,GAAW,EAAE,KAAc;QACrC,KAAK,CAAC;YACJ,IAAI,EAAE,4BAA4B;YAClC,OAAO,EAAE,GAAG,GAAG,0CAA0C;YACzD,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAChE,KAAK;SACN,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ export { SchemaLoaderError, SchemaLoaderNetworkError, SchemaLoaderHttpError, SchemaLoaderInvalidJsonError, } from './errors.js';
2
+ export { createSchemaLoader, type SchemaLoader } from './schema-loader.js';
3
+ export { createJsonLdDocumentLoader, type JsonLdDocumentLoaderOptions, type LoadedRemoteDocument, } from './jsonld-document-loader.js';
@@ -0,0 +1,4 @@
1
+ export { SchemaLoaderError, SchemaLoaderNetworkError, SchemaLoaderHttpError, SchemaLoaderInvalidJsonError, } from './errors.js';
2
+ export { createSchemaLoader } from './schema-loader.js';
3
+ export { createJsonLdDocumentLoader, } from './jsonld-document-loader.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/loaders/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,qBAAqB,EACrB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAqB,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACL,0BAA0B,GAG3B,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { ResolveDocumentOptions } from '../resolvers/index.js';
2
+ import type { TtlCache } from '../cache/ttl-cache.js';
3
+ /**
4
+ * The subset of jsonld's `RemoteDocument` shape this loader returns. Carries
5
+ * the fields jsonld.js reads at runtime; it is deliberately not assignable to
6
+ * `@types/jsonld`'s `RemoteDocument` (whose `document: JsonLd` is narrower
7
+ * than the `unknown` a fetched body can be), which is one reason the `toRDF`
8
+ * call site casts its options (see `validateJsonLd`).
9
+ */
10
+ export interface LoadedRemoteDocument {
11
+ contextUrl?: string;
12
+ documentUrl: string;
13
+ document: unknown;
14
+ }
15
+ /** Options for {@link createJsonLdDocumentLoader}. */
16
+ export interface JsonLdDocumentLoaderOptions extends ResolveDocumentOptions {
17
+ /** `Accept` header for `@context` fetches. Defaults to JSON-LD then JSON. */
18
+ accept?: string;
19
+ /**
20
+ * Optional cache, keyed by URL, for resolved `@context` documents. When
21
+ * supplied, a document fetched once is reused for the cache's TTL rather
22
+ * than re-fetched on every expansion. Only successful fetches are cached
23
+ * (the {@link TtlCache} contract does not cache rejected fetches), so a
24
+ * URL the guard rejects is re-checked every time. A successfully fetched
25
+ * and parsed document is cached even if downstream JSON-LD processing
26
+ * later rejects it.
27
+ */
28
+ cache?: TtlCache<LoadedRemoteDocument>;
29
+ }
30
+ /**
31
+ * Builds a jsonld document loader that fetches every remote `@context`
32
+ * document through {@link resolveJsonDocument}, so each URL (and every
33
+ * redirect hop) passes `validatePublicUrl` and is pinned to the validated IP
34
+ * before any connection is made, with the resolver's size, timeout, and
35
+ * redirect bounds applied.
36
+ *
37
+ * Without this loader, `jsonld.toRDF` falls back to jsonld.js's default Node
38
+ * document loader, which fetches `@context` URLs with no SSRF protection: a
39
+ * crafted `@context` could drive requests to internal or cloud-metadata
40
+ * addresses during expansion. Wiring this loader in is the JSON-LD
41
+ * document-loader guard committed to in ADR-033 §7.
42
+ *
43
+ * @see ../../../../docs/adrs/033-cvc-architecture.md ADR-033 §7 (Security considerations).
44
+ * @see https://github.com/uncefact/tests-untp/issues/707
45
+ */
46
+ export declare function createJsonLdDocumentLoader(options?: JsonLdDocumentLoaderOptions): (url: string) => Promise<LoadedRemoteDocument>;
@@ -0,0 +1,34 @@
1
+ /** Default `Accept` for `@context` documents: JSON-LD, falling back to JSON. */
2
+ const CONTEXT_ACCEPT_HEADER = 'application/ld+json, application/json;q=0.9';
3
+ /**
4
+ * Builds a jsonld document loader that fetches every remote `@context`
5
+ * document through {@link resolveJsonDocument}, so each URL (and every
6
+ * redirect hop) passes `validatePublicUrl` and is pinned to the validated IP
7
+ * before any connection is made, with the resolver's size, timeout, and
8
+ * redirect bounds applied.
9
+ *
10
+ * Without this loader, `jsonld.toRDF` falls back to jsonld.js's default Node
11
+ * document loader, which fetches `@context` URLs with no SSRF protection: a
12
+ * crafted `@context` could drive requests to internal or cloud-metadata
13
+ * addresses during expansion. Wiring this loader in is the JSON-LD
14
+ * document-loader guard committed to in ADR-033 §7.
15
+ *
16
+ * @see ../../../../docs/adrs/033-cvc-architecture.md ADR-033 §7 (Security considerations).
17
+ * @see https://github.com/uncefact/tests-untp/issues/707
18
+ */
19
+ export function createJsonLdDocumentLoader(options) {
20
+ const { cache, accept, ...resolverOptions } = options ?? {};
21
+ const load = async (url) => {
22
+ // Lazy import: the resolver stack pulls in undici, which jsdom test
23
+ // environments cannot evaluate, so it loads at fetch time to keep this
24
+ // module importable there.
25
+ const { resolveJsonDocument } = await import('../resolvers/index.js');
26
+ const { json, finalUrl } = await resolveJsonDocument(url, {
27
+ ...resolverOptions,
28
+ accept: accept ?? CONTEXT_ACCEPT_HEADER,
29
+ });
30
+ return { documentUrl: finalUrl, document: json };
31
+ };
32
+ return cache ? (url) => cache.get(url, () => load(url)) : load;
33
+ }
34
+ //# sourceMappingURL=jsonld-document-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonld-document-loader.js","sourceRoot":"","sources":["../../src/loaders/jsonld-document-loader.ts"],"names":[],"mappings":"AAgCA,gFAAgF;AAChF,MAAM,qBAAqB,GAAG,6CAA6C,CAAC;AAE5E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAAqC;IAErC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;IAE5D,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAiC,EAAE;QAChE,oEAAoE;QACpE,uEAAuE;QACvE,2BAA2B;QAC3B,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;QACtE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,mBAAmB,CAAC,GAAG,EAAE;YACxD,GAAG,eAAe;YAClB,MAAM,EAAE,MAAM,IAAI,qBAAqB;SACxC,CAAC,CAAC;QACH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACnD,CAAC,CAAC;IAEF,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjE,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ import { jest } from '@jest/globals';
2
+ import { createInMemoryTtlCache } from '../cache/in-memory-ttl-cache.js';
3
+ const resolveJsonDocument = jest.fn();
4
+ jest.unstable_mockModule('../resolvers/index.js', () => ({
5
+ resolveJsonDocument,
6
+ }));
7
+ const { createJsonLdDocumentLoader } = await import('./jsonld-document-loader.js');
8
+ describe('createJsonLdDocumentLoader', () => {
9
+ beforeEach(() => {
10
+ resolveJsonDocument.mockReset();
11
+ });
12
+ it('resolves the URL through resolveJsonDocument and returns the RemoteDocument shape', async () => {
13
+ resolveJsonDocument.mockResolvedValue({
14
+ json: { '@context': { name: 'http://schema.org/name' } },
15
+ finalUrl: 'https://ex.test/ctx',
16
+ });
17
+ const result = await createJsonLdDocumentLoader()('https://ex.test/ctx');
18
+ expect(result).toEqual({
19
+ documentUrl: 'https://ex.test/ctx',
20
+ document: { '@context': { name: 'http://schema.org/name' } },
21
+ });
22
+ });
23
+ it('requests JSON-LD via the default Accept', async () => {
24
+ resolveJsonDocument.mockResolvedValue({ json: {}, finalUrl: 'https://ex.test/ctx' });
25
+ await createJsonLdDocumentLoader()('https://ex.test/ctx');
26
+ expect(resolveJsonDocument).toHaveBeenCalledWith('https://ex.test/ctx', expect.objectContaining({ accept: expect.stringContaining('application/ld+json') }));
27
+ });
28
+ it('lets the caller override the Accept header', async () => {
29
+ resolveJsonDocument.mockResolvedValue({ json: {}, finalUrl: 'https://ex.test/ctx' });
30
+ await createJsonLdDocumentLoader({ accept: 'application/json' })('https://ex.test/ctx');
31
+ expect(resolveJsonDocument).toHaveBeenCalledWith('https://ex.test/ctx', expect.objectContaining({ accept: 'application/json' }));
32
+ });
33
+ it('reports the post-redirect finalUrl as documentUrl', async () => {
34
+ resolveJsonDocument.mockResolvedValue({ json: {}, finalUrl: 'https://ex.test/final' });
35
+ const result = await createJsonLdDocumentLoader()('https://ex.test/start');
36
+ expect(result.documentUrl).toBe('https://ex.test/final');
37
+ });
38
+ it('forwards resolver options (e.g. allowedSchemes) to resolveJsonDocument', async () => {
39
+ resolveJsonDocument.mockResolvedValue({ json: {}, finalUrl: 'https://ex.test/ctx' });
40
+ await createJsonLdDocumentLoader({ allowedSchemes: ['https'] })('https://ex.test/ctx');
41
+ expect(resolveJsonDocument).toHaveBeenCalledWith('https://ex.test/ctx', expect.objectContaining({ allowedSchemes: ['https'] }));
42
+ });
43
+ it('propagates resolveJsonDocument rejections (SSRF guard, HTTP errors, bad JSON) unchanged', async () => {
44
+ const guardError = Object.assign(new Error('private address'), { code: 'url.private-address' });
45
+ resolveJsonDocument.mockRejectedValue(guardError);
46
+ await expect(createJsonLdDocumentLoader()('http://127.0.0.1/ctx')).rejects.toBe(guardError);
47
+ });
48
+ describe('with a cache', () => {
49
+ it('reuses a resolved context within the TTL instead of re-fetching', async () => {
50
+ resolveJsonDocument.mockResolvedValue({ json: { '@context': {} }, finalUrl: 'https://ex.test/ctx' });
51
+ const cache = createInMemoryTtlCache({ ttlMs: 60_000 });
52
+ const load = createJsonLdDocumentLoader({ cache });
53
+ await load('https://ex.test/ctx');
54
+ await load('https://ex.test/ctx');
55
+ expect(resolveJsonDocument).toHaveBeenCalledTimes(1);
56
+ });
57
+ it('does not cache a rejected resolution (re-checks next time)', async () => {
58
+ const cache = createInMemoryTtlCache({ ttlMs: 60_000 });
59
+ const load = createJsonLdDocumentLoader({ cache });
60
+ resolveJsonDocument.mockRejectedValueOnce(new Error('blocked'));
61
+ await expect(load('https://ex.test/ctx')).rejects.toThrow('blocked');
62
+ resolveJsonDocument.mockResolvedValueOnce({ json: {}, finalUrl: 'https://ex.test/ctx' });
63
+ await expect(load('https://ex.test/ctx')).resolves.toEqual({
64
+ documentUrl: 'https://ex.test/ctx',
65
+ document: {},
66
+ });
67
+ expect(resolveJsonDocument).toHaveBeenCalledTimes(2);
68
+ });
69
+ });
70
+ });
71
+ //# sourceMappingURL=jsonld-document-loader.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonld-document-loader.test.js","sourceRoot":"","sources":["../../src/loaders/jsonld-document-loader.test.ts"],"names":[],"mappings":"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"}