diff --git a/.changeset/tough-moles-arrive.md b/.changeset/tough-moles-arrive.md new file mode 100644 index 00000000000..9231533f22b --- /dev/null +++ b/.changeset/tough-moles-arrive.md @@ -0,0 +1,7 @@ +--- +'@whatwg-node/node-fetch': patch +--- + +When `fetch('file:///...')` is used to read files; +- 404 is returned if the file is missing +- 403 is returned if the file is not accessible \ No newline at end of file diff --git a/packages/node-fetch/src/fetch.ts b/packages/node-fetch/src/fetch.ts index a682f211cf2..74081f872df 100644 --- a/packages/node-fetch/src/fetch.ts +++ b/packages/node-fetch/src/fetch.ts @@ -1,5 +1,5 @@ import { Buffer } from 'node:buffer'; -import { createReadStream } from 'node:fs'; +import { createReadStream, promises as fsPromises } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { fetchCurl } from './fetchCurl.js'; import { fetchNodeHttp } from './fetchNodeHttp.js'; @@ -10,10 +10,35 @@ import { fakePromise } from './utils.js'; const BASE64_SUFFIX = ';base64'; -function getResponseForFile(url: string) { +async function getResponseForFile(url: string) { const path = fileURLToPath(url); - const readable = createReadStream(path); - return new PonyfillResponse(readable); + try { + const stats = await fsPromises.stat(path, { + bigint: true, + }); + const readable = createReadStream(path); + return new PonyfillResponse(readable, { + status: 200, + statusText: 'OK', + headers: { + 'content-type': 'application/octet-stream', + 'last-modified': stats.mtime.toUTCString(), + }, + }); + } catch (err: any) { + if (err.code === 'ENOENT') { + return new PonyfillResponse(null, { + status: 404, + statusText: 'Not Found', + }); + } else if (err.code === 'EACCES') { + return new PonyfillResponse(null, { + status: 403, + statusText: 'Forbidden', + }); + } + throw err; + } } function getResponseForDataUri(url: string) { @@ -73,7 +98,7 @@ export function fetchPonyfill( if (fetchRequest.url.startsWith('file:')) { const response = getResponseForFile(fetchRequest.url); - return fakePromise(response); + return response; } if (fetchRequest.url.startsWith('blob:')) { const response = getResponseForBlob(fetchRequest.url); diff --git a/packages/node-fetch/tests/non-http-fetch.spec.ts b/packages/node-fetch/tests/non-http-fetch.spec.ts index 2a7e32027e5..582d224604e 100644 --- a/packages/node-fetch/tests/non-http-fetch.spec.ts +++ b/packages/node-fetch/tests/non-http-fetch.spec.ts @@ -4,13 +4,21 @@ import { pathToFileURL } from 'node:url'; import { describe, expect, it } from '@jest/globals'; import { fetchPonyfill } from '../src/fetch.js'; -it('should respect file protocol', async () => { - const response = await fetchPonyfill( - pathToFileURL(join(process.cwd(), './packages/node-fetch/tests/fixtures/test.json')), - ); - expect(response.status).toBe(200); - const body = await response.json(); - expect(body.foo).toBe('bar'); +describe('File protocol', () => { + it('reads', async () => { + const response = await fetchPonyfill( + pathToFileURL(join(process.cwd(), './packages/node-fetch/tests/fixtures/test.json')), + ); + expect(response.status).toBe(200); + const body = await response.json(); + expect(body.foo).toBe('bar'); + }); + it('returns 404 if file does not exist', async () => { + const response = await fetchPonyfill( + pathToFileURL(join(process.cwd(), './packages/node-fetch/tests/fixtures/missing.json')), + ); + expect(response.status).toBe(404); + }); }); describe('data uris', () => {