From e0525c33e166e60dac751b453e3b07b88997cc1e Mon Sep 17 00:00:00 2001 From: Nitzan Uziely Date: Thu, 17 Feb 2022 11:09:29 +0200 Subject: [PATCH] add support for file urls in fetch --- lib/fetch/index.js | 71 +++++++++++++++++++++++++++++++++++++- test/fetch/client-fetch.js | 21 +++++++++++ test/fixtures/file.text | 1 + 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/file.text diff --git a/lib/fetch/index.js b/lib/fetch/index.js index 0eae43cb28f..5fd26c35a46 100644 --- a/lib/fetch/index.js +++ b/lib/fetch/index.js @@ -52,6 +52,7 @@ const { PassThrough, pipeline } = require('stream') const { isErrored, isReadable } = require('../core/util') const { kIsMockActive } = require('../mock/mock-symbols') const { dataURLProcessor } = require('./dataURL') +const fs = require('fs') /** @type {import('buffer').resolveObjectURL} */ let resolveObjectURL @@ -882,7 +883,8 @@ async function schemeFetch (fetchParams) { case 'file:': { // For now, unfortunate as it is, file URLs are left as an exercise for the reader. // When in doubt, return a network error. - return makeNetworkError('not implemented... yet...') + const currentURL = requestCurrentURL(request) + return fileFetch.call(this, request, currentURL) } case 'http:': case 'https:': { @@ -1990,4 +1992,71 @@ function httpNetworkFetch ( }) } +async function fileFetch (request, url) { + const context = this + if (request.method !== 'GET') { + return makeNetworkError(`Fetching files only supports the GET method. Received ${request.method}`) + } + + let stream + let file + + try { + const path = url.href.substring('file://'.length) + file = await fs.promises.open(path) + if (context.terminated) { + if (context.terminated.aborted) { + throw new AbortError() + } else { + throw new Error('terminated') + } + } + stream = file.createReadStream({ autoClose: true }) + } catch (originalError) { + if (stream) { + stream.destroy(originalError) + } else if (file) { + try { + await file.close() + } catch (closeError) { + return createAggregateNetworkError(originalError, closeError) + } + } + return makeNetworkError(originalError) + } + + try { + function onTerminated () { + const aborted = context.terminated.aborted + if (aborted) { + response.aborted = true + stream.destroy(new AbortError()) + } else { + stream.destroy(new TypeError('terminated')) + } + } + function streamClose () { + context.off('terminated', onTerminated) + } + const response = makeResponse({ + status: 200, + statusText: 'OK', + headersList: [], + body: safelyExtractBody(stream)[0] + }) + context.on('terminated', onTerminated) + stream.on('close', streamClose) + return response + } catch (err) { + stream.destroy(err) + return makeNetworkError(err) + } +} + +function createAggregateNetworkError (originalError, secondError) { + const err = new AggregateError([originalError, secondError], originalError.message) + err.code = originalError.code + return makeNetworkError(err) +} + module.exports = fetch diff --git a/test/fetch/client-fetch.js b/test/fetch/client-fetch.js index ef6942f6bea..dbecd6d4231 100644 --- a/test/fetch/client-fetch.js +++ b/test/fetch/client-fetch.js @@ -7,6 +7,7 @@ const { createServer } = require('http') const { ReadableStream } = require('stream/web') const { Blob } = require('buffer') const { fetch, Response, Request, FormData, File, FileLike } = require('../..') +const { join } = require('path') test('function signature', (t) => { t.plan(2) @@ -332,3 +333,23 @@ test('post FormData with File', (t) => { t.ok(/filename123/.test(result)) }) }) + +test('fetch file', async (t) => { + t.plan(1) + const fileToRead = join(__dirname, '..', 'fixtures', 'file.text') + const body = await fetch(`file://${fileToRead}`) + const text = await body.text() + t.equal(text, 'hello world') +}) + +test('fetch file does not exist', async (t) => { + t.plan(2) + const fileToRead = join(__dirname, '..', 'fixtures', 'does-not-exist.text') + try { + await fetch(`file://${fileToRead}`) + t.fail('fetch should have thrown') + } catch (err) { + t.ok(err.cause) + t.equal(err.cause.code, 'ENOENT') + } +}) diff --git a/test/fixtures/file.text b/test/fixtures/file.text new file mode 100644 index 00000000000..95d09f2b101 --- /dev/null +++ b/test/fixtures/file.text @@ -0,0 +1 @@ +hello world \ No newline at end of file