@lde/distribution-probe 0.2.3 → 0.2.4
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/dist/probe.js +23 -11
- package/package.json +1 -1
package/dist/probe.js
CHANGED
|
@@ -551,7 +551,7 @@ async function validateDumpBody(url, headers, options, headResponse) {
|
|
|
551
551
|
const { text, truncated, corrupt } = await decodeProbeBody(bounded);
|
|
552
552
|
return corrupt
|
|
553
553
|
? 'Distribution is not valid gzip'
|
|
554
|
-
: await validateBody(text, getResponse.headers.get('Content-Type'), url,
|
|
554
|
+
: await validateBody(text, getResponse.headers.get('Content-Type'), url, budgetController.signal, truncated);
|
|
555
555
|
})().catch(() => null);
|
|
556
556
|
let budgetTimer;
|
|
557
557
|
const budgetExpiry = new Promise((resolve) => {
|
|
@@ -697,7 +697,7 @@ const rdfContentTypes = [
|
|
|
697
697
|
// validated if it happened to fit the read cap in full; beyond that it is
|
|
698
698
|
// inconclusive, and we must not download it in full to find out.
|
|
699
699
|
const nonStreamableRdfContentTypes = ['application/ld+json'];
|
|
700
|
-
async function validateBody(body, contentType, baseIRI,
|
|
700
|
+
async function validateBody(body, contentType, baseIRI, budgetSignal, truncated) {
|
|
701
701
|
if (body.length === 0) {
|
|
702
702
|
// A complete, empty body is a faulty distribution; an empty *prefix* (a
|
|
703
703
|
// truncated read that yielded no bytes, e.g. a corrupt gzip header) is
|
|
@@ -717,7 +717,7 @@ async function validateBody(body, contentType, baseIRI, timeoutMs, truncated) {
|
|
|
717
717
|
// a complete document, small enough to fit the read cap, can be validated.
|
|
718
718
|
return null;
|
|
719
719
|
}
|
|
720
|
-
const outcome = await classifyRdfBody(body, serialization, baseIRI,
|
|
720
|
+
const outcome = await classifyRdfBody(body, serialization, baseIRI, budgetSignal, truncated);
|
|
721
721
|
switch (outcome.type) {
|
|
722
722
|
case 'empty':
|
|
723
723
|
return 'Distribution contains no RDF triples';
|
|
@@ -734,33 +734,45 @@ async function validateBody(body, contentType, baseIRI, timeoutMs, truncated) {
|
|
|
734
734
|
* Parse an RDF body just far enough to tell whether it carries any triples:
|
|
735
735
|
* resolve on the first triple (presence is all we need, not a full count), on a
|
|
736
736
|
* clean end with none ('empty'), or on a parse error. The parse is bounded by
|
|
737
|
-
* `
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
737
|
+
* `budgetSignal` – the caller's validation-budget {@link AbortController} –
|
|
738
|
+
* because a JSON-LD `@context` is fetched from its origin, and a slow or hanging
|
|
739
|
+
* context host would otherwise stall the probe past its budget; on abort – and
|
|
740
|
+
* likewise when a remote `@context` is unreachable – the outcome is
|
|
741
|
+
* 'inconclusive', so a valid distribution is never flagged faulty for a context
|
|
742
|
+
* host's failure. Sharing the caller's single budget (rather than starting a
|
|
743
|
+
* second, identical timer) keeps that timeout deterministic: it fires once, when
|
|
744
|
+
* the budget elapses, instead of leaving an orphan timer to settle on its own
|
|
745
|
+
* schedule. `baseIRI` resolves any relative IRIs in the document.
|
|
742
746
|
*
|
|
743
747
|
* When `truncated` is true the body is only a bounded prefix of a larger one, so
|
|
744
748
|
* only finding a triple ('hasTriples') is conclusive: a parse error at the cut
|
|
745
749
|
* or a clean end with no triple yet means we did not read far enough, not that
|
|
746
750
|
* the distribution is empty or malformed, and is reported as 'inconclusive'.
|
|
747
751
|
*/
|
|
748
|
-
function classifyRdfBody(body, contentType, baseIRI,
|
|
752
|
+
function classifyRdfBody(body, contentType, baseIRI, budgetSignal, truncated) {
|
|
749
753
|
return new Promise((resolve) => {
|
|
750
754
|
const quads = rdfParser.parse(Readable.from([body]), {
|
|
751
755
|
contentType,
|
|
752
756
|
baseIRI,
|
|
753
757
|
});
|
|
754
|
-
const
|
|
758
|
+
const onBudgetElapsed = () => settle({ type: 'inconclusive' });
|
|
755
759
|
let settled = false;
|
|
756
760
|
function settle(outcome) {
|
|
757
761
|
if (settled)
|
|
758
762
|
return;
|
|
759
763
|
settled = true;
|
|
760
|
-
|
|
764
|
+
budgetSignal.removeEventListener('abort', onBudgetElapsed);
|
|
761
765
|
quads.destroy();
|
|
762
766
|
resolve(outcome);
|
|
763
767
|
}
|
|
768
|
+
// The budget may already have elapsed while the body was being read; treat an
|
|
769
|
+
// already-aborted signal as an immediate expiry rather than waiting for an
|
|
770
|
+
// 'abort' event that will never come.
|
|
771
|
+
if (budgetSignal.aborted) {
|
|
772
|
+
onBudgetElapsed();
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
budgetSignal.addEventListener('abort', onBudgetElapsed, { once: true });
|
|
764
776
|
quads
|
|
765
777
|
.on('data', () => settle({ type: 'hasTriples' }))
|
|
766
778
|
.on('error', (error) => settle(truncated || isRemoteContextError(error)
|