feat: Created a mini nodeJS server with NewMan for testing without PostMan GUI.

This will mimic a run in a CD/CI environment or docker container.
This commit is contained in:
Simon Priet
2021-09-08 14:01:19 +02:00
parent 5fbd7c88fa
commit e69a613a37
5610 changed files with 740417 additions and 3 deletions

1662
node_modules/postman-collection/CHANGELOG.yaml generated vendored Normal file

File diff suppressed because it is too large Load Diff

176
node_modules/postman-collection/LICENSE.md generated vendored Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

61
node_modules/postman-collection/README.md generated vendored Normal file
View File

@@ -0,0 +1,61 @@
# Postman Collection SDK [![Build Status](https://travis-ci.com/postmanlabs/postman-collection.svg?branch=develop)](https://travis-ci.com/postmanlabs/postman-collection) [![codecov](https://codecov.io/gh/postmanlabs/postman-collection/branch/develop/graph/badge.svg)](https://codecov.io/gh/postmanlabs/postman-collection)
Postman Collection SDK is a NodeJS module that allows a developer to work with Postman Collections. Using this module a
developer can create collections, manipulate them and then export them in a format that the Postman Apps and Postman CLI
Runtimes (such as [Newman](https://github.com/postmanlabs/newman)) can consume.
A collection lets you group individual requests together. These requests can be further organized into folders to
accurately mirror your API. Requests can also store sample responses when saved in a collection. You can add metadata
like name and description too so that all the information that a developer needs to use your API is available easily.
To know more about Postman Collections, visit the
[collection documentation section on Postman Website](https://www.getpostman.com/collection).
> The new [Collection Format v2](http://blog.getpostman.com/2015/06/05/travelogue-of-postman-collection-format-v2/)
> builds a stronger foundation for improving your productivity while working with APIs. We want your feedback and iron
> out issues before this goes into the Postman Apps.
## Installing the SDK
Postman Collection SDK can be installed using NPM or directly from the git repository within your NodeJS projects. If
installing from NPM, the following command installs the SDK and saves in your `package.json`
```terminal
> npm install postman-collection --save
```
## Getting Started
In this example snippet we will get started by loading a collection from a file and output the same in console.
```javascript
var fs = require('fs'), // needed to read JSON file from disk
Collection = require('postman-collection').Collection,
myCollection;
// Load a collection to memory from a JSON file on disk (say, sample-collection.json)
myCollection = new Collection(JSON.parse(fs.readFileSync('sample-collection.json').toString()));
// log items at root level of the collection
console.log(myCollection.toJSON());
```
After loading the collection from file, one can do a lot more using the functions that are available in the SDK. To know
more about these functions, head over to
[Collection SDK Docs](http://www.postmanlabs.com/postman-collection).
## Postman Collection Schema
The collection schema outlines the JSON definition of data structure accepted by the constructor of each properties of
this SDK. In other words, this SDK provides JavaScript level object manipulation for the JSON structure defined by
Postman Collection Format in [http://schema.postman.com/](http://schema.postman.com/).
| Schema Version | Compatible SDK Versions |
|----------------|-------------------------|
| 1.0 | none |
| 2.0 | <3.0 |
| 2.1 | >= 3.0 |
Conceptually, a JSON input to the constructor of an SDK property should provide similar output when that property
instance's `.toJSON()` is called.

20
node_modules/postman-collection/index.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
/**!
* @license http://www.apache.org/licenses/LICENSE-2.0
*
* Copyright 2015 Postdot Technologies Pvt. Ltd.
*
* 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.
*/
/**
* @fileOverview This is the entry point to PostmanCollection modules. The structure of the module is defined here.
*/
module.exports = require('./lib/index');

View File

@@ -0,0 +1,84 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Url = require('./url').Url,
Certificate = require('./certificate').Certificate,
CertificateList;
_.inherit((
/**
* @constructor
* @extends {PropertyList}
*
* @param {Object} parent -
* @param {Array} list - The list of certificate representations
*
* @example <caption>Create a new CertificateList</caption>
* var CertificateList = require('postman-collection').CertificateList,
* certificateList = new CertificateList({}, [
* {
* name: 'my certificate for example.com',
* matches: ['https://example.com/*'],
* key: { src: '/path/to/key/file' },
* cert: { src: '/path/to/certificate/file' }
* },
* {
* name: 'my certificate for example2.com',
* matches: ['https://example2.com/*'],
* key: { src: '/path/to/key/file' },
* cert: { src: '/path/to/key/file' }
* }
* ]);
*/
CertificateList = function (parent, list) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
CertificateList.super_.call(this, Certificate, parent, list);
}), PropertyList);
_.assign(CertificateList.prototype, /** @lends CertificateList.prototype */ {
/**
* Matches the given url against the member certificates' allowed matches
* and returns the certificate that can be used for the url.
*
* @param {String} url The url to find the certificate for
* @returns {Certificate.definition=} The matched certificate
*/
resolveOne (url) {
// url must be either string or an instance of url.
if (!_.isString(url) && !Url.isUrl(url)) {
return;
}
// find a certificate that can be applied to the url
return this.find(function (certificate) {
return certificate.canApplyTo(url);
});
}
});
_.assign(CertificateList, /** @lends CertificateList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'CertificateList',
/**
* Checks if the given object is a CertificateList
*
* @param {*} obj -
* @returns {Boolean}
*/
isCertificateList: function (obj) {
return Boolean(obj) && ((obj instanceof CertificateList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', CertificateList._postman_propertyName));
}
});
module.exports = {
CertificateList
};

View File

@@ -0,0 +1,208 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyBase = require('./property-base').PropertyBase,
Url = require('./url').Url,
UrlMatchPatternList = require('../url-pattern/url-match-pattern-list').UrlMatchPatternList,
STRING = 'string',
HTTPS = 'https',
Certificate;
/**
* The following is the object representation accepted as param for the Certificate constructor.
* Also the type of the object returned when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
* Certificate instance.
*
* @typedef Certificate.definition
* @property {String} [name] A name for the certificate
* @property {Array} [matches] A list of match patterns
* @property {{ src: (String) }} [key] Object with path on the file system for private key file, as src
* @property {{ src: (String) }} [cert] Object with path on the file system for certificate file, as src
* @property {String} [passphrase] The passphrase for the certificate key
*
* @example <caption>JSON definition of an example certificate object</caption>
* {
* "name": "My certificate for example.com",
* "matches": ["https://example.com/*"],
* "key": { "src": "/path/to/key" },
* "cert": { "src": "/User/path/to/certificate" },
* "passphrase": "iampassphrase"
* }
*/
_.inherit((
/**
* A Certificate definition that represents the ssl certificate
* to be used for an url.
* Properties can then use the `.toObjectResolved` function to procure an object representation of the property with
* all the variable references replaced by corresponding values.
*
* @constructor
* @extends {Property}
*
* @param {Certificate.definition=} [options] Object with matches, key, cert and passphrase
*
* @example <caption> Create a new Certificate</caption>
*
* var Certificate = require('postman-collection').Certificate,
* certificate = new Certificate({
* name: 'Certificate for example.com',
* matches: ['example.com'],
* key: { src: '/User/path/to/certificate/key' },
* cert: { src: '/User/path/to/certificate' },
* passphrase: 'iampassphrase'
* });
*/
Certificate = function Certificate (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Certificate.super_.apply(this, arguments);
this.update(options);
}), Property);
_.assign(Certificate.prototype, /** @lends Certificate.prototype */ {
/**
* Ensure all object have id
*
* @private
*/
_postman_propertyRequiresId: true,
/**
* Updates the certificate with the given properties.
*
* @param {Certificate.definition=} [options] Object with matches, key, cert and passphrase
*/
update: function (options) {
// return early if options is empty or invalid
if (!_.isObject(options)) {
return;
}
_.mergeDefined(this, /** @lends Certificate.prototype */ {
/**
* Unique identifier
*
* @type {String}
*/
id: options.id,
/**
* Name for user reference
*
* @type {String}
*/
name: options.name,
/**
* List of match pattern
*
* @type {UrlMatchPatternList}
*/
matches: options.matches && new UrlMatchPatternList({}, options.matches),
/**
* Private Key
*
* @type {{ src: (string) }}
*/
key: _.isObject(options.key) ? options.key : { src: options.key },
/**
* Certificate
*
* @type {{ src: (string) }}
*/
cert: _.isObject(options.cert) ? options.cert : { src: options.cert },
/**
* PFX or PKCS12 Certificate
*
* @type {{ src: (string) }}
*/
pfx: _.isObject(options.pfx) ? options.pfx : { src: options.pfx },
/**
* passphrase
*
* @type {Object}
*/
passphrase: options.passphrase
});
},
/**
* Checks if the certificate can be applied to a given url
*
* @param {String|Url} url The url string for which the certificate is checked for match.
*/
canApplyTo: function (url) {
if (_.isEmpty(url)) {
return false;
}
// convert url strings to Url
(typeof url === STRING) && (url = new Url(url));
// this ensures we don't proceed any further for any protocol
// that is not https
if (url.protocol !== HTTPS) {
return false;
}
// test the input url against allowed matches
return this.matches.test(url);
},
/**
* Allows the serialization of a {@link Certificate}
*
* This is overridden, in order to ensure that certificate contents are not accidentally serialized,
* which can be a security risk.
*/
toJSON: function () {
var obj = PropertyBase.toJSON(this);
_.unset(obj, 'key.value');
_.unset(obj, 'cert.value');
_.unset(obj, 'pfx.value');
return obj;
}
});
_.assign(Certificate, /** @lends Certificate */ {
/**
* Defines the name of this property for internal use
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Certificate',
/**
* Specify the key to be used while indexing this object
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyIndexKey: 'id',
/**
* Checks if the given object is a Certificate
*
* @param {*} obj -
* @returns {Boolean}
*/
isCertificate: function (obj) {
return Boolean(obj) && ((obj instanceof Certificate) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Certificate._postman_propertyName));
}
});
module.exports = {
Certificate
};

View File

@@ -0,0 +1,242 @@
var _ = require('../util').lodash,
ItemGroup = require('./item-group').ItemGroup,
VariableList = require('./variable-list').VariableList,
Version = require('./version').Version,
Collection, // constructor
SCHEMA_URL = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json';
/**
* The following is the object structure accepted as constructor parameter while calling `new Collection(...)`. It is
* also the structure exported when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
* collection instance.
*
* @typedef Collection.definition
*
* @property {Object=} [info] The meta information regarding the collection is provided as the `info` object.
* @property {String=} [info.id] Every collection is identified by the unique value of this property. It is recommended
* that you maintain the same id since changing the id usually implies that is a different collection than it was
* originally.
* @property {String=} [info.name] A collection's friendly name is defined by this property. You would want to set this
* field to a value that would allow you to easily identify this collection among a bunch of other collections.
* @property {String=} [info.version] Postman allows you to version your collections as they grow, and this field holds
* the version number. While optional, it is recommended that you use this field to its fullest extent.
* @property {Array<(Item.definition|ItemGroup.definition)>} [item] Items are the basic unit for a Postman collection.
* You can think of them as corresponding to a single API endpoint. Each Item has one request and may have multiple API
* responses associated with it.
* @property {Variable.definition=} [variable] Collection variables allow you to define a set of variables,
* that are a *part of the collection*, as opposed to environments, which are separate entities.
* @property {RequestAuth.definition=} [auth] Collection auth allows you to define an authentication,
* that *applies to all items* in the collection.
* @property {Array<Event.definition>=} [event] Postman allows you to configure scripts to run when specific events
* occur.
* @property {String|Version.definition=} [version] Version of the collection expressed in [semver](http://semver.org/)
* format.
*
* @see {ItemGroup.definition} - Since `Collection` inherits {@link ItemGroup}, the properties supported by ItemGroup
* are applicable as well.
*
* @example <caption>JSON definition of an example collection</caption>
* {
* "info": {
* "name": "My Postman Collection",
* "version": "1.0.0"
* }
* "item": [{
* "request": "{{base-url}}/get"
* }],
* "variables": [{
* "id": "base-url",
* "value": "https://postman-echo.com"
* }]
* }
*/
_.inherit((
/**
* Create or load an instance of [Postman Collection](https://www.getpostman.com/docs/collections) as a JavaScript
* object that can be manipulated easily.
*
* A collection lets you group individual requests together. These requests can be further organized into folders to
* accurately mirror your API. Requests can also store sample responses when saved in a collection. You can add
* metadata like name and description too so that all the information that a developer needs to use your API is
* available easily.
*
* @constructor
* @extends {ItemGroup}
*
* @param {Collection.definition=} [definition] - Pass the initial definition of the collection (name, id, etc) as
* the `definition` parameter. The definition object is structured exactly as the collection format as defined in
* [https://www.schema.getpostman.com/](https://www.schema.getpostman.com/). This parameter is optional. That
* implies that you can create an empty instance of collection and add requests and other properties in order to
* build a new collection.
* @param {Array<Object>=} [environments] - The collection instance constructor accepts the second parameter as an
* array of environment objects. Environments objects store variable definitions that are inherited by
* {@link Collection#variables}. These environment variables are usually the ones that are exported from the Postman
* App to use them with different collections. Refer to Postman
* [documentation on environment variables](https://www.getpostman.com/docs/environments).
*
* @example <caption>Load a Collection JSON file from disk</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* pretty = function (obj) { // function to neatly log the collection object to console
* return require('util').inspect(obj, {colors: true});
* },
* Collection = require('postman-collection').Collection,
* myCollection;
*
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // log items at root level of the collection
* console.log(pretty(myCollection));
*
* @example <caption>Create a blank collection and write to file</caption>
* var fs = require('fs'),
* Collection = require('postman-collection').Collection,
* mycollection;
*
* myCollection = new Collection({
* info: {
* name: "my Collection"
* }
* });
*
* // log the collection to console to see its contents
* fs.writeFileSync('myCollection.postman_collection', JSON.stringify(myCollection, null, 2));
*/
Collection = function PostmanCollection (definition, environments) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Collection.super_.call(this, definition);
_.assign(this, /** @lends Collection.prototype */ {
/**
* The `variables` property holds a list of variables that are associated with a Collection. These variables
* are stored within a collection so that they can be re-used and replaced in rest of the collection. For
* example, if one has a variable named `port` with value `8080`, then one can write a request {@link Url}
* as `http://localhost:{{port}}/my/endpoint` and that will be replaced to form
* `http://localhost:8080/my/endpoint`. **Collection Variables** are like
* [environment variables](https://www.getpostman.com/docs/environments), but stored locally within a
* collection.
*
* @type {VariableList}
*
* @example <caption>Creating a collection with variables</caption>
* var fs = require('fs'),
* Collection = require('postman-collection').Collection,
* mycollection;
*
* // Create a new empty collection.
* myCollection = new Collection();
*
* // Add a variable to the collection
* myCollection.variables.add({
* id: 'apiBaseUrl',
* value: 'http://timeapi.org',
* type: 'string'
* });
*
* //Add a request that uses the variable that we just added.
* myCollection.items.add({
* id: 'utc-time-now',
* name: 'Get the current time in UTC',
* request: '{{apiBaseUrl}}/utc/now'
* });
*/
variables: new VariableList(this, definition && definition.variable, environments),
/**
* The `version` key in collection is used to express the version of the collection. It is useful in either
* tracking development iteration of an API server or the version of an API itself. It can also be used to
* represent the number of iterations of the collection as it is updated through its lifetime.
*
* Version is expressed in [semver](http://semver.org/) format.
*
* @type {Version}
* @optional
*
* @see {@link http://semver.org/}
*/
version: (definition && definition.info && definition.info.version) ?
new Version(definition.info.version) : undefined
});
}), ItemGroup);
_.assign(Collection.prototype, /** @lends Collection.prototype */ {
/**
* Using this function, one can sync the values of collection variables from a reference object.
*
* @param {Object} obj -
* @param {Boolean=} [track] -
*
* @returns {Object}
*/
syncVariablesFrom (obj, track) {
return this.variables.syncFromObject(obj, track);
},
/**
* Transfer the variables in this scope to an object
*
* @param {Object=} [obj] -
*
* @returns {Object}
*/
syncVariablesTo (obj) {
return this.variables.syncToObject(obj);
},
/**
* Convert the collection to JSON compatible plain object
*
* @returns {Object}
*/
toJSON () {
var json = ItemGroup.prototype.toJSON.apply(this);
// move ids and stuff from root level to `info` object
json.info = {
_postman_id: this.id,
name: this.name,
version: this.version,
schema: SCHEMA_URL
};
delete json.id;
delete json.name;
delete json.version;
if (_.has(json, 'description')) {
json.info.description = this.description;
delete json.description;
}
return json;
}
});
_.assign(Collection, /** @lends Collection */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Collection',
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isCollection: function (obj) {
return Boolean(obj) && ((obj instanceof Collection) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Collection._postman_propertyName));
}
});
module.exports = {
Collection
};

View File

@@ -0,0 +1,49 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Cookie = require('./cookie').Cookie,
CookieList;
_.inherit((
/**
* Contains a list of header elements
*
* @constructor
* @param {Object} parent -
* @param {Object[]} cookies -
* @extends {PropertyList}
*/
CookieList = function (parent, cookies) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
CookieList.super_.call(this, Cookie, parent, cookies);
}), PropertyList);
// _.assign(CookieList.prototype, /** @lends CookieList.prototype */ {
// });
_.assign(CookieList, /** @lends CookieList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'CookieList',
/**
* Checks if the given object is a CookieList
*
* @param {*} obj -
* @returns {Boolean}
*/
isCookieList: function (obj) {
return Boolean(obj) && ((obj instanceof CookieList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', CookieList._postman_propertyName));
}
});
module.exports = {
CookieList
};

View File

@@ -0,0 +1,426 @@
var _ = require('../util').lodash,
PropertyBase = require('./property-base').PropertyBase,
PropertyList = require('./property-list').PropertyList,
E = '',
EQ = '=',
PAIR_SPLIT_REGEX = /; */,
COOKIES_SEPARATOR = '; ',
/**
* Enum for all the Cookie attributes.
*
* @private
* @readonly
* @enum {string} CookieAttributes
*/
cookieAttributes = {
httponly: 'httpOnly',
secure: 'secure',
domain: 'domain',
path: 'path',
'max-age': 'maxAge',
session: 'session',
expires: 'expires'
},
Cookie;
/**
* The following is the object structure accepted as constructor parameter while calling `new Cookie(...)`. It is
* also the structure exported when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
* Cookie instance.
*
* @typedef Cookie.definition
*
* @property {String=} [key] The name of the cookie. Some call it the "name".
* @property {String=} [value] The value stored in the Cookie
* @property {String=} [expires] Expires sets an expiry date for when a cookie gets deleted. It should either be a
* date object or timestamp string of date.
* @property {Number=} [maxAge] Max-age sets the time in seconds for when a cookie will be deleted.
* @property {String=} [domain] Indicates the domain(s) for which the cookie should be sent.
* @property {String=} [path] Limits the scope of the cookie to a specified path, e.g: "/accounts"
* @property {Boolean=} [secure] A secure cookie will only be sent to the server when a request is made using SSL and
* the HTTPS protocol.
* The idea that the contents of the cookie are of high value and could be potentially damaging to transmit
* as clear text.
* @property {Boolean=} [httpOnly] The idea behind HTTP-only cookies is to instruct a browser that a cookie should never
* be accessible via JavaScript through the document.cookie property. This feature was designed as a security measure
* to help prevent cross-site scripting (XSS) attacks perpetrated by stealing cookies via JavaScript.
* @property {Boolean=} [hostOnly] Indicates that this cookie is only valid for the given domain (and not its parent or
* child domains.)
* @property {Boolean=} [session] Indicates whether this is a Session Cookie. (A transient cookie, which is deleted at
* the end of an HTTP session.)
* @property {Array=} [extensions] Any extra attributes that are extensions to the original Cookie specification can be
* specified here.
* @property {String} [extensions[].key] Name of the extension.
* @property {String} [extensions[].value] Value of the extension
*
* @example <caption>JSON definition of an example cookie</caption>
* {
* "key": "my-cookie-name",
* "expires": "1464769543832",
* // UNIX timestamp, in *milliseconds*
* "maxAge": "300",
* // In seconds. In this case, the Cookie is valid for 5 minutes
* "domain": "something.example.com",
* "path": "/",
* "secure": false,
* "httpOnly": true,
* "session": false,
* "value": "my-cookie-value",
* "extensions": [{
* "key": "Priority",
* "value": "HIGH"
* }]
* }
*/
_.inherit((
/**
* A Postman Cookie definition that comprehensively represents an HTTP Cookie.
*
* @constructor
* @extends {PropertyBase}
*
* @param {Cookie.definition} [options] Pass the initial definition of the Cookie.
* @example <caption>Create a new Cookie</caption>
* var Cookie = require('postman-collection').Cookie,
* myCookie = new Cookie({
* name: 'my-cookie-name',
* expires: '1464769543832', // UNIX timestamp, in *milliseconds*
* maxAge: '300', // In seconds. In this case, the Cookie is valid for 5 minutes
* domain: 'something.example.com',
* path: '/',
* secure: false,
* httpOnly: true,
* session: false,
* value: 'my-cookie-value',
* extensions: [{
* key: 'Priority',
* value: 'HIGH'
* }]
* });
*
* @example <caption>Parse a Cookie Header</caption>
* var Cookie = require('postman-collection').Cookie,
* rawHeader = 'myCookie=myValue;Path=/;Expires=Sun, 04-Feb-2018 14:18:27 GMT;Secure;HttpOnly;Priority=HIGH'
* myCookie = new Cookie(rawHeader);
*
* console.log(myCookie.toJSON());
*/
Cookie = function PostmanCookie (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Cookie.super_.call(this, options);
_.isString(options) && (options = Cookie.parse(options));
options && this.update(options);
}), PropertyBase);
_.assign(Cookie.prototype, /** @lends Cookie.prototype */ {
update (options) {
_.mergeDefined(this, /** @lends Cookie.prototype */ {
/**
* The name of the cookie.
*
* @type {String}
*/
name: _.choose(options.name, options.key),
/**
* Expires sets an expiry date for when a cookie gets deleted. It should either be a date object or
* timestamp string of date.
*
* @type {Date|String}
*
* @note
* The value for this option is a date in the format Wdy, DD-Mon-YYYY HH:MM:SS GMT such as
* "Sat, 02 May 2009 23:38:25 GMT". Without the expires option, a cookie has a lifespan of a single session.
* A session is defined as finished when the browser is shut down, so session cookies exist only while the
* browser remains open. If the expires option is set to a date that appears in the past, then the cookie is
* immediately deleted in browser.
*
* @todo Accept date object and convert stringified date (timestamp only) to date object
* @todo Consider using Infinity as a default
*/
expires: _.isString(options.expires) ? new Date(options.expires) : options.expires,
/**
* Max-age sets the time in seconds for when a cookie will be deleted.
*
* @type {Number}
*/
maxAge: _.has(options, 'maxAge') ? Number(options.maxAge) : undefined,
/**
* Indicates the domain(s) for which the cookie should be sent.
*
* @type {String}
*
* @note
* By default, domain is set to the host name of the page setting the cookie, so the cookie value is sent
* whenever a request is made to the same host name. The value set for the domain option must be part of the
* host name that is sending the Set-Cookie header. The SDK does not perform this check, but the underlying
* client that actually sends the request could do it automatically.
*/
domain: options.domain,
/**
* @type {String}
*
* @note
* On server, the default value for the path option is the path of the URL that sent the Set-Cookie header.
*/
path: options.path,
/**
* A secure cookie will only be sent to the server when a request is made using SSL and the HTTPS protocol.
* The idea that the contents of the cookie are of high value and could be potentially damaging to transmit
* as clear text.
*
* @type {Boolean}
*/
secure: _.has(options, 'secure') ? Boolean(options.secure) : undefined,
/**
* The idea behind HTTP-only cookies is to instruct a browser that a cookie should never be accessible via
* JavaScript through the document.cookie property. This feature was designed as a security measure to help
* prevent cross-site scripting (XSS) attacks perpetrated by stealing cookies via JavaScript.
*
* @type {Boolean}
*/
httpOnly: _.has(options, 'httpOnly') ? Boolean(options.httpOnly) : undefined,
/**
* @type {Boolean}
*/
hostOnly: _.has(options, 'hostOnly') ? Boolean(options.hostOnly) : undefined,
/**
* Indicates whether this is a Session Cookie.
*
* @type {Boolean}
*/
session: _.has(options, 'session') ? Boolean(options.session) : undefined,
/**
* @note The commonly held belief is that cookie values must be URL-encoded, but this is a fallacy even
* though it is the de facto implementation. The original specification indicates that only three types of
* characters must be encoded: semicolon, comma, and white space. The specification indicates that URL
* encoding may be used but stops short of requiring it. The RFC makes no mention of encoding whatsoever.
* Still, almost all implementations perform some sort of URL encoding on cookie values.
* @type {String}
*/
value: options.value ? _.ensureEncoded(options.value) : undefined,
/**
* Any extra parameters that are not strictly a part of the Cookie spec go here.
*
* @type {Array}
*/
extensions: options.extensions || undefined
});
},
/**
* Get the value of this cookie.
*
* @returns {String}
*/
valueOf () {
try {
return decodeURIComponent(this.value);
}
// handle malformed URI sequence
// refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Malformed_URI
catch (error) {
/* istanbul ignore next */
return this.value;
}
},
/**
* Converts the Cookie to a single Set-Cookie header string.
*
* @returns {String}
*/
toString () {
var str = Cookie.unparseSingle(this);
if (this.expires && this.expires instanceof Date) {
// check for valid date
if (!Number.isNaN(this.expires.getTime())) {
str += '; Expires=' + this.expires.toUTCString();
}
}
else if (this.expires) {
str += '; Expires=' + this.expires;
}
if (this.maxAge && this.maxAge !== Infinity) {
str += '; Max-Age=' + this.maxAge;
}
if (this.domain && !this.hostOnly) {
str += '; Domain=' + this.domain;
}
if (this.path) {
str += '; Path=' + this.path;
}
if (this.secure) {
str += '; Secure';
}
if (this.httpOnly) {
str += '; HttpOnly';
}
if (this.extensions) {
this.extensions.forEach(({ key, value }) => {
str += `; ${key}`;
str += value === true ? '' : `=${value}`;
});
}
return str;
}
});
_.assign(Cookie, /** @lends Cookie */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Cookie',
// define behaviour of this object when put in list
_postman_propertyIndexKey: 'name',
_postman_propertyIndexCaseInsensitive: true,
_postman_propertyAllowsMultipleValues: true,
/**
* Check whether an object is an instance of PostmanCookie.
*
* @param {*} obj -
* @returns {Boolean}
*/
isCookie: function (obj) {
return Boolean(obj) && ((obj instanceof Cookie) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Cookie._postman_propertyName));
},
/**
* Stringifies an Array or {@link PropertyList} of Cookies into a single string.
*
* @param {Cookie[]} cookies - List of cookie definition object
* @returns {String}
*/
unparse: function (cookies) {
if (!_.isArray(cookies) && !PropertyList.isPropertyList(cookies)) {
return E;
}
return cookies.map(Cookie.unparseSingle).join(COOKIES_SEPARATOR);
},
/**
* Unparses a single Cookie.
*
* @param {Cookie} cookie - Cookie definition object
* @returns {String}
*/
unparseSingle: function (cookie) {
if (!_.isObject(cookie)) { return E; }
var value = _.isNil(cookie.value) ? E : cookie.value;
// for the empty name, return just the value to match the browser behavior
if (!cookie.name) {
return value;
}
return cookie.name + EQ + value;
},
/**
* Cookie header parser
*
* @param {String} str -
* @returns {*} A plain cookie options object, use it to create a new Cookie
*/
parse: function (str) {
if (!_.isString(str)) {
return str;
}
var obj = {},
pairs = str.split(PAIR_SPLIT_REGEX),
nameval;
nameval = Cookie.splitParam(pairs.shift()); // The first kvp is the name and value
obj.key = nameval.key;
obj.value = nameval.value;
pairs.forEach(function (pair) {
var keyval = Cookie.splitParam(pair),
value = keyval.value,
keyLower = keyval.key.toLowerCase();
if (cookieAttributes[keyLower]) {
obj[cookieAttributes[keyLower]] = value;
}
else {
obj.extensions = obj.extensions || [];
obj.extensions.push(keyval);
}
});
// Handle the hostOnly flag
if (!obj.domain) {
obj.hostOnly = true;
}
return obj;
},
/**
* Converts the Cookie to a single Set-Cookie header string.
*
* @param {Cookie} cookie - Cookie definition object
* @returns {String}
*/
stringify: function (cookie) {
return Cookie.prototype.toString.call(cookie);
},
/**
* Splits a Cookie parameter into a key and a value
*
* @private
* @param {String} param -
* @returns {{key: *, value: (Boolean|*)}}
*/
splitParam: function (param) {
var split = param.split('='),
key, value;
key = split[0].trim();
value = _.isString(split[1]) ? split[1].trim() : true;
if (_.isString(value) && value[0] === '"') {
value = value.slice(1, -1);
}
return { key, value };
}
});
module.exports = {
Cookie
};

View File

@@ -0,0 +1,126 @@
var _ = require('../util').lodash,
E = '',
DEFAULT_MIMETYPE = 'text/plain',
Description;
/**
* @typedef Description.definition
* @property {String} content
* @property {String} type
*/
/**
* This is one of the properties that are (if provided) processed by all other properties. Any property can have an
* instance of `Description` property assigned to it with the key name `description` and it should be treated as
* something that "describes" the property within which it belongs. Usually this property is used to generate
* documentation and other contextual information for a property in a Collection.
*
* @constructor
*
* @param {Description.definition|String} [definition] The content of the description can be passed as a string when it
* is in `text/plain` format or otherwise be sent as part of an object adhering to the {@link Description.definition}
* structure having `content` and `type`.
*
* @example <caption>Add a description to an instance of Collection</caption>
* var SDK = require('postman-collection'),
* Collection = SDK.Collection,
* Description = SDK.Description,
* mycollection;
*
* // create a blank collection
* myCollection = new Collection();
* myCollection.description = new Description({
* content: '&lt;h1&gt;Hello World&lt;/h1&gt;&lt;p&gt;I am a Collection&lt;/p&gt;',
* type: 'text/html'
* });
*
* // alternatively, you could also use the `.describe` method of any property to set or update the description of the
* // property.
* myCollection.describe('Hey! This is a cool collection.');
*/
Description = function PostmanPropertyDescription (definition) {
// if the definition is a string, it implies that this is a get of URL
_.isString(definition) && (definition = {
content: definition,
type: DEFAULT_MIMETYPE
});
// populate the description
definition && this.update(definition);
};
_.assign(Description.prototype, /** @lends Description.prototype */ {
/**
* Updates the content of this description property.
*
* @param {String|Description.definition} content -
* @param {String=} [type] -
* @todo parse version of description
*/
update (content, type) {
_.isObject(content) && ((type = content.type), (content = content.content));
_.assign(this, /** @lends Description.prototype */ {
/**
* The raw content of the description
*
* @type {String}
*/
content: content,
/**
* The mime-type of the description.
*
* @type {String}
*/
type: type || DEFAULT_MIMETYPE
});
},
/**
* Returns stringified Description.
*
* @returns {String}
*/
toString () {
return this.content || E;
},
/**
* Creates a JSON representation of the Description (as a plain Javascript object).
*
* @returns {{content: *, type: *, version: (String|*)}}
*/
toJSON () {
return {
content: this.content,
type: this.type
};
}
});
_.assign(Description, /** @lends Description */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Description',
/**
* Checks whether a property is an instance of Description object.
*
* @param {*} obj -
* @returns {Boolean}
*/
isDescription: function (obj) {
return Boolean(obj) && ((obj instanceof Description) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Description._postman_propertyName));
}
});
module.exports = {
Description
};

View File

@@ -0,0 +1,94 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Event = require('./event').Event,
EventList;
_.inherit((
/**
* A type of {@link PropertyList}, EventList handles resolving events from parents. If an {@link ItemGroup} contains
* a set of events, each {@link Item} in that group will inherit those events from its parent, and so on.
*
* @constructor
* @param {Object} parent -
* @param {Object[]} populate -
* @extends {PropertyList}
*
* This is useful when we need to have a common test across all requests.
*/
EventList = function PostmanEventList (parent, populate) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
EventList.super_.call(this, Event, parent, populate);
}), PropertyList);
_.assign(EventList.prototype, /** @lends EventList.prototype */ {
/**
* Returns an array of listeners filtered by the listener name
*
* @note
* If one needs to access disabled events, use {@link PropertyList#all} or
* any other similar {@link PropertyList} method.
*
* @param {String} name -
* @returns {Array<Event>}
*/
listeners (name) {
var all;
// we first procure all matching events from this list
all = this.listenersOwn(name);
this.eachParent(function (parent) {
var parentEvents;
// we check that the parent is not immediate mother. then we check whether the non immediate mother has a
// valid `events` store and only if this store has events with specified listener, we push them to the
// array we are compiling for return
(parent !== this.__parent) && EventList.isEventList(parent.events) &&
(parentEvents = parent.events.listenersOwn(name)) && parentEvents.length &&
all.unshift.apply(all, parentEvents); // eslint-disable-line prefer-spread
}, this);
return all;
},
/**
* Returns all events with specific listeners only within this list. Refer to {@link EventList#listeners} for
* procuring all inherited events
*
* @param {string} name -
* @returns {Array<Event>}
*/
listenersOwn (name) {
return this.filter(function (event) {
return (!event.disabled && event.listen === name);
});
}
});
_.assign(EventList, /** @lends EventList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'EventList',
/**
* Checks if the given object is an EventList.
*
* @param {*} obj -
* @returns {Boolean}
*/
isEventList: function (obj) {
return Boolean(obj) && ((obj instanceof EventList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', EventList._postman_propertyName));
}
});
module.exports = {
EventList
};

View File

@@ -0,0 +1,95 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
Script = require('./script').Script,
Event;
/**
* @typedef Event.definition
* @property {String} listen The event-name that this script will be called for. Usually either "test" or "prerequest"
* @property {Script|String} script A {@link Script} instance that will be executed on this event. In case of a
* string, a new {@link Script} is created.
* @example <caption>Constructing an event</caption>
* var Event = require('postman-collection').Event,
* rawEvent = {
* listen: 'test',
* script: 'tests["response code is 401"] = responseCode.code === 401'
* },
* myEvent;
* myEvent = new Event(rawEvent);
*/
_.inherit((
/**
* A Postman event definition that refers to an event to be listened to and a script reference or definition to be
* executed.
*
* @constructor
* @extends {Property}
*
* @param {Event.definition} definition Pass the initial definition of the event as the options parameter.
*/
Event = function PostmanEvent (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Event.super_.call(this, definition);
// set initial values of this event
definition && this.update(definition);
}), Property);
_.assign(Event.prototype, /** @lends Event.prototype */ {
/**
* Update an event.
*
* @param {Event.definition} definition -
*/
update (definition) {
if (!definition) {
return;
}
var result,
script = definition.script;
if (Script.isScript(script)) {
result = script;
}
else if (_.isArray(script) || _.isString(script)) {
result = new Script({ exec: script });
}
else if (_.isObject(script)) {
result = new Script(script);
}
_.mergeDefined(this, /** @lends Event.prototype */ {
/**
* Name of the event that this instance is intended to listen to.
*
* @type {String}
*/
listen: _.isString(definition.listen) ? definition.listen.toLowerCase() : undefined,
/**
* The script that is to be executed when this event is triggered.
*
* @type {Script}
*/
script: result
});
}
});
_.assign(Event, /** @lends Event */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Event'
});
module.exports = {
Event
};

View File

@@ -0,0 +1,103 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyBase = require('./property-base').PropertyBase,
FormParam;
/**
* @typedef FormParam.definition
* @property {String} key The name ("key") of the form data parameter.
* @property {String} value The value of the parameter.
*/
_.inherit((
/**
* Represents a Form Data parameter, which can exist in request body.
*
* @constructor
* @param {FormParam.definition} options Pass the initial definition of the form data parameter.
*/
FormParam = function PostmanFormParam (options = {}) {
FormParam.super_.apply(this, arguments);
this.key = options.key || '';
this.value = options.value || '';
this.type = options.type;
this.src = options.src;
this.contentType = options.contentType;
}), Property);
_.assign(FormParam.prototype, /** @lends FormParam.prototype */ {
/**
* Converts the FormParameter to a single param string.
*
* @returns {String}
*/
toString () {
return this.key + '=' + this.value;
},
/**
* Returns the value of the form parameter (if any).
*
* @returns {*|String}
*/
valueOf () {
return this.value; // can be multiple types, so just return whatever we have instead of being too clever
},
/**
* Convert the form-param to JSON compatible plain object.
*
* @returns {Object}
*/
toJSON () {
var obj = PropertyBase.toJSON(this);
// remove value from file param because it is non-serializable ReadStream
if (obj.type === 'file') {
_.unset(obj, 'value');
}
return obj;
}
});
_.assign(FormParam, /** @lends FormParam */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'FormParam',
/**
* Declare the list index key, so that property lists of form parameters work correctly
*
* @type {String}
*/
_postman_propertyIndexKey: 'key',
/**
* Form params can have multiple values, so set this to true.
*
* @type {Boolean}
*/
_postman_propertyAllowsMultipleValues: true,
/**
* Parse a form data string into an array of objects, where each object contains a key and a value.
*
* @todo implement this, not implemented yet.
* @param formdata {String}
* @returns {Array}
*/
parse: _.noop
});
module.exports = {
FormParam
};

View File

@@ -0,0 +1,61 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Header = require('./header').Header,
PROP_NAME = '_postman_propertyName',
HeaderList;
_.inherit((
/**
* Contains a list of header elements
*
* @constructor
* @param {Object} parent -
* @param {Header[]} headers -
* @extends {PropertyList}
*/
HeaderList = function (parent, headers) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
HeaderList.super_.call(this, Header, parent, headers);
}), PropertyList);
_.assign(HeaderList.prototype, /** @lends HeaderList.prototype */ {
/**
* Gets size of a list of headers excluding standard header prefix.
*
* @returns {Number}
*/
contentSize () {
if (!this.count()) { return 0; }
return Header.unparse(this).length;
}
});
_.assign(HeaderList, /** @lends HeaderList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'HeaderList',
/**
* Checks if the given object is a HeaderList
*
* @param {*} obj -
* @returns {Boolean}
*/
isHeaderList: function (obj) {
return Boolean(obj) && ((obj instanceof HeaderList) ||
_.inSuperChain(obj.constructor, PROP_NAME, HeaderList._postman_propertyName));
}
});
module.exports = {
HeaderList
};

View File

@@ -0,0 +1,295 @@
var util = require('../util'),
_ = util.lodash,
E = '',
SPC = ' ',
CRLF = '\r\n',
HEADER_KV_SEPARATOR = ':',
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
Header;
/**
* @typedef Header.definition
* @property {String} key The Header name (e.g: 'Content-Type')
* @property {String} value The value of the header.
*
* @example <caption>Create a header</caption>
* var Header = require('postman-collection').Header,
* header = new Header({
* key: 'Content-Type',
* value: 'application/xml'
* });
*
* console.log(header.toString()) // prints the string representation of the Header.
*/
_.inherit((
/**
* Represents an HTTP header, for requests or for responses.
*
* @constructor
* @extends {Property}
*
* @param {Header.definition|String} options - Pass the header definition as an object or the value of the header.
* If the value is passed as a string, it should either be in `name:value` format or the second "name" parameter
* should be used to pass the name as string
* @param {String} [name] - optional override the header name or use when the first parameter is the header value as
* string.
*
* @example <caption>Parse a string of headers into an array of Header objects</caption>
* var Header = require('postman-collection').Header,
* headerString = 'Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n';
*
* var rawHeaders = Header.parse(headerString);
* console.log(rawHeaders); // [{ 'Content-Type': 'application/json', 'User-Agent': 'MyClientLibrary/2.0' }]
*
* var headers = rawHeaders.map(function (h) {
* return new Header(h);
* });
*
* function assert(condition, message) {
* if (!condition) {
* message = message || "Assertion failed";
* if (typeof Error !== "undefined") {
* throw new Error(message);
* }
* throw message; //fallback
* }
* else {
* console.log("Assertion passed");
* }
* }
*
* assert(headerString.trim() === Header.unparse(headers).trim());
*/
Header = function PostmanHeader (options, name) {
if (_.isString(options)) {
options = _.isString(name) ? { key: name, value: options } : Header.parseSingle(options);
}
// this constructor is intended to inherit and as such the super constructor is required to be executed
Header.super_.apply(this, arguments);
this.update(options);
}), Property);
_.assign(Header.prototype, /** @lends Header.prototype */ {
/**
* Converts the header to a single header string.
*
* @returns {String}
*/
toString () {
return this.key + ': ' + this.value;
},
/**
* Return the value of this header.
*
* @returns {String}
*/
valueOf () {
return this.value;
},
/**
* Assigns the given properties to the Header
*
* @param {Object} options -
* @todo check for allowed characters in header key-value or store encoded.
*/
update (options) {
/**
* The header Key
*
* @type {String}
* @todo avoid headers with falsy key.
*/
this.key = _.get(options, 'key') || E;
/**
* The header value
*
* @type {String}
*/
this.value = _.get(options, 'value', E);
/**
* Indicates whether the header was added by internal SDK operations, such as authorizing a request.
*
* @type {*|boolean}
*/
_.has(options, 'system') && (this.system = options.system);
/**
* Indicates whether the header should be .
*
* @type {*|boolean}
* @todo figure out whether this should be in property.js
*/
_.has(options, 'disabled') && (this.disabled = options.disabled);
}
});
_.assign(Header, /** @lends Header */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Header',
/**
* Specify the key to be used while indexing this object
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyIndexKey: 'key',
/**
* Specifies whether the index lookup of this property, when in a list is case insensitive or not
*
* @private
* @readOnly
* @type {boolean}
*/
_postman_propertyIndexCaseInsensitive: true,
/**
* Since each header may have multiple possible values, this is set to true.
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyAllowsMultipleValues: true,
/**
* Parses a multi line header string into an array of {@link Header.definition}.
*
* @param {String} headerString -
* @returns {Array}
*/
parse: function (headerString) {
var headers = [],
regexes = {
header: /^(\S+):(.*)$/gm,
fold: /\r\n([ \t])/g,
trim: /^\s*(.*\S)?\s*$/ // eslint-disable-line security/detect-unsafe-regex
},
match = regexes.header.exec(headerString);
headerString = headerString.toString().replace(regexes.fold, '$1');
while (match) {
headers.push({
key: match[1],
value: match[2].replace(regexes.trim, '$1')
});
match = regexes.header.exec(headerString);
}
return headers;
},
/**
* Parses a single Header.
*
* @param {String} header -
* @returns {{key: String, value: String}}
*/
parseSingle: function (header) {
if (!_.isString(header)) { return { key: E, value: E }; }
var index = header.indexOf(HEADER_KV_SEPARATOR),
key,
value;
(index < 0) && (index = header.length);
key = header.substr(0, index);
value = header.substr(index + 1);
return {
key: _.trim(key),
value: _.trim(value)
};
},
/**
* Stringifies an Array or {@link PropertyList} of Headers into a single string.
*
* @note Disabled headers are excluded.
*
* @param {Array|PropertyList<Header>} headers -
* @param {String=} [separator='\r\n'] - Specify a string for separating each header
* @returns {String}
*/
unparse: function (headers, separator = CRLF) {
if (!_.isArray(headers) && !PropertyList.isPropertyList(headers)) {
return E;
}
return headers.reduce(function (acc, header) {
if (header && !header.disabled) {
acc += Header.unparseSingle(header) + separator;
}
return acc;
}, E);
},
/**
* Unparses a single Header.
*
* @param {String} header -
* @returns {String}
*/
unparseSingle: function (header) {
if (!_.isObject(header)) { return E; }
return header.key + HEADER_KV_SEPARATOR + SPC + header.value;
},
/**
* Check whether an object is an instance of PostmanHeader.
*
* @param {*} obj -
* @returns {Boolean}
*/
isHeader: function (obj) {
return Boolean(obj) && ((obj instanceof Header) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Header._postman_propertyName));
},
/* eslint-disable jsdoc/check-param-names */
/**
* Create a new header instance
*
* @param {Header.definition|String} [value] - Pass the header definition as an object or the value of the header.
* If the value is passed as a string, it should either be in `name:value` format or the second "name" parameter
* should be used to pass the name as string
* @param {String} [name] - optional override the header name or use when the first parameter is the header value as
* string.
* @returns {Header}
*/
create: function () {
var args = Array.prototype.slice.call(arguments);
args.unshift(Header);
return new (Header.bind.apply(Header, args))(); // eslint-disable-line prefer-spread
}
/* eslint-enable jsdoc/check-param-names */
});
module.exports = {
Header
};

View File

@@ -0,0 +1,341 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
EventList = require('./event-list').EventList,
Item = require('./item').Item,
Request = require('./request').Request,
RequestAuth = require('./request-auth').RequestAuth,
ItemGroup,
/**
* @private
* @type {String}
*/
OBJECT = 'object';
/**
* The following defines the object (or JSON) structure that one can pass to the ItemGroup while creating a new
* ItemGroup instance. This is also the object structure returned when `.toJSON()` is called on an ItemGroup instance.
*
* @typedef ItemGroup.definition
* @property {Array<ItemGroup.definition|Item.definition>=} [item]
* @property {RequestAuth.definition=} [auth]
* @property {Array<Event.definition>=} [event]
*
* @example
* {
* "name": "Echo Get Requests",
* "id": "echo-get-requests",
* "item": [{
* "request": "https://postman-echo.com/get"
* }, {
* "request": "https://postman-echo.com/headers"
* }],
* "auth": {
* "type": "basic",
* "basic": {
* "username": "jean",
* "password": "{{somethingsecret}}"
* }
* },
* "event": [{
* "listen": "prerequest",
* "script": {
* "type": "text/javascript",
* "exec": "console.log(new Date())"
* }
* }]
* }
*/
_.inherit((
/**
* An ItemGroup represents a composite list of {@link Item} or ItemGroup. In terms of Postman App, ItemGroup
* represents a "Folder". This allows one to group Items into subsets that can have their own meaning. An
* ItemGroup also allows one to define a subset of common properties to be applied to each Item within it. For
* example, a `test` event defined on an ItemGroup is executed while testing any Item that belongs to that group.
* Similarly, ItemGroups can have a common {@RequestAuth} defined so that every {@link Request}, when processed,
* requires to be authenticated using the `auth` defined in the group.
*
* Essentially, {@link Collection} too is a special type of ItemGroup ;-).
*
* @constructor
* @extends {Property}
*
* @param {ItemGroup.definition=} [definition] While creating a new instance of ItemGroup, one can provide the
* initial configuration of the item group with the requests it contains, the authentication applied to all
* requests, events that the requests responds to, etc.
*
* @example <caption>Add a new ItemGroup to a collection instance</caption>
* var Collection = require('postman-collection').Collection,
* ItemGroup = require('postman-collection').ItemGroup,
* myCollection;
*
* myCollection = new Collection(); // create an empty collection
* myCollection.items.add(new ItemGroup({ // add a folder called "blank folder"
* "name": "This is a blank folder"
* }));
*/
ItemGroup = function PostmanItemGroup (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
ItemGroup.super_.apply(this, arguments);
_.mergeDefined(this, /** @lends ItemGroup.prototype */ {
/**
* This is a {@link PropertyList} that holds the list of {@link Item}s or {@link ItemGroup}s belonging to a
* {@link Collection} or to an {@link ItemGroup}. Operation on an individual item in this list can be
* performed using various functions available to a {@link PropertyList}.
*
* @type {PropertyList<(Item|ItemGroup)>}
*
* @example <caption>Fetch empty ItemGroups in a list loaded from a file</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* Collection = require('postman-collection').Collection,
* myCollection,
* emptyGroups;
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // Filter items in Collection root that is an empty ItemGroup
* emptyGroups = myCollection.items.filter(function (item) {
* return item && item.items && (item.items.count() === 0);
* });
*
* // Log the emptyGroups array to check it's contents
* console.log(emptyGroups);
*/
items: new PropertyList(ItemGroup._createNewGroupOrItem, this, definition && definition.item),
/**
* One can define the default authentication method required for every item that belongs to this list.
* Individual {@link Request}s can override this in their own definitions. More on how to define an
* authentication method is outlined in the {@link RequestAuth} property.
*
* @type {RequestAuth}
*
* @example <caption>Define an entire ItemGroup (folder) or Collection to follow Basic Auth</caption>
* var fs = require('fs'),
* Collection = require('postman-collection').Collection,
* RequestAuth = require('postman-collection').RequestAuth,
* mycollection;
*
* // Create a collection having two requests
* myCollection = new Collection();
* myCollection.items.add([
* { name: 'GET Request', request: 'https://postman-echo.com/get?auth=basic' },
* { name: 'PUT Request', request: 'https://postman-echo.com/put?auth=basic' }
* ]);
*
* // Add basic auth to the Collection, to be applied on all requests.
* myCollection.auth = new RequestAuth({
* type: 'basic',
* username: 'postman',
* password: 'password'
* });
*/
// auth is a special case, empty RequestAuth should not be created for falsy values
// to allow inheritance from parent
auth: definition && definition.auth ? new RequestAuth(definition.auth) : undefined,
/**
* In this list, one can define the {@link Script}s to be executed when an event is triggered. Events are
* triggered before certain actions are taken on a Collection, Request, etc. For example, executing a
* request causes the `prerequest` and the `test` events to be triggered.
*
* @type {EventList}
* @memberOf Collection.prototype
*
* @example <caption>Executing a common test script for all requests in a collection</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* Collection = require('postman-collection').Collection,
* myCollection;
*
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // Add an event listener to the collection that listens to the `test` event.
* myCollection.events.add({
* listen: 'test',
* script: {
* exec: 'tests["Status code is 200"] = (responseCode.code === 200)'
* }
* });
*/
events: new EventList(this, definition && definition.event),
/**
* Set of configurations used to alter the usual behavior of sending the request.
*
* @type {Object}
* @property {Boolean} disableBodyPruning Disable body pruning for request methods like GET, HEAD etc.
*/
protocolProfileBehavior: definition && typeof definition.protocolProfileBehavior === OBJECT ?
definition.protocolProfileBehavior : undefined
});
}), Property);
_.assign(ItemGroup.prototype, /** @lends ItemGroup.prototype */ {
/**
* Defines that this property requires an ID field
*
* @private
* @readonly
*/
_postman_propertyRequiresId: true,
/**
* Calls the callback for each item belonging to itself. If any ItemGroups are encountered,
* they will call the callback on their own Items.
*
* @private
* @param {Function} callback -
*/
forEachItem: function forEachItem (callback) {
this.items.each(function (item) {
return ItemGroup.isItemGroup(item) ? item.forEachItem(callback) : callback(item, this);
}, this);
},
/**
* Calls the callback for each itemgroup belonging to itself. All ItemGroups encountered will also,
* call the callback on their own ItemGroups
*
* @private
* @param {Function} callback -
*/
forEachItemGroup: function forEachItemGroup (callback) {
this.items.each(function (item) {
if (ItemGroup.isItemGroup(item)) {
item.forEachItemGroup(callback);
callback(item, this); // eslint-disable-line callback-return
}
}, this);
},
/**
* Finds the first item with the given name or id in the current ItemGroup.
*
* @param {String} idOrName -
*/
oneDeep: function (idOrName) {
if (!_.isString(idOrName)) { return; }
var item;
this.items.each(function (eachItem) {
if (eachItem.id === idOrName || eachItem.name === idOrName) {
item = eachItem;
return false; // we found something, so bail out of the for loop.
}
if (ItemGroup.isItemGroup(eachItem)) {
item = eachItem.oneDeep(idOrName);
return !item; // bail out of the for loop if we found anything
}
});
return item;
},
/**
* Fetches protocol profile behavior for the current ItemGroup
*
* @private
* @returns {Object}
*
* @note This will not inherit protocol profile behaviors from parent,
* use `getProtocolProfileBehaviorResolved` to achieve that behavior.
*/
getProtocolProfileBehavior: Item.prototype.getProtocolProfileBehavior,
/**
* Fetches protocol profile behavior applicable for the current ItemGroup,
* inherited from parent ItemGroups(s).
*
* @private
* @returns {Object}
*/
getProtocolProfileBehaviorResolved: Item.prototype.getProtocolProfileBehaviorResolved,
/**
* Set or update protocol profile behavior for the current ItemGroup.
*
* @example <caption> Set or update protocol profile behavior </caption>
* itemGroup.setProtocolProfileBehavior('strictSSL', false);
*
* @private
* @param {String} key - protocol profile behavior name
* @param {*} value - protocol profile behavior value
* @returns {ItemGroup}
*/
setProtocolProfileBehavior: Item.prototype.setProtocolProfileBehavior,
/**
* Unset or delete protocol profile behavior for the current ItemGroup.
*
* @example <caption> Unset protocol profile behavior </caption>
* itemGroup.unsetProtocolProfileBehavior('strictSSL');
*
* @private
* @param {String} key - protocol profile behavior name to unset
* @returns {ItemGroup}
*/
unsetProtocolProfileBehavior: Item.prototype.unsetProtocolProfileBehavior,
/**
* Sets authentication method for all the items within this group
*
* @param {?String|RequestAuth.definition} type
* @param {VariableList=} [options]
*
* @note This function was previously (in v2 of SDK) used to clone request and populate headers. Now it is used to
* only set auth information to request
*/
authorizeRequestsUsing: Request.prototype.authorizeUsing
});
_.assign(ItemGroup, /** @lends ItemGroup */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'ItemGroup',
/**
* Iterator function to update an itemgroup's item array with appropriate objects from definition.
*
* @private
* @this {ItemGroup}
* @param {Object} item - the definition of an item or group
* @returns {ItemGroup|Item}
* @note
* This function is intended to be used in scope of an instance of a {@link ItemGroup).
*/
_createNewGroupOrItem: function (item) {
if (Item.isItem(item) || ItemGroup.isItemGroup(item)) { return item; }
return item && item.item ? new ItemGroup(item) : new Item(item);
},
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isItemGroup: function (obj) {
return Boolean(obj) && ((obj instanceof ItemGroup) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', ItemGroup._postman_propertyName));
}
});
module.exports = {
ItemGroup
};

359
node_modules/postman-collection/lib/collection/item.js generated vendored Normal file
View File

@@ -0,0 +1,359 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
EventList = require('./event-list').EventList,
Request = require('./request').Request,
RequestAuth = require('./request-auth').RequestAuth,
Response = require('./response').Response,
Item,
/**
* @private
* @type {String}
*/
OBJECT = 'object',
/**
* @private
* @type {String}
*/
STRING = 'string',
/**
* Extracts `auth` from an entity. Checks if `auth` is present and it is not falsy type.
*
* @private
*
* @param {Object} [entity] -
*/
extractAuth = function (entity) {
var auth;
return (entity && (auth = entity.auth) && RequestAuth.isValidType(auth.type)) ? auth : undefined;
},
/**
* Extracts `protocolProfileBehavior` from an entity.
*
* @private
*
* @param {Item|ItemGroup} entity -
* @returns {Object}
*/
extractProtocolProfileBehavior = function (entity) {
var protocolProfileBehavior = entity && entity.protocolProfileBehavior;
return typeof protocolProfileBehavior === OBJECT ? protocolProfileBehavior : {};
};
/**
* The following defines the object (or JSON) structure that one can pass to the Item while creating a new Item
* instance. This is also the object structure returned when `.toJSON()` is called on an Item instance.
*
* @typedef Item.definition
*
* @property {Request.definition=} [request] A request represents an HTTP request. If a string, the string is assumed to
* be the request URL and the method is assumed to be 'GET'.
* @property {Array<Response.definition>=} [responses] Sample responses for this request can be stored along with the
* item definition.
* @property {Array<Event.definition>=} [events] Postman allows you to configure scripts to run when specific events
* occur. These scripts are stored here, and can be referenced in the collection by their id.
*
* @example
* {
* "name": "Get Headers from Echo",
* "id": "my-request-1",
* "description": "Makes a GET call to echo service and returns the client headers that were sent",
*
* "request": {
* "url": "https://postman-echo.com/headers",
* "method": "GET"
* }
* }
*
* @todo add response and event to example
*/
_.inherit((
/**
* A Postman Collection Item that holds your request definition, responses and other stuff. An Item essentially is
* a HTTP request definition along with the sample responses and test scripts clubbed together. One or more of these
* items can be grouped together and placed in an {@link ItemGroup} and as such forms a {@link Collection} of
* requests.
*
* @constructor
* @extends {Property}
*
* @param {Item.definition=} [definition] While creating a new instance of Item, one can provide the initial
* configuration of the item with the the request it sends, the expected sample responses, tests, etc
*
* @example <caption>Add a new Item to a folder in a collection instance</caption>
* var Collection = require('postman-collection').Collection,
* Item = require('postman-collection').Item,
* myCollection;
*
* myCollection = new Collection({
* "item": [{
* "id": "my-folder-1",
* "name": "The solo folder in this collection",
* "item": [] // blank array indicates this is a folder
* }]
* }); // create a collection with an empty folder
* // add a request to "my-folder-1" that sends a GET request
* myCollection.items.one("my-folder-1").items.add(new Item({
* "name": "Send a GET request",
* "id": "my-get-request",
* "request": {
* "url": "https://postman-echo.com/get",
* "method": "GET"
* }
* }));
*/
Item = function PostmanItem (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Item.super_.apply(this, arguments);
_.mergeDefined(this, /** @lends Item.prototype */ {
/**
* The instance of the {@link Request} object inside an Item defines the HTTP request that is supposed to be
* sent. It further contains the request method, url, request body, etc.
*
* @type {Request}
*/
request: definition && (new Request(definition.request)),
/**
* An Item also contains a list of sample responses that is expected when the request defined in the item is
* executed. The sample responses are useful in elaborating API usage and is also useful for other
* integrations that use the sample responses to do something - say a mock service.
*
* @type {PropertyList<Response>}
*/
responses: new PropertyList(Response, this, definition && definition.response),
/**
* Events are a set of of {@link Script}s that are executed when certain activities are triggered on an
* Item. For example, on defining an event that listens to the "test" event, would cause the associated
* script of the event to be executed when the test runs.
*
* @type {EventList}
*
* @example <caption>Add a script to be executed on "prerequest" event</caption>
* var Collection = require('postman-collection').Collection,
* Item = require('postman-collection').Item,
* myCollection;
*
* myCollection = new Collection({
* "item": [{
* "name": "Send a GET request",
* "id": "my-get-request",
* "request": {
* "url": "https://postman-echo.com/get",
* "method": "GET"
* }
* }]
* }); // create a collection with one request
*
* // add a pre-request script to the event list
* myCollection.items.one('my-get-request').events.add({
* "listen": "prerequest",
* "script": {
* "type": "text/javascript",
* "exec": "console.log(new Date())"
* }
* });
*/
events: new EventList(this, definition && definition.event),
/**
* Set of configurations used to alter the usual behavior of sending the request.
*
* @type {Object}
*/
protocolProfileBehavior: definition && typeof definition.protocolProfileBehavior === OBJECT ?
definition.protocolProfileBehavior : undefined
});
}), Property);
_.assign(Item.prototype, /** @lends Item.prototype */ {
/**
* Defines whether this property instances requires an id
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyRequiresId: true,
/**
* Fetches applicable AuthType from the current item.
*
* @returns {RequestAuth}
*
* @note Since v3.0 release, this returns the entire auth RequestAuth, instead of just the parameters
*
* @todo Deprecate this and use getAuthResolved instead
*/
getAuth: function () {
var requestAuth;
// find auth on request, if not found or empty auth, lookup in the parents
// eslint-disable-next-line no-cond-assign
return (requestAuth = extractAuth(this.request)) ? requestAuth : this.findInParents('auth', extractAuth);
},
/**
* Fetches protocol profile behavior for the current Item
*
* @private
* @returns {Object}
*
* @note This will not inherit protocol profile behaviors from parent,
* use `getProtocolProfileBehaviorResolved` to achieve that behavior.
*/
getProtocolProfileBehavior: function () {
return extractProtocolProfileBehavior(this);
},
/**
* Fetches protocol profile behavior applicable for the current Item,
* inherited from parent ItemGroup(s).
*
* @private
* @returns {Object}
*/
getProtocolProfileBehaviorResolved: function () {
var protocolProfileBehavior = extractProtocolProfileBehavior(this);
// inherit protocolProfileBehavior from ItemGroup(s)
this.forEachParent({ withRoot: true }, function (entity) {
protocolProfileBehavior = {
...extractProtocolProfileBehavior(entity),
...protocolProfileBehavior
};
});
return protocolProfileBehavior;
},
/**
* Set or update protocol profile behavior for the current Item.
*
* @example <caption> Set or update protocol profile behavior </caption>
* item.setProtocolProfileBehavior('strictSSL', false);
*
* @private
* @param {String} key - protocol profile behavior name
* @param {*} value - protocol profile behavior value
* @returns {Item}
*/
setProtocolProfileBehavior: function (key, value) {
// bail out if key is non-string
if (typeof key !== STRING) { return this; }
!this.protocolProfileBehavior && (this.protocolProfileBehavior = {});
this.protocolProfileBehavior[key] = value;
return this;
},
/**
* Unset or delete protocol profile behavior for the current Item.
*
* @example <caption> Unset protocol profile behavior </caption>
* item.unsetProtocolProfileBehavior('strictSSL');
*
* @private
* @param {String} key - protocol profile behavior name to unset
* @returns {Item}
*/
unsetProtocolProfileBehavior: function (key) {
// bail out if property protocolProfileBehavior is not set or key is non-string
if (!(typeof this.protocolProfileBehavior === OBJECT && typeof key === STRING)) {
return this;
}
if (_.has(this.protocolProfileBehavior, key)) {
delete this.protocolProfileBehavior[key];
}
return this;
},
/**
* Returns {@link Event}s corresponding to a particular event name. If no name is given, returns all events. This
* is useful when you want to trigger all associated scripts for an event.
*
* @param {String} name - one of the available event types such as `test`, `prerequest`, `postrequest`, etc.
* @returns {Array<Event>}
*
* @example <caption>Get all events for an item and evaluate their scripts</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* Collection = require('postman-collection').Collection,
* myCollection;
*
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // assuming the collection has a request called "my-request-1" in root, we get it's test events
* myCollection.items.one("my-request-1").getEvents("test").forEach(function (event) {
* event.script && eval(event.script.toSource());
* });
*
* @todo decide appropriate verb names based on the fact that it gets events for a specific listener name
* @draft
*/
getEvents: function (name) {
if (!name) {
return this.events.all(); // return all events if name is not provided.
}
return this.events.filter(function (ev) {
return ev.listen === name;
});
},
/**
* Sets authentication method for the request within this item
*
* @param {?String|RequestAuth.definition} type -
* @param {VariableList=} [options] -
*
* @note This function was previously (in v2 of SDK) used to clone request and populate headers. Now it is used to
* only set auth information to request
*/
authorizeRequestUsing: function (type, options) {
if (!this.request) { this.request = new Request(); } // worst case
return this.request.authorizeUsing(type, options);
}
});
_.assign(Item, /** @lends Item */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Item',
/**
* Check whether an object is an instance of PostmanItem.
*
* @param {*} obj -
* @returns {Boolean}
*/
isItem: function (obj) {
return Boolean(obj) && ((obj instanceof Item) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Item._postman_propertyName));
}
});
module.exports = {
Item
};

View File

@@ -0,0 +1,243 @@
var _ = require('../util').lodash,
PropertyBase = require('./property-base').PropertyBase,
/**
* Primitive mutation types.
*
* @private
* @constant
* @type {Object}
*/
PRIMITIVE_MUTATIONS = {
SET: 'set',
UNSET: 'unset'
},
/**
* Detects if the mutation is a primitive mutation type. A primitive mutation is the simplified mutation structure.
*
* @private
* @param {MutationTracker.mutation} mutation -
* @returns {Boolean}
*/
isPrimitiveMutation = function (mutation) {
return mutation && mutation.length <= 2;
},
/**
* Applies a single mutation on a target.
*
* @private
* @param {*} target -
* @param {MutationTracker.mutation} mutation -
*/
applyMutation = function applyMutation (target, mutation) {
// only `set` and `unset` instructions are supported
// for non primitive mutations, the instruction would have to be extracted from mutation
/* istanbul ignore if */
if (!isPrimitiveMutation(mutation)) {
return;
}
// extract instruction from the mutation
var operation = mutation.length > 1 ? PRIMITIVE_MUTATIONS.SET : PRIMITIVE_MUTATIONS.UNSET;
// now hand over applying mutation to the target
target.applyMutation(operation, ...mutation);
},
MutationTracker;
/**
* A JSON representation of a mutation on an object. Here objects mean instances of postman-collection classes.
* This captures the instruction and the parameters of the instruction so that it can be replayed on a different object.
* Mutations can be any change on an object. For example setting a key or unsetting a key.
*
* For example, the mutation to set `name` on an object to 'Bruce Wayne' would look like ['name', 'Bruce Wayne']. Where
* the first item is the key path and second item is the value. To add a property `punchLine` to the object it would be
* the same as updating the property i.e. ['punchLine', 'I\'m Batman']. To remove a property `age` the mutation would
* look like ['age'].
*
* This format of representing changes is derived from
* {@link http://json-delta.readthedocs.io/en/latest/philosophy.html}.
*
* The `set` and `unset` are primitive instructions and can be derived from the mutation without explicitly stating the
* instruction. For more complex mutation the instruction would have to be explicitly stated.
*
* @typedef {Array} MutationTracker.mutation
*/
/**
* A JSON representation of the MutationTracker.
*
* @typedef MutationTracker.definition
*
* @property {Array} stream contains the stream mutations tracked
* @property {Object} compacted contains a compacted version of the mutations
* @property {Boolean} [autoCompact=false] when set to true, all new mutations would be compacted immediately
*/
_.inherit((
/**
* A MutationTracker allows to record mutations on any of object and store them. This stored mutations can be
* transported for reporting or to replay on similar objects.
*
* @constructor
* @extends {PropertyBase}
*
* @param {MutationTracker.definition} definition serialized mutation tracker
*/
MutationTracker = function MutationTracker (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
MutationTracker.super_.call(this, definition);
definition = definition || {};
// initialize options
this.autoCompact = Boolean(definition.autoCompact);
// restore mutations
this.stream = Array.isArray(definition.stream) ? definition.stream : [];
this.compacted = _.isPlainObject(definition.compacted) ? definition.compacted : {};
}), PropertyBase);
_.assign(MutationTracker.prototype, /** @lends MutationTracker.prototype */ {
/**
* Records a new mutation.
*
* @private
* @param {MutationTracker.mutation} mutation -
*/
addMutation (mutation) {
// bail out for empty or unsupported mutations
if (!(mutation && isPrimitiveMutation(mutation))) {
return;
}
// if autoCompact is set, we need to compact while adding
if (this.autoCompact) {
this.addAndCompact(mutation);
return;
}
// otherwise just push to the stream of mutations
this.stream.push(mutation);
},
/**
* Records a mutation compacting existing mutations for the same key path.
*
* @private
* @param {MutationTracker.mutation} mutation -
*/
addAndCompact (mutation) {
// for `set` and `unset` mutations the key to compact with is the `keyPath`
var key = mutation[0];
// convert `keyPath` to a string
key = Array.isArray(key) ? key.join('.') : key;
this.compacted[key] = mutation;
},
/**
* Track a mutation.
*
* @param {String} instruction the type of mutation
* @param {...*} payload mutation parameters
*/
track (instruction, ...payload) {
// invalid call
if (!(instruction && payload)) {
return;
}
// unknown instruction
if (!(instruction === PRIMITIVE_MUTATIONS.SET || instruction === PRIMITIVE_MUTATIONS.UNSET)) {
return;
}
// for primitive mutations the arguments form the mutation object
// if there is more complex mutation, we have to use a processor to create a mutation for the instruction
this.addMutation(payload);
},
/**
* Compacts the recorded mutations removing duplicate mutations that apply on the same key path.
*/
compact () {
// for each of the mutation, add to compacted list
this.stream.forEach(this.addAndCompact.bind(this));
// reset the `stream`, all the mutations are now recorded in the `compacted` storage
this.stream = [];
},
/**
* Returns the number of mutations tracked so far.
*
* @returns {Number}
*/
count () {
// the total count of mutations is the sum of
// mutations in the stream
var mutationCount = this.stream.length;
// and the compacted mutations
mutationCount += Object.keys(this.compacted).length;
return mutationCount;
},
/**
* Applies all the recorded mutations on a target object.
*
* @param {*} target Target to apply mutations. Must implement `applyMutation`.
*/
applyOn (target) {
if (!(target && target.applyMutation)) {
return;
}
var applyIndividualMutation = function applyIndividualMutation (mutation) {
applyMutation(target, mutation);
};
// mutations move from `stream` to `compacted`, so we apply the compacted mutations first
// to ensure FIFO of mutations
// apply the compacted mutations first
_.forEach(this.compacted, applyIndividualMutation);
// apply the mutations in the stream
_.forEach(this.stream, applyIndividualMutation);
}
});
_.assign(MutationTracker, /** @lends MutationTracker */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'MutationTracker',
/**
* Check whether an object is an instance of {@link MutationTracker}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isMutationTracker: function (obj) {
return Boolean(obj) && ((obj instanceof MutationTracker) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', MutationTracker._postman_propertyName));
}
});
module.exports = {
MutationTracker
};

View File

@@ -0,0 +1,231 @@
var _ = require('../util').lodash,
__PARENT = '__parent',
PropertyBase; // constructor
/**
* @typedef PropertyBase.definition
* @property {String|Description} [description]
*/
/**
* Base of all properties in Postman Collection. It defines the root for all standalone properties for postman
* collection.
*
* @constructor
* @param {PropertyBase.definition} definition -
*/
PropertyBase = function PropertyBase (definition) {
// In case definition object is missing, there is no point moving forward. Also if the definition is basic string
// we do not need to do anything with it.
if (!definition || typeof definition === 'string') { return; }
// call the meta extraction functions to create the object where all keys that are prefixed with underscore can be
// stored. more details on that can be retrieved from the propertyExtractMeta function itself.
// @todo: make this a closed function to do getter and setter which is non enumerable
var src = definition && definition.info || definition,
meta = _(src).pickBy(PropertyBase.propertyIsMeta).mapKeys(PropertyBase.propertyUnprefixMeta).value();
if (_.keys(meta).length) {
this._ = _.isObject(this._) ?
/* istanbul ignore next */
_.mergeDefined(this._, meta) :
meta;
}
};
_.assign(PropertyBase.prototype, /** @lends PropertyBase.prototype */ {
/**
* Invokes the given iterator for every parent in the parent chain of the given element.
*
* @param {Object} options - A set of options for the parent chain traversal.
* @param {?Boolean} [options.withRoot=false] - Set to true to include the collection object as well.
* @param {Function} iterator - The function to call for every parent in the ancestry chain.
* @todo Cache the results
*/
forEachParent (options, iterator) {
_.isFunction(options) && (iterator = options, options = {});
if (!_.isFunction(iterator) || !_.isObject(options)) { return; }
var parent = this.parent(),
grandparent = parent && _.isFunction(parent.parent) && parent.parent();
while (parent && (grandparent || options.withRoot)) {
iterator(parent);
parent = grandparent;
grandparent = grandparent && _.isFunction(grandparent.parent) && grandparent.parent();
}
},
/**
* Tries to find the given property locally, and then proceeds to lookup in each parent,
* going up the chain as necessary. Lookup will continue until `customizer` returns a truthy value. If used
* without a customizer, the lookup will stop at the first parent that contains the property.
*
* @param {String} property -
* @param {Function} [customizer] -
* @returns {*|undefined}
*/
findInParents (property, customizer) {
var owner = this.findParentContaining(property, customizer);
return owner ? owner[property] : undefined;
},
/**
* Looks up the closest parent which has a truthy value for the given property. Lookup will continue
* until `customizer` returns a truthy value. If used without a customizer,
* the lookup will stop at the first parent that contains the property.
*
* @private
* @param {String} property -
* @param {Function} [customizer] -
* @returns {PropertyBase|undefined}
*/
findParentContaining (property, customizer) {
var parent = this;
// if customizer is present test with it
if (customizer) {
customizer = customizer.bind(this);
do {
// else check for existence
if (customizer(parent)) {
return parent;
}
parent = parent.__parent;
} while (parent);
}
// else check for existence
else {
do {
if (parent[property]) {
return parent;
}
parent = parent.__parent;
} while (parent);
}
},
/**
* Returns the JSON representation of a property, which conforms to the way it is defined in a collection.
* You can use this method to get the instantaneous representation of any property, including a {@link Collection}.
*/
toJSON () {
return _.reduce(this, function (accumulator, value, key) {
if (value === undefined) { // true/false/null need to be preserved.
return accumulator;
}
// Handle plurality of PropertyLists in the SDK vs the exported JSON.
// Basically, removes the trailing "s" from key if the value is a property list.
// eslint-disable-next-line max-len
if (value && value._postman_propertyIsList && !value._postman_proprtyIsSerialisedAsPlural && _.endsWith(key, 's')) {
key = key.slice(0, -1);
}
// Handle 'PropertyBase's
if (value && _.isFunction(value.toJSON)) {
accumulator[key] = value.toJSON();
return accumulator;
}
// Handle Strings
if (_.isString(value)) {
accumulator[key] = value;
return accumulator;
}
// Everything else
accumulator[key] = _.cloneElement(value);
return accumulator;
}, {});
},
/**
* Returns the meta keys associated with the property
*
* @returns {*}
*/
meta () {
return arguments.length ? _.pick(this._, Array.prototype.slice.apply(arguments)) : _.cloneDeep(this._);
},
/**
* Returns the parent of item
*
* @returns {*|undefined}
*/
parent () {
// @todo return grandparent only if it is a list
return this && this.__parent && (this.__parent.__parent || this.__parent) || undefined;
},
/**
* Accepts an object and sets it as the parent of the current property.
*
* @param {Object} parent The object to set as parent.
* @private
*/
setParent (parent) {
_.assignHidden(this, __PARENT, parent);
}
});
_.assign(PropertyBase, /** @lends PropertyBase */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'PropertyBase',
/**
* Filter function to check whether a key starts with underscore or not. These usually are the meta properties. It
* returns `true` if the criteria is matched.
*
* @param {*} value -
* @param {String} key -
*
* @returns {boolean}
*/
propertyIsMeta: function (value, key) {
return _.startsWith(key, '_') && (key !== '_');
},
/**
* Map function that removes the underscore prefix from an object key.
*
* @param {*} value -
* @param {String} key -
* @returns {String}
*/
propertyUnprefixMeta: function (value, key) {
return _.trimStart(key, '_');
},
/**
* Static function which allows calling toJSON() on any object.
*
* @param {Object} obj -
* @returns {*}
*/
toJSON: function (obj) {
return PropertyBase.prototype.toJSON.call(obj);
}
});
module.exports = {
PropertyBase
};

View File

@@ -0,0 +1,708 @@
var _ = require('../util').lodash,
PropertyBase = require('./property-base').PropertyBase,
__PARENT = '__parent',
DEFAULT_INDEX_ATTR = 'id',
DEFAULT_INDEXCASE_ATTR = false,
DEFAULT_INDEXMULTI_ATTR = false,
PropertyList;
/**
* An item constructed of PropertyList.Type.
*
* @typedef {Object} PropertyList.Type
*/
_.inherit((
/**
* @constructor
* @param {Function} type -
* @param {Object} parent -
* @param {Array} populate -
*/
PropertyList = function PostmanPropertyList (type, parent, populate) {
// @todo add this test sometime later
// if (!type) {
// throw new Error('postman-collection: cannot initialise a list without a type parameter');
// }
PropertyList.super_.call(this); // call super with appropriate options
this.setParent(parent); // save reference to parent
_.assign(this, /** @lends PropertyList.prototype */ {
/**
* @private
* @type {Array}
*/
members: this.members || [],
/**
* @private
* @type {Object}
* @note This should not be used, and it's not guaranteed to be in sync with the actual list of members.
*/
reference: this.reference || {},
/**
* @private
* @type {Function}
*/
Type: type
});
// if the type this list holds has its own index key, then use the same
_.getOwn(type, '_postman_propertyIndexKey') && (this._postman_listIndexKey = type._postman_propertyIndexKey);
// if the type has case sensitivity flags, set the same
_.getOwn(type, '_postman_propertyIndexCaseInsensitive') && (this._postman_listIndexCaseInsensitive =
type._postman_propertyIndexCaseInsensitive);
// if the type allows multiple values, set the flag
_.getOwn(type, '_postman_propertyAllowsMultipleValues') && (this._postman_listAllowsMultipleValues =
type._postman_propertyAllowsMultipleValues);
// prepopulate
populate && this.populate(populate);
}), PropertyBase);
_.assign(PropertyList.prototype, /** @lends PropertyList.prototype */ {
/**
* Indicates that this element contains a number of other elements.
*
* @private
*/
_postman_propertyIsList: true,
/**
* Holds the attribute to index this PropertyList by. Default: 'id'
*
* @private
* @type {String}
*/
_postman_listIndexKey: DEFAULT_INDEX_ATTR,
/**
* Holds the attribute whether indexing of this list is case sensitive or not
*
* @private
* @type {String}
*/
_postman_listIndexCaseInsensitive: DEFAULT_INDEXCASE_ATTR,
/**
* Holds the attribute whether exporting the index retains duplicate index items
*
* @private
* @type {String}
*/
_postman_listAllowsMultipleValues: DEFAULT_INDEXMULTI_ATTR,
/**
* Insert an element at the end of this list. When a reference member specified via second parameter is found, the
* member is inserted at an index before the reference member.
*
* @param {PropertyList.Type} item -
* @param {PropertyList.Type|String} [before] -
*/
insert: function (item, before) {
if (!_.isObject(item)) { return; } // do not proceed on empty param
var duplicate = this.indexOf(item),
index;
// remove from previous list
PropertyList.isPropertyList(item[__PARENT]) && (item[__PARENT] !== this) && item[__PARENT].remove(item);
// inject the parent reference
_.assignHidden(item, __PARENT, this);
// ensure that we do not double insert things into member array
(duplicate > -1) && this.members.splice(duplicate, 1);
// find the position of the reference element
before && (before = this.indexOf(before));
// inject to the members array ata position or at the end in case no item is there for reference
(before > -1) ? this.members.splice(before, 0, item) : this.members.push(item);
// store reference by id, so create the index string. we first ensure that the index value is truthy and then
// recheck that the string conversion of the same is truthy as well.
if ((index = item[this._postman_listIndexKey]) && (index = String(index))) {
// desensitise case, if the property needs it to be
this._postman_listIndexCaseInsensitive && (index = index.toLowerCase());
// if multiple values are allowed, the reference may contain an array of items, mapped to an index.
if (this._postman_listAllowsMultipleValues && Object.hasOwnProperty.call(this.reference, index)) {
// if the value is not an array, convert it to an array.
!_.isArray(this.reference[index]) && (this.reference[index] = [this.reference[index]]);
// add the item to the array of items corresponding to this index
this.reference[index].push(item);
}
else {
this.reference[index] = item;
}
}
},
/**
* Insert an element at the end of this list. When a reference member specified via second parameter is found, the
* member is inserted at an index after the reference member.
*
* @param {PropertyList.Type} item -
* @param {PropertyList.Type|String} [after] -
*/
insertAfter: function (item, after) {
// convert item to positional reference
return this.insert(item, this.idx(this.indexOf(after) + 1));
},
/**
* Adds or moves an item to the end of this list.
*
* @param {PropertyList.Type} item -
*/
append: function (item) {
return this.insert(item);
},
/**
* Adds or moves an item to the beginning of this list.
*
* @param {PropertyList.Type} item -
*/
prepend: function (item) {
return this.insert(item, this.idx(0));
},
/**
* Add an item or item definition to this list.
*
* @param {Object|PropertyList.Type} item -
* @todo
* - remove item from original parent if already it has a parent
* - validate that the original parent's constructor matches this parent's constructor
*/
add: function (item) {
// do not proceed on empty param, but empty strings are in fact valid.
// eslint-disable-next-line lodash/prefer-is-nil
if (_.isNull(item) || _.isUndefined(item) || _.isNaN(item)) { return; }
// create new instance of the item based on the type specified if it is not already
this.insert((item.constructor === this.Type) ? item :
// if the property has a create static function, use it.
// eslint-disable-next-line prefer-spread
(_.has(this.Type, 'create') ? this.Type.create.apply(this.Type, arguments) : new this.Type(item)));
},
/**
* Add an item or update an existing item
*
* @param {PropertyList.Type} item -
* @returns {?Boolean}
*/
upsert: function (item) {
// do not proceed on empty param, but empty strings are in fact valid.
if (_.isNil(item) || _.isNaN(item)) { return null; }
var indexer = this._postman_listIndexKey,
existing = this.one(item[indexer]);
if (existing) {
if (!_.isFunction(existing.update)) {
throw new Error('collection: unable to upsert into a list of Type that does not support .update()');
}
existing.update(item);
return false;
}
// since there is no existing item, just add a new one
this.add(item);
return true; // indicate added
},
/**
* Removes all elements from the PropertyList for which the predicate returns truthy.
*
* @param {Function|String|PropertyList.Type} predicate -
* @param {Object} context Optional context to bind the predicate to.
*/
remove: function (predicate, context) {
var match; // to be used if predicate is an ID
!context && (context = this);
if (_.isString(predicate)) {
// if predicate is id, then create a function to remove that
// need to take care of case sensitivity as well :/
match = this._postman_listIndexCaseInsensitive ? predicate.toLowerCase() : predicate;
predicate = function (item) {
var id = item[this._postman_listIndexKey];
this._postman_listIndexCaseInsensitive && (id = id.toLowerCase());
return id === match;
}.bind(this);
}
else if (predicate instanceof this.Type) {
// in case an object reference is sent, prepare it for removal using direct reference comparison
match = predicate;
predicate = function (item) {
return (item === match);
};
}
_.isFunction(predicate) && _.remove(this.members, function (item) {
var index;
if (predicate.apply(context, arguments)) {
if ((index = item[this._postman_listIndexKey]) && (index = String(index))) {
this._postman_listIndexCaseInsensitive && (index = index.toLowerCase());
if (this._postman_listAllowsMultipleValues && _.isArray(this.reference[index])) {
// since we have an array of multiple values, remove only the value for which the
// predicate returned truthy. If the array becomes empty, just delete it.
_.remove(this.reference[index], function (each) {
return each === item;
});
// If the array becomes empty, remove it
/* istanbul ignore next */
(this.reference[index].length === 0) && (delete this.reference[index]);
// If the array contains only one element, remove the array, and assign the element
// as the reference value
(this.reference[index].length === 1) && (this.reference[index] = this.reference[index][0]);
}
else {
delete this.reference[index];
}
}
delete item[__PARENT]; // unlink from its parent
return true;
}
}.bind(this));
},
/**
* Removes all items in the list
*/
clear: function () {
// we unlink every member from it's parent (assuming this is their parent)
this.all().forEach(PropertyList._unlinkItemFromParent);
this.members.length = 0; // remove all items from list
// now we remove all items from index reference
Object.keys(this.reference).forEach(function (key) {
delete this.reference[key];
}.bind(this));
},
/**
* Load one or more items
*
* @param {Object|Array} items -
*/
populate: function (items) {
// if Type supports parsing of string headers then do it before adding it.
_.isString(items) && _.isFunction(this.Type.parse) && (items = this.Type.parse(items));
// add a single item or an array of items.
_.forEach(_.isArray(items) ? items :
// if population is not an array, we send this as single item in an array or send each property separately
// if the core Type supports Type.create
((_.isPlainObject(items) && _.has(this.Type, 'create')) ? items : [items]), this.add.bind(this));
},
/**
* Clears the list and adds new items.
*
* @param {Object|Array} items -
*/
repopulate: function (items) {
this.clear();
this.populate(items);
},
/**
* Add or update values from a source list.
*
* @param {PropertyList|Array} source -
* @param {Boolean} [prune=false] Setting this to `true` will cause the extra items from the list to be deleted
*/
assimilate: function (source, prune) {
var members = PropertyList.isPropertyList(source) ? source.members : source,
list = this,
indexer = list._postman_listIndexKey,
sourceKeys = {}; // keeps track of added / updated keys for later exclusion
if (!_.isArray(members)) {
return;
}
members.forEach(function (item) {
/* istanbul ignore if */
if (!(item && _.has(item, indexer))) { return; }
list.upsert(item);
sourceKeys[item[indexer]] = true;
});
// now remove any variable that is not in source object
// @note - using direct `this.reference` list of keys here so that we can mutate the list while iterating
// on it
if (prune) {
_.forEach(list.reference, function (value, key) {
if (_.has(sourceKeys, key)) { return; } // de not delete if source obj has this variable
list.remove(key); // use PropertyList functions to remove so that the .members array is cleared too
});
}
},
/**
* Returns a map of all items.
*
* @returns {Object}
*/
all: function () {
return _.clone(this.members);
},
/**
* Get Item in this list by `ID` reference. If multiple values are allowed, the last value is returned.
*
* @param {String} id -
* @returns {PropertyList.Type}
*/
one: function (id) {
var val = this.reference[this._postman_listIndexCaseInsensitive ? String(id).toLowerCase() : id];
if (this._postman_listAllowsMultipleValues && Array.isArray(val)) {
return val.length ? val[val.length - 1] :
/* istanbul ignore next */
undefined;
}
return val;
},
/**
* Get the value of an item in this list. This is similar to {@link PropertyList.one} barring the fact that it
* returns the value of the underlying type of the list content instead of the item itself.
*
* @param {String|Function} key -
* @returns {PropertyList.Type|*}
*/
get: function (key) {
var member = this.one(key);
if (!member) { return; } // eslint-disable-line getter-return
return member.valueOf();
},
/**
* Iterate on each item of this list.
*
* @param {Function} iterator -
* @param {Object} context -
*/
each: function (iterator, context) {
_.forEach(this.members, _.isFunction(iterator) ? iterator.bind(context || this.__parent) : iterator);
},
/**
* @param {Function} rule -
* @param {Object} context -
*/
filter: function (rule, context) {
return _.filter(this.members, _.isFunction(rule) && _.isObject(context) ? rule.bind(context) : rule);
},
/**
* Find an item within the item group
*
* @param {Function} rule -
* @param {Object} [context] -
* @returns {Item|ItemGroup}
*/
find: function (rule, context) {
return _.find(this.members, _.isFunction(rule) && _.isObject(context) ? rule.bind(context) : rule);
},
/**
* Iterates over the property list.
*
* @param {Function} iterator Function to call on each item.
* @param {Object} context Optional context, defaults to the PropertyList itself.
*/
map: function (iterator, context) {
return _.map(this.members, _.isFunction(iterator) ? iterator.bind(context || this) : iterator);
},
/**
* Iterates over the property list and accumulates the result.
*
* @param {Function} iterator Function to call on each item.
* @param {*} accumulator Accumulator initial value
* @param {Object} context Optional context, defaults to the PropertyList itself.
*/
reduce: function (iterator, accumulator, context) {
return _.reduce(this.members, _.isFunction(iterator) ?
iterator.bind(context || this) :
/* istanbul ignore next */
iterator
, accumulator);
},
/**
* Returns the length of the PropertyList
*
* @returns {Number}
*/
count: function () {
return this.members.length;
},
/**
* Get a member of this list by it's index
*
* @param {Number} index -
* @returns {PropertyList.Type}
*/
idx: function (index) {
return this.members[index];
},
/**
* Find the index of an item in this list
*
* @param {String|Object} item -
* @returns {Number}
*/
indexOf: function (item) {
return this.members.indexOf(_.isString(item) ? (item = this.one(item)) : item);
},
/**
* Check whether an item exists in this list
*
* @param {String|PropertyList.Type} item -
* @param {*=} value -
* @returns {Boolean}
*/
has: function (item, value) {
var match,
val,
i;
match = _.isString(item) ?
this.reference[this._postman_listIndexCaseInsensitive ? item.toLowerCase() : item] :
this.filter(function (member) {
return member === item;
});
// If we don't have a match, there's nothing to do
if (!match) { return false; }
// if no value is provided, just check if item exists
if (arguments.length === 1) {
return Boolean(_.isArray(match) ? match.length : match);
}
// If this property allows multiple values and we get an array, we need to iterate through it and see
// if any element matches.
if (this._postman_listAllowsMultipleValues && _.isArray(match)) {
for (i = 0; i < match.length; i++) {
// use the value of the current element
val = _.isFunction(match[i].valueOf) ? match[i].valueOf() :
/* istanbul ignore next */
match[i];
if (val === value) { return true; }
}
// no matches were found, so return false here.
return false;
}
// We didn't have an array, so just check if the matched value equals the provided value.
_.isFunction(match.valueOf) && (match = match.valueOf());
return match === value;
},
/**
* Iterates over all parents of the property list
*
* @param {Function} iterator -
* @param {Object=} [context] -
*/
eachParent: function (iterator, context) {
// validate parameters
if (!_.isFunction(iterator)) { return; }
!context && (context = this);
var parent = this.__parent,
prev;
// iterate till there is no parent
while (parent) {
// call iterator with the parent and previous parent
iterator.call(context, parent, prev);
// update references
prev = parent;
parent = parent.__parent;
}
},
/**
* Converts a list of Properties into an object where key is `_postman_propertyIndexKey` and value is determined
* by the `valueOf` function
*
* @param {?Boolean} [excludeDisabled=false] - When set to true, disabled properties are excluded from the resultant
* object.
* @param {?Boolean} [caseSensitive] - When set to true, properties are treated strictly as per their original
* case. The default value for this property also depends on the case insensitivity definition of the current
* property.
* @param {?Boolean} [multiValue=false] - When set to true, only the first value of a multi valued property is
* returned.
* @param {Boolean} [sanitizeKeys=false] - When set to true, properties with falsy keys are removed.
* @todo Change the function signature to an object of options instead of the current structure.
* @returns {Object}
*/
toObject: function (excludeDisabled, caseSensitive, multiValue, sanitizeKeys) {
var obj = {}, // create transformation data accumulator
// gather all the switches of the list
key = this._postman_listIndexKey,
sanitiseKeys = this._postman_sanitizeKeys || sanitizeKeys,
sensitive = !this._postman_listIndexCaseInsensitive || caseSensitive,
multivalue = this._postman_listAllowsMultipleValues || multiValue;
// iterate on each member to create the transformation object
this.each(function (member) {
// Bail out for the current member if ANY of the conditions below is true:
// 1. The member is falsy.
// 2. The member does not have the specified property list index key.
// 3. The member is disabled and disabled properties have to be ignored.
// 4. The member has a falsy key, and sanitize is true.
if (!member || !_.has(member, key) || (excludeDisabled && member.disabled) ||
(sanitiseKeys && !member[key])) {
return;
}
// based on case sensitivity settings, we get the property name of the item
var prop = sensitive ? member[key] : String(member[key]).toLowerCase();
// now, if transformation object already has a member with same property name, we either overwrite it or
// append to an array of values based on multi-value support
if (multivalue && _.has(obj, prop)) {
(!Array.isArray(obj[prop])) && (obj[prop] = [obj[prop]]);
obj[prop].push(member.valueOf());
}
else {
obj[prop] = member.valueOf();
}
});
return obj;
},
/**
* Adds ability to convert a list to a string provided it's underlying format has unparse function defined.
*
* @returns {String}
*/
toString: function () {
if (this.Type.unparse) {
return this.Type.unparse(this.members);
}
return this.constructor ? this.constructor.prototype.toString.call(this) : '';
},
toJSON: function () {
if (!this.count()) {
return [];
}
return _.map(this.members, function (member) {
// use member.toJSON if it exists
if (!_.isEmpty(member) && _.isFunction(member.toJSON)) {
return member.toJSON();
}
return _.reduce(member, function (accumulator, value, key) {
if (value === undefined) { // true/false/null need to be preserved.
return accumulator;
}
// Handle plurality of PropertyLists in the SDK vs the exported JSON.
// Basically, removes the trailing "s" from key if the value is a property list.
if (value && value._postman_propertyIsList && !value._postman_proprtyIsSerialisedAsPlural &&
_.endsWith(key, 's')) {
key = key.slice(0, -1);
}
// Handle 'PropertyBase's
if (value && _.isFunction(value.toJSON)) {
accumulator[key] = value.toJSON();
return accumulator;
}
// Handle Strings
if (_.isString(value)) {
accumulator[key] = value;
return accumulator;
}
// Everything else
accumulator[key] = _.cloneElement(value);
return accumulator;
}, {});
});
}
});
_.assign(PropertyList, /** @lends PropertyList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'PropertyList',
/**
* Removes child-parent links for the provided PropertyList member.
*
* @param {Property} item - The property for which to perform parent de-linking.
* @private
*/
_unlinkItemFromParent: function (item) {
item.__parent && (delete item.__parent); // prevents V8 from making unnecessary look-ups if there is no __parent
},
/**
* Checks whether an object is a PropertyList
*
* @param {*} obj -
* @returns {Boolean}
*/
isPropertyList: function (obj) {
return Boolean(obj) && ((obj instanceof PropertyList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', PropertyList._postman_propertyName));
}
});
module.exports = {
PropertyList
};

View File

@@ -0,0 +1,345 @@
var _ = require('../util').lodash,
uuid = require('uuid'),
PropertyBase = require('./property-base').PropertyBase,
Description = require('./description').Description,
Substitutor = require('../superstring').Substitutor,
DISABLED = 'disabled',
DESCRIPTION = 'description',
REGEX_EXTRACT_VARS = Substitutor.REGEX_EXTRACT_VARS,
Property; // constructor
/**
* Recursively traverses a variable and detects all instances of variable
* replacements within the string of the object.
*
* @private
* @param {*} value Any JS variable within which we are trying to discover {{variables}}
* @param {[Object]} seen Set of objects traversed before to avoid infinite recursion
* @param {[Object]} result Set of variables to accumulate result in the recursive call
* @returns {Object} Set of variables
*/
function _findSubstitutions (value, seen = new Set(), result = new Set()) {
if (!value || seen.has(value)) {
return result;
}
if (Array.isArray(value)) {
seen.add(value);
for (let i = 0, ii = value.length; i < ii; i++) {
_findSubstitutions(value[i], seen, result);
}
}
else if (typeof value === 'object') {
seen.add(value);
for (const key in value) {
if (Object.hasOwnProperty.call(value, key)) {
_findSubstitutions(value[key], seen, result);
}
}
}
else if (typeof value === 'string') {
let match;
while ((match = REGEX_EXTRACT_VARS.exec(value)) !== null) {
result.add(match[0].slice(2, -2));
}
}
return result;
}
/**
* @typedef Property.definition
* @property {String=} [id] A unique string that identifies the property.
* @property {String=} [name] A distinctive and human-readable name of the property.
* @property {Boolean=} [disabled] Denotes whether the property is disabled or not.
* @property {Object=} [info] The meta information regarding the Property is provided as the `info` object.
* @property {String=} [info.id] If set, this is used instead of the definition root's id.
* @property {String=} [info.name] If set, this is used instead of the definition root's name.
*/
_.inherit((
/**
* The Property class forms the base of all postman collection SDK elements. This is to be used only for SDK
* development or to extend the SDK with additional functionalities. All SDK classes (constructors) that are
* supposed to be identifyable (i.e. ones that can have a `name` and `id`) are derived from this class.
*
* For more information on what is the structure of the `definition` the function parameter, have a look at
* {@link Property.definition}.
*
* > This is intended to be a private class except for those who want to extend the SDK itself and add more
* > functionalities.
*
* @constructor
* @extends {PropertyBase}
*
* @param {Property.definition=} [definition] Every constructor inherited from `Property` is required to call the
* super constructor function. This implies that construction parameters of every inherited member is propagated
* to be sent up to this point.
*
* @see Property.definition
*/
Property = function PostmanProperty (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Property.super_.apply(this, arguments);
// The definition can have an `info` object that stores the identification of this property. If that is present,
// we use it instead of the definition root.
var src = definition && definition.info || definition,
id;
// first we extract id from all possible sources
// we also check if this property is marked to require an ID, we generate one if not found.
id = (src && src.id) || this.id || (this._ && this._.postman_id) || (this._postman_propertyRequiresId &&
uuid.v4());
/**
* The `id` of the property is a unique string that identifies this property and can be used to refer to
* this property from relevant other places. It is a good practice to define the id or let the system
* auto generate a UUID if one is not defined for properties that require an `id`.
*
* @name id
* @type {String}
* @memberOf Property.prototype
*
* @note The property can also be present in the `postman_id` meta in case it is not specified in the
* object. An auto-generated property is used wherever one is not specified
*/
id && (this.id = id);
/**
* A property can have a distinctive and human-readable name. This is to be used to display the name of the
* property within Postman, Newman or other runtimes that consume collection. In certain cases, the absence
* of name might cause the runtime to use the `id` as a fallback.
*
* @name name
* @memberOf Property.prototype
* @type {String}
*/
src && src.name && (this.name = src.name);
/**
* This (optional) flag denotes whether this property is disabled or not. Usually, this is helpful when a
* property is part of a {@link PropertyList}. For example, in a PropertyList of {@link Header}s, the ones
* that are disabled can be filtered out and not processed.
*
* @type {Boolean}
* @optional
* @name disabled
*
* @memberOf Property.prototype
*/
definition && _.has(definition, DISABLED) && (this.disabled = Boolean(definition.disabled));
/**
* The `description` property holds the detailed documentation of any property.
* It is recommended that this property be updated using the [describe](#describe) function.
*
* @type {Description}
* @see Property#describe
*
* @example <caption>Accessing descriptions of all root items in a collection</caption>
* var fs = require('fs'), // needed to read JSON file from disk
* Collection = require('postman-collection').Collection,
* myCollection;
*
* // Load a collection to memory from a JSON file on disk (say, sample-collection.json)
* myCollection = new Collection(JSON.stringify(fs.readFileSync('sample-collection.json').toString()));
*
* // Log the description of all root items
* myCollection.item.all().forEach(function (item) {
* console.log(item.name || 'Untitled Item');
* item.description && console.log(item.description.toString());
* });
*/
// eslint-disable-next-line max-len
_.has(src, DESCRIPTION) && (this.description = _.createDefined(src, DESCRIPTION, Description, this.description));
}), PropertyBase);
_.assign(Property.prototype, /** @lends Property.prototype */ {
/**
* This function allows to describe the property for the purpose of detailed identification or documentation
* generation. This function sets or updates the `description` child-property of this property.
*
* @param {String} content The content of the description can be provided here as a string. Note that it is expected
* that if the content is formatted in any other way than simple text, it should be specified in the subsequent
* `type` parameter.
* @param {String=} [type="text/plain"] The type of the content.
*
* @example <caption>Add a description to an instance of Collection</caption>
* var Collection = require('postman-collection').Collection,
* mycollection;
*
* // create a blank collection
* myCollection = new Collection();
* myCollection.describe('Hey! This is a cool collection.');
*
* console.log(myCollection.description.toString()); // read the description
*/
describe (content, type) {
(Description.isDescription(this.description) ? this.description : (this.description = new Description()))
.update(content, type);
},
/**
* Returns an object representation of the Property with its variable references substituted.
*
* @example <caption>Resolve an object using variable definitions from itself and its parents</caption>
* property.toObjectResolved();
*
* @example <caption>Resolve an object using variable definitions on a different object</caption>
* property.toObjectResolved(item);
*
* @example <caption>Resolve an object using variables definitions as a flat list of variables</caption>
* property.toObjectResolved(null, [variablesDefinition1, variablesDefinition1], {ignoreOwnVariables: true});
*
* @private
* @draft
* @param {?Item|ItemGroup=} [scope] - One can specifically provide an item or group with `.variables`. In
* the event one is not provided, the variables are taken from this object or one from the parent tree.
* @param {Array<Object>} overrides - additional objects to lookup for variable values
* @param {Object} [options] -
* @param {Boolean} [options.ignoreOwnVariables] - if set to true, `.variables` on self(or scope)
* will not be used for variable resolution. Only variables in `overrides` will be used for resolution.
* @returns {Object|undefined}
* @throws {Error} If `variables` cannot be resolved up the parent chain.
*/
toObjectResolved (scope, overrides, options) {
var ignoreOwnVariables = options && options.ignoreOwnVariables,
variableSourceObj,
variables,
reference;
// ensure you do not substitute variables itself!
reference = this.toJSON();
_.isArray(reference.variable) && (delete reference.variable);
// if `ignoreScopeVariables` is turned on, ignore `.variables` and resolve with only `overrides`
// otherwise find `.variables` on current object or `scope`
if (ignoreOwnVariables) {
return Property.replaceSubstitutionsIn(reference, overrides);
}
// 1. if variables is passed as params, use it or fall back to oneself
// 2. for a source from point (1), and look for `.variables`
// 3. if `.variables` is not found, then rise up the parent to find first .variables
variableSourceObj = scope || this;
do {
variables = variableSourceObj.variables;
variableSourceObj = variableSourceObj.__parent;
} while (!variables && variableSourceObj);
if (!variables) { // worst case = no variable param and none detected in tree or object
throw Error('Unable to resolve variables. Require a List type property for variable auto resolution.');
}
return variables.substitute(reference, overrides);
},
/**
* Returns all the substitutions (variables) that are needed (or referenced) in this property (recursively).
*
* @returns {String[]}
*
* @example
* // returns ['host', 'path1']
* prop.findSubstitutions({request: 'https://{{host}}/{{path1}}-action/'});
*
* @see {Property.findSubstitutions}
*/
findSubstitutions () {
return Property.findSubstitutions(this.toJSON());
}
});
_.assign(Property, /** @lends Property */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Property',
/**
* This function accepts a string followed by a number of variable sources as arguments. One or more variable
* sources can be provided and it will use the one that has the value in left-to-right order.
*
* @param {String} str -
* @param {VariableList|Object|Array.<VariableList|Object>} variables -
* @returns {String}
*/
// @todo: improve algorithm via variable replacement caching
replaceSubstitutions: function (str, variables) {
// if there is nothing to replace, we move on
if (!(str && _.isString(str))) { return str; }
// if variables object is not an instance of substitutor then ensure that it is an array so that it becomes
// compatible with the constructor arguments for a substitutor
!Substitutor.isInstance(variables) && !_.isArray(variables) && (variables = _.tail(arguments));
return Substitutor.box(variables, Substitutor.DEFAULT_VARS).parse(str).toString();
},
/**
* This function accepts an object followed by a number of variable sources as arguments. One or more variable
* sources can be provided and it will use the one that has the value in left-to-right order.
*
* @param {Object} obj -
* @param {Array.<VariableList|Object>} variables -
* @returns {Object}
*/
replaceSubstitutionsIn: function (obj, variables) {
// if there is nothing to replace, we move on
if (!(obj && _.isObject(obj))) {
return obj;
}
// convert the variables to a substitutor object (will not reconvert if already substitutor)
variables = Substitutor.box(variables, Substitutor.DEFAULT_VARS);
var customizer = function (objectValue, sourceValue) {
objectValue = objectValue || {};
if (!_.isString(sourceValue)) {
_.forOwn(sourceValue, function (value, key) {
sourceValue[key] = customizer(objectValue[key], value);
});
return sourceValue;
}
return this.replaceSubstitutions(sourceValue, variables);
}.bind(this);
return _.mergeWith({}, obj, customizer);
},
/**
* This function recursively traverses a variable and detects all instances of variable replacements
* within the string of the object
*
* @param {*} obj Any JS variable within which we are trying to discover {{variables}}
* @returns {String[]}
*
* @example
* // returns ['host', 'path1']
* Property.findSubstitutions({request: 'https://{{host}}/{{path1}}-action/'});
*
* @todo
* - tonne of scope for performance optimizations
* - accept a reference variable scope so that substitutions can be applied to find recursive
* replacements (e.g. {{hello-{{hi}}-var}})
*/
findSubstitutions: function (obj) {
return Array.from(_findSubstitutions(obj));
}
});
module.exports = {
Property
};

View File

@@ -0,0 +1,75 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
ProxyConfig = require('./proxy-config').ProxyConfig,
Url = require('./url').Url,
ProxyConfigList;
_.inherit((
/**
* @constructor
* @extends {PropertyList}
*
* @param {Object} parent -
* @param {Array} populate The list of proxy objects
*
* @example <caption>Create a new ProxyConfigList</caption>
* var ProxyConfigList = require('postman-collection').ProxyConfigList,
* myProxyConfig = new ProxyConfigList({}, [
* {match: 'https://example.com/*', host: 'proxy.com', port: 8080, tunnel: true},
* {match: 'http+https://example2.com/*', host: 'proxy2.com'},
* ]);
*/
ProxyConfigList = function PostmanProxyConfigList (parent, populate) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
ProxyConfigList.super_.call(this, ProxyConfig, parent, populate);
}), PropertyList);
_.assign(ProxyConfigList.prototype, /** @lends ProxyConfigList.prototype */ {
/**
* Matches and gets the proxy config for the particular url.
*
* @returns {ProxyConfig.definition} The matched proxyConfig object
* @param {URL=} [url] The url for which the proxy config needs to be fetched
*/
resolve (url) {
// url must be either string or an instance of url.
if (!_.isString(url) && !Url.isUrl(url)) {
return;
}
// @todo - use a fixed-length cacheing of regexes in future
return this.find(function (proxyConfig) {
return !proxyConfig.disabled && proxyConfig.test(url);
});
}
});
_.assign(ProxyConfigList, /** @lends ProxyConfigList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*
* @note that this is directly accessed only in case of ProxyConfigList from _.findValue lodash util mixin
*/
_postman_propertyName: 'ProxyConfigList',
/**
* Checks whether an object is a ProxyConfigList
*
* @param {*} obj -
* @returns {Boolean}
*/
isProxyConfigList: function (obj) {
return Boolean(obj) && ((obj instanceof ProxyConfigList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', ProxyConfigList._postman_propertyName));
}
});
module.exports = {
ProxyConfigList
};

View File

@@ -0,0 +1,301 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
Url = require('./url').Url,
UrlMatchPattern = require('../url-pattern/url-match-pattern').UrlMatchPattern,
UrlMatchPatternList = require('../url-pattern/url-match-pattern-list').UrlMatchPatternList,
ProxyConfig,
PROTOCOL_DELIMITER = UrlMatchPattern.PROTOCOL_DELIMITER,
E = '',
COLON = ':',
DEFAULT_PORT = 8080,
PROTOCOL_HOST_SEPARATOR = '://',
MATCH_ALL_HOST_AND_PATH = '*:*/*',
AUTH_CREDENTIALS_SEPARATOR = '@',
DEFAULT_PROTOCOL = 'http',
ALLOWED_PROTOCOLS = ['http', 'https'],
// 'http+https://*:*/*'
DEFAULT_PATTERN = ALLOWED_PROTOCOLS.join(PROTOCOL_DELIMITER) + PROTOCOL_HOST_SEPARATOR + MATCH_ALL_HOST_AND_PATH;
/**
* The following is the object structure accepted as constructor parameter while calling `new ProxyConfig(...)`. It is
* also the structure exported when {@link Property#toJSON} or {@link Property#toObjectResolved} is called on a
* Proxy instance.
*
* @typedef ProxyConfig.definition
*
* @property {String=} [match = 'http+https://*\/*'] The match for which the proxy needs to be configured.
* @property {String=} [host = ''] The proxy server url.
* @property {Number=} [port = 8080] The proxy server port number.
* @property {Boolean=} [tunnel = false] The tunneling option for the proxy request.
* @property {Boolean=} [disabled = false] To override the proxy for the particular url, you need to provide true.
* @property {Boolean=} [authenticate = false] To enable authentication for the proxy, you need to provide true.
* @property {String=} [username] The proxy authentication username
* @property {String=} [password] The proxy authentication password
*
* @example <caption>JSON definition of an example proxy object</caption>
* {
* "match": "http+https://example.com/*",
* "host": "proxy.com",
* "port": "8080",
* "tunnel": true,
* "disabled": false,
* "authenticate": true,
* "username": "proxy_username",
* "password": "proxy_password"
* }
*/
_.inherit((
/**
* A ProxyConfig definition that represents the proxy configuration for an url match.
* Properties can then use the `.toObjectResolved` function to procure an object representation of the property with
* all the variable references replaced by corresponding values.
*
* @constructor
* @extends {Property}
* @param {ProxyConfig.definition=} [options] - Specifies object with props matches, server and tunnel.
*
* @example <caption>Create a new ProxyConfig</caption>
* var ProxyConfig = require('postman-collection').ProxyConfig,
* myProxyConfig = new ProxyConfig({
* host: 'proxy.com',
* match: 'http+https://example.com/*',
* port: 8080,
* tunnel: true,
* disabled: false,
* authenticate: true,
* username: 'proxy_username',
* password: 'proxy_password'
* });
*/
ProxyConfig = function ProxyConfig (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
ProxyConfig.super_.call(this, options);
// Assign defaults before proceeding
_.assign(this, /** @lends ProxyConfig */ {
/**
* The proxy server host or ip
*
* @type {String}
*/
host: E,
/**
* The url mach for which the proxy has been associated with.
*
* @type {String}
*/
match: new UrlMatchPattern(DEFAULT_PATTERN),
/**
* The proxy server port number
*
* @type {Number}
*/
port: DEFAULT_PORT,
/**
* This represents whether the tunneling needs to done while proxying this request.
*
* @type Boolean
*/
tunnel: false,
/**
* Proxy bypass list
*
* @type {UrlMatchPatternList}
*/
bypass: undefined,
/**
* Enable proxy authentication
*
* @type {Boolean}
*/
authenticate: false,
/**
* Proxy auth username
*
* @type {String}
*/
username: undefined,
/**
* Proxy auth password
*
* @type {String}
*/
password: undefined
});
this.update(options);
}), Property);
_.assign(ProxyConfig.prototype, /** @lends ProxyConfig.prototype */ {
/**
* Defines whether this property instances requires an id
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyRequiresId: true,
/**
* Updates the properties of the proxy object based on the options provided.
*
* @param {ProxyConfig.definition} options The proxy object structure.
*/
update: function (options) {
if (!_.isObject(options)) {
return;
}
var parsedUrl,
port = _.get(options, 'port') >> 0;
if (_.isString(options.host)) {
// strip the protocol from given host
parsedUrl = new Url(options.host);
this.host = parsedUrl.getHost();
}
_.isString(options.match) && (this.match = new UrlMatchPattern(options.match));
_.isString(_.get(options, 'match.pattern')) && (this.match = new UrlMatchPattern(options.match.pattern));
port && (this.port = port);
_.isBoolean(options.tunnel) && (this.tunnel = options.tunnel);
// todo: Add update method in parent class Property and call that here
_.isBoolean(options.disabled) && (this.disabled = options.disabled);
_.isBoolean(options.authenticate) && (this.authenticate = options.authenticate);
_.isString(options.username) && (this.username = options.username);
_.isString(options.password) && (this.password = options.password);
// init bypass list from the given array
if (Array.isArray(options.bypass)) {
this.bypass = new UrlMatchPatternList(null, options.bypass);
}
// or, convert existing PropertyList or UrlMatchPatternList
else if (PropertyList.isPropertyList(options.bypass)) {
this.bypass = new UrlMatchPatternList(null, options.bypass.all());
}
},
/**
* Updates the protocols in the match pattern
*
* @param {Array.<String>} protocols The array of protocols
*/
updateProtocols: function (protocols) {
if (!protocols) {
return;
}
var updatedProtocols,
hostAndPath = _.split(this.match.pattern, PROTOCOL_HOST_SEPARATOR)[1];
if (!hostAndPath) {
return;
}
updatedProtocols = _.intersection(ALLOWED_PROTOCOLS, _.castArray(protocols));
_.isEmpty(updatedProtocols) && (updatedProtocols = ALLOWED_PROTOCOLS);
this.match.update({
pattern: updatedProtocols.join(PROTOCOL_DELIMITER) + PROTOCOL_HOST_SEPARATOR + hostAndPath
});
},
/**
* Tests the url string with the match provided.
* Follows the https://developer.chrome.com/extensions/match_patterns pattern for pattern validation and matching
*
* @param {String=} [urlStr] The url string for which the proxy match needs to be done.
*/
test: function (urlStr) {
var protocol = Url.isUrl(urlStr) ? urlStr.protocol : (Url.parse(urlStr || E).protocol || E);
// to allow target URLs without any protocol. e.g.: 'foo.com/bar'
if (_.isEmpty(protocol)) {
protocol = DEFAULT_PROTOCOL;
urlStr = protocol + PROTOCOL_HOST_SEPARATOR + urlStr;
}
// this ensures we don't proceed any further for any non-supported protocol
if (!_.includes(ALLOWED_PROTOCOLS, protocol)) {
return false;
}
// don't proceed if the given URL should skip use of a proxy all together
if (this.bypass && this.bypass.test(urlStr)) {
return false;
}
return this.match.test(urlStr);
},
/**
* Returns the proxy server url.
*
* @returns {String}
*/
getProxyUrl: function () {
var auth = E;
// Add authentication method to URL if the same is requested. We do it this way because
// this is how `postman-request` library accepts auth credentials in its proxy configuration.
if (this.authenticate) {
auth = encodeURIComponent(this.username || E);
if (this.password) {
auth += COLON + encodeURIComponent(this.password);
}
if (auth) {
auth += AUTH_CREDENTIALS_SEPARATOR;
}
}
return DEFAULT_PROTOCOL + PROTOCOL_HOST_SEPARATOR + auth + this.host + COLON + this.port;
},
/**
* Returns the protocols supported.
*
* @returns {Array.<String>}
*/
getProtocols: function () {
return this.match.getProtocols();
}
});
_.assign(ProxyConfig, /** @lends ProxyConfig */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'ProxyConfig',
/**
* Check whether an object is an instance of PostmanItem.
*
* @param {*} obj -
* @returns {Boolean}
*/
isProxyConfig: function (obj) {
return Boolean(obj) && ((obj instanceof ProxyConfig) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', ProxyConfig._postman_propertyName));
}
});
module.exports = {
ProxyConfig,
ALLOWED_PROTOCOLS,
DEFAULT_PATTERN
};

View File

@@ -0,0 +1,291 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
E = '',
AMPERSAND = '&',
STRING = 'string',
EQUALS = '=',
EMPTY = '',
HASH = '#',
REGEX_HASH = /#/g,
REGEX_EQUALS = /=/g, // eslint-disable-line no-div-regex
REGEX_AMPERSAND = /&/g,
REGEX_EXTRACT_VARS = /{{[^{}]*[&#=][^{}]*}}/g,
QueryParam,
/**
* Percent encode reserved chars (&, = and #) in the given string.
*
* @private
* @param {String} str -
* @param {Boolean} encodeEquals -
* @returns {String}
*/
encodeReservedChars = function (str, encodeEquals) {
if (!str) {
return str;
}
// eslint-disable-next-line lodash/prefer-includes
str.indexOf(AMPERSAND) !== -1 && (str = str.replace(REGEX_AMPERSAND, '%26'));
// eslint-disable-next-line lodash/prefer-includes
str.indexOf(HASH) !== -1 && (str = str.replace(REGEX_HASH, '%23'));
// eslint-disable-next-line lodash/prefer-includes
encodeEquals && str.indexOf(EQUALS) !== -1 && (str = str.replace(REGEX_EQUALS, '%3D'));
return str;
},
/**
* Normalize the given param string by percent-encoding the reserved chars
* such that it won't affect the re-parsing.
*
* @note `&`, `=` and `#` needs to be percent-encoded otherwise re-parsing
* the same URL string will generate different output
*
* @private
* @param {String} str -
* @param {Boolean} encodeEquals -
* @returns {String}
*/
normalizeParam = function (str, encodeEquals) {
// bail out if the given sting is null or empty
if (!(str && typeof str === STRING)) {
return str;
}
// bail out if the given string does not include reserved chars
// eslint-disable-next-line lodash/prefer-includes
if (str.indexOf(AMPERSAND) === -1 && str.indexOf(HASH) === -1) {
// eslint-disable-next-line lodash/prefer-includes
if (!(encodeEquals && str.indexOf(EQUALS) !== -1)) {
return str;
}
}
var normalizedString = '',
pointer = 0,
variable,
match,
index;
// find all the instances of {{<variable>}} which includes reserved chars
while ((match = REGEX_EXTRACT_VARS.exec(str)) !== null) {
variable = match[0];
index = match.index;
// [pointer, index) string is normalized + the matched variable
normalizedString += encodeReservedChars(str.slice(pointer, index), encodeEquals) + variable;
// update the pointer
pointer = index + variable.length;
}
// whatever left in the string is normalized as well
if (pointer < str.length) {
normalizedString += encodeReservedChars(str.slice(pointer), encodeEquals);
}
return normalizedString;
};
/**
* @typedef QueryParam.definition
* @property {String} key The name ("key") of the query parameter.
* @property {String} value The value of the parameter.
*/
_.inherit((
/**
* Represents a URL query parameter, which can exist in request URL or POST data.
*
* @constructor
* @extends {Property}
* @param {FormParam.definition|String} options Pass the initial definition of the query parameter. In case of
* string, the query parameter is parsed using {@link QueryParam.parseSingle}.
*/
QueryParam = function PostmanQueryParam (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
QueryParam.super_.apply(this, arguments);
this.update(options);
}), Property);
_.assign(QueryParam.prototype, /** @lends QueryParam.prototype */ {
/**
* Converts the QueryParameter to a single param string.
*
* @returns {String}
*/
toString () {
return QueryParam.unparseSingle(this);
},
/**
* Updates the key and value of the query parameter
*
* @param {String|Object} param -
* @param {String} param.key -
* @param {String=} [param.value] -
*/
update (param) {
_.assign(this, /** @lends QueryParam.prototype */ _.isString(param) ? QueryParam.parseSingle(param) : {
key: _.get(param, 'key'), // we do not replace falsey with blank string since null has a meaning
value: _.get(param, 'value')
});
_.has(param, 'system') && (this.system = param.system);
},
valueOf () {
return _.isString(this.value) ? this.value : EMPTY;
}
});
_.assign(QueryParam, /** @lends QueryParam */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'QueryParam',
/**
* Declare the list index key, so that property lists of query parameters work correctly
*
* @type {String}
*/
_postman_propertyIndexKey: 'key',
/**
* Query params can have multiple values, so set this to true.
*
* @type {Boolean}
*/
_postman_propertyAllowsMultipleValues: true,
/**
* Parse a query string into an array of objects, where each object contains a key and a value.
*
* @param {String} query -
* @returns {Array}
*/
parse: function (query) {
return _.isString(query) ? query.split(AMPERSAND).map(QueryParam.parseSingle) : [];
},
/**
* Parses a single query parameter.
*
* @param {String} param -
* @param {Number} idx -
* @param {String[]} all - array of all params, in case this is being called while parsing multiple params.
* @returns {{key: String|null, value: String|null}}
*/
parseSingle: function (param, idx, all) {
// helps handle weird edge cases such as "/get?a=b&&"
if (param === EMPTY && // if param is empty
_.isNumber(idx) && // this and the next condition ensures that this is part of a map call
_.isArray(all) &&
idx !== (all && (all.length - 1))) { // not last parameter in the array
return { key: null, value: null };
}
var index = (typeof param === STRING) ? param.indexOf(EQUALS) : -1,
paramObj = {};
// this means that there was no value for this key (not even blank, so we store this info) and the value is set
// to null
if (index < 0) {
paramObj.key = param.substr(0, param.length);
paramObj.value = null;
}
else {
paramObj.key = param.substr(0, index);
paramObj.value = param.substr(index + 1);
}
return paramObj;
},
/**
* Create a query string from array of parameters (or object of key-values).
*
* @note Disabled parameters are excluded.
*
* @param {Array|Object} params -
* @returns {String}
*/
unparse: function (params) {
if (!params) { return EMPTY; }
var str,
firstEnabledParam = true;
// Convert hash maps to an array of params
if (!_.isArray(params) && !PropertyList.isPropertyList(params)) {
return _.reduce(params, function (result, value, key) {
result && (result += AMPERSAND);
return result + QueryParam.unparseSingle({ key, value });
}, EMPTY);
}
// construct a query parameter string from the list, with considerations for disabled values
str = params.reduce(function (result, param) {
// bail out if param is disabled
if (param.disabled === true) { return result; }
// don't add '&' for the very first enabled param
if (firstEnabledParam) {
firstEnabledParam = false;
}
// add '&' before concatenating param
else {
result += AMPERSAND;
}
return result + QueryParam.unparseSingle(param);
}, EMPTY);
return str;
},
/**
* Takes a query param and converts to string
*
* @param {Object} obj -
* @returns {String}
*/
unparseSingle: function (obj) {
if (!obj) { return EMPTY; }
var key = obj.key,
value = obj.value,
result;
if (typeof key === STRING) {
result = normalizeParam(key, true);
}
else {
result = E;
}
if (typeof value === STRING) {
result += EQUALS + normalizeParam(value);
}
return result;
}
});
module.exports = {
QueryParam
};

View File

@@ -0,0 +1,188 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
VariableList = require('./variable-list').VariableList,
RequestAuth;
/**
* This defines the definition of the authentication method to be used.
*
* @typedef RequestAuth.definition
* @property {String=} type The Auth type to use. Check the names in {@link AuthTypes}
*
* @example <caption>Sample auth definition for Basic Auth</caption>
* {
* "type": "basic",
* "basic": [
* { "key": "username", "value": "postman" },
* { "key": "password", "value": "secrets" }
* ]
* }
*/
_.inherit((
/**
* A Postman Auth definition that comprehensively represents different types of auth mechanisms available.
*
* @constructor
* @extends {Property}
*
* @param {RequestAuth.definition} options Pass the initial definition of the Auth.
* @param {Property|PropertyList=} [parent] optionally pass the parent of this auth. aides variable resolution.
*
* @example <caption>Creating a request with two auth data and one selected</caption>
* var auth = new RequestAuth({
* type: 'digest',
*
* basic: [
* { key: "username", value: "postman" },
* { key: "password", value: "secrets" }
* ],
* digest: [
* { key: "nonce", value: "aef54cde" },
* { key: "realm", value: "items.x" }
* ]
* });
*
* // change the selected auth
* auth.use('basic');
*/
RequestAuth = function PostmanRequestAuth (options, parent) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
RequestAuth.super_.call(this, options);
// set the parent
parent && this.setParent(parent);
// set the type, if passed via options
if (_.has(options, 'type')) {
this.use(options.type);
}
// load all possible auth parameters from options
_.forEach(_.omit(options, 'type'), this.update.bind(this));
}), Property);
_.assign(RequestAuth.prototype, /** @lends RequestAuth.prototype */ {
/**
* Update the parameters of a specific authentication type. If none is provided then it uses the one marked as to be
* used.
*
* @param {VariableList|Array|Object} options -
* @param {String=} [type=this.type] -
*/
update (options, type) {
// update must have options
if (!_.isObject(options)) { return; }
// choose default from existing type if not provided
if (!type) { type = this.type; }
// validate type parameter and return in case type is not valid.
if (!RequestAuth.isValidType(type)) { return; }
var parameters = this[type];
// in case the type holder is not created, we create one and send the population variables
if (!VariableList.isVariableList(parameters)) {
// @todo optimise the handling of legacy object type auth parameters
parameters = this[type] = new VariableList(this);
parameters._postman_requestAuthType = type;
}
// we simply assimilate the new options either it is an array or an object
if (_.isArray(options) || VariableList.isVariableList(options)) {
parameters.assimilate(options);
}
else {
parameters.syncFromObject(options, false, false); // params: no need to track and no need to prune
}
},
/**
* Sets the authentication type to be used by this item.
*
* @param {String} type -
* @param {VariableList|Array|Object} options - note that options set here would replace all existing
* options for the particular auth
*/
use (type, options) {
if (!RequestAuth.isValidType(type)) { return; }
this.type = type; // set the type
var parameters = this[type];
if (!VariableList.isVariableList(parameters)) {
parameters = this[type] = new VariableList(this);
}
// we simply assimilate the new options either it is an array or an object
if (_.isArray(options) || VariableList.isVariableList(options)) {
parameters.assimilate(options);
}
else {
parameters.syncFromObject(options, false, false); // params: no need to track and no need to prune
}
},
/**
* @private
* @deprecated discontinued in v4.0
*/
current () {
throw new Error('`Request#current` has been discontinued, use `Request#parameters` instead.');
},
/**
* Returns the parameters of the selected auth type
*
* @returns {VariableList}
*/
parameters () {
return this[this.type];
},
/**
* Clears the definition of an auth type.
*
* @param {String} type -
*/
clear (type) {
if (!(RequestAuth.isValidType(type) && VariableList.isVariableList(this[type]))) {
return;
}
// clear the variable list
this[type].clear();
// if it is not a currently selected auth type, do not delete the variable list, but simply delete it
if (type !== this.type) {
delete this[type];
}
}
});
_.assign(RequestAuth, /** @lends RequestAuth */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'RequestAuth',
/**
* Determines whether an authentication type name is valid or not
*
* @param {String} type -
* @returns {Boolean}
*/
isValidType: function (type) {
// no auth name can be "type", else will have namespace collision with type selector
return _.isString(type) && (type !== 'type');
}
});
module.exports = {
RequestAuth
};

View File

@@ -0,0 +1,248 @@
var _ = require('../util').lodash,
PropertyBase = require('./property-base').PropertyBase,
PropertyList = require('./property-list').PropertyList,
QueryParam = require('./query-param').QueryParam,
FormParam = require('./form-param').FormParam,
EMPTY = '',
RequestBody;
/**
* @typedef RequestBody.definition
* @property {String} mode
* @property {String} raw
* @property {String} file
* @property {Object} graphql
* @property {Object[]} formdata
* @property {Object[]|String} urlencoded
*/
_.inherit((
/**
* RequestBody holds data related to the request body. By default, it provides a nice wrapper for url-encoded,
* form-data, and raw types of request bodies.
*
* @constructor
* @extends {PropertyBase}
*
* @param {Object} options -
*/
RequestBody = function PostmanRequestBody (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
RequestBody.super_.apply(this, arguments);
if (!options) { return; } // in case definition object is missing, there is no point moving forward
this.update(options);
}), PropertyBase);
_.assign(RequestBody.prototype, /** @lends RequestBody.prototype */ {
/**
* Set the content of this request data
*
* @param {Object} options -
*/
update (options) {
_.isString(options) && (options = { mode: 'raw', raw: options });
if (!options.mode) { return; } // need a valid mode @todo raise error?
var mode = RequestBody.MODES[options.mode.toString().toLowerCase()] || RequestBody.MODES.raw,
urlencoded = options.urlencoded,
formdata = options.formdata,
graphql = options.graphql,
file = options.file,
raw = options.raw;
// Handle URL Encoded data
if (options.urlencoded) {
_.isString(options.urlencoded) && (urlencoded = QueryParam.parse(options.urlencoded));
urlencoded = new PropertyList(QueryParam, this, urlencoded);
}
// Handle Form data
if (options.formdata) {
formdata = new PropertyList(FormParam, this, options.formdata);
}
// Handle GraphQL data
if (options.graphql) {
graphql = {
query: graphql.query,
operationName: graphql.operationName,
variables: graphql.variables
};
}
_.isString(options.file) && (file = { src: file });
// If mode is raw but options does not give raw content, set it to empty string
(mode === RequestBody.MODES.raw && !raw) && (raw = '');
// If mode is urlencoded but options does not provide any content, set it to an empty property list
(mode === RequestBody.MODES.urlencoded && !urlencoded) && (urlencoded = new PropertyList(QueryParam, this, []));
// If mode is formdata but options does not provide any content, set it to an empty property list
(mode === RequestBody.MODES.formdata && !formdata) && (formdata = new PropertyList(FormParam, this, []));
// If mode is graphql but options does not provide any content, set empty query
(mode === RequestBody.MODES.graphql && !graphql) && (graphql = {});
_.assign(this, /** @lends RequestBody.prototype */ {
/**
* Indicates the type of request data to use.
*
* @type {String}
*/
mode: mode,
/**
* If the request has raw body data associated with it, the data is held in this field.
*
* @type {String}
*/
raw: raw,
/**
* Any URL encoded body params go here.
*
* @type {PropertyList<QueryParam>}
*/
urlencoded: urlencoded,
/**
* Form data parameters for this request are held in this field.
*
* @type {PropertyList<FormParam>}
*/
formdata: formdata,
/**
* Holds a reference to a file which should be read as the RequestBody. It can be a file path (when used
* with Node) or a unique ID (when used with the browser).
*
* @note The reference stored here should be resolved by a resolver function (which should be provided to
* the Postman Runtime).
*/
file: file,
/**
* If the request has raw graphql data associated with it, the data is held in this field.
*
* @type {Object}
*/
graphql: graphql,
/**
* If the request has body Options associated with it, the data is held in this field.
*
* @type {Object}
*/
options: _.isObject(options.options) ? options.options : undefined,
/**
* Indicates whether to include body in request or not.
*
* @type {Boolean}
*/
disabled: options.disabled
});
},
/**
* Stringifies and returns the request body.
*
* @note FormData is not supported yet.
* @returns {*}
*/
toString () {
// Formdata. Goodluck.
if (this.mode === RequestBody.MODES.formdata || this.mode === RequestBody.MODES.file) {
// @todo: implement this, check if we need to return undefined or something.
return EMPTY;
}
if (this.mode === RequestBody.MODES.urlencoded) {
return PropertyList.isPropertyList(this.urlencoded) ? QueryParam.unparse(this.urlencoded.all()) :
((this.urlencoded && _.isFunction(this.urlencoded.toString)) ? this.urlencoded.toString() : EMPTY);
}
if (this.mode === RequestBody.MODES.raw) {
return (this.raw && _.isFunction(this.raw.toString)) ? this.raw.toString() : EMPTY;
}
return EMPTY;
},
/**
* If the request body is set to a mode, but does not contain data, then we should not be sending it.
*
* @returns {Boolean}
*/
isEmpty () {
var mode = this.mode,
data = mode && this[mode];
// bail out if there's no data for the selected mode
if (!data) {
return true;
}
// Handle file mode
// @note this is a legacy exception. ideally every individual data mode
// in future would declare its "empty state".
if (mode === RequestBody.MODES.file) {
return !(data.src || data.content);
}
if (_.isString(data)) {
return (data.length === 0);
}
if (_.isFunction(data.count)) { // handle for property lists
return (data.count() === 0);
}
return _.isEmpty(data); // catch all for remaining data modes
},
/**
* Convert the request body to JSON compatible plain object
*
* @returns {Object}
*/
toJSON () {
var obj = PropertyBase.toJSON(this);
// make sure that file content is removed because it is non-serializable ReadStream
_.unset(obj, 'file.content');
return obj;
}
});
_.assign(RequestBody, /** @lends RequestBody **/{
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'RequestBody',
/**
* @enum {string} MODES
*/
MODES: {
file: 'file',
formdata: 'formdata',
graphql: 'graphql',
raw: 'raw',
urlencoded: 'urlencoded'
}
});
module.exports = {
RequestBody
};

View File

@@ -0,0 +1,418 @@
var util = require('../util'),
_ = util.lodash,
PropertyBase = require('./property-base').PropertyBase,
Property = require('./property').Property,
Url = require('./url').Url,
ProxyConfig = require('./proxy-config').ProxyConfig,
Certificate = require('./certificate').Certificate,
HeaderList = require('./header-list').HeaderList,
RequestBody = require('./request-body').RequestBody,
RequestAuth = require('./request-auth').RequestAuth,
Request,
/**
* Default request method
*
* @private
* @const
* @type {String}
*/
DEFAULT_REQ_METHOD = 'GET',
/**
* Content length header name
*
* @private
* @const
* @type {String}
*/
CONTENT_LENGTH = 'Content-Length',
/**
* Single space
*
* @private
* @const
* @type {String}
*/
SP = ' ',
/**
* Carriage return + line feed
*
* @private
* @const
* @type {String}
*/
CRLF = '\r\n',
/**
* HTTP version
*
* @private
* @const
* @type {String}
*/
HTTP_X_X = 'HTTP/X.X',
/**
* @private
* @type {Boolean}
*/
supportsBuffer = (typeof Buffer !== undefined) && _.isFunction(Buffer.byteLength),
/**
* Source of request body size calculation.
* Either computed from body or used Content-Length header value.
*
* @private
* @const
* @type {Object}
*/
SIZE_SOURCE = {
computed: 'COMPUTED',
contentLength: 'CONTENT-LENGTH'
};
/**
* @typedef Request.definition
* @property {String|Url} url The URL of the request. This can be a {@link Url.definition} or a string.
* @property {String} method The request method, e.g: "GET" or "POST".
* @property {Array<Header.definition>} header The headers that should be sent as a part of this request.
* @property {RequestBody.definition} body The request body definition.
* @property {RequestAuth.definition} auth The authentication/signing information for this request.
* @property {ProxyConfig.definition} proxy The proxy information for this request.
* @property {Certificate.definition} certificate The certificate information for this request.
*/
_.inherit((
/**
* A Postman HTTP request object.
*
* @constructor
* @extends {Property}
* @param {Request.definition} options -
*/
Request = function PostmanRequest (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Request.super_.apply(this, arguments);
// if the definition is a string, it implies that this is a get of URL
(typeof options === 'string') && (options = {
url: options
});
// Create the default properties
_.assign(this, /** @lends Request.prototype */ {
/**
* @type {Url}
*/
url: new Url(),
/**
* @type {HeaderList}
*/
headers: new HeaderList(this, options && options.header),
// Although a similar check is being done in the .update call below, this handles falsy options as well.
/**
* @type {String}
* @todo: Clean this up
*/
// the negated condition is required to keep DEFAULT_REQ_METHOD as a fallback
method: _.has(options, 'method') && !_.isNil(options.method) ?
String(options.method).toUpperCase() : DEFAULT_REQ_METHOD
});
this.update(options);
}), Property);
_.assign(Request.prototype, /** @lends Request.prototype */ {
/**
* Updates the different properties of the request.
*
* @param {Request.definition} options -
*/
update: function (options) {
// Nothing to do
if (!options) { return; }
// The existing url is updated.
_.has(options, 'url') && this.url.update(options.url);
// The existing list of headers must be cleared before adding the given headers to it.
options.header && this.headers.repopulate(options.header);
// Only update the method if one is provided.
_.has(options, 'method') && (this.method = _.isNil(options.method) ?
DEFAULT_REQ_METHOD : String(options.method).toUpperCase());
// The rest of the properties are not assumed to exist so we merge in the defined ones.
_.mergeDefined(this, /** @lends Request.prototype */ {
/**
* @type {RequestBody|undefined}
*/
body: _.createDefined(options, 'body', RequestBody),
// auth is a special case, empty RequestAuth should not be created for falsy values
// to allow inheritance from parent
/**
* @type {RequestAuth}
*/
auth: options.auth ? new RequestAuth(options.auth) : undefined,
/**
* @type {ProxyConfig}
*/
proxy: options.proxy && new ProxyConfig(options.proxy),
/**
* @type {Certificate|undefined}
*/
certificate: options.certificate && new Certificate(options.certificate)
});
},
/**
* Sets authentication method for the request
*
* @param {?String|RequestAuth.definition} type -
* @param {VariableList=} [options] -
*
* @note This function was previously (in v2 of SDK) used to clone request and populate headers. Now it is used to
* only set auth information to request
*
* @note that ItemGroup#authorizeUsing depends on this function
*/
authorizeUsing: function (type, options) {
if (_.isObject(type) && _.isNil(options)) {
options = _.omit(type, 'type');
type = type.type;
}
// null = delete request
if (type === null) {
_.has(this, 'auth') && (delete this.auth);
return;
}
if (!RequestAuth.isValidType(type)) {
return;
}
// create a new authentication data
if (!this.auth) {
this.auth = new RequestAuth(null, this);
}
else {
this.auth.clear(type);
}
this.auth.use(type, options);
},
/**
* Returns an object where the key is a header name and value is the header value.
*
* @param {Object=} options -
* @param {Boolean} options.ignoreCase When set to "true", will ensure that all the header keys are lower case.
* @param {Boolean} options.enabled Only get the enabled headers
* @param {Boolean} options.multiValue When set to "true", duplicate header values will be stored in an array
* @param {Boolean} options.sanitizeKeys When set to "true", headers with falsy keys are removed
* @returns {Object}
* @note If multiple headers are present in the same collection with same name, but different case
* (E.g "x-forward-port" and "X-Forward-Port", and `options.ignoreCase` is set to true,
* the values will be stored in an array.
*/
getHeaders: function getHeaders (options) {
!options && (options = {});
// @note: options.multiValue will not be respected since, Header._postman_propertyAllowsMultipleValues
// gets higher precedence in PropertyLists.toObject.
// @todo: sanitizeKeys for headers by default.
return this.headers.toObject(options.enabled, !options.ignoreCase, options.multiValue, options.sanitizeKeys);
},
/**
* Calls the given callback on each Header object contained within the request.
*
* @param {Function} callback -
*/
forEachHeader: function forEachHeader (callback) {
this.headers.all().forEach(function (header) {
return callback(header, this);
}, this);
},
/**
* Adds a header to the PropertyList of headers.
*
* @param {Header| {key: String, value: String}} header Can be a {Header} object, or a raw header object.
*/
addHeader: function (header) {
this.headers.add(header);
},
/**
* Removes a header from the request.
*
* @param {String|Header} toRemove A header object to remove, or a string containing the header key.
* @param {Object} options -
* @param {Boolean} options.ignoreCase If set to true, ignores case while removing the header.
*/
removeHeader: function (toRemove, options) {
toRemove = _.isString(toRemove) ? toRemove : toRemove.key;
options = options || {};
if (!toRemove) { // Nothing to remove :(
return;
}
options.ignoreCase && (toRemove = toRemove.toLowerCase());
this.headers.remove(function (header) {
var key = options.ignoreCase ? header.key.toLowerCase() : header.key;
return key === toRemove;
});
},
/**
* Updates or inserts the given header.
*
* @param {Object} header -
*/
upsertHeader: function (header) {
if (!(header && header.key)) { return; } // if no valid header is provided, do nothing
var existing = this.headers.find({ key: header.key });
if (!existing) {
return this.headers.add(header);
}
existing.value = header.value;
},
/**
* Add query parameters to the request.
*
* @todo: Rename this?
* @param {Array<QueryParam>|String} params -
*/
addQueryParams: function (params) {
this.url.addQueryParams(params);
},
/**
* Removes parameters passed in params.
*
* @param {String|Array} params -
*/
removeQueryParams: function (params) {
this.url.removeQueryParams(params);
},
/**
* Get the request size by computing the headers and body or using the
* actual content length header once the request is sent.
*
* @returns {Object}
*/
size: function () {
var contentLength = this.headers.get(CONTENT_LENGTH),
requestTarget = this.url.getPathWithQuery(),
bodyString,
sizeInfo = {
body: 0,
header: 0,
total: 0,
source: SIZE_SOURCE.computed
};
// if 'Content-Length' header is present, we take body as declared by
// the client(postman-request or user-defined). else we need to compute the same.
if (contentLength && util.isNumeric(contentLength)) {
sizeInfo.body = parseInt(contentLength, 10);
sizeInfo.source = SIZE_SOURCE.contentLength;
}
// otherwise, if body is defined, we calculate the length of the body
else if (this.body) {
// @note body.toString() returns E for formdata or file mode
bodyString = this.body.toString();
sizeInfo.body = supportsBuffer ? Buffer.byteLength(bodyString) :
/* istanbul ignore next */
bodyString.length;
}
// https://tools.ietf.org/html/rfc7230#section-3
// HTTP-message = start-line (request-line / status-line)
// *( header-field CRLF )
// CRLF
// [ message-body ]
// request-line = method SP request-target SP HTTP-version CRLF
sizeInfo.header = (this.method + SP + requestTarget + SP + HTTP_X_X + CRLF + CRLF).length +
this.headers.contentSize();
// compute the approximate total body size by adding size of header and body
sizeInfo.total = (sizeInfo.body || 0) + (sizeInfo.header);
return sizeInfo;
},
/**
* Converts the Request to a plain JavaScript object, which is also how the request is
* represented in a collection file.
*
* @returns {{url: (*|String), method: *, header: (undefined|*), body: *, auth: *, certificate: *}}
*/
toJSON: function () {
var obj = PropertyBase.toJSON(this);
// remove header array if blank
if (_.isArray(obj.header) && !obj.header.length) {
delete obj.header;
}
return obj;
},
/**
* Creates a clone of this request
*
* @returns {Request}
*/
clone: function () {
return new Request(this.toJSON());
}
});
_.assign(Request, /** @lends Request */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Request',
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isRequest: function (obj) {
return Boolean(obj) && ((obj instanceof Request) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Request._postman_propertyName));
}
});
module.exports = {
Request
};

View File

@@ -0,0 +1,613 @@
var util = require('../util'),
_ = util.lodash,
httpReasons = require('http-reasons'),
LJSON = require('liquid-json'),
Property = require('./property').Property,
PropertyBase = require('./property-base').PropertyBase,
Request = require('./request').Request,
CookieList = require('./cookie-list').CookieList,
HeaderList = require('./header-list').HeaderList,
contentInfo = require('../content-info').contentInfo,
/**
* @private
* @const
* @type {string}
*/
E = '',
/**
* @private
* @const
* @type {String}
*/
HEADER = 'header',
/**
* @private
* @const
* @type {String}
*/
BODY = 'body',
/**
* @private
* @const
* @type {String}
*/
GZIP = 'gzip',
/**
* @private
* @const
* @type {String}
*/
CONTENT_ENCODING = 'Content-Encoding',
/**
* @private
* @const
* @type {String}
*/
CONTENT_LENGTH = 'Content-Length',
/**
* @private
* @const
* @type {string}
*/
BASE64 = 'base64',
/**
* @private
* @const
* @type {string}
*/
STREAM_TYPE_BUFFER = 'Buffer',
/**
* @private
* @const
* @type {string}
*/
STREAM_TYPE_BASE64 = 'Base64',
/**
* @private
* @const
* @type {string}
*/
FUNCTION = 'function',
/**
* @private
* @const
* @type {string}
*/
STRING = 'string',
/**
* @private
* @const
* @type {String}
*/
HTTP_X_X = 'HTTP/X.X',
/**
* @private
* @const
* @type {String}
*/
SP = ' ',
/**
* @private
* @const
* @type {String}
*/
CRLF = '\r\n',
/**
* @private
* @const
* @type {RegExp}
*/
REGEX_JSONP_LEFT = /^[^{(].*\(/,
/**
* @private
* @const
* @type {RegExp}
*/
REGEX_JSONP_RIGHT = /\)[^}].*$|\)$/,
/**
* Remove JSON padded string to pure JSON
*
* @private
* @param {String} str -
* @returns {String}
*/
stripJSONP = function (str) {
return str.replace(REGEX_JSONP_LEFT, E).replace(REGEX_JSONP_RIGHT, E);
},
/**
* @private
* @type {Boolean}
*/
supportsBuffer = (typeof Buffer !== undefined) && _.isFunction(Buffer.byteLength),
/**
* Normalizes an input Buffer, Buffer.toJSON() or base64 string into a Buffer or ArrayBuffer.
*
* @private
* @param {Buffer|Object} stream - An instance of Buffer, Buffer.toJSON(), or Base64 string
* @returns {Buffer|ArrayBuffer|undefined}
*/
normalizeStream = function (stream) {
if (!stream) { return; }
// create buffer from buffer's JSON representation
if (stream.type === STREAM_TYPE_BUFFER && _.isArray(stream.data)) {
// @todo Add tests for Browser environments, where ArrayBuffer is returned instead of Buffer
return typeof Buffer === FUNCTION ? Buffer.from(stream.data) : new Uint8Array(stream.data).buffer;
}
// create buffer from base64 string
if (stream.type === STREAM_TYPE_BASE64 && typeof stream.data === STRING) {
return Buffer.from(stream.data, BASE64);
}
// probably it's already of type buffer
return stream;
},
Response; // constructor
/**
* @typedef Response.definition
* @property {Number} code - define the response code
* @property {String=} [reason] - optionally, if the response has a non-standard response code reason, provide it here
* @property {Array<Header.definition>} [header]
* @property {Array<Cookie.definition>} [cookie]
* @property {String} [body]
* @property {Buffer|ArrayBuffer} [stream]
* @property {Number} responseTime
*
* @todo pluralise `header`, `cookie`
*/
_.inherit((
/**
* Response holds data related to the request body. By default, it provides a nice wrapper for url-encoded,
* form-data, and raw types of request bodies.
*
* @constructor
* @extends {Property}
*
* @param {Response.definition} options -
*/
Response = function PostmanResponse (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Response.super_.apply(this, arguments);
this.update(options || {});
}), Property);
_.assign(Response.prototype, /** @lends Response.prototype */ {
update (options) {
// options.stream accepts Buffer, Buffer.toJSON() or base64 string
// @todo this temporarily doubles the memory footprint (options.stream + generated buffer).
var stream = normalizeStream(options.stream);
_.mergeDefined((this._details = _.clone(httpReasons.lookup(options.code))), {
name: _.choose(options.reason, options.status),
code: options.code,
standardName: this._details.name
});
_.mergeDefined(this, /** @lends Response.prototype */ {
/**
* @type {Request}
*/
originalRequest: options.originalRequest ? new Request(options.originalRequest) : undefined,
/**
* @type {String}
*/
status: this._details.name,
/**
* @type {Number}
*/
code: options.code,
/**
* @type {HeaderList}
*/
headers: new HeaderList(this, options.header),
/**
* @type {String}
*/
body: options.body,
/**
* @private
*
* @type {Buffer|UInt8Array}
*/
stream: (options.body && _.isObject(options.body)) ? options.body : stream,
/**
* @type {CookieList}
*/
cookies: new CookieList(this, options.cookie),
/**
* Time taken for the request to complete.
*
* @type {Number}
*/
responseTime: options.responseTime,
/**
* @private
* @type {Number}
*/
responseSize: stream && stream.byteLength
});
}
});
_.assign(Response.prototype, /** @lends Response.prototype */ {
/**
* Defines that this property requires an ID field
*
* @private
* @readOnly
*/
_postman_propertyRequiresId: true,
/**
* Convert this response into a JSON serializable object. The _details meta property is omitted.
*
* @returns {Object}
*
* @todo consider switching to a different response buffer (stream) representation as Buffer.toJSON
* appears to cause multiple performance issues.
*/
toJSON: function () {
// @todo benchmark PropertyBase.toJSON, response Buffer.toJSON or _.cloneElement might
// be the bottleneck.
var response = PropertyBase.toJSON(this);
response._details && (delete response._details);
return response;
},
/**
* Get the http response reason phrase based on the current response code.
*
* @returns {String|undefined}
*/
reason: function () {
return this.status || httpReasons.lookup(this.code).name;
},
/**
* Creates a JSON representation of the current response details, and returns it.
*
* @returns {Object} A set of response details, including the custom server reason.
* @private
*/
details: function () {
if (!this._details || this._details.code !== this.code) {
this._details = _.clone(httpReasons.lookup(this.code));
this._details.code = this.code;
this._details.standardName = this._details.name;
}
return _.clone(this._details);
},
/**
* Get the response body as a string/text.
*
* @returns {String|undefined}
*/
text: function () {
return (this.stream ? util.bufferOrArrayBufferToString(this.stream, this.contentInfo().charset) : this.body);
},
/**
* Get the response body as a JavaScript object. Note that it throws an error if the response is not a valid JSON
*
* @param {Function=} [reviver] -
* @param {Boolean} [strict=false] Specify whether JSON parsing will be strict. This will fail on comments and BOM
* @example
* // assuming that the response is stored in a collection instance `myCollection`
* var response = myCollection.items.one('some request').responses.idx(0),
* jsonBody;
* try {
* jsonBody = response.json();
* }
* catch (e) {
* console.log("There was an error parsing JSON ", e);
* }
* // log the root-level keys in the response JSON.
* console.log('All keys in json response: ' + Object.keys(json));
*
* @returns {Object}
*/
json: function (reviver, strict) {
return LJSON.parse(this.text(), reviver, strict);
},
/**
* Get the JSON from response body that reuturns JSONP response.
*
* @param {Function=} [reviver] -
* @param {Boolean} [strict=false] Specify whether JSON parsing will be strict. This will fail on comments and BOM
*
* @throws {JSONError} when response body is empty
*/
jsonp: function (reviver, strict) {
return LJSON.parse(stripJSONP(this.text() || /* istanbul ignore next */ E), reviver, strict);
},
/**
* Extracts mime type, format, charset, extension and filename of the response content
* A fallback of default filename is given, if filename is not present in content-disposition header
*
* @returns {Response.ResponseContentInfo} - contentInfo for the response
*/
contentInfo: function () {
return contentInfo(this);
},
/**
* @private
* @deprecated discontinued in v4.0
*/
mime: function () {
throw new Error('`Response#mime` has been discontinued, use `Response#contentInfo` instead.');
},
/**
* Converts the response to a dataURI that can be used for storage or serialisation. The data URI is formed using
* the following syntax `data:<content-type>;baseg4, <base64-encoded-body>`.
*
* @returns {String}
* @todo write unit tests
*/
dataURI: function () {
const { contentType } = this.contentInfo();
// if there is no mime detected, there is no accurate way to render this thing
/* istanbul ignore if */
if (!contentType) {
return E;
}
// we create the body string first from stream and then fallback to body
return `data:${contentType};base64, ` + ((!_.isNil(this.stream) &&
util.bufferOrArrayBufferToBase64(this.stream)) || (!_.isNil(this.body) && util.btoa(this.body)) || E);
},
/**
* Get the response size by computing the same from content length header or using the actual response body.
*
* @returns {Number}
* @todo write unit tests
*/
size: function () {
var sizeInfo = {
body: 0,
header: 0,
total: 0
},
contentEncoding = this.headers.get(CONTENT_ENCODING),
contentLength = this.headers.get(CONTENT_LENGTH),
isCompressed = false,
byteLength;
// if server sent encoded data, we should first try deriving length from headers
if (_.isString(contentEncoding)) {
// desensitise case of content encoding
contentEncoding = contentEncoding.toLowerCase();
// eslint-disable-next-line lodash/prefer-includes
isCompressed = (contentEncoding.indexOf('gzip') > -1) || (contentEncoding.indexOf('deflate') > -1);
}
// if 'Content-Length' header is present and encoding is of type gzip/deflate, we take body as declared by
// server. else we need to compute the same.
if (contentLength && isCompressed && util.isNumeric(contentLength)) {
sizeInfo.body = _.parseInt(contentLength, 10);
}
// if there is a stream defined which looks like buffer, use it's data and move on
else if (this.stream) {
byteLength = this.stream.byteLength;
sizeInfo.body = util.isNumeric(byteLength) ? byteLength :
/* istanbul ignore next */
0;
}
// otherwise, if body is defined, we try get the true length of the body
else if (!_.isNil(this.body)) {
sizeInfo.body = supportsBuffer ? Buffer.byteLength(this.body.toString()) :
/* istanbul ignore next */
this.body.toString().length;
}
// size of header is added
// https://tools.ietf.org/html/rfc7230#section-3
// HTTP-message = start-line (request-line / status-line)
// *( header-field CRLF )
// CRLF
// [ message-body ]
// status-line = HTTP-version SP status-code SP reason-phrase CRLF
sizeInfo.header = (HTTP_X_X + SP + this.code + SP + this.reason() + CRLF + CRLF).length +
this.headers.contentSize();
// compute the approximate total body size by adding size of header and body
sizeInfo.total = (sizeInfo.body || 0) + (sizeInfo.header);
return sizeInfo;
},
/**
* Returns the response encoding defined as header or detected from body.
*
* @private
* @returns {Object} - {format: string, source: string}
*/
encoding: function () {
var contentEncoding = this.headers.get(CONTENT_ENCODING),
body = this.stream || this.body,
source;
if (contentEncoding) {
source = HEADER;
}
// if the encoding is not found, we check
else if (body) { // @todo add detection for deflate
// eslint-disable-next-line lodash/prefer-matches
if (body[0] === 0x1F && body[1] === 0x8B && body[2] === 0x8) {
contentEncoding = GZIP;
}
if (contentEncoding) {
source = BODY;
}
}
return {
format: contentEncoding,
source: source
};
}
});
_.assign(Response, /** @lends Response */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Response',
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isResponse: function (obj) {
return Boolean(obj) && ((obj instanceof Response) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Response._postman_propertyName));
},
/**
* Converts the response object from the request module to the postman responseBody format
*
* @param {Object} response The response object, as received from the request module
* @param {Object} cookies -
* @returns {Object} The transformed responseBody
* @todo Add a key: `originalRequest` to the returned object as well, referring to response.request
*/
createFromNode: function (response, cookies) {
return new Response({
cookie: cookies,
body: response.body.toString(),
stream: response.body,
header: response.headers,
code: response.statusCode,
status: response.statusMessage,
responseTime: response.elapsedTime
});
},
/**
* @private
* @deprecated discontinued in v4.0
*/
mimeInfo: function () {
throw new Error('`Response.mimeInfo` has been discontinued, use `Response#contentInfo` instead.');
},
/**
* Returns the durations of each request phase in milliseconds
*
* @typedef Response.timings
* @property {Number} start - timestamp of the request sent from the client (in Unix Epoch milliseconds)
* @property {Object} offset - event timestamps in millisecond resolution relative to start
* @property {Number} offset.request - timestamp of the start of the request
* @property {Number} offset.socket - timestamp when the socket is assigned to the request
* @property {Number} offset.lookup - timestamp when the DNS has been resolved
* @property {Number} offset.connect - timestamp when the server acknowledges the TCP connection
* @property {Number} offset.secureConnect - timestamp when secure handshaking process is completed
* @property {Number} offset.response - timestamp when the first bytes are received from the server
* @property {Number} offset.end - timestamp when the last bytes of the response are received
* @property {Number} offset.done - timestamp when the response is received at the client
*
* @note If there were redirects, the properties reflect the timings
* of the final request in the redirect chain
*
* @param {Response.timings} timings -
* @returns {Object}
*
* @example Output
* Request.timingPhases(timings);
* {
* prepare: Number, // duration of request preparation
* wait: Number, // duration of socket initialization
* dns: Number, // duration of DNS lookup
* tcp: Number, // duration of TCP connection
* secureHandshake: Number, // duration of secure handshake
* firstByte: Number, // duration of HTTP server response
* download: Number, // duration of HTTP download
* process: Number, // duration of response processing
* total: Number // duration entire HTTP round-trip
* }
*
* @note if there were redirects, the properties reflect the timings of the
* final request in the redirect chain.
*/
timingPhases: function (timings) {
// bail out if timing information is not provided
if (!(timings && timings.offset)) {
return;
}
var phases,
offset = timings.offset;
// REFER: https://github.com/postmanlabs/postman-request/blob/v2.88.1-postman.5/request.js#L996
phases = {
prepare: offset.request,
wait: offset.socket - offset.request,
dns: offset.lookup - offset.socket,
tcp: offset.connect - offset.lookup,
firstByte: offset.response - offset.connect,
download: offset.end - offset.response,
process: offset.done - offset.end,
total: offset.done
};
if (offset.secureConnect) {
phases.secureHandshake = offset.secureConnect - offset.connect;
phases.firstByte = offset.response - offset.secureConnect;
}
return phases;
}
});
module.exports = {
Response
};

View File

@@ -0,0 +1,109 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
Url = require('./url').Url,
Script,
SCRIPT_NEWLINE_PATTERN = /\r?\n/g;
_.inherit((
/**
* Postman scripts that are executed upon events on a collection / request such as test and pre request.
*
* @constructor
* @extends {Property}
*
* @param {Object} options -
*/
Script = function PostmanScript (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Script.super_.apply(this, arguments);
options && this.update(options);
}), Property);
_.assign(Script.prototype, /** @lends Script.prototype */ {
/**
* Defines whether this property instances requires an id
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyRequiresId: true,
/**
* Converts the script lines array to a single source string.
*
* @returns {String}
*/
toSource: function () {
return this.exec ? this.exec.join('\n') : undefined;
},
/**
* Updates the properties of a Script.
*
* @param {Object} [options] -
* @param {String} [options.type] Script type
* @param {String} [options.src] Script source url
* @param {String[]|String} [options.exec] Script to execute
*/
update: function (options) {
// no splitting is being done here, as string scripts are split right before assignment below anyway
(_.isString(options) || _.isArray(options)) && (options = { exec: options });
if (!options) { return; } // in case definition object is missing, there is no point moving forward
// create the request property
/**
* @augments {Script.prototype}
* @type {string}
*/
this.type = options.type || 'text/javascript';
_.has(options, 'src') && (
/**
* @augments {Script.prototype}
* @type {Url}
*/
this.src = new Url(options.src)
);
if (!this.src && _.has(options, 'exec')) {
/**
* @augments {Script.prototype}
* @type {Array<string>}
*/
this.exec = _.isString(options.exec) ? options.exec.split(SCRIPT_NEWLINE_PATTERN) :
_.isArray(options.exec) ? options.exec : undefined;
}
}
});
_.assign(Script, /** @lends Script */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Script',
/**
* Check whether an object is an instance of {@link ItemGroup}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isScript: function (obj) {
return Boolean(obj) && ((obj instanceof Script) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Script._postman_propertyName));
}
});
module.exports = {
Script
};

443
node_modules/postman-collection/lib/collection/url.js generated vendored Normal file
View File

@@ -0,0 +1,443 @@
var _ = require('../util').lodash,
url_parse = require('postman-url-encoder/parser').parse,
PropertyBase = require('./property-base').PropertyBase,
QueryParam = require('./query-param').QueryParam,
PropertyList = require('./property-list').PropertyList,
VariableList = require('./variable-list').VariableList,
E = '',
STRING = 'string',
FUNCTION = 'function',
PROTOCOL_HTTPS = 'https',
PROTOCOL_HTTP = 'http',
HTTPS_PORT = '443',
HTTP_PORT = '80',
PATH_SEPARATOR = '/',
PATH_VARIABLE_IDENTIFIER = ':',
PORT_SEPARATOR = ':',
DOMAIN_SEPARATOR = '.',
PROTOCOL_SEPARATOR = '://',
AUTH_SEPARATOR = ':',
AUTH_CREDENTIALS_SEPARATOR = '@',
QUERY_SEPARATOR = '?',
SEARCH_SEPARATOR = '#',
DEFAULT_PROTOCOL = PROTOCOL_HTTP + PROTOCOL_SEPARATOR,
MATCH_1 = '$1',
regexes = {
trimPath: /^\/((.+))$/,
splitDomain: /\.(?![^{]*\}{2})/g
},
Url;
_.inherit((
/**
* Defines a URL.
*
* @constructor
* @extends {PropertyBase}
* @param {Object|String} options -
*/
Url = function PostmanUrl (options) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Url.super_.apply(this, arguments);
// create the url properties
this.update(options);
}), PropertyBase);
_.assign(Url.prototype, /** @lends Url.prototype */ {
/**
* Set a URL.
*
* @draft
* @param {String|Object} url -
*/
update (url) {
!url && (url = E);
var parsedUrl = _.isString(url) ? Url.parse(url) : url,
auth = parsedUrl.auth,
protocol = parsedUrl.protocol,
port = parsedUrl.port,
path = parsedUrl.path,
hash = parsedUrl.hash,
host = parsedUrl.host,
query = parsedUrl.query,
variable = parsedUrl.variable;
// convert object based query string to array
// @todo: create a key value parser
if (query) {
if (_.isString(query)) {
query = QueryParam.parse(query);
}
if (!_.isArray(query) && _.keys(query).length) {
query = _.map(_.keys(query), function (key) {
return {
key: key,
value: query[key]
};
});
}
}
// backward compatibility with path variables being storing thins with `id`
if (_.isArray(variable)) {
variable = _.map(variable, function (v) {
_.isObject(v) && (v.key = v.key || v.id); // @todo Remove once path variables are deprecated
return v;
});
}
// expand string path name
if (_.isString(path)) {
path && (path = path.replace(regexes.trimPath, MATCH_1)); // remove leading slash for valid path
// if path is blank string, we set it to undefined, if '/' then single blank string array
path = path ? (path === PATH_SEPARATOR ? [E] : path.split(PATH_SEPARATOR)) : undefined;
}
// expand host string
_.isString(host) && (host = host.split(regexes.splitDomain));
_.assign(this, /** @lends Url.prototype */ {
/**
* @type {{ user: String, password: String }}
*/
auth: auth,
/**
* @type {String}
*/
protocol: protocol,
/**
* @type {String}
*/
port: port,
/**
* @type {Array<String>}
*/
path: path,
/**
* @type {String}
*/
hash: hash,
/**
* @type {Array<String>}
*/
host: host,
/**
* @type {PropertyList<QueryParam>}
*
* @todo consider setting this as undefined in v4 otherwise it's
* difficult to detect URL like `localhost/?`.
* currently it's replying upon a single member with empty key.
*/
query: new PropertyList(QueryParam, this, query || []),
/**
* @type {VariableList}
*/
variables: new VariableList(this, variable || [])
});
},
/**
* Add query parameters to the URL.
*
* @param {Object|String} params Key value pairs to add to the URL.
*/
addQueryParams (params) {
params = _.isString(params) ? QueryParam.parse(params) : params;
this.query.populate(params);
},
/**
* Removes query parameters from the URL.
*
* @param {Array<QueryParam>|Array<String>|String} params Params should be an array of strings, or an array of
* actual query parameters, or a string containing the parameter key.
* @note Input should *not* be a query string.
*/
removeQueryParams (params) {
params = _.isArray(params) ? _.map(params, function (param) {
return param.key ? param.key : param;
}) : [params];
this.query.remove(function (param) {
return _.includes(params, param.key);
});
},
/**
* @private
* @deprecated discontinued in v4.0
*/
getRaw () {
throw new Error('`Url#getRaw` has been discontinued, use `Url#toString` instead.');
},
/**
* Unparses a {PostmanUrl} into a string.
*
* @param {Boolean=} forceProtocol - Forces the URL to have a protocol
* @returns {String}
*/
toString (forceProtocol) {
var rawUrl = E,
protocol = this.protocol,
queryString,
authString;
forceProtocol && !protocol && (protocol = DEFAULT_PROTOCOL);
if (protocol) {
rawUrl += (_.endsWith(protocol, PROTOCOL_SEPARATOR) ? protocol : protocol + PROTOCOL_SEPARATOR);
}
if (this.auth) {
if (typeof this.auth.user === STRING) {
authString = this.auth.user;
}
if (typeof this.auth.password === STRING) {
!authString && (authString = E);
authString += AUTH_SEPARATOR + this.auth.password;
}
if (typeof authString === STRING) {
rawUrl += authString + AUTH_CREDENTIALS_SEPARATOR;
}
}
if (this.host) {
rawUrl += this.getHost();
}
if (typeof _.get(this.port, 'toString') === FUNCTION) {
rawUrl += PORT_SEPARATOR + this.port.toString();
}
if (this.path) {
rawUrl += this.getPath();
}
if (this.query && this.query.count()) {
queryString = this.getQueryString();
// either all the params are disabled or a single param is like { key: '' } (http://localhost?)
// in that case, query separator ? must be included in the raw URL.
// @todo return undefined or string from getQueryString method to distinguish
// no params vs empty param.
if (queryString === E) {
// check if there's any enabled param, if so, set queryString to empty string
// otherwise (all disabled), it will be set as undefined
queryString = this.query.find(function (param) { return !(param && param.disabled); }) && E;
}
if (typeof queryString === STRING) {
rawUrl += QUERY_SEPARATOR + queryString;
}
}
if (typeof this.hash === STRING) {
rawUrl += SEARCH_SEPARATOR + this.hash;
}
return rawUrl;
},
/**
* Returns the request path, with a leading '/'.
*
* @param {?Boolean=} [unresolved=false] -
* @returns {String}
*/
getPath (unresolved) {
// for unresolved case, this is super simple as that is how raw data is stored
if (unresolved) {
return PATH_SEPARATOR + this.path.join(PATH_SEPARATOR);
}
var self = this,
segments;
segments = _.transform(this.path, function (res, segment) {
var variable;
// check if the segment has path variable prefix followed by the variable name.
if (_.startsWith(segment, PATH_VARIABLE_IDENTIFIER) && segment !== PATH_VARIABLE_IDENTIFIER) {
variable = self.variables.one(segment.slice(1)); // remove path variable prefix.
}
variable = variable && variable.valueOf && variable.valueOf();
res.push(_.isString(variable) ? variable : segment);
}, []);
return PATH_SEPARATOR + segments.join(PATH_SEPARATOR); // add leading slash
},
/**
* Returns the stringified query string for this URL.
*
* @returns {String}
*/
getQueryString () {
if (!this.query.count()) {
return E;
}
return QueryParam.unparse(this.query.all());
},
/**
* Returns the complete path, including the query string.
*
* @returns {*|String}
* @example /something/postman?hi=notbye
*/
getPathWithQuery () {
var path = this.getPath();
// check count first so that, we can ensure that ba `?` is always appended, even if a blank query string exists
if (this.query.count()) {
path += (QUERY_SEPARATOR + this.getQueryString());
}
return path;
},
/**
* Returns the host part of the URL
*
* @returns {String}
*/
getHost () {
if (!this.host) {
return E;
}
return _.isArray(this.host) ? this.host.join(DOMAIN_SEPARATOR) : this.host.toString();
},
/**
* Returns the host *and* port (if any), separated by a ":"
*
* @param {?Boolean} [forcePort=false] - forces the port to be added even for the protocol default ones (89, 443)
* @returns {String}
*/
getRemote (forcePort) {
var host = this.getHost(),
port = this.port && this.port.toString();
if (forcePort && !port) { // this (!port) works since it assumes port as a string
port = this.protocol && (this.protocol === PROTOCOL_HTTPS) ? HTTPS_PORT : HTTP_PORT;
}
return port ? (host + PORT_SEPARATOR + port) : host;
},
/**
* Returns a OAuth1.0-a compatible representation of the request URL, also called "Base URL".
* For details, http://oauth.net/core/1.0a/#anchor13
*
* todo: should we ignore the auth parameters of the URL or not? (the standard does not mention them)
* we currently are.
*
* @private
* @returns {String}
*
* @deprecated since v3.5 in favour of getBaseUrl
* @note not discontinue yet because it's used in Twitter APIs public collections
*/
getOAuth1BaseUrl () {
var protocol = this.protocol || PROTOCOL_HTTP,
port = this.port ? this.port.toString() : undefined,
host = ((port === HTTP_PORT ||
port === HTTPS_PORT ||
port === undefined) && this.host.join(DOMAIN_SEPARATOR)) || (this.host.join(DOMAIN_SEPARATOR) +
PORT_SEPARATOR + port),
path = this.getPath();
protocol = (_.endsWith(protocol, PROTOCOL_SEPARATOR) ? protocol : protocol + PROTOCOL_SEPARATOR);
return protocol.toLowerCase() + host.toLowerCase() + path;
}
});
_.assign(Url, /** @lends Url */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Url',
/**
* Parses a string to a PostmanUrl, decomposing the URL into it's constituent parts,
* such as path, host, port, etc.
*
* @param {String} url -
* @returns {Object}
*/
parse: function (url) {
url = url_parse(url);
var pathVariables,
pathVariableKeys = {};
if (url.auth) {
url.auth = {
user: url.auth[0],
password: url.auth[1]
};
}
if (url.query) {
url.query = url.query.map(QueryParam.parseSingle);
}
// extract path variables
pathVariables = _.transform(url.path, function (res, segment) {
// check if the segment has path variable prefix followed by the variable name and
// the variable is not already added in the list.
if (_.startsWith(segment, PATH_VARIABLE_IDENTIFIER) &&
segment !== PATH_VARIABLE_IDENTIFIER &&
!pathVariableKeys[segment]) {
pathVariableKeys[segment] = true;
res.push({ key: segment.slice(1) }); // remove path variable prefix.
}
}, []);
url.variable = pathVariables.length ? pathVariables : undefined;
return url;
},
/**
* Checks whether an object is a Url
*
* @param {*} obj -
* @returns {Boolean}
*/
isUrl: function (obj) {
return Boolean(obj) && ((obj instanceof Url) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Url._postman_propertyName));
}
});
module.exports = {
Url
};

View File

@@ -0,0 +1,196 @@
var _ = require('../util').lodash,
PropertyList = require('./property-list').PropertyList,
Property = require('./property').Property,
Variable = require('./variable').Variable,
VariableList;
_.inherit((
/**
* @constructor
* @extends {PropertyList}
*
* @param {Property} parent -
* @param {Object|Array} populate -
*/
VariableList = function PostmanVariableList (parent, populate) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
VariableList.super_.call(this, Variable, parent, populate);
}), PropertyList);
_.assign(VariableList.prototype, /** @lends VariableList.prototype */ {
/**
* Replaces the variable tokens inside a string with its actual values.
*
* @param {String} str -
* @param {Object} [overrides] - additional objects to lookup for variable values
* @returns {String}
*/
replace (str, overrides) {
return Property.replaceSubstitutions(str, this, overrides);
},
/**
* Recursively replace strings in an object with instances of variables. Note that it clones the original object. If
* the `mutate` param is set to true, then it replaces the same object instead of creating a new one.
*
* @param {Array|Object} obj -
* @param {?Array<Object>=} [overrides] - additional objects to lookup for variable values
* @param {Boolean=} [mutate=false] -
* @returns {Array|Object}
*/
substitute (obj, overrides, mutate) {
var resolutionQueue = [], // we use this to store the queue of variable hierarchy
// this is an intermediate object to stimulate a property (makes the do-while loop easier)
variableSource = {
variables: this,
__parent: this.__parent
};
do { // iterate and accumulate as long as you find `.variables` in parent tree
variableSource.variables && resolutionQueue.push(variableSource.variables);
variableSource = variableSource.__parent;
} while (variableSource);
variableSource = null; // cautious cleanup
return Property.replaceSubstitutionsIn(obj, _.union(resolutionQueue, overrides), mutate);
},
/**
* Using this function, one can sync the values of this variable list from a reference object.
*
* @param {Object} obj -
* @param {Boolean=} track -
* @param {Boolean} [prune=true] -
*
* @returns {Object}
*/
syncFromObject (obj, track, prune) {
var list = this,
ops = track && {
created: [],
updated: [],
deleted: []
},
indexer = list._postman_listIndexKey,
tmp;
if (!_.isObject(obj)) { return ops; }
// ensure that all properties in the object is updated in this list
_.forOwn(obj, function (value, key) {
// we need to create new variable if exists or update existing
if (list.has(key)) {
list.one(key).set(value);
ops && ops.updated.push(key);
}
else {
tmp = { value };
tmp[indexer] = key;
list.add(tmp);
tmp = null;
ops && ops.created.push(key);
}
});
// now remove any variable that is not in source object
// @note - using direct `this.reference` list of keys here so that we can mutate the list while iterating
// on it
if (prune !== false) {
_.forEach(list.reference, function (value, key) {
if (_.has(obj, key)) { return; } // de not delete if source obj has this variable
list.remove(key); // use PropertyList functions to remove so that the .members array is cleared too
ops && ops.deleted.push(key);
});
}
return ops;
},
/**
* Transfer all variables from this list to an object
*
* @param {Object=} [obj] -
* @returns {Object}
*/
syncToObject (obj) {
var list = this;
// in case user did not provide an object to mutate, create a new one
!_.isObject(obj) && (obj = {});
// delete extra variables from object that are not present in list
_.forEach(obj, function (value, key) {
!_.has(list.reference, key) && (delete obj[key]);
});
// we first sync all variables in this list to the object
list.each(function (variable) {
obj[variable.key] = variable.valueOf();
});
return obj;
},
/**
* Fetches a variable and normalize its reference if disabled.
* This updates the disabled variable `reference` in VariableList with its
* last enabled duplicate(if found) in the `members` list.
*
* @private
* @param {String} variableName - The name of the variable to get
* @returns {Variable} - In case of duplicates, returns last enabled
*/
oneNormalizedVariable (variableName) {
var indexKey = this._postman_listIndexKey, // `key` for Variable
variable = this.reference[variableName],
i;
if (variable && !variable.disabled) {
return variable;
}
// traverse the members list in reverse direction in order to find the last enabled
for (i = this.members.length - 1; i >= 0; i--) {
variable = this.members[i];
if (variable[indexKey] === variableName && !variable.disabled) {
// update the input variable reference if comparison is not disabled
this.reference[variableName] = variable;
break; // return updated reference variable
}
}
return this.reference[variableName];
}
});
_.assign(VariableList, /** @lends VariableList */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*
* @note that this is directly accessed only in case of VariableList from _.findValue lodash util mixin
*/
_postman_propertyName: 'VariableList',
/**
* Checks whether an object is a VariableList
*
* @param {*} obj -
* @returns {Boolean}
*/
isVariableList: function (obj) {
return Boolean(obj) && ((obj instanceof VariableList) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', VariableList._postman_propertyName));
}
});
module.exports = {
VariableList
};

View File

@@ -0,0 +1,468 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
PropertyBase = require('./property-base').PropertyBase,
VariableList = require('./variable-list').VariableList,
MutationTracker = require('./mutation-tracker').MutationTracker,
/**
* Known variable mutation types.
*
* @private
* @constant
* @type {Object}
*/
MUTATIONS = {
SET: 'set',
UNSET: 'unset'
},
VariableScope;
/**
* Environment and Globals of postman is exported and imported in a specified data structure. This data structure can be
* passed on to the constructor parameter of {@link VariableScope} or {@link VariableList} to instantiate an instance of
* the same with pre-populated values from arguments.
*
* @typedef VariableScope.definition
* @property {String} [id] ID of the scope
* @property {String} [name] A name of the scope
* @property {Array.<Variable.definition>} [values] A list of variables defined in an array in form of `{name:String,
* value:String}`
*
* @example <caption>JSON definition of a VariableScope (environment, globals, etc)</caption>
* {
* "name": "globals",
* "values": [{
* "key": "var-1",
* "value": "value-1"
* }, {
* "key": "var-2",
* "value": "value-2"
* }]
* }
*/
_.inherit((
/**
* VariableScope is a representation of a list of variables in Postman, such as the environment variables or the
* globals. Using this object, it is easy to perform operations on this list of variables such as get a variable or
* set a variable.
*
* @constructor
* @extends {Property}
*
* @param {VariableScope.definition} definition The constructor accepts an initial set of values for initialising
* the scope
* @param {Array<VariableList>=} layers Additional parent scopes to search for and resolve variables
*
* @example <caption>Load a environment from file, modify and save back</caption>
* var fs = require('fs'), // assuming NodeJS
* env,
* sum;
*
* // load env from file assuming it has initial data
* env = new VariableScope(JSON.parse(fs.readFileSync('./my-postman-environment.postman_environment').toString()));
*
* // get two variables and add them
* sum = env.get('one-var') + env.get('another-var');
*
* // save it back in environment and write to file
* env.set('sum', sum, 'number');
* fs.writeFileSync('./sum-of-vars.postman_environment', JSON.stringify(env.toJSON()));
*/
VariableScope = function PostmanVariableScope (definition, layers) {
// in case the definition is an array (legacy format) or existing as list, we convert to actual format
if (_.isArray(definition) || VariableList.isVariableList(definition)) {
definition = { values: definition };
}
// we accept parent scopes to increase search area. Here we normalize the argument to be an array
// so we can easily loop though them and add them to the instance.
layers && !_.isArray(layers) && (layers = [layers]);
// this constructor is intended to inherit and as such the super constructor is required to be executed
VariableScope.super_.call(this, definition);
var values = definition && definition.values, // access the values (need this var to reuse access)
// enable mutation tracking if `mutations` are in definition (restore the state)
// or is enabled through options
mutations = definition && definition.mutations,
ii,
i;
/**
* @memberof VariableScope.prototype
* @type {VariableList}
*/
this.values = new VariableList(this, VariableList.isVariableList(values) ? values.toJSON() : values);
// in above line, we clone the values if it is already a list. there is no point directly using the instance of
// a variable list since one cannot be created with a parent reference to begin with.
if (layers) {
this._layers = [];
for (i = 0, ii = layers.length; i < ii; i++) {
VariableList.isVariableList(layers[i]) && this._layers.push(layers[i]);
}
}
// restore previously tracked mutations
if (mutations) {
this.mutations = new MutationTracker(mutations);
}
}), Property);
/**
* @note Handling disabled and duplicate variables:
* | method | single enabled | single disabled | with duplicates |
* |--------|-------------------|-----------------|------------------------------------------------------------------- |
* | has | true | false | true (if last enabled) OR false (if all disabled) |
* | get | {Variable} | undefined | last enabled {Variable} OR undefined (if all disabled) |
* | set | update {Variable} | new {Variable} | update last enabled {Variable} OR new {Variable} (if all disabled) |
* | unset | delete {Variable} | noop | delete all enabled {Variable} |
*
* @todo Expected behavior of `unset` with duplicates:
* delete last enabled {Variable} and update the reference with last enabled in rest of the list.
* This requires unique identifier in the variable list for mutations to work correctly.
*/
_.assign(VariableScope.prototype, /** @lends VariableScope.prototype */ {
/**
* Defines whether this property instances requires an id
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyRequiresId: true,
/**
* @private
* @deprecated discontinued in v4.0
*/
variables: function () {
// eslint-disable-next-line max-len
throw new Error('`VariableScope#variables` has been discontinued, use `VariableScope#syncVariablesTo` instead.');
},
/**
* Converts a list of Variables into an object where key is `_postman_propertyIndexKey` and value is determined
* by the `valueOf` function
*
* @param {Boolean} excludeDisabled -
* @param {Boolean} caseSensitive -
* @returns {Object}
*/
toObject: function (excludeDisabled, caseSensitive) {
// if the scope has no layers, we simply export the contents of primary store
if (!this._layers) {
return this.values.toObject(excludeDisabled, caseSensitive);
}
var mergedLayers = {};
_.forEachRight(this._layers, function (layer) {
_.assign(mergedLayers, layer.toObject(excludeDisabled, caseSensitive));
});
return _.assign(mergedLayers, this.values.toObject(excludeDisabled, caseSensitive));
},
/**
* Determines whether one particular variable is defined in this scope of variables.
*
* @param {String} key - The name of the variable to check
* @returns {Boolean} - Returns true if an enabled variable with given key is present in current or parent scopes,
* false otherwise
*/
has: function (key) {
var variable = this.values.oneNormalizedVariable(key),
i,
ii;
// if a variable is disabled or does not exist in local scope,
// we search all the layers and return the first occurrence.
if ((!variable || variable.disabled === true) && this._layers) {
for (i = 0, ii = this._layers.length; i < ii; i++) {
variable = this._layers[i].oneNormalizedVariable(key);
if (variable && variable.disabled !== true) { break; }
}
}
return Boolean(variable && variable.disabled !== true);
},
/**
* Fetches a variable from the current scope or from parent scopes if present.
*
* @param {String} key - The name of the variable to get.
* @returns {*} The value of the specified variable across scopes.
*/
get: function (key) {
var variable = this.values.oneNormalizedVariable(key),
i,
ii;
// if a variable does not exist in local scope, we search all the layers and return the first occurrence.
if ((!variable || variable.disabled === true) && this._layers) {
for (i = 0, ii = this._layers.length; i < ii; i++) {
variable = this._layers[i].oneNormalizedVariable(key);
if (variable && variable.disabled !== true) { break; }
}
}
return (variable && variable.disabled !== true) ? variable.valueOf() : undefined;
},
/**
* Creates a new variable, or updates an existing one.
*
* @param {String} key - The name of the variable to set.
* @param {*} value - The value of the variable to be set.
* @param {Variable.types} [type] - Optionally, the value of the variable can be set to a type
*/
set: function (key, value, type) {
var variable = this.values.oneNormalizedVariable(key),
// create an object that will be used as setter
update = { key, value };
_.isString(type) && (update.type = type);
// If a variable by the name key exists, update it's value and return.
// @note adds new variable if existing is disabled. Disabled variables are not updated.
if (variable && !variable.disabled) {
variable.update(update);
}
else {
this.values.add(update);
}
// track the change if mutation tracking is enabled
this._postman_enableTracking && this.mutations.track(MUTATIONS.SET, key, value);
},
/**
* Removes the variable with the specified name.
*
* @param {String} key -
*/
unset: function (key) {
var lastDisabledVariable;
this.values.remove(function (variable) {
// bail out if variable name didn't match
if (variable.key !== key) {
return false;
}
// don't delete disabled variables
if (variable.disabled) {
lastDisabledVariable = variable;
return false;
}
// delete all enabled variables
return true;
});
// restore the reference with the last disabled variable
if (lastDisabledVariable) {
this.values.reference[key] = lastDisabledVariable;
}
// track the change if mutation tracking is enabled
this._postman_enableTracking && this.mutations.track(MUTATIONS.UNSET, key);
},
/**
* Removes *all* variables from the current scope. This is a destructive action.
*/
clear: function () {
var mutations = this.mutations;
// track the change if mutation tracking is enabled
// do this before deleting the keys
if (this._postman_enableTracking) {
this.values.each(function (variable) {
mutations.track(MUTATIONS.UNSET, variable.key);
});
}
this.values.clear();
},
/**
* Replace all variable names with their values in the given template.
*
* @param {String|Object} template - A string or an object to replace variables in
* @returns {String|Object} The string or object with variables (if any) substituted with their values
*/
replaceIn: function (template) {
if (_.isString(template) || _.isArray(template)) {
// convert template to object because replaceSubstitutionsIn only accepts objects
var result = Property.replaceSubstitutionsIn({ template }, _.concat(this.values, this._layers));
return result.template;
}
if (_.isObject(template)) {
return Property.replaceSubstitutionsIn(template, _.concat(this.values, this._layers));
}
return template;
},
/**
* Enable mutation tracking.
*
* @note: Would do nothing if already enabled.
* @note: Any previously tracked mutations would be reset when starting a new tracking session.
*
* @param {MutationTracker.definition} [options] Options for Mutation Tracker. See {@link MutationTracker}
*/
enableTracking: function (options) {
// enabled already, do nothing
if (this._postman_enableTracking) {
return;
}
/**
* Controls if mutation tracking is enabled
*
* @memberof VariableScope.prototype
*
* @private
* @property {Boolean}
*/
this._postman_enableTracking = true;
// we don't want to add more mutations to existing mutations
// that will lead to mutations not capturing the correct state
// so we reset this with the new instance
this.mutations = new MutationTracker(options);
},
/**
* Disable mutation tracking.
*/
disableTracking: function () {
// disable further tracking but keep the tracked mutations
this._postman_enableTracking = false;
},
/**
* Apply a mutation instruction on this variable scope.
*
* @private
* @param {String} instruction Instruction identifying the type of the mutation, e.g. `set`, `unset`
* @param {String} key -
* @param {*} value -
*/
applyMutation: function (instruction, key, value) {
// we know that `set` and `unset` are the only supported instructions
// and we know the parameter signature of both is the same as the items in a mutation
/* istanbul ignore else */
if (this[instruction]) {
this[instruction](key, value);
}
},
/**
* Using this function, one can sync the values of this variable list from a reference object.
*
* @private
* @param {Object} obj -
* @param {Boolean=} [track] -
* @returns {Object}
*/
syncVariablesFrom: function (obj, track) {
return this.values.syncFromObject(obj, track);
},
/**
* Transfer the variables in this scope to an object
*
* @private
* @param {Object=} [obj] -
* @returns {Object}
*/
syncVariablesTo: function (obj) {
return this.values.syncToObject(obj);
},
/**
* Convert this variable scope into a JSON serialisable object. Useful to transport or store, environment and
* globals as a whole.
*
* @returns {Object}
*/
toJSON: function () {
var obj = PropertyBase.toJSON(this);
// @todo - remove this when pluralisation is complete
if (obj.value) {
obj.values = obj.value;
delete obj.value;
}
// ensure that the concept of layers is not exported as JSON. JSON cannot retain references and this will end up
// being a pointless object post JSONification.
if (obj._layers) {
delete obj._layers;
}
// ensure that tracking flag is not serialized
// otherwise, it is very easy to let tracking trickle to many instances leading to a snowball effect
if (obj._postman_enableTracking) {
delete obj._postman_enableTracking;
}
return obj;
},
/**
* Adds a variable list to the current instance in order to increase the surface area of variable resolution.
* This enables consumers to search across scopes (eg. environment and globals).
*
* @private
* @param {VariableList} [list] -
*/
addLayer: function (list) {
if (!VariableList.isVariableList(list)) {
return;
}
!this._layers && (this._layers = []); // lazily initialize layers
this._layers.push(list);
}
});
_.assign(VariableScope, /** @lends VariableScope */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*
* @note that this is directly accessed only in case of VariableScope from _.findValue lodash util mixin
*/
_postman_propertyName: 'VariableScope',
/**
* Check whether an object is an instance of {@link VariableScope}.
*
* @param {*} obj -
* @returns {Boolean}
*/
isVariableScope: function (obj) {
return Boolean(obj) && ((obj instanceof VariableScope) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', VariableScope._postman_propertyName));
}
});
module.exports = {
VariableScope
};

View File

@@ -0,0 +1,379 @@
var _ = require('../util').lodash,
Property = require('./property').Property,
E = '',
ANY = 'any',
NULL = 'null',
STRING = 'string',
Variable;
/**
* The object representation of a Variable consists the variable value and type. It also optionally includes the `id`
* and a friendly `name` of the variable. The `id` and the `name` of a variable is usually managed and used when a
* variable is made part of a {@link VariableList} instance.
*
* @typedef {Object} Variable.definition
* @property {*=} [value] - The value of the variable that will be stored and will be typecast to the `type`
* set in the variable or passed along in this parameter.
* @property {String=} [type] - The type of this variable from the list of types defined at {@link Variable.types}.
*
* @example
* {
* "id": "my-var-1",
* "name": "MyFirstVariable",
* "value": "Hello World",
* "type": "string"
* }
*/
_.inherit((
/**
* A variable inside a collection is similar to variables in any programming construct. The variable has an
* identifier name (provided by its id) and a value. A variable is optionally accompanied by a variable type. One
* or more variables can be associated with a collection and can be referred from anywhere else in the collection
* using the double-brace {{variable-id}} format. Properties can then use the `.toObjectResolved` function to
* procure an object representation of the property with all variable references replaced by corresponding values.
*
* @constructor
* @extends {Property}
* @param {Variable.definition=} [definition] - Specify the initial value and type of the variable.
*/
Variable = function PostmanVariable (definition) {
// this constructor is intended to inherit and as such the super constructor is required to be executed
Variable.super_.apply(this, arguments);
// check what is the property name for indexing this variable
var indexer = this.constructor._postman_propertyIndexKey;
_.assign(this, /** @lends Variable.prototype */ {
/**
* @type {Variable.types}
*/
type: ANY,
/**
* @type {*}
*/
value: undefined
});
if (!_.isNil(definition)) {
/**
* The name of the variable. This is used for referencing this variable from other locations and scripts
*
* @type {String}
* @name key
* @memberOf Variable.prototype
*/
_.has(definition, indexer) && (this[indexer] = definition[indexer]);
this.update(definition);
}
}), Property);
_.assign(Variable.prototype, /** @lends Variable.prototype */ {
/**
* Gets the value of the variable.
*
* @returns {Variable.types}
*/
get () {
return _.isFunction(this.value) ? this.castOut(this.value()) : this.castOut(this.value);
},
/**
* Sets the value of the variable.
*
* @param {*} value -
*/
set (value) {
// @todo - figure out how secure is this!
this.value = _.isFunction(value) ? value : this.castIn(value);
},
/**
* An alias of this.get and this.set.
*
* @param {*=} [value] -
* @returns {*}
*/
valueOf (value) {
arguments.length && this.set(value);
return this.get();
},
/**
* Returns the stringified value of the variable.
*
* @returns {String}
*/
toString () {
var value = this.valueOf();
// returns String representation of null as it's a valid JSON type
// refer: https://github.com/postmanlabs/postman-app-support/issues/8493
if (value === null) {
return NULL;
}
// returns empty string if the value is undefined or does not implement
// the toString method
return (!_.isNil(value) && _.isFunction(value.toString)) ? value.toString() : E;
},
/**
* Typecasts a value to the {@link Variable.types} of this {@link Variable}. Returns the value of the variable
* converted to the type specified in {@link Variable#type}.
*
* @param {*} value -
* @returns {*}
*/
cast (value) {
return this.castOut(value);
},
/**
* Typecasts a value to the {@link Variable.types} of this {@link Variable}. Returns the value of the variable
* converted to the type specified in {@link Variable#type}.
*
* @private
* @param {*} value -
* @returns {*}
*/
castIn (value) {
var handler = Variable.types[this.type] || Variable.types.any;
return _.isFunction(handler) ? handler(value) : handler.in(value);
},
/**
* Typecasts a value from the {@link Variable.types} of this {@link Variable}. Returns the value of the variable
* converted to the type specified in {@link Variable#type}.
*
* @private
* @param {*} value -
* @returns {*}
*/
castOut (value) {
var handler = Variable.types[this.type] || Variable.types.any;
return _.isFunction(handler) ? handler(value) : handler.out(value);
},
/**
* Sets or gets the type of the value.
*
* @param {String} typeName -
* @param {Boolean} _noCast -
* @returns {String} - returns the current type of the variable from the list of {@link Variable.types}
*/
valueType (typeName, _noCast) {
!_.isNil(typeName) && (typeName = typeName.toString().toLowerCase()); // sanitize
if (!Variable.types[typeName]) {
return this.type || ANY; // @todo: throw new Error('Invalid variable type.');
}
// set type if it is valid
this.type = typeName;
// 1. get the current value
// 2. set the new type if it is valid and cast the stored value
// 3. then set the interstitial value
var interstitialCastValue;
// do not touch value functions
if (!(_noCast || _.isFunction(this.value))) {
interstitialCastValue = this.get();
this.set(interstitialCastValue);
interstitialCastValue = null; // just a precaution
}
return this.type;
},
/**
* Updates the type and value of a variable from an object or JSON definition of the variable.
*
* @param {Variable.definition} options -
*/
update (options) {
if (!_.isObject(options)) {
return;
}
// set type and value.
// @note that we cannot update the key, once created during construction
_.has(options, 'type') && this.valueType(options.type, _.has(options, 'value'));
_.has(options, 'value') && this.set(options.value);
_.has(options, 'system') && (this.system = options.system);
_.has(options, 'disabled') && (this.disabled = options.disabled);
}
});
_.assign(Variable, /** @lends Variable */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Variable',
/**
* Specify the key to be used while indexing this object
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyIndexKey: 'key',
/**
* The possible supported types of a variable is defined here. The keys defined here are the possible values of
* {@link Variable#type}.
*
* Additional variable types can be supported by adding the type-casting function to this enumeration.
*
* @enum {Function}
* @readonly
*/
types: {
/**
* When a variable's `type` is set to "string", it ensures that {@link Variable#get} converts the value of the
* variable to a string before returning the data.
*/
string: String,
/**
* A boolean type of variable can either be set to `true` or `false`. Any other value set is converted to
* Boolean when procured from {@link Variable#get}.
*/
boolean: Boolean,
/**
* A "number" type variable ensures that the value is always represented as a number. A non-number type value
* is returned as `NaN`.
*/
number: Number,
/**
* A "array" type value stores Array data format
*/
array: {
/**
* @param {Array} val -
* @returns {String}
*/
in (val) {
var value;
try {
// @todo: should we check if `val` is a valid Array or Array string?
value = typeof val === STRING ? val : JSON.stringify(val);
}
catch (e) {
value = NULL;
}
return value;
},
/**
* A "array" type value stores Array data format
*
* @param {String} val -
* @returns {Object}
*/
out (val) {
var value;
try {
value = JSON.parse(val);
}
catch (e) {
value = undefined;
}
return Array.isArray(value) ? value : undefined;
}
},
/**
* A "object" type value stores Object data format
*/
object: {
/**
* @param {Object} val -
* @returns {String}
*/
in (val) {
var value;
try {
// @todo: should we check if `val` is a valid JSON string?
value = typeof val === STRING ? val : JSON.stringify(val);
}
catch (e) {
value = NULL;
}
return value;
},
/**
* A "object" type value stores Object data format
*
* @param {String} val -
* @returns {Object}
*/
out (val) {
var value;
try {
value = JSON.parse(val);
}
catch (e) {
value = undefined;
}
return (value instanceof Object && !Array.isArray(value)) ? value : undefined;
}
},
/**
* Free-form type of a value. This is the default for any variable, unless specified otherwise. It ensures that
* the variable can store data in any type and no conversion is done while using {@link Variable#get}.
*/
any: {
/**
* @param {*} val -
* @returns {*}
*/
in (val) {
return val; // pass through
},
/**
* @param {*} val -
* @returns {*}
*/
out (val) {
return val; // pass through
}
}
},
/**
* @param {*} obj -
* @returns {Boolean}
*/
isVariable: function (obj) {
return Boolean(obj) && ((obj instanceof Variable) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Variable._postman_propertyName));
}
});
module.exports = {
Variable
};

View File

@@ -0,0 +1,99 @@
var _ = require('../util').lodash,
semver = require('semver'),
PropertyBase = require('./property-base').PropertyBase,
Version;
/**
* @typedef {Object|String} Version.definition
*/
_.inherit((
/**
* Defines a Version.
*
* @constructor
* @extends {PropertyBase}
* @param {Version.definition} definition -
*/
Version = function PostmanPropertyVersion (definition) {
// in case definition object is missing, there is no point moving forward
if (!definition) { return; }
// call the setter to process the version string and assign it to this object
this.set(definition);
}), PropertyBase);
_.assign(Version.prototype, /** @lends Version.prototype */ {
/**
* Set the version value as string or object with separate components of version
*
* @draft
* @param {object|string} value -
*/
set (value) {
// extract the version logic and in case it failes and value passed is an object, we use that assuming parsed
// value has been sent.
var ver = semver.parse(value) || value || {};
_.assign(this, /** @lends Version.prototype */ {
/**
* The raw URL string. If {@link Version#set} is called with a string parameter, the string is saved here
* before parsing various Version components.
*
* @type {String}
*/
raw: ver.raw,
/**
* @type {String}
*/
major: ver.major,
/**
* @type {String}
*/
minor: ver.minor,
/**
* @type {String}
*/
patch: ver.patch,
/**
* @type {String}
*/
prerelease: ver.prerelease && ver.prerelease.join && ver.prerelease.join() || ver.prerelease,
/**
* @type {String}
*/
build: ver.build && ver.build.join && ver.build.join() || ver.build,
/**
* @type {String}
*/
string: ver.version
});
},
toString () {
// TODO: is this enough? should we build the semver back up?
return this.string || this.raw;
}
});
_.assign(Version, /** @lends Version */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Version'
});
module.exports = {
Version
};

View File

@@ -0,0 +1,382 @@
var util = require('../util'),
_ = util.lodash,
fileType = require('file-type'),
mimeType = require('mime-types'),
mimeFormat = require('mime-format'),
/**
* @private
* @const
* @type {String}
*/
E = '',
/**
* @private
* @const
* @type {String}
*/
DOT = '.',
/**
* @private
* @const
* @type {String}
*/
QUESTION_MARK = '?',
/**
* @private
* @const
* @type {String}
*/
DOUBLE_QUOTES = '"',
/**
* @private
* @const
* @type {String}
*/
TOKEN_$1 = '$1',
/**
* @private
* @const
* @type {String}
*/
BINARY = 'binary',
/**
* @private
* @const
* @type {String}
*/
CHARSET_UTF8 = 'utf8',
/**
* @private
* @const
* @type {String}
*/
CONTENT_TYPE_TEXT_PLAIN = 'text/plain',
/**
* Enum for all the Content Headers
*
* @private
* @const
* @enum {String} HEADERS
*/
HEADERS = {
CONTENT_TYPE: 'Content-Type',
CONTENT_DISPOSITION: 'Content-Disposition'
},
/**
* @private
* @const
* @type {String}
*/
DEFAULT_RESPONSE_FILENAME = 'response',
/**
* @private
* @type {Boolean}
*/
supportsBuffer = (typeof Buffer !== undefined) && _.isFunction(Buffer.byteLength),
/**
* Regexes for extracting and decoding the filename from content-disposition header
*
* @private
* @type {Object}
*/
regexes = {
/**
* RegExp for extracting filename from content-disposition header
*
* RFC 2616 grammar
* parameter = token "=" ( token | quoted-string )
* token = 1*<any CHAR except CTLs or separators>
* separators = "(" | ")" | "<" | ">" | "@"
* | "," | ";" | ":" | "\" | <">
* | "/" | "[" | "]" | "?" | "="
* | "{" | "}" | SP | HT
* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
* qdtext = <any TEXT except <">>
* quoted-pair = "\" CHAR
* CHAR = <any US-ASCII character (octets 0 - 127)>
* TEXT = <any OCTET except CTLs, but including LWS>
* LWS = [CRLF] 1*( SP | HT )
* CRLF = CR LF
* CR = <US-ASCII CR, carriage return (13)>
* LF = <US-ASCII LF, linefeed (10)>
* SP = <US-ASCII SP, space (32)>
* HT = <US-ASCII HT, horizontal-tab (9)>
* CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
* OCTET = <any 8-bit sequence of data>
*
* egHeader: inline; filename=testResponse.json
* egHeader: inline; filename="test Response.json"
* Reference: https://github.com/jshttp/content-disposition
*/
// eslint-disable-next-line max-len
fileNameRegex: /;[ \t]*(?:filename)[ \t]*=[ \t]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[ \t]*/,
/**
* RegExp for extracting filename* from content-disposition header
*
* RFC 5987 grammar
* parameter = reg-parameter / ext-parameter
* ext-parameter = parmname "*" LWSP "=" LWSP ext-value
* parmname = 1*attr-char
* ext-value = charset "'" [ language ] "'" value-chars
; like RFC 2231's <extended-initial-value>
; (see [RFC2231], Section 7)
* charset = "UTF-8" / "ISO-8859-1" / mime-charset
* mime-charset = 1*mime-charsetc
* mime-charsetc = ALPHA / DIGIT
/ "!" / "#" / "$" / "%" / "&"
/ "+" / "-" / "^" / "_" / "`"
/ "{" / "}" / "~"
; as <mime-charset> in Section 2.3 of [RFC2978]
; except that the single quote is not included
; SHOULD be registered in the IANA charset registry
* language = <Language-Tag, defined in [RFC5646], Section 2.1>
* value-chars = *( pct-encoded / attr-char )
* pct-encoded = "%" HEXDIG HEXDIG
; see [RFC3986], Section 2.1
* attr-char = ALPHA / DIGIT
/ "!" / "#" / "$" / "&" / "+" / "-" / "."
/ "^" / "_" / "`" / "|" / "~"
; token except ( "*" / "'" / "%" )
*
* egHeader: attachment;filename*=utf-8''%E4%BD%A0%E5%A5%BD.txt
* Reference: https://github.com/jshttp/content-disposition
*/
// eslint-disable-next-line max-len
encodedFileNameRegex: /;[ \t]*(?:filename\*)[ \t]*=[ \t]*([A-Za-z0-9!#$%&+\-^_`{}~]+)'.*'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)[ \t]*/,
/**
* RegExp to match quoted-pair in RFC 2616
*
* quoted-pair = "\" CHAR
* CHAR = <any US-ASCII character (octets 0 - 127)>
*/
quotedPairRegex: /\\([ -~])/g,
/**
* Regex to match all the hexadecimal number inside encoded string
*/
hexCharMatchRegex: /%([0-9A-Fa-f]{2})/g,
/**
* Regex to match non-latin characters
*/
nonLatinCharMatchRegex: /[^\x20-\x7e\xa0-\xff]/g
},
/**
* Decodes the hexcode to charCode
*
* @private
* @param {String} str - The matched string part of a hexadecimal number
* @param {String} hex - The hexadecimal string which needs to be converted to charCode
* @returns {String} - String with decoded hexcode values
*/
decodeHexcode = function (str, hex) {
return String.fromCharCode(parseInt(hex, 16));
},
/**
* HashMap for decoding string with supported characterSets
* iso-8859-1
* utf-8
*
* @private
* @type {Object}
*/
characterDecoders = {
/**
* Replaces non-latin characters with '?'
*
* @private
* @param {String} val - Input encoded string
* @returns {String} - String with latin characters
*/
'iso-8859-1' (val) {
return val.replace(regexes.nonLatinCharMatchRegex, QUESTION_MARK);
},
/**
* Decodes the given string with utf-8 character set
*
* @private
* @param {?String} encodedString - Input encoded string
* @returns {?String} - String with decoded character with utf-8
*/
'utf-8' (encodedString) {
/* istanbul ignore if */
if (!supportsBuffer) {
return;
}
return Buffer.from(encodedString, BINARY).toString(CHARSET_UTF8);
}
},
/**
* Decodes the given filename with given charset
* The supported character sets are
* iso-8859-1
* utf-8
*
* @private
* @param {String} encodedFileName - Input encoded file name
* @param {String} charset - The character set to be used while decoding
* @returns {String} - Returns the decoded filename
*/
decodeFileName = function (encodedFileName, charset) {
/* istanbul ignore if */
if (!encodedFileName) {
return;
}
if (!characterDecoders[charset]) {
return;
}
// decodes the hexadecimal numbers to charCode in encodedFileName and then decodes with given charset
return characterDecoders[charset](encodedFileName.replace(regexes.hexCharMatchRegex, decodeHexcode));
},
/**
* Takes the content-type header value and performs the mime sniffing with known mime types.
* If content-type header is not present, detects the mime type from the response stream or response body
* If content-type is not provided and not able to detect, then text/plain is taken as default
*
* @private
* @param {?String} contentType - The value of content type header
* @param {Stream|String} response - The response stream or body, for which content-info should be determined
* @returns {Object} - mime information from response headers
*/
getMimeInfo = function (contentType, response) {
var normalized,
detected,
detectedExtension;
if (!contentType) {
detected = fileType(response);
detected && (contentType = detected.mime) && (detectedExtension = detected.ext);
}
// if contentType is not detected set text/plain as default
if (!contentType) {
contentType = CONTENT_TYPE_TEXT_PLAIN;
}
normalized = mimeFormat.lookup(contentType);
return {
contentType: normalized.source,
mimeType: normalized.type, // sanitized mime type base
mimeFormat: normalized.format, // format specific to the type returned
charset: normalized.charset || CHARSET_UTF8,
extension: detectedExtension || mimeType.extension(normalized.source) || E
};
},
/**
* Parses Content disposition header, and returns file name and extension
*
* @private
* @param {?String} dispositionHeader - Content-disposition Header from the response
* @returns {?String} - Returns file name from content disposition header if present
*/
getFileNameFromDispositionHeader = function (dispositionHeader) {
if (!dispositionHeader) {
return;
}
var encodedFileName,
fileName;
// Get filename* value from the dispositionHeader
encodedFileName = regexes.encodedFileNameRegex.exec(dispositionHeader);
if (encodedFileName) {
fileName = decodeFileName(encodedFileName[2], encodedFileName[1]);
}
// If filename* is not present or unparseable, then we are checking for filename in header
if (!fileName) {
fileName = regexes.fileNameRegex.exec(dispositionHeader);
fileName && (fileName = fileName[1]);
// check if file name is wrapped in double quotes
// file name can contain escaped characters if wrapped in quotes
if (fileName && fileName[0] === DOUBLE_QUOTES) {
// remove quotes and escapes
fileName = fileName
.substr(1, fileName.length - 2)
.replace(regexes.quotedPairRegex, TOKEN_$1);
}
}
return fileName;
};
module.exports = {
/**
* Extracts content related information from response.
* Includes response mime information, character set and file name.
*
* @private
* @param {Response} response - response instance
* @returns {Response.ResponseContentInfo} - Return contentInfo of the response
*/
contentInfo (response) {
var contentType = response.headers.get(HEADERS.CONTENT_TYPE),
contentDisposition = response.headers.get(HEADERS.CONTENT_DISPOSITION),
mimeInfo = getMimeInfo(contentType, response.stream || response.body),
fileName = getFileNameFromDispositionHeader(contentDisposition),
fileExtension = mimeInfo.extension,
/**
* @typedef Response.ResponseContentInfo
*
* @property {String} mimeType sanitized mime type
* @property {String} mimeFormat format for the identified mime type
* @property {String} charset the normalized character set
* @property {String} fileExtension extension identified from the mime type
* @property {String} fileName file name extracted from disposition header
* @property {String} contentType sanitized content-type extracted from header
*/
contentInfo = {};
// if file name is not present in the content disposition headers, use a default file name
if (!fileName) {
fileName = DEFAULT_RESPONSE_FILENAME;
// add extension to default if present
fileExtension && (fileName += (DOT + fileExtension));
}
// create a compacted list of content info from mime info and file name
mimeInfo.contentType && (contentInfo.contentType = mimeInfo.contentType);
mimeInfo.mimeType && (contentInfo.mimeType = mimeInfo.mimeType);
mimeInfo.mimeFormat && (contentInfo.mimeFormat = mimeInfo.mimeFormat);
mimeInfo.charset && (contentInfo.charset = mimeInfo.charset);
fileExtension && (contentInfo.fileExtension = fileExtension);
fileName && (contentInfo.fileName = fileName);
return contentInfo;
},
// regexes are extracted for vulnerability tests
regexes
};

34
node_modules/postman-collection/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,34 @@
module.exports = {
PropertyBase: require('./collection/property-base').PropertyBase,
Certificate: require('./collection/certificate').Certificate,
CertificateList: require('./collection/certificate-list').CertificateList,
Collection: require('./collection/collection').Collection,
Cookie: require('./collection/cookie').Cookie,
CookieList: require('./collection/cookie-list').CookieList,
Description: require('./collection/description').Description,
Event: require('./collection/event').Event,
EventList: require('./collection/event-list').EventList,
FormParam: require('./collection/form-param').FormParam,
Header: require('./collection/header').Header,
HeaderList: require('./collection/header-list').HeaderList,
Item: require('./collection/item').Item,
ItemGroup: require('./collection/item-group').ItemGroup,
MutationTracker: require('./collection/mutation-tracker').MutationTracker,
PropertyList: require('./collection/property-list').PropertyList,
Property: require('./collection/property').Property,
QueryParam: require('./collection/query-param').QueryParam,
Request: require('./collection/request').Request,
RequestAuth: require('./collection/request-auth').RequestAuth,
RequestBody: require('./collection/request-body').RequestBody,
Response: require('./collection/response').Response,
Script: require('./collection/script').Script,
Url: require('./collection/url').Url,
UrlMatchPattern: require('./url-pattern/url-match-pattern').UrlMatchPattern,
UrlMatchPatternList: require('./url-pattern/url-match-pattern-list').UrlMatchPatternList,
Variable: require('./collection/variable').Variable,
VariableList: require('./collection/variable-list').VariableList,
VariableScope: require('./collection/variable-scope').VariableScope,
ProxyConfig: require('./collection/proxy-config').ProxyConfig,
ProxyConfigList: require('./collection/proxy-config-list').ProxyConfigList,
Version: require('./collection/version').Version
};

View File

@@ -0,0 +1,615 @@
var faker = require('faker/locale/en'),
uuid = require('uuid'),
// locale list generated from: https://github.com/chromium/chromium/blob/master/ui/base/l10n/l10n_util.cc
LOCALES = ['af', 'am', 'an', 'ar', 'ast', 'az', 'be', 'bg', 'bh', 'bn', 'br', 'bs', 'ca', 'ceb', 'ckb', 'co', 'cs',
'cy', 'da', 'de', 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'fi', 'fil', 'fo', 'fr', 'fy', 'ga', 'gd', 'gl',
'gn', 'gu', 'ha', 'haw', 'he', 'hi', 'hmn', 'hr', 'ht', 'hu', 'hy', 'ia', 'id', 'ig', 'is', 'it', 'ja', 'jv',
'ka', 'kk', 'km', 'kn', 'ko', 'ku', 'ky', 'la', 'lb', 'ln', 'lo', 'lt', 'lv', 'mg', 'mi', 'mk', 'ml', 'mn',
'mo', 'mr', 'ms', 'mt', 'my', 'nb', 'ne', 'nl', 'nn', 'no', 'ny', 'oc', 'om', 'or', 'pa', 'pl', 'ps', 'pt',
'qu', 'rm', 'ro', 'ru', 'sd', 'sh', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'st', 'su', 'sv', 'sw',
'ta', 'te', 'tg', 'th', 'ti', 'tk', 'to', 'tr', 'tt', 'tw', 'ug', 'uk', 'ur', 'uz', 'vi', 'wa', 'xh', 'yi',
'yo', 'zh', 'zu'],
// paths for directories
DIRECTORY_PATHS = [
'/Applications',
'/bin',
'/boot',
'/boot/defaults',
'/dev',
'/etc',
'/etc/defaults',
'/etc/mail',
'/etc/namedb',
'/etc/periodic',
'/etc/ppp',
'/home',
'/home/user',
'/home/user/dir',
'/lib',
'/Library',
'/lost+found',
'/media',
'/mnt',
'/net',
'/Network',
'/opt',
'/opt/bin',
'/opt/include',
'/opt/lib',
'/opt/sbin',
'/opt/share',
'/private',
'/private/tmp',
'/private/var',
'/proc',
'/rescue',
'/root',
'/sbin',
'/selinux',
'/srv',
'/sys',
'/System',
'/tmp',
'/Users',
'/usr',
'/usr/X11R6',
'/usr/bin',
'/usr/include',
'/usr/lib',
'/usr/libdata',
'/usr/libexec',
'/usr/local/bin',
'/usr/local/src',
'/usr/obj',
'/usr/ports',
'/usr/sbin',
'/usr/share',
'/usr/src',
'/var',
'/var/log',
'/var/mail',
'/var/spool',
'/var/tmp',
'/var/yp'
],
// generators for $random* variables
dynamicGenerators = {
$guid: {
description: 'A v4 style guid',
generator: function () {
return uuid.v4();
}
},
$timestamp: {
description: 'The current timestamp',
generator: function () {
return Math.round(Date.now() / 1000);
}
},
$isoTimestamp: {
description: 'The current ISO timestamp at zero UTC',
generator: function () {
return new Date().toISOString();
}
},
$randomInt: {
description: 'A random integer between 0 and 1000',
generator: function () {
return ~~(Math.random() * (1000 + 1));
}
},
// faker.phone.phoneNumber returns phone number with or without
// extension randomly. this only returns a phone number without extension.
$randomPhoneNumber: {
description: 'A random 10-digit phone number',
generator: function () {
return faker.phone.phoneNumberFormat(0);
}
},
// faker.phone.phoneNumber returns phone number with or without
// extension randomly. this only returns a phone number with extension.
$randomPhoneNumberExt: {
description: 'A random phone number with extension (12 digits)',
generator: function () {
return faker.datatype.number({ min: 1, max: 99 }) + '-' + faker.phone.phoneNumberFormat(0);
}
},
// faker's random.locale only returns 'en'. this returns from a list of
// random locales
$randomLocale: {
description: 'A random two-letter language code (ISO 639-1)',
generator: function () {
return faker.random.arrayElement(LOCALES);
}
},
// fakers' random.words returns random number of words between 1, 3.
// this returns number of words between 2, 5.
$randomWords: {
description: 'Some random words',
generator: function () {
var words = [],
count = faker.datatype.number({ min: 2, max: 5 }),
i;
for (i = 0; i < count; i++) {
words.push(faker.random.word());
}
return words.join(' ');
}
},
// faker's system.filePath retuns nothing. this returns a path for a file.
$randomFilePath: {
description: 'A random file path',
generator: function () {
return dynamicGenerators.$randomDirectoryPath.generator() + '/' + faker.system.fileName();
}
},
// faker's system.directoryPath retuns nothing. this returns a path for
// a directory.
$randomDirectoryPath: {
description: 'A random directory path',
generator: function () {
return faker.random.arrayElement(DIRECTORY_PATHS);
}
},
$randomCity: {
description: 'A random city name',
generator: faker.address.city
},
$randomStreetName: {
description: 'A random street name',
generator: faker.address.streetName
},
$randomStreetAddress: {
description: 'A random street address (e.g. 1234 Main Street)',
generator: faker.address.streetAddress
},
$randomCountry: {
description: 'A random country',
generator: faker.address.country
},
$randomCountryCode: {
description: 'A random 2-letter country code (ISO 3166-1 alpha-2)',
generator: faker.address.countryCode
},
$randomLatitude: {
description: 'A random latitude coordinate',
generator: faker.address.latitude
},
$randomLongitude: {
description: 'A random longitude coordinate',
generator: faker.address.longitude
},
$randomColor: {
description: 'A random color',
generator: faker.commerce.color
},
$randomDepartment: {
description: 'A random commerce category (e.g. electronics, clothing)',
generator: faker.commerce.department
},
$randomProductName: {
description: 'A random product name (e.g. handmade concrete tuna)',
generator: faker.commerce.productName
},
$randomProductAdjective: {
description: 'A random product adjective (e.g. tasty, eco-friendly)',
generator: faker.commerce.productAdjective
},
$randomProductMaterial: {
description: 'A random product material (e.g. steel, plastic, leather)',
generator: faker.commerce.productMaterial
},
$randomProduct: {
description: 'A random product (e.g. shoes, table, chair)',
generator: faker.commerce.product
},
$randomCompanyName: {
description: 'A random company name',
generator: faker.company.companyName
},
$randomCompanySuffix: {
description: 'A random company suffix (e.g. Inc, LLC, Group)',
generator: faker.company.companySuffix
},
$randomCatchPhrase: {
description: 'A random catchphrase',
generator: faker.company.catchPhrase
},
$randomBs: {
description: 'A random phrase of business speak',
generator: faker.company.bs
},
$randomCatchPhraseAdjective: {
description: 'A random catchphrase adjective',
generator: faker.company.catchPhraseAdjective
},
$randomCatchPhraseDescriptor: {
description: 'A random catchphrase descriptor',
generator: faker.company.catchPhraseDescriptor
},
$randomCatchPhraseNoun: {
description: 'Randomly generates a catchphrase noun',
generator: faker.company.catchPhraseNoun
},
$randomBsAdjective: {
description: 'A random business speak adjective',
generator: faker.company.bsAdjective
},
$randomBsBuzz: {
description: 'A random business speak buzzword',
generator: faker.company.bsBuzz
},
$randomBsNoun: {
description: 'A random business speak noun',
generator: faker.company.bsNoun
},
$randomDatabaseColumn: {
description: 'A random database column name (e.g. updatedAt, token, group)',
generator: faker.database.column
},
$randomDatabaseType: {
description: 'A random database type (e.g. tiny int, double, point)',
generator: faker.database.type
},
$randomDatabaseCollation: {
description: 'A random database collation (e.g. cp1250_bin)',
generator: faker.database.collation
},
$randomDatabaseEngine: {
description: 'A random database engine (e.g. Memory, Archive, InnoDB)',
generator: faker.database.engine
},
$randomDatePast: {
description: 'A random past datetime',
generator: faker.date.past
},
$randomDateFuture: {
description: 'A random future datetime',
generator: faker.date.future
},
$randomDateRecent: {
description: 'A random recent datetime',
generator: faker.date.recent
},
$randomMonth: {
description: 'A random month',
generator: faker.date.month
},
$randomWeekday: {
description: 'A random weekday',
generator: faker.date.weekday
},
$randomBankAccount: {
description: 'A random 8-digit bank account number',
generator: faker.finance.account
},
$randomBankAccountName: {
description: 'A random bank account name (e.g. savings account, checking account)',
generator: faker.finance.accountName
},
$randomCreditCardMask: {
description: 'A random masked credit card number',
generator: faker.finance.mask
},
$randomPrice: {
description: 'A random price between 0.00 and 1000.00',
generator: faker.finance.amount
},
$randomTransactionType: {
description: 'A random transaction type (e.g. invoice, payment, deposit)',
generator: faker.finance.transactionType
},
$randomCurrencyCode: {
description: 'A random 3-letter currency code (ISO-4217)',
generator: faker.finance.currencyCode
},
$randomCurrencyName: {
description: 'A random currency name',
generator: faker.finance.currencyName
},
$randomCurrencySymbol: {
description: 'A random currency symbol',
generator: faker.finance.currencySymbol
},
$randomBitcoin: {
description: 'A random bitcoin address',
generator: faker.finance.bitcoinAddress
},
$randomBankAccountIban: {
description: 'A random 15-31 character IBAN (International Bank Account Number)',
generator: faker.finance.iban
},
$randomBankAccountBic: {
description: 'A random BIC (Bank Identifier Code)',
generator: faker.finance.bic
},
$randomAbbreviation: {
description: 'A random abbreviation',
generator: faker.hacker.abbreviation
},
$randomAdjective: {
description: 'A random adjective',
generator: faker.hacker.adjective
},
$randomNoun: {
description: 'A random noun',
generator: faker.hacker.noun
},
$randomVerb: {
description: 'A random verb',
generator: faker.hacker.verb
},
$randomIngverb: {
description: 'A random verb ending in “-ing”',
generator: faker.hacker.ingverb
},
$randomPhrase: {
description: 'A random phrase',
generator: faker.hacker.phrase
},
$randomAvatarImage: {
description: 'A random avatar image',
generator: faker.image.avatar
},
$randomImageUrl: {
description: 'A URL for a random image',
generator: faker.image.imageUrl
},
$randomAbstractImage: {
description: 'A URL for a random abstract image',
generator: faker.image.abstract
},
$randomAnimalsImage: {
description: 'A URL for a random animal image',
generator: faker.image.animals
},
$randomBusinessImage: {
description: 'A URL for a random stock business image',
generator: faker.image.business
},
$randomCatsImage: {
description: 'A URL for a random cat image',
generator: faker.image.cats
},
$randomCityImage: {
description: 'A URL for a random city image',
generator: faker.image.city
},
$randomFoodImage: {
description: 'A URL for a random food image',
generator: faker.image.food
},
$randomNightlifeImage: {
description: 'A URL for a random nightlife image',
generator: faker.image.nightlife
},
$randomFashionImage: {
description: 'A URL for a random fashion image',
generator: faker.image.fashion
},
$randomPeopleImage: {
description: 'A URL for a random image of a person',
generator: faker.image.people
},
$randomNatureImage: {
description: 'A URL for a random nature image',
generator: faker.image.nature
},
$randomSportsImage: {
description: 'A URL for a random sports image',
generator: faker.image.sports
},
$randomTransportImage: {
description: 'A URL for a random transportation image',
generator: faker.image.transport
},
$randomImageDataUri: {
description: 'A random image data URI',
generator: faker.image.dataUri
},
$randomEmail: {
description: 'A random email address',
generator: faker.internet.email
},
$randomExampleEmail: {
description: 'A random email address from an “example” domain (e.g. ben@example.com)',
generator: faker.internet.exampleEmail
},
$randomUserName: {
description: 'A random username',
generator: faker.internet.userName
},
$randomProtocol: {
description: 'A random internet protocol',
generator: faker.internet.protocol
},
$randomUrl: {
description: 'A random URL',
generator: faker.internet.url
},
$randomDomainName: {
description: 'A random domain name (e.g. gracie.biz, trevor.info)',
generator: faker.internet.domainName
},
$randomDomainSuffix: {
description: 'A random domain suffix (e.g. .com, .net, .org)',
generator: faker.internet.domainSuffix
},
$randomDomainWord: {
description: 'A random unqualified domain name (a name with no dots)',
generator: faker.internet.domainWord
},
$randomIP: {
description: 'A random IPv4 address',
generator: faker.internet.ip
},
$randomIPV6: {
description: 'A random IPv6 address',
generator: faker.internet.ipv6
},
$randomUserAgent: {
description: 'A random user agent',
generator: faker.internet.userAgent
},
$randomHexColor: {
description: 'A random hex value',
generator: faker.internet.color
},
$randomMACAddress: {
description: 'A random MAC address',
generator: faker.internet.mac
},
$randomPassword: {
description: 'A random 15-character alpha-numeric password',
generator: faker.internet.password
},
$randomLoremWord: {
description: 'A random word of lorem ipsum text',
generator: faker.lorem.word
},
$randomLoremWords: {
description: 'Some random words of lorem ipsum text',
generator: faker.lorem.words
},
$randomLoremSentence: {
description: 'A random sentence of lorem ipsum text',
generator: faker.lorem.sentence
},
$randomLoremSlug: {
description: 'A random lorem ipsum URL slug',
generator: faker.lorem.slug
},
$randomLoremSentences: {
description: 'A random 2-6 sentences of lorem ipsum text',
generator: faker.lorem.sentences
},
$randomLoremParagraph: {
description: 'A random paragraph of lorem ipsum text',
generator: faker.lorem.paragraph
},
$randomLoremParagraphs: {
description: '3 random paragraphs of lorem ipsum text',
generator: faker.lorem.paragraphs
},
$randomLoremText: {
description: 'A random amount of lorem ipsum text',
generator: faker.lorem.text
},
$randomLoremLines: {
description: '1-5 random lines of lorem ipsum',
generator: faker.lorem.lines
},
$randomFirstName: {
description: 'A random first name',
generator: faker.name.firstName
},
$randomLastName: {
description: 'A random last name',
generator: faker.name.lastName
},
$randomFullName: {
description: 'A random first and last name',
generator: faker.name.findName
},
$randomJobTitle: {
description: 'A random job title (e.g. senior software developer)',
generator: faker.name.jobTitle
},
$randomNamePrefix: {
description: 'A random name prefix (e.g. Mr., Mrs., Dr.)',
generator: faker.name.prefix
},
$randomNameSuffix: {
description: 'A random name suffix (e.g. Jr., MD, PhD)',
generator: faker.name.suffix
},
$randomJobDescriptor: {
description: 'A random job descriptor (e.g., senior, chief, corporate, etc.)',
generator: faker.name.jobDescriptor
},
$randomJobArea: {
description: 'A random job area (e.g. branding, functionality, usability)',
generator: faker.name.jobArea
},
$randomJobType: {
description: 'A random job type (e.g. supervisor, manager, coordinator, etc.)',
generator: faker.name.jobType
},
$randomUUID: {
description: 'A random 36-character UUID',
generator: faker.datatype.uuid
},
$randomBoolean: {
description: 'A random boolean value (true/false)',
generator: faker.datatype.boolean
},
$randomWord: {
description: 'A random word',
generator: faker.random.word
},
$randomAlphaNumeric: {
description: 'A random alpha-numeric character',
generator: faker.random.alphaNumeric
},
$randomFileName: {
description: 'A random file name (includes uncommon extensions)',
generator: faker.system.fileName
},
$randomCommonFileName: {
description: 'A random file name',
generator: faker.system.commonFileName
},
$randomMimeType: {
description: 'A random MIME type',
generator: faker.system.mimeType
},
$randomCommonFileType: {
description: 'A random, common file type (e.g., video, text, image, etc.)',
generator: faker.system.commonFileType
},
$randomCommonFileExt: {
description: 'A random, common file extension (.doc, .jpg, etc.)',
generator: faker.system.commonFileExt
},
$randomFileType: {
description: 'A random file type (includes uncommon file types)',
generator: faker.system.fileType
},
$randomFileExt: {
description: 'A random file extension (includes uncommon extensions)',
generator: faker.system.fileExt
},
$randomSemver: {
description: 'A random semantic version number',
generator: faker.system.semver
}
};
module.exports = dynamicGenerators;

View File

@@ -0,0 +1,243 @@
var _ = require('../util').lodash,
dynamicVariables = require('./dynamic-variables'),
E = '',
SuperString, // constructor
Substitutor; // constructor
/**
* A string-like instance with additional functionalities to track string operations better
*
* @constructor
* @private
* @param {String} value -
*/
SuperString = function SuperString (value) {
this.value = _.isString(value) ? value : (_.isFunction(value.toString) && value.toString() || E);
/**
* The total number of times there was a successful substitution.
*
* @type {number}
*/
this.substitutions = 0;
/**
* Keeps a track of the number of tokens replaced in the last replace command
*
* @type {number}
*/
this.replacements = 0;
};
_.assign(SuperString.prototype, /** @lends SuperString.prototype */ {
/**
* Equivalent to string replace but performs additional tracking of the number of tokens replaced
*
* @param {RegExp|String} regex -
* @param {Function|String} fn -
* @returns {SuperString}
*/
replace (regex, fn) {
var replacements = 0; // maintain a count of tokens replaced
// to ensure we do not perform needless operations in the replacement, we use multiple replacement functions
// after validating the parameters
this.value = (this.value.replace(regex, _.isFunction(fn) ?
function () {
replacements += 1;
return fn.apply(this, arguments);
} :
// this case is returned when replacer is not a function (ensures we do not need to check it)
/* istanbul ignore next */
function () {
replacements += 1;
return fn;
})
);
this.replacements = replacements; // store the last replacements
replacements && (this.substitutions += 1); // if any replacement is done, count that some substitution was made
return this;
},
/**
* @returns {String}
*/
toString () {
return this.value;
},
/**
* @returns {String}
*/
valueOf () {
return this.value;
}
});
/**
* Perform replacement of tokens in a SuperString with values stored in keys of an array of objects.
*
* @constructor
* @private
* @param {Array} variables -
* @param {Object} defaults -
*/
Substitutor = function (variables, defaults) {
defaults && variables.push(defaults);
this.variables = variables;
};
_.assign(Substitutor.prototype, /** @lends Substitutor.prototype */ {
/**
* Find a key from the array of variable objects provided to the substitutor
*
* @param {String} key -
* @returns {*}
*/
find (key) {
var arr = this.variables,
obj,
value,
i,
ii;
for (i = 0, ii = arr.length; i < ii; i++) {
obj = arr[i];
// ensure that the item is an object
if (!(obj && _.isObject(obj))) {
continue;
}
// in case the object is a postman variable list, we give special attention
if (obj.constructor._postman_propertyName === 'VariableList') {
value = obj.oneNormalizedVariable(key);
if (value && !value.disabled) {
return value;
}
}
// else we return the value from the plain object
else if (_.has(obj, key)) {
return obj[key];
}
}
},
/**
* @param {String} value -
* @returns {String}
*/
parse (value) {
// convert the value into a SuperString so that it can return tracking results during replacements
value = new SuperString(value);
// get an instance of a replacer function that would be used to replace ejs like variable replacement
// tokens
var replacer = Substitutor.replacer(this);
// replace the value once and keep on doing it until all tokens are replaced or we have reached a limit of
// replacements
do {
value = value.replace(Substitutor.REGEX_EXTRACT_VARS, replacer);
} while (value.replacements && (value.substitutions < Substitutor.VARS_SUBREPLACE_LIMIT));
// @todo: uncomment this code, and try to raise a warning in some way.
// do a final check that if recursion limits are reached then replace with blank string
// if (value.substitutions >= Substitutor.VARS_SUBREPLACE_LIMIT) {
// value = value.replace(Substitutor.REGEX_EXTRACT_VARS, E);
// }
return value;
}
});
_.assign(Substitutor, /** @lends Substitutor */ {
/**
* Regular expression to be used in {String}.replace for extracting variable substitutions
*
* @readOnly
* @type {RegExp}
*/
REGEX_EXTRACT_VARS: /\{\{([^{}]*?)}}/g,
/**
* Defines the number of times the variable substitution mechanism will repeat until all tokens are resolved
*
* @type {Number}
*/
VARS_SUBREPLACE_LIMIT: 19,
/**
* Maintain a list of types that are native
*
* @readOnly
* @enum {String}
*/
NATIVETYPES: {
string: true,
number: true,
boolean: true
},
/**
* Holds the default variables that Postman supports.
*
* @type {Object}
*/
DEFAULT_VARS: {},
/**
* Create an instance of a substitutor or reuse one
*
* @param {Array|Substitutor} variables -
* @param {Object=} defaults An object containing default variables to substitute
* @returns {Substitutor}
*/
box: function (variables, defaults) {
return (variables instanceof Substitutor) ? variables : new Substitutor(variables, defaults);
},
/**
* Checks whether a variable is instance of substitutor
*
* @param {*} subject -
* @returns {Boolean}
*/
isInstance: function (subject) {
return (subject instanceof Substitutor);
},
/**
* Get an instance of a function that is useful to be passed to a string replace function for extracting tokens
* and replacing by substitutions
*
* @private
* @param {Substitutor} substitutor -
* @returns {Function}
*/
replacer: function (substitutor) {
return function (match, token) {
var r = substitutor.find(token);
r && _.isFunction(r) && (r = r());
r && _.isFunction(r.toString) && (r = r.toString());
return Substitutor.NATIVETYPES[(typeof r)] ? r : match;
};
}
});
// @todo make the default variables of SuperString extensible and do this anywhere else but here
_.forOwn(dynamicVariables, function (variable, name) {
Substitutor.DEFAULT_VARS[name] = variable.generator;
});
module.exports = {
SuperString,
Substitutor
};

View File

@@ -0,0 +1,102 @@
var _ = require('../util').lodash,
PropertyList = require('../collection/property-list').PropertyList,
Url = require('../collection/url').Url,
UrlMatchPattern = require('./url-match-pattern').UrlMatchPattern,
MATCH_ALL_URLS = UrlMatchPattern.MATCH_ALL_URLS,
UrlMatchPatternList;
_.inherit((
/**
* UrlMatchPattern is a list of UrlMatchPatterns.
* This allows you to test for any url over a list of match patterns.
*
* @constructor
* @extends {PropertyList}
*
* @param {Object} parent -
* @param {String[]} list -
* @example <caption>An example UrlMatchPatternList</caption>
* var matchPatternList = new UrlMatchPatternList(['https://*.google.com/*']);
*/
UrlMatchPatternList = function (parent, list) {
UrlMatchPatternList.super_.call(this, UrlMatchPattern, parent, list);
}), PropertyList);
_.assign(UrlMatchPatternList.prototype, /** @lends UrlMatchPatternList.prototype */ {
/**
* Allows this property to be serialised into its plural form.
* This is here because Property.prototype.toJSON() tries to singularise
* the keys which are PropertyLists.
* i.e. when a property has a key - `matches = new PropertyList()`,
* toJSON on the property tries to singularise 'matches' and ends up with 'matche'.
*
* @private
* @readOnly
* @type {String}
*/
_postman_proprtyIsSerialisedAsPlural: true,
/**
* Tests the url string with the match pattern list provided to see if it matches any of it.
* Follows the https://developer.chrome.com/extensions/match_patterns pattern for pattern validation and matching
*
* @param {String=} [urlStr] The url string for which the proxy match needs to be done.
* @returns {Boolean=}
*/
test: function (urlStr) {
/*
* Similar to the UrlMatchPattern.test, however instead of testing
* MATCH_ALL_URLS and Regex conditions serially with each of the pattern,
* this method first searches for MATCH_ALL_URLS in all patterns
* and then moves on to the slower Regex based searches.
*/
var url,
matchAllUrlsPattern,
matchedSpecificPattern;
matchAllUrlsPattern = this.find(function (urlMatchPattern) {
return urlMatchPattern.pattern === MATCH_ALL_URLS;
});
if (_.isObject(matchAllUrlsPattern)) {
return true;
}
url = new Url(urlStr);
matchedSpecificPattern = this.find(function (urlMatchPattern) {
var matchRegexObject = urlMatchPattern._matchPatternObject;
// Empty matchRegexObject represents the match is INVALID match
if (_.isEmpty(matchRegexObject)) {
return false;
}
return (urlMatchPattern.testProtocol(url.protocol) &&
urlMatchPattern.testHost(url.getHost()) &&
urlMatchPattern.testPort(url.port, url.protocol) &&
urlMatchPattern.testPath(url.getPath()));
});
return Boolean(matchedSpecificPattern);
}
});
_.assign(UrlMatchPatternList, /** @lends UrlMatchPatternList */ {
/**
* Defines the name of this property for internal use
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'UrlMatchPatternList'
});
module.exports = {
UrlMatchPatternList
};

View File

@@ -0,0 +1,348 @@
var _ = require('../util').lodash,
Property = require('../collection/property').Property,
Url = require('../collection/url').Url,
STRING = 'string',
UNDEFINED = 'undefined',
MATCH_ALL = '*',
PREFIX_DELIMITER = '^',
PROTOCOL_DELIMITER = '+',
POSTFIX_DELIMITER = '$',
MATCH_ALL_URLS = '<all_urls>',
ALLOWED_PROTOCOLS = ['http', 'https', 'file', 'ftp'],
ALLOWED_PROTOCOLS_REGEX = ALLOWED_PROTOCOLS.join('|'),
// @todo initialize this and ALLOWED_PROTOCOLS via UrlMatchPattern options
DEFAULT_PROTOCOL_PORT = {
ftp: '21',
http: '80',
https: '443'
},
regexes = {
escapeMatcher: /[.+^${}()|[\]\\]/g,
escapeMatchReplacement: '\\$&',
questionmarkMatcher: /\?/g,
questionmarkReplacment: '.',
starMatcher: '*',
starReplacement: '.*',
// @todo match valid HOST name
// @note PATH is required(can be empty '/' or '/*') i.e, {PROTOCOL}://{HOST}/
patternSplit: '^((' + ALLOWED_PROTOCOLS_REGEX + '|\\*)(\\+(' + ALLOWED_PROTOCOLS_REGEX +
'))*)://(\\*|\\*\\.[^*/:]+|[^*/:]+)(:\\*|:\\d+)?(/.*)$'
},
UrlMatchPattern;
/**
* @typedef UrlMatchPattern.definition
* @property {String} pattern The url match pattern string
*/
_.inherit((
/**
* UrlMatchPattern allows to create rules to define Urls to match for.
* It is based on Google's Match Pattern - https://developer.chrome.com/extensions/match_patterns
*
* @constructor
* @extends {Property}
* @param {UrlMatchPattern.definition} options -
*
* @example <caption>An example UrlMatchPattern</caption>
* var matchPattern = new UrlMatchPattern('https://*.google.com/*');
*/
UrlMatchPattern = function UrlMatchPattern (options) {
// called as new UrlMatchPattern('https+http://*.example.com/*')
if (_.isString(options)) {
options = { pattern: options };
}
// this constructor is intended to inherit and as such the super constructor is required to be executed
UrlMatchPattern.super_.apply(this, arguments);
// Assign defaults before proceeding
_.assign(this, /** @lends UrlMatchPattern */ {
/**
* The url match pattern string
*
* @type {String}
*/
pattern: MATCH_ALL_URLS
});
this.update(options);
}), Property);
_.assign(UrlMatchPattern.prototype, /** @lends UrlMatchPattern.prototype */ {
/**
* Assigns the given properties to the UrlMatchPattern.
*
* @param {{ pattern: (string) }} options -
*/
update (options) {
_.has(options, 'pattern') && (_.isString(options.pattern) && !_.isEmpty(options.pattern)) &&
(this.pattern = options.pattern);
// create a match pattern and store it on cache
this._matchPatternObject = this.createMatchPattern();
},
/**
* Used to generate the match regex object from the match string we have.
*
* @private
* @returns {*} Match regex object
*/
createMatchPattern () {
var matchPattern = this.pattern,
// Check the match pattern of sanity and split it into protocol, host and path
match = matchPattern.match(regexes.patternSplit);
if (!match) {
// This ensures it is a invalid match pattern
return;
}
return {
protocols: _.uniq(match[1].split(PROTOCOL_DELIMITER)),
host: match[5],
port: match[6] && match[6].substr(1), // remove leading `:`
path: this.globPatternToRegexp(match[7])
};
},
/**
* Converts a given glob pattern into a regular expression.
*
* @private
* @param {String} pattern Glob pattern string
* @returns {RegExp=}
*/
globPatternToRegexp (pattern) {
// Escape everything except ? and *.
pattern = pattern.replace(regexes.escapeMatcher, regexes.escapeMatchReplacement);
pattern = pattern.replace(regexes.questionmarkMatcher, regexes.questionmarkReplacment);
pattern = pattern.replace(regexes.starMatcher, regexes.starReplacement);
// eslint-disable-next-line security/detect-non-literal-regexp
return new RegExp(PREFIX_DELIMITER + pattern + POSTFIX_DELIMITER);
},
/**
* Tests if the given protocol string, is allowed by the pattern.
*
* @param {String=} protocol The protocol to be checked if the pattern allows.
* @returns {Boolean=}
*/
testProtocol (protocol) {
var matchRegexObject = this._matchPatternObject;
return _.includes(ALLOWED_PROTOCOLS, protocol) &&
(_.includes(matchRegexObject.protocols, MATCH_ALL) || _.includes(matchRegexObject.protocols, protocol));
},
/**
* Returns the protocols supported
*
* @returns {Array.<String>}
*/
getProtocols () {
return _.get(this, '_matchPatternObject.protocols') || [];
},
/**
* Tests if the given host string, is allowed by the pattern.
*
* @param {String=} host The host to be checked if the pattern allows.
* @returns {Boolean=}
*/
testHost (host) {
/*
* For Host match, we are considering the port with the host, hence we are using getRemote() instead of getHost()
* We need to address three cases for the host urlStr
* 1. * It matches all the host + protocol, hence we are not having any parsing logic for it.
* 2. *.foo.bar.com Here the prefix could be anything but it should end with foo.bar.com
* 3. foo.bar.com This is the absolute matching needs to done.
*/
var matchRegexObject = this._matchPatternObject;
return (
this.matchAnyHost(matchRegexObject) ||
this.matchAbsoluteHostPattern(matchRegexObject, host) ||
this.matchSuffixHostPattern(matchRegexObject, host)
);
},
/**
* Checks whether the matchRegexObject has the MATCH_ALL host.
*
* @private
* @param {Object=} matchRegexObject The regex object generated by the createMatchPattern function.
* @returns {Boolean}
*/
matchAnyHost (matchRegexObject) {
return matchRegexObject.host === MATCH_ALL;
},
/**
* Check for the (*.foo.bar.com) kind of matches with the remote provided.
*
* @private
* @param {Object=} matchRegexObject The regex object generated by the createMatchPattern function.
* @param {String=} remote The remote url (host+port) of the url for which the hostpattern needs to checked
* @returns {Boolean}
*/
matchSuffixHostPattern (matchRegexObject, remote) {
var hostSuffix = matchRegexObject.host.substr(2);
return matchRegexObject.host[0] === MATCH_ALL && (remote === hostSuffix || remote.endsWith('.' + hostSuffix));
},
/**
* Check for the absolute host match.
*
* @private
* @param {Object=} matchRegexObject The regex object generated by the createMatchPattern function.
* @param {String=} remote The remote url, host+port of the url for which the hostpattern needs to checked
* @returns {Boolean}
*/
matchAbsoluteHostPattern (matchRegexObject, remote) {
return matchRegexObject.host === remote;
},
/**
* Tests if the current pattern allows the given port.
*
* @param {String} port The port to be checked if the pattern allows.
* @param {String} protocol Protocol to refer default port.
* @returns {Boolean}
*/
testPort (port, protocol) {
var portRegex = this._matchPatternObject.port,
// default port for given protocol
defaultPort = protocol && DEFAULT_PROTOCOL_PORT[protocol];
// return true if both given port and match pattern are absent
if (typeof port === UNDEFINED && typeof portRegex === UNDEFINED) {
return true;
}
// convert integer port to string
(port && typeof port !== STRING) && (port = String(port));
// assign default port or portRegex
!port && (port = defaultPort);
!portRegex && (portRegex = defaultPort);
// matches * or specific port
return (
portRegex === MATCH_ALL ||
portRegex === port
);
},
/**
* Tests if the current pattern allows the given path.
*
* @param {String=} path The path to be checked if the pattern allows.
* @returns {Boolean=}
*/
testPath (path) {
var matchRegexObject = this._matchPatternObject;
return !_.isEmpty(path.match(matchRegexObject.path));
},
/**
* Tests the url string with the match pattern provided.
* Follows the https://developer.chrome.com/extensions/match_patterns pattern for pattern validation and matching
*
* @param {String=} urlStr The url string for which the proxy match needs to be done.
* @returns {Boolean=}
*/
test (urlStr) {
/*
* This function executes the code in the following sequence for early return avoiding the costly regex matches.
* To avoid most of the memory consuming code.
* 1. It check whether the match string is <all_urls> in that case, it return immediately without any further
* processing.
* 2. Checks whether the matchPattern follows the rules, https://developer.chrome.com/extensions/match_patterns,
* If not then, don't process it.
* 3. Check for the protocol, as it is a normal array check.
* 4. Checks the host, as it doesn't involve regex match and has only string comparisons.
* 5. Finally, checks for the path, which actually involves the Regex matching, the slow process.
*/
// If the matchPattern is <all_urls> then there is no need for any validations.
if (this.pattern === MATCH_ALL_URLS) {
return true;
}
// Empty _matchPatternObject represents the match is INVALID match
if (_.isEmpty(this._matchPatternObject)) {
return false;
}
const url = new Url(urlStr);
return (this.testProtocol(url.protocol) &&
this.testHost(url.getHost()) &&
this.testPort(url.port, url.protocol) &&
this.testPath(url.getPath()));
},
/**
* Returns a string representation of the match pattern
*
* @returns {String} pattern
*/
toString () {
return String(this.pattern);
},
/**
* Returns the JSON representation.
*
* @returns {{ pattern: (String) }}
*/
toJSON () {
var pattern;
pattern = this.toString();
return { pattern };
}
});
_.assign(UrlMatchPattern, /** @lends UrlMatchPattern */ {
/**
* Defines the name of this property for internal use
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'UrlMatchPattern',
/**
* Multiple protocols in the match pattern should be separated by this string
*
* @readOnly
* @type {String}
*/
PROTOCOL_DELIMITER: PROTOCOL_DELIMITER,
/**
* String representation for matching all urls - <all_urls>
*
* @readOnly
* @type {String}
*/
MATCH_ALL_URLS: MATCH_ALL_URLS
});
module.exports = {
UrlMatchPattern
};

299
node_modules/postman-collection/lib/util.js generated vendored Normal file
View File

@@ -0,0 +1,299 @@
var _ = require('lodash').noConflict(),
iconvlite = require('iconv-lite'),
util,
ASCII_SOURCE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
ASCII_SOURCE_LENGTH = ASCII_SOURCE.length,
EMPTY = '';
/**
* @module util
* @private
*/
_.mixin(/** @lends util */ {
/**
* Creates an inheritance relation between the child and the parent, adding a 'super_' attribute to the
* child, and setting up the child prototype.
*
* @param {Function} child - The target object to create parent references for,.
* @param {Function} base - The parent association to assign to the provided child definition.
* @returns {*}
*/
inherit (child, base) {
Object.defineProperty(child, 'super_', {
value: _.isFunction(base) ? base : _.noop,
configurable: false,
enumerable: false,
writable: false
});
child.prototype = Object.create((_.isFunction(base) ? base.prototype : base), {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
return child;
},
/**
* Creates an array from a Javascript "arguments" object.
* https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/arguments
*
* @param {Array} args -
* @returns {Array.<T>}
*/
args (args) {
return Array.prototype.slice.call(args);
},
/**
* Makes sure the given string is encoded only once.
*
* @param {String} string -
* @returns {String}
*/
ensureEncoded (string) {
// Takes care of the case where the string is already encoded.
try {
string = decodeURIComponent(string);
}
catch (e) {} // eslint-disable-line no-empty
try {
return encodeURIComponent(string);
}
// handle malformed URI sequence
// refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Malformed_URI
catch (error) {
return string;
}
},
/**
* Creates a locked property on an object, which is not writable or enumerable.
*
* @param {Object} obj -
* @param {String} name -
* @param {*} prop -
* @returns {*}
*/
assignLocked (obj, name, prop) {
Object.defineProperty(obj, name, {
value: prop,
configurable: false,
enumerable: false,
writable: false
});
return obj;
},
/**
* Creates a hidden property on an object, which can be changed, but is not enumerable.
*
* @param {Object} obj -
* @param {String} name -
* @param {*} prop -
* @returns {*}
*/
assignHidden (obj, name, prop) {
Object.defineProperty(obj, name, {
value: prop,
configurable: true,
enumerable: false,
writable: true
});
return obj;
},
/**
* Creates a property on an object, with the given type.
*
* @param {Object} obj -
* @param {String} name -
* @param {Property} Prop -
* @param {*} [fallback] -
* @returns {Prop|undefined}
*/
createDefined (obj, name, Prop, fallback) {
return _.has(obj, name) ? (new Prop(obj[name])) : fallback;
},
/**
* Merges defined keys from the target object onto the source object.
*
* @param {Object} target -
* @param {Object} source -
* @returns {Object}
*/
mergeDefined (target, source) {
var key;
for (key in source) {
if (_.has(source, key) && !_.isUndefined(source[key])) {
target[key] = source[key];
}
}
return target;
},
/**
* Returns the value of a property if defined in object, else the default
*
* @param {Object} obj -
* @param {String} prop -
* @param {*=} def -
*
* @returns {*}
*/
getOwn (obj, prop, def) {
return _.has(obj, prop) ? obj[prop] : def;
},
/**
* Creates a clone of an object, but uses the toJSON method if available.
*
* @param {Object} obj -
* @returns {*}
*/
cloneElement (obj) {
return _.cloneDeepWith(obj, function (value) {
// falls back to default deepclone if object does not have explicit toJSON().
if (value && _.isFunction(value.toJSON)) {
return value.toJSON();
}
});
},
/**
* Returns the match of a value of a property by traversing the prototype
*
* @param {Object} obj -
* @param {String} key -
* @param {*} value -
*
* @returns {Boolean}
*/
inSuperChain (obj, key, value) {
return obj ? ((obj[key] === value) || _.inSuperChain(obj.super_, key, value)) : false;
},
/**
* Generates a random string of given length (useful for nonce generation, etc).
*
* @param {Number} length -
*/
randomString (length) {
length = length || 6;
var result = [],
i;
for (i = 0; i < length; i++) {
result[i] = ASCII_SOURCE[(Math.random() * ASCII_SOURCE_LENGTH) | 0];
}
return result.join(EMPTY);
},
choose () {
for (var i = 0, ii = arguments.length; i < ii; i++) {
if (!_.isEmpty(arguments[i])) {
return arguments[i];
}
}
}
});
util = {
lodash: _,
/**
*
* @param {String} data -
* @returns {String} [description]
*/
btoa: (/* istanbul ignore next */
(typeof btoa !== 'function' && typeof Buffer === 'function') ? function (data) {
return Buffer.from(data).toString('base64');
} : function (data) {
return btoa(data);
}
), // @todo use browserify to normalise this
/**
* ArrayBuffer to String
*
* @param {ArrayBuffer} buffer -
* @returns {String}
*/
arrayBufferToString: function (buffer) {
var str = '',
uArrayVal = new Uint8Array(buffer),
i,
ii;
for (i = 0, ii = uArrayVal.length; i < ii; i++) {
str += String.fromCharCode(uArrayVal[i]);
}
return str;
},
bufferOrArrayBufferToString: function (buffer, charset) {
if (!buffer || _.isString(buffer)) {
return buffer || '';
}
if (Buffer.isBuffer(buffer)) {
if (iconvlite.encodingExists(charset)) {
return iconvlite.decode(buffer, charset);
}
return buffer.toString(); // Default to utf8 if iconvlite also not supporting the charset
}
return util.arrayBufferToString(buffer);
},
bufferOrArrayBufferToBase64: function (buffer) {
if (!buffer) {
return '';
}
// handle when buffer is pure string
if (_.isString(buffer)) {
return util.btoa(buffer);
}
// check if tostring works
var base64 = buffer.toString('base64') || '';
if (base64 === '[object ArrayBuffer]') {
return util.btoa(util.arrayBufferToString(buffer));
}
return base64;
},
/**
* Check whether a value is number-like
* https://github.com/lodash/lodash/issues/1148#issuecomment-141139153
*
* @param {*} n - The candidate to be checked for numeric compliance.
* @returns {Boolean}
*/
isNumeric: function (n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
};
module.exports = util;

82
node_modules/postman-collection/package.json generated vendored Normal file
View File

@@ -0,0 +1,82 @@
{
"name": "postman-collection",
"version": "4.1.0",
"description": "Enables developers to use a unified Postman Collection format Object across projects",
"author": "Postman Inc.",
"license": "Apache-2.0",
"main": "index.js",
"homepage": "https://github.com/postmanlabs/postman-collection#readme",
"bugs": {
"url": "https://github.com/postmanlabs/postman-collection/issues",
"email": "help@postman.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/postmanlabs/postman-collection.git"
},
"keywords": [
"postman",
"collection",
"sdk"
],
"scripts": {
"build-docs": "node npm/build-docs.js",
"build-types": "node npm/build-types.js",
"codecov": "node npm/publish-coverage.js",
"release": "node npm/create-release.js",
"test": "npm run test-lint && npm run test-system && npm run test-unit && npm run test-browser",
"test-benchmark": "node npm/test-benchmark.js",
"test-browser": "node npm/test-browser.js",
"test-lint": "node npm/test-lint.js",
"test-system": "node npm/test-system.js",
"test-unit": "nyc --nycrc-path=.nycrc.js node npm/test-unit.js"
},
"dependencies": {
"faker": "5.5.3",
"file-type": "3.9.0",
"http-reasons": "0.1.0",
"iconv-lite": "0.6.3",
"liquid-json": "0.3.1",
"lodash": "4.17.21",
"mime-format": "2.0.1",
"mime-types": "2.1.32",
"postman-url-encoder": "3.0.5",
"semver": "7.3.5",
"uuid": "8.3.2"
},
"devDependencies": {
"@postman/shipit": "^0.3.0",
"async": "^3.2.1",
"bipbip": "^0.4.2",
"browserify": "^17.0.0",
"btoa": "^1.2.1",
"chai": "^4.3.4",
"chalk": "^4.1.2",
"dependency-check": "^4.1.0",
"eslint": "^7.32.0",
"eslint-plugin-jsdoc": "^36.0.7",
"eslint-plugin-lodash": "^7.2.0",
"eslint-plugin-mocha": "^9.0.0",
"eslint-plugin-security": "^1.4.0",
"js-yaml": "^4.1.0",
"jsdoc": "^3.6.7",
"karma": "^6.3.4",
"karma-browserify": "^8.1.0",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"mocha": "^9.0.3",
"nyc": "^15.1.0",
"parse-gitignore": "^1.0.1",
"postman-jsdoc-theme": "^0.0.3",
"postman-request": "^2.88.1-postman.30",
"recursive-readdir": "^2.2.2",
"require-all": "^3.0.0",
"shelljs": "^0.8.4",
"strip-json-comments": "^3.1.1",
"tsd-jsdoc": "^2.5.0"
},
"engines": {
"node": ">=10"
}
}

2652
node_modules/postman-collection/types/index.d.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff