@helia/verified-fetch 2.6.2 → 2.6.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/index.min.js +39 -39
- package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-car.js +12 -4
- package/dist/src/plugins/plugin-handle-car.js.map +1 -1
- package/dist/src/utils/content-type-parser.d.ts.map +1 -1
- package/dist/src/utils/content-type-parser.js +34 -10
- package/dist/src/utils/content-type-parser.js.map +1 -1
- package/dist/src/utils/dir-index-html.d.ts +3 -0
- package/dist/src/utils/dir-index-html.d.ts.map +1 -1
- package/dist/src/utils/dir-index-html.js +40 -17
- package/dist/src/utils/dir-index-html.js.map +1 -1
- package/package.json +1 -1
- package/src/plugins/plugin-handle-car.ts +14 -4
- package/src/utils/content-type-parser.ts +32 -9
- package/src/utils/dir-index-html.ts +49 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-handle-car.d.ts","sourceRoot":"","sources":["../../../src/plugins/plugin-handle-car.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin-handle-car.d.ts","sourceRoot":"","sources":["../../../src/plugins/plugin-handle-car.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAY/C;;;GAGG;AACH,qBAAa,SAAU,SAAQ,UAAU;IACvC,SAAS,CAAE,OAAO,EAAE,aAAa,GAAG,OAAO;IAKrC,MAAM,CAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC;CAezD"}
|
|
@@ -2,6 +2,14 @@ import { car } from '@helia/car';
|
|
|
2
2
|
import toBrowserReadableStream from 'it-to-browser-readablestream';
|
|
3
3
|
import { okResponse } from '../utils/responses.js';
|
|
4
4
|
import { BasePlugin } from './plugin-base.js';
|
|
5
|
+
function getFilename({ cid, ipfsPath, query }) {
|
|
6
|
+
if (query.filename != null) {
|
|
7
|
+
return query.filename;
|
|
8
|
+
}
|
|
9
|
+
// convert context.ipfsPath to a filename. replace all / with _, replace prefix protocol with empty string
|
|
10
|
+
const filename = ipfsPath.replace(/\/ipfs\//, '').replace(/\/ipns\//, '').replace(/\//g, '_');
|
|
11
|
+
return `${filename}.car`;
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* Accepts a `CID` and returns a `Response` with a body stream that is a CAR
|
|
7
15
|
* of the `DAG` referenced by the `CID`.
|
|
@@ -12,14 +20,14 @@ export class CarPlugin extends BasePlugin {
|
|
|
12
20
|
return context.accept?.startsWith('application/vnd.ipld.car') === true || context.query.format === 'car'; // application/vnd.ipld.car
|
|
13
21
|
}
|
|
14
22
|
async handle(context) {
|
|
15
|
-
const { options } = context;
|
|
23
|
+
const { options, pathDetails, cid } = context;
|
|
16
24
|
const { getBlockstore, helia } = this.pluginOptions;
|
|
17
25
|
context.reqFormat = 'car';
|
|
18
26
|
context.query.download = true;
|
|
19
|
-
context.query.filename = context
|
|
20
|
-
const blockstore = getBlockstore(
|
|
27
|
+
context.query.filename = getFilename(context);
|
|
28
|
+
const blockstore = getBlockstore(cid, context.resource, options?.session ?? true, options);
|
|
21
29
|
const c = car({ blockstore, getCodec: helia.getCodec });
|
|
22
|
-
const stream = toBrowserReadableStream(c.stream(
|
|
30
|
+
const stream = toBrowserReadableStream(c.stream(pathDetails?.terminalElement.cid ?? cid, options));
|
|
23
31
|
const response = okResponse(context.resource, stream);
|
|
24
32
|
response.headers.set('content-type', 'application/vnd.ipld.car; version=1');
|
|
25
33
|
return response;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-handle-car.js","sourceRoot":"","sources":["../../../src/plugins/plugin-handle-car.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA;AAChC,OAAO,uBAAuB,MAAM,8BAA8B,CAAA;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAG7C;;;GAGG;AACH,MAAM,OAAO,SAAU,SAAQ,UAAU;IACvC,SAAS,CAAE,OAAsB;QAC/B,IAAI,CAAC,GAAG,CAAC,6CAA6C,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QACpF,OAAO,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,0BAA0B,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,KAAK,CAAA,CAAC,2BAA2B;IACtI,CAAC;IAED,KAAK,CAAC,MAAM,CAAE,OAAsB;QAClC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin-handle-car.js","sourceRoot":"","sources":["../../../src/plugins/plugin-handle-car.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA;AAChC,OAAO,uBAAuB,MAAM,8BAA8B,CAAA;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAG7C,SAAS,WAAW,CAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAqD;IAC/F,IAAI,KAAK,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,QAAQ,CAAA;IACvB,CAAC;IAED,0GAA0G;IAC1G,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IAE7F,OAAO,GAAG,QAAQ,MAAM,CAAA;AAC1B,CAAC;AACD;;;GAGG;AACH,MAAM,OAAO,SAAU,SAAQ,UAAU;IACvC,SAAS,CAAE,OAAsB;QAC/B,IAAI,CAAC,GAAG,CAAC,6CAA6C,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QACpF,OAAO,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,0BAA0B,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,KAAK,CAAA,CAAC,2BAA2B;IACtI,CAAC;IAED,KAAK,CAAC,MAAM,CAAE,OAAsB;QAClC,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,OAAO,CAAA;QAC7C,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,aAAa,CAAA;QACnD,OAAO,CAAC,SAAS,GAAG,KAAK,CAAA;QACzB,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAA;QAC7B,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;QAC7C,MAAM,UAAU,GAAG,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,OAAO,CAAC,CAAA;QAC1F,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAA;QACvD,MAAM,MAAM,GAAG,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,eAAe,CAAC,GAAG,IAAI,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;QAElG,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QACrD,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,qCAAqC,CAAC,CAAA;QAE3E,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-type-parser.d.ts","sourceRoot":"","sources":["../../../src/utils/content-type-parser.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"content-type-parser.d.ts","sourceRoot":"","sources":["../../../src/utils/content-type-parser.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,6BAA6B,CAAA;AAgCzD,wBAAsB,iBAAiB,CAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyD9F"}
|
|
@@ -2,14 +2,14 @@ import { logger } from '@libp2p/logger';
|
|
|
2
2
|
import { fileTypeFromBuffer } from 'file-type';
|
|
3
3
|
const log = logger('helia:verified-fetch:content-type-parser');
|
|
4
4
|
export const defaultMimeType = 'application/octet-stream';
|
|
5
|
-
function checkForSvg(
|
|
5
|
+
function checkForSvg(text) {
|
|
6
6
|
log('checking for svg');
|
|
7
|
-
return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(
|
|
7
|
+
return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(text);
|
|
8
8
|
}
|
|
9
|
-
async function checkForJson(
|
|
9
|
+
async function checkForJson(text) {
|
|
10
10
|
log('checking for json');
|
|
11
11
|
try {
|
|
12
|
-
JSON.parse(
|
|
12
|
+
JSON.parse(text);
|
|
13
13
|
return true;
|
|
14
14
|
}
|
|
15
15
|
catch (err) {
|
|
@@ -17,6 +17,20 @@ async function checkForJson(bytes) {
|
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
function getText(bytes) {
|
|
21
|
+
log('checking for text');
|
|
22
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
23
|
+
try {
|
|
24
|
+
return decoder.decode(bytes);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function checkForHtml(text) {
|
|
31
|
+
log('checking for html');
|
|
32
|
+
return /^\s*<(?:!doctype\s+html|html|head|body)\b/i.test(text);
|
|
33
|
+
}
|
|
20
34
|
export async function contentTypeParser(bytes, fileName) {
|
|
21
35
|
log('contentTypeParser called for fileName: %s, byte size=%s', fileName, bytes.length);
|
|
22
36
|
const detectedType = (await fileTypeFromBuffer(bytes))?.mime;
|
|
@@ -26,12 +40,22 @@ export async function contentTypeParser(bytes, fileName) {
|
|
|
26
40
|
}
|
|
27
41
|
log('no detectedType');
|
|
28
42
|
if (fileName == null) {
|
|
29
|
-
// no other way to determine file-type.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
// it's likely text... no other way to determine file-type.
|
|
44
|
+
const text = getText(bytes);
|
|
45
|
+
if (text != null) {
|
|
46
|
+
// check for svg, json, html, or it's plain text.
|
|
47
|
+
if (checkForSvg(text)) {
|
|
48
|
+
return 'image/svg+xml';
|
|
49
|
+
}
|
|
50
|
+
else if (await checkForJson(text)) {
|
|
51
|
+
return 'application/json';
|
|
52
|
+
}
|
|
53
|
+
else if (await checkForHtml(text)) {
|
|
54
|
+
return 'text/html; charset=utf-8';
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return 'text/plain; charset=utf-8';
|
|
58
|
+
}
|
|
35
59
|
}
|
|
36
60
|
return defaultMimeType;
|
|
37
61
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-type-parser.js","sourceRoot":"","sources":["../../../src/utils/content-type-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAE9C,MAAM,GAAG,GAAG,MAAM,CAAC,0CAA0C,CAAC,CAAA;AAE9D,MAAM,CAAC,MAAM,eAAe,GAAG,0BAA0B,CAAA;AACzD,SAAS,WAAW,CAAE,
|
|
1
|
+
{"version":3,"file":"content-type-parser.js","sourceRoot":"","sources":["../../../src/utils/content-type-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAE9C,MAAM,GAAG,GAAG,MAAM,CAAC,0CAA0C,CAAC,CAAA;AAE9D,MAAM,CAAC,MAAM,eAAe,GAAG,0BAA0B,CAAA;AACzD,SAAS,WAAW,CAAE,IAAY;IAChC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IACvB,OAAO,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACpD,CAAC;AAED,KAAK,UAAU,YAAY,CAAE,IAAY;IACvC,GAAG,CAAC,mBAAmB,CAAC,CAAA;IACxB,IAAI,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAChB,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAA;QACnC,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAE,KAAiB;IACjC,GAAG,CAAC,mBAAmB,CAAC,CAAA;IACxB,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACzD,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,KAAK,UAAU,YAAY,CAAE,IAAY;IACvC,GAAG,CAAC,mBAAmB,CAAC,CAAA;IACxB,OAAO,4CAA4C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAE,KAAiB,EAAE,QAAiB;IAC3E,GAAG,CAAC,yDAAyD,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACtF,MAAM,YAAY,GAAG,CAAC,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAA;IAC5D,IAAI,YAAY,IAAI,IAAI,EAAE,CAAC;QACzB,GAAG,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAA;QACrC,OAAO,YAAY,CAAA;IACrB,CAAC;IACD,GAAG,CAAC,iBAAiB,CAAC,CAAA;IAEtB,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,2DAA2D;QAC3D,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;QAC3B,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACjB,iDAAiD;YACjD,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtB,OAAO,eAAe,CAAA;YACxB,CAAC;iBAAM,IAAI,MAAM,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpC,OAAO,kBAAkB,CAAA;YAC3B,CAAC;iBAAM,IAAI,MAAM,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpC,OAAO,0BAA0B,CAAA;YACnC,CAAC;iBAAM,CAAC;gBACN,OAAO,2BAA2B,CAAA;YACpC,CAAC;QACH,CAAC;QACD,OAAO,eAAe,CAAA;IACxB,CAAC;IAED,qGAAqG;IACrG,QAAQ,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;QAClC,KAAK,KAAK;YACR,OAAO,UAAU,CAAA;QACnB,KAAK,MAAM;YACT,OAAO,0BAA0B,CAAA;QACnC,KAAK,IAAI;YACP,OAAO,wBAAwB,CAAA;QACjC,KAAK,MAAM;YACT,OAAO,kBAAkB,CAAA;QAC3B,KAAK,KAAK;YACR,OAAO,YAAY,CAAA;QACrB,KAAK,OAAO;YACV,OAAO,YAAY,CAAA;QACrB,2EAA2E;QAC3E,KAAK,KAAK;YACR,OAAO,eAAe,CAAA;QACxB,KAAK,KAAK;YACR,OAAO,UAAU,CAAA;QACnB,KAAK,KAAK;YACR,OAAO,oBAAoB,CAAA;QAC7B,KAAK,KAAK;YACR,OAAO,0BAA0B,CAAA;QACnC,KAAK,KAAK;YACR,OAAO,+BAA+B,CAAA;QACxC,KAAK,KAAK;YACR,OAAO,0BAA0B,CAAA;QACnC;YACE,OAAO,eAAe,CAAA;IAC1B,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dir-index-html.d.ts","sourceRoot":"","sources":["../../../src/utils/dir-index-html.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"dir-index-html.d.ts","sourceRoot":"","sources":["../../../src/utils/dir-index-html.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAyCvD,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;CACZ;AAoGD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,QAAS,WAAW,SAAS,WAAW,EAAE,gCAAgC,mBAAmB,KAAG,MAyFxH,CAAA"}
|
|
@@ -5,29 +5,51 @@ function iconFromExt(name) {
|
|
|
5
5
|
return 'ipfs-_blank';
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* If they click on the short hash, it should link to the host + /ipfs/ + hash + ?filename={filename}
|
|
8
|
+
* If they click on the short hash, it should link to the host-without-subdomain + /ipfs/ + hash + ?filename={filename}
|
|
9
9
|
*/
|
|
10
10
|
function itemShortHashCell(item, dirData) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
const host = dirData.globalData.gatewayURLWithoutSubdomain.host;
|
|
12
|
+
const protocol = dirData.globalData.gatewayURLWithoutSubdomain.protocol;
|
|
13
|
+
return `<a class="ipfs-hash" translate="no" href="${protocol}//${host}/ipfs/${item.hash}?filename=${item.name}">${item.shortHash}</a>`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns a new host with the subdomain removed if it includes "ipfs" or "ipns".
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* subdomain.ipfs.dweb.link -> dweb.link
|
|
20
|
+
* abc.ipns.localhost -> localhost
|
|
21
|
+
* bafyfoo.ipfs.localhost:3441 -> localhost:3441
|
|
22
|
+
* bafyfoo.ipfs.foo.localhost:3441 -> foo.localhost:3441
|
|
23
|
+
*/
|
|
24
|
+
function removeIpfsOrIpnsSubdomain(host) {
|
|
25
|
+
const segments = host.split('.');
|
|
26
|
+
const keepSegments = [];
|
|
27
|
+
// Walk from the right to the left
|
|
28
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
29
|
+
const seg = segments[i];
|
|
30
|
+
// If we hit "ipfs" or "ipns", stop (ignore everything to the left)
|
|
31
|
+
if (seg === 'ipfs' || seg === 'ipns') {
|
|
32
|
+
break;
|
|
21
33
|
}
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
keepSegments.push(seg);
|
|
35
|
+
}
|
|
36
|
+
// Reverse because we collected from right to left
|
|
37
|
+
keepSegments.reverse();
|
|
38
|
+
// If keepSegments is empty, it means "ipfs" or "ipns" was at the TLD level
|
|
39
|
+
// but typically that means the next domain is empty; just return empty string
|
|
40
|
+
return keepSegments.join('.');
|
|
41
|
+
}
|
|
42
|
+
function getGatewayURLWithoutSubdomain(gatewayURL) {
|
|
43
|
+
let currentUrl;
|
|
44
|
+
try {
|
|
45
|
+
currentUrl = new URL(gatewayURL);
|
|
24
46
|
}
|
|
25
47
|
catch {
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
href = `ipfs://${item.hash}`;
|
|
48
|
+
// If the gatewayURL is invalid (ipfs:// or ipns:// or just a CID), use inbrowser.link as a fallback
|
|
49
|
+
currentUrl = new URL('https://inbrowser.link');
|
|
29
50
|
}
|
|
30
|
-
|
|
51
|
+
currentUrl.host = removeIpfsOrIpnsSubdomain(currentUrl.host);
|
|
52
|
+
return currentUrl;
|
|
31
53
|
}
|
|
32
54
|
function dirListingTitle(dirData) {
|
|
33
55
|
if (dirData.path != null) {
|
|
@@ -74,6 +96,7 @@ export const dirIndexHtml = (dir, items, { gatewayURL, dnsLink, log }) => {
|
|
|
74
96
|
const dirData = {
|
|
75
97
|
globalData: {
|
|
76
98
|
gatewayURL,
|
|
99
|
+
gatewayURLWithoutSubdomain: getGatewayURLWithoutSubdomain(gatewayURL),
|
|
77
100
|
dnsLink: dnsLink ?? false
|
|
78
101
|
},
|
|
79
102
|
listing: items.map((item) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dir-index-html.js","sourceRoot":"","sources":["../../../src/utils/dir-index-html.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dir-index-html.js","sourceRoot":"","sources":["../../../src/utils/dir-index-html.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helia/verified-fetch",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.4",
|
|
4
4
|
"description": "A fetch-like API for obtaining verified & trustless IPFS content on the web",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"homepage": "https://github.com/ipfs/helia-verified-fetch/tree/main/packages/verified-fetch#readme",
|
|
@@ -4,6 +4,16 @@ import { okResponse } from '../utils/responses.js'
|
|
|
4
4
|
import { BasePlugin } from './plugin-base.js'
|
|
5
5
|
import type { PluginContext } from './types.js'
|
|
6
6
|
|
|
7
|
+
function getFilename ({ cid, ipfsPath, query }: Pick<PluginContext, 'query' | 'cid' | 'ipfsPath'>): string {
|
|
8
|
+
if (query.filename != null) {
|
|
9
|
+
return query.filename
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// convert context.ipfsPath to a filename. replace all / with _, replace prefix protocol with empty string
|
|
13
|
+
const filename = ipfsPath.replace(/\/ipfs\//, '').replace(/\/ipns\//, '').replace(/\//g, '_')
|
|
14
|
+
|
|
15
|
+
return `${filename}.car`
|
|
16
|
+
}
|
|
7
17
|
/**
|
|
8
18
|
* Accepts a `CID` and returns a `Response` with a body stream that is a CAR
|
|
9
19
|
* of the `DAG` referenced by the `CID`.
|
|
@@ -15,14 +25,14 @@ export class CarPlugin extends BasePlugin {
|
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
async handle (context: PluginContext): Promise<Response> {
|
|
18
|
-
const { options } = context
|
|
28
|
+
const { options, pathDetails, cid } = context
|
|
19
29
|
const { getBlockstore, helia } = this.pluginOptions
|
|
20
30
|
context.reqFormat = 'car'
|
|
21
31
|
context.query.download = true
|
|
22
|
-
context.query.filename = context
|
|
23
|
-
const blockstore = getBlockstore(
|
|
32
|
+
context.query.filename = getFilename(context)
|
|
33
|
+
const blockstore = getBlockstore(cid, context.resource, options?.session ?? true, options)
|
|
24
34
|
const c = car({ blockstore, getCodec: helia.getCodec })
|
|
25
|
-
const stream = toBrowserReadableStream(c.stream(
|
|
35
|
+
const stream = toBrowserReadableStream(c.stream(pathDetails?.terminalElement.cid ?? cid, options))
|
|
26
36
|
|
|
27
37
|
const response = okResponse(context.resource, stream)
|
|
28
38
|
response.headers.set('content-type', 'application/vnd.ipld.car; version=1')
|
|
@@ -4,15 +4,15 @@ import { fileTypeFromBuffer } from 'file-type'
|
|
|
4
4
|
const log = logger('helia:verified-fetch:content-type-parser')
|
|
5
5
|
|
|
6
6
|
export const defaultMimeType = 'application/octet-stream'
|
|
7
|
-
function checkForSvg (
|
|
7
|
+
function checkForSvg (text: string): boolean {
|
|
8
8
|
log('checking for svg')
|
|
9
|
-
return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(
|
|
9
|
+
return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(text)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
async function checkForJson (
|
|
12
|
+
async function checkForJson (text: string): Promise<boolean> {
|
|
13
13
|
log('checking for json')
|
|
14
14
|
try {
|
|
15
|
-
JSON.parse(
|
|
15
|
+
JSON.parse(text)
|
|
16
16
|
return true
|
|
17
17
|
} catch (err) {
|
|
18
18
|
log('failed to parse as json', err)
|
|
@@ -20,6 +20,21 @@ async function checkForJson (bytes: Uint8Array): Promise<boolean> {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function getText (bytes: Uint8Array): string | null {
|
|
24
|
+
log('checking for text')
|
|
25
|
+
const decoder = new TextDecoder('utf-8', { fatal: true })
|
|
26
|
+
try {
|
|
27
|
+
return decoder.decode(bytes)
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function checkForHtml (text: string): Promise<boolean> {
|
|
34
|
+
log('checking for html')
|
|
35
|
+
return /^\s*<(?:!doctype\s+html|html|head|body)\b/i.test(text)
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
export async function contentTypeParser (bytes: Uint8Array, fileName?: string): Promise<string> {
|
|
24
39
|
log('contentTypeParser called for fileName: %s, byte size=%s', fileName, bytes.length)
|
|
25
40
|
const detectedType = (await fileTypeFromBuffer(bytes))?.mime
|
|
@@ -30,11 +45,19 @@ export async function contentTypeParser (bytes: Uint8Array, fileName?: string):
|
|
|
30
45
|
log('no detectedType')
|
|
31
46
|
|
|
32
47
|
if (fileName == null) {
|
|
33
|
-
// no other way to determine file-type.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
// it's likely text... no other way to determine file-type.
|
|
49
|
+
const text = getText(bytes)
|
|
50
|
+
if (text != null) {
|
|
51
|
+
// check for svg, json, html, or it's plain text.
|
|
52
|
+
if (checkForSvg(text)) {
|
|
53
|
+
return 'image/svg+xml'
|
|
54
|
+
} else if (await checkForJson(text)) {
|
|
55
|
+
return 'application/json'
|
|
56
|
+
} else if (await checkForHtml(text)) {
|
|
57
|
+
return 'text/html; charset=utf-8'
|
|
58
|
+
} else {
|
|
59
|
+
return 'text/plain; charset=utf-8'
|
|
60
|
+
}
|
|
38
61
|
}
|
|
39
62
|
return defaultMimeType
|
|
40
63
|
}
|
|
@@ -11,6 +11,7 @@ import type { UnixFSEntry } from 'ipfs-unixfs-exporter'
|
|
|
11
11
|
interface GlobalData {
|
|
12
12
|
// Menu []MenuItem
|
|
13
13
|
gatewayURL: string
|
|
14
|
+
gatewayURLWithoutSubdomain: URL
|
|
14
15
|
dnsLink: boolean
|
|
15
16
|
// root: UnixFSEntry
|
|
16
17
|
}
|
|
@@ -40,6 +41,9 @@ interface Breadcrumb {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export interface DirIndexHtmlOptions {
|
|
44
|
+
/**
|
|
45
|
+
* The URL of the requested resource
|
|
46
|
+
*/
|
|
43
47
|
gatewayURL: string
|
|
44
48
|
dnsLink?: boolean
|
|
45
49
|
log: Logger
|
|
@@ -53,29 +57,56 @@ function iconFromExt (name: string): string {
|
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
/**
|
|
56
|
-
* If they click on the short hash, it should link to the host + /ipfs/ + hash + ?filename={filename}
|
|
60
|
+
* If they click on the short hash, it should link to the host-without-subdomain + /ipfs/ + hash + ?filename={filename}
|
|
57
61
|
*/
|
|
58
62
|
function itemShortHashCell (item: DirectoryItem, dirData: DirectoryTemplateData): string {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
const host = dirData.globalData.gatewayURLWithoutSubdomain.host
|
|
64
|
+
const protocol = dirData.globalData.gatewayURLWithoutSubdomain.protocol
|
|
65
|
+
|
|
66
|
+
return `<a class="ipfs-hash" translate="no" href="${protocol}//${host}/ipfs/${item.hash}?filename=${item.name}">${item.shortHash}</a>`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns a new host with the subdomain removed if it includes "ipfs" or "ipns".
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* subdomain.ipfs.dweb.link -> dweb.link
|
|
74
|
+
* abc.ipns.localhost -> localhost
|
|
75
|
+
* bafyfoo.ipfs.localhost:3441 -> localhost:3441
|
|
76
|
+
* bafyfoo.ipfs.foo.localhost:3441 -> foo.localhost:3441
|
|
77
|
+
*/
|
|
78
|
+
function removeIpfsOrIpnsSubdomain (host: string): string {
|
|
79
|
+
const segments = host.split('.')
|
|
80
|
+
const keepSegments: string[] = []
|
|
81
|
+
|
|
82
|
+
// Walk from the right to the left
|
|
83
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
84
|
+
const seg = segments[i]
|
|
85
|
+
// If we hit "ipfs" or "ipns", stop (ignore everything to the left)
|
|
86
|
+
if (seg === 'ipfs' || seg === 'ipns') {
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
keepSegments.push(seg)
|
|
90
|
+
}
|
|
69
91
|
|
|
70
|
-
|
|
92
|
+
// Reverse because we collected from right to left
|
|
93
|
+
keepSegments.reverse()
|
|
71
94
|
|
|
72
|
-
|
|
95
|
+
// If keepSegments is empty, it means "ipfs" or "ipns" was at the TLD level
|
|
96
|
+
// but typically that means the next domain is empty; just return empty string
|
|
97
|
+
return keepSegments.join('.')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getGatewayURLWithoutSubdomain (gatewayURL: string): URL {
|
|
101
|
+
let currentUrl: URL
|
|
102
|
+
try {
|
|
103
|
+
currentUrl = new URL(gatewayURL)
|
|
73
104
|
} catch {
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
href = `ipfs://${item.hash}`
|
|
105
|
+
// If the gatewayURL is invalid (ipfs:// or ipns:// or just a CID), use inbrowser.link as a fallback
|
|
106
|
+
currentUrl = new URL('https://inbrowser.link')
|
|
77
107
|
}
|
|
78
|
-
|
|
108
|
+
currentUrl.host = removeIpfsOrIpnsSubdomain(currentUrl.host)
|
|
109
|
+
return currentUrl
|
|
79
110
|
}
|
|
80
111
|
|
|
81
112
|
function dirListingTitle (dirData: DirectoryTemplateData): string {
|
|
@@ -129,6 +160,7 @@ export const dirIndexHtml = (dir: UnixFSEntry, items: UnixFSEntry[], { gatewayUR
|
|
|
129
160
|
const dirData: DirectoryTemplateData = {
|
|
130
161
|
globalData: {
|
|
131
162
|
gatewayURL,
|
|
163
|
+
gatewayURLWithoutSubdomain: getGatewayURLWithoutSubdomain(gatewayURL),
|
|
132
164
|
dnsLink: dnsLink ?? false
|
|
133
165
|
},
|
|
134
166
|
listing: items.map((item) => {
|