Skip to content

Commit

Permalink
feat(node-fetch): improve file access
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Jan 24, 2025
1 parent 49adb28 commit bf76ab6
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 12 deletions.
7 changes: 7 additions & 0 deletions .changeset/tough-moles-arrive.md
Original file line number Diff line number Diff line change
@@ -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
35 changes: 30 additions & 5 deletions packages/node-fetch/src/fetch.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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) {
Expand Down Expand Up @@ -73,7 +98,7 @@ export function fetchPonyfill<TResponseJSON = any, TRequestJSON = any>(

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);
Expand Down
22 changes: 15 additions & 7 deletions packages/node-fetch/tests/non-http-fetch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down

0 comments on commit bf76ab6

Please sign in to comment.