@uncefact/untp-utils 0.0.1 → 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.
- package/LICENSE +202 -674
- package/README.md +23 -1
- package/build/artefacts/detect-version-from-context.d.ts +46 -0
- package/build/artefacts/detect-version-from-context.js +77 -0
- package/build/artefacts/detect-version-from-context.js.map +1 -0
- package/build/artefacts/detect-version-from-context.test.d.ts +1 -0
- package/build/artefacts/detect-version-from-context.test.js +69 -0
- package/build/artefacts/detect-version-from-context.test.js.map +1 -0
- package/build/artefacts/index.d.ts +3 -0
- package/build/artefacts/index.js +4 -0
- package/build/artefacts/index.js.map +1 -0
- package/build/artefacts/urls.d.ts +79 -0
- package/build/artefacts/urls.js +133 -0
- package/build/artefacts/urls.js.map +1 -0
- package/build/artefacts/urls.test.d.ts +1 -0
- package/build/artefacts/urls.test.js +91 -0
- package/build/artefacts/urls.test.js.map +1 -0
- package/build/artefacts/version.d.ts +9 -0
- package/build/artefacts/version.js +17 -0
- package/build/artefacts/version.js.map +1 -0
- package/build/artefacts/version.test.d.ts +1 -0
- package/build/artefacts/version.test.js +16 -0
- package/build/artefacts/version.test.js.map +1 -0
- package/build/cache/in-memory-ttl-cache.d.ts +2 -0
- package/build/cache/in-memory-ttl-cache.js +76 -0
- package/build/cache/in-memory-ttl-cache.js.map +1 -0
- package/build/cache/in-memory-ttl-cache.test.d.ts +1 -0
- package/build/cache/in-memory-ttl-cache.test.js +236 -0
- package/build/cache/in-memory-ttl-cache.test.js.map +1 -0
- package/build/cache/index.d.ts +2 -0
- package/build/cache/index.js +2 -0
- package/build/cache/index.js.map +1 -0
- package/build/cache/ttl-cache.d.ts +22 -0
- package/build/cache/ttl-cache.js +2 -0
- package/build/cache/ttl-cache.js.map +1 -0
- package/build/common/as-non-empty-string.d.ts +7 -0
- package/build/common/as-non-empty-string.js +10 -0
- package/build/common/as-non-empty-string.js.map +1 -0
- package/build/common/as-non-empty-string.test.d.ts +1 -0
- package/build/common/as-non-empty-string.test.js +25 -0
- package/build/common/as-non-empty-string.test.js.map +1 -0
- package/build/common/require-string.d.ts +12 -0
- package/build/common/require-string.js +27 -0
- package/build/common/require-string.js.map +1 -0
- package/build/common/require-string.test.d.ts +1 -0
- package/build/common/require-string.test.js +53 -0
- package/build/common/require-string.test.js.map +1 -0
- package/build/conformity-vocabulary/codes.d.ts +10 -0
- package/build/conformity-vocabulary/codes.js +10 -0
- package/build/conformity-vocabulary/codes.js.map +1 -0
- package/build/conformity-vocabulary/errors.d.ts +44 -0
- package/build/conformity-vocabulary/errors.js +68 -0
- package/build/conformity-vocabulary/errors.js.map +1 -0
- package/build/conformity-vocabulary/index.d.ts +6 -0
- package/build/conformity-vocabulary/index.js +7 -0
- package/build/conformity-vocabulary/index.js.map +1 -0
- package/build/conformity-vocabulary/parse-conformity-catalogue.d.ts +26 -0
- package/build/conformity-vocabulary/parse-conformity-catalogue.js +106 -0
- package/build/conformity-vocabulary/parse-conformity-catalogue.js.map +1 -0
- package/build/conformity-vocabulary/parse-conformity-catalogue.test.d.ts +1 -0
- package/build/conformity-vocabulary/parse-conformity-catalogue.test.js +327 -0
- package/build/conformity-vocabulary/parse-conformity-catalogue.test.js.map +1 -0
- package/build/conformity-vocabulary/parse-conformity-scheme.d.ts +28 -0
- package/build/conformity-vocabulary/parse-conformity-scheme.js +47 -0
- package/build/conformity-vocabulary/parse-conformity-scheme.js.map +1 -0
- package/build/conformity-vocabulary/parse-conformity-scheme.test.d.ts +1 -0
- package/build/conformity-vocabulary/parse-conformity-scheme.test.js +284 -0
- package/build/conformity-vocabulary/parse-conformity-scheme.test.js.map +1 -0
- package/build/conformity-vocabulary/parsers/v0-7-0.parser.d.ts +19 -0
- package/build/conformity-vocabulary/parsers/v0-7-0.parser.js +219 -0
- package/build/conformity-vocabulary/parsers/v0-7-0.parser.js.map +1 -0
- package/build/conformity-vocabulary/types.d.ts +180 -0
- package/build/conformity-vocabulary/types.js +2 -0
- package/build/conformity-vocabulary/types.js.map +1 -0
- package/build/conformity-vocabulary/validate-conformity-claim.d.ts +26 -0
- package/build/conformity-vocabulary/validate-conformity-claim.js +165 -0
- package/build/conformity-vocabulary/validate-conformity-claim.js.map +1 -0
- package/build/conformity-vocabulary/validate-conformity-claim.test.d.ts +1 -0
- package/build/conformity-vocabulary/validate-conformity-claim.test.js +478 -0
- package/build/conformity-vocabulary/validate-conformity-claim.test.js.map +1 -0
- package/build/http-headers/index.d.ts +5 -0
- package/build/http-headers/index.js +6 -0
- package/build/http-headers/index.js.map +1 -0
- package/build/http-headers/is-safe-header-value.d.ts +10 -0
- package/build/http-headers/is-safe-header-value.js +15 -0
- package/build/http-headers/is-safe-header-value.js.map +1 -0
- package/build/http-headers/is-safe-header-value.test.d.ts +1 -0
- package/build/http-headers/is-safe-header-value.test.js +37 -0
- package/build/http-headers/is-safe-header-value.test.js.map +1 -0
- package/build/http-headers/parse-entity-tag.d.ts +9 -0
- package/build/http-headers/parse-entity-tag.js +18 -0
- package/build/http-headers/parse-entity-tag.js.map +1 -0
- package/build/http-headers/parse-entity-tag.test.d.ts +1 -0
- package/build/http-headers/parse-entity-tag.test.js +41 -0
- package/build/http-headers/parse-entity-tag.test.js.map +1 -0
- package/build/http-headers/parse-imf-date.d.ts +13 -0
- package/build/http-headers/parse-imf-date.js +23 -0
- package/build/http-headers/parse-imf-date.js.map +1 -0
- package/build/http-headers/parse-imf-date.test.d.ts +1 -0
- package/build/http-headers/parse-imf-date.test.js +31 -0
- package/build/http-headers/parse-imf-date.test.js.map +1 -0
- package/build/http-headers/parse-media-type.d.ts +8 -0
- package/build/http-headers/parse-media-type.js +21 -0
- package/build/http-headers/parse-media-type.js.map +1 -0
- package/build/http-headers/parse-media-type.test.d.ts +1 -0
- package/build/http-headers/parse-media-type.test.js +39 -0
- package/build/http-headers/parse-media-type.test.js.map +1 -0
- package/build/http-headers/user-agent.d.ts +35 -0
- package/build/http-headers/user-agent.js +45 -0
- package/build/http-headers/user-agent.js.map +1 -0
- package/build/http-headers/user-agent.test.d.ts +1 -0
- package/build/http-headers/user-agent.test.js +27 -0
- package/build/http-headers/user-agent.test.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/loaders/errors.d.ts +21 -0
- package/build/loaders/errors.js +45 -0
- package/build/loaders/errors.js.map +1 -0
- package/build/loaders/index.d.ts +3 -0
- package/build/loaders/index.js +4 -0
- package/build/loaders/index.js.map +1 -0
- package/build/loaders/jsonld-document-loader.d.ts +46 -0
- package/build/loaders/jsonld-document-loader.js +34 -0
- package/build/loaders/jsonld-document-loader.js.map +1 -0
- package/build/loaders/jsonld-document-loader.test.d.ts +1 -0
- package/build/loaders/jsonld-document-loader.test.js +71 -0
- package/build/loaders/jsonld-document-loader.test.js.map +1 -0
- package/build/loaders/schema-loader.d.ts +21 -0
- package/build/loaders/schema-loader.js +67 -0
- package/build/loaders/schema-loader.js.map +1 -0
- package/build/loaders/schema-loader.ssrf.test.d.ts +1 -0
- package/build/loaders/schema-loader.ssrf.test.js +55 -0
- package/build/loaders/schema-loader.ssrf.test.js.map +1 -0
- package/build/loaders/schema-loader.test.d.ts +1 -0
- package/build/loaders/schema-loader.test.js +162 -0
- package/build/loaders/schema-loader.test.js.map +1 -0
- package/build/multibase-digest/multibase-digest.d.ts +23 -0
- package/build/multibase-digest/multibase-digest.js +40 -0
- package/build/multibase-digest/multibase-digest.js.map +1 -1
- package/build/multibase-digest/multibase-digest.test.js +46 -0
- package/build/multibase-digest/multibase-digest.test.js.map +1 -1
- package/build/node/errors.d.ts +41 -0
- package/build/node/errors.js +80 -0
- package/build/node/errors.js.map +1 -0
- package/build/node/index.d.ts +3 -0
- package/build/node/index.js +4 -0
- package/build/node/index.js.map +1 -0
- package/build/node/is-private-ip.d.ts +41 -0
- package/build/node/is-private-ip.js +138 -0
- package/build/node/is-private-ip.js.map +1 -0
- package/build/node/is-private-ip.test.d.ts +1 -0
- package/build/node/is-private-ip.test.js +96 -0
- package/build/node/is-private-ip.test.js.map +1 -0
- package/build/node/validate-public-url.d.ts +49 -0
- package/build/node/validate-public-url.js +94 -0
- package/build/node/validate-public-url.js.map +1 -0
- package/build/node/validate-public-url.test.d.ts +1 -0
- package/build/node/validate-public-url.test.js +177 -0
- package/build/node/validate-public-url.test.js.map +1 -0
- package/build/resolvers/errors.d.ts +43 -0
- package/build/resolvers/errors.js +99 -0
- package/build/resolvers/errors.js.map +1 -0
- package/build/resolvers/index.d.ts +5 -0
- package/build/resolvers/index.js +6 -0
- package/build/resolvers/index.js.map +1 -0
- package/build/resolvers/resolve-document-if-changed.d.ts +48 -0
- package/build/resolvers/resolve-document-if-changed.js +42 -0
- package/build/resolvers/resolve-document-if-changed.js.map +1 -0
- package/build/resolvers/resolve-document-if-changed.test.d.ts +1 -0
- package/build/resolvers/resolve-document-if-changed.test.js +109 -0
- package/build/resolvers/resolve-document-if-changed.test.js.map +1 -0
- package/build/resolvers/resolve-document.d.ts +86 -0
- package/build/resolvers/resolve-document.js +239 -0
- package/build/resolvers/resolve-document.js.map +1 -0
- package/build/resolvers/resolve-document.test.d.ts +1 -0
- package/build/resolvers/resolve-document.test.js +299 -0
- package/build/resolvers/resolve-document.test.js.map +1 -0
- package/build/resolvers/resolve-json-document.d.ts +40 -0
- package/build/resolvers/resolve-json-document.js +42 -0
- package/build/resolvers/resolve-json-document.js.map +1 -0
- package/build/resolvers/resolve-json-document.test.d.ts +1 -0
- package/build/resolvers/resolve-json-document.test.js +61 -0
- package/build/resolvers/resolve-json-document.test.js.map +1 -0
- package/build/structured-error.d.ts +49 -0
- package/build/structured-error.js +17 -0
- package/build/structured-error.js.map +1 -0
- package/build/structured-error.test.d.ts +1 -0
- package/build/structured-error.test.js +112 -0
- package/build/structured-error.test.js.map +1 -0
- package/build/validation/describe-jsonld-failure.d.ts +50 -0
- package/build/validation/describe-jsonld-failure.js +104 -0
- package/build/validation/describe-jsonld-failure.js.map +1 -0
- package/build/validation/describe-jsonld-failure.test.d.ts +1 -0
- package/build/validation/describe-jsonld-failure.test.js +141 -0
- package/build/validation/describe-jsonld-failure.test.js.map +1 -0
- package/build/validation/errors.d.ts +40 -0
- package/build/validation/errors.js +75 -0
- package/build/validation/errors.js.map +1 -0
- package/build/validation/index.d.ts +4 -0
- package/build/validation/index.js +5 -0
- package/build/validation/index.js.map +1 -0
- package/build/validation/validate-against-schemas.d.ts +22 -0
- package/build/validation/validate-against-schemas.js +65 -0
- package/build/validation/validate-against-schemas.js.map +1 -0
- package/build/validation/validate-against-schemas.test.d.ts +1 -0
- package/build/validation/validate-against-schemas.test.js +169 -0
- package/build/validation/validate-against-schemas.test.js.map +1 -0
- package/build/validation/validate-jsonld.context-cache.test.d.ts +1 -0
- package/build/validation/validate-jsonld.context-cache.test.js +32 -0
- package/build/validation/validate-jsonld.context-cache.test.js.map +1 -0
- package/build/validation/validate-jsonld.d.ts +33 -0
- package/build/validation/validate-jsonld.js +99 -0
- package/build/validation/validate-jsonld.js.map +1 -0
- package/build/validation/validate-jsonld.ssrf.test.d.ts +1 -0
- package/build/validation/validate-jsonld.ssrf.test.js +108 -0
- package/build/validation/validate-jsonld.ssrf.test.js.map +1 -0
- package/build/validation/validate-jsonld.test.d.ts +1 -0
- package/build/validation/validate-jsonld.test.js +200 -0
- package/build/validation/validate-jsonld.test.js.map +1 -0
- package/package.json +77 -11
|
@@ -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 './
|
|
1
|
+
export * from './structured-error.js';
|
package/build/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './structured-error.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,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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"}
|