@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.
@@ -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;AAE/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"}
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.query.filename ?? `${context.cid.toString()}.car`;
20
- const blockstore = getBlockstore(context.cid, context.resource, options?.session ?? true, options);
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(context.cid, options));
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;QAC3B,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,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAA;QAClF,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,OAAO,CAAC,CAAA;QAClG,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,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;QAEtE,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
+ {"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;AAiBzD,wBAAsB,iBAAiB,CAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiD9F"}
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(bytes) {
5
+ function checkForSvg(text) {
6
6
  log('checking for svg');
7
- return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(new TextDecoder().decode(bytes.slice(0, 64)));
7
+ return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(text);
8
8
  }
9
- async function checkForJson(bytes) {
9
+ async function checkForJson(text) {
10
10
  log('checking for json');
11
11
  try {
12
- JSON.parse(new TextDecoder().decode(bytes));
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
- if (checkForSvg(bytes)) {
31
- return 'image/svg+xml';
32
- }
33
- else if (await checkForJson(bytes)) {
34
- return 'application/json';
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,KAAiB;IACrC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IACvB,OAAO,gCAAgC,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;AAC5F,CAAC;AAED,KAAK,UAAU,YAAY,CAAE,KAAiB;IAC5C,GAAG,CAAC,mBAAmB,CAAC,CAAA;IACxB,IAAI,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAC3C,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,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,uCAAuC;QACvC,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,eAAe,CAAA;QACxB,CAAC;aAAM,IAAI,MAAM,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;YACrC,OAAO,kBAAkB,CAAA;QAC3B,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
+ {"version":3,"file":"content-type-parser.js","sourceRoot":"","sources":["../../../src/utils/content-type-parser.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,9 @@
1
1
  import type { Logger } from '@libp2p/interface';
2
2
  import type { UnixFSEntry } from 'ipfs-unixfs-exporter';
3
3
  export interface DirIndexHtmlOptions {
4
+ /**
5
+ * The URL of the requested resource
6
+ */
4
7
  gatewayURL: string;
5
8
  dnsLink?: boolean;
6
9
  log: Logger;
@@ -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;AAwCvD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;CACZ;AAyED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,QAAS,WAAW,SAAS,WAAW,EAAE,gCAAgC,mBAAmB,KAAG,MAwFxH,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
- let href;
12
- try {
13
- const currentUrl = new URL(dirData.globalData.gatewayURL);
14
- const currentHost = dirData.globalData.dnsLink ? 'inbrowser.dev' : currentUrl.host;
15
- let newHost = currentHost;
16
- if (currentHost.includes('.ipfs.')) {
17
- newHost = currentHost.split('.ipfs.')[1];
18
- }
19
- else if (currentHost.includes('.ipns.')) {
20
- newHost = currentHost.split('.ipns.')[1];
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
- href = `${currentUrl.protocol}//${newHost}/ipfs/${item.hash}?filename=${item.name}`;
23
- // return `<a class="ipfs-hash" translate="no" href="${href}">${item.shortHash}</a>`
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
- // resource is not a URL.
27
- // TODO: handle unknown gatewayURLs in a more standardized way? if someone calls verified fetch and gatewayURL is not a valid URL, are ipfs:// links okay, or should we error in this plugin?
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
- return `<a class="ipfs-hash" translate="no" href="${href}">${item.shortHash}</a>`;
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":"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"}
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.2",
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.query.filename ?? `${context.cid.toString()}.car`
23
- const blockstore = getBlockstore(context.cid, context.resource, options?.session ?? true, options)
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(context.cid, options))
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 (bytes: Uint8Array): boolean {
7
+ function checkForSvg (text: string): boolean {
8
8
  log('checking for svg')
9
- return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(new TextDecoder().decode(bytes.slice(0, 64)))
9
+ return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(text)
10
10
  }
11
11
 
12
- async function checkForJson (bytes: Uint8Array): Promise<boolean> {
12
+ async function checkForJson (text: string): Promise<boolean> {
13
13
  log('checking for json')
14
14
  try {
15
- JSON.parse(new TextDecoder().decode(bytes))
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
- if (checkForSvg(bytes)) {
35
- return 'image/svg+xml'
36
- } else if (await checkForJson(bytes)) {
37
- return 'application/json'
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
- let href: string
60
- try {
61
- const currentUrl = new URL(dirData.globalData.gatewayURL)
62
- const currentHost = dirData.globalData.dnsLink ? 'inbrowser.dev' : currentUrl.host
63
- let newHost = currentHost
64
- if (currentHost.includes('.ipfs.')) {
65
- newHost = currentHost.split('.ipfs.')[1]
66
- } else if (currentHost.includes('.ipns.')) {
67
- newHost = currentHost.split('.ipns.')[1]
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
- href = `${currentUrl.protocol}//${newHost}/ipfs/${item.hash}?filename=${item.name}`
92
+ // Reverse because we collected from right to left
93
+ keepSegments.reverse()
71
94
 
72
- // return `<a class="ipfs-hash" translate="no" href="${href}">${item.shortHash}</a>`
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
- // resource is not a URL.
75
- // TODO: handle unknown gatewayURLs in a more standardized way? if someone calls verified fetch and gatewayURL is not a valid URL, are ipfs:// links okay, or should we error in this plugin?
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
- return `<a class="ipfs-hash" translate="no" href="${href}">${item.shortHash}</a>`
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) => {