@fedify/vocab-runtime 2.2.0-dev.606 → 2.2.0-dev.613
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/deno.json +1 -1
- package/dist/mod.cjs +32 -14
- package/dist/mod.d.cts +7 -0
- package/dist/mod.d.ts +7 -0
- package/dist/mod.js +32 -14
- package/dist/tests/decimal.test.cjs +2 -2
- package/dist/tests/decimal.test.js +2 -2
- package/dist/tests/{docloader-Ch--TwSL.js → docloader-DEi540Xh.js} +32 -14
- package/dist/tests/{docloader-BVqyre7B.cjs → docloader-txRGFv1x.cjs} +32 -14
- package/dist/tests/docloader.test.cjs +55 -2
- package/dist/tests/docloader.test.js +55 -2
- package/dist/tests/{request-CvhpFknJ.cjs → request-73FIhAHw.cjs} +1 -1
- package/dist/tests/{request-DHlP8Ayx.js → request-BO6hGoBJ.js} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.js +1 -1
- package/package.json +1 -1
- package/src/docloader.test.ts +78 -0
- package/src/docloader.ts +52 -12
package/deno.json
CHANGED
package/dist/mod.cjs
CHANGED
|
@@ -4376,7 +4376,7 @@ var contexts_default = preloadedContexts;
|
|
|
4376
4376
|
//#endregion
|
|
4377
4377
|
//#region deno.json
|
|
4378
4378
|
var name = "@fedify/vocab-runtime";
|
|
4379
|
-
var version = "2.2.0-dev.
|
|
4379
|
+
var version = "2.2.0-dev.613+cf8cd122";
|
|
4380
4380
|
var license = "MIT";
|
|
4381
4381
|
var exports$1 = {
|
|
4382
4382
|
".": "./src/mod.ts",
|
|
@@ -4735,6 +4735,7 @@ const logger = (0, __logtape_logtape.getLogger)([
|
|
|
4735
4735
|
"runtime",
|
|
4736
4736
|
"docloader"
|
|
4737
4737
|
]);
|
|
4738
|
+
const DEFAULT_MAX_REDIRECTION = 20;
|
|
4738
4739
|
/**
|
|
4739
4740
|
* Gets a {@link RemoteDocument} from the given response.
|
|
4740
4741
|
* @param url The URL of the document to load.
|
|
@@ -4850,34 +4851,37 @@ async function getRemoteDocument(url, response, fetch$1) {
|
|
|
4850
4851
|
* @returns The document loader.
|
|
4851
4852
|
* @since 1.3.0
|
|
4852
4853
|
*/
|
|
4853
|
-
function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}) {
|
|
4854
|
+
function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedContexts, userAgent } = {}) {
|
|
4854
4855
|
const tracerProvider = __opentelemetry_api.trace.getTracerProvider();
|
|
4855
4856
|
const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version);
|
|
4856
|
-
|
|
4857
|
+
const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
4858
|
+
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
4857
4859
|
options?.signal?.throwIfAborted();
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
+
const currentUrl = new URL(url).href;
|
|
4861
|
+
if (!skipPreloadedContexts && currentUrl in contexts_default) {
|
|
4862
|
+
logger.debug("Using preloaded context: {url}.", { url: currentUrl });
|
|
4860
4863
|
return {
|
|
4861
4864
|
contextUrl: null,
|
|
4862
|
-
document: contexts_default[
|
|
4863
|
-
documentUrl:
|
|
4865
|
+
document: contexts_default[currentUrl],
|
|
4866
|
+
documentUrl: currentUrl
|
|
4864
4867
|
};
|
|
4865
4868
|
}
|
|
4866
4869
|
if (!allowPrivateAddress) try {
|
|
4867
|
-
await validatePublicUrl(
|
|
4870
|
+
await validatePublicUrl(currentUrl);
|
|
4868
4871
|
} catch (error) {
|
|
4869
4872
|
if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
|
|
4870
|
-
url,
|
|
4873
|
+
url: currentUrl,
|
|
4871
4874
|
error
|
|
4872
4875
|
});
|
|
4873
4876
|
throw error;
|
|
4874
4877
|
}
|
|
4878
|
+
visited.add(currentUrl);
|
|
4875
4879
|
return await tracer.startActiveSpan("activitypub.fetch_document", {
|
|
4876
4880
|
kind: __opentelemetry_api.SpanKind.CLIENT,
|
|
4877
|
-
attributes: { "url.full":
|
|
4881
|
+
attributes: { "url.full": currentUrl }
|
|
4878
4882
|
}, async (span) => {
|
|
4879
4883
|
try {
|
|
4880
|
-
const request = createActivityPubRequest(
|
|
4884
|
+
const request = createActivityPubRequest(currentUrl, { userAgent });
|
|
4881
4885
|
logRequest(logger, request);
|
|
4882
4886
|
const response = await fetch(request, {
|
|
4883
4887
|
redirect: "manual",
|
|
@@ -4885,11 +4889,25 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
4885
4889
|
});
|
|
4886
4890
|
span.setAttribute("http.response.status_code", response.status);
|
|
4887
4891
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
4888
|
-
|
|
4892
|
+
if (redirected >= maximumRedirection) {
|
|
4893
|
+
logger.error("Too many redirections ({redirections}) while fetching document.", {
|
|
4894
|
+
redirections: redirected + 1,
|
|
4895
|
+
url: currentUrl
|
|
4896
|
+
});
|
|
4897
|
+
throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
4898
|
+
}
|
|
4899
|
+
const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
|
|
4889
4900
|
span.setAttribute("http.redirect.url", redirectUrl);
|
|
4890
|
-
|
|
4901
|
+
if (visited.has(redirectUrl)) {
|
|
4902
|
+
logger.error("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
|
|
4903
|
+
url: currentUrl,
|
|
4904
|
+
redirectUrl
|
|
4905
|
+
});
|
|
4906
|
+
throw new FetchError(currentUrl, `Redirect loop detected: ${redirectUrl}`);
|
|
4907
|
+
}
|
|
4908
|
+
return await load(redirectUrl, options, redirected + 1, visited);
|
|
4891
4909
|
}
|
|
4892
|
-
const result = await getRemoteDocument(
|
|
4910
|
+
const result = await getRemoteDocument(currentUrl, response, load);
|
|
4893
4911
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
4894
4912
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
4895
4913
|
return result;
|
package/dist/mod.d.cts
CHANGED
|
@@ -136,6 +136,12 @@ interface DocumentLoaderFactoryOptions {
|
|
|
136
136
|
* If an object is given, it is passed to {@link getUserAgent} function.
|
|
137
137
|
*/
|
|
138
138
|
userAgent?: GetUserAgentOptions | string;
|
|
139
|
+
/**
|
|
140
|
+
* The maximum number of redirections to follow.
|
|
141
|
+
* @default `20`
|
|
142
|
+
* @since 2.2.0
|
|
143
|
+
*/
|
|
144
|
+
maxRedirection?: number;
|
|
139
145
|
}
|
|
140
146
|
/**
|
|
141
147
|
* A factory function that creates an authenticated {@link DocumentLoader} for
|
|
@@ -190,6 +196,7 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
190
196
|
*/
|
|
191
197
|
declare function getDocumentLoader({
|
|
192
198
|
allowPrivateAddress,
|
|
199
|
+
maxRedirection,
|
|
193
200
|
skipPreloadedContexts,
|
|
194
201
|
userAgent
|
|
195
202
|
}?: GetDocumentLoaderOptions): DocumentLoader;
|
package/dist/mod.d.ts
CHANGED
|
@@ -136,6 +136,12 @@ interface DocumentLoaderFactoryOptions {
|
|
|
136
136
|
* If an object is given, it is passed to {@link getUserAgent} function.
|
|
137
137
|
*/
|
|
138
138
|
userAgent?: GetUserAgentOptions | string;
|
|
139
|
+
/**
|
|
140
|
+
* The maximum number of redirections to follow.
|
|
141
|
+
* @default `20`
|
|
142
|
+
* @since 2.2.0
|
|
143
|
+
*/
|
|
144
|
+
maxRedirection?: number;
|
|
139
145
|
}
|
|
140
146
|
/**
|
|
141
147
|
* A factory function that creates an authenticated {@link DocumentLoader} for
|
|
@@ -190,6 +196,7 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
190
196
|
*/
|
|
191
197
|
declare function getDocumentLoader({
|
|
192
198
|
allowPrivateAddress,
|
|
199
|
+
maxRedirection,
|
|
193
200
|
skipPreloadedContexts,
|
|
194
201
|
userAgent
|
|
195
202
|
}?: GetDocumentLoaderOptions): DocumentLoader;
|
package/dist/mod.js
CHANGED
|
@@ -4375,7 +4375,7 @@ var contexts_default = preloadedContexts;
|
|
|
4375
4375
|
//#endregion
|
|
4376
4376
|
//#region deno.json
|
|
4377
4377
|
var name = "@fedify/vocab-runtime";
|
|
4378
|
-
var version = "2.2.0-dev.
|
|
4378
|
+
var version = "2.2.0-dev.613+cf8cd122";
|
|
4379
4379
|
var license = "MIT";
|
|
4380
4380
|
var exports = {
|
|
4381
4381
|
".": "./src/mod.ts",
|
|
@@ -4734,6 +4734,7 @@ const logger = getLogger([
|
|
|
4734
4734
|
"runtime",
|
|
4735
4735
|
"docloader"
|
|
4736
4736
|
]);
|
|
4737
|
+
const DEFAULT_MAX_REDIRECTION = 20;
|
|
4737
4738
|
/**
|
|
4738
4739
|
* Gets a {@link RemoteDocument} from the given response.
|
|
4739
4740
|
* @param url The URL of the document to load.
|
|
@@ -4849,34 +4850,37 @@ async function getRemoteDocument(url, response, fetch$1) {
|
|
|
4849
4850
|
* @returns The document loader.
|
|
4850
4851
|
* @since 1.3.0
|
|
4851
4852
|
*/
|
|
4852
|
-
function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}) {
|
|
4853
|
+
function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedContexts, userAgent } = {}) {
|
|
4853
4854
|
const tracerProvider = trace.getTracerProvider();
|
|
4854
4855
|
const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version);
|
|
4855
|
-
|
|
4856
|
+
const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
4857
|
+
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
4856
4858
|
options?.signal?.throwIfAborted();
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
+
const currentUrl = new URL(url).href;
|
|
4860
|
+
if (!skipPreloadedContexts && currentUrl in contexts_default) {
|
|
4861
|
+
logger.debug("Using preloaded context: {url}.", { url: currentUrl });
|
|
4859
4862
|
return {
|
|
4860
4863
|
contextUrl: null,
|
|
4861
|
-
document: contexts_default[
|
|
4862
|
-
documentUrl:
|
|
4864
|
+
document: contexts_default[currentUrl],
|
|
4865
|
+
documentUrl: currentUrl
|
|
4863
4866
|
};
|
|
4864
4867
|
}
|
|
4865
4868
|
if (!allowPrivateAddress) try {
|
|
4866
|
-
await validatePublicUrl(
|
|
4869
|
+
await validatePublicUrl(currentUrl);
|
|
4867
4870
|
} catch (error) {
|
|
4868
4871
|
if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
|
|
4869
|
-
url,
|
|
4872
|
+
url: currentUrl,
|
|
4870
4873
|
error
|
|
4871
4874
|
});
|
|
4872
4875
|
throw error;
|
|
4873
4876
|
}
|
|
4877
|
+
visited.add(currentUrl);
|
|
4874
4878
|
return await tracer.startActiveSpan("activitypub.fetch_document", {
|
|
4875
4879
|
kind: SpanKind.CLIENT,
|
|
4876
|
-
attributes: { "url.full":
|
|
4880
|
+
attributes: { "url.full": currentUrl }
|
|
4877
4881
|
}, async (span) => {
|
|
4878
4882
|
try {
|
|
4879
|
-
const request = createActivityPubRequest(
|
|
4883
|
+
const request = createActivityPubRequest(currentUrl, { userAgent });
|
|
4880
4884
|
logRequest(logger, request);
|
|
4881
4885
|
const response = await fetch(request, {
|
|
4882
4886
|
redirect: "manual",
|
|
@@ -4884,11 +4888,25 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
4884
4888
|
});
|
|
4885
4889
|
span.setAttribute("http.response.status_code", response.status);
|
|
4886
4890
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
4887
|
-
|
|
4891
|
+
if (redirected >= maximumRedirection) {
|
|
4892
|
+
logger.error("Too many redirections ({redirections}) while fetching document.", {
|
|
4893
|
+
redirections: redirected + 1,
|
|
4894
|
+
url: currentUrl
|
|
4895
|
+
});
|
|
4896
|
+
throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
4897
|
+
}
|
|
4898
|
+
const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
|
|
4888
4899
|
span.setAttribute("http.redirect.url", redirectUrl);
|
|
4889
|
-
|
|
4900
|
+
if (visited.has(redirectUrl)) {
|
|
4901
|
+
logger.error("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
|
|
4902
|
+
url: currentUrl,
|
|
4903
|
+
redirectUrl
|
|
4904
|
+
});
|
|
4905
|
+
throw new FetchError(currentUrl, `Redirect loop detected: ${redirectUrl}`);
|
|
4906
|
+
}
|
|
4907
|
+
return await load(redirectUrl, options, redirected + 1, visited);
|
|
4890
4908
|
}
|
|
4891
|
-
const result = await getRemoteDocument(
|
|
4909
|
+
const result = await getRemoteDocument(currentUrl, response, load);
|
|
4892
4910
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
4893
4911
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
4894
4912
|
return result;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-DWy1uDak.cjs');
|
|
2
|
-
require('./docloader-
|
|
3
|
-
require('./request-
|
|
2
|
+
require('./docloader-txRGFv1x.cjs');
|
|
3
|
+
require('./request-73FIhAHw.cjs');
|
|
4
4
|
require('./link-DYNFAdNu.cjs');
|
|
5
5
|
require('./url-DIjOdK8Q.cjs');
|
|
6
6
|
require('./multicodec--6hQ74zI.cjs');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FetchError, createActivityPubRequest, deno_default, logRequest } from "./request-
|
|
1
|
+
import { FetchError, createActivityPubRequest, deno_default, logRequest } from "./request-BO6hGoBJ.js";
|
|
2
2
|
import { HttpHeaderLink } from "./link-C3q2TC2G.js";
|
|
3
3
|
import { UrlError, validatePublicUrl } from "./url-CWEP9Zs9.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
@@ -4373,6 +4373,7 @@ const logger = getLogger([
|
|
|
4373
4373
|
"runtime",
|
|
4374
4374
|
"docloader"
|
|
4375
4375
|
]);
|
|
4376
|
+
const DEFAULT_MAX_REDIRECTION = 20;
|
|
4376
4377
|
/**
|
|
4377
4378
|
* Gets a {@link RemoteDocument} from the given response.
|
|
4378
4379
|
* @param url The URL of the document to load.
|
|
@@ -4488,34 +4489,37 @@ async function getRemoteDocument(url, response, fetch$1) {
|
|
|
4488
4489
|
* @returns The document loader.
|
|
4489
4490
|
* @since 1.3.0
|
|
4490
4491
|
*/
|
|
4491
|
-
function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}) {
|
|
4492
|
+
function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedContexts, userAgent } = {}) {
|
|
4492
4493
|
const tracerProvider = trace.getTracerProvider();
|
|
4493
4494
|
const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version);
|
|
4494
|
-
|
|
4495
|
+
const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
4496
|
+
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
4495
4497
|
options?.signal?.throwIfAborted();
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
+
const currentUrl = new URL(url).href;
|
|
4499
|
+
if (!skipPreloadedContexts && currentUrl in contexts_default) {
|
|
4500
|
+
logger.debug("Using preloaded context: {url}.", { url: currentUrl });
|
|
4498
4501
|
return {
|
|
4499
4502
|
contextUrl: null,
|
|
4500
|
-
document: contexts_default[
|
|
4501
|
-
documentUrl:
|
|
4503
|
+
document: contexts_default[currentUrl],
|
|
4504
|
+
documentUrl: currentUrl
|
|
4502
4505
|
};
|
|
4503
4506
|
}
|
|
4504
4507
|
if (!allowPrivateAddress) try {
|
|
4505
|
-
await validatePublicUrl(
|
|
4508
|
+
await validatePublicUrl(currentUrl);
|
|
4506
4509
|
} catch (error) {
|
|
4507
4510
|
if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
|
|
4508
|
-
url,
|
|
4511
|
+
url: currentUrl,
|
|
4509
4512
|
error
|
|
4510
4513
|
});
|
|
4511
4514
|
throw error;
|
|
4512
4515
|
}
|
|
4516
|
+
visited.add(currentUrl);
|
|
4513
4517
|
return await tracer.startActiveSpan("activitypub.fetch_document", {
|
|
4514
4518
|
kind: SpanKind.CLIENT,
|
|
4515
|
-
attributes: { "url.full":
|
|
4519
|
+
attributes: { "url.full": currentUrl }
|
|
4516
4520
|
}, async (span) => {
|
|
4517
4521
|
try {
|
|
4518
|
-
const request = createActivityPubRequest(
|
|
4522
|
+
const request = createActivityPubRequest(currentUrl, { userAgent });
|
|
4519
4523
|
logRequest(logger, request);
|
|
4520
4524
|
const response = await fetch(request, {
|
|
4521
4525
|
redirect: "manual",
|
|
@@ -4523,11 +4527,25 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
4523
4527
|
});
|
|
4524
4528
|
span.setAttribute("http.response.status_code", response.status);
|
|
4525
4529
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
4526
|
-
|
|
4530
|
+
if (redirected >= maximumRedirection) {
|
|
4531
|
+
logger.error("Too many redirections ({redirections}) while fetching document.", {
|
|
4532
|
+
redirections: redirected + 1,
|
|
4533
|
+
url: currentUrl
|
|
4534
|
+
});
|
|
4535
|
+
throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
4536
|
+
}
|
|
4537
|
+
const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
|
|
4527
4538
|
span.setAttribute("http.redirect.url", redirectUrl);
|
|
4528
|
-
|
|
4539
|
+
if (visited.has(redirectUrl)) {
|
|
4540
|
+
logger.error("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
|
|
4541
|
+
url: currentUrl,
|
|
4542
|
+
redirectUrl
|
|
4543
|
+
});
|
|
4544
|
+
throw new FetchError(currentUrl, `Redirect loop detected: ${redirectUrl}`);
|
|
4545
|
+
}
|
|
4546
|
+
return await load(redirectUrl, options, redirected + 1, visited);
|
|
4529
4547
|
}
|
|
4530
|
-
const result = await getRemoteDocument(
|
|
4548
|
+
const result = await getRemoteDocument(currentUrl, response, load);
|
|
4531
4549
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
4532
4550
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
4533
4551
|
return result;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-DWy1uDak.cjs');
|
|
2
|
-
const require_request = require('./request-
|
|
2
|
+
const require_request = require('./request-73FIhAHw.cjs');
|
|
3
3
|
const require_link = require('./link-DYNFAdNu.cjs');
|
|
4
4
|
const require_url = require('./url-DIjOdK8Q.cjs');
|
|
5
5
|
const __logtape_logtape = require_chunk.__toESM(require("@logtape/logtape"));
|
|
@@ -4374,6 +4374,7 @@ const logger = (0, __logtape_logtape.getLogger)([
|
|
|
4374
4374
|
"runtime",
|
|
4375
4375
|
"docloader"
|
|
4376
4376
|
]);
|
|
4377
|
+
const DEFAULT_MAX_REDIRECTION = 20;
|
|
4377
4378
|
/**
|
|
4378
4379
|
* Gets a {@link RemoteDocument} from the given response.
|
|
4379
4380
|
* @param url The URL of the document to load.
|
|
@@ -4489,34 +4490,37 @@ async function getRemoteDocument(url, response, fetch$1) {
|
|
|
4489
4490
|
* @returns The document loader.
|
|
4490
4491
|
* @since 1.3.0
|
|
4491
4492
|
*/
|
|
4492
|
-
function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}) {
|
|
4493
|
+
function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedContexts, userAgent } = {}) {
|
|
4493
4494
|
const tracerProvider = __opentelemetry_api.trace.getTracerProvider();
|
|
4494
4495
|
const tracer = tracerProvider.getTracer(require_request.deno_default.name, require_request.deno_default.version);
|
|
4495
|
-
|
|
4496
|
+
const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
4497
|
+
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
4496
4498
|
options?.signal?.throwIfAborted();
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
+
const currentUrl = new URL(url).href;
|
|
4500
|
+
if (!skipPreloadedContexts && currentUrl in contexts_default) {
|
|
4501
|
+
logger.debug("Using preloaded context: {url}.", { url: currentUrl });
|
|
4499
4502
|
return {
|
|
4500
4503
|
contextUrl: null,
|
|
4501
|
-
document: contexts_default[
|
|
4502
|
-
documentUrl:
|
|
4504
|
+
document: contexts_default[currentUrl],
|
|
4505
|
+
documentUrl: currentUrl
|
|
4503
4506
|
};
|
|
4504
4507
|
}
|
|
4505
4508
|
if (!allowPrivateAddress) try {
|
|
4506
|
-
await require_url.validatePublicUrl(
|
|
4509
|
+
await require_url.validatePublicUrl(currentUrl);
|
|
4507
4510
|
} catch (error) {
|
|
4508
4511
|
if (error instanceof require_url.UrlError) logger.error("Disallowed private URL: {url}", {
|
|
4509
|
-
url,
|
|
4512
|
+
url: currentUrl,
|
|
4510
4513
|
error
|
|
4511
4514
|
});
|
|
4512
4515
|
throw error;
|
|
4513
4516
|
}
|
|
4517
|
+
visited.add(currentUrl);
|
|
4514
4518
|
return await tracer.startActiveSpan("activitypub.fetch_document", {
|
|
4515
4519
|
kind: __opentelemetry_api.SpanKind.CLIENT,
|
|
4516
|
-
attributes: { "url.full":
|
|
4520
|
+
attributes: { "url.full": currentUrl }
|
|
4517
4521
|
}, async (span) => {
|
|
4518
4522
|
try {
|
|
4519
|
-
const request = require_request.createActivityPubRequest(
|
|
4523
|
+
const request = require_request.createActivityPubRequest(currentUrl, { userAgent });
|
|
4520
4524
|
require_request.logRequest(logger, request);
|
|
4521
4525
|
const response = await fetch(request, {
|
|
4522
4526
|
redirect: "manual",
|
|
@@ -4524,11 +4528,25 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
4524
4528
|
});
|
|
4525
4529
|
span.setAttribute("http.response.status_code", response.status);
|
|
4526
4530
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
4527
|
-
|
|
4531
|
+
if (redirected >= maximumRedirection) {
|
|
4532
|
+
logger.error("Too many redirections ({redirections}) while fetching document.", {
|
|
4533
|
+
redirections: redirected + 1,
|
|
4534
|
+
url: currentUrl
|
|
4535
|
+
});
|
|
4536
|
+
throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
4537
|
+
}
|
|
4538
|
+
const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
|
|
4528
4539
|
span.setAttribute("http.redirect.url", redirectUrl);
|
|
4529
|
-
|
|
4540
|
+
if (visited.has(redirectUrl)) {
|
|
4541
|
+
logger.error("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
|
|
4542
|
+
url: currentUrl,
|
|
4543
|
+
redirectUrl
|
|
4544
|
+
});
|
|
4545
|
+
throw new require_request.FetchError(currentUrl, `Redirect loop detected: ${redirectUrl}`);
|
|
4546
|
+
}
|
|
4547
|
+
return await load(redirectUrl, options, redirected + 1, visited);
|
|
4530
4548
|
}
|
|
4531
|
-
const result = await getRemoteDocument(
|
|
4549
|
+
const result = await getRemoteDocument(currentUrl, response, load);
|
|
4532
4550
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
4533
4551
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
4534
4552
|
return result;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-DWy1uDak.cjs');
|
|
2
|
-
const require_docloader = require('./docloader-
|
|
3
|
-
const require_request = require('./request-
|
|
2
|
+
const require_docloader = require('./docloader-txRGFv1x.cjs');
|
|
3
|
+
const require_request = require('./request-73FIhAHw.cjs');
|
|
4
4
|
require('./link-DYNFAdNu.cjs');
|
|
5
5
|
const require_url = require('./url-DIjOdK8Q.cjs');
|
|
6
6
|
const node_assert = require_chunk.__toESM(require("node:assert"));
|
|
@@ -1512,6 +1512,59 @@ var esm_default = FetchMock_default;
|
|
|
1512
1512
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader2("https://example.com/localhost-redirect"), expected);
|
|
1513
1513
|
(0, node_assert.deepStrictEqual)(await fetchDocumentLoader2("https://example.com/localhost-link"), expected);
|
|
1514
1514
|
});
|
|
1515
|
+
let redirectAttempts = 0;
|
|
1516
|
+
esm_default.get("begin:https://example.com/too-many-redirects/", (cl) => {
|
|
1517
|
+
redirectAttempts++;
|
|
1518
|
+
const index = Number(cl.url.split("/").at(-1));
|
|
1519
|
+
return {
|
|
1520
|
+
status: 302,
|
|
1521
|
+
headers: { Location: `https://example.com/too-many-redirects/${index + 1}` }
|
|
1522
|
+
};
|
|
1523
|
+
});
|
|
1524
|
+
await t.test("too many redirects", async () => {
|
|
1525
|
+
redirectAttempts = 0;
|
|
1526
|
+
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
|
|
1527
|
+
(0, node_assert.deepStrictEqual)(redirectAttempts, 21);
|
|
1528
|
+
});
|
|
1529
|
+
await t.test("custom max redirection", async () => {
|
|
1530
|
+
redirectAttempts = 0;
|
|
1531
|
+
const loader = require_docloader.getDocumentLoader({ maxRedirection: 1 });
|
|
1532
|
+
await (0, node_assert.rejects)(() => loader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
|
|
1533
|
+
(0, node_assert.deepStrictEqual)(redirectAttempts, 2);
|
|
1534
|
+
});
|
|
1535
|
+
let loopAttempts = 0;
|
|
1536
|
+
esm_default.get("https://example.com/redirect-loop-a", () => {
|
|
1537
|
+
loopAttempts++;
|
|
1538
|
+
return {
|
|
1539
|
+
status: 302,
|
|
1540
|
+
headers: { Location: "https://example.com/redirect-loop-b" }
|
|
1541
|
+
};
|
|
1542
|
+
});
|
|
1543
|
+
esm_default.get("https://example.com/redirect-loop-b", () => {
|
|
1544
|
+
loopAttempts++;
|
|
1545
|
+
return {
|
|
1546
|
+
status: 302,
|
|
1547
|
+
headers: { Location: "https://example.com/redirect-loop-a" }
|
|
1548
|
+
};
|
|
1549
|
+
});
|
|
1550
|
+
await t.test("redirect loop", async () => {
|
|
1551
|
+
loopAttempts = 0;
|
|
1552
|
+
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-a"), require_request.FetchError, "Redirect loop detected");
|
|
1553
|
+
(0, node_assert.deepStrictEqual)(loopAttempts, 2);
|
|
1554
|
+
});
|
|
1555
|
+
let relativeLoopAttempts = 0;
|
|
1556
|
+
esm_default.get("https://example.com/redirect-loop-relative", () => {
|
|
1557
|
+
relativeLoopAttempts++;
|
|
1558
|
+
return {
|
|
1559
|
+
status: 302,
|
|
1560
|
+
headers: { Location: "/redirect-loop-relative" }
|
|
1561
|
+
};
|
|
1562
|
+
});
|
|
1563
|
+
await t.test("redirect loop with relative location", async () => {
|
|
1564
|
+
relativeLoopAttempts = 0;
|
|
1565
|
+
await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-relative"), require_request.FetchError, "Redirect loop detected");
|
|
1566
|
+
(0, node_assert.deepStrictEqual)(relativeLoopAttempts, 1);
|
|
1567
|
+
});
|
|
1515
1568
|
const maliciousPayload = "<a" + " a=\"b\"".repeat(30) + " ";
|
|
1516
1569
|
esm_default.get("https://example.com/redos", {
|
|
1517
1570
|
body: maliciousPayload,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { contexts_default, getDocumentLoader } from "./docloader-
|
|
2
|
-
import { FetchError } from "./request-
|
|
1
|
+
import { contexts_default, getDocumentLoader } from "./docloader-DEi540Xh.js";
|
|
2
|
+
import { FetchError } from "./request-BO6hGoBJ.js";
|
|
3
3
|
import "./link-C3q2TC2G.js";
|
|
4
4
|
import { UrlError } from "./url-CWEP9Zs9.js";
|
|
5
5
|
import "node:module";
|
|
@@ -1538,6 +1538,59 @@ test("getDocumentLoader()", async (t) => {
|
|
|
1538
1538
|
deepStrictEqual(await fetchDocumentLoader2("https://example.com/localhost-redirect"), expected);
|
|
1539
1539
|
deepStrictEqual(await fetchDocumentLoader2("https://example.com/localhost-link"), expected);
|
|
1540
1540
|
});
|
|
1541
|
+
let redirectAttempts = 0;
|
|
1542
|
+
esm_default.get("begin:https://example.com/too-many-redirects/", (cl) => {
|
|
1543
|
+
redirectAttempts++;
|
|
1544
|
+
const index = Number(cl.url.split("/").at(-1));
|
|
1545
|
+
return {
|
|
1546
|
+
status: 302,
|
|
1547
|
+
headers: { Location: `https://example.com/too-many-redirects/${index + 1}` }
|
|
1548
|
+
};
|
|
1549
|
+
});
|
|
1550
|
+
await t.test("too many redirects", async () => {
|
|
1551
|
+
redirectAttempts = 0;
|
|
1552
|
+
await rejects(() => fetchDocumentLoader("https://example.com/too-many-redirects/0"), FetchError, "Too many redirections");
|
|
1553
|
+
deepStrictEqual(redirectAttempts, 21);
|
|
1554
|
+
});
|
|
1555
|
+
await t.test("custom max redirection", async () => {
|
|
1556
|
+
redirectAttempts = 0;
|
|
1557
|
+
const loader = getDocumentLoader({ maxRedirection: 1 });
|
|
1558
|
+
await rejects(() => loader("https://example.com/too-many-redirects/0"), FetchError, "Too many redirections");
|
|
1559
|
+
deepStrictEqual(redirectAttempts, 2);
|
|
1560
|
+
});
|
|
1561
|
+
let loopAttempts = 0;
|
|
1562
|
+
esm_default.get("https://example.com/redirect-loop-a", () => {
|
|
1563
|
+
loopAttempts++;
|
|
1564
|
+
return {
|
|
1565
|
+
status: 302,
|
|
1566
|
+
headers: { Location: "https://example.com/redirect-loop-b" }
|
|
1567
|
+
};
|
|
1568
|
+
});
|
|
1569
|
+
esm_default.get("https://example.com/redirect-loop-b", () => {
|
|
1570
|
+
loopAttempts++;
|
|
1571
|
+
return {
|
|
1572
|
+
status: 302,
|
|
1573
|
+
headers: { Location: "https://example.com/redirect-loop-a" }
|
|
1574
|
+
};
|
|
1575
|
+
});
|
|
1576
|
+
await t.test("redirect loop", async () => {
|
|
1577
|
+
loopAttempts = 0;
|
|
1578
|
+
await rejects(() => fetchDocumentLoader("https://example.com/redirect-loop-a"), FetchError, "Redirect loop detected");
|
|
1579
|
+
deepStrictEqual(loopAttempts, 2);
|
|
1580
|
+
});
|
|
1581
|
+
let relativeLoopAttempts = 0;
|
|
1582
|
+
esm_default.get("https://example.com/redirect-loop-relative", () => {
|
|
1583
|
+
relativeLoopAttempts++;
|
|
1584
|
+
return {
|
|
1585
|
+
status: 302,
|
|
1586
|
+
headers: { Location: "/redirect-loop-relative" }
|
|
1587
|
+
};
|
|
1588
|
+
});
|
|
1589
|
+
await t.test("redirect loop with relative location", async () => {
|
|
1590
|
+
relativeLoopAttempts = 0;
|
|
1591
|
+
await rejects(() => fetchDocumentLoader("https://example.com/redirect-loop-relative"), FetchError, "Redirect loop detected");
|
|
1592
|
+
deepStrictEqual(relativeLoopAttempts, 1);
|
|
1593
|
+
});
|
|
1541
1594
|
const maliciousPayload = "<a" + " a=\"b\"".repeat(30) + " ";
|
|
1542
1595
|
esm_default.get("https://example.com/redos", {
|
|
1543
1596
|
body: maliciousPayload,
|
|
@@ -3,7 +3,7 @@ const node_process = require_chunk.__toESM(require("node:process"));
|
|
|
3
3
|
|
|
4
4
|
//#region deno.json
|
|
5
5
|
var name = "@fedify/vocab-runtime";
|
|
6
|
-
var version = "2.2.0-dev.
|
|
6
|
+
var version = "2.2.0-dev.613+cf8cd122";
|
|
7
7
|
var license = "MIT";
|
|
8
8
|
var exports$1 = {
|
|
9
9
|
".": "./src/mod.ts",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-DWy1uDak.cjs');
|
|
2
|
-
const require_request = require('./request-
|
|
2
|
+
const require_request = require('./request-73FIhAHw.cjs');
|
|
3
3
|
const node_assert = require_chunk.__toESM(require("node:assert"));
|
|
4
4
|
const node_test = require_chunk.__toESM(require("node:test"));
|
|
5
5
|
const node_process = require_chunk.__toESM(require("node:process"));
|
package/package.json
CHANGED
package/src/docloader.test.ts
CHANGED
|
@@ -369,6 +369,84 @@ test("getDocumentLoader()", async (t) => {
|
|
|
369
369
|
);
|
|
370
370
|
});
|
|
371
371
|
|
|
372
|
+
let redirectAttempts = 0;
|
|
373
|
+
fetchMock.get("begin:https://example.com/too-many-redirects/", (cl) => {
|
|
374
|
+
redirectAttempts++;
|
|
375
|
+
const index = Number(cl.url.split("/").at(-1));
|
|
376
|
+
return {
|
|
377
|
+
status: 302,
|
|
378
|
+
headers: {
|
|
379
|
+
Location: `https://example.com/too-many-redirects/${index + 1}`,
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
await t.test("too many redirects", async () => {
|
|
385
|
+
redirectAttempts = 0;
|
|
386
|
+
await rejects(
|
|
387
|
+
() => fetchDocumentLoader("https://example.com/too-many-redirects/0"),
|
|
388
|
+
FetchError,
|
|
389
|
+
"Too many redirections",
|
|
390
|
+
);
|
|
391
|
+
deepStrictEqual(redirectAttempts, 21);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
await t.test("custom max redirection", async () => {
|
|
395
|
+
redirectAttempts = 0;
|
|
396
|
+
const loader = getDocumentLoader({ maxRedirection: 1 });
|
|
397
|
+
await rejects(
|
|
398
|
+
() => loader("https://example.com/too-many-redirects/0"),
|
|
399
|
+
FetchError,
|
|
400
|
+
"Too many redirections",
|
|
401
|
+
);
|
|
402
|
+
deepStrictEqual(redirectAttempts, 2);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
let loopAttempts = 0;
|
|
406
|
+
fetchMock.get("https://example.com/redirect-loop-a", () => {
|
|
407
|
+
loopAttempts++;
|
|
408
|
+
return {
|
|
409
|
+
status: 302,
|
|
410
|
+
headers: { Location: "https://example.com/redirect-loop-b" },
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
fetchMock.get("https://example.com/redirect-loop-b", () => {
|
|
414
|
+
loopAttempts++;
|
|
415
|
+
return {
|
|
416
|
+
status: 302,
|
|
417
|
+
headers: { Location: "https://example.com/redirect-loop-a" },
|
|
418
|
+
};
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
await t.test("redirect loop", async () => {
|
|
422
|
+
loopAttempts = 0;
|
|
423
|
+
await rejects(
|
|
424
|
+
() => fetchDocumentLoader("https://example.com/redirect-loop-a"),
|
|
425
|
+
FetchError,
|
|
426
|
+
"Redirect loop detected",
|
|
427
|
+
);
|
|
428
|
+
deepStrictEqual(loopAttempts, 2);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
let relativeLoopAttempts = 0;
|
|
432
|
+
fetchMock.get("https://example.com/redirect-loop-relative", () => {
|
|
433
|
+
relativeLoopAttempts++;
|
|
434
|
+
return {
|
|
435
|
+
status: 302,
|
|
436
|
+
headers: { Location: "/redirect-loop-relative" },
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
await t.test("redirect loop with relative location", async () => {
|
|
441
|
+
relativeLoopAttempts = 0;
|
|
442
|
+
await rejects(
|
|
443
|
+
() => fetchDocumentLoader("https://example.com/redirect-loop-relative"),
|
|
444
|
+
FetchError,
|
|
445
|
+
"Redirect loop detected",
|
|
446
|
+
);
|
|
447
|
+
deepStrictEqual(relativeLoopAttempts, 1);
|
|
448
|
+
});
|
|
449
|
+
|
|
372
450
|
// Regression test for ReDoS vulnerability (CVE-2025-68475)
|
|
373
451
|
// Malicious HTML payload: <a a="b" a="b" ... (unclosed tag)
|
|
374
452
|
// With the vulnerable regex, this causes catastrophic backtracking
|
package/src/docloader.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { UrlError, validatePublicUrl } from "./url.ts";
|
|
13
13
|
|
|
14
14
|
const logger = getLogger(["fedify", "runtime", "docloader"]);
|
|
15
|
+
const DEFAULT_MAX_REDIRECTION = 20;
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* A remote JSON-LD document and its context fetched by
|
|
@@ -87,6 +88,13 @@ export interface DocumentLoaderFactoryOptions {
|
|
|
87
88
|
* If an object is given, it is passed to {@link getUserAgent} function.
|
|
88
89
|
*/
|
|
89
90
|
userAgent?: GetUserAgentOptions | string;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The maximum number of redirections to follow.
|
|
94
|
+
* @default `20`
|
|
95
|
+
* @since 2.2.0
|
|
96
|
+
*/
|
|
97
|
+
maxRedirection?: number;
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
/**
|
|
@@ -284,47 +292,55 @@ export interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
284
292
|
* @since 1.3.0
|
|
285
293
|
*/
|
|
286
294
|
export function getDocumentLoader(
|
|
287
|
-
{ allowPrivateAddress, skipPreloadedContexts, userAgent }:
|
|
295
|
+
{ allowPrivateAddress, maxRedirection, skipPreloadedContexts, userAgent }:
|
|
288
296
|
GetDocumentLoaderOptions = {},
|
|
289
297
|
): DocumentLoader {
|
|
290
298
|
const tracerProvider = trace.getTracerProvider();
|
|
291
299
|
const tracer = tracerProvider.getTracer(metadata.name, metadata.version);
|
|
300
|
+
const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
292
301
|
|
|
293
302
|
async function load(
|
|
294
303
|
url: string,
|
|
295
304
|
options?: DocumentLoaderOptions,
|
|
305
|
+
redirected = 0,
|
|
306
|
+
visited = new Set<string>(),
|
|
296
307
|
): Promise<RemoteDocument> {
|
|
297
308
|
options?.signal?.throwIfAborted();
|
|
298
|
-
|
|
299
|
-
|
|
309
|
+
const currentUrl = new URL(url).href;
|
|
310
|
+
if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
|
|
311
|
+
logger.debug("Using preloaded context: {url}.", { url: currentUrl });
|
|
300
312
|
return {
|
|
301
313
|
contextUrl: null,
|
|
302
|
-
document: preloadedContexts[
|
|
303
|
-
documentUrl:
|
|
314
|
+
document: preloadedContexts[currentUrl],
|
|
315
|
+
documentUrl: currentUrl,
|
|
304
316
|
};
|
|
305
317
|
}
|
|
306
318
|
if (!allowPrivateAddress) {
|
|
307
319
|
try {
|
|
308
|
-
await validatePublicUrl(
|
|
320
|
+
await validatePublicUrl(currentUrl);
|
|
309
321
|
} catch (error) {
|
|
310
322
|
if (error instanceof UrlError) {
|
|
311
|
-
logger.error("Disallowed private URL: {url}", {
|
|
323
|
+
logger.error("Disallowed private URL: {url}", {
|
|
324
|
+
url: currentUrl,
|
|
325
|
+
error,
|
|
326
|
+
});
|
|
312
327
|
}
|
|
313
328
|
throw error;
|
|
314
329
|
}
|
|
315
330
|
}
|
|
331
|
+
visited.add(currentUrl);
|
|
316
332
|
|
|
317
333
|
return await tracer.startActiveSpan(
|
|
318
334
|
"activitypub.fetch_document",
|
|
319
335
|
{
|
|
320
336
|
kind: SpanKind.CLIENT,
|
|
321
337
|
attributes: {
|
|
322
|
-
"url.full":
|
|
338
|
+
"url.full": currentUrl,
|
|
323
339
|
},
|
|
324
340
|
},
|
|
325
341
|
async (span) => {
|
|
326
342
|
try {
|
|
327
|
-
const request = createActivityPubRequest(
|
|
343
|
+
const request = createActivityPubRequest(currentUrl, { userAgent });
|
|
328
344
|
logRequest(logger, request);
|
|
329
345
|
const response = await fetch(request, {
|
|
330
346
|
// Since Bun has a bug that ignores the `Request.redirect` option,
|
|
@@ -340,12 +356,36 @@ export function getDocumentLoader(
|
|
|
340
356
|
response.status >= 300 && response.status < 400 &&
|
|
341
357
|
response.headers.has("Location")
|
|
342
358
|
) {
|
|
343
|
-
|
|
359
|
+
if (redirected >= maximumRedirection) {
|
|
360
|
+
logger.error(
|
|
361
|
+
"Too many redirections ({redirections}) while fetching document.",
|
|
362
|
+
{ redirections: redirected + 1, url: currentUrl },
|
|
363
|
+
);
|
|
364
|
+
throw new FetchError(
|
|
365
|
+
currentUrl,
|
|
366
|
+
`Too many redirections (${redirected + 1})`,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
const redirectUrl = new URL(
|
|
370
|
+
response.headers.get("Location")!,
|
|
371
|
+
response.url === "" ? currentUrl : response.url,
|
|
372
|
+
).href;
|
|
344
373
|
span.setAttribute("http.redirect.url", redirectUrl);
|
|
345
|
-
|
|
374
|
+
if (visited.has(redirectUrl)) {
|
|
375
|
+
logger.error(
|
|
376
|
+
"Detected a redirect loop while fetching document: {url} -> " +
|
|
377
|
+
"{redirectUrl}",
|
|
378
|
+
{ url: currentUrl, redirectUrl },
|
|
379
|
+
);
|
|
380
|
+
throw new FetchError(
|
|
381
|
+
currentUrl,
|
|
382
|
+
`Redirect loop detected: ${redirectUrl}`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return await load(redirectUrl, options, redirected + 1, visited);
|
|
346
386
|
}
|
|
347
387
|
|
|
348
|
-
const result = await getRemoteDocument(
|
|
388
|
+
const result = await getRemoteDocument(currentUrl, response, load);
|
|
349
389
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
350
390
|
if (result.contextUrl != null) {
|
|
351
391
|
span.setAttribute("docloader.context_url", result.contextUrl);
|