file-fetch
Advanced tools
Comparing version 1.5.0 to 1.6.0
101
index.js
const fs = require('fs') | ||
const { access } = require('fs/promises') | ||
const path = require('path') | ||
const { promisify } = require('util') | ||
const { URL } = require('url') | ||
const { Readable } = require('stream') | ||
@@ -11,16 +10,16 @@ const getStream = require('get-stream') | ||
const access = promisify(fs.access) | ||
const { R_OK } = fs.constants | ||
function decodeIRI (iri) { | ||
function decodeIRI (iri, baseDir, baseURL) { | ||
// IRIs without file scheme are used directly | ||
if (!iri.startsWith('file:')) { | ||
return iri | ||
if (!iri.startsWith('file:') && !baseURL) { | ||
return path.join(baseDir, iri) | ||
} | ||
const pathname = decodeURIComponent(new URL(iri).pathname) | ||
const pathname = decodeURIComponent(new URL(iri, baseURL).pathname) | ||
// remove the leading slash for IRIs with file scheme and relative path | ||
if (!iri.startsWith('file:/')) { | ||
return pathname.split('/').slice(1).join('/') | ||
if (!iri.startsWith('file:/') && | ||
(!baseURL || !pathname.startsWith('/'))) { | ||
return './' + (path.join(baseDir, '.' + pathname)) | ||
} | ||
@@ -42,58 +41,64 @@ | ||
async function fetch (iri, { body, contentTypeLookup = contentType, method = 'GET' } = {}) { | ||
method = method.toUpperCase() | ||
function create ({ baseDir = '', baseURL } = {}) { | ||
return async function fetch (iri, { body, contentTypeLookup = contentType, method = 'GET' } = {}) { | ||
method = method.toUpperCase() | ||
const pathname = decodeIRI(iri) | ||
const extension = path.extname(pathname) | ||
const pathname = decodeIRI(iri, baseDir, baseURL) | ||
const extension = path.extname(pathname) | ||
if (method === 'GET') { | ||
return new Promise((resolve) => { | ||
const stream = fs.createReadStream(pathname) | ||
if (method === 'GET') { | ||
return new Promise((resolve) => { | ||
const stream = fs.createReadStream(pathname) | ||
stream.on('error', () => { | ||
resolve(response(404, new ReadableError(new Error('File not found')))) | ||
}) | ||
stream.on('error', () => { | ||
resolve(response(404, new ReadableError(new Error('File not found')))) | ||
}) | ||
stream.on('open', () => { | ||
resolve(response(200, stream, { | ||
'content-type': contentTypeLookup(extension) || contentType(extension) | ||
})) | ||
stream.on('open', () => { | ||
resolve(response(200, stream, { | ||
'content-type': contentTypeLookup(extension) || contentType(extension) | ||
})) | ||
}) | ||
}) | ||
}) | ||
} | ||
if (method === 'HEAD') { | ||
try { | ||
await access(pathname, R_OK) | ||
} catch (error) { | ||
return response(404, new ReadableError(new Error('File not found'))) | ||
} | ||
const stream = new Readable() | ||
stream.push(null) | ||
if (method === 'HEAD') { | ||
try { | ||
await access(pathname, R_OK) | ||
} catch (error) { | ||
return response(404, new ReadableError(new Error('File not found'))) | ||
} | ||
return response(200, stream, { | ||
'content-type': contentTypeLookup(extension) || contentType(extension) | ||
}) | ||
} | ||
const stream = new Readable() | ||
stream.push(null) | ||
if (method === 'PUT') { | ||
if (!body) { | ||
return response(406, new ReadableError(new Error('body required'))) | ||
return response(200, stream, { | ||
'content-type': contentTypeLookup(extension) || contentType(extension) | ||
}) | ||
} | ||
return new Promise((resolve) => { | ||
body.pipe(fs.createWriteStream(pathname)).on('finish', () => { | ||
resolve(response(201)) | ||
}).on('error', (err) => { | ||
resolve(response(500, new ReadableError(err))) | ||
if (method === 'PUT') { | ||
if (!body) { | ||
return response(406, new ReadableError(new Error('body required'))) | ||
} | ||
return new Promise((resolve) => { | ||
body.pipe(fs.createWriteStream(pathname)).on('finish', () => { | ||
resolve(response(201)) | ||
}).on('error', (err) => { | ||
resolve(response(500, new ReadableError(err))) | ||
}) | ||
}) | ||
}) | ||
} | ||
return response(405, new ReadableError(new Error('method not allowed'))) | ||
} | ||
return response(405, new ReadableError(new Error('method not allowed'))) | ||
} | ||
const fetch = create() | ||
fetch.Headers = Headers | ||
fetch.create = create | ||
module.exports = fetch |
{ | ||
"name": "file-fetch", | ||
"version": "1.5.0", | ||
"version": "1.6.0", | ||
"description": "fetch for read and write access to the local file system", | ||
@@ -20,2 +20,12 @@ "main": "index.js", | ||
], | ||
"nyc": { | ||
"statements": 100, | ||
"branches": 100, | ||
"lines": 100, | ||
"functions": 100, | ||
"check-coverage": true | ||
}, | ||
"engines": { | ||
"node": ">=10.0.0" | ||
}, | ||
"author": "Thomas Bergwinkl <bergi@axolotlfarm.org> (https://www.bergnet.org/people/bergi/card#me)", | ||
@@ -31,12 +41,12 @@ "contributors": [ | ||
"dependencies": { | ||
"get-stream": "^5.1.0", | ||
"mime-types": "^2.1.25", | ||
"node-fetch": "^2.6.0", | ||
"get-stream": "^6.0.1", | ||
"mime-types": "^2.1.30", | ||
"node-fetch": "^2.6.1", | ||
"readable-error": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"mocha": "^6.2.2", | ||
"nyc": "^15.0.0", | ||
"standard": "^14.3.1" | ||
"mocha": "^8.3.2", | ||
"nyc": "^15.1.0", | ||
"standard": "^16.0.3" | ||
} | ||
} |
@@ -153,2 +153,24 @@ const fs = require('fs') | ||
it('should return a 200 status but no body with method HEAD and existing file (fallback to contentType)', async () => { | ||
function contentTypeLookup () { | ||
return undefined | ||
} | ||
const pathname = path.join(__dirname, 'support/file.txt') | ||
const res = await fileFetch('file://' + pathname, { | ||
method: 'HEAD', | ||
contentTypeLookup | ||
}) | ||
assert.strictEqual(res.status, 200) | ||
res.body.on('data', (chunk) => { | ||
assert(false) | ||
}) | ||
return new Promise((resolve) => { | ||
res.body.on('end', () => { | ||
resolve() | ||
}) | ||
}) | ||
}) | ||
it('should return a 404 status with method HEAD and non-existent file', async () => { | ||
@@ -155,0 +177,0 @@ const pathname = path.join(__dirname, 'support/missing.txt') |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17470
8
471
3
9
+ Addedget-stream@6.0.1(transitive)
- Removedend-of-stream@1.4.4(transitive)
- Removedget-stream@5.2.0(transitive)
- Removedonce@1.4.0(transitive)
- Removedpump@3.0.2(transitive)
- Removedwrappy@1.0.2(transitive)
Updatedget-stream@^6.0.1
Updatedmime-types@^2.1.30
Updatednode-fetch@^2.6.1