blob: 3e2d77c025d39e7a0afc8b1f936204bd4ad6d2f5 [file]
// Generated by //js/private/node-patches:compile
"use strict";
/**
* @license
* Copyright 2019 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.escapeFunction = exports.isSubPath = exports.patcher = void 0;
const path = require("path");
const util = require("util");
// using require here on purpose so we can override methods with any
// also even though imports are mutable in typescript the cognitive dissonance is too high because
// es modules
const _fs = require('fs');
const patcher = (fs = _fs, roots) => {
fs = fs || _fs;
roots = roots || [];
roots = roots.filter((root) => fs.existsSync(root));
if (!roots.length) {
if (process.env.VERBOSE_LOGS) {
console.error('fs patcher called without any valid root paths ' + __filename);
}
return;
}
const origLstat = fs.lstat.bind(fs);
const origLstatSync = fs.lstatSync.bind(fs);
const origReaddir = fs.readdir.bind(fs);
const origReaddirSync = fs.readdirSync.bind(fs);
const origReadlink = fs.readlink.bind(fs);
const origReadlinkSync = fs.readlinkSync.bind(fs);
const origRealpath = fs.realpath.bind(fs);
const origRealpathNative = fs.realpath.native;
const origRealpathSync = fs.realpathSync.bind(fs);
const origRealpathSyncNative = fs.realpathSync.native;
const isEscape = (0, exports.escapeFunction)(roots);
// =========================================================================
// fs.lstat
// =========================================================================
fs.lstat = (...args) => {
let cb = args.length > 1 ? args[args.length - 1] : undefined;
// preserve error when calling function without required callback
if (!cb) {
return origLstat(...args);
}
cb = once(cb);
// override the callback
args[args.length - 1] = (err, stats) => {
if (err)
return cb(err);
if (!stats.isSymbolicLink()) {
// the file is not a symbolic link so there is nothing more to do
return cb(null, stats);
}
args[0] = path.resolve(args[0]);
return guardedReadLink(args[0], (str) => {
if (str != args[0]) {
// there are one or more hops within the guards so there is nothing more to do
return cb(null, stats);
}
// there are no hops so lets report the stats of the real file;
// we can't use origRealPath here since that function calls lstat internally
// which can result in an infinite loop
return unguardedRealPath(args[0], (err, str) => {
if (err) {
if (err.code === 'ENOENT') {
// broken link so there is nothing more to do
return cb(null, stats);
}
return cb(err);
}
return origLstat(str, (err, str) => cb(err, str));
});
});
};
origLstat(...args);
};
fs.lstatSync = (...args) => {
const stats = origLstatSync(...args);
if (!stats.isSymbolicLink()) {
// the file is not a symbolic link so there is nothing more to do
return stats;
}
args[0] = path.resolve(args[0]);
const guardedReadLink = guardedReadLinkSync(args[0]);
if (guardedReadLink != args[0]) {
// there are one or more hops within the guards so there is nothing more to do
return stats;
}
try {
// there are no hops so lets report the stats of the real file;
// we can't use origRealPathSync here since that function calls lstat internally
// which can result in an infinite loop
return origLstatSync(unguardedRealPathSync(args[0]), args.slice(1));
}
catch (err) {
if (err.code === 'ENOENT') {
// broken link so there is nothing more to do
return stats;
}
throw err;
}
};
// =========================================================================
// fs.realpath
// =========================================================================
fs.realpath = (...args) => {
let cb = args.length > 1 ? args[args.length - 1] : undefined;
// preserve error when calling function without required callback
if (!cb) {
return origRealpath(...args);
}
cb = once(cb);
args[args.length - 1] = (err, str) => {
if (err)
return cb(err);
const escapedRoot = isEscape(args[0], str);
if (escapedRoot) {
return guardedRealPath(args[0], (err, str) => cb(err, str), escapedRoot);
}
else {
return cb(null, str);
}
};
origRealpath(...args);
};
fs.realpath.native = (...args) => {
let cb = args.length > 1 ? args[args.length - 1] : undefined;
// preserve error when calling function without required callback
if (!cb) {
return origRealpathNative(...args);
}
cb = once(cb);
args[args.length - 1] = (err, str) => {
if (err)
return cb(err);
const escapedRoot = isEscape(args[0], str);
if (escapedRoot) {
return guardedRealPath(args[0], (err, str) => cb(err, str), escapedRoot);
}
else {
return cb(null, str);
}
};
origRealpathNative(...args);
};
fs.realpathSync = (...args) => {
const str = origRealpathSync(...args);
const escapedRoot = isEscape(args[0], str);
if (escapedRoot) {
return guardedRealPathSync(args[0], escapedRoot);
}
return str;
};
fs.realpathSync.native = (...args) => {
const str = origRealpathSyncNative(...args);
const escapedRoot = isEscape(args[0], str);
if (escapedRoot) {
return guardedRealPathSync(args[0], escapedRoot);
}
return str;
};
// =========================================================================
// fs.readlink
// =========================================================================
fs.readlink = (...args) => {
let cb = args.length > 1 ? args[args.length - 1] : undefined;
// preserve error when calling function without required callback
if (!cb) {
return origReadlink(...args);
}
cb = once(cb);
args[args.length - 1] = (err, str) => {
if (err)
return cb(err);
const resolved = path.resolve(args[0]);
str = path.resolve(path.dirname(resolved), str);
const escapedRoot = isEscape(resolved, str);
if (escapedRoot) {
return nextHop(str, (next) => {
if (!next) {
if (next == undefined) {
// The escape from the root is not mappable back into the root; throw EINVAL
return cb(enoent('readlink', args[0]));
}
else {
// The escape from the root is not mappable back into the root; throw EINVAL
return cb(einval('readlink', args[0]));
}
}
next = path.resolve(path.dirname(resolved), path.relative(path.dirname(str), next));
if (next != resolved &&
!isEscape(resolved, next, [escapedRoot])) {
return cb(null, next);
}
// The escape from the root is not mappable back into the root; we must make
// this look like a real file so we call readlink on the realpath which we
// expect to return an error
return origRealpath(resolved, (err, str) => {
if (err)
return cb(err);
return origReadlink(str, (err, str) => cb(err, str));
});
});
}
else {
return cb(null, str);
}
};
origReadlink(...args);
};
fs.readlinkSync = (...args) => {
const resolved = path.resolve(args[0]);
const str = path.resolve(path.dirname(resolved), origReadlinkSync(...args));
const escapedRoot = isEscape(resolved, str);
if (escapedRoot) {
let next = nextHopSync(str);
if (!next) {
if (next == undefined) {
// The escape from the root is not mappable back into the root; throw EINVAL
throw enoent('readlink', args[0]);
}
else {
// The escape from the root is not mappable back into the root; throw EINVAL
throw einval('readlink', args[0]);
}
}
next = path.resolve(path.dirname(resolved), path.relative(path.dirname(str), next));
if (next != resolved && !isEscape(resolved, next, [escapedRoot])) {
return next;
}
// The escape from the root is not mappable back into the root; throw EINVAL
throw einval('readlink', args[0]);
}
return str;
};
// =========================================================================
// fs.readdir
// =========================================================================
fs.readdir = (...args) => {
const p = path.resolve(args[0]);
let cb = args[args.length - 1];
if (typeof cb !== 'function') {
// this will likely throw callback required error.
return origReaddir(...args);
}
cb = once(cb);
args[args.length - 1] = (err, result) => {
if (err)
return cb(err);
// user requested withFileTypes
if (result[0] && result[0].isSymbolicLink) {
Promise.all(result.map((v) => handleDirent(p, v)))
.then(() => {
cb(null, result);
})
.catch((err) => {
cb(err);
});
}
else {
// string array return for readdir.
cb(null, result);
}
};
origReaddir(...args);
};
fs.readdirSync = (...args) => {
const res = origReaddirSync(...args);
const p = path.resolve(args[0]);
res.forEach((v) => {
handleDirentSync(p, v);
});
return res;
};
// =========================================================================
// fs.opendir
// =========================================================================
if (fs.opendir) {
const origOpendir = fs.opendir.bind(fs);
fs.opendir = (...args) => {
let cb = args[args.length - 1];
// if this is not a function opendir should throw an error.
// we call it so we don't have to throw a mock
if (typeof cb === 'function') {
cb = once(cb);
args[args.length - 1] = async (err, dir) => {
try {
cb(null, await handleDir(dir));
}
catch (err) {
cb(err);
}
};
origOpendir(...args);
}
else {
return origOpendir(...args).then((dir) => {
return handleDir(dir);
});
}
};
}
// =========================================================================
// fs.promises
// =========================================================================
/**
* patch fs.promises here.
*
* this requires a light touch because if we trigger the getter on older nodejs versions
* it will log an experimental warning to stderr
*
* `(node:62945) ExperimentalWarning: The fs.promises API is experimental`
*
* this api is available as experimental without a flag so users can access it at any time.
*/
const promisePropertyDescriptor = Object.getOwnPropertyDescriptor(fs, 'promises');
if (promisePropertyDescriptor) {
const promises = {};
promises.lstat = util.promisify(fs.lstat);
// NOTE: node core uses the newer realpath function fs.promises.native instead of fs.realPath
promises.realpath = util.promisify(fs.realpath.native);
promises.readlink = util.promisify(fs.readlink);
promises.readdir = util.promisify(fs.readdir);
if (fs.opendir)
promises.opendir = util.promisify(fs.opendir);
// handle experimental api warnings.
// only applies to version of node where promises is a getter property.
if (promisePropertyDescriptor.get) {
const oldGetter = promisePropertyDescriptor.get.bind(fs);
const cachedPromises = {};
promisePropertyDescriptor.get = () => {
const _promises = oldGetter();
Object.assign(cachedPromises, _promises, promises);
return cachedPromises;
};
Object.defineProperty(fs, 'promises', promisePropertyDescriptor);
}
else {
// api can be patched directly
Object.assign(fs.promises, promises);
}
}
// =========================================================================
// helper functions for dirs
// =========================================================================
async function handleDir(dir) {
const p = path.resolve(dir.path);
const origIterator = dir[Symbol.asyncIterator].bind(dir);
const origRead = dir.read.bind(dir);
dir[Symbol.asyncIterator] = function () {
return __asyncGenerator(this, arguments, function* () {
var e_1, _a;
try {
for (var _b = __asyncValues(origIterator()), _c; _c = yield __await(_b.next()), !_c.done;) {
const entry = _c.value;
yield __await(handleDirent(p, entry));
yield yield __await(entry);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield __await(_a.call(_b));
}
finally { if (e_1) throw e_1.error; }
}
});
};
dir.read = async (...args) => {
if (typeof args[args.length - 1] === 'function') {
const cb = args[args.length - 1];
args[args.length - 1] = async (err, entry) => {
cb(err, entry ? await handleDirent(p, entry) : null);
};
origRead(...args);
}
else {
const entry = await origRead(...args);
if (entry) {
await handleDirent(p, entry);
}
return entry;
}
};
const origReadSync = dir.readSync.bind(dir);
dir.readSync = () => {
return handleDirentSync(p, origReadSync()); // intentionally sync for simplicity
};
return dir;
}
function handleDirent(p, v) {
return new Promise((resolve, reject) => {
if (!v.isSymbolicLink()) {
return resolve(v);
}
const f = path.resolve(p, v.name);
return guardedReadLink(f, (str) => {
if (f != str) {
return resolve(v);
}
// There are no hops so we should hide the fact that the file is a symlink
v.isSymbolicLink = () => false;
origRealpath(f, (err, str) => {
if (err) {
throw err;
}
fs.stat(str, (err, stat) => {
if (err) {
throw err;
}
patchDirent(v, stat);
resolve(v);
});
});
});
});
}
function handleDirentSync(p, v) {
if (v && v.isSymbolicLink) {
if (v.isSymbolicLink()) {
const f = path.resolve(p, v.name);
if (f == guardedReadLinkSync(f)) {
// There are no hops so we should hide the fact that the file is a symlink
v.isSymbolicLink = () => false;
const stat = fs.statSync(origRealpathSync(f));
patchDirent(v, stat);
}
}
}
}
function nextHop(loc, cb) {
let nested = [];
let escapedHop = false;
const oneHop = (maybe, cb) => {
origReadlink(maybe, (err, str) => {
if (err) {
if (err.code === 'ENOENT') {
// file does not exist
return cb(undefined);
}
nested.push(path.basename(maybe));
const dirname = path.dirname(maybe);
if (!dirname ||
dirname == maybe ||
dirname == '.' ||
dirname == '/') {
// not a link
return cb(escapedHop);
}
maybe = dirname;
return oneHop(maybe, cb);
}
if (!path.isAbsolute(str)) {
str = path.resolve(path.dirname(maybe), str);
}
str = path.join(str, ...nested.reverse());
if (isEscape(loc, str)) {
if (!escapedHop) {
escapedHop = str;
}
nested.push(path.basename(maybe));
const dirname = path.dirname(maybe);
if (!dirname ||
dirname == maybe ||
dirname == '.' ||
dirname == '/') {
// not a link
return cb(escapedHop);
}
maybe = dirname;
return oneHop(maybe, cb);
}
else {
return cb(str);
}
});
};
oneHop(loc, cb);
}
function nextHopSync(loc) {
let readlink;
let nested = [];
let maybe = loc;
let escapedHop = false;
function _tryParent() {
nested.push(path.basename(maybe));
const dirname = path.dirname(maybe);
if (!dirname ||
dirname == maybe ||
dirname == '.' ||
dirname == '/') {
// not a link
return false;
}
maybe = dirname;
return true;
}
for (;;) {
try {
readlink = origReadlinkSync(maybe);
if (!path.isAbsolute(readlink)) {
readlink = path.resolve(path.dirname(maybe), readlink);
}
readlink = path.join(readlink, ...nested.reverse());
if (isEscape(loc, readlink)) {
if (!escapedHop) {
escapedHop = readlink;
}
if (!_tryParent()) {
return escapedHop;
}
continue;
}
return readlink;
}
catch (err) {
if (err.code === 'ENOENT') {
// file does not exist
return undefined;
}
if (!_tryParent()) {
return escapedHop;
}
continue;
}
}
}
function guardedReadLink(start, cb) {
let loc = start;
return nextHop(loc, (next) => {
if (!next) {
// we're no longer hopping but we haven't escaped;
// something funky happened in the filesystem
return cb(loc);
}
if (isEscape(loc, next)) {
// this hop takes us out of the guard
return nextHop(next, (next2) => {
if (!next2) {
// the chain is done
return cb(loc);
}
const maybe = path.resolve(path.dirname(loc), path.relative(path.dirname(next), next2));
if (!isEscape(loc, maybe)) {
// outside of the guard is a symlink but it is a relative link path
// we can map within the guard so return that
return cb(maybe);
}
// outside of the guard is a symlink that is not mappable inside the guard
return cb(loc);
});
}
return cb(next);
});
}
function guardedReadLinkSync(start) {
let loc = start;
let next = nextHopSync(loc);
if (!next) {
// we're no longer hopping but we haven't escaped;
// something funky happened in the filesystem
return loc;
}
if (isEscape(loc, next)) {
// this hop takes us out of the guard
const next2 = nextHopSync(next);
if (!next2) {
// the chain is done
return loc;
}
const maybe = path.resolve(path.dirname(loc), path.relative(path.dirname(next), next2));
if (!isEscape(loc, maybe)) {
// outside of the guard is a symlink but it is a relative link path
// we can map within the guard so return that
return maybe;
}
// outside of the guard is a symlink that is not mappable inside the guard
return loc;
}
return next;
}
function unguardedRealPath(start, cb) {
start = String(start); // handle the "undefined" case (matches behavior as fs.realpath)
const oneHop = (loc, cb) => {
nextHop(loc, (next) => {
if (next == undefined) {
// file does not exist (broken link)
return cb(enoent('realpath', start));
}
else if (!next) {
// we've hit a real file
return cb(null, loc);
}
oneHop(next, cb);
});
};
oneHop(start, cb);
}
function guardedRealPath(start, cb, escapedRoot = undefined) {
start = String(start); // handle the "undefined" case (matches behavior as fs.realpath)
const oneHop = (loc, cb) => {
nextHop(loc, (next) => {
if (!next) {
// we're no longer hopping but we haven't escaped
return fs.exists(loc, (e) => {
if (e) {
// we hit a real file within the guard and can go no further
return cb(null, loc);
}
else {
// something funky happened in the filesystem
return cb(enoent('realpath', start));
}
});
}
if (escapedRoot
? isEscape(loc, next, [escapedRoot])
: isEscape(loc, next)) {
// this hop takes us out of the guard
return nextHop(next, (next2) => {
if (!next2) {
// the chain is done
return cb(null, loc);
}
const maybe = path.resolve(path.dirname(loc), path.relative(path.dirname(next), next2));
if (isEscape(loc, maybe)) {
// outside of the guard is a symlink that is not mappable inside the guard;
// call the unguarded realpath which will throw if the link is dangling;
// if it doesn't throw then return the last path within the guard
return origRealpath(start, (err, _) => {
if (err)
return cb(err);
return cb(null, loc);
});
}
return oneHop(maybe, cb);
});
}
oneHop(next, cb);
});
};
oneHop(start, cb);
}
function unguardedRealPathSync(start) {
start = String(start); // handle the "undefined" case (matches behavior as fs.realpathSync)
for (let loc = start, next;; loc = next) {
next = nextHopSync(loc);
if (next == undefined) {
// file does not exist (broken link)
throw enoent('realpath', start);
}
else if (!next) {
// we've hit a real file
return loc;
}
}
}
function guardedRealPathSync(start, escapedRoot = undefined) {
start = String(start); // handle the "undefined" case (matches behavior as fs.realpathSync)
for (let loc = start, next;; loc = next) {
next = nextHopSync(loc);
if (!next) {
// we're no longer hopping but we haven't escaped
if (fs.existsSync(loc)) {
// we hit a real file within the guard and can go no further
return loc;
}
else {
// something funky happened in the filesystem; throw ENOENT
throw enoent('realpath', start);
}
}
if (escapedRoot
? isEscape(loc, next, [escapedRoot])
: isEscape(loc, next)) {
// this hop takes us out of the guard
const next2 = nextHopSync(next);
if (!next2) {
// the chain is done
return loc;
}
const maybe = path.resolve(path.dirname(loc), path.relative(path.dirname(next), next2));
if (isEscape(loc, maybe)) {
// outside of the guard is a symlink that is not mappable inside the guard;
// call the unguarded realpath which will throw if the link is dangling;
// if it doesn't throw then return the last path within the guard
origRealpathSync(start);
return loc;
}
next = maybe;
// outside of the guard is a symlink but it is a relative link path
// we can map within the guard so lets iterate one more time
}
}
}
};
exports.patcher = patcher;
// =========================================================================
// generic helper functions
// =========================================================================
function isSubPath(parent, child) {
return !path.relative(parent, child).startsWith('..');
}
exports.isSubPath = isSubPath;
const escapeFunction = (_roots) => {
// ensure roots are always absolute
_roots = _roots.map((root) => path.resolve(root));
function _isEscape(linkPath, linkTarget, roots = _roots) {
// linkPath is the path of the symlink file itself
// linkTarget is a path that the symlink points to one or more hops away
if (!path.isAbsolute(linkPath)) {
linkPath = path.resolve(linkPath);
}
if (!path.isAbsolute(linkTarget)) {
linkTarget = path.resolve(linkTarget);
}
let escapedRoot = undefined;
for (const root of roots) {
// If the link is in the root check if the realPath has escaped
if (isSubPath(root, linkPath) || linkPath == root) {
if (!isSubPath(root, linkTarget) && linkTarget != root) {
if (!escapedRoot || escapedRoot.length < root.length) {
// if escaping multiple roots then choose the longest one
escapedRoot = root;
}
}
}
}
if (escapedRoot) {
return escapedRoot;
}
return false;
}
return _isEscape;
};
exports.escapeFunction = escapeFunction;
function once(fn) {
let called = false;
return (...args) => {
if (called)
return;
called = true;
let err = false;
try {
fn(...args);
}
catch (_e) {
err = _e;
}
// blow the stack to make sure this doesn't fall into any unresolved promise contexts
if (err) {
setImmediate(() => {
throw err;
});
}
};
}
function patchDirent(dirent, stat) {
// add all stat is methods to Dirent instances with their result.
for (const i in stat) {
if (i.indexOf('is') === 0 && typeof stat[i] === 'function') {
//
const result = stat[i]();
if (result)
dirent[i] = () => true;
else
dirent[i] = () => false;
}
}
}
function enoent(s, p) {
let err = new Error(`ENOENT: no such file or directory, ${s} '${p}'`);
err.errno = -2;
err.syscall = s;
err.code = 'ENOENT';
err.path = p;
return err;
}
function einval(s, p) {
let err = new Error(`EINVAL: invalid argument, ${s} '${p}'`);
err.errno = -22;
err.syscall = s;
err.code = 'EINVAL';
err.path = p;
return err;
}