Skip to content

Commit

Permalink
Added postman-fs and related options
Browse files Browse the repository at this point in the history
  • Loading branch information
vikiCoder committed Apr 25, 2019
1 parent e26539a commit 959dac2
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
master:
new features:
- Passed postman-fs instance to runtime
- Added --working-dir and --no-parentfileread options

4.4.1:
date: 2019-03-25
chores:
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ For more details on [Reporters](#reporters) and writing your own [External Repor
Run requests within a particular folder/folders in a collection. Multiple folders can be specified by using
`--folder` multiple times, like so: `--folder f1 --folder f2`.

- `-w <path>`, `--working-dir <path>`<br />
The path to the directory containing files that are used in specified collection. Default is current directory.

- `--no-parentfileread`<br />
Don't allow newman to read files outside of working directory while running a collection.

- `--export-environment <path>`<br />
The path to the file where Newman will output the final environment variables file before completing a run.

Expand Down Expand Up @@ -237,6 +243,8 @@ return of the `newman.run` function is a run instance, which emits run events th
| options.iterationCount | Specify the number of iterations to run on the collection. This is usually accompanied by providing a data file reference as `options.iterationData`.<br /><br />_Optional_<br />Type: `number`, Default value: `1` |
| options.iterationData | Path to the JSON or CSV file or URL to be used as data source when running multiple iterations on a collection.<br /><br />_Optional_<br />Type: `string` |
| options.folder | The name or ID of the folder/folders (ItemGroup) in the collection which would be run instead of the entire collection.<br /><br />_Optional_<br />Type: `string\|array` |
| options.workingDir | The path to the directory containing files that are used in specified collection.<br /><br />_Optional_<br />Type: `string`, Default value: current directory |
| options.noParentfileread | Don't allow newman to read files outside of working directory while running a collection.<br /><br />_Optional_<br />Type: `boolean`, Default value: `false` |
| options.timeout | Specify the time (in milliseconds) to wait for the entire collection run to complete execution.<br /><br />_Optional_<br />Type: `number`, Default value: `Infinity` |
| options.timeoutRequest | Specify the time (in milliseconds) to wait for requests to return a response.<br /><br />_Optional_<br />Type: `number`, Default value: `Infinity` |
| options.timeoutScript | Specify the time (in milliseconds) to wait for scripts to return a response.<br /><br />_Optional_<br />Type: `number`, Default value: `Infinity` |
Expand Down
2 changes: 2 additions & 0 deletions bin/newman.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ program
.option('-g, --globals <path>', 'Specify a URL or Path to a file containing Postman Globals.')
// eslint-disable-next-line max-len
.option('--folder <path>', 'Specify folder to run from a collection. Can be specified multiple times to run multiple folders', util.cast.memoize, [])
.option('-w, --working-dir <path>', 'Specify working directory that contains files needed for the collection run. Default is current directory.', process.cwd())
.option('--no-parentfileread', 'Don\'t allow reading files outside of working directory')
.option('-r, --reporters [reporters]', 'Specify the reporters to use for this run.', util.cast.csvParse, ['cli'])
.option('-n, --iteration-count <n>', 'Define the number of iterations to run.', util.cast.integer)
.option('-d, --iteration-data <path>', 'Specify a data file to use for iterations (either json or csv).')
Expand Down
3 changes: 2 additions & 1 deletion lib/run/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var fs = require('fs'),
PostmanFs = require('./postmanFs'),
_ = require('lodash'),
sdk = require('postman-collection'),
asyncEach = require('async/each'),
Expand Down Expand Up @@ -152,7 +153,7 @@ module.exports = function (options, callback) {
request: options.timeoutRequest || 0,
script: options.timeoutScript || 0
},
fileResolver: fs,
fileResolver: new PostmanFs(options.workingDir, !Boolean(options.noParentfileread)),
requester: {
cookieJar: request.jar(),
followRedirects: _.has(options, 'ignoreRedirects') ? !options.ignoreRedirects : undefined,
Expand Down
183 changes: 183 additions & 0 deletions lib/run/postmanFs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
const fs = require('fs'),
path = require('path'),
Readable = require('stream').Readable,
_ = require('lodash'),
util = require('util'),

PPERM_ERR = 'PPERM: insecure file access outside working directory',

// Use simple character check instead of regex to prevent regex attack
/**
* Windows root directory can be of the following from
*
* | File System | Actual | Modified |
* |-------------|------------------|-------------------|
* | LFS (Local) | C:\Program | /C:/Program |
* | UNC | \\Server\Program | ///Server/Program |
*/
isWindowsRoot = function (path) {
const drive = path.charAt(1);

return ((path.charAt(0) === '/') &&
((drive >= 'A' && drive <= 'Z') || (drive >= 'a' && drive <= 'z')) &&
(path.charAt(2) === ':')) ||
path.slice(0, 3) === '///'; // Modified UNC path
},

stripTrailingSep = function (thePath) {
if (thePath[thePath.length - 1] === path.sep) {
return thePath.slice(0, -1);
}

return thePath;
},

pathIsInside = function (thePath, potentialParent) {
// For inside-directory checking, we want to allow trailing slashes, so normalize.
thePath = stripTrailingSep(thePath);
potentialParent = stripTrailingSep(potentialParent);

// Node treats only Windows as case-insensitive in its path module; we follow those conventions.
if (global.process.platform === 'win32') {
thePath = thePath.toLowerCase();
potentialParent = potentialParent.toLowerCase();
}

return thePath.lastIndexOf(potentialParent, 0) === 0 &&
(
thePath[potentialParent.length] === path.sep ||
thePath[potentialParent.length] === undefined
);
};

/**
* Postman file resolver wrapper over fs
*
* @param {*} workingDir - Path of working directory
* @param {*} insecureFileRead - If true, allow reading files outside working directory
* @param {*} fileWhitelist - List of allowed files outside of working directory
*/
function PostmanFs (workingDir, insecureFileRead = false, fileWhitelist = []) {
this._fs = fs;
this._path = path;
this.constants = this._fs.constants;

this.workingDir = workingDir;
this.insecureFileRead = insecureFileRead;
this.fileWhitelist = fileWhitelist;

this.isWindows = global.process.platform === 'win32';
}

/**
* Private method to resole the path based based on working directory
*
* @param {String} relOrAbsPath - Relative or absolute path to resolve
* @param {Array} whiteList - A list of absolute path to whitelist
*
* @returns {String} The resolved path
*/
PostmanFs.prototype._resolve = function (relOrAbsPath, whiteList) {
// Special handling for windows absolute paths to work cross platform
this.isWindows && isWindowsRoot(relOrAbsPath) && (relOrAbsPath = relOrAbsPath.substring(1));

// Resolve the path from the working directory. The file should always be resolved so that
// cross os variations are mitigated
let resolvedPath = this._path.resolve(this.workingDir, relOrAbsPath);

// Check file is within working directory
if (!this.insecureFileRead && // insecureFile read disabled
!pathIsInside(resolvedPath, this.workingDir) && // File not inside working directory
!_.includes(whiteList, resolvedPath)) { // File not in whitelist
// Exit
return;
}

return resolvedPath;
};

/**
* Asynchronous path resolver function
*
* @param {String} relOrAbsPath - Relative or absolute path to resolve
* @param {Array} [whiteList] - A optional list of additional absolute path to whitelist
* @param {Function} callback -
*/
PostmanFs.prototype.resolvePath = function (relOrAbsPath, whiteList, callback) {
if (!callback && typeof whiteList === 'function') {
callback = whiteList;
whiteList = [];
}

let resolvedPath = this._resolve(relOrAbsPath, _.concat(this.fileWhitelist, whiteList));

if (!resolvedPath) {
return callback(new Error(PPERM_ERR));
}

return callback(null, resolvedPath);
};

/**
* Synchronous path resolver function
*
* @param {String} relOrAbsPath - Relative or absolute path to resolve
* @param {Array} [whiteList] - A optional list of additional absolute path to whitelist
*
* @returns {String} The resolved path
*/
PostmanFs.prototype.resolvePathSync = function (relOrAbsPath, whiteList) {
// Resolve the path from the working directory
const resolvedPath = this._resolve(relOrAbsPath, _.concat(this.fileWhitelist, whiteList));

if (!resolvedPath) {
throw new Error(PPERM_ERR);
}

return resolvedPath;
};

// Attach all functions in fs to postman-fs
Object.getOwnPropertyNames(fs).map((prop) => {
if (typeof fs[prop] !== 'function') {
return;
}

PostmanFs.prototype[prop] = fs[prop];
});

// Override the required functions
PostmanFs.prototype.stat = function (path, callback) {
this.resolvePath(path, (err, resolvedPath) => {
if (err) {
return callback(err);
}

return this._fs.stat(resolvedPath, callback);
});
};

PostmanFs.prototype.createReadStream = function (path, options) {
try {
return this._fs.createReadStream(this.resolvePathSync(path), options);
}
catch (err) {
// Create a fake read steam that emits and error and
const ErrorReadStream = function () {
// Replicating behavior of fs module of disabling emitClose on destroy
Readable.call(this, { emitClose: false });

// Emit the error event with insure file access error
this.emit('error', new Error(PPERM_ERR));

// Options exists and disables autoClose then don't destroy
(options && !options.autoClose) || this.destroy();
};

util.inherits(ErrorReadStream, Readable);

return new ErrorReadStream();
}
};

module.exports = PostmanFs;

0 comments on commit 959dac2

Please sign in to comment.