@helia/verified-fetch 2.6.2 → 2.6.3

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":"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"}
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.3",
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
  }